Files
penpot/frontend/src/app/main/data/workspace/path/undo.cljs
alonso.torres 65ad46ab38 Shortcuts for paths
2021-04-22 14:08:12 +02:00

163 lines
5.5 KiB
Clojure

;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) UXBOX Labs SL
(ns app.main.data.workspace.path.undo
(:require
[app.common.data :as d]
[app.common.data.undo-stack :as u]
[app.common.uuid :as uuid]
[app.main.data.workspace.path.state :as st]
[app.main.data.workspace.path.changes :as changes]
[app.main.store :as store]
[beicon.core :as rx]
[okulary.core :as l]
[potok.core :as ptk]))
(defn undo-event?
[event]
(= :app.main.data.workspace.common/undo (ptk/type event)))
(defn redo-event?
[event]
(= :app.main.data.workspace.common/redo (ptk/type event)))
(defn- make-entry [state]
(let [id (st/get-path-id state)]
{:content (get-in state (st/get-path state :content))
:selrect (get-in state (st/get-path state :selrect))
:points (get-in state (st/get-path state :points))
:preview (get-in state [:workspace-local :edit-path id :preview])
:last-point (get-in state [:workspace-local :edit-path id :last-point])
:prev-handler (get-in state [:workspace-local :edit-path id :prev-handler])}))
(defn- load-entry [state {:keys [content selrect points preview last-point prev-handler]}]
(let [id (st/get-path-id state)
old-content (get-in state (st/get-path state :content))]
(-> state
(d/assoc-in-when (st/get-path state :content) content)
(d/assoc-in-when (st/get-path state :selrect) selrect)
(d/assoc-in-when (st/get-path state :points) points)
(d/update-in-when
[:workspace-local :edit-path id]
assoc
:preview preview
:last-point last-point
:prev-handler prev-handler
:old-content old-content))))
(defn undo-path []
(ptk/reify ::undo-path
ptk/UpdateEvent
(update [_ state]
(let [id (st/get-path-id state)
undo-stack (-> (get-in state [:workspace-local :edit-path id :undo-stack])
(u/undo))
entry (u/peek undo-stack)]
(cond-> state
(some? entry)
(-> (load-entry entry)
(d/assoc-in-when
[:workspace-local :edit-path id :undo-stack]
undo-stack)))))
ptk/WatchEvent
(watch [_ state stream]
(rx/of (changes/save-path-content {:preserve-move-to true})))))
(defn redo-path []
(ptk/reify ::redo-path
ptk/UpdateEvent
(update [_ state]
(let [id (st/get-path-id state)
undo-stack (-> (get-in state [:workspace-local :edit-path id :undo-stack])
(u/redo))
entry (u/peek undo-stack)]
(-> state
(load-entry entry)
(d/assoc-in-when
[:workspace-local :edit-path id :undo-stack]
undo-stack))))
ptk/WatchEvent
(watch [_ state stream]
(rx/of (changes/save-path-content)))))
(defn merge-head
"Joins the head with the previous undo in one. This is done so when the user changes a
node handlers after adding it the undo merges both in one operation only"
[]
(ptk/reify ::add-undo-entry
ptk/UpdateEvent
(update [_ state]
(let [id (st/get-path-id state)
entry (make-entry state)
stack (get-in state [:workspace-local :edit-path id :undo-stack])
head (u/peek stack)
stack (-> stack (u/undo) (u/fixup head))]
(-> state
(d/assoc-in-when
[:workspace-local :edit-path id :undo-stack]
stack))))))
(defn add-undo-entry []
(ptk/reify ::add-undo-entry
ptk/UpdateEvent
(update [_ state]
(let [id (st/get-path-id state)
entry (make-entry state)]
(-> state
(d/update-in-when
[:workspace-local :edit-path id :undo-stack]
u/append entry))))))
(defn end-path-undo
[]
(ptk/reify ::end-path-undo
ptk/UpdateEvent
(update [_ state]
(-> state
(d/update-in-when
[:workspace-local :edit-path (st/get-path-id state)]
dissoc :undo-lock :undo-stack)))))
(defn- stop-undo? [event]
(= :app.main.data.workspace.common/clear-edition-mode (ptk/type event)))
(def path-content-ref
(letfn [(selector [state]
(get-in state (st/get-path state :content)))]
(l/derived selector store/state)))
(defn start-path-undo
[]
(let [lock (uuid/next)]
(ptk/reify ::start-path-undo
ptk/UpdateEvent
(update [_ state]
(let [undo-lock (get-in state [:workspace-local :edit-path (st/get-path-id state) :undo-lock])]
(cond-> state
(not undo-lock)
(update-in [:workspace-local :edit-path (st/get-path-id state)]
assoc
:undo-lock lock
:undo-stack (u/make-stack)))))
ptk/WatchEvent
(watch [_ state stream]
(let [undo-lock (get-in state [:workspace-local :edit-path (st/get-path-id state) :undo-lock])]
(when (= undo-lock lock)
(let [stop-undo-stream (->> stream
(rx/filter stop-undo?)
(rx/take 1))]
(rx/concat
(->> (rx/from-atom path-content-ref {:emit-current-value? true})
(rx/take-until stop-undo-stream)
(rx/filter (comp not nil?))
(rx/map #(add-undo-entry)))
(rx/of (end-path-undo))))))))))