0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-03-25 06:01:46 -05:00

♻️ Refactor persistence layer

This commit is contained in:
Andrey Antukh 2023-07-25 11:20:52 +02:00
parent d679001955
commit 6436ef334b
62 changed files with 1030 additions and 1070 deletions

View file

@ -12,6 +12,7 @@
(def registry (atom {}))
(defn potok-reify
[{:keys [:node :filename] :as params}]
(let [[rnode rtype & other] (:children node)

View file

@ -9,9 +9,11 @@
### :heart: Community contributions (Thank you!)
### :sparkles: New features
- Improve auth process [Taiga #Change Auth Process](https://tree.taiga.io/project/penpot/us/7094)
- Add locking degrees increment (hold shift) on path edition [Taiga #7761](https://tree.taiga.io/project/penpot/issue/7761)
- Allow library colors as recent colors [Taiga issue #7640](https://tree.taiga.io/project/penpot/issue/7640)
- Improve auth process [Taiga #7094](https://tree.taiga.io/project/penpot/us/7094)
- Add locking degrees increment (hold shift) on path edition [Taiga Issue #7761](https://tree.taiga.io/project/penpot/issue/7761)
- Persistence & Concurrent Edition Enhancements [Taiga #5657](https://tree.taiga.io/project/penpot/us/5657)
- Allow library colors as recent colors [Taiga Issue #7640](https://tree.taiga.io/project/penpot/issue/7640)
### :bug: Bugs fixed

View file

@ -262,8 +262,8 @@
;; Send asynchronous notifications
(send-notifications! cfg params)
;; Retrieve and return lagged data
(get-lagged-changes conn params))))
{:revn (:revn file)
:lagged (get-lagged-changes conn params)})))
(defn- soft-validate-file-schema!
[file]

View file

@ -0,0 +1,166 @@
;; 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.changes
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.changes :as cpc]
[app.common.logging :as log]
[app.common.types.shape-tree :as ctst]
[app.common.uuid :as uuid]
[app.main.features :as features]
[app.main.worker :as uw]
[app.util.time :as dt]
[beicon.v2.core :as rx]
[potok.v2.core :as ptk]))
;; Change this to :info :debug or :trace to debug this module
(log/set-level! :debug)
(def page-change?
#{:add-page :mod-page :del-page :mov-page})
(def update-layout-attr?
#{:hidden})
(def commit?
(ptk/type? ::commit))
(defn update-indexes
"Given a commit, send the changes to the worker for updating the
indexes."
[{:keys [changes] :as commit}]
(ptk/reify ::update-indexes
ptk/WatchEvent
(watch [_ _ _]
(let [changes (->> changes
(map (fn [{:keys [id type page] :as change}]
(cond-> change
(and (page-change? type) (nil? (:page-id change)))
(assoc :page-id (or id (:id page))))))
(filter :page-id)
(group-by :page-id))]
(->> (rx/from changes)
(rx/merge-map (fn [[page-id changes]]
(log/debug :hint "update-indexes" :page-id page-id :changes (count changes))
(uw/ask! {:cmd :update-page-index
:page-id page-id
:changes changes})))
(rx/ignore))))))
(defn- get-pending-commits
[{:keys [persistence]}]
(->> (:queue persistence)
(map (d/getf (:index persistence)))
(not-empty)))
(defn commit
"Create a commit event instance"
[{:keys [commit-id redo-changes undo-changes origin save-undo? features
file-id file-revn undo-group tags stack-undo? source]}]
(dm/assert!
"expect valid vector of changes"
(and (cpc/check-changes! redo-changes)
(cpc/check-changes! undo-changes)))
(let [commit-id (or commit-id (uuid/next))
commit {:id commit-id
:created-at (dt/now)
:source (d/nilv source :local)
:origin (ptk/type origin)
:features features
:file-id file-id
:file-revn file-revn
:changes redo-changes
:redo-changes redo-changes
:undo-changes undo-changes
:save-undo? save-undo?
:undo-group undo-group
:tags tags
:stack-undo? stack-undo?}]
(ptk/reify ::commit
cljs.core/IDeref
(-deref [_] commit)
ptk/UpdateEvent
(update [_ state]
(let [current-file-id (get state :current-file-id)
path (if (= file-id current-file-id)
[:workspace-data]
[:workspace-libraries file-id :data])
not-local? (not= source :local)
pending (if not-local?
(get-pending-commits state)
nil)
undo-changes (if pending
(->> pending
(map :undo-changes)
(reverse)
(mapcat identity)
(vec))
nil)
redo-changes (if pending
(into redo-changes
(comp
(map :redo-changes)
(mapcat identity))
pending)
redo-changes)]
(d/update-in-when state path
(fn [file]
(let [file (cpc/process-changes file undo-changes false)
file (cpc/process-changes file redo-changes false)
pids (into #{} (map :page-id) redo-changes)]
(reduce #(ctst/update-object-indices %1 %2) file pids)))))))))
(defn- resolve-file-revn
[state file-id]
(let [file (:workspace-file state)]
(if (= (:id file) file-id)
(:revn file)
(dm/get-in state [:workspace-libraries file-id :revn]))))
(defn commit-changes
"Schedules a list of changes to execute now, and add the corresponding undo changes to
the undo stack.
Options:
- save-undo?: if set to false, do not add undo changes.
- undo-group: if some consecutive changes (or even transactions) share the same
undo-group, they will be undone or redone in a single step
"
[{:keys [redo-changes undo-changes save-undo? undo-group tags stack-undo? file-id]
:or {save-undo? true
stack-undo? false
undo-group (uuid/next)
tags #{}}
:as params}]
(ptk/reify ::commit-changes
ptk/WatchEvent
(watch [_ state _]
(let [file-id (or file-id (:current-file-id state))
uchg (vec undo-changes)
rchg (vec redo-changes)
features (features/get-team-enabled-features state)]
(rx/of (-> params
(assoc :undo-group undo-group)
(assoc :features features)
(assoc :tags tags)
(assoc :stack-undo? stack-undo?)
(assoc :save-undo? save-undo?)
(assoc :file-id file-id)
(assoc :file-revn (resolve-file-revn state file-id))
(assoc :undo-changes uchg)
(assoc :redo-changes rchg)
(commit)))))))

View file

@ -8,7 +8,7 @@
(:require
[app.common.uuid :as uuid]
[app.main.data.modal :as modal]
[app.main.data.workspace.persistence :as dwp]
[app.main.data.persistence :as dwp]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.refs :as refs]
[app.main.repo :as rp]

View file

@ -0,0 +1,230 @@
;; 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.persistence
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.logging :as log]
[app.common.uuid :as uuid]
[app.main.data.changes :as dch]
[app.main.repo :as rp]
[app.util.router :as rt]
[beicon.v2.core :as rx]
[potok.v2.core :as ptk]))
(declare ^:private run-persistence-task)
(log/set-level! :warn)
(def running (atom false))
(def revn-data (atom {}))
(def queue-conj (fnil conj #queue []))
(defn- update-status
[status]
(ptk/reify ::update-status
ptk/UpdateEvent
(update [_ state]
(update state :persistence (fn [pstate]
(log/trc :hint "update-status"
:from (:status pstate)
:to status)
(let [status (if (and (= status :pending)
(= (:status pstate) :saving))
(:status pstate)
status)]
(-> (assoc pstate :status status)
(cond-> (= status :error)
(dissoc :run-id))
(cond-> (= status :saved)
(dissoc :run-id)))))))))
(defn- update-file-revn
[file-id revn]
(ptk/reify ::update-file-revn
ptk/UpdateEvent
(update [_ state]
(log/dbg :hint "update-file-revn" :file-id (dm/str file-id) :revn revn)
(if-let [current-file-id (:current-file-id state)]
(if (= file-id current-file-id)
(update-in state [:workspace-file :revn] max revn)
(d/update-in-when state [:workspace-libraries file-id :revn] max revn))
state))
ptk/EffectEvent
(effect [_ _ _]
(swap! revn-data update file-id (fnil max 0) revn))))
(defn- discard-commit
[commit-id]
(ptk/reify ::discard-commit
ptk/UpdateEvent
(update [_ state]
(update state :persistence (fn [pstate]
(-> pstate
(update :queue (fn [queue]
(if (= commit-id (peek queue))
(pop queue)
(throw (ex-info "invalid state" {})))))
(update :index dissoc commit-id)))))))
(defn- append-commit
"Event used internally to append the current change to the
persistence queue."
[{:keys [id] :as commit}]
(let [run-id (uuid/next)]
(ptk/reify ::append-commit
ptk/UpdateEvent
(update [_ state]
(log/trc :hint "append-commit" :method "update" :commit-id (dm/str id))
(update state :persistence
(fn [pstate]
(-> pstate
(update :run-id d/nilv run-id)
(update :queue queue-conj id)
(update :index assoc id commit)))))
ptk/WatchEvent
(watch [_ state _]
(let [pstate (:persistence state)]
(when (= run-id (:run-id pstate))
(rx/of (run-persistence-task)
(update-status :saving))))))))
(defn- persist-commit
[commit-id]
(ptk/reify ::persist-commit
ptk/WatchEvent
(watch [_ state _]
(log/dbg :hint "persist-commit" :commit-id (dm/str commit-id))
(when-let [{:keys [file-id file-revn changes features] :as commit} (dm/get-in state [:persistence :index commit-id])]
(let [sid (:session-id state)
revn (max file-revn (get @revn-data file-id 0))
params {:id file-id
:revn revn
:session-id sid
:origin (:origin commit)
:created-at (:created-at commit)
:commit-id commit-id
:changes (vec changes)
:features features}]
;; FIXME: handle lagged here !!!!
(->> (rp/cmd! :update-file params)
(rx/mapcat (fn [{:keys [revn lagged] :as response}]
(log/debug :hint "changes persisted" :commit-id (dm/str commit-id) :lagged (count lagged))
(rx/of (ptk/data-event ::commit-persisted commit)
(update-file-revn file-id revn))))
(rx/catch (fn [cause]
(rx/concat
(if (= :authentication (:type cause))
(rx/empty)
(rx/of (rt/assign-exception cause)
(ptk/data-event ::error cause)
(update-status :error)))
(rx/throw cause))))))))))
(defn- run-persistence-task
[]
(ptk/reify ::run-persistence-task
ptk/WatchEvent
(watch [_ state stream]
(let [queue (-> state :persistence :queue)]
(if-let [commit-id (peek queue)]
(let [stoper-s (rx/merge
(rx/filter (ptk/type? ::run-persistence-task) stream)
(rx/filter (ptk/type? ::error) stream))]
(log/dbg :hint "run-persistence-task" :commit-id (dm/str commit-id))
(->> (rx/merge
(rx/of (persist-commit commit-id))
(->> stream
(rx/filter (ptk/type? ::commit-persisted))
(rx/map deref)
(rx/filter #(= commit-id (:id %)))
(rx/take 1)
(rx/mapcat (fn [_]
(rx/of (discard-commit commit-id)
(run-persistence-task))))))
(rx/take-until stoper-s)))
(rx/of (update-status :saved)))))))
(def ^:private xf-mapcat-undo
(mapcat :undo-changes))
(def ^:private xf-mapcat-redo
(mapcat :redo-changes))
(defn- merge-commit
[buffer]
(->> (rx/from (group-by :file-id buffer))
(rx/map (fn [[_ [item :as commits]]]
(let [uchg (into [] xf-mapcat-undo commits)
rchg (into [] xf-mapcat-redo commits)]
(-> item
(assoc :undo-changes uchg)
(assoc :redo-changes rchg)
(assoc :changes rchg)))))))
(defn initialize-persistence
[]
(ptk/reify ::initialize-persistence
ptk/WatchEvent
(watch [_ _ stream]
(log/debug :hint "initialize persistence")
(let [stoper-s (rx/filter (ptk/type? ::initialize-persistence) stream)
commits-s
(->> stream
(rx/filter dch/commit?)
(rx/map deref)
(rx/filter #(= :local (:source %)))
(rx/filter (complement empty?))
(rx/share))
notifier-s
(rx/merge
(->> commits-s
(rx/debounce 3000)
(rx/tap #(log/trc :hint "persistence beat")))
(->> stream
(rx/filter #(= % ::force-persist))))]
(rx/merge
(->> commits-s
(rx/debounce 200)
(rx/map (fn [_]
(update-status :pending)))
(rx/take-until stoper-s))
;; Here we watch for local commits, buffer them in a small
;; chunks (very near in time commits) and append them to the
;; persistence queue
(->> commits-s
(rx/buffer-until notifier-s)
(rx/mapcat merge-commit)
(rx/mapcat (fn [commit]
(rx/of (append-commit commit)
(dch/update-indexes commit))))
(rx/take-until (rx/delay 100 stoper-s))
(rx/finalize (fn []
(log/debug :hint "finalize persistence: changes watcher"))))
;; Here we track all incoming remote commits for maintain
;; updated the local state with the file revn
(->> stream
(rx/filter dch/commit?)
(rx/map deref)
(rx/filter #(= :remote (:source %)))
(rx/mapcat (fn [{:keys [file-id file-revn] :as commit}]
(rx/of (update-file-revn file-id file-revn)
(dch/update-indexes commit))))
(rx/take-until stoper-s)))))))

View file

@ -19,6 +19,7 @@
[app.common.geom.rect :as grc]
[app.common.geom.shapes :as gsh]
[app.common.geom.shapes.grid-layout :as gslg]
[app.common.logging :as log]
[app.common.logic.libraries :as cll]
[app.common.logic.shapes :as cls]
[app.common.schema :as sm]
@ -34,14 +35,15 @@
[app.common.types.typography :as ctt]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.main.data.changes :as dch]
[app.main.data.comments :as dcm]
[app.main.data.events :as ev]
[app.main.data.fonts :as df]
[app.main.data.messages :as msg]
[app.main.data.modal :as modal]
[app.main.data.persistence :as dps]
[app.main.data.users :as du]
[app.main.data.workspace.bool :as dwb]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.collapse :as dwco]
[app.main.data.workspace.drawing :as dwd]
[app.main.data.workspace.edition :as dwe]
@ -59,7 +61,6 @@
[app.main.data.workspace.notifications :as dwn]
[app.main.data.workspace.path :as dwdp]
[app.main.data.workspace.path.shapes-to-path :as dwps]
[app.main.data.workspace.persistence :as dwp]
[app.main.data.workspace.selection :as dws]
[app.main.data.workspace.shape-layout :as dwsl]
[app.main.data.workspace.shapes :as dwsh]
@ -87,6 +88,7 @@
[potok.v2.core :as ptk]))
(def default-workspace-local {:zoom 1})
(log/set-level! :debug)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Workspace Initialization
@ -341,15 +343,32 @@
:workspace-presence {}))
ptk/WatchEvent
(watch [_ _ _]
(rx/of msg/hide
(dcm/retrieve-comment-threads file-id)
(dwp/initialize-file-persistence file-id)
(fetch-bundle project-id file-id)))
(watch [_ _ stream]
(log/debug :hint "initialize-file" :file-id file-id)
(let [stoper-s (rx/filter (ptk/type? ::finalize-file) stream)]
(rx/merge
(rx/of msg/hide
(features/initialize)
(dcm/retrieve-comment-threads file-id)
(fetch-bundle project-id file-id))
(->> stream
(rx/filter dch/commit?)
(rx/map deref)
(rx/mapcat (fn [{:keys [save-undo? undo-changes redo-changes undo-group tags stack-undo?]}]
(if (and save-undo? (seq undo-changes))
(let [entry {:undo-changes undo-changes
:redo-changes redo-changes
:undo-group undo-group
:tags tags}]
(rx/of (dwu/append-undo entry stack-undo?)))
(rx/empty))))
(rx/take-until stoper-s)))))
ptk/EffectEvent
(effect [_ _ _]
(let [name (str "workspace-" file-id)]
(let [name (dm/str "workspace-" file-id)]
(unchecked-set ug/global "name" name)))))
(defn finalize-file
@ -671,7 +690,7 @@
(ptk/reify ::update-shape
ptk/WatchEvent
(watch [_ _ _]
(rx/of (dch/update-shapes [id] #(merge % attrs))))))
(rx/of (dwsh/update-shapes [id] #(merge % attrs))))))
(defn start-rename-shape
"Start shape renaming process"
@ -982,7 +1001,7 @@
(assoc shape :proportion-lock false)
(-> (assoc shape :proportion-lock true)
(gpp/assign-proportions))))]
(rx/of (dch/update-shapes [id] assign-proportions))))))
(rx/of (dwsh/update-shapes [id] assign-proportions))))))
(defn toggle-proportion-lock
[]
@ -996,8 +1015,8 @@
multi (attrs/get-attrs-multi selected-obj [:proportion-lock])
multi? (= :multiple (:proportion-lock multi))]
(if multi?
(rx/of (dch/update-shapes selected #(assoc % :proportion-lock true)))
(rx/of (dch/update-shapes selected #(update % :proportion-lock not))))))))
(rx/of (dwsh/update-shapes selected #(assoc % :proportion-lock true)))
(rx/of (dwsh/update-shapes selected #(update % :proportion-lock not))))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Navigation
@ -1258,7 +1277,7 @@
(assoc :section section)
(some? frame-id)
(assoc :frame-id frame-id))]
(rx/of ::dwp/force-persist
(rx/of ::dps/force-persist
(rt/nav-new-window* {:rname :viewer
:path-params pparams
:query-params qparams
@ -1271,7 +1290,7 @@
ptk/WatchEvent
(watch [_ state _]
(when-let [team-id (or team-id (:current-team-id state))]
(rx/of ::dwp/force-persist
(rx/of ::dps/force-persist
(rt/nav :dashboard-projects {:team-id team-id})))))))
(defn go-to-dashboard-fonts
@ -1280,7 +1299,7 @@
ptk/WatchEvent
(watch [_ state _]
(let [team-id (:current-team-id state)]
(rx/of ::dwp/force-persist
(rx/of ::dps/force-persist
(rt/nav :dashboard-fonts {:team-id team-id}))))))

View file

@ -15,8 +15,9 @@
[app.common.types.shape :as cts]
[app.common.types.shape.layout :as ctl]
[app.common.uuid :as uuid]
[app.main.data.workspace.changes :as dch]
[app.main.data.changes :as dch]
[app.main.data.workspace.selection :as dws]
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.state-helpers :as wsh]
[beicon.v2.core :as rx]
[cuerdas.core :as str]
@ -117,7 +118,7 @@
change-to-bool
(fn [shape] (group->bool shape bool-type objects))]
(when-not (ctn/has-any-copy-parent? objects (get objects shape-id))
(rx/of (dch/update-shapes [shape-id] change-to-bool {:reg-objects? true})))))))
(rx/of (dwsh/update-shapes [shape-id] change-to-bool {:reg-objects? true})))))))
(defn bool-to-group
[shape-id]
@ -128,7 +129,7 @@
change-to-group
(fn [shape] (bool->group shape objects))]
(when-not (ctn/has-any-copy-parent? objects (get objects shape-id))
(rx/of (dch/update-shapes [shape-id] change-to-group {:reg-objects? true})))))))
(rx/of (dwsh/update-shapes [shape-id] change-to-group {:reg-objects? true})))))))
(defn change-bool-type
@ -140,4 +141,4 @@
change-type
(fn [shape] (assoc shape :bool-type bool-type))]
(when-not (ctn/has-any-copy-parent? objects (get objects shape-id))
(rx/of (dch/update-shapes [shape-id] change-type {:reg-objects? true})))))))
(rx/of (dwsh/update-shapes [shape-id] change-type {:reg-objects? true})))))))

View file

@ -1,264 +0,0 @@
;; 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.changes
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.common.files.changes :as cpc]
[app.common.files.changes-builder :as pcb]
[app.common.files.helpers :as cph]
[app.common.logging :as log]
[app.common.logic.shapes :as cls]
[app.common.schema :as sm]
[app.common.types.shape-tree :as ctst]
[app.common.uuid :as uuid]
[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.v2.core :as rx]
[potok.v2.core :as ptk]))
;; Change this to :info :debug or :trace to debug this module
(log/set-level! :warn)
(defonce page-change? #{:add-page :mod-page :del-page :mov-page})
(defonce update-layout-attr? #{:hidden})
(declare commit-changes)
(defn- add-undo-group
[changes state]
(let [undo (:workspace-undo state)
items (:items undo)
index (or (:index undo) (dec (count items)))
prev-item (when-not (or (empty? items) (= index -1))
(get items index))
undo-group (:undo-group prev-item)
add-undo-group? (and
(not (nil? undo-group))
(= (get-in changes [:redo-changes 0 :type]) :mod-obj)
(= (get-in prev-item [:redo-changes 0 :type]) :add-obj)
(contains? (:tags prev-item) :alt-duplication))] ;; This is a copy-and-move with mouse+alt
(cond-> changes add-undo-group? (assoc :undo-group undo-group))))
(def commit-changes? (ptk/type? ::commit-changes))
(defn update-shapes
([ids update-fn] (update-shapes ids update-fn nil))
([ids update-fn {:keys [reg-objects? save-undo? stack-undo? attrs ignore-tree page-id ignore-remote? ignore-touched undo-group with-objects?]
:or {reg-objects? false save-undo? true stack-undo? false ignore-remote? false ignore-touched false with-objects? false}}]
(dm/assert!
"expected a valid coll of uuid's"
(sm/check-coll-of-uuid! ids))
(dm/assert! (fn? update-fn))
(ptk/reify ::update-shapes
ptk/WatchEvent
(watch [it state _]
(let [page-id (or page-id (:current-page-id state))
objects (wsh/lookup-page-objects state page-id)
ids (into [] (filter some?) ids)
update-layout-ids
(->> ids
(map (d/getf objects))
(filter #(some update-layout-attr? (pcb/changed-attrs % objects update-fn {:attrs attrs :with-objects? with-objects?})))
(map :id))
changes (-> (pcb/empty-changes it page-id)
(pcb/set-save-undo? save-undo?)
(pcb/set-stack-undo? stack-undo?)
(cls/generate-update-shapes ids
update-fn
objects
{:attrs attrs
:ignore-tree ignore-tree
:ignore-touched ignore-touched
:with-objects? with-objects?})
(cond-> undo-group
(pcb/set-undo-group undo-group)))
changes (add-undo-group changes state)]
(rx/concat
(if (seq (:redo-changes changes))
(let [changes (cond-> changes reg-objects? (pcb/resize-parents ids))
changes (cond-> changes ignore-remote? (pcb/ignore-remote))]
(rx/of (commit-changes changes)))
(rx/empty))
;; Update layouts for properties marked
(if (d/not-empty? update-layout-ids)
(rx/of (ptk/data-event :layout/update {:ids update-layout-ids}))
(rx/empty))))))))
(defn send-update-indices
[]
(ptk/reify ::send-update-indices
ptk/WatchEvent
(watch [_ _ _]
(->> (rx/of
(fn [state]
(-> state
(dissoc ::update-indices-debounce)
(dissoc ::update-changes))))
(rx/observe-on :async)))
ptk/EffectEvent
(effect [_ state _]
(doseq [[page-id changes] (::update-changes state)]
(uw/ask! {:cmd :update-page-index
:page-id page-id
:changes changes})))))
;; Update indices will debounce operations so we don't have to update
;; the index several times (which is an expensive operation)
(defn update-indices
[page-id changes]
(let [start (uuid/next)]
(ptk/reify ::update-indices
ptk/UpdateEvent
(update [_ state]
(if (nil? (::update-indices-debounce state))
(assoc state ::update-indices-debounce start)
(update-in state [::update-changes page-id] (fnil d/concat-vec []) changes)))
ptk/WatchEvent
(watch [_ state stream]
(if (= (::update-indices-debounce state) start)
(let [stopper (->> stream (rx/filter (ptk/type? :app.main.data.workspace/finalize)))]
(rx/merge
(->> stream
(rx/filter (ptk/type? ::update-indices))
(rx/debounce 50)
(rx/take 1)
(rx/map #(send-update-indices))
(rx/take-until stopper))
(rx/of (update-indices page-id changes))))
(rx/empty))))))
(defn changed-frames
"Extracts the frame-ids changed in the given changes"
[changes objects]
(let [change->ids
(fn [change]
(case (:type change)
:add-obj
[(:parent-id change)]
(:mod-obj :del-obj)
[(:id change)]
:mov-objects
(d/concat-vec (:shapes change) [(:parent-id change)])
[]))]
(into #{}
(comp (mapcat change->ids)
(keep #(cph/get-shape-id-root-frame objects %))
(remove #(= uuid/zero %)))
changes)))
(defn commit-changes
"Schedules a list of changes to execute now, and add the corresponding undo changes to
the undo stack.
Options:
- save-undo?: if set to false, do not add undo changes.
- undo-group: if some consecutive changes (or even transactions) share the same
undo-group, they will be undone or redone in a single step
"
[{:keys [redo-changes undo-changes
origin save-undo? file-id undo-group tags stack-undo?]
:or {save-undo? true stack-undo? false tags #{} undo-group (uuid/next)}}]
(let [error (volatile! nil)
page-id (:current-page-id @st/state)
frames (changed-frames redo-changes (wsh/lookup-page-objects @st/state))
undo-changes (vec undo-changes)
redo-changes (vec redo-changes)]
(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
:page-id page-id
:frames frames
:save-undo? save-undo?
:undo-group undo-group
:tags tags
:stack-undo? stack-undo?})
ptk/UpdateEvent
(update [_ state]
(log/info :msg "commit-changes"
:js/undo-group (str undo-group)
:js/file-id (str (or file-id "nil"))
:js/redo-changes redo-changes
:js/undo-changes undo-changes)
(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
(dm/assert!
"expect valid vector of changes"
(and (cpc/check-changes! redo-changes)
(cpc/check-changes! undo-changes)))
(update-in state path (fn [file]
(-> file
(cpc/process-changes redo-changes false)
(ctst/update-object-indices page-id))))
(catch :default err
(when-let [data (ex-data err)]
(js/console.log (ex/explain data)))
(when (ex/error? err)
(js/console.log (.-stack ^js err)))
(vreset! error err)
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
(and (page-change? type) (nil? (:page-id change)))
(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
:undo-group undo-group
:tags tags}]
(rx/of (dwu/append-undo entry stack-undo?)))))))))))

View file

@ -15,9 +15,9 @@
[app.main.broadcast :as mbc]
[app.main.data.events :as ev]
[app.main.data.modal :as md]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.layout :as layout]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.data.workspace.texts :as dwt]
[app.main.data.workspace.undo :as dwu]
@ -116,7 +116,7 @@
(rx/concat
(rx/of (dwu/start-undo-transaction undo-id))
(rx/from (map #(dwt/update-text-with-function % transform-attrs) text-ids))
(rx/of (dch/update-shapes shape-ids transform-attrs))
(rx/of (dwsh/update-shapes shape-ids transform-attrs))
(rx/of (dwu/commit-undo-transaction undo-id)))))
(defn swap-attrs [shape attr index new-index]
@ -140,7 +140,7 @@
(rx/concat
(rx/from (map #(dwt/update-text-with-function % transform-attrs) text-ids))
(rx/of (dch/update-shapes shape-ids transform-attrs)))))))
(rx/of (dwsh/update-shapes shape-ids transform-attrs)))))))
(defn change-fill
[ids color position]
@ -203,10 +203,10 @@
is-text? #(= :text (:type (get objects %)))
shape-ids (filter (complement is-text?) ids)
attrs {:hide-fill-on-export hide-fill-on-export}]
(rx/of (dch/update-shapes shape-ids (fn [shape]
(if (= (:type shape) :frame)
(d/merge shape attrs)
shape))))))))
(rx/of (dwsh/update-shapes shape-ids (fn [shape]
(if (= (:type shape) :frame)
(d/merge shape attrs)
shape))))))))
(defn change-stroke
[ids attrs index]
(ptk/reify ::change-stroke
@ -236,7 +236,7 @@
(dissoc :image)
(dissoc :gradient))]
(rx/of (dch/update-shapes
(rx/of (dwsh/update-shapes
ids
(fn [shape]
(let [new-attrs (merge (get-in shape [:strokes index]) attrs)
@ -264,7 +264,7 @@
(ptk/reify ::change-shadow
ptk/WatchEvent
(watch [_ _ _]
(rx/of (dch/update-shapes
(rx/of (dwsh/update-shapes
ids
(fn [shape]
(let [;; If we try to set a gradient to a shadow (for
@ -288,7 +288,7 @@
(watch [_ _ _]
(let [add-shadow (fn [shape]
(update shape :shadow #(into [shadow] %)))]
(rx/of (dch/update-shapes ids add-shadow))))))
(rx/of (dwsh/update-shapes ids add-shadow))))))
(defn add-stroke
[ids stroke]
@ -296,7 +296,7 @@
ptk/WatchEvent
(watch [_ _ _]
(let [add-stroke (fn [shape] (update shape :strokes #(into [stroke] %)))]
(rx/of (dch/update-shapes ids add-stroke))))))
(rx/of (dwsh/update-shapes ids add-stroke))))))
(defn remove-stroke
[ids position]
@ -309,7 +309,7 @@
(mapv second)))
(remove-stroke [shape]
(update shape :strokes remove-fill-by-index position))]
(rx/of (dch/update-shapes ids remove-stroke))))))
(rx/of (dwsh/update-shapes ids remove-stroke))))))
(defn remove-all-strokes
[ids]
@ -317,14 +317,14 @@
ptk/WatchEvent
(watch [_ _ _]
(let [remove-all #(assoc % :strokes [])]
(rx/of (dch/update-shapes ids remove-all))))))
(rx/of (dwsh/update-shapes ids remove-all))))))
(defn reorder-shadows
[ids index new-index]
(ptk/reify ::reorder-shadow
ptk/WatchEvent
(watch [_ _ _]
(rx/of (dch/update-shapes
(rx/of (dwsh/update-shapes
ids
#(swap-attrs % :shadow index new-index))))))
@ -333,7 +333,7 @@
(ptk/reify ::reorder-strokes
ptk/WatchEvent
(watch [_ _ _]
(rx/of (dch/update-shapes
(rx/of (dwsh/update-shapes
ids
#(swap-attrs % :strokes index new-index))))))

View file

@ -12,9 +12,9 @@
[app.common.geom.shapes :as gsh]
[app.common.schema :as sm]
[app.common.types.shape-tree :as ctst]
[app.main.data.changes :as dch]
[app.main.data.comments :as dcm]
[app.main.data.events :as ev]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.common :as dwco]
[app.main.data.workspace.drawing :as dwd]
[app.main.data.workspace.state-helpers :as wsh]

View file

@ -6,14 +6,7 @@
(ns app.main.data.workspace.common
(:require
[app.common.data.macros :as dm]
[app.common.logging :as log]
[app.common.types.shape.layout :as ctl]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.data.workspace.undo :as dwu]
[app.util.router :as rt]
[beicon.v2.core :as rx]
[potok.v2.core :as ptk]))
;; Change this to :info :debug or :trace to debug this module
@ -34,136 +27,11 @@
[e]
(= e :interrupt))
(defn- assure-valid-current-page
[]
(ptk/reify ::assure-valid-current-page
ptk/WatchEvent
(watch [_ state _]
(let [current_page (:current-page-id state)
pages (get-in state [:workspace-data :pages])
exists? (some #(= current_page %) pages)
project-id (:current-project-id state)
file-id (:current-file-id state)
pparams {:file-id file-id :project-id project-id}
qparams {:page-id (first pages)}]
(if exists?
(rx/empty)
(rx/of (rt/nav :workspace pparams qparams)))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; UNDO
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(declare undo-to-index)
;; These functions should've been in
;; `src/app/main/data/workspace/undo.cljs` but doing that causes a
;; circular dependency with `src/app/main/data/workspace/changes.cljs`
(def undo
(ptk/reify ::undo
ptk/WatchEvent
(watch [it state _]
(let [objects (wsh/lookup-page-objects state)
edition (get-in state [:workspace-local :edition])
drawing (get state :workspace-drawing)]
;; Editors handle their own undo's
(when (or (and (nil? edition) (nil? (:object drawing)))
(ctl/grid-layout? objects edition))
(let [undo (:workspace-undo state)
items (:items undo)
index (or (:index undo) (dec (count items)))]
(when-not (or (empty? items) (= index -1))
(let [item (get items index)
changes (:undo-changes item)
undo-group (:undo-group item)
find-first-group-idx
(fn [index]
(if (= (dm/get-in items [index :undo-group]) undo-group)
(recur (dec index))
(inc index)))
undo-group-index
(when undo-group
(find-first-group-idx index))]
(if undo-group
(rx/of (undo-to-index (dec undo-group-index)))
(rx/of (dwu/materialize-undo changes (dec index))
(dch/commit-changes {:redo-changes changes
:undo-changes []
:save-undo? false
:origin it})
(assure-valid-current-page)))))))))))
(def redo
(ptk/reify ::redo
ptk/WatchEvent
(watch [it state _]
(let [objects (wsh/lookup-page-objects state)
edition (get-in state [:workspace-local :edition])
drawing (get state :workspace-drawing)]
(when (and (or (nil? edition) (ctl/grid-layout? objects edition))
(or (empty? drawing) (= :curve (:tool drawing))))
(let [undo (:workspace-undo state)
items (:items undo)
index (or (:index undo) (dec (count items)))]
(when-not (or (empty? items) (= index (dec (count items))))
(let [item (get items (inc index))
changes (:redo-changes item)
undo-group (:undo-group item)
find-last-group-idx (fn flgidx [index]
(let [item (get items index)]
(if (= (:undo-group item) undo-group)
(flgidx (inc index))
(dec index))))
redo-group-index (when undo-group
(find-last-group-idx (inc index)))]
(if undo-group
(rx/of (undo-to-index redo-group-index))
(rx/of (dwu/materialize-undo changes (inc index))
(dch/commit-changes {:redo-changes changes
:undo-changes []
:origin it
:save-undo? false})))))))))))
(defn undo-to-index
"Repeat undoing or redoing until dest-index is reached."
[dest-index]
(ptk/reify ::undo-to-index
ptk/WatchEvent
(watch [it state _]
(let [objects (wsh/lookup-page-objects state)
edition (get-in state [:workspace-local :edition])
drawing (get state :workspace-drawing)]
(when-not (and (or (some? edition) (some? (:object drawing)))
(not (ctl/grid-layout? objects edition)))
(let [undo (:workspace-undo state)
items (:items undo)
index (or (:index undo) (dec (count items)))]
(when (and (some? items)
(<= -1 dest-index (dec (count items))))
(let [changes (vec (apply concat
(cond
(< dest-index index)
(->> (subvec items (inc dest-index) (inc index))
(reverse)
(map :undo-changes))
(> dest-index index)
(->> (subvec items (inc index) (inc dest-index))
(map :redo-changes))
:else [])))]
(when (seq changes)
(rx/of (dwu/materialize-undo changes dest-index)
(dch/commit-changes {:redo-changes changes
:undo-changes []
:origin it
:save-undo? false})))))))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Toolbar
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View file

@ -8,7 +8,8 @@
(:require
[app.common.data :as d]
[app.common.geom.shapes :as gsh]
[app.main.data.workspace.changes :as dch]
[app.main.data.changes :as dch]
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.state-helpers :as wsh]
[beicon.v2.core :as rx]
[potok.v2.core :as ptk]))
@ -82,9 +83,9 @@
:objects (-> component migrate-component :objects)}))
components)]
(rx/of (dch/update-shapes ids #(update-shape % objects) {:reg-objects? false
:save-undo? false
:ignore-tree true}))
(rx/of (dwsh/update-shapes ids #(update-shape % objects) {:reg-objects? false
:save-undo? false
:ignore-tree true}))
(if (empty? component-changes)
(rx/empty)

View file

@ -6,7 +6,7 @@
(ns app.main.data.workspace.fix-broken-shapes
(:require
[app.main.data.workspace.changes :as dch]
[app.main.data.changes :as dch]
[beicon.v2.core :as rx]
[potok.v2.core :as ptk]))

View file

@ -9,7 +9,8 @@
[app.common.data :as d]
[app.common.files.helpers :as cfh]
[app.common.text :as txt]
[app.main.data.workspace.changes :as dch]
[app.main.data.changes :as dwc]
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.fonts :as fonts]
[beicon.v2.core :as rx]
@ -111,19 +112,19 @@
typographies)]
(rx/concat
(rx/of (dch/update-shapes ids #(fix-deleted-font-shape %) {:reg-objects? false
:save-undo? false
:ignore-tree true}))
(rx/of (dwsh/update-shapes ids #(fix-deleted-font-shape %) {:reg-objects? false
:save-undo? false
:ignore-tree true}))
(if (empty? component-changes)
(rx/empty)
(rx/of (dch/commit-changes {:origin it
(rx/of (dwc/commit-changes {:origin it
:redo-changes component-changes
:undo-changes []
:save-undo? false})))
(if (empty? typography-changes)
(rx/empty)
(rx/of (dch/commit-changes {:origin it
(rx/of (dwc/commit-changes {:origin it
:redo-changes typography-changes
:undo-changes []
:save-undo? false}))))))))

View file

@ -10,7 +10,8 @@
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.changes-builder :as pcb]
[app.main.data.workspace.changes :as dch]
[app.main.data.changes :as dch]
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.state-helpers :as wsh]
[beicon.v2.core :as rx]
[potok.v2.core :as ptk]))
@ -51,8 +52,8 @@
grid {:type :square
:params params
:display true}]
(rx/of (dch/update-shapes [frame-id]
(fn [obj] (update obj :grids (fnil #(conj % grid) [])))))))))
(rx/of (dwsh/update-shapes [frame-id]
(fn [obj] (update obj :grids (fnil #(conj % grid) [])))))))))
(defn remove-frame-grid
@ -60,14 +61,14 @@
(ptk/reify ::remove-frame-grid
ptk/WatchEvent
(watch [_ _ _]
(rx/of (dch/update-shapes [frame-id] (fn [o] (update o :grids (fnil #(d/remove-at-index % index) []))))))))
(rx/of (dwsh/update-shapes [frame-id] (fn [o] (update o :grids (fnil #(d/remove-at-index % index) []))))))))
(defn set-frame-grid
[frame-id index data]
(ptk/reify ::set-frame-grid
ptk/WatchEvent
(watch [_ _ _]
(rx/of (dch/update-shapes [frame-id] #(assoc-in % [:grids index] data))))))
(rx/of (dwsh/update-shapes [frame-id] #(assoc-in % [:grids index] data))))))
(defn set-default-grid
[type params]

View file

@ -8,7 +8,7 @@
(:require
[app.main.data.shortcuts :as ds]
[app.main.data.workspace :as dw]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.undo :as dwu]
[app.main.store :as st]
[beicon.v2.core :as rx]
[potok.v2.core :as ptk]))
@ -38,11 +38,11 @@
:undo {:tooltip (ds/meta "Z")
:command (ds/c-mod "z")
:fn #(st/emit! dwc/undo)}
:fn #(st/emit! dwu/undo)}
:redo {:tooltip (ds/meta "Y")
:command [(ds/c-mod "shift+z") (ds/c-mod "y")]
:fn #(st/emit! dwc/redo)}
:fn #(st/emit! dwu/redo)}
;; ZOOM

View file

@ -16,7 +16,7 @@
[app.common.types.shape :as cts]
[app.common.types.shape.layout :as ctl]
[app.common.uuid :as uuid]
[app.main.data.workspace.changes :as dch]
[app.main.data.changes :as dch]
[app.main.data.workspace.selection :as dws]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.data.workspace.undo :as dwu]

View file

@ -11,8 +11,8 @@
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.types.page :as ctp]
[app.main.data.changes :as dwc]
[app.main.data.events :as ev]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.state-helpers :as wsh]
[beicon.v2.core :as rx]
[potok.v2.core :as ptk]))
@ -42,7 +42,7 @@
(-> (pcb/empty-changes it)
(pcb/with-page page)
(pcb/update-page-option :guides assoc (:id guide) guide))]
(rx/of (dch/commit-changes changes))))))
(rx/of (dwc/commit-changes changes))))))
(defn remove-guide
[guide]
@ -66,7 +66,7 @@
(-> (pcb/empty-changes it)
(pcb/with-page page)
(pcb/update-page-option :guides dissoc (:id guide)))]
(rx/of (dch/commit-changes changes))))))
(rx/of (dwc/commit-changes changes))))))
(defn remove-guides
[ids]

View file

@ -15,7 +15,8 @@
[app.common.types.shape-tree :as ctst]
[app.common.types.shape.interactions :as ctsi]
[app.common.uuid :as uuid]
[app.main.data.workspace.changes :as dch]
[app.main.data.changes :as dch]
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.data.workspace.undo :as dwu]
[app.main.streams :as ms]
@ -125,13 +126,13 @@
:flows] [])
flow (ctp/get-frame-flow flows (:id frame))]
(rx/concat
(rx/of (dch/update-shapes [(:id shape)]
(fn [shape]
(let [new-interaction (-> ctsi/default-interaction
(ctsi/set-destination destination)
(assoc :position-relative-to (:id shape)))]
(update shape :interactions
ctsi/add-interaction new-interaction)))))
(rx/of (dwsh/update-shapes [(:id shape)]
(fn [shape]
(let [new-interaction (-> ctsi/default-interaction
(ctsi/set-destination destination)
(assoc :position-relative-to (:id shape)))]
(update shape :interactions
ctsi/add-interaction new-interaction)))))
(when (and (not (connected-frame? objects (:id frame)))
(nil? flow))
(rx/of (add-flow (:id frame))))))))))
@ -141,20 +142,19 @@
(ptk/reify ::remove-interaction
ptk/WatchEvent
(watch [_ _ _]
(rx/of (dch/update-shapes [(:id shape)]
(fn [shape]
(update shape :interactions
ctsi/remove-interaction index)))))))
(rx/of (dwsh/update-shapes [(:id shape)]
(fn [shape]
(update shape :interactions
ctsi/remove-interaction index)))))))
(defn update-interaction
[shape index update-fn]
(ptk/reify ::update-interaction
ptk/WatchEvent
(watch [_ _ _]
(rx/of (dch/update-shapes [(:id shape)]
(fn [shape]
(update shape :interactions
ctsi/update-interaction index update-fn)))))))
(rx/of (dwsh/update-shapes [(:id shape)]
(fn [shape]
(update shape :interactions
ctsi/update-interaction index update-fn)))))))
(defn remove-all-interactions-nav-to
"Remove all interactions that navigate to the given frame."
@ -171,9 +171,9 @@
new-interactions (ctsi/remove-interactions #(ctsi/navs-to? % frame-id)
interactions)]
(when (not= (count interactions) (count new-interactions))
(dch/update-shapes [(:id shape)]
(fn [shape]
(assoc shape :interactions new-interactions))))))]
(dwsh/update-shapes [(:id shape)]
(fn [shape]
(assoc shape :interactions new-interactions))))))]
(rx/from (->> (vals objects)
(map remove-interactions-shape)
@ -260,20 +260,20 @@
(dwu/start-undo-transaction undo-id)
(when (:hide-in-viewer target-frame)
; If the target frame is hidden, we need to unhide it so
; users can navigate to it.
(dch/update-shapes [(:id target-frame)]
#(dissoc % :hide-in-viewer)))
;; If the target frame is hidden, we need to unhide it so
;; users can navigate to it.
(dwsh/update-shapes [(:id target-frame)]
#(dissoc % :hide-in-viewer)))
(cond
(or (nil? shape)
;; Didn't changed the position for the interaction
;; Didn't changed the position for the interaction
(= position initial-pos)
;; New interaction but invalid target
;; New interaction but invalid target
(and (nil? index) (nil? target-frame)))
nil
;; Dropped interaction in an invalid target. We remove it
;; Dropped interaction in an invalid target. We remove it
(and (some? index) (nil? target-frame))
(remove-interaction shape index)
@ -364,5 +364,5 @@
(update interactions index
#(ctsi/set-overlay-position % overlay-pos))]
(rx/of (dch/update-shapes [(:id shape)] #(merge % {:interactions new-interactions})))))))
(rx/of (dwsh/update-shapes [(:id shape)] #(merge % {:interactions new-interactions})))))))

View file

@ -9,7 +9,7 @@
(:require
[app.common.data :as d]
[app.common.math :as mth]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.state-helpers :as wsh]
[beicon.v2.core :as rx]
[cuerdas.core :as str]
@ -48,7 +48,7 @@
shapes (map #(get objects %) selected)
shapes-ids (->> shapes
(map :id))]
(rx/of (dch/update-shapes shapes-ids #(assoc % :opacity opacity)))))))
(rx/of (dwsh/update-shapes shapes-ids #(assoc % :opacity opacity)))))))
(defn pressed-opacity
[opacity]

View file

@ -24,15 +24,16 @@
[app.common.types.shape.layout :as ctl]
[app.common.types.typography :as ctt]
[app.common.uuid :as uuid]
[app.main.data.changes :as dch]
[app.main.data.comments :as dc]
[app.main.data.events :as ev]
[app.main.data.messages :as msg]
[app.main.data.modal :as modal]
[app.main.data.workspace :as-alias dw]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.groups :as dwg]
[app.main.data.workspace.notifications :as-alias dwn]
[app.main.data.workspace.selection :as dws]
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.specialized-panel :as dwsp]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.data.workspace.thumbnails :as dwt]
@ -54,7 +55,6 @@
;; Change this to :info :debug or :trace to debug this module, or :warn to reset to default
(log/set-level! :warn)
(defn- pretty-file
[file-id state]
(if (= file-id (:current-file-id state))
@ -441,7 +441,7 @@
;; NOTE: only when components-v2 is enabled
(when (and shape-id page-id)
(rx/of (dch/update-shapes [shape-id] #(assoc % :name clean-name) {:page-id page-id :stack-undo? true}))))))))))
(rx/of (dwsh/update-shapes [shape-id] #(assoc % :name clean-name) {:page-id page-id :stack-undo? true}))))))))))
(defn duplicate-component
"Create a new component copied from the one with the given id."
@ -1148,7 +1148,7 @@
changes-s
(->> stream
(rx/filter #(or (dch/commit-changes? %)
(rx/filter #(or (dch/commit? %)
(ptk/type? % ::dwn/handle-file-change)))
(rx/observe-on :async))

View file

@ -20,9 +20,9 @@
[app.common.types.shape :as cts]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.main.data.changes :as dch]
[app.main.data.media :as dmm]
[app.main.data.messages :as msg]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.state-helpers :as wsh]

View file

@ -23,9 +23,9 @@
[app.common.types.shape.layout :as ctl]
[app.common.uuid :as uuid]
[app.main.constants :refer [zoom-half-pixel-precision]]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.comments :as-alias dwcm]
[app.main.data.workspace.guides :as-alias dwg]
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.data.workspace.undo :as dwu]
[beicon.v2.core :as rx]
@ -499,7 +499,7 @@
(rx/empty))
(rx/of (ptk/event ::dwg/move-frame-guides ids-with-children)
(ptk/event ::dwcm/move-frame-comment-threads ids-with-children)
(dch/update-shapes
(dwsh/update-shapes
ids
(fn [shape]
(let [modif (get-in object-modifiers [(:id shape) :modifiers])

View file

@ -11,11 +11,10 @@
[app.common.files.changes :as cpc]
[app.common.schema :as sm]
[app.common.uuid :as uuid]
[app.main.data.changes :as dch]
[app.main.data.common :refer [handle-notification]]
[app.main.data.websocket :as dws]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.persistence :as dwp]
[app.util.globals :refer [global]]
[app.util.mouse :as mse]
[app.util.object :as obj]
@ -84,7 +83,7 @@
(->> stream
(rx/filter mse/pointer-event?)
(rx/filter #(= :viewport (mse/get-pointer-source %)))
(rx/pipe (rxs/throttle 100))
(rx/pipe (rxs/throttle 50))
(rx/map #(handle-pointer-send file-id (:pt %)))))
(rx/take-until stopper))]
@ -197,9 +196,10 @@
[:changes ::cpc/changes]]))
(defn handle-file-change
[{:keys [file-id changes] :as msg}]
[{:keys [file-id changes revn] :as msg}]
(dm/assert!
"expected valid arguments"
"expected valid parameters"
(sm/check! schema:handle-file-change msg))
(ptk/reify ::handle-file-change
@ -209,15 +209,11 @@
ptk/WatchEvent
(watch [_ state _]
(let [page-id (:current-page-id state)
position-data-operation?
(fn [{:keys [type attr]}]
(and (= :set type) (= attr :position-data)))
;;add-origin-session-id
;;(fn [{:keys [] :as op}]
;; (cond-> op
;; (position-data-operation? op)
;; (update :val with-meta {:session-id (:session-id msg)})))
(and (= :set type)
(= attr :position-data)))
update-position-data
(fn [change]
@ -228,24 +224,23 @@
(= :mod-obj (:type change)))
(update :operations #(d/removev position-data-operation? %))))
process-page-changes
(fn [[page-id changes]]
(dch/update-indices page-id changes))
;; We update `position-data` from the incoming message
changes (->> changes
(mapv update-position-data)
(d/removev (fn [change]
(and (= page-id (:page-id change))
(:ignore-remote? change)))))
(map update-position-data)
(remove (fn [change]
(and (= page-id (:page-id change))
(:ignore-remote? change))))
(vec))]
changes-by-pages (group-by :page-id changes)]
(rx/merge
(rx/of (dwp/shapes-changes-persisted file-id (assoc msg :changes changes)))
(when-not (empty? changes-by-pages)
(rx/from (map process-page-changes changes-by-pages))))))))
;; The commit event is responsible to apply the data localy
;; and update the persistence internal state with the updated
;; file-revn
(rx/of (dch/commit {:file-id file-id
:file-revn revn
:save-undo? false
:source :remote
:redo-changes changes
:undo-changes []}))))))
(def ^:private
schema:handle-library-change

View file

@ -8,7 +8,7 @@
(:require
[app.common.data.macros :as dm]
[app.common.files.changes-builder :as pcb]
[app.main.data.workspace.changes :as dch]
[app.main.data.changes :as dch]
[app.main.data.workspace.path.common :refer [check-path-content!]]
[app.main.data.workspace.path.helpers :as helpers]
[app.main.data.workspace.path.state :as st]

View file

@ -15,7 +15,6 @@
[app.common.types.shape :as cts]
[app.common.types.shape-tree :as ctst]
[app.common.types.shape.layout :as ctl]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.drawing.common :as dwdc]
[app.main.data.workspace.edition :as dwe]
[app.main.data.workspace.path.changes :as changes]
@ -24,6 +23,7 @@
[app.main.data.workspace.path.state :as st]
[app.main.data.workspace.path.streams :as streams]
[app.main.data.workspace.path.undo :as undo]
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.state-helpers :as wsh]
[app.util.mouse :as mse]
[beicon.v2.core :as rx]
@ -333,7 +333,7 @@
edit-mode (get-in state [:workspace-local :edit-path id :edit-mode])]
(if (= :draw edit-mode)
(rx/concat
(rx/of (dch/update-shapes [id] upsp/convert-to-path))
(rx/of (dwsh/update-shapes [id] upsp/convert-to-path))
(rx/of (handle-drawing id))
(->> stream
(rx/filter (ptk/type? ::common/finish-path))

View file

@ -14,7 +14,7 @@
[app.common.svg.path.command :as upc]
[app.common.svg.path.shapes-to-path :as upsp]
[app.common.svg.path.subpath :as ups]
[app.main.data.workspace.changes :as dch]
[app.main.data.changes :as dch]
[app.main.data.workspace.edition :as dwe]
[app.main.data.workspace.path.changes :as changes]
[app.main.data.workspace.path.drawing :as drawing]
@ -23,6 +23,7 @@
[app.main.data.workspace.path.state :as st]
[app.main.data.workspace.path.streams :as streams]
[app.main.data.workspace.path.undo :as undo]
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.streams :as ms]
[app.util.mouse :as mse]
@ -143,7 +144,7 @@
selected? (contains? selected-points position)]
(streams/drag-stream
(rx/of
(dch/update-shapes [id] upsp/convert-to-path)
(dwsh/update-shapes [id] upsp/convert-to-path)
(when-not selected? (selection/select-node position shift?))
(drag-selected-points @ms/mouse-position))
(rx/of (selection/select-node position shift?)))))))
@ -227,7 +228,7 @@
mov-vec (gpt/multiply (get-displacement direction) scale)]
(rx/concat
(rx/of (dch/update-shapes [id] upsp/convert-to-path))
(rx/of (dwsh/update-shapes [id] upsp/convert-to-path))
(rx/merge
(->> move-events
(rx/take-until stopper)
@ -265,7 +266,7 @@
(streams/drag-stream
(rx/concat
(rx/of (dch/update-shapes [id] upsp/convert-to-path))
(rx/of (dwsh/update-shapes [id] upsp/convert-to-path))
(->> (streams/move-handler-stream handler point handler opposite points)
(rx/map
(fn [{:keys [x y alt? shift?]}]
@ -354,5 +355,5 @@
ptk/WatchEvent
(watch [_ state _]
(let [id (st/get-path-id state)]
(rx/of (dch/update-shapes [id] upsp/convert-to-path)
(rx/of (dwsh/update-shapes [id] upsp/convert-to-path)
(split-segments event))))))

View file

@ -10,7 +10,7 @@
[app.common.files.helpers :as cph]
[app.common.svg.path.shapes-to-path :as upsp]
[app.common.types.container :as ctn]
[app.main.data.workspace.changes :as dch]
[app.main.data.changes :as dch]
[app.main.data.workspace.state-helpers :as wsh]
[beicon.v2.core :as rx]
[potok.v2.core :as ptk]))

View file

@ -8,10 +8,11 @@
(:require
[app.common.svg.path.shapes-to-path :as upsp]
[app.common.svg.path.subpath :as ups]
[app.main.data.workspace.changes :as dch]
[app.main.data.changes :as dch]
[app.main.data.workspace.edition :as dwe]
[app.main.data.workspace.path.changes :as changes]
[app.main.data.workspace.path.state :as st]
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.state-helpers :as wsh]
[app.util.path.tools :as upt]
[beicon.v2.core :as rx]
@ -37,7 +38,7 @@
changes (changes/generate-path-changes it objects page-id shape (:content shape) new-content)]
(rx/concat
(rx/of (dch/update-shapes [id] upsp/convert-to-path))
(rx/of (dwsh/update-shapes [id] upsp/convert-to-path))
(rx/of (dch/commit-changes changes)
(when (empty? new-content)
(dwe/clear-edition-mode)))))))))))

View file

@ -1,263 +0,0 @@
;; 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.persistence
(:require
[app.common.data.macros :as dm]
[app.common.files.changes :as cpc]
[app.common.logging :as log]
[app.common.types.shape-tree :as ctst]
[app.common.uuid :as uuid]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.thumbnails :as dwt]
[app.main.features :as features]
[app.main.repo :as rp]
[app.main.store :as st]
[app.util.time :as dt]
[beicon.v2.core :as rx]
[okulary.core :as l]
[potok.v2.core :as ptk]))
(log/set-level! :info)
(declare persist-changes)
(declare persist-synchronous-changes)
(declare shapes-changes-persisted)
(declare shapes-changes-persisted-finished)
(declare update-persistence-status)
;; --- Persistence
(defn initialize-file-persistence
[file-id]
(ptk/reify ::initialize-persistence
ptk/WatchEvent
(watch [_ _ stream]
(log/debug :hint "initialize persistence")
(let [stopper (rx/filter (ptk/type? ::initialize-persistence) stream)
commits (l/atom [])
saving? (l/atom false)
local-file?
#(as-> (:file-id %) event-file-id
(or (nil? event-file-id)
(= event-file-id file-id)))
library-file?
#(as-> (:file-id %) event-file-id
(and (some? event-file-id)
(not= event-file-id file-id)))
on-dirty
(fn []
;; Enable reload stopper
(swap! st/ongoing-tasks conj :workspace-change)
(st/emit! (update-persistence-status {:status :pending})))
on-saving
(fn []
(reset! saving? true)
(st/emit! (update-persistence-status {:status :saving})))
on-saved
(fn []
;; Disable reload stopper
(swap! st/ongoing-tasks disj :workspace-change)
(st/emit! (update-persistence-status {:status :saved}))
(reset! saving? false))]
(rx/merge
(->> stream
(rx/filter dch/commit-changes?)
(rx/map deref)
(rx/filter local-file?)
(rx/tap on-dirty)
(rx/filter (complement empty?))
(rx/map (fn [commit]
(-> commit
(assoc :id (uuid/next))
(assoc :file-id file-id))))
(rx/observe-on :async)
(rx/tap #(swap! commits conj %))
(rx/take-until (rx/delay 100 stopper))
(rx/finalize (fn []
(log/debug :hint "finalize persistence: changes watcher"))))
(->> (rx/from-atom commits)
(rx/filter (complement empty?))
(rx/sample-when
(rx/merge
(rx/filter #(= ::force-persist %) stream)
(->> (rx/merge
(rx/interval 5000)
(->> (rx/from-atom commits)
(rx/filter (complement empty?))
(rx/debounce 2000)))
;; Not sample while saving so there are no race conditions
(rx/filter #(not @saving?)))))
(rx/tap #(reset! commits []))
(rx/tap on-saving)
(rx/mapcat (fn [changes]
;; NOTE: this is needed for don't start the
;; next persistence before this one is
;; finished.
(if-let [file-revn (dm/get-in @st/state [:workspace-file :revn])]
(rx/merge
(->> (rx/of (persist-changes file-id file-revn changes commits))
(rx/observe-on :async))
(->> stream
;; We wait for every change to be persisted
(rx/filter (ptk/type? ::shapes-changes-persisted-finished))
(rx/take 1)
(rx/tap on-saved)
(rx/ignore)))
(rx/empty))))
(rx/take-until (rx/delay 100 stopper))
(rx/finalize (fn []
(log/debug :hint "finalize persistence: save loop"))))
;; Synchronous changes
(->> stream
(rx/filter dch/commit-changes?)
(rx/map deref)
(rx/filter library-file?)
(rx/filter (complement #(empty? (:changes %))))
(rx/map persist-synchronous-changes)
(rx/take-until (rx/delay 100 stopper))
(rx/finalize (fn []
(log/debug :hint "finalize persistence: synchronous save loop")))))))))
(defn persist-changes
[file-id file-revn changes pending-commits]
(log/debug :hint "persist changes" :changes (count changes))
(dm/assert! (uuid? file-id))
(ptk/reify ::persist-changes
ptk/WatchEvent
(watch [_ state _]
(let [sid (:session-id state)
features (features/get-team-enabled-features state)
params {:id file-id
:revn file-revn
:session-id sid
:changes-with-metadata (into [] changes)
:features features}]
(->> (rp/cmd! :update-file params)
(rx/mapcat (fn [lagged]
(log/debug :hint "changes persisted" :lagged (count lagged))
(let [frame-updates
(-> (group-by :page-id changes)
(update-vals #(into #{} (mapcat :frames) %)))
commits
(->> @pending-commits
(map #(assoc % :revn file-revn)))]
(rx/concat
(rx/merge
(->> (rx/from frame-updates)
(rx/mapcat (fn [[page-id frames]]
(->> frames (map (fn [frame-id] [file-id page-id frame-id])))))
(rx/map (fn [data]
(ptk/data-event ::dwt/update data))))
(->> (rx/from (concat lagged commits))
(rx/merge-map
(fn [{:keys [changes] :as entry}]
(rx/merge
(rx/from
(for [[page-id changes] (group-by :page-id changes)]
(dch/update-indices page-id changes)))
(rx/of (shapes-changes-persisted file-id entry)))))))
(rx/of (shapes-changes-persisted-finished))))))
(rx/catch (fn [cause]
(if (instance? js/TypeError cause)
(->> (rx/timer 2000)
(rx/map (fn [_]
(persist-changes file-id file-revn changes pending-commits))))
(rx/throw cause)))))))))
;; Event to be thrown after the changes have been persisted
(defn shapes-changes-persisted-finished
[]
(ptk/reify ::shapes-changes-persisted-finished))
(defn persist-synchronous-changes
[{:keys [file-id changes]}]
(dm/assert! (uuid? file-id))
(ptk/reify ::persist-synchronous-changes
ptk/WatchEvent
(watch [_ state _]
(let [features (features/get-team-enabled-features state)
sid (:session-id state)
file (dm/get-in state [:workspace-libraries file-id])
params {:id (:id file)
:revn (:revn file)
:session-id sid
:changes changes
:features features}]
(when (:id params)
(->> (rp/cmd! :update-file params)
(rx/ignore)))))))
(defn update-persistence-status
[{:keys [status reason]}]
(ptk/reify ::update-persistence-status
ptk/UpdateEvent
(update [_ state]
(update state :workspace-persistence
(fn [local]
(assoc local
:reason reason
:status status
:updated-at (dt/now)))))))
(defn shapes-persisted-event? [event]
(= (ptk/type event) ::changes-persisted))
(defn shapes-changes-persisted
[file-id {:keys [revn changes] persisted-session-id :session-id}]
(dm/assert! (uuid? file-id))
(dm/assert! (int? revn))
(dm/assert! (cpc/check-changes! changes))
(ptk/reify ::shapes-changes-persisted
ptk/UpdateEvent
(update [_ state]
;; NOTE: we don't set the file features context here because
;; there are no useful context for code that need to be executed
;; on the frontend side
(let [current-file-id (:current-file-id state)
current-session-id (:session-id state)]
(if (and (some? current-file-id)
;; If the remote change is from teh current session we skip
(not= persisted-session-id current-session-id))
(if (= file-id current-file-id)
(let [changes (group-by :page-id changes)]
(-> state
(update-in [:workspace-file :revn] max revn)
(update :workspace-data
(fn [file]
(loop [fdata file
entries (seq changes)]
(if-let [[page-id changes] (first entries)]
(recur (-> fdata
(cpc/process-changes changes)
(cond-> (some? page-id)
(ctst/update-object-indices page-id)))
(rest entries))
fdata))))))
(-> state
(update-in [:workspace-libraries file-id :revn] max revn)
(update-in [:workspace-libraries file-id :data] cpc/process-changes changes)))
state)))))

View file

@ -18,9 +18,9 @@
[app.common.record :as cr]
[app.common.types.component :as ctk]
[app.common.uuid :as uuid]
[app.main.data.changes :as dch]
[app.main.data.events :as ev]
[app.main.data.modal :as md]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.collapse :as dwc]
[app.main.data.workspace.specialized-panel :as-alias dwsp]
[app.main.data.workspace.state-helpers :as wsh]

View file

@ -20,8 +20,8 @@
[app.common.types.modifiers :as ctm]
[app.common.types.shape.layout :as ctl]
[app.common.uuid :as uuid]
[app.main.data.changes :as dch]
[app.main.data.events :as ev]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.colors :as cl]
[app.main.data.workspace.grid-layout.editor :as dwge]
[app.main.data.workspace.modifiers :as dwm]
@ -148,8 +148,8 @@
layout-initializer (get-layout-initializer type from-frame? calculate-params?)]
(rx/of (dwu/start-undo-transaction undo-id)
(dch/update-shapes [id] layout-initializer {:with-objects? true})
(dch/update-shapes (dm/get-prop parent :shapes) #(dissoc % :constraints-h :constraints-v))
(dwsh/update-shapes [id] layout-initializer {:with-objects? true})
(dwsh/update-shapes (dm/get-prop parent :shapes) #(dissoc % :constraints-h :constraints-v))
(ptk/data-event :layout/update {:ids [id]})
(dwu/commit-undo-transaction undo-id))))))
@ -188,8 +188,8 @@
(dwsh/create-artboard-from-selection new-shape-id parent-id group-index (:name (first selected-shapes)))
(cl/remove-all-fills [new-shape-id] {:color clr/black :opacity 1})
(create-layout-from-id new-shape-id type)
(dch/update-shapes [new-shape-id] #(assoc % :layout-item-h-sizing :auto :layout-item-v-sizing :auto))
(dch/update-shapes selected #(assoc % :layout-item-h-sizing :fix :layout-item-v-sizing :fix))
(dwsh/update-shapes [new-shape-id] #(assoc % :layout-item-h-sizing :auto :layout-item-v-sizing :auto))
(dwsh/update-shapes selected #(assoc % :layout-item-h-sizing :fix :layout-item-v-sizing :fix))
(dwsh/delete-shapes page-id selected)
(ptk/data-event :layout/update {:ids [new-shape-id]})
(dwu/commit-undo-transaction undo-id)))
@ -199,8 +199,8 @@
(dwsh/create-artboard-from-selection new-shape-id)
(cl/remove-all-fills [new-shape-id] {:color clr/black :opacity 1})
(create-layout-from-id new-shape-id type)
(dch/update-shapes [new-shape-id] #(assoc % :layout-item-h-sizing :auto :layout-item-v-sizing :auto))
(dch/update-shapes selected #(assoc % :layout-item-h-sizing :fix :layout-item-v-sizing :fix))))
(dwsh/update-shapes [new-shape-id] #(assoc % :layout-item-h-sizing :auto :layout-item-v-sizing :auto))
(dwsh/update-shapes selected #(assoc % :layout-item-h-sizing :fix :layout-item-v-sizing :fix))))
(rx/of (ptk/data-event :layout/update {:ids [new-shape-id]})
(dwu/commit-undo-transaction undo-id)))))))
@ -213,7 +213,7 @@
(let [undo-id (js/Symbol)]
(rx/of
(dwu/start-undo-transaction undo-id)
(dch/update-shapes ids #(apply dissoc % layout-keys))
(dwsh/update-shapes ids #(apply dissoc % layout-keys))
(ptk/data-event :layout/update {:ids ids})
(dwu/commit-undo-transaction undo-id))))))
@ -266,7 +266,7 @@
(watch [_ _ _]
(let [undo-id (js/Symbol)]
(rx/of (dwu/start-undo-transaction undo-id)
(dch/update-shapes ids (d/patch-object changes))
(dwsh/update-shapes ids (d/patch-object changes))
(ptk/data-event :layout/update {:ids ids})
(dwu/commit-undo-transaction undo-id))))))
@ -280,7 +280,7 @@
(watch [_ _ _]
(let [undo-id (js/Symbol)]
(rx/of (dwu/start-undo-transaction undo-id)
(dch/update-shapes
(dwsh/update-shapes
ids
(fn [shape]
(case type
@ -313,7 +313,7 @@
(if shapes-to-delete
(dwsh/delete-shapes shapes-to-delete)
(rx/empty))
(dch/update-shapes
(dwsh/update-shapes
ids
(fn [shape objects]
(case type
@ -387,7 +387,7 @@
(watch [_ _ _]
(let [undo-id (js/Symbol)]
(rx/of (dwu/start-undo-transaction undo-id)
(dch/update-shapes
(dwsh/update-shapes
ids
(fn [shape]
(case type
@ -433,7 +433,7 @@
:row :layout-grid-rows
:column :layout-grid-columns)]
(rx/of (dwu/start-undo-transaction undo-id)
(dch/update-shapes
(dwsh/update-shapes
ids
(fn [shape]
(-> shape
@ -525,9 +525,9 @@
parent-ids (->> ids (map #(cfh/get-parent-id objects %)))
undo-id (js/Symbol)]
(rx/of (dwu/start-undo-transaction undo-id)
(dch/update-shapes ids (d/patch-object changes))
(dch/update-shapes children-ids (partial fix-child-sizing objects changes))
(dch/update-shapes
(dwsh/update-shapes ids (d/patch-object changes))
(dwsh/update-shapes children-ids (partial fix-child-sizing objects changes))
(dwsh/update-shapes
parent-ids
(fn [parent objects]
(-> parent
@ -546,8 +546,7 @@
(let [undo-id (js/Symbol)]
(rx/of
(dwu/start-undo-transaction undo-id)
(dch/update-shapes
(dwsh/update-shapes
[layout-id]
(fn [shape]
(->> ids
@ -570,7 +569,7 @@
(let [undo-id (js/Symbol)]
(rx/of
(dwu/start-undo-transaction undo-id)
(dch/update-shapes
(dwsh/update-shapes
[layout-id]
(fn [shape objects]
(case mode
@ -636,7 +635,7 @@
(let [undo-id (js/Symbol)]
(rx/of
(dwu/start-undo-transaction undo-id)
(dch/update-shapes
(dwsh/update-shapes
[layout-id]
(fn [shape objects]
(let [cells (->> ids (map #(get-in shape [:layout-grid-cells %])))
@ -668,7 +667,7 @@
(let [undo-id (js/Symbol)]
(rx/of
(dwu/start-undo-transaction undo-id)
(dch/update-shapes
(dwsh/update-shapes
[layout-id]
(fn [shape objects]
(let [prev-data (-> (dm/get-in shape [:layout-grid-cells cell-id])

View file

@ -16,8 +16,8 @@
[app.common.types.container :as ctn]
[app.common.types.shape :as cts]
[app.common.types.shape-tree :as ctst]
[app.main.data.changes :as dch]
[app.main.data.comments :as dc]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.edition :as dwe]
[app.main.data.workspace.selection :as dws]
[app.main.data.workspace.state-helpers :as wsh]
@ -26,6 +26,74 @@
[beicon.v2.core :as rx]
[potok.v2.core :as ptk]))
(def ^:private update-layout-attr? #{:hidden})
(defn- add-undo-group
[changes state]
(let [undo (:workspace-undo state)
items (:items undo)
index (or (:index undo) (dec (count items)))
prev-item (when-not (or (empty? items) (= index -1))
(get items index))
undo-group (:undo-group prev-item)
add-undo-group? (and
(not (nil? undo-group))
(= (get-in changes [:redo-changes 0 :type]) :mod-obj)
(= (get-in prev-item [:redo-changes 0 :type]) :add-obj)
(contains? (:tags prev-item) :alt-duplication))] ;; This is a copy-and-move with mouse+alt
(cond-> changes add-undo-group? (assoc :undo-group undo-group))))
(defn update-shapes
([ids update-fn] (update-shapes ids update-fn nil))
([ids update-fn {:keys [reg-objects? save-undo? stack-undo? attrs ignore-tree page-id ignore-remote? ignore-touched undo-group with-objects?]
:or {reg-objects? false save-undo? true stack-undo? false ignore-remote? false ignore-touched false with-objects? false}}]
(dm/assert!
"expected a valid coll of uuid's"
(sm/check-coll-of-uuid! ids))
(dm/assert! (fn? update-fn))
(ptk/reify ::update-shapes
ptk/WatchEvent
(watch [it state _]
(let [page-id (or page-id (:current-page-id state))
objects (wsh/lookup-page-objects state page-id)
ids (into [] (filter some?) ids)
update-layout-ids
(->> ids
(map (d/getf objects))
(filter #(some update-layout-attr? (pcb/changed-attrs % objects update-fn {:attrs attrs :with-objects? with-objects?})))
(map :id))
changes (-> (pcb/empty-changes it page-id)
(pcb/set-save-undo? save-undo?)
(pcb/set-stack-undo? stack-undo?)
(cls/generate-update-shapes ids
update-fn
objects
{:attrs attrs
:ignore-tree ignore-tree
:ignore-touched ignore-touched
:with-objects? with-objects?})
(cond-> undo-group
(pcb/set-undo-group undo-group)))
changes (add-undo-group changes state)]
(rx/concat
(if (seq (:redo-changes changes))
(let [changes (cond-> changes reg-objects? (pcb/resize-parents ids))
changes (cond-> changes ignore-remote? (pcb/ignore-remote))]
(rx/of (dch/commit-changes changes)))
(rx/empty))
;; Update layouts for properties marked
(if (d/not-empty? update-layout-ids)
(rx/of (ptk/data-event :layout/update {:ids update-layout-ids}))
(rx/empty))))))))
(defn add-shape
([shape]
(add-shape shape {}))
@ -227,7 +295,7 @@
ids (if (boolean? blocked)
(into ids (->> ids (mapcat #(cfh/get-children-ids objects %))))
ids)]
(rx/of (dch/update-shapes ids update-fn {:attrs #{:blocked :hidden} :undo-group undo-group}))))))
(rx/of (update-shapes ids update-fn {:attrs #{:blocked :hidden} :undo-group undo-group}))))))
(defn toggle-visibility-selected
[]
@ -235,7 +303,7 @@
ptk/WatchEvent
(watch [_ state _]
(let [selected (wsh/lookup-selected state)]
(rx/of (dch/update-shapes selected #(update % :hidden not)))))))
(rx/of (update-shapes selected #(update % :hidden not)))))))
(defn toggle-lock-selected
[]
@ -243,7 +311,7 @@
ptk/WatchEvent
(watch [_ state _]
(let [selected (wsh/lookup-selected state)]
(rx/of (dch/update-shapes selected #(update % :blocked not)))))))
(rx/of (update-shapes selected #(update % :blocked not)))))))
;; FIXME: this need to be refactored
@ -273,8 +341,8 @@
(map (partial vector id)))))))
(d/group-by first second)
(map (fn [[page-id frame-ids]]
(dch/update-shapes frame-ids #(dissoc % :use-for-thumbnail) {:page-id page-id})))))
(update-shapes frame-ids #(dissoc % :use-for-thumbnail) {:page-id page-id})))))
;; And finally: toggle the flag value on all the selected shapes
(rx/of (dch/update-shapes selected #(update % :use-for-thumbnail not))
(rx/of (update-shapes selected #(update % :use-for-thumbnail not))
(dwu/commit-undo-transaction undo-id)))))))

View file

@ -14,7 +14,6 @@
[app.main.data.users :as du]
[app.main.data.workspace :as dw]
[app.main.data.workspace.colors :as mdc]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.drawing :as dwd]
[app.main.data.workspace.layers :as dwly]
[app.main.data.workspace.libraries :as dwl]
@ -51,12 +50,12 @@
:undo {:tooltip (ds/meta "Z")
:command (ds/c-mod "z")
:subsections [:edit]
:fn #(emit-when-no-readonly dwc/undo)}
:fn #(emit-when-no-readonly dwu/undo)}
:redo {:tooltip (ds/meta "Y")
:command [(ds/c-mod "shift+z") (ds/c-mod "y")]
:subsections [:edit]
:fn #(emit-when-no-readonly dwc/redo)}
:fn #(emit-when-no-readonly dwu/redo)}
:clear-undo {:tooltip (ds/alt "Q")
:command "alt+q"

View file

@ -14,7 +14,7 @@
[app.common.svg.shapes-builder :as csvg.shapes-builder]
[app.common.types.shape-tree :as ctst]
[app.common.uuid :as uuid]
[app.main.data.workspace.changes :as dch]
[app.main.data.changes :as dch]
[app.main.data.workspace.selection :as dws]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.data.workspace.undo :as dwu]

View file

@ -17,7 +17,6 @@
[app.common.types.modifiers :as ctm]
[app.common.uuid :as uuid]
[app.main.data.events :as ev]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.modifiers :as dwm]
@ -93,7 +92,7 @@
(some? (:current-page-id state))
(some? shape))
(rx/of
(dch/update-shapes
(dwsh/update-shapes
[id]
(fn [shape]
(let [{:keys [width height position-data]} modifiers]
@ -230,7 +229,7 @@
shape-ids (cond (cfh/text-shape? shape) [id]
(cfh/group-shape? shape) (cfh/get-children-ids objects id))]
(rx/of (dch/update-shapes shape-ids update-fn))))))
(rx/of (dwsh/update-shapes shape-ids update-fn))))))
(defn update-paragraph-attrs
[{:keys [id attrs]}]
@ -257,7 +256,7 @@
(cfh/text-shape? shape) [id]
(cfh/group-shape? shape) (cfh/get-children-ids objects id))]
(rx/of (dch/update-shapes shape-ids update-fn))))))))
(rx/of (dwsh/update-shapes shape-ids update-fn))))))))
(defn update-text-attrs
[{:keys [id attrs]}]
@ -277,7 +276,7 @@
shape-ids (cond
(cfh/text-shape? shape) [id]
(cfh/group-shape? shape) (cfh/get-children-ids objects id))]
(rx/of (dch/update-shapes shape-ids #(update-text-content % update-node? d/txt-merge attrs))))))))
(rx/of (dwsh/update-shapes shape-ids #(update-text-content % update-node? d/txt-merge attrs))))))))
(defn migrate-node
@ -337,7 +336,7 @@
(dissoc :fills)
(d/update-when :content update-content)))]
(rx/of (dch/update-shapes shape-ids update-shape)))))))
(rx/of (dwsh/update-shapes shape-ids update-shape)))))))
;; --- RESIZE UTILS
@ -390,10 +389,10 @@
(let [ids (into #{} (filter changed-text?) (keys props))]
(rx/of (dwu/start-undo-transaction undo-id)
(dch/update-shapes ids update-fn {:reg-objects? true
:stack-undo? true
:ignore-remote? true
:ignore-touched true})
(dwsh/update-shapes ids update-fn {:reg-objects? true
:stack-undo? true
:ignore-remote? true
:ignore-touched true})
(ptk/data-event :layout/update {:ids ids})
(dwu/commit-undo-transaction undo-id))))))))
@ -532,7 +531,7 @@
(watch [_ state _]
(let [position-data (::update-position-data state)]
(rx/concat
(rx/of (dch/update-shapes
(rx/of (dwsh/update-shapes
(keys position-data)
(fn [shape]
(-> shape

View file

@ -10,7 +10,8 @@
[app.common.files.helpers :as cfh]
[app.common.logging :as l]
[app.common.thumbnails :as thc]
[app.main.data.workspace.changes :as dch]
[app.main.data.changes :as dch]
[app.main.data.persistence :as-alias dps]
[app.main.data.workspace.notifications :as-alias wnt]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.rasterizer :as thr]
@ -24,9 +25,10 @@
[app.util.timers :as tm]
[app.util.webapi :as wapi]
[beicon.v2.core :as rx]
[cuerdas.core :as str]
[potok.v2.core :as ptk]))
(l/set-level! :info)
(l/set-level! :warn)
(declare update-thumbnail)
@ -65,10 +67,9 @@
ptk/EffectEvent
(effect [_ _ _]
(l/dbg :hint "request thumbnail" :requester requester :file-id file-id :page-id page-id :shape-id shape-id :tag tag)
(q/enqueue-unique
queue
(create-request file-id page-id shape-id tag)
(partial find-request file-id page-id shape-id tag))))))
(q/enqueue-unique queue
(create-request file-id page-id shape-id tag)
(partial find-request file-id page-id shape-id tag))))))
;; This function first renders the HTML calling `render/render-frame` that
;; returns HTML as a string, then we send that data to the iframe rasterizer
@ -92,24 +93,36 @@
(defn clear-thumbnail
([file-id page-id frame-id tag]
(clear-thumbnail (thc/fmt-object-id file-id page-id frame-id tag)))
([object-id]
(let [emit-rpc? (volatile! false)]
(clear-thumbnail file-id (thc/fmt-object-id file-id page-id frame-id tag)))
([file-id object-id]
(let [pending (volatile! false)]
(ptk/reify ::clear-thumbnail
cljs.core/IDeref
(-deref [_] object-id)
ptk/UpdateEvent
(update [_ state]
(let [uri (dm/get-in state [:workspace-thumbnails object-id])]
(if (some? uri)
(do
(l/dbg :hint "clear thumbnail" :object-id object-id)
(vreset! emit-rpc? true)
(tm/schedule-on-idle (partial wapi/revoke-uri uri))
(update state :workspace-thumbnails dissoc object-id))
(update state :workspace-thumbnails
(fn [thumbs]
(if-let [uri (get thumbs object-id)]
(do (vreset! pending uri)
(dissoc thumbs object-id))
thumbs))))
state)))))))
ptk/WatchEvent
(watch [_ _ _]
(if-let [uri @pending]
(do
(l/trc :hint "clear-thumbnail" :uri uri)
(when (str/starts-with? uri "blob:")
(tm/schedule-on-idle (partial wapi/revoke-uri uri)))
(let [params {:file-id file-id
:object-id object-id}]
(->> (rp/cmd! :delete-file-object-thumbnail params)
(rx/catch rx/empty)
(rx/ignore))))
(rx/empty)))))))
(defn- assoc-thumbnail
[object-id uri]
@ -181,7 +194,7 @@
(defn- extract-root-frame-changes
"Process a changes set in a commit to extract the frames that are changing"
[page-id [event [old-data new-data]]]
(let [changes (-> event deref :changes)
(let [changes (:changes event)
extract-ids
(fn [{:keys [page-id type] :as change}]
@ -239,58 +252,53 @@
(rx/buffer 2 1)
(rx/share))
local-changes-s
;; All commits stream, indepentendly of the source of the commit
all-commits-s
(->> stream
(rx/filter dch/commit-changes?)
(rx/with-latest-from workspace-data-s)
(rx/merge-map (partial extract-root-frame-changes page-id))
(rx/tap #(l/trc :hint "incoming change" :origin "local" :frame-id (dm/str %))))
notification-changes-s
(->> stream
(rx/filter (ptk/type? ::wnt/handle-file-change))
(rx/filter dch/commit?)
(rx/map deref)
(rx/observe-on :async)
(rx/with-latest-from workspace-data-s)
(rx/merge-map (partial extract-root-frame-changes page-id))
(rx/tap #(l/trc :hint "incoming change" :origin "notifications" :frame-id (dm/str %))))
persistence-changes-s
(->> stream
(rx/filter (ptk/type? ::update))
(rx/map deref)
(rx/filter (fn [[file-id page-id]]
(and (= file-id file-id)
(= page-id page-id))))
(rx/map (fn [[_ _ frame-id]] frame-id))
(rx/tap #(l/trc :hint "incoming change" :origin "persistence" :frame-id (dm/str %))))
all-changes-s
(->> (rx/merge
;; LOCAL CHANGES
local-changes-s
;; NOTIFICATIONS CHANGES
notification-changes-s
;; PERSISTENCE CHANGES
persistence-changes-s)
(rx/tap #(l/trc :hint "inconming change" :origin "local" :frame-id (dm/str %)))
(rx/share))
;; BUFFER NOTIFIER (window of 5s of inactivity)
local-commits-s
(->> stream
(rx/filter dch/commit?)
(rx/map deref)
(rx/filter #(= :local (:source %)))
(rx/observe-on :async)
(rx/with-latest-from workspace-data-s)
(rx/merge-map (partial extract-root-frame-changes page-id))
(rx/tap #(l/trc :hint "inconming change" :origin "local" :frame-id (dm/str %)))
(rx/share))
;; BUFFER NOTIFIER: only on local changes, remote changes
;; we expect to receive thumbnail uri once it is
;; generated va notifications subsystem
notifier-s
(->> all-changes-s
(rx/debounce 1000)
(->> stream
(rx/filter (ptk/type? ::dps/commit-persisted))
(rx/map deref)
(rx/observe-on :async)
(rx/with-latest-from workspace-data-s)
(rx/merge-map (partial extract-root-frame-changes page-id))
(rx/tap #(l/trc :hint "inconming change" :origin "local" :frame-id (dm/str %)))
(rx/debounce 5000)
(rx/tap #(l/trc :hint "buffer initialized")))]
(->> (rx/merge
;; Perform instant thumbnail cleaning of affected frames
;; and interrupt any ongoing update-thumbnail process
;; related to current frame-id
(->> all-changes-s
(rx/map #(clear-thumbnail file-id page-id % "frame")))
(->> all-commits-s
(rx/map (fn [frame-id]
(clear-thumbnail file-id page-id frame-id "frame"))))
;; Generate thumbnails in batchs, once user becomes
;; inactive for some instant
(->> all-changes-s
;; inactive for some instant only for local changes
(->> local-commits-s
(rx/buffer-until notifier-s)
(rx/mapcat #(into #{} %))
(rx/map #(request-thumbnail file-id page-id % "frame" "watch-state-changes"))))

View file

@ -25,7 +25,7 @@
[app.common.types.modifiers :as ctm]
[app.common.types.shape-tree :as ctst]
[app.common.types.shape.layout :as ctl]
[app.main.data.workspace.changes :as dch]
[app.main.data.changes :as dch]
[app.main.data.workspace.collapse :as dwc]
[app.main.data.workspace.modifiers :as dwm]
[app.main.data.workspace.selection :as dws]

View file

@ -11,18 +11,18 @@
[app.common.files.changes :as cpc]
[app.common.logging :as log]
[app.common.schema :as sm]
[app.common.types.shape.layout :as ctl]
[app.main.data.changes :as dch]
[app.main.data.workspace.state-helpers :as wsh]
[app.util.router :as rt]
[app.util.time :as dt]
[beicon.v2.core :as rx]
[potok.v2.core :as ptk]))
(def discard-transaction-time-millis (* 20 1000))
;; Change this to :info :debug or :trace to debug this module
(log/set-level! :warn)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Undo / Redo
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def discard-transaction-time-millis (* 20 1000))
(def ^:private
schema:undo-entry
@ -44,7 +44,6 @@
(subvec undo (- cnt MAX-UNDO-SIZE))
undo)))
;; TODO: Review the necessity of this method
(defn materialize-undo
[_changes index]
(ptk/reify ::materialize-undo
@ -84,8 +83,7 @@
(-> state
(update-in [:workspace-undo :transaction :undo-changes] #(into undo-changes %))
(update-in [:workspace-undo :transaction :redo-changes] #(into % redo-changes))
(cond->
(nil? (get-in state [:workspace-undo :transaction :undo-group]))
(cond-> (nil? (get-in state [:workspace-undo :transaction :undo-group]))
(assoc-in [:workspace-undo :transaction :undo-group] undo-group))
(assoc-in [:workspace-undo :transaction :tags] tags)))
@ -182,3 +180,125 @@
(rx/tap #(js/console.warn (dm/str "FORCE COMMIT TRANSACTION AFTER " (second %) "MS")))
(rx/map first)
(rx/map commit-undo-transaction))))))
(defn undo-to-index
"Repeat undoing or redoing until dest-index is reached."
[dest-index]
(ptk/reify ::undo-to-index
ptk/WatchEvent
(watch [it state _]
(let [objects (wsh/lookup-page-objects state)
edition (get-in state [:workspace-local :edition])
drawing (get state :workspace-drawing)]
(when-not (and (or (some? edition) (some? (:object drawing)))
(not (ctl/grid-layout? objects edition)))
(let [undo (:workspace-undo state)
items (:items undo)
index (or (:index undo) (dec (count items)))]
(when (and (some? items)
(<= -1 dest-index (dec (count items))))
(let [changes (vec (apply concat
(cond
(< dest-index index)
(->> (subvec items (inc dest-index) (inc index))
(reverse)
(map :undo-changes))
(> dest-index index)
(->> (subvec items (inc index) (inc dest-index))
(map :redo-changes))
:else [])))]
(when (seq changes)
(rx/of (materialize-undo changes dest-index)
(dch/commit-changes {:redo-changes changes
:undo-changes []
:origin it
:save-undo? false})))))))))))
(declare ^:private assure-valid-current-page)
(def undo
(ptk/reify ::undo
ptk/WatchEvent
(watch [it state _]
(let [objects (wsh/lookup-page-objects state)
edition (get-in state [:workspace-local :edition])
drawing (get state :workspace-drawing)]
;; Editors handle their own undo's
(when (or (and (nil? edition) (nil? (:object drawing)))
(ctl/grid-layout? objects edition))
(let [undo (:workspace-undo state)
items (:items undo)
index (or (:index undo) (dec (count items)))]
(when-not (or (empty? items) (= index -1))
(let [item (get items index)
changes (:undo-changes item)
undo-group (:undo-group item)
find-first-group-idx
(fn [index]
(if (= (dm/get-in items [index :undo-group]) undo-group)
(recur (dec index))
(inc index)))
undo-group-index
(when undo-group
(find-first-group-idx index))]
(if undo-group
(rx/of (undo-to-index (dec undo-group-index)))
(rx/of (materialize-undo changes (dec index))
(dch/commit-changes {:redo-changes changes
:undo-changes []
:save-undo? false
:origin it})
(assure-valid-current-page)))))))))))
(def redo
(ptk/reify ::redo
ptk/WatchEvent
(watch [it state _]
(let [objects (wsh/lookup-page-objects state)
edition (get-in state [:workspace-local :edition])
drawing (get state :workspace-drawing)]
(when (and (or (nil? edition) (ctl/grid-layout? objects edition))
(or (empty? drawing) (= :curve (:tool drawing))))
(let [undo (:workspace-undo state)
items (:items undo)
index (or (:index undo) (dec (count items)))]
(when-not (or (empty? items) (= index (dec (count items))))
(let [item (get items (inc index))
changes (:redo-changes item)
undo-group (:undo-group item)
find-last-group-idx (fn flgidx [index]
(let [item (get items index)]
(if (= (:undo-group item) undo-group)
(flgidx (inc index))
(dec index))))
redo-group-index (when undo-group
(find-last-group-idx (inc index)))]
(if undo-group
(rx/of (undo-to-index redo-group-index))
(rx/of (materialize-undo changes (inc index))
(dch/commit-changes {:redo-changes changes
:undo-changes []
:origin it
:save-undo? false})))))))))))
(defn- assure-valid-current-page
[]
(ptk/reify ::assure-valid-current-page
ptk/WatchEvent
(watch [_ state _]
(let [current_page (:current-page-id state)
pages (get-in state [:workspace-data :pages])
exists? (some #(= current_page %) pages)
project-id (:current-project-id state)
file-id (:current-file-id state)
pparams {:file-id file-id :project-id project-id}
qparams {:page-id (first pages)}]
(if exists?
(rx/empty)
(rx/of (rt/nav :workspace pparams qparams)))))))

View file

@ -45,6 +45,9 @@
(def export
(l/derived :export st/state))
(def persistence
(l/derived :persistence st/state))
;; ---- Dashboard refs
(def dashboard-local

View file

@ -34,8 +34,6 @@
(def debug-exclude-events
#{:app.main.data.workspace.notifications/handle-pointer-update
:app.main.data.workspace.notifications/handle-pointer-send
:app.main.data.workspace.persistence/update-persistence-status
:app.main.data.workspace.changes/update-indices
:app.main.data.websocket/send-message
:app.main.data.workspace.selection/change-hover-state})
@ -65,7 +63,7 @@
:app.util.router/assign-exception}]
(->> (rx/merge
(->> stream
(rx/filter (ptk/type? :app.main.data.workspace.changes/commit-changes))
(rx/filter (ptk/type? :app.main.data.changes/commit))
(rx/map #(-> % deref :hint-origin)))
(rx/map ptk/type stream))
(rx/filter #(not (contains? omitset %)))

View file

@ -101,7 +101,7 @@
(cond-> (and value is-checkbox?) (assoc :default-checked value))
(cond-> (and touched? (:message error)) (assoc "aria-invalid" "true"
"aria-describedby" (dm/str "error-" input-name)))
(obj/clj->props))
(obj/map->obj obj/prop-key-fn))
checked? (and is-checkbox? (= value true))
show-valid? (and show-success? touched? (not error))
@ -201,7 +201,7 @@
:on-blur on-blur
;; :placeholder label
:on-change on-change)
(obj/clj->props))]
(obj/map->obj obj/prop-key-fn))]
[:div {:class (dm/str klass " " (stl/css :textarea-wrapper))}
[:label {:class (stl/css :textarea-label)} label]

View file

@ -10,9 +10,9 @@
[app.common.data.macros :as dm]
[app.main.data.messages :as msg]
[app.main.data.modal :as modal]
[app.main.data.persistence :as dps]
[app.main.data.workspace :as dw]
[app.main.data.workspace.colors :as dc]
[app.main.data.workspace.persistence :as dwp]
[app.main.features :as features]
[app.main.refs :as refs]
[app.main.store :as st]
@ -177,6 +177,9 @@
background-color (:background-color wglobal)]
(mf/with-effect []
(st/emit! (dps/initialize-persistence)))
;; Setting the layout preset by its name
(mf/with-effect [layout-name]
(st/emit! (dw/initialize-layout layout-name)))
@ -188,7 +191,7 @@
(mf/with-effect [project-id file-id]
(st/emit! (dw/initialize-file project-id file-id))
(fn []
(st/emit! ::dwp/force-persist
(st/emit! ::dps/force-persist
(dc/stop-picker)
(modal/hide)
msg/hide

View file

@ -19,9 +19,9 @@
[app.main.data.shortcuts :as scd]
[app.main.data.users :as du]
[app.main.data.workspace :as dw]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.shortcuts :as sc]
[app.main.data.workspace.undo :as dwu]
[app.main.features :as features]
[app.main.refs :as refs]
[app.main.store :as st]
@ -414,8 +414,8 @@
::mf/wrap [mf/memo]}
[{:keys [on-close]}]
(let [select-all (mf/use-fn #(st/emit! (dw/select-all)))
undo (mf/use-fn #(st/emit! dwc/undo))
redo (mf/use-fn #(st/emit! dwc/redo))]
undo (mf/use-fn #(st/emit! dwu/undo))
redo (mf/use-fn #(st/emit! dwu/redo))]
[:& dropdown-menu {:show true
:list-class (stl/css-case :sub-menu true
:edit true)

View file

@ -25,38 +25,35 @@
[okulary.core :as l]
[rumext.v2 :as mf]))
(def ref:workspace-persistence
(l/derived :workspace-persistence st/state))
(def ref:persistence-status
(l/derived :status refs/persistence))
;; --- Persistence state Widget
(mf/defc persistence-state-widget
{::mf/wrap [mf/memo]}
{::mf/wrap [mf/memo]
::mf/wrap-props false}
[]
(let [{:keys [status]} (mf/deref ref:workspace-persistence)]
(let [status (mf/deref ref:persistence-status)]
[:div {:class (stl/css :persistence-status-widget)}
(case status
:pending
[:div {:class (stl/css-case :status-icon true
:pending-status true)
[:div {:class (stl/css :status-icon :pending-status)
:title (tr "workspace.header.unsaved")}
i/status-alert]
:saving
[:div {:class (stl/css-case :status-icon true
:saving-status true)
[:div {:class (stl/css :status-icon :saving-status)
:title (tr "workspace.header.saving")}
i/status-update]
:saved
[:div {:class (stl/css-case :status-icon true
:saved-status true)
[:div {:class (stl/css :status-icon :saved-status)
:title (tr "workspace.header.saved")}
i/status-tick]
:error
[:div {:class (stl/css-case :status-icon true
:error-status true)
[:div {:class (stl/css :status-icon :error-status)
:title "There was an error saving the data. Please refresh if this persists."}
i/status-wrong]

View file

@ -11,7 +11,7 @@
[app.common.data.macros :as dm]
[app.main.data.events :as ev]
[app.main.data.workspace :as dw]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.undo :as dwu]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.icons :as i]
@ -304,7 +304,7 @@
:show-detail @show-detail?)
:on-pointer-enter #(reset! hover? true)
:on-pointer-leave #(reset! hover? false)
:on-click #(st/emit! (dwc/undo-to-index idx-entry))}
:on-click #(st/emit! (dwu/undo-to-index idx-entry))}
[:div {:class (stl/css :history-entry-summary)}
[:div {:class (stl/css :history-entry-summary-icon)}

View file

@ -8,7 +8,7 @@
(:require-macros [app.main.style :as stl])
(:require
[app.common.uuid :as uuid]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.shapes :as dwsh]
[app.main.store :as st]
[app.main.ui.components.numeric-input :refer [numeric-input*]]
[app.main.ui.components.title-bar :refer [title-bar]]
@ -44,7 +44,7 @@
(mf/use-fn
(mf/deps ids)
(fn [update-fn]
(st/emit! (dch/update-shapes ids update-fn))))
(st/emit! (dwsh/update-shapes ids update-fn))))
handle-add
(mf/use-fn

View file

@ -11,7 +11,7 @@
[app.common.geom.rect :as grc]
[app.common.geom.shapes :as gsh]
[app.common.uuid :as uuid]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.shapes :as dwsh]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.components.select :refer [select]]
@ -102,7 +102,7 @@
[:constraints-h :center])
nil ())]
(st/emit! (dch/update-shapes
(st/emit! (dwsh/update-shapes
ids
#(assoc % constraint new-value))))))
@ -111,7 +111,7 @@
(mf/deps ids)
(fn [value]
(when-not (str/empty? value)
(st/emit! (dch/update-shapes
(st/emit! (dwsh/update-shapes
ids
#(assoc % :constraints-h (keyword value)))))))
@ -120,7 +120,7 @@
(mf/deps ids)
(fn [value]
(when-not (str/empty? value)
(st/emit! (dch/update-shapes
(st/emit! (dwsh/update-shapes
ids
#(assoc % :constraints-v (keyword value)))))))
@ -128,7 +128,7 @@
(mf/use-fn
(mf/deps ids)
(fn [_]
(st/emit! (dch/update-shapes ids #(update % :fixed-scroll not)))))
(st/emit! (dwsh/update-shapes ids #(update % :fixed-scroll not)))))
options-h
(mf/with-memo [constraints-h]

View file

@ -9,7 +9,7 @@
(:require
[app.common.data :as d]
[app.main.data.exports :as de]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.refs :as refs]
[app.main.store :as st]
@ -97,9 +97,9 @@
(mf/deps ids)
(fn []
(let [xspec {:type :png :suffix "" :scale 1}]
(st/emit! (dch/update-shapes ids
(fn [shape]
(assoc shape :exports (into [xspec] (:exports shape)))))))))
(st/emit! (dwsh/update-shapes ids
(fn [shape]
(assoc shape :exports (into [xspec] (:exports shape)))))))))
delete-export
(mf/use-fn
@ -110,16 +110,16 @@
(mapv second)))
remove (fn [shape] (update shape :exports remove-fill-by-index position))]
(st/emit! (dch/update-shapes ids remove)))))
(st/emit! (dwsh/update-shapes ids remove)))))
on-scale-change
(mf/use-fn
(mf/deps ids)
(fn [index event]
(let [scale (d/parse-double event)]
(st/emit! (dch/update-shapes ids
(fn [shape]
(assoc-in shape [:exports index :scale] scale)))))))
(st/emit! (dwsh/update-shapes ids
(fn [shape]
(assoc-in shape [:exports index :scale] scale)))))))
on-suffix-change
(mf/use-fn
@ -129,26 +129,26 @@
index (-> (dom/get-current-target event)
(dom/get-data "value")
(d/parse-integer))]
(st/emit! (dch/update-shapes ids
(fn [shape]
(assoc-in shape [:exports index :suffix] value)))))))
(st/emit! (dwsh/update-shapes ids
(fn [shape]
(assoc-in shape [:exports index :suffix] value)))))))
on-type-change
(mf/use-fn
(mf/deps ids)
(fn [index event]
(let [type (keyword event)]
(st/emit! (dch/update-shapes ids
(fn [shape]
(assoc-in shape [:exports index :type] type)))))))
(st/emit! (dwsh/update-shapes ids
(fn [shape]
(assoc-in shape [:exports index :type] type)))))))
on-remove-all
(mf/use-fn
(mf/deps ids)
(fn []
(st/emit! (dch/update-shapes ids
(fn [shape]
(assoc shape :exports []))))))
(st/emit! (dwsh/update-shapes ids
(fn [shape]
(assoc shape :exports []))))))
manage-key-down
(mf/use-fn
(fn [event]

View file

@ -10,7 +10,7 @@
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.main.data.workspace :as dw]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.shapes :as dwsh]
[app.main.store :as st]
[app.main.ui.components.numeric-input :refer [numeric-input*]]
[app.main.ui.components.select :refer [select]]
@ -55,7 +55,7 @@
(mf/use-fn
(mf/deps ids)
(fn [prop value]
(st/emit! (dch/update-shapes ids #(assoc % prop value)))))
(st/emit! (dwsh/update-shapes ids #(assoc % prop value)))))
handle-change-blend-mode
(mf/use-fn

View file

@ -13,8 +13,8 @@
[app.common.types.shape.radius :as ctsr]
[app.main.constants :refer [size-presets]]
[app.main.data.workspace :as udw]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.interactions :as dwi]
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.undo :as dwu]
[app.main.refs :as refs]
[app.main.store :as st]
@ -249,13 +249,13 @@
(mf/use-fn
(mf/deps ids-with-children)
(fn [update-fn]
(dch/update-shapes ids-with-children
(fn [shape]
(if (ctsr/has-radius? shape)
(update-fn shape)
shape))
{:reg-objects? true
:attrs [:rx :ry :r1 :r2 :r3 :r4]})))
(dwsh/update-shapes ids-with-children
(fn [shape]
(if (ctsr/has-radius? shape)
(update-fn shape)
shape))
{:reg-objects? true
:attrs [:rx :ry :r1 :r2 :r3 :r4]})))
on-switch-to-radius-1
(mf/use-fn
@ -317,7 +317,7 @@
(mf/deps ids)
(fn [event]
(let [value (-> event dom/get-target dom/checked?)]
(st/emit! (dch/update-shapes ids (fn [shape] (assoc shape :show-content (not value))))))))
(st/emit! (dwsh/update-shapes ids (fn [shape] (assoc shape :show-content (not value))))))))
on-change-show-in-viewer
(mf/use-fn
@ -327,7 +327,7 @@
undo-id (js/Symbol)]
(do
(st/emit! (dwu/start-undo-transaction undo-id)
(dch/update-shapes ids (fn [shape] (assoc shape :hide-in-viewer (not value)))))
(dwsh/update-shapes ids (fn [shape] (assoc shape :hide-in-viewer (not value)))))
(when-not value
;; when a frame is no longer shown in view mode, cannot have
@ -399,7 +399,7 @@
:placeholder (if (= :multiple (:width values)) (tr "settings.multiple") "--")
:on-change on-width-change
:disabled disabled-width-sizing?
:className (stl/css :numeric-input)
:class (stl/css :numeric-input)
:value (:width values)}]]
[:div {:class (stl/css-case :height true
:disabled disabled-height-sizing?)
@ -410,7 +410,7 @@
:placeholder (if (= :multiple (:height values)) (tr "settings.multiple") "--")
:on-change on-height-change
:disabled disabled-height-sizing?
:className (stl/css :numeric-input)
:class (stl/css :numeric-input)
:value (:height values)}]]
[:button {:class (stl/css-case
:lock-size-btn true
@ -430,7 +430,7 @@
:placeholder (if (= :multiple (:x values)) (tr "settings.multiple") "--")
:on-change on-pos-x-change
:disabled disabled-position-x?
:className (stl/css :numeric-input)
:class (stl/css :numeric-input)
:value (:x values)}]]
[:div {:class (stl/css-case :y-position true
@ -441,7 +441,7 @@
:placeholder (if (= :multiple (:y values)) (tr "settings.multiple") "--")
:disabled disabled-position-y?
:on-change on-pos-y-change
:className (stl/css :numeric-input)
:class (stl/css :numeric-input)
:value (:y values)}]]])
(when (or (options :rotation) (options :radius))
[:div {:class (stl/css :rotation-radius)}
@ -457,7 +457,7 @@
:data-wrap true
:placeholder (if (= :multiple (:rotation values)) (tr "settings.multiple") "--")
:on-change on-rotation-change
:className (stl/css :numeric-input)
:class (stl/css :numeric-input)
:value (:rotation values)}]])
(when (options :radius)
@ -473,7 +473,7 @@
:ref radius-input-ref
:min 0
:on-change on-radius-1-change
:className (stl/css :numeric-input)
:class (stl/css :numeric-input)
:value (:rx values)}]]
@radius-multi?
@ -485,7 +485,7 @@
:placeholder "Mixed"
:min 0
:on-change on-radius-multi-change
:className (stl/css :numeric-input)
:class (stl/css :numeric-input)
:value (if all-equal? (:rx values) nil)}]]
@ -497,7 +497,7 @@
{:placeholder "--"
:min 0
:on-change on-radius-r1-change
:className (stl/css :numeric-input)
:class (stl/css :numeric-input)
:value (:r1 values)}]]
[:div {:class (stl/css :small-input)
@ -506,7 +506,7 @@
{:placeholder "--"
:min 0
:on-change on-radius-r2-change
:className (stl/css :numeric-input)
:class (stl/css :numeric-input)
:value (:r2 values)}]]
[:div {:class (stl/css :small-input)
@ -515,7 +515,7 @@
{:placeholder "--"
:min 0
:on-change on-radius-r4-change
:className (stl/css :numeric-input)
:class (stl/css :numeric-input)
:value (:r4 values)}]]
[:div {:class (stl/css :small-input)
@ -524,7 +524,7 @@
{:placeholder "--"
:min 0
:on-change on-radius-r3-change
:className (stl/css :numeric-input)
:class (stl/css :numeric-input)
:value (:r3 values)}]]])]
[:button {:class (stl/css-case :radius-mode true
:selected (= radius-mode :radius-4))

View file

@ -12,8 +12,8 @@
[app.common.data.macros :as dm]
[app.common.math :as mth]
[app.common.uuid :as uuid]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.colors :as dc]
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.undo :as dwu]
[app.main.store :as st]
[app.main.ui.components.numeric-input :refer [numeric-input*]]
@ -73,7 +73,7 @@
(mf/use-fn
(mf/deps ids index)
(fn []
(st/emit! (dch/update-shapes ids #(update % :shadow remove-shadow-by-index index)))))
(st/emit! (dwsh/update-shapes ids #(update % :shadow remove-shadow-by-index index)))))
on-drop
(mf/use-fn
@ -102,7 +102,7 @@
([index attr update-ref]
(fn [value]
(when (mth/finite? value)
(st/emit! (dch/update-shapes ids #(assoc-in % [:shadow index attr] value)))
(st/emit! (dwsh/update-shapes ids #(assoc-in % [:shadow index attr] value)))
(when-let [update-node (and update-ref (mf/ref-val update-ref))]
(dom/set-value! update-node value))))))
@ -110,7 +110,7 @@
(mf/use-fn
(mf/deps ids index)
(fn [color]
(st/emit! (dch/update-shapes
(st/emit! (dwsh/update-shapes
ids
#(assoc-in % [:shadow index :color] (d/without-nils color))))))
@ -119,7 +119,7 @@
(mf/deps ids index value)
(fn [_color _opacity]
(when-not (string? (:color value))
(st/emit! (dch/update-shapes
(st/emit! (dwsh/update-shapes
ids
#(assoc-in % [:shadow index :color]
(dissoc (:color value) :id :file-id)))))))
@ -128,7 +128,7 @@
(mf/use-fn
(mf/deps ids index)
(fn []
(st/emit! (dch/update-shapes ids #(update-in % [:shadow index :hidden] not)))))
(st/emit! (dwsh/update-shapes ids #(update-in % [:shadow index :hidden] not)))))
on-toggle-open-shadow
(fn []
@ -139,7 +139,7 @@
(mf/deps ids index)
(fn [event]
(let [value (keyword event)]
(st/emit! (dch/update-shapes ids #(assoc-in % [:shadow index :style] value))))))
(st/emit! (dwsh/update-shapes ids #(assoc-in % [:shadow index :style] value))))))
type-options [{:value "drop-shadow" :label (tr "workspace.options.shadow-options.drop-shadow")}
{:value "inner-shadow" :label (tr "workspace.options.shadow-options.inner-shadow")}]
@ -268,7 +268,7 @@
(mf/use-fn
(mf/deps ids)
(fn []
(st/emit! (dch/update-shapes ids #(dissoc % :shadow)))))
(st/emit! (dwsh/update-shapes ids #(dissoc % :shadow)))))
handle-reorder
(mf/use-fn

View file

@ -8,7 +8,7 @@
(:require-macros [app.main.style :as stl])
(:require
[app.common.data :as d]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.shapes :as dwsh]
[app.main.store :as st]
[app.main.ui.components.title-bar :refer [title-bar]]
[app.main.ui.icons :as i]
@ -66,7 +66,7 @@
(fn [attr value]
(let [update-fn
(fn [shape] (assoc-in shape (concat [:svg-attrs] attr) value))]
(st/emit! (dch/update-shapes ids update-fn)))))
(st/emit! (dwsh/update-shapes ids update-fn)))))
handle-delete
(mf/use-fn
@ -81,7 +81,7 @@
(empty? (get-in shape [:svg-attrs :style]))
(update :svg-attrs dissoc :style))]
shape))]
(st/emit! (dch/update-shapes ids update-fn)))))]
(st/emit! (dwsh/update-shapes ids update-fn)))))]
(when-not (empty? attrs)
[:div {:class (stl/css :element-set)}

View file

@ -10,8 +10,8 @@
[app.common.data :as d]
[app.common.text :as txt]
[app.common.uuid :as uuid]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.shortcuts :as sc]
[app.main.data.workspace.texts :as dwt]
[app.main.data.workspace.undo :as dwu]
@ -127,8 +127,8 @@
grow-type (keyword value)]
(st/emit!
(dwu/start-undo-transaction uid)
(dch/update-shapes ids #(assoc % :grow-type grow-type)))
;; We asynchronously commit so every sychronous event is resolved first and inside the transaction
(dwsh/update-shapes ids #(assoc % :grow-type grow-type)))
;; We asynchronously commit so every sychronous event is resolved first and inside the transaction
(ts/schedule #(st/emit! (dwu/commit-undo-transaction uid))))
(when (some? on-blur) (on-blur))))]

View file

@ -14,7 +14,7 @@
[app.common.text :as txt]
[app.common.types.shape :as cts]
[app.common.uuid :as uuid]
[app.main.data.workspace.changes :as ch]
[app.main.data.changes :as ch]
[app.main.data.workspace.groups :as dwg]
[app.main.data.workspace.media :as dwm]
[app.main.store :as st]

View file

@ -18,7 +18,6 @@
[app.common.types.shape.radius :as ctsr]
[app.common.uuid :as uuid]
[app.main.data.workspace :as udw]
[app.main.data.workspace.changes :as dwc]
[app.main.data.workspace.selection :as dws]
[app.main.data.workspace.shape-layout :as dwsl]
[app.main.data.workspace.shapes :as dwsh]
@ -118,25 +117,25 @@
:get #(-> % proxy->shape :name)
:set (fn [self value]
(let [id (obj/get self "$id")]
(st/emit! (dwc/update-shapes [id] #(assoc % :name value)))))}
(st/emit! (dwsh/update-shapes [id] #(assoc % :name value)))))}
{:name "blocked"
:get #(-> % proxy->shape :blocked boolean)
:set (fn [self value]
(let [id (obj/get self "$id")]
(st/emit! (dwc/update-shapes [id] #(assoc % :blocked value)))))}
(st/emit! (dwsh/update-shapes [id] #(assoc % :blocked value)))))}
{:name "hidden"
:get #(-> % proxy->shape :hidden boolean)
:set (fn [self value]
(let [id (obj/get self "$id")]
(st/emit! (dwc/update-shapes [id] #(assoc % :hidden value)))))}
(st/emit! (dwsh/update-shapes [id] #(assoc % :hidden value)))))}
{:name "proportionLock"
:get #(-> % proxy->shape :proportion-lock boolean)
:set (fn [self value]
(let [id (obj/get self "$id")]
(st/emit! (dwc/update-shapes [id] #(assoc % :proportion-lock value)))))}
(st/emit! (dwsh/update-shapes [id] #(assoc % :proportion-lock value)))))}
{:name "constraintsHorizontal"
:get #(-> % proxy->shape :constraints-h d/name)
@ -144,7 +143,7 @@
(let [id (obj/get self "$id")
value (keyword value)]
(when (contains? cts/horizontal-constraint-types value)
(st/emit! (dwc/update-shapes [id] #(assoc % :constraints-h value))))))}
(st/emit! (dwsh/update-shapes [id] #(assoc % :constraints-h value))))))}
{:name "constraintsVertical"
:get #(-> % proxy->shape :constraints-v d/name)
@ -152,7 +151,7 @@
(let [id (obj/get self "$id")
value (keyword value)]
(when (contains? cts/vertical-constraint-types value)
(st/emit! (dwc/update-shapes [id] #(assoc % :constraints-v value))))))}
(st/emit! (dwsh/update-shapes [id] #(assoc % :constraints-v value))))))}
{:name "borderRadius"
:get #(-> % proxy->shape :rx)
@ -161,8 +160,8 @@
shape (proxy->shape self)]
(when (us/safe-int? value)
(when (or (not (ctsr/has-radius? shape)) (ctsr/radius-4? shape))
(st/emit! (dwc/update-shapes [id] ctsr/switch-to-radius-1)))
(st/emit! (dwc/update-shapes [id] #(ctsr/set-radius-1 % value))))))}
(st/emit! (dwsh/update-shapes [id] ctsr/switch-to-radius-1)))
(st/emit! (dwsh/update-shapes [id] #(ctsr/set-radius-1 % value))))))}
{:name "borderRadiusTopLeft"
:get #(-> % proxy->shape :r1)
@ -171,8 +170,8 @@
shape (proxy->shape self)]
(when (us/safe-int? value)
(when (or (not (ctsr/has-radius? shape)) (not (ctsr/radius-4? shape)))
(st/emit! (dwc/update-shapes [id] ctsr/switch-to-radius-4)))
(st/emit! (dwc/update-shapes [id] #(ctsr/set-radius-4 % :r1 value))))))}
(st/emit! (dwsh/update-shapes [id] ctsr/switch-to-radius-4)))
(st/emit! (dwsh/update-shapes [id] #(ctsr/set-radius-4 % :r1 value))))))}
{:name "borderRadiusTopRight"
:get #(-> % proxy->shape :r2)
@ -181,8 +180,8 @@
shape (proxy->shape self)]
(when (us/safe-int? value)
(when (or (not (ctsr/has-radius? shape)) (not (ctsr/radius-4? shape)))
(st/emit! (dwc/update-shapes [id] ctsr/switch-to-radius-4)))
(st/emit! (dwc/update-shapes [id] #(ctsr/set-radius-4 % :r2 value))))))}
(st/emit! (dwsh/update-shapes [id] ctsr/switch-to-radius-4)))
(st/emit! (dwsh/update-shapes [id] #(ctsr/set-radius-4 % :r2 value))))))}
{:name "borderRadiusBottomRight"
:get #(-> % proxy->shape :r3)
@ -191,8 +190,8 @@
shape (proxy->shape self)]
(when (us/safe-int? value)
(when (or (not (ctsr/has-radius? shape)) (not (ctsr/radius-4? shape)))
(st/emit! (dwc/update-shapes [id] ctsr/switch-to-radius-4)))
(st/emit! (dwc/update-shapes [id] #(ctsr/set-radius-4 % :r3 value))))))}
(st/emit! (dwsh/update-shapes [id] ctsr/switch-to-radius-4)))
(st/emit! (dwsh/update-shapes [id] #(ctsr/set-radius-4 % :r3 value))))))}
{:name "borderRadiusBottomLeft"
:get #(-> % proxy->shape :r4)
@ -201,15 +200,15 @@
shape (proxy->shape self)]
(when (us/safe-int? value)
(when (or (not (ctsr/has-radius? shape)) (not (ctsr/radius-4? shape)))
(st/emit! (dwc/update-shapes [id] ctsr/switch-to-radius-4)))
(st/emit! (dwc/update-shapes [id] #(ctsr/set-radius-4 % :r4 value))))))}
(st/emit! (dwsh/update-shapes [id] ctsr/switch-to-radius-4)))
(st/emit! (dwsh/update-shapes [id] #(ctsr/set-radius-4 % :r4 value))))))}
{:name "opacity"
:get #(-> % proxy->shape :opacity)
:set (fn [self value]
(let [id (obj/get self "$id")]
(when (and (us/safe-number? value) (>= value 0) (<= value 1))
(st/emit! (dwc/update-shapes [id] #(assoc % :opacity value))))))}
(st/emit! (dwsh/update-shapes [id] #(assoc % :opacity value))))))}
{:name "blendMode"
:get #(-> % proxy->shape :blend-mode (d/nilv :normal) d/name)
@ -217,7 +216,7 @@
(let [id (obj/get self "$id")
value (keyword value)]
(when (contains? cts/blend-modes value)
(st/emit! (dwc/update-shapes [id] #(assoc % :blend-mode value))))))}
(st/emit! (dwsh/update-shapes [id] #(assoc % :blend-mode value))))))}
{:name "shadows"
:get #(-> % proxy->shape :shadow array-to-js)
@ -236,13 +235,13 @@
:hidden false}
(utils/from-js val #{:style :type})))
value)]
(st/emit! (dwc/update-shapes [id] #(assoc % :shadow value)))))}
(st/emit! (dwsh/update-shapes [id] #(assoc % :shadow value)))))}
{:name "blur"
:get #(-> % proxy->shape :blur utils/to-js)
:set (fn [self value]
(if (nil? value)
(st/emit! (dwc/update-shapes [id] #(dissoc % :blur)))
(st/emit! (dwsh/update-shapes [id] #(dissoc % :blur)))
(let [id (obj/get self "$id")
value
(d/patch-object
@ -251,14 +250,14 @@
:value 4
:hidden false}
(utils/from-js value))]
(st/emit! (dwc/update-shapes [id] #(assoc % :blur value))))))}
(st/emit! (dwsh/update-shapes [id] #(assoc % :blur value))))))}
{:name "exports"
:get #(-> % proxy->shape :exports array-to-js)
:set (fn [self value]
(let [id (obj/get self "$id")
value (mapv #(utils/from-js %) value)]
(st/emit! (dwc/update-shapes [id] #(assoc % :exports value)))))}
(st/emit! (dwsh/update-shapes [id] #(assoc % :exports value)))))}
;; Geometry properties
{:name "x"
@ -354,14 +353,14 @@
:set (fn [self value]
(let [id (obj/get self "$id")
value (mapv #(utils/from-js %) value)]
(st/emit! (dwc/update-shapes [id] #(assoc % :fills value)))))}
(st/emit! (dwsh/update-shapes [id] #(assoc % :fills value)))))}
{:name "strokes"
:get #(-> % proxy->shape :strokes array-to-js)
:set (fn [self value]
(let [id (obj/get self "$id")
value (mapv #(utils/from-js % #{:stroke-style :stroke-alignment}) value)]
(st/emit! (dwc/update-shapes [id] #(assoc % :strokes value)))))}
(st/emit! (dwsh/update-shapes [id] #(assoc % :strokes value)))))}
{:name "layoutChild"
:get
@ -421,7 +420,7 @@
:set (fn [self value]
(let [id (obj/get self "$id")
value (mapv #(utils/from-js %) value)]
(st/emit! (dwc/update-shapes [id] #(assoc % :grids value)))))}
(st/emit! (dwsh/update-shapes [id] #(assoc % :grids value)))))}
{:name "horizontalSizing"
:get #(-> % proxy->shape :layout-item-h-sizing (d/nilv :fix) d/name)
@ -463,7 +462,7 @@
ted/import-content
ted/create-editor-state)]
(st/emit! (dwt/update-editor-state shape editor))))
(st/emit! (dwc/update-shapes [id] #(txt/change-text % value)))))}
(st/emit! (dwsh/update-shapes [id] #(txt/change-text % value)))))}
{:name "growType"
:get #(-> % proxy->shape :grow-type d/name)
@ -472,7 +471,7 @@
(let [id (obj/get self "$id")
value (keyword value)]
(when (contains? #{:auto-width :auto-height :fixed} value)
(st/emit! (dwc/update-shapes [id] #(assoc % :grow-type value))))))}
(st/emit! (dwsh/update-shapes [id] #(assoc % :grow-type value))))))}
{:name "fontId"
:get #(-> % proxy->shape text-props :font-id)

View file

@ -8,7 +8,10 @@
"A collection of helpers for work with javascript objects."
(:refer-clojure :exclude [set! new get get-in merge clone contains? array? into-array])
(:require
[cuerdas.core :as str]))
[cuerdas.core :as str]
;; FIXME: we use goog.string here for performance reasons, pending
;; to apply this optimizations directly to cuerdas.
[goog.string :as gstr]))
(defn array?
[o]
@ -105,28 +108,51 @@
[obj prop]
(js* "~{} in ~{}" prop obj))
(defn- transform-prop-key
[s]
(let [result (js* "~{}.replace(\":\", \"-\").replace(/-./g, x=>x[1].toUpperCase())", s)]
(if ^boolean (gstr/startsWith s "-")
(gstr/capitalize result)
result)))
(defn prop-key-fn
[k]
(when (string? k)
(cond
(or (= k "class")
(= k "class-name"))
"className"
(gstr/startsWith k "data-")
k
:else
(transform-prop-key k))))
(defn map->obj
[x]
(cond
(nil? x)
nil
"A simplified version of clj->js with focus on performance"
([x] (map->obj x identity))
([x ^function key-fn]
(cond
(nil? x)
nil
(keyword? x)
(name x)
(keyword? x)
(name x)
(map? x)
(reduce-kv (fn [m k v]
(let [k (if (keyword? k) (name k) k)]
(unchecked-set m k (^function map->obj v))
m))
#js {}
x)
(map? x)
(reduce-kv (fn [m k v]
(let [k (if (keyword? k) (name k) k)]
(unchecked-set m (key-fn k) (map->obj v key-fn))
m))
#js {}
x)
(coll? x)
(reduce (fn [arr v]
(.push arr v)
arr)
(array)
x)
(coll? x)
(reduce (fn [arr v]
(.push arr v)
arr)
(array)
x)
:else x))
:else x)))

View file

@ -17,11 +17,11 @@
[app.common.uri :as u]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.main.data.changes :as dwc]
[app.main.data.dashboard.shortcuts]
[app.main.data.preview :as dp]
[app.main.data.viewer.shortcuts]
[app.main.data.workspace :as dw]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.path.shortcuts]
[app.main.data.workspace.selection :as dws]
[app.main.data.workspace.shortcuts]
@ -52,8 +52,6 @@
(def debug-exclude-events
#{:app.main.data.workspace.notifications/handle-pointer-update
:app.main.data.workspace.notifications/handle-pointer-send
:app.main.data.workspace.persistence/update-persistence-status
:app.main.data.workspace.changes/update-indices
:app.main.data.websocket/send-message
:app.main.data.workspace.selection/change-hover-state})
@ -303,7 +301,7 @@
(let [file-id (:current-file-id @st/state)
changes (t/decode-str changes*)]
(st/emit! (dch/commit-changes {:redo-changes changes
(st/emit! (dwc/commit-changes {:redo-changes changes
:undo-changes []
:save-undo? true
:file-id file-id}))))

View file

@ -3,12 +3,13 @@
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns frontend-tests.basic-shapes-test
(:require
[app.common.test-helpers.files :as cthf]
[app.common.test-helpers.ids-map :as cthi]
[app.common.test-helpers.shapes :as cths]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.shapes :as dwsh]
[cljs.test :as t :include-macros true]
[frontend-tests.helpers.state :as ths]))
@ -21,12 +22,12 @@
(-> (cthf/sample-file :file1 :page-label :page1)
(cths/add-sample-shape :shape1)))
;; ==== Action
;; ==== Action
events
[(dch/update-shapes [(cthi/id :shape1)]
#(assoc % :fills
(cths/sample-fills-color :fill-color
"#fabada")))]]
[(dwsh/update-shapes [(cthi/id :shape1)]
#(assoc % :fills
(cths/sample-fills-color :fill-color
"#fabada")))]]
(ths/run-store
store done events
@ -40,7 +41,7 @@
fills' (:fills shape1')
fill' (first fills')]
;; ==== Check
;; ==== Check
(t/is (some? shape1'))
(t/is (= (count fills') 1))
(t/is (= (:fill-color fill') "#fabada"))

View file

@ -30,7 +30,7 @@
event occurs, and then call the function with the final state at
this point."
[state done completed-cb]
(let [store (ptk/store {:state state :on-error on-error})
(let [store (ptk/store {:state state :on-error on-error})
stream (ptk/input-stream store)
stream (->> stream
(rx/take-until (rx/filter #(= :the/end %) stream))

View file

@ -38,21 +38,3 @@
{:name "Rect 1"}))
shape (thp/get-shape state :shape1)]
(t/is (= (:name shape) "Rect 1")))))
(t/deftest asynctest
(t/testing "asynctest"
(t/async done
(let [state {}
color {:color clr/white}
store (the/prepare-store state done
(fn [new-state]
(t/is (= (get-in new-state [:workspace-data
:recent-colors])
[color]))))]
(ptk/emit!
store
(dwl/add-recent-color color)
:the/end)))))