;; 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) KALEIDOS INC (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.common :as dwc] [app.main.data.workspace.path.changes :as changes] [app.main.data.workspace.path.common :as common] [app.main.data.workspace.path.state :as st] [app.main.store :as store] [beicon.v2.core :as rx] [okulary.core :as l] [potok.v2.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) shape (st/get-path state)] {:content (:content shape) :selrect (:selrect shape) :points (:points shape) :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 (st/get-path state :content)] (-> state (d/assoc-in-when (st/get-path-location state :content) content) (d/assoc-in-when (st/get-path-location state :selrect) selrect) (d/assoc-in-when (st/get-path-location 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 _] (let [id (st/get-path-id state) undo-stack (get-in state [:workspace-local :edit-path id :undo-stack])] (if (> (:index undo-stack) 0) (rx/of (changes/save-path-content {:preserve-move-to true})) (rx/of (changes/save-path-content {:preserve-move-to true}) (common/finish-path) (dwc/show-toolbar))))))) (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 [_ _ _] (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 ::merge-head ptk/UpdateEvent (update [_ state] (let [id (st/get-path-id 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] (or (= :app.main.data.workspace.common/clear-edition-mode (ptk/type event)) (= :app.main.data.workspace/finalize-page (ptk/type event)))) (def path-content-ref (letfn [(selector [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))))))))))