mirror of
https://github.com/penpot/penpot.git
synced 2026-05-08 09:38:43 +00:00
181 lines
5.9 KiB
Clojure
181 lines
5.9 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.changes
|
|
(:require
|
|
[app.common.data :as d]
|
|
[app.common.logging :as log]
|
|
[app.common.pages :as cp]
|
|
[app.common.pages.spec :as spec]
|
|
[app.common.spec :as us]
|
|
[app.main.data.workspace.state-helpers :as wsh]
|
|
[app.main.data.workspace.undo :as dwu]
|
|
[app.main.store :as st]
|
|
[app.main.worker :as uw]
|
|
[beicon.core :as rx]
|
|
[cljs.spec.alpha :as s]
|
|
[potok.core :as ptk]))
|
|
|
|
;; Change this to :info :debug or :trace to debug this module
|
|
(log/set-level! :warn)
|
|
|
|
(s/def ::coll-of-uuid
|
|
(s/every ::us/uuid))
|
|
|
|
(defonce page-change? #{:add-page :mod-page :del-page :mov-page})
|
|
|
|
(declare commit-changes)
|
|
|
|
(def commit-changes? (ptk/type? ::commit-changes))
|
|
|
|
(defn- generate-operation
|
|
"Given an object old and new versions and an attribute will append into changes
|
|
the set and undo operations"
|
|
[changes attr old new ignore-geometry?]
|
|
(let [old-val (get old attr)
|
|
new-val (get new attr)]
|
|
(if (= old-val new-val)
|
|
changes
|
|
(-> changes
|
|
(update :rops conj {:type :set :attr attr :val new-val :ignore-geometry ignore-geometry?})
|
|
(update :uops conj {:type :set :attr attr :val old-val :ignore-touched true})))))
|
|
|
|
(defn- update-shape-changes
|
|
"Calculate the changes and undos to be done when a function is applied to a
|
|
single object"
|
|
[changes page-id objects update-fn attrs id ignore-geometry?]
|
|
(let [old-obj (get objects id)
|
|
new-obj (update-fn old-obj)
|
|
|
|
attrs (or attrs (d/concat-set (keys old-obj) (keys new-obj)))
|
|
|
|
{rops :rops uops :uops}
|
|
(reduce #(generate-operation %1 %2 old-obj new-obj ignore-geometry?)
|
|
{:rops [] :uops []}
|
|
attrs)
|
|
|
|
uops (cond-> uops
|
|
(seq uops)
|
|
(conj {:type :set-touched :touched (:touched old-obj)}))
|
|
|
|
change {:type :mod-obj :page-id page-id :id id}]
|
|
|
|
(cond-> changes
|
|
(seq rops)
|
|
(update :redo-changes conj (assoc change :operations rops))
|
|
|
|
(seq uops)
|
|
(update :undo-changes conj (assoc change :operations uops)))))
|
|
|
|
(defn update-shapes
|
|
([ids f] (update-shapes ids f nil))
|
|
([ids f {:keys [reg-objects? save-undo? attrs ignore-tree]
|
|
:or {reg-objects? false save-undo? true attrs nil}}]
|
|
|
|
(us/assert ::coll-of-uuid ids)
|
|
(us/assert fn? f)
|
|
|
|
(ptk/reify ::update-shapes
|
|
ptk/WatchEvent
|
|
(watch [it state _]
|
|
(let [page-id (:current-page-id state)
|
|
objects (wsh/lookup-page-objects state)
|
|
changes {:redo-changes []
|
|
:undo-changes []
|
|
:origin it
|
|
:save-undo? save-undo?}
|
|
|
|
ids (into [] (filter some?) ids)
|
|
|
|
changes (reduce
|
|
#(update-shape-changes %1 page-id objects f attrs %2 (get ignore-tree %2))
|
|
changes ids)]
|
|
|
|
(when-not (empty? (:redo-changes changes))
|
|
(let [reg-objs {:type :reg-objects
|
|
:page-id page-id
|
|
:shapes ids}
|
|
changes (cond-> changes
|
|
reg-objects?
|
|
(-> (update :redo-changes conj reg-objs)
|
|
(update :undo-changes conj reg-objs)))]
|
|
(rx/of (commit-changes changes)))))))))
|
|
|
|
(defn update-indices
|
|
[page-id changes]
|
|
(ptk/reify ::update-indices
|
|
ptk/EffectEvent
|
|
(effect [_ _ _]
|
|
(uw/ask! {:cmd :update-page-indices
|
|
:page-id page-id
|
|
:changes changes}))))
|
|
|
|
(defn commit-changes
|
|
[{:keys [redo-changes undo-changes origin save-undo? file-id]
|
|
:or {save-undo? true}}]
|
|
|
|
(log/debug :msg "commit-changes"
|
|
:js/redo-changes redo-changes
|
|
:js/undo-changes undo-changes)
|
|
|
|
(let [error (volatile! nil)]
|
|
(ptk/reify ::commit-changes
|
|
cljs.core/IDeref
|
|
(-deref [_]
|
|
{:file-id file-id
|
|
:hint-events @st/last-events
|
|
:hint-origin (ptk/type origin)
|
|
:changes redo-changes})
|
|
|
|
ptk/UpdateEvent
|
|
(update [_ state]
|
|
(let [current-file-id (get state :current-file-id)
|
|
file-id (or file-id current-file-id)
|
|
path (if (= file-id current-file-id)
|
|
|
|
[:workspace-data]
|
|
[:workspace-libraries file-id :data])]
|
|
(try
|
|
(us/assert ::spec/changes redo-changes)
|
|
(us/assert ::spec/changes undo-changes)
|
|
|
|
;; (prn "====== commit-changes ======" path)
|
|
;; (cljs.pprint/pprint redo-changes)
|
|
;; (cljs.pprint/pprint undo-changes)
|
|
|
|
(update-in state path cp/process-changes redo-changes false)
|
|
|
|
(catch :default e
|
|
(vreset! error e)
|
|
state))))
|
|
|
|
ptk/WatchEvent
|
|
(watch [_ _ _]
|
|
(when-not @error
|
|
(let [;; adds page-id to page changes (that have the `id` field instead)
|
|
add-page-id
|
|
(fn [{:keys [id type page] :as change}]
|
|
(cond-> change
|
|
(page-change? type)
|
|
(assoc :page-id (or id (:id page)))))
|
|
|
|
changes-by-pages
|
|
(->> redo-changes
|
|
(map add-page-id)
|
|
(remove #(nil? (:page-id %)))
|
|
(group-by :page-id))
|
|
|
|
process-page-changes
|
|
(fn [[page-id _changes]]
|
|
(update-indices page-id redo-changes))]
|
|
(rx/concat
|
|
(rx/from (map process-page-changes changes-by-pages))
|
|
|
|
(when (and save-undo? (seq undo-changes))
|
|
(let [entry {:undo-changes undo-changes
|
|
:redo-changes redo-changes}]
|
|
(rx/of (dwu/append-undo entry)))))))))))
|