mirror of
https://github.com/penpot/penpot.git
synced 2025-02-13 02:28:18 -05:00
✨ Undo/redo paths
This commit is contained in:
parent
e2cf3a5a98
commit
0455aaa4cd
7 changed files with 217 additions and 7 deletions
60
common/app/common/data/undo_stack.cljc
Normal file
60
common/app/common/data/undo_stack.cljc
Normal file
|
@ -0,0 +1,60 @@
|
|||
;; 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.common.data.undo-stack
|
||||
(:refer-clojure :exclude [peek])
|
||||
(:require
|
||||
#?(:cljs [cljs.core :as core]
|
||||
:clj [clojure.core :as core])))
|
||||
|
||||
(defonce MAX-UNDO-SIZE 100)
|
||||
|
||||
(defn make-stack
|
||||
[]
|
||||
{:index -1
|
||||
:items []})
|
||||
|
||||
(defn peek
|
||||
[{index :index items :items :as stack}]
|
||||
(when (and (>= index 0) (< index (count items)))
|
||||
(nth items index)))
|
||||
|
||||
(defn append
|
||||
[{index :index items :items :as stack} value]
|
||||
|
||||
(if (and (some? stack) (not= value (peek stack)))
|
||||
(let [items (cond-> items
|
||||
(> index 0)
|
||||
(subvec 0 (inc index))
|
||||
|
||||
(> (+ index 2) MAX-UNDO-SIZE)
|
||||
(subvec 1 (inc index))
|
||||
|
||||
:always
|
||||
(conj value))
|
||||
|
||||
index (min (dec MAX-UNDO-SIZE) (inc index))]
|
||||
{:index index
|
||||
:items items})
|
||||
stack))
|
||||
|
||||
(defn fixup
|
||||
[{index :index :as stack} value]
|
||||
(assoc-in stack [:items index] value))
|
||||
|
||||
(defn undo
|
||||
[{index :index items :items :as stack}]
|
||||
(update stack :index dec))
|
||||
|
||||
(defn redo
|
||||
[{index :index items :items :as stack}]
|
||||
(cond-> stack
|
||||
(< index (dec (count items)))
|
||||
(update :index inc)))
|
||||
|
||||
(defn size
|
||||
[{index :index items :items :as stack}]
|
||||
(inc index))
|
|
@ -356,13 +356,15 @@
|
|||
(>= index 0) (accumulate-undo-entry (get-in state [:workspace-undo :items index]))
|
||||
(>= index 0) (update-in [:workspace-undo :index] dec))))))
|
||||
|
||||
;; If these functions change modules review /src/app/main/data/workspace/path/undo.cljs
|
||||
(def undo
|
||||
(ptk/reify ::undo
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [edition (get-in state [:workspace-local :edition])]
|
||||
(let [edition (get-in state [:workspace-local :edition])
|
||||
drawing (get state :workspace-drawing)]
|
||||
;; Editors handle their own undo's
|
||||
(when-not (some? edition)
|
||||
(when-not (or (some? edition) (some? drawing))
|
||||
(let [undo (:workspace-undo state)
|
||||
items (:items undo)
|
||||
index (or (:index undo) (dec (count items)))]
|
||||
|
@ -375,8 +377,9 @@
|
|||
(ptk/reify ::redo
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [edition (get-in state [:workspace-local :edition])]
|
||||
(when-not (some? edition)
|
||||
(let [edition (get-in state [:workspace-local :edition])
|
||||
drawing (get state :workspace-drawing)]
|
||||
(when-not (or (some? edition) (some? drawing))
|
||||
(let [undo (:workspace-undo state)
|
||||
items (:items undo)
|
||||
index (or (:index undo) (dec (count items)))]
|
||||
|
@ -543,6 +546,7 @@
|
|||
(rx/take 1)
|
||||
(rx/map (constantly clear-edition-mode)))))))
|
||||
|
||||
;; If these event change modules review /src/app/main/data/workspace/path/undo.cljs
|
||||
(def clear-edition-mode
|
||||
(ptk/reify ::clear-edition-mode
|
||||
ptk/UpdateEvent
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
[app.main.data.workspace.path.state :as st]
|
||||
[app.main.data.workspace.path.streams :as streams]
|
||||
[app.main.data.workspace.path.tools :as tools]
|
||||
[app.main.data.workspace.path.undo :as undo]
|
||||
[app.main.streams :as ms]
|
||||
[app.util.geom.path :as ugp]
|
||||
[beicon.core :as rx]
|
||||
|
@ -245,6 +246,7 @@
|
|||
(make-drag-stream stream snap-toggled zoom points %))))]
|
||||
|
||||
(rx/concat
|
||||
(rx/of (undo/start-path-undo))
|
||||
(rx/of (common/init-path))
|
||||
(rx/merge mousemove-events
|
||||
mousedown-events)
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
[app.main.data.workspace.path.state :as st]
|
||||
[app.main.data.workspace.path.streams :as streams]
|
||||
[app.main.data.workspace.path.drawing :as drawing]
|
||||
[app.main.data.workspace.path.undo :as undo]
|
||||
[app.main.streams :as ms]
|
||||
[app.util.geom.path :as ugp]
|
||||
[beicon.core :as rx]
|
||||
|
@ -221,6 +222,7 @@
|
|||
(watch [_ state stream]
|
||||
(let [mode (get-in state [:workspace-local :edit-path id :edit-mode])]
|
||||
(rx/concat
|
||||
(rx/of (undo/start-path-undo))
|
||||
(rx/of (drawing/change-edit-mode mode))
|
||||
(->> stream
|
||||
(rx/take-until (->> stream (rx/filter (ptk/type? ::start-path-edit))))
|
||||
|
|
140
frontend/src/app/main/data/workspace/path/undo.cljs
Normal file
140
frontend/src/app/main/data/workspace/path/undo.cljs
Normal file
|
@ -0,0 +1,140 @@
|
|||
;; 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.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))
|
||||
: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 preview last-point prev-handler]}]
|
||||
(let [id (st/get-path-id state)]
|
||||
(-> state
|
||||
(d/assoc-in-when (st/get-path state :content) content)
|
||||
(d/update-in-when
|
||||
[:workspace-local :edit-path id]
|
||||
assoc
|
||||
:preview preview
|
||||
:last-point last-point
|
||||
:prev-handler prev-handler))))
|
||||
|
||||
(defn undo []
|
||||
(ptk/reify ::undo
|
||||
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)))))))
|
||||
|
||||
(defn redo []
|
||||
(ptk/reify ::redo
|
||||
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))))))
|
||||
|
||||
(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/merge
|
||||
(->> (rx/from-atom path-content-ref {:emit-current-value? true})
|
||||
(rx/filter (comp not nil?))
|
||||
(rx/map #(add-undo-entry)))
|
||||
|
||||
(->> stream
|
||||
(rx/filter undo-event?)
|
||||
(rx/map #(undo)))
|
||||
|
||||
(->> stream
|
||||
(rx/filter redo-event?)
|
||||
(rx/map #(redo))))
|
||||
|
||||
(rx/take-until stop-undo-stream))
|
||||
|
||||
(rx/of (end-path-undo))))))))))
|
||||
|
|
@ -205,8 +205,10 @@
|
|||
(->> points (remove selected-points) (into #{}))])
|
||||
|
||||
show-snap? (and snap-toggled
|
||||
(empty? hover-points)
|
||||
(or (some? drag-handler) (some? preview) (some? moving-handler) moving-nodes))
|
||||
(or (some? drag-handler)
|
||||
(some? preview)
|
||||
(some? moving-handler)
|
||||
moving-nodes))
|
||||
|
||||
handle-double-click-outside
|
||||
(fn [event]
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
#{:app.main.data.workspace.notifications/handle-pointer-update
|
||||
:app.main.data.workspace.selection/change-hover-state})
|
||||
|
||||
(defonce ^:dynamic *debug* (atom #{}))
|
||||
(defonce ^:dynamic *debug* (atom #{#_:events}))
|
||||
|
||||
(defn debug-all! [] (reset! *debug* debug-options))
|
||||
(defn debug-none! [] (reset! *debug* #{}))
|
||||
|
|
Loading…
Add table
Reference in a new issue