mirror of
https://github.com/penpot/penpot.git
synced 2025-05-14 20:26:38 +02: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) (accumulate-undo-entry (get-in state [:workspace-undo :items index]))
|
||||||
(>= index 0) (update-in [:workspace-undo :index] dec))))))
|
(>= index 0) (update-in [:workspace-undo :index] dec))))))
|
||||||
|
|
||||||
|
;; If these functions change modules review /src/app/main/data/workspace/path/undo.cljs
|
||||||
(def undo
|
(def undo
|
||||||
(ptk/reify ::undo
|
(ptk/reify ::undo
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(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
|
;; Editors handle their own undo's
|
||||||
(when-not (some? edition)
|
(when-not (or (some? edition) (some? drawing))
|
||||||
(let [undo (:workspace-undo state)
|
(let [undo (:workspace-undo state)
|
||||||
items (:items undo)
|
items (:items undo)
|
||||||
index (or (:index undo) (dec (count items)))]
|
index (or (:index undo) (dec (count items)))]
|
||||||
|
@ -375,8 +377,9 @@
|
||||||
(ptk/reify ::redo
|
(ptk/reify ::redo
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(let [edition (get-in state [:workspace-local :edition])]
|
(let [edition (get-in state [:workspace-local :edition])
|
||||||
(when-not (some? edition)
|
drawing (get state :workspace-drawing)]
|
||||||
|
(when-not (or (some? edition) (some? drawing))
|
||||||
(let [undo (:workspace-undo state)
|
(let [undo (:workspace-undo state)
|
||||||
items (:items undo)
|
items (:items undo)
|
||||||
index (or (:index undo) (dec (count items)))]
|
index (or (:index undo) (dec (count items)))]
|
||||||
|
@ -543,6 +546,7 @@
|
||||||
(rx/take 1)
|
(rx/take 1)
|
||||||
(rx/map (constantly clear-edition-mode)))))))
|
(rx/map (constantly clear-edition-mode)))))))
|
||||||
|
|
||||||
|
;; If these event change modules review /src/app/main/data/workspace/path/undo.cljs
|
||||||
(def clear-edition-mode
|
(def clear-edition-mode
|
||||||
(ptk/reify ::clear-edition-mode
|
(ptk/reify ::clear-edition-mode
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
[app.main.data.workspace.path.state :as st]
|
[app.main.data.workspace.path.state :as st]
|
||||||
[app.main.data.workspace.path.streams :as streams]
|
[app.main.data.workspace.path.streams :as streams]
|
||||||
[app.main.data.workspace.path.tools :as tools]
|
[app.main.data.workspace.path.tools :as tools]
|
||||||
|
[app.main.data.workspace.path.undo :as undo]
|
||||||
[app.main.streams :as ms]
|
[app.main.streams :as ms]
|
||||||
[app.util.geom.path :as ugp]
|
[app.util.geom.path :as ugp]
|
||||||
[beicon.core :as rx]
|
[beicon.core :as rx]
|
||||||
|
@ -245,6 +246,7 @@
|
||||||
(make-drag-stream stream snap-toggled zoom points %))))]
|
(make-drag-stream stream snap-toggled zoom points %))))]
|
||||||
|
|
||||||
(rx/concat
|
(rx/concat
|
||||||
|
(rx/of (undo/start-path-undo))
|
||||||
(rx/of (common/init-path))
|
(rx/of (common/init-path))
|
||||||
(rx/merge mousemove-events
|
(rx/merge mousemove-events
|
||||||
mousedown-events)
|
mousedown-events)
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
[app.main.data.workspace.path.state :as st]
|
[app.main.data.workspace.path.state :as st]
|
||||||
[app.main.data.workspace.path.streams :as streams]
|
[app.main.data.workspace.path.streams :as streams]
|
||||||
[app.main.data.workspace.path.drawing :as drawing]
|
[app.main.data.workspace.path.drawing :as drawing]
|
||||||
|
[app.main.data.workspace.path.undo :as undo]
|
||||||
[app.main.streams :as ms]
|
[app.main.streams :as ms]
|
||||||
[app.util.geom.path :as ugp]
|
[app.util.geom.path :as ugp]
|
||||||
[beicon.core :as rx]
|
[beicon.core :as rx]
|
||||||
|
@ -221,6 +222,7 @@
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(let [mode (get-in state [:workspace-local :edit-path id :edit-mode])]
|
(let [mode (get-in state [:workspace-local :edit-path id :edit-mode])]
|
||||||
(rx/concat
|
(rx/concat
|
||||||
|
(rx/of (undo/start-path-undo))
|
||||||
(rx/of (drawing/change-edit-mode mode))
|
(rx/of (drawing/change-edit-mode mode))
|
||||||
(->> stream
|
(->> stream
|
||||||
(rx/take-until (->> stream (rx/filter (ptk/type? ::start-path-edit))))
|
(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 #{}))])
|
(->> points (remove selected-points) (into #{}))])
|
||||||
|
|
||||||
show-snap? (and snap-toggled
|
show-snap? (and snap-toggled
|
||||||
(empty? hover-points)
|
(or (some? drag-handler)
|
||||||
(or (some? drag-handler) (some? preview) (some? moving-handler) moving-nodes))
|
(some? preview)
|
||||||
|
(some? moving-handler)
|
||||||
|
moving-nodes))
|
||||||
|
|
||||||
handle-double-click-outside
|
handle-double-click-outside
|
||||||
(fn [event]
|
(fn [event]
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
#{:app.main.data.workspace.notifications/handle-pointer-update
|
#{:app.main.data.workspace.notifications/handle-pointer-update
|
||||||
:app.main.data.workspace.selection/change-hover-state})
|
: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-all! [] (reset! *debug* debug-options))
|
||||||
(defn debug-none! [] (reset! *debug* #{}))
|
(defn debug-none! [] (reset! *debug* #{}))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue