Add proper impl of undo/redo.

This commit is contained in:
Andrey Antukh 2016-07-31 15:11:53 +03:00
parent 675f04ff9c
commit 56d64cf737
No known key found for this signature in database
GPG key ID: 4DFEBCB8316A8B95
4 changed files with 143 additions and 38 deletions

View file

@ -55,8 +55,7 @@
(let [data (:data page) (let [data (:data page)
shapes (:shapes data) shapes (:shapes data)
shapes-by-id (:shapes-by-id data) shapes-by-id (:shapes-by-id data)
page (-> page page (-> (dissoc page :data)
(dissoc page :data)
(assoc :shapes shapes))] (assoc :shapes shapes))]
(-> state (-> state
(update :shapes-by-id merge shapes-by-id) (update :shapes-by-id merge shapes-by-id)

View file

@ -5,32 +5,19 @@
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz> ;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.data.undo (ns uxbox.main.data.undo
(:require [cuerdas.core :as str] (:require #_[cljs.pprint :as pp]
[promesa.core :as p]
[beicon.core :as rx] [beicon.core :as rx]
[lentes.core :as l]
[uxbox.util.rstore :as rs] [uxbox.util.rstore :as rs]
[uxbox.util.router :as r]
[uxbox.main.repo :as rp]
[uxbox.util.i18n :refer (tr)]
[uxbox.util.schema :as sc]
[uxbox.main.data.pages :as udp] [uxbox.main.data.pages :as udp]
[uxbox.main.state :as st] [uxbox.main.state :as st]))
[uxbox.util.datetime :as dt]
[uxbox.util.data :refer (without-keys
replace-by-id
index-by)]))
(defrecord SaveUndoEntry [id] ;; --- Watch Page Changes
rs/UpdateEvent
(-apply-update [_ state]
(let [page (udp/pack-page state id)]
(update-in state [:undo id :stack] (fnil conj []) page)))
rs/EffectEvent (declare save-undo-entry)
(-apply-effect [_ state] (declare save-undo-entry?)
(let [undo (get-in state [:undo id])] (declare undo?)
(println (pr-str undo))))) (declare redo?)
(declare initialize-undo-for-page)
(defn watch-page-changes (defn watch-page-changes
"A function that starts watching for `IPageUpdate` "A function that starts watching for `IPageUpdate`
@ -38,10 +25,123 @@
reacts on them emiting an other event that just reacts on them emiting an other event that just
persists the state of the page in an undo stack." persists the state of the page in an undo stack."
[id] [id]
(letfn [(on-value [] (rs/emit! (initialize-undo-for-page id))
(rs/emit! (->SaveUndoEntry id)))] (as-> rs/stream $
(as-> rs/stream $ (rx/filter #(satisfies? udp/IPageUpdate %) $)
(rx/filter #(satisfies? udp/IPageUpdate %) $) (rx/filter #(not (undo? %)) $)
(rx/debounce 500 $) (rx/filter #(not (redo? %)) $)
(rx/on-next $ on-value)))) (rx/debounce 500 $)
(rx/on-next $ #(rs/emit! (save-undo-entry id)))))
;; -- Save Undo Entry
(defrecord SaveUndoEntry [id]
rs/UpdateEvent
(-apply-update [_ state]
(let [page (udp/pack-page state id)]
(-> state
(update-in [:undo id :stack] #(cons (:data page) %))
(assoc-in [:undo id :selected] 0)))))
;; rs/EffectEvent
;; (-apply-effect [_ state]
;; (let [undo (get-in state [:undo id])]
;; (println (pr-str undo)))))
(defn save-undo-entry
[id]
(SaveUndoEntry. id))
(defn save-undo-entry?
[v]
(instance? SaveUndoEntry v))
;; --- Initialize Undo (For page)
(defrecord InitializeUndoForPage [id]
rs/WatchEvent
(-apply-watch [_ state stream]
(let [initialized? (get-in state [:undo id])
page-loaded? (get-in state [:pages-by-id id])]
(cond
(and page-loaded? initialized?)
(rx/empty)
page-loaded?
(rx/of (save-undo-entry id))
:else
(->> stream
(rx/filter udp/pages-fetched?)
(rx/take 1)
(rx/map #(initialize-undo-for-page id)))))))
(defn- initialize-undo-for-page
[id]
(InitializeUndoForPage. id))
;; --- Select Previous Entry
(defrecord Undo []
udp/IPageUpdate
rs/UpdateEvent
(-apply-update [_ state]
(let [page-id (get-in state [:workspace :page])
undo (get-in state [:undo page-id])
stack (:stack undo)
selected (:selected undo 0)]
(if (>= selected (dec (count stack)))
state
(let [pointer (inc selected)
page (get-in state [:pages-by-id page-id])
data (nth stack pointer)
packed (assoc page :data data)]
;; (println "Undo: pointer=" pointer)
;; (println "Undo: packed=")
;; (pp/pprint packed)
(-> state
(udp/unpack-page packed)
(assoc-in [:undo page-id :selected] pointer)))))))
(defn undo
[]
(Undo.))
(defn undo?
[v]
(instance? Undo v))
;; --- Select Next Entry
(defrecord Redo []
udp/IPageUpdate
rs/UpdateEvent
(-apply-update [_ state]
(let [page-id (get-in state [:workspace :page])
undo (get-in state [:undo page-id])
stack (:stack undo)
selected (:selected undo)]
(if (or (nil? selected) (zero? selected))
state
(let [pointer (dec selected)
data (nth stack pointer)
page (get-in state [:pages-by-id page-id])
packed (assoc page :data data)]
;; (println "Redo: pointer=" pointer)
;; (println "Redo: packed=")
;; (pp/pprint packed)
(-> state
(udp/unpack-page packed)
(assoc-in [:undo page-id :selected] pointer)))))))
(defn redo
[]
(Redo.))
(defn redo?
[v]
(instance? Redo v))

View file

@ -47,8 +47,8 @@
(let [[projectid pageid] (:rum/args own) (let [[projectid pageid] (:rum/args own)
sub1 (scroll/watch-scroll-interactions own) sub1 (scroll/watch-scroll-interactions own)
sub2 (udp/watch-page-changes pageid) sub2 (udp/watch-page-changes pageid)
sub3 (udh/watch-page-changes) sub3 (udu/watch-page-changes pageid)
sub4 (udu/watch-page-changes pageid) sub4 (udh/watch-page-changes)
dom (mx/ref-node own "workspace-canvas")] dom (mx/ref-node own "workspace-canvas")]
;; Set initial scroll position ;; Set initial scroll position
@ -78,12 +78,17 @@
(do (do
(rs/emit! (dw/initialize projectid pageid)) (rs/emit! (dw/initialize projectid pageid))
(.close (::sub2 old-state)) (.close (::sub2 old-state))
(.close (::sub3 old-state))
(assoc state (assoc state
::sub1 (::sub1 old-state) ::sub1 (::sub1 old-state)
::sub2 (udp/watch-page-changes pageid))) ::sub2 (udp/watch-page-changes pageid)
::sub3 (udu/watch-page-changes pageid)
::sub4 (::sub4 old-state)))
(assoc state (assoc state
::sub1 (::sub1 old-state) ::sub1 (::sub1 old-state)
::sub2 (::sub2 old-state))))) ::sub2 (::sub2 old-state)
::sub3 (::sub3 old-state)
::sub4 (::sub4 old-state)))))
(defn- on-scroll (defn- on-scroll
[event] [event]

View file

@ -12,8 +12,9 @@
[uxbox.main.data.lightbox :as udl] [uxbox.main.data.lightbox :as udl]
[uxbox.main.data.workspace :as dw] [uxbox.main.data.workspace :as dw]
[uxbox.main.data.shapes :as uds] [uxbox.main.data.shapes :as uds]
[uxbox.main.ui.workspace.sidebar.drawtools :as wsd] [uxbox.main.data.undo :as udu]
[uxbox.main.data.history :as udh]) [uxbox.main.data.history :as udh]
[uxbox.main.ui.workspace.sidebar.drawtools :as wsd])
(:import goog.events.EventType (:import goog.events.EventType
goog.events.KeyCodes goog.events.KeyCodes
goog.ui.KeyboardShortcutHandler goog.ui.KeyboardShortcutHandler
@ -36,13 +37,13 @@
:ctrl+d #(rs/emit! (uds/duplicate-selected)) :ctrl+d #(rs/emit! (uds/duplicate-selected))
:ctrl+c #(rs/emit! (dw/copy-to-clipboard)) :ctrl+c #(rs/emit! (dw/copy-to-clipboard))
:ctrl+v #(rs/emit! (dw/paste-from-clipboard)) :ctrl+v #(rs/emit! (dw/paste-from-clipboard))
:ctrl+z #(rs/emit! (udh/backwards-to-previous-version)) :ctrl+shift+v #(udl/open! :clipboard)
:ctrl+z #(rs/emit! (udu/undo))
:ctrl+shift+z #(rs/emit! (udu/redo))
:ctrl+b #(rs/emit! (dw/select-for-drawing wsd/+draw-tool-rect+)) :ctrl+b #(rs/emit! (dw/select-for-drawing wsd/+draw-tool-rect+))
:ctrl+e #(rs/emit! (dw/select-for-drawing wsd/+draw-tool-circle+)) :ctrl+e #(rs/emit! (dw/select-for-drawing wsd/+draw-tool-circle+))
:ctrl+l #(rs/emit! (dw/select-for-drawing wsd/+draw-tool-line+)) :ctrl+l #(rs/emit! (dw/select-for-drawing wsd/+draw-tool-line+))
:ctrl+t #(rs/emit! (dw/select-for-drawing wsd/+draw-tool-text+)) :ctrl+t #(rs/emit! (dw/select-for-drawing wsd/+draw-tool-text+))
:ctrl+shift+z #(rs/emit! (udh/forward-to-next-version))
:ctrl+shift+v #(udl/open! :clipboard)
:esc #(rs/emit! (uds/deselect-all)) :esc #(rs/emit! (uds/deselect-all))
:backspace #(rs/emit! (uds/delete-selected)) :backspace #(rs/emit! (uds/delete-selected))
:delete #(rs/emit! (uds/delete-selected)) :delete #(rs/emit! (uds/delete-selected))