0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-04-12 15:01:28 -05:00

Merge remote-tracking branch 'origin/staging' into develop

This commit is contained in:
Andrey Antukh 2021-04-29 14:52:12 +02:00
commit e9ae59ad00
67 changed files with 1278 additions and 1087 deletions

View file

@ -28,6 +28,17 @@
- Fix problem with pan and space [#811](https://github.com/penpot/penpot/issues/811)
- Fix issue when parsing exponential numbers in paths
- Remove legacy system user and team [#843](https://github.com/penpot/penpot/issues/843)
- Fix ordering of copy pasted objects [Taiga #1618](https://tree.taiga.io/project/penpot/issue/1617)
- Fix problems with blending modes [#837](https://github.com/penpot/penpot/issues/837)
- Fix problem with zoom an selection rect [#845](https://github.com/penpot/penpot/issues/845)
- Fix problem displaying team statistics [#859](https://github.com/penpot/penpot/issues/859)
- Fix problems with text editor selection [Taiga #1546](https://tree.taiga.io/project/penpot/issue/1546)
- Fix problem when opening the context menu in dashboard at the bottom [#856](https://github.com/penpot/penpot/issues/856)
- Fix problem when clicking an interactive group in view mode [#863](https://github.com/penpot/penpot/issues/863)
- Fix visibility of pages in sitemap when changing page [Taiga #1618](https://tree.taiga.io/project/penpot/issue/1618)
- Fix visual problem with group invite [Taiga #1290](https://tree.taiga.io/project/penpot/issue/1290)
- Fix issues with promote owner panel [Taiga #763](https://tree.taiga.io/project/penpot/issue/763)
- Allow use library colors when defining gradients [Taiga #1614](https://tree.taiga.io/project/penpot/issue/1614)
### :arrow_up: Deps updates

View file

@ -17,28 +17,6 @@
[app.common.geom.shapes.intersect :as gin]
[app.common.spec :as us]))
;; --- Relative Movement
(defn move
"Move the shape relativelly to its current
position applying the provided delta."
[shape {dx :x dy :y}]
(let [dx (d/check-num dx)
dy (d/check-num dy)]
(-> shape
(assoc-in [:modifiers :displacement] (gmt/translate-matrix (gpt/point dx dy)))
(gtr/transform-shape))))
;; --- Absolute Movement
(declare absolute-move-rect)
(defn absolute-move
"Move the shape to the exactly specified position."
[shape {:keys [x y]}]
(let [dx (- (d/check-num x) (-> shape :selrect :x))
dy (- (d/check-num y) (-> shape :selrect :y))]
(move shape (gpt/point dx dy))))
;; --- Resize (Dimensions)
(defn resize-modifiers
@ -120,38 +98,8 @@
(gpr/join-selrects)))
(defn translate-to-frame
[{:keys [type x y] :as shape} {:keys [x y]}]
(let [move-point
(fn [point]
(-> point
(update :x - x)
(update :y - y)))
move-segment
(fn [segment]
(-> segment
(d/update-in-when [:params :x] - x)
(d/update-in-when [:params :y] - y)
(d/update-in-when [:params :c1x] - x)
(d/update-in-when [:params :c1y] - y)
(d/update-in-when [:params :c2x] - x)
(d/update-in-when [:params :c2y] - y)))]
(-> shape
(d/update-when :x - x)
(d/update-when :y - y)
(update-in [:selrect :x] - x)
(update-in [:selrect :y] - y)
(update-in [:selrect :x1] - x)
(update-in [:selrect :y1] - y)
(update-in [:selrect :x2] - x)
(update-in [:selrect :y2] - y)
(d/update-when :points #(mapv move-point %))
(cond-> (= :path type)
(d/update-when :content #(mapv move-segment %))))))
[shape {:keys [x y]}]
(gtr/move shape (gpt/negate (gpt/point x y))) )
;; --- Helpers
@ -244,6 +192,8 @@
(d/export gtr/update-group-selrect)
(d/export gtr/transform-points)
(d/export gtr/calculate-adjust-matrix)
(d/export gtr/move)
(d/export gtr/absolute-move)
;; PATHS
(d/export gsp/content->points)

View file

@ -139,6 +139,23 @@
(update :width #(if (mth/almost-zero? %) 1 %))
(update :height #(if (mth/almost-zero? %) 1 %)))))
(defn move-content [content move-vec]
(let [set-tr (fn [params px py]
(let [tr-point (-> (gpt/point (get params px) (get params py))
(gpt/add move-vec))]
(assoc params
px (:x tr-point)
py (:y tr-point))))
transform-params
(fn [{:keys [x c1x c2x] :as params}]
(cond-> params
(not (nil? x)) (set-tr :x :y)
(not (nil? c1x)) (set-tr :c1x :c1y)
(not (nil? c2x)) (set-tr :c2x :c2y)))]
(mapv #(update % :params transform-params) content)))
(defn transform-content [content transform]
(let [set-tr (fn [params px py]
(let [tr-point (-> (gpt/point (get params px) (get params py))

View file

@ -14,6 +14,49 @@
[app.common.math :as mth]
[app.common.data :as d]))
;; --- Relative Movement
(defn move-selrect [selrect {dx :x dy :y}]
(-> selrect
(d/update-when :x + dx)
(d/update-when :y + dy)
(d/update-when :x1 + dx)
(d/update-when :y1 + dy)
(d/update-when :x2 + dx)
(d/update-when :y2 + dy)))
(defn move-points [points move-vec]
(->> points
(mapv #(gpt/add % move-vec))))
(defn move
"Move the shape relativelly to its current
position applying the provided delta."
[shape {dx :x dy :y}]
(let [dx (d/check-num dx)
dy (d/check-num dy)
move-vec (gpt/point dx dy)]
(-> shape
(update :selrect move-selrect move-vec)
(update :points move-points move-vec)
(d/update-when :x + dx)
(d/update-when :y + dy)
(cond-> (= :path (:type shape))
(update :content gpa/move-content move-vec)))))
;; --- Absolute Movement
(declare absolute-move-rect)
(defn absolute-move
"Move the shape to the exactly specified position."
[shape {:keys [x y]}]
(let [dx (- (d/check-num x) (-> shape :selrect :x))
dy (- (d/check-num y) (-> shape :selrect :y))]
(move shape (gpt/point dx dy))))
(defn- modif-rotation [shape]
(let [cur-rotation (d/check-num (:rotation shape))
delta-angle (d/check-num (get-in shape [:modifiers :rotation]))]
@ -272,12 +315,27 @@
(and rx (< rx 0)) (update :flip-x not)
(and ry (< ry 0)) (update :flip-y not))))
(defn transform-shape [shape]
(let [center (gco/center-shape shape)]
(if (and (:modifiers shape) center)
(let [transform (modifiers->transform center (:modifiers shape))]
(defn apply-displacement [shape]
(let [modifiers (:modifiers shape)]
(if (contains? modifiers :displacement)
(let [mov-vec (-> (gpt/point 0 0)
(gpt/transform (:displacement modifiers)))
shape (move shape mov-vec)
modifiers (dissoc modifiers :displacement)]
(-> shape
(set-flip (:modifiers shape))
(assoc :modifiers modifiers)
(cond-> (empty? modifiers)
(dissoc :modifiers))))
shape)))
(defn transform-shape [shape]
(let [shape (apply-displacement shape)
center (gco/center-shape shape)
modifiers (:modifiers shape)]
(if (and modifiers center)
(let [transform (modifiers->transform center modifiers)]
(-> shape
(set-flip modifiers)
(apply-transform transform)
(dissoc :modifiers)))
shape)))

View file

@ -31,7 +31,7 @@
;; When verify? false we spec the schema validation. Currently used to make just
;; 1 validation even if the changes are applied twice
(when verify?
(us/verify ::spec/changes items))
(us/assert ::spec/changes items))
(let [pages (into #{} (map :page-id) items)
result (->> items

View file

@ -24,7 +24,8 @@
}
.custom-select {
width: 103px
width: 103px;
overflow: hidden;
}
.action-buttons {

View file

@ -134,6 +134,11 @@
margin-bottom: 0px;
}
}
}
.btn-disabled {
opacity: 0.5;
}
}

View file

@ -7,7 +7,6 @@
(ns app.main.data.workspace
(:require
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.geom.align :as gal]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
@ -19,37 +18,31 @@
[app.common.spec :as us]
[app.common.uuid :as uuid]
[app.config :as cfg]
[app.main.constants :as c]
[app.main.data.workspace.colors :as mdc]
[app.main.data.messages :as dm]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.drawing :as dwd]
[app.main.data.workspace.path :as dwdp]
[app.main.data.workspace.groups :as dwg]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.notifications :as dwn]
[app.main.data.workspace.path :as dwdp]
[app.main.data.workspace.persistence :as dwp]
[app.main.data.workspace.selection :as dws]
[app.main.data.workspace.transforms :as dwt]
[app.main.data.workspace.svg-upload :as svg]
[app.main.data.workspace.transforms :as dwt]
[app.main.data.workspace.undo :as dwu]
[app.main.repo :as rp]
[app.main.store :as st]
[app.main.streams :as ms]
[app.main.worker :as uw]
[app.util.dom :as dom]
[app.util.http :as http]
[app.util.i18n :refer [tr] :as i18n]
[app.util.i18n :as i18n]
[app.util.logging :as log]
[app.util.object :as obj]
[app.util.router :as rt]
[app.util.timers :as ts]
[app.util.transit :as t]
[app.util.webapi :as wapi]
[beicon.core :as rx]
[cljs.spec.alpha :as s]
[clojure.set :as set]
[cuerdas.core :as str]
[goog.string.path :as path]
[potok.core :as ptk]))
;; (log/set-level! :trace)
@ -269,7 +262,7 @@
:name name}
uchange {:type :del-page
:id id}]
(rx/of (dwc/commit-changes [rchange] [uchange] {:commit-local? true}))))))
(rx/of (dch/commit-changes [rchange] [uchange] {:commit-local? true}))))))
(defn duplicate-page [page-id]
(ptk/reify ::duplicate-page
@ -287,7 +280,7 @@
:page page}
uchange {:type :del-page
:id id}]
(rx/of (dwc/commit-changes [rchange] [uchange] {:commit-local? true}))))))
(rx/of (dch/commit-changes [rchange] [uchange] {:commit-local? true}))))))
(s/def ::rename-page
(s/keys :req-un [::id ::name]))
@ -306,7 +299,7 @@
uchg {:type :mod-page
:id id
:name (:name page)}]
(rx/of (dwc/commit-changes [rchg] [uchg] {:commit-local? true}))))))
(rx/of (dch/commit-changes [rchg] [uchg] {:commit-local? true}))))))
(declare purge-page)
(declare go-to-file)
@ -323,7 +316,7 @@
:id id}
uchg {:type :add-page
:page page}]
(rx/of (dwc/commit-changes [rchg] [uchg] {:commit-local? true})
(rx/of (dch/commit-changes [rchg] [uchg] {:commit-local? true})
(when (= id (:current-page-id state))
go-to-file))))))
@ -604,7 +597,7 @@
(ptk/reify ::update-shape
ptk/WatchEvent
(watch [_ state stream]
(rx/of (dwc/update-shapes [id] #(merge % attrs))))))
(rx/of (dch/update-shapes [id] #(merge % attrs))))))
(defn start-rename-shape
[id]
@ -712,7 +705,7 @@
:index (cp/position-on-parent id objects)}))
selected)]
;; TODO: maybe missing the :reg-objects event?
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}))))))
(rx/of (dch/commit-changes rchanges uchanges {:commit-local? true}))))))
;; --- Change Shape Order (D&D Ordering)
@ -986,7 +979,7 @@
shapes-to-detach
shapes-to-reroot
shapes-to-deroot)]
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})
(rx/of (dch/commit-changes rchanges uchanges {:commit-local? true})
(dwc/expand-collapse parent-id))))))
(defn relocate-selected-shapes
@ -1039,7 +1032,7 @@
uchg {:type :mov-page
:id id
:index cidx}]
(rx/of (dwc/commit-changes [rchg] [uchg] {:commit-local? true}))))))
(rx/of (dch/commit-changes [rchg] [uchg] {:commit-local? true}))))))
;; --- Shape / Selection Alignment and Distribution
@ -1057,30 +1050,13 @@
selected (get-in state [:workspace-local :selected])
moved (if (= 1 (count selected))
(align-object-to-frame objects (first selected) axis)
(align-objects-list objects selected axis))]
(loop [moved (seq moved)
rchanges []
uchanges []]
(if (nil? moved)
(do
;; (println "================ rchanges")
;; (cljs.pprint/pprint rchanges)
;; (println "================ uchanges")
;; (cljs.pprint/pprint uchanges)
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})))
(let [curr (first moved)
prev (get objects (:id curr))
ops1 (dwc/generate-operations prev curr)
ops2 (dwc/generate-operations curr prev true)]
(recur (next moved)
(conj rchanges {:type :mod-obj
:page-id page-id
:operations ops1
:id (:id curr)})
(conj uchanges {:type :mod-obj
:page-id page-id
:operations ops2
:id (:id curr)})))))))))
(align-objects-list objects selected axis))
moved-objects (->> moved (group-by :id))
ids (keys moved-objects)
update-fn (fn [shape] (first (get moved-objects (:id shape))))]
(rx/of (dch/update-shapes ids update-fn {:reg-objects? true}))))))
(defn align-object-to-frame
[objects object-id axis]
@ -1104,30 +1080,12 @@
objects (dwc/lookup-page-objects state page-id)
selected (get-in state [:workspace-local :selected])
moved (-> (map #(get objects %) selected)
(gal/distribute-space axis objects))]
(loop [moved (seq moved)
rchanges []
uchanges []]
(if (nil? moved)
(do
;; (println "================ rchanges")
;; (cljs.pprint/pprint rchanges)
;; (println "================ uchanges")
;; (cljs.pprint/pprint uchanges)
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})))
(let [curr (first moved)
prev (get objects (:id curr))
ops1 (dwc/generate-operations prev curr)
ops2 (dwc/generate-operations curr prev true)]
(recur (next moved)
(conj rchanges {:type :mod-obj
:page-id page-id
:operations ops1
:id (:id curr)})
(conj uchanges {:type :mod-obj
:page-id page-id
:operations ops2
:id (:id curr)})))))))))
(gal/distribute-space axis objects))
moved-objects (->> moved (group-by :id))
ids (keys moved-objects)
update-fn (fn [shape] (first (get moved-objects (:id shape))))]
(rx/of (dch/update-shapes ids update-fn {:reg-objects? true}))))))
;; --- Shape Proportions
@ -1141,7 +1099,7 @@
(assoc shape :proportion-lock false)
(-> (assoc shape :proportion-lock true)
(gpr/assign-proportions))))]
(rx/of (dwc/update-shapes [id] assign-proportions))))))
(rx/of (dch/update-shapes [id] assign-proportions))))))
;; --- Update Shape Position
@ -1183,7 +1141,7 @@
(cond-> obj
(boolean? blocked) (assoc :blocked blocked)
(boolean? hidden) (assoc :hidden hidden)))]
(rx/of (dwc/update-shapes-recursive [id] update-fn))))))
(rx/of (dch/update-shapes-recursive [id] update-fn))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -1410,7 +1368,7 @@
(catch :default e
(let [data (ex-data e)]
(if (:not-implemented data)
(rx/of (dm/warn (tr "errors.clipboard-not-implemented")))
(rx/of (dm/warn (i18n/tr "errors.clipboard-not-implemented")))
(js/console.error "ERROR" e))))))))
(defn paste-from-event
@ -1582,7 +1540,7 @@
(map #(get-in % [:obj :id]))
(into (d/ordered-set)))]
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})
(rx/of (dch/commit-changes rchanges uchanges {:commit-local? true})
(dwc/select-shapes selected))))]
(ptk/reify ::paste-shape
ptk/WatchEvent
@ -1629,10 +1587,10 @@
:height height
:grow-type (if (> (count text) 100) :auto-height :auto-width)
:content (as-content text)})]
(rx/of (dwc/start-undo-transaction)
(rx/of (dwu/start-undo-transaction)
(dws/deselect-all)
(dwc/add-shape shape)
(dwc/commit-undo-transaction))))))
(dwu/commit-undo-transaction))))))
(defn- paste-svg
[text]
@ -1743,7 +1701,7 @@
(let [page-id (get state :current-page-id)
options (dwc/lookup-page-options state page-id)
previus-color (:background options)]
(rx/of (dwc/commit-changes
(rx/of (dch/commit-changes
[{:type :set-option
:page-id page-id
:option :background

View file

@ -0,0 +1,222 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) UXBOX Labs SL
(ns app.main.data.workspace.changes
(:require
[app.common.data :as d]
[app.common.pages :as cp]
[app.common.pages.spec :as spec]
[app.common.spec :as us]
[app.main.data.workspace.undo :as dwu]
[app.main.worker :as uw]
[app.util.logging :as log]
[beicon.core :as rx]
[cljs.spec.alpha :as s]
[clojure.set :as set]
[potok.core :as ptk]))
;; Change this to :info :debug or :trace to debug this module
(log/set-level! :warn)
(s/def ::coll-of-uuid
(s/every ::us/uuid))
(defonce page-change? #{:add-page :mod-page :del-page :mov-page})
(declare commit-changes)
(def commit-changes? (ptk/type? ::commit-changes))
(defn- generate-operations
([ma mb] (generate-operations ma mb false))
([ma mb undo?]
(let [ops (let [ma-keys (set (keys ma))
mb-keys (set (keys mb))
added (set/difference mb-keys ma-keys)
removed (set/difference ma-keys mb-keys)
both (set/intersection ma-keys mb-keys)]
(d/concat
(mapv #(array-map :type :set :attr % :val (get mb %)) added)
(mapv #(array-map :type :set :attr % :val nil) removed)
(loop [items (seq both)
result []]
(if items
(let [k (first items)
vma (get ma k)
vmb (get mb k)]
(if (= vma vmb)
(recur (next items) result)
(recur (next items)
(conj result {:type :set
:attr k
:val vmb
:ignore-touched undo?}))))
result))))]
(if undo?
(conj ops {:type :set-touched :touched (:touched mb)})
ops))))
(defn update-shapes
([ids f] (update-shapes ids f nil))
([ids f {:keys [reg-objects?] :or {reg-objects? false}}]
(us/assert ::coll-of-uuid ids)
(us/assert fn? f)
(ptk/reify ::update-shapes
ptk/WatchEvent
(watch [_ state stream]
(let [page-id (:current-page-id state)
objects (get-in state [:workspace-data :pages-index page-id :objects])
reg-objects {:type :reg-objects :page-id page-id :shapes (vec ids)}]
(loop [ids (seq ids)
rch []
uch []]
(if (nil? ids)
(rx/of (let [has-rch? (not (empty? rch))
has-uch? (not (empty? uch))
rch (cond-> rch (and has-rch? reg-objects?) (conj reg-objects))
uch (cond-> uch (and has-rch? reg-objects?) (conj reg-objects))]
(when (and has-rch? has-uch?)
(commit-changes rch uch {:commit-local? true}))))
(let [id (first ids)
obj1 (get objects id)
obj2 (f obj1)
rch-operations (generate-operations obj1 obj2)
uch-operations (generate-operations obj2 obj1 true)
rchg {:type :mod-obj
:page-id page-id
:operations rch-operations
:id id}
uchg {:type :mod-obj
:page-id page-id
:operations uch-operations
:id id}]
(recur (next ids)
(if (empty? rch-operations) rch (conj rch rchg))
(if (empty? uch-operations) uch (conj uch uchg)))))))))))
(defn update-shapes-recursive
[ids f]
(us/assert ::coll-of-uuid ids)
(us/assert fn? f)
(letfn [(impl-get-children [objects id]
(cons id (cp/get-children id objects)))
(impl-gen-changes [objects page-id ids]
(loop [sids (seq ids)
cids (seq (impl-get-children objects (first sids)))
rchanges []
uchanges []]
(cond
(nil? sids)
[rchanges uchanges]
(nil? cids)
(recur (next sids)
(seq (impl-get-children objects (first (next sids))))
rchanges
uchanges)
:else
(let [id (first cids)
obj1 (get objects id)
obj2 (f obj1)
rops (generate-operations obj1 obj2)
uops (generate-operations obj2 obj1 true)
rchg {:type :mod-obj
:page-id page-id
:operations rops
:id id}
uchg {:type :mod-obj
:page-id page-id
:operations uops
:id id}]
(recur sids
(next cids)
(conj rchanges rchg)
(conj uchanges uchg))))))]
(ptk/reify ::update-shapes-recursive
ptk/WatchEvent
(watch [_ state stream]
(let [page-id (:current-page-id state)
objects (get-in state [:workspace-data :pages-index page-id :objects])
[rchanges uchanges] (impl-gen-changes objects page-id (seq ids))]
(rx/of (commit-changes rchanges uchanges {:commit-local? true})))))))
(defn update-indices
[page-id changes]
(ptk/reify ::update-indices
ptk/EffectEvent
(effect [_ state stream]
(uw/ask! {:cmd :update-page-indices
:page-id page-id
:changes changes}))))
(defn commit-changes
([changes undo-changes]
(commit-changes changes undo-changes {}))
([changes undo-changes {:keys [save-undo?
commit-local?
file-id]
:or {save-undo? true
commit-local? false}
:as opts}]
(us/assert ::cp/changes changes)
(us/assert ::cp/changes undo-changes)
(log/debug :msg "commit-changes"
:js/changes changes
:js/undo-changes undo-changes)
(let [error (volatile! nil)]
(ptk/reify ::commit-changes
cljs.core/IDeref
(-deref [_] {:file-id file-id :changes changes})
ptk/UpdateEvent
(update [_ state]
(let [current-file-id (get state :current-file-id)
file-id (or file-id current-file-id)
path1 (if (= file-id current-file-id)
[:workspace-file :data]
[:workspace-libraries file-id :data])
path2 (if (= file-id current-file-id)
[:workspace-data]
[:workspace-libraries file-id :data])]
(try
(us/assert ::spec/changes changes)
(let [state (update-in state path1 cp/process-changes changes false)]
(cond-> state
commit-local? (update-in path2 cp/process-changes changes false)))
(catch :default e
(vreset! error e)
state))))
ptk/WatchEvent
(watch [_ state stream]
(when-not @error
(let [;; adds page-id to page changes (that have the `id` field instead)
add-page-id
(fn [{:keys [id type page] :as change}]
(cond-> change
(page-change? type)
(assoc :page-id (or id (:id page)))))
changes-by-pages
(->> changes
(map add-page-id)
(remove #(nil? (:page-id %)))
(group-by :page-id))
process-page-changes
(fn [[page-id changes]]
(update-indices page-id 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 changes}]
(rx/of (dwu/append-undo entry))))))))))))

View file

@ -13,6 +13,7 @@
[app.main.data.modal :as md]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.texts :as dwt]
[app.main.data.workspace.changes :as dch]
[app.main.repo :as rp]
[app.main.store :as st]
[app.main.streams :as ms]
@ -150,7 +151,7 @@
(rx/concat
(rx/from (map #(dwt/update-text-attrs {:id % :attrs attrs}) text-ids))
(rx/of (dwc/update-shapes shape-ids (fn [shape] (d/merge shape attrs)))))))))
(rx/of (dch/update-shapes shape-ids (fn [shape] (d/merge shape attrs)))))))))
(defn change-stroke
[ids color]
@ -176,7 +177,7 @@
(contains? color :opacity)
(assoc :stroke-opacity (:opacity color)))]
(rx/of (dwc/update-shapes ids (fn [shape]
(rx/of (dch/update-shapes ids (fn [shape]
(cond-> (d/merge shape attrs)
(= (:stroke-style shape) :none)
(assoc :stroke-style :solid

View file

@ -10,17 +10,15 @@
[app.common.geom.proportions :as gpr]
[app.common.geom.shapes :as gsh]
[app.common.pages :as cp]
[app.common.pages.spec :as spec]
[app.common.spec :as us]
[app.common.uuid :as uuid]
[app.main.worker :as uw]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.undo :as dwu]
[app.main.streams :as ms]
[app.main.worker :as uw]
[app.util.logging :as log]
[app.util.timers :as ts]
[beicon.core :as rx]
[cljs.spec.alpha :as s]
[clojure.set :as set]
[cuerdas.core :as str]
[potok.core :as ptk]))
;; Change this to :info :debug or :trace to debug this module
@ -58,121 +56,6 @@
(get-in state [:workspace-data :components component-id :objects])))
;; --- Changes Handling
(defonce page-change? #{:add-page :mod-page :del-page :mov-page})
(defn commit-changes
([changes undo-changes]
(commit-changes changes undo-changes {}))
([changes undo-changes {:keys [save-undo?
commit-local?
file-id]
:or {save-undo? true
commit-local? false}
:as opts}]
(us/verify ::cp/changes changes)
(us/verify ::cp/changes undo-changes)
(log/debug :msg "commit-changes"
:js/changes changes
:js/undo-changes undo-changes)
(let [error (volatile! nil)]
(ptk/reify ::commit-changes
cljs.core/IDeref
(-deref [_] {:file-id file-id :changes changes})
ptk/UpdateEvent
(update [_ state]
(let [current-file-id (get state :current-file-id)
file-id (or file-id current-file-id)
path1 (if (= file-id current-file-id)
[:workspace-file :data]
[:workspace-libraries file-id :data])
path2 (if (= file-id current-file-id)
[:workspace-data]
[:workspace-libraries file-id :data])]
(try
(us/verify ::spec/changes changes)
(let [state (update-in state path1 cp/process-changes changes false)]
(cond-> state
commit-local? (update-in path2 cp/process-changes changes false)))
(catch :default e
(vreset! error e)
state))))
ptk/WatchEvent
(watch [_ state stream]
(when-not @error
(let [;; adds page-id to page changes (that have the `id` field instead)
add-page-id
(fn [{:keys [id type page] :as change}]
(cond-> change
(page-change? type)
(assoc :page-id (or id (:id page)))))
changes-by-pages
(->> changes
(map add-page-id)
(remove #(nil? (:page-id %)))
(group-by :page-id))
process-page-changes
(fn [[page-id changes]]
(update-indices page-id 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 changes}]
(rx/of (append-undo entry))))))))))))
(defn generate-operations
([ma mb] (generate-operations ma mb false))
([ma mb undo?]
(let [ops (let [ma-keys (set (keys ma))
mb-keys (set (keys mb))
added (set/difference mb-keys ma-keys)
removed (set/difference ma-keys mb-keys)
both (set/intersection ma-keys mb-keys)]
(d/concat
(mapv #(array-map :type :set :attr % :val (get mb %)) added)
(mapv #(array-map :type :set :attr % :val nil) removed)
(loop [items (seq both)
result []]
(if items
(let [k (first items)
vma (get ma k)
vmb (get mb k)]
(if (= vma vmb)
(recur (next items) result)
(recur (next items)
(conj result {:type :set
:attr k
:val vmb
:ignore-touched undo?}))))
result))))]
(if undo?
(conj ops {:type :set-touched :touched (:touched mb)})
ops))))
(defn generate-changes
[page-id objects1 objects2]
(letfn [(impl-diff [res id]
(let [obj1 (get objects1 id)
obj2 (get objects2 id)
ops (generate-operations (dissoc obj1 :shapes :frame-id)
(dissoc obj2 :shapes :frame-id))]
(if (empty? ops)
res
(conj res {:type :mod-obj
:page-id page-id
:operations ops
:id id}))))]
(reduce impl-diff [] (set/union (set (keys objects1))
(set (keys objects2))))))
;; --- Selection Index Handling
(defn initialize-indices
@ -186,14 +69,7 @@
(->> (uw/ask! msg)
(rx/map (constantly ::index-initialized)))))))
(defn update-indices
[page-id changes]
(ptk/reify ::update-indices
ptk/EffectEvent
(effect [_ state stream]
(uw/ask! {:cmd :update-page-indices
:page-id page-id
:changes changes}))))
;; --- Common Helpers & Events
@ -253,110 +129,8 @@
(update state :workspace-local dissoc :expanded))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Undo / Redo
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(s/def ::undo-changes ::cp/changes)
(s/def ::redo-changes ::cp/changes)
(s/def ::undo-entry
(s/keys :req-un [::undo-changes ::redo-changes]))
(def MAX-UNDO-SIZE 50)
(defn- conj-undo-entry
[undo data]
(let [undo (conj undo data)
cnt (count undo)]
(if (> cnt MAX-UNDO-SIZE)
(subvec undo (- cnt MAX-UNDO-SIZE))
undo)))
(defn- materialize-undo
[changes index]
(ptk/reify ::materialize-undo
ptk/UpdateEvent
(update [_ state]
(-> state
(update :workspace-data cp/process-changes changes)
(assoc-in [:workspace-undo :index] index)))))
(defn- reset-undo
[index]
(ptk/reify ::reset-undo
ptk/UpdateEvent
(update [_ state]
(-> state
(update :workspace-undo dissoc :undo-index)
(update-in [:workspace-undo :items] (fn [queue] (into [] (take (inc index) queue))))))))
(defn- add-undo-entry
[state entry]
(if (and entry
(not-empty (:undo-changes entry))
(not-empty (:redo-changes entry)))
(let [index (get-in state [:workspace-undo :index] -1)
items (get-in state [:workspace-undo :items] [])
items (->> items (take (inc index)) (into []))
items (conj-undo-entry items entry)]
(-> state
(update :workspace-undo assoc :items items
:index (min (inc index)
(dec MAX-UNDO-SIZE)))))
state))
(defn- accumulate-undo-entry
[state {:keys [undo-changes redo-changes]}]
(-> state
(update-in [:workspace-undo :transaction :undo-changes] #(into undo-changes %))
(update-in [:workspace-undo :transaction :redo-changes] #(into % redo-changes))))
(defn- append-undo
[entry]
(us/verify ::undo-entry entry)
(ptk/reify ::append-undo
ptk/UpdateEvent
(update [_ state]
(if (get-in state [:workspace-undo :transaction])
(accumulate-undo-entry state entry)
(add-undo-entry state entry)))))
(defonce empty-tx {:undo-changes [] :redo-changes []})
(defn start-undo-transaction []
(ptk/reify ::start-undo-transaction
ptk/UpdateEvent
(update [_ state]
;; We commit the old transaction before starting the new one
(let [current-tx (get-in state [:workspace-undo :transaction])]
(cond-> state
(nil? current-tx) (assoc-in [:workspace-undo :transaction] empty-tx))))))
(defn discard-undo-transaction []
(ptk/reify ::discard-undo-transaction
ptk/UpdateEvent
(update [_ state]
(update state :workspace-undo dissoc :transaction))))
(defn commit-undo-transaction []
(ptk/reify ::commit-undo-transaction
ptk/UpdateEvent
(update [_ state]
(-> state
(add-undo-entry (get-in state [:workspace-undo :transaction]))
(update :workspace-undo dissoc :transaction)))))
(def pop-undo-into-transaction
(ptk/reify ::last-undo-into-transaction
ptk/UpdateEvent
(update [_ state]
(let [index (get-in state [:workspace-undo :index] -1)]
(cond-> state
(>= index 0) (accumulate-undo-entry (get-in state [:workspace-undo :items index]))
(>= index 0) (update-in [:workspace-undo :index] dec))))))
;; If these functions change modules review /src/app/main/data/workspace/path/undo.cljs
;; 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
@ -370,8 +144,8 @@
index (or (:index undo) (dec (count items)))]
(when-not (or (empty? items) (= index -1))
(let [changes (get-in items [index :undo-changes])]
(rx/of (materialize-undo changes (dec index))
(commit-changes changes [] {:save-undo? false}))))))))))
(rx/of (dwu/materialize-undo changes (dec index))
(dch/commit-changes changes [] {:save-undo? false}))))))))))
(def redo
(ptk/reify ::redo
@ -385,16 +159,8 @@
index (or (:index undo) (dec (count items)))]
(when-not (or (empty? items) (= index (dec (count items))))
(let [changes (get-in items [(inc index) :redo-changes])]
(rx/of (materialize-undo changes (inc index))
(commit-changes changes [] {:save-undo? false}))))))))))
(def reinitialize-undo
(ptk/reify ::reset-undo
ptk/UpdateEvent
(update [_ state]
(assoc state :workspace-undo {}))))
(rx/of (dwu/materialize-undo changes (inc index))
(dch/commit-changes changes [] {:save-undo? false}))))))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Shapes
@ -420,93 +186,6 @@
;; NOTE: This is a generic implementation for update multiple shapes
;; in one single commit/undo entry.
(s/def ::coll-of-uuid
(s/every ::us/uuid))
(defn update-shapes
([ids f] (update-shapes ids f nil))
([ids f {:keys [reg-objects?] :or {reg-objects? false}}]
(us/assert ::coll-of-uuid ids)
(us/assert fn? f)
(ptk/reify ::update-shapes
ptk/WatchEvent
(watch [_ state stream]
(let [page-id (:current-page-id state)
objects (lookup-page-objects state page-id)]
(loop [ids (seq ids)
rch []
uch []]
(if (nil? ids)
(rx/of (commit-changes
(cond-> rch reg-objects? (conj {:type :reg-objects :page-id page-id :shapes (vec ids)}))
(cond-> uch reg-objects? (conj {:type :reg-objects :page-id page-id :shapes (vec ids)}))
{:commit-local? true}))
(let [id (first ids)
obj1 (get objects id)
obj2 (f obj1)
rch-operations (generate-operations obj1 obj2)
uch-operations (generate-operations obj2 obj1 true)
rchg {:type :mod-obj
:page-id page-id
:operations rch-operations
:id id}
uchg {:type :mod-obj
:page-id page-id
:operations uch-operations
:id id}]
(recur (next ids)
(if (empty? rch-operations) rch (conj rch rchg))
(if (empty? uch-operations) uch (conj uch uchg)))))))))))
(defn update-shapes-recursive
[ids f]
(us/assert ::coll-of-uuid ids)
(us/assert fn? f)
(letfn [(impl-get-children [objects id]
(cons id (cp/get-children id objects)))
(impl-gen-changes [objects page-id ids]
(loop [sids (seq ids)
cids (seq (impl-get-children objects (first sids)))
rchanges []
uchanges []]
(cond
(nil? sids)
[rchanges uchanges]
(nil? cids)
(recur (next sids)
(seq (impl-get-children objects (first (next sids))))
rchanges
uchanges)
:else
(let [id (first cids)
obj1 (get objects id)
obj2 (f obj1)
rops (generate-operations obj1 obj2)
uops (generate-operations obj2 obj1 true)
rchg {:type :mod-obj
:page-id page-id
:operations rops
:id id}
uchg {:type :mod-obj
:page-id page-id
:operations uops
:id id}]
(recur sids
(next cids)
(conj rchanges rchg)
(conj uchanges uchg))))))]
(ptk/reify ::update-shapes-recursive
ptk/WatchEvent
(watch [_ state stream]
(let [page-id (:current-page-id state)
objects (lookup-page-objects state page-id)
[rchanges uchanges] (impl-gen-changes objects page-id (seq ids))]
(rx/of (commit-changes rchanges uchanges {:commit-local? true})))))))
(defn select-shapes
[ids]
@ -639,7 +318,7 @@
(assoc :name name)))]
(rx/concat
(rx/of (commit-changes rchanges uchanges {:commit-local? true})
(rx/of (dch/commit-changes rchanges uchanges {:commit-local? true})
(select-shapes (d/ordered-set id)))
(when (= :text (:type attrs))
(->> (rx/of (start-edition-mode id))
@ -672,7 +351,7 @@
:page-id page-id
:index index
:shapes [shape-id]})))]
(rx/of (commit-changes rchanges uchanges {:commit-local? true}))))))
(rx/of (dch/commit-changes rchanges uchanges {:commit-local? true}))))))
(defn delete-shapes
@ -779,7 +458,7 @@
;; (cljs.pprint/pprint rchanges)
;; (println "================ uchanges")
;; (cljs.pprint/pprint uchanges)
(rx/of (commit-changes rchanges uchanges {:commit-local? true}))))))
(rx/of (dch/commit-changes rchanges uchanges {:commit-local? true}))))))
;; --- Add shape to Workspace

View file

@ -12,6 +12,7 @@
[app.common.geom.shapes :as gsh]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.selection :as dws]
[app.main.data.workspace.undo :as dwu]
[app.main.streams :as ms]
[app.main.worker :as uw]))
@ -54,7 +55,7 @@
;; Add & select the created shape to the workspace
(rx/concat
(if (= :text (:type shape))
(rx/of (dwc/start-undo-transaction))
(rx/of (dwu/start-undo-transaction))
(rx/empty))
(rx/of (dwc/add-shape shape))

View file

@ -10,7 +10,7 @@
[potok.core :as ptk]
[app.common.data :as d]
[app.common.spec :as us]
[app.main.data.workspace.common :as dwc]))
[app.main.data.workspace.changes :as dch]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Grid
@ -48,7 +48,7 @@
grid {:type :square
:params params
:display true}]
(rx/of (dwc/update-shapes [frame-id]
(rx/of (dch/update-shapes [frame-id]
(fn [obj] (update obj :grids (fnil #(conj % grid) [])))))))))
@ -57,14 +57,14 @@
(ptk/reify ::set-frame-grid
ptk/WatchEvent
(watch [_ state stream]
(rx/of (dwc/update-shapes [frame-id] (fn [o] (update o :grids (fnil #(d/remove-at-index % index) []))))))))
(rx/of (dch/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 [_ state stream]
(rx/of (dwc/update-shapes [frame-id] #(assoc-in % [:grids index] data))))))
(rx/of (dch/update-shapes [frame-id] #(assoc-in % [:grids index] data))))))
(defn set-default-grid
[type params]
@ -73,7 +73,7 @@
(watch [_ state stream]
(let [pid (:current-page-id state)
prev-value (get-in state [:workspace-data :pages-index pid :options :saved-grids type])]
(rx/of (dwc/commit-changes [{:type :set-option
(rx/of (dch/commit-changes [{:type :set-option
:page-id pid
:option [:saved-grids type]
:value params}]

View file

@ -4,6 +4,7 @@
[app.common.geom.shapes :as gsh]
[app.common.pages :as cp]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.selection :as dws]
[beicon.core :as rx]
[potok.core :as ptk]))
@ -106,7 +107,7 @@
shapes (shapes-for-grouping objects selected)]
(when-not (empty? shapes)
(let [[group rchanges uchanges] (prepare-create-group page-id shapes "Group-" false)]
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})
(rx/of (dch/commit-changes rchanges uchanges {:commit-local? true})
(dwc/select-shapes (d/ordered-set (:id group))))))))))
(def ungroup-selected
@ -122,7 +123,7 @@
(= (:type group) :group))
(let [[rchanges uchanges]
(prepare-remove-group page-id group objects)]
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}))))))))
(rx/of (dch/commit-changes rchanges uchanges {:commit-local? true}))))))))
(def mask-group
(ptk/reify ::mask-group
@ -176,7 +177,7 @@
:page-id page-id
:shapes [(:id group)]})]
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})
(rx/of (dch/commit-changes rchanges uchanges {:commit-local? true})
(dwc/select-shapes (d/ordered-set (:id group))))))))))
(def unmask-group
@ -209,7 +210,7 @@
:page-id page-id
:shapes [(:id group)]}]]
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})
(rx/of (dch/commit-changes rchanges uchanges {:commit-local? true})
(dwc/select-shapes (d/ordered-set (:id group))))))))))

View file

@ -14,6 +14,7 @@
[app.common.geom.shapes :as geom]
[app.main.data.messages :as dm]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.libraries-helpers :as dwlh]
[app.common.pages :as cp]
[app.main.repo :as rp]
@ -90,7 +91,7 @@
uchg {:type :del-color
:id id}]
(rx/of #(assoc-in % [:workspace-local :color-for-rename] id)
(dwc/commit-changes [rchg] [uchg] {:commit-local? true})))))))
(dch/commit-changes [rchg] [uchg] {:commit-local? true})))))))
(defn add-recent-color
[color]
@ -100,7 +101,7 @@
(watch [_ state s]
(let [rchg {:type :add-recent-color
:color color}]
(rx/of (dwc/commit-changes [rchg] [] {:commit-local? true}))))))
(rx/of (dch/commit-changes [rchg] [] {:commit-local? true}))))))
(def clear-color-for-rename
(ptk/reify ::clear-color-for-rename
@ -120,7 +121,7 @@
:color color}
uchg {:type :mod-color
:color prev}]
(rx/of (dwc/commit-changes [rchg] [uchg] {:commit-local? true})
(rx/of (dch/commit-changes [rchg] [uchg] {:commit-local? true})
(sync-file (:current-file-id state) file-id))))))
(defn delete-color
@ -134,7 +135,7 @@
:id id}
uchg {:type :add-color
:color prev}]
(rx/of (dwc/commit-changes [rchg] [uchg] {:commit-local? true}))))))
(rx/of (dch/commit-changes [rchg] [uchg] {:commit-local? true}))))))
(defn add-media
[{:keys [id] :as media}]
@ -147,7 +148,7 @@
:object obj}
uchg {:type :del-media
:id id}]
(rx/of (dwc/commit-changes [rchg] [uchg] {:commit-local? true}))))))
(rx/of (dch/commit-changes [rchg] [uchg] {:commit-local? true}))))))
(defn rename-media
[id new-name]
@ -169,7 +170,7 @@
:name (:name object)
:path (:path object)}}]]
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}))))))
(rx/of (dch/commit-changes rchanges uchanges {:commit-local? true}))))))
(defn delete-media
[{:keys [id] :as params}]
@ -182,7 +183,7 @@
:id id}
uchg {:type :add-media
:object prev}]
(rx/of (dwc/commit-changes [rchg] [uchg] {:commit-local? true}))))))
(rx/of (dch/commit-changes [rchg] [uchg] {:commit-local? true}))))))
(defn add-typography
([typography] (add-typography typography true))
@ -196,7 +197,7 @@
:typography typography}
uchg {:type :del-typography
:id (:id typography)}]
(rx/of (dwc/commit-changes [rchg] [uchg] {:commit-local? true})
(rx/of (dch/commit-changes [rchg] [uchg] {:commit-local? true})
#(cond-> %
edit?
(assoc-in [:workspace-local :rename-typography] (:id typography))))))))))
@ -213,7 +214,7 @@
:typography typography}
uchg {:type :mod-typography
:typography prev}]
(rx/of (dwc/commit-changes [rchg] [uchg] {:commit-local? true})
(rx/of (dch/commit-changes [rchg] [uchg] {:commit-local? true})
(sync-file (:current-file-id state) file-id))))))
(defn delete-typography
@ -227,7 +228,7 @@
:id id}
uchg {:type :add-typography
:typography prev}]
(rx/of (dwc/commit-changes [rchg] [uchg] {:commit-local? true}))))))
(rx/of (dch/commit-changes [rchg] [uchg] {:commit-local? true}))))))
(def add-component
"Add a new component to current file library, from the currently selected shapes."
@ -242,7 +243,7 @@
(let [[group rchanges uchanges]
(dwlh/generate-add-component selected objects page-id file-id)]
(when-not (empty? rchanges)
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})
(rx/of (dch/commit-changes rchanges uchanges {:commit-local? true})
(dwc/select-shapes (d/ordered-set (:id group))))))))))
(defn rename-component
@ -273,7 +274,7 @@
:path (:path component)
:objects objects}]]
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}))))))
(rx/of (dch/commit-changes rchanges uchanges {:commit-local? true}))))))
(defn duplicate-component
"Create a new component copied from the one with the given id."
@ -301,7 +302,7 @@
uchanges [{:type :del-component
:id (:id new-shape)}]]
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}))))))
(rx/of (dch/commit-changes rchanges uchanges {:commit-local? true}))))))
(defn delete-component
"Delete the component with the given id, from the current file library."
@ -321,7 +322,7 @@
:path (:path component)
:shapes (vals (:objects component))}]]
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}))))))
(rx/of (dch/commit-changes rchanges uchanges {:commit-local? true}))))))
(defn instantiate-component
"Create a new shape in the current page, from the component with the given id
@ -398,7 +399,7 @@
:ignore-touched true})
new-shapes)]
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})
(rx/of (dch/commit-changes rchanges uchanges {:commit-local? true})
(dwc/select-shapes (d/ordered-set (:id new-shape))))))))
(defn detach-component
@ -461,7 +462,7 @@
:val (:touched obj)}]})
shapes)]
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}))))))
(rx/of (dch/commit-changes rchanges uchanges {:commit-local? true}))))))
(defn nav-to-component-file
[file-id]
@ -514,7 +515,7 @@
rchanges
local-library))
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}))))))
(rx/of (dch/commit-changes rchanges uchanges {:commit-local? true}))))))
(defn update-component
"Modify the component linked to the shape with the given id, in the
@ -569,11 +570,11 @@
file))
(rx/of (when (seq local-rchanges)
(dwc/commit-changes local-rchanges local-uchanges
(dch/commit-changes local-rchanges local-uchanges
{:commit-local? true
:file-id (:id local-library)}))
(when (seq rchanges)
(dwc/commit-changes rchanges uchanges
(dch/commit-changes rchanges uchanges
{:commit-local? true
:file-id file-id})))))))
@ -623,7 +624,7 @@
(rx/concat
(rx/of (dm/hide-tag :sync-dialog))
(when rchanges
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true
(rx/of (dch/commit-changes rchanges uchanges {:commit-local? true
:file-id file-id})))
(when (not= file-id library-id)
;; When we have just updated the library file, give some time for the
@ -666,7 +667,7 @@
(log/debug :msg "SYNC-FILE (2nd stage) finished" :js/rchanges (log-changes
rchanges
file))
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true
(rx/of (dch/commit-changes rchanges uchanges {:commit-local? true
:file-id file-id})))))))
(def ignore-sync

View file

@ -24,6 +24,7 @@
(d/export edition/start-move-path-point)
(d/export edition/start-path-edit)
(d/export edition/create-node-at-position)
(d/export edition/move-selected)
;; Selection
(d/export selection/handle-selection)

View file

@ -7,7 +7,7 @@
(ns app.main.data.workspace.path.changes
(:require
[app.common.spec :as us]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.path.helpers :as helpers]
[app.main.data.workspace.path.spec :as spec]
[app.main.data.workspace.path.state :as st]
@ -67,7 +67,7 @@
(let [shape (get-in state (st/get-path state))
page-id (:current-page-id state)
[rch uch] (generate-path-changes page-id shape old-content (:content shape))]
(rx/of (dwc/commit-changes rch uch {:commit-local? true})))
(rx/of (dch/commit-changes rch uch {:commit-local? true})))
(rx/empty)))))))

View file

@ -9,7 +9,7 @@
[app.common.data :as d]
[app.common.geom.point :as gpt]
[app.common.math :as mth]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.path.changes :as changes]
[app.main.data.workspace.path.common :as common]
[app.main.data.workspace.path.helpers :as helpers]
@ -60,49 +60,65 @@
[rch uch] (changes/generate-path-changes page-id shape (:content shape) new-content)]
(rx/of (dwc/commit-changes rch uch {:commit-local? true})
(rx/of (dch/commit-changes rch uch {:commit-local? true})
(selection/update-selection point-change)
(fn [state] (update-in state [:workspace-local :edit-path id] dissoc :content-modifiers :moving-nodes :moving-handler)))))))
(defn modify-content-point
[content {dx :x dy :y} modifiers point]
(let [point-indices (upc/point-indices content point) ;; [indices]
handler-indices (upc/handler-indices content point) ;; [[index prefix]]
modify-point
(fn [modifiers index]
(-> modifiers
(update index assoc :x dx :y dy)))
modify-handler
(fn [modifiers [index prefix]]
(let [cx (d/prefix-keyword prefix :x)
cy (d/prefix-keyword prefix :y)]
(-> modifiers
(update index assoc cx dx cy dy))))]
(as-> modifiers $
(reduce modify-point $ point-indices)
(reduce modify-handler $ handler-indices))))
(defn set-move-modifier
[points move-modifier]
(ptk/reify ::set-modifiers
ptk/UpdateEvent
(update [_ state]
(let [id (st/get-path-id state)
content (get-in state (st/get-path state :content))
modifiers-reducer (partial modify-content-point content move-modifier)
content-modifiers (get-in state [:workspace-local :edit-path id :content-modifiers] {})
content-modifiers (->> points
(reduce modifiers-reducer content-modifiers))]
(-> state
(assoc-in [:workspace-local :edit-path id :content-modifiers] content-modifiers))))))
(defn move-selected-path-point [from-point to-point]
(letfn [(modify-content-point [content {dx :x dy :y} modifiers point]
(let [point-indices (upc/point-indices content point) ;; [indices]
handler-indices (upc/handler-indices content point) ;; [[index prefix]]
(ptk/reify ::move-point
ptk/UpdateEvent
(update [_ state]
(let [id (st/get-path-id state)
content (get-in state (st/get-path state :content))
delta (gpt/subtract to-point from-point)
modify-point
(fn [modifiers index]
(-> modifiers
(update index assoc :x dx :y dy)))
modifiers-reducer (partial modify-content-point content delta)
modify-handler
(fn [modifiers [index prefix]]
(let [cx (d/prefix-keyword prefix :x)
cy (d/prefix-keyword prefix :y)]
(-> modifiers
(update index assoc cx dx cy dy))))]
points (get-in state [:workspace-local :edit-path id :selected-points] #{})
(as-> modifiers $
(reduce modify-point $ point-indices)
(reduce modify-handler $ handler-indices))))]
modifiers (get-in state [:workspace-local :edit-path id :content-modifiers] {})
modifiers (->> points
(reduce modifiers-reducer modifiers))]
(ptk/reify ::move-point
ptk/UpdateEvent
(update [_ state]
(let [id (st/get-path-id state)
content (get-in state (st/get-path state :content))
delta (gpt/subtract to-point from-point)
modifiers-reducer (partial modify-content-point content delta)
points (get-in state [:workspace-local :edit-path id :selected-points] #{})
modifiers (get-in state [:workspace-local :edit-path id :content-modifiers] {})
modifiers (->> points
(reduce modifiers-reducer {}))]
(-> state
(assoc-in [:workspace-local :edit-path id :moving-nodes] true)
(assoc-in [:workspace-local :edit-path id :content-modifiers] modifiers)))))))
(-> state
(assoc-in [:workspace-local :edit-path id :moving-nodes] true)
(assoc-in [:workspace-local :edit-path id :content-modifiers] modifiers))))))
(declare drag-selected-points)
@ -126,7 +142,6 @@
ptk/WatchEvent
(watch [_ state stream]
(let [stopper (->> stream (rx/filter ms/mouse-up?))
zoom (get-in state [:workspace-local :zoom])
id (get-in state [:workspace-local :edition])
snap-toggled (get-in state [:workspace-local :edit-path id :snap-toggled])
@ -143,6 +158,73 @@
(rx/map #(move-selected-path-point start-position %)))
(rx/of (apply-content-modifiers)))))))
(defn- get-displacement
"Retrieve the correct displacement delta point for the
provided direction speed and distances thresholds."
[direction]
(case direction
:up (gpt/point 0 (- 1))
:down (gpt/point 0 1)
:left (gpt/point (- 1) 0)
:right (gpt/point 1 0)))
(defn finish-move-selected []
(ptk/reify ::move-selected
ptk/UpdateEvent
(update [_ state]
(let [id (get-in state [:workspace-local :edition])]
(-> state
(update-in [:workspace-local :edit-path id] dissoc :current-move))))))
(defn move-selected
[direction shift?]
(let [same-event (js/Symbol "same-event")]
(ptk/reify ::move-selected
IDeref
(-deref [_] direction)
ptk/UpdateEvent
(update [_ state]
(let [id (get-in state [:workspace-local :edition])
current-move (get-in state [:workspace-local :edit-path id :current-move])]
(if (nil? current-move)
(-> state
(assoc-in [:workspace-local :edit-path id :moving-nodes] true)
(assoc-in [:workspace-local :edit-path id :current-move] same-event))
state)))
ptk/WatchEvent
(watch [_ state stream]
(let [id (get-in state [:workspace-local :edition])
current-move (get-in state [:workspace-local :edit-path id :current-move])]
(if (= same-event current-move)
(let [points (get-in state [:workspace-local :edit-path id :selected-points] #{})
move-events (->> stream
(rx/filter (ptk/type? ::move-selected))
(rx/filter #(= direction (deref %))))
stopper (->> move-events (rx/debounce 100) (rx/take 1))
scale (if shift? (gpt/point 10) (gpt/point 1))
mov-vec (gpt/multiply (get-displacement direction) scale)]
(rx/concat
(rx/merge
(->> move-events
(rx/take-until stopper)
(rx/scan #(gpt/add %1 mov-vec) (gpt/point 0 0))
(rx/map #(set-move-modifier points %)))
;; First event is not read by the stream so we need to send it again
(rx/of (move-selected direction shift?)))
(rx/of (apply-content-modifiers)
(finish-move-selected))))
(rx/empty)))))))
(defn start-move-handler
[index prefix]
(ptk/reify ::start-move-handler

View file

@ -18,9 +18,6 @@
[app.util.path.subpaths :as ups]
[potok.core :as ptk]))
;; CONSTANTS
(defonce enter-keycode 13)
(defn end-path-event? [{:keys [type shift] :as event}]
(or (= (ptk/type event) ::common/finish-path)
(= (ptk/type event) :esc-pressed)

View file

@ -32,61 +32,95 @@
(rx/empty))))))
(def shortcuts
{:move-nodes {:tooltip "V"
:command "v"
:fn #(st/emit! (drp/change-edit-mode :move))}
{:move-nodes {:tooltip "V"
:command "v"
:fn #(st/emit! (drp/change-edit-mode :move))}
:draw-nodes {:tooltip "P"
:command "p"
:fn #(st/emit! (drp/change-edit-mode :draw))}
:draw-nodes {:tooltip "P"
:command "p"
:fn #(st/emit! (drp/change-edit-mode :draw))}
:add-node {:tooltip "+"
:command "+"
:fn #(st/emit! (drp/add-node))}
:add-node {:tooltip "+"
:command "+"
:fn #(st/emit! (drp/add-node))}
:delete-node {:tooltip (ds/supr)
:command ["del" "backspace"]
:fn #(st/emit! (drp/remove-node))}
:delete-node {:tooltip (ds/supr)
:command ["del" "backspace"]
:fn #(st/emit! (drp/remove-node))}
:merge-nodes {:tooltip (ds/meta "J")
:command (ds/c-mod "j")
:fn #(st/emit! (drp/merge-nodes))}
:merge-nodes {:tooltip (ds/meta "J")
:command (ds/c-mod "j")
:fn #(st/emit! (drp/merge-nodes))}
:join-nodes {:tooltip "J"
:command "j"
:fn #(st/emit! (drp/join-nodes))}
:join-nodes {:tooltip "J"
:command "j"
:fn #(st/emit! (drp/join-nodes))}
:separate-nodes {:tooltip "K"
:command "k"
:fn #(st/emit! (drp/separate-nodes))}
:separate-nodes {:tooltip "K"
:command "k"
:fn #(st/emit! (drp/separate-nodes))}
:make-corner {:tooltip "B"
:command "b"
:fn #(st/emit! (drp/make-corner))}
:make-corner {:tooltip "B"
:command "b"
:fn #(st/emit! (drp/make-corner))}
:make-curve {:tooltip (ds/meta "B")
:command (ds/c-mod "b")
:fn #(st/emit! (drp/make-curve))}
:make-curve {:tooltip (ds/meta "B")
:command (ds/c-mod "b")
:fn #(st/emit! (drp/make-curve))}
:snap-nodes {:tooltip (ds/meta "'")
:command (ds/c-mod "'")
:fn #(st/emit! (drp/toggle-snap))}
:escape {:tooltip (ds/esc)
:command "escape"
:fn #(st/emit! (esc-pressed))}
:snap-nodes {:tooltip (ds/meta "'")
:command (ds/c-mod "'")
:fn #(st/emit! (drp/toggle-snap))}
:start-editing {:tooltip (ds/enter)
:command "enter"
:fn #(st/emit! (dw/start-editing-selected))}
:escape {:tooltip (ds/esc)
:command "escape"
:fn #(st/emit! (esc-pressed))}
:undo {:tooltip (ds/meta "Z")
:command (ds/c-mod "z")
:fn #(st/emit! (drp/undo-path))}
:start-editing {:tooltip (ds/enter)
:command "enter"
:fn #(st/emit! (dw/start-editing-selected))}
:undo {:tooltip (ds/meta "Z")
:command (ds/c-mod "z")
:fn #(st/emit! (drp/undo-path))}
:redo {:tooltip (ds/meta "Y")
:command [(ds/c-mod "shift+z") (ds/c-mod "y")]
:fn #(st/emit! (drp/redo-path))}
;; Arrow movement
:move-fast-up {:tooltip (ds/shift ds/up-arrow)
:command "shift+up"
:fn #(st/emit! (drp/move-selected :up true))}
:move-fast-down {:tooltip (ds/shift ds/down-arrow)
:command "shift+down"
:fn #(st/emit! (drp/move-selected :down true))}
:move-fast-right {:tooltip (ds/shift ds/right-arrow)
:command "shift+right"
:fn #(st/emit! (drp/move-selected :right true))}
:move-fast-left {:tooltip (ds/shift ds/left-arrow)
:command "shift+left"
:fn #(st/emit! (drp/move-selected :left true))}
:move-unit-up {:tooltip ds/up-arrow
:command "up"
:fn #(st/emit! (drp/move-selected :up false))}
:move-unit-down {:tooltip ds/down-arrow
:command "down"
:fn #(st/emit! (drp/move-selected :down false))}
:move-unit-left {:tooltip ds/right-arrow
:command "right"
:fn #(st/emit! (drp/move-selected :right false))}
:move-unit-right {:tooltip ds/left-arrow
:command "left"
:fn #(st/emit! (drp/move-selected :left false))}
:redo {:tooltip (ds/meta "Y")
:command [(ds/c-mod "shift+z") (ds/c-mod "y")]
:fn #(st/emit! (drp/redo-path))}
})
(defn get-tooltip [shortcut]

View file

@ -6,7 +6,7 @@
(ns app.main.data.workspace.path.tools
(:require
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.path.changes :as changes]
[app.main.data.workspace.path.common :as common]
[app.main.data.workspace.path.state :as st]
@ -32,7 +32,7 @@
new-content (-> (tool-fn (:content shape) points)
(ups/close-subpaths))
[rch uch] (changes/generate-path-changes page-id shape (:content shape) new-content)]
(rx/of (dwc/commit-changes rch uch {:commit-local? true})))))))
(rx/of (dch/commit-changes rch uch {:commit-local? true})))))))
(defn make-corner
([]

View file

@ -17,6 +17,7 @@
[app.main.data.media :as di]
[app.main.data.messages :as dm]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.selection :as dws]
[app.main.data.workspace.svg-upload :as svg]
@ -51,7 +52,7 @@
(let [stoper (rx/filter #(= ::finalize %) stream)
forcer (rx/filter #(= ::force-persist %) stream)
notifier (->> stream
(rx/filter (ptk/type? ::dwc/commit-changes))
(rx/filter dch/commit-changes?)
(rx/debounce 2000)
(rx/merge stoper forcer))
@ -79,7 +80,7 @@
(st/emit! (update-persistence-status {:status :saved})))]
(->> (rx/merge
(->> stream
(rx/filter (ptk/type? ::dwc/commit-changes))
(rx/filter dch/commit-changes?)
(rx/map deref)
(rx/filter local-file?)
(rx/tap on-dirty)
@ -91,7 +92,7 @@
(rx/tap on-saving)
(rx/take-until (rx/delay 100 stoper)))
(->> stream
(rx/filter (ptk/type? ::dwc/commit-changes))
(rx/filter dch/commit-changes?)
(rx/map deref)
(rx/filter library-file?)
(rx/filter (complement #(empty? (:changes %))))

View file

@ -18,6 +18,7 @@
[app.common.spec :as us]
[app.common.uuid :as uuid]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.changes :as dch]
[app.main.data.modal :as md]
[app.main.streams :as ms]
[app.main.worker :as uw]))
@ -395,7 +396,7 @@
(map #(get-in % [:obj :id]))
(into (d/ordered-set)))]
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})
(rx/of (dch/commit-changes rchanges uchanges {:commit-local? true})
(select-shapes selected))))))
(defn change-hover-state

View file

@ -10,6 +10,7 @@
[app.main.data.workspace :as dw]
[app.main.data.workspace.colors :as mdc]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.undo :as dwu]
[app.main.data.workspace.drawing :as dwd]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.texts :as dwtxt]
@ -123,7 +124,7 @@
:clear-undo {:tooltip (ds/meta "Q")
:command (ds/c-mod "q")
:fn #(st/emit! dwc/reinitialize-undo)}
:fn #(st/emit! dwu/reinitialize-undo)}
:draw-frame {:tooltip "A"
:command "a"

View file

@ -14,6 +14,7 @@
[app.common.pages :as cp]
[app.common.uuid :as uuid]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.changes :as dch]
[app.main.repo :as rp]
[app.util.color :as uc]
[app.util.path.parser :as upp]
@ -462,7 +463,7 @@
rchanges (conj rchanges reg-objects-action)]
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})
(rx/of (dch/commit-changes rchanges uchanges {:commit-local? true})
(dwc/select-shapes (d/ordered-set root-id))))
(catch :default e

View file

@ -15,6 +15,8 @@
[app.main.data.workspace.selection :as dws]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.transforms :as dwt]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.undo :as dwu]
[app.main.fonts :as fonts]
[app.util.object :as obj]
[app.util.text-editor :as ted]
@ -77,8 +79,8 @@
(when (and (not= content (:content shape))
(some? (:current-page-id state)))
(rx/of
(dwc/update-shapes [id] #(assoc % :content content))
(dwc/commit-undo-transaction)))))
(dch/update-shapes [id] #(assoc % :content content))
(dwu/commit-undo-transaction)))))
(rx/of (dws/deselect-shape id)
(dwc/delete-shapes [id])))))))
@ -141,7 +143,7 @@
shape-ids (cond (= (:type shape) :text) [id]
(= (:type shape) :group) (cp/get-children id objects))]
(rx/of (dwc/update-shapes shape-ids update-fn))))))
(rx/of (dch/update-shapes shape-ids update-fn))))))
(defn update-paragraph-attrs
[{:keys [id attrs]}]
@ -169,7 +171,7 @@
shape-ids (cond (= (:type shape) :text) [id]
(= (:type shape) :group) (cp/get-children id objects))]
(rx/of (dwc/update-shapes shape-ids update-fn))))))))
(rx/of (dch/update-shapes shape-ids update-fn))))))))
(defn update-text-attrs
[{:keys [id attrs]}]
@ -187,7 +189,7 @@
update-fn #(update-shape % txt/is-text-node? attrs/merge attrs)
shape-ids (cond (= (:type shape) :text) [id]
(= (:type shape) :group) (cp/get-children id objects))]
(rx/of (dwc/update-shapes shape-ids update-fn)))))))
(rx/of (dch/update-shapes shape-ids update-fn)))))))
;; --- RESIZE UTILS
@ -218,58 +220,41 @@
ptk/WatchEvent
(watch [_ state stream]
(let [page-id (:current-page-id state)
objects0 (get-in state [:workspace-file :data :pages-index page-id :objects])
objects1 (get-in state [:workspace-data :pages-index page-id :objects])]
(if-not (every? #(contains? objects1(first %)) changes)
objects (get-in state [:workspace-data :pages-index page-id :objects])]
(if-not (every? #(contains? objects(first %)) changes)
(rx/empty)
(let [change-text-shape
(fn [objects [id [new-width new-height]]]
(when (contains? objects id)
(let [shape (get objects id)
{:keys [selrect grow-type overflow-text]} (gsh/transform-shape shape)
{shape-width :width shape-height :height} selrect
modifier-width (gsh/resize-modifiers shape :width new-width)
modifier-height (gsh/resize-modifiers shape :height new-height)
(let [changes-map (->> changes (into {}))
ids (keys changes-map)
update-fn
(fn [shape]
(let [[new-width new-height] (get changes-map (:id shape))
{:keys [selrect grow-type overflow-text]} (gsh/transform-shape shape)
{shape-width :width shape-height :height} selrect
shape (cond-> shape
(and overflow-text (not= :fixed grow-type))
(assoc :overflow-text false)
modifier-width (gsh/resize-modifiers shape :width new-width)
modifier-height (gsh/resize-modifiers shape :height new-height)]
(and (= :fixed grow-type) (not overflow-text) (> new-height shape-height))
(assoc :overflow-text true)
(cond-> shape
(and overflow-text (not= :fixed grow-type))
(assoc :overflow-text false)
(and (= :fixed grow-type) overflow-text (<= new-height shape-height))
(assoc :overflow-text false)
(and (= :fixed grow-type) (not overflow-text) (> new-height shape-height))
(assoc :overflow-text true)
(and (not-changed? shape-width new-width) (= grow-type :auto-width))
(-> (assoc :modifiers modifier-width)
(gsh/transform-shape))
(and (= :fixed grow-type) overflow-text (<= new-height shape-height))
(assoc :overflow-text false)
(and (not-changed? shape-height new-height)
(or (= grow-type :auto-height) (= grow-type :auto-width)))
(-> (assoc :modifiers modifier-height)
(gsh/transform-shape)))]
(assoc objects id shape))))
(and (not-changed? shape-width new-width) (= grow-type :auto-width))
(-> (assoc :modifiers modifier-width)
(gsh/transform-shape))
undo-transaction (get-in state [:workspace-undo :transaction])
objects2 (->> changes (reduce change-text-shape objects1))
(and (not-changed? shape-height new-height)
(or (= grow-type :auto-height) (= grow-type :auto-width)))
(-> (assoc :modifiers modifier-height)
(gsh/transform-shape)))))]
regchg {:type :reg-objects
:page-id page-id
:shapes (vec (keys changes))}
rchanges (dwc/generate-changes page-id objects1 objects2)
uchanges (dwc/generate-changes page-id objects2 objects0)]
(if (seq rchanges)
(rx/concat
(when-not undo-transaction
(rx/of (dwc/start-undo-transaction)))
(rx/of (dwc/commit-changes (conj rchanges regchg) (conj uchanges regchg) {:commit-local? true}))
(when-not undo-transaction
(rx/of (dwc/discard-undo-transaction)))))))))))
(rx/of (dch/update-shapes ids update-fn {:reg-objects? true}))))))))
;; When a resize-event arrives we start "buffering" for a time
;; after that time we invoke `resize-text-batch` with all the changes

View file

@ -14,7 +14,9 @@
[app.common.pages :as cp]
[app.common.spec :as us]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.selection :as dws]
[app.main.data.workspace.undo :as dwu]
[app.main.refs :as refs]
[app.main.snap :as snap]
[app.main.store :as st]
@ -140,7 +142,7 @@
(filter #(= :text (:type %)))
(map :id))]
(rx/concat
(rx/of (dwc/update-shapes text-shapes-ids #(assoc % :grow-type :fixed)))
(rx/of (dch/update-shapes text-shapes-ids #(assoc % :grow-type :fixed)))
(->> ms/mouse-position
(rx/with-latest vector ms/mouse-position-shift)
(rx/map normalize-proportion-lock)
@ -259,9 +261,9 @@
:shapes [(:id shape)]})))]
(when-not (empty? rch)
(rx/of dwc/pop-undo-into-transaction
(dwc/commit-changes rch uch {:commit-local? true})
(dwc/commit-undo-transaction)
(rx/of dwu/pop-undo-into-transaction
(dch/commit-changes rch uch {:commit-local? true})
(dwu/commit-undo-transaction)
(dwc/expand-collapse frame-id)))))))
(defn start-move
@ -452,35 +454,13 @@
(ptk/reify ::apply-modifiers
ptk/WatchEvent
(watch [_ state stream]
(let [page-id (:current-page-id state)
objects0 (get-in state [:workspace-file :data :pages-index page-id :objects])
objects1 (get-in state [:workspace-data :pages-index page-id :objects])
;; ID's + Children ID's
ids-with-children (d/concat [] (mapcat #(cp/get-children % objects1) ids) ids)
;; For each shape applies the modifiers by transforming the objects
update-shape #(update %1 %2 gsh/transform-shape)
objects2 (reduce update-shape objects1 ids-with-children)
regchg {:type :reg-objects
:page-id page-id
:shapes (vec ids)}
;; we need to generate redo chages from current
;; state (with current temporal values) to new state but
;; the undo should be calculated from clear current
;; state (without temporal values in it, for this reason
;; we have 3 different objects references).
rchanges (conj (dwc/generate-changes page-id objects1 objects2) regchg)
uchanges (conj (dwc/generate-changes page-id objects2 objects0) regchg)]
(rx/of (dwc/start-undo-transaction)
(dwc/commit-changes rchanges uchanges {:commit-local? true})
(let [objects (dwc/lookup-page-objects state)
children-ids (->> ids (mapcat #(cp/get-children % objects)))
ids-with-children (d/concat [] children-ids ids)]
(rx/of (dwu/start-undo-transaction)
(dch/update-shapes ids-with-children gsh/transform-shape {:reg-objects? true})
(clear-local-transform)
(dwc/commit-undo-transaction))))))
(dwu/commit-undo-transaction))))))
;; --- Update Dimensions

View file

@ -0,0 +1,134 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) UXBOX Labs SL
(ns app.main.data.workspace.undo
(:require
[app.common.data :as d]
[app.common.geom.proportions :as gpr]
[app.common.geom.shapes :as gsh]
[app.common.pages :as cp]
[app.common.pages.spec :as spec]
[app.common.spec :as us]
[app.common.uuid :as uuid]
[app.main.worker :as uw]
[app.main.streams :as ms]
[app.util.logging :as log]
[app.util.timers :as ts]
[beicon.core :as rx]
[cljs.spec.alpha :as s]
[clojure.set :as set]
[cuerdas.core :as str]
[potok.core :as ptk]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Undo / Redo
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(s/def ::undo-changes ::cp/changes)
(s/def ::redo-changes ::cp/changes)
(s/def ::undo-entry
(s/keys :req-un [::undo-changes ::redo-changes]))
(def MAX-UNDO-SIZE 50)
(defn- conj-undo-entry
[undo data]
(let [undo (conj undo data)
cnt (count undo)]
(if (> cnt MAX-UNDO-SIZE)
(subvec undo (- cnt MAX-UNDO-SIZE))
undo)))
(defn- materialize-undo
[changes index]
(ptk/reify ::materialize-undo
ptk/UpdateEvent
(update [_ state]
(-> state
(update :workspace-data cp/process-changes changes)
(assoc-in [:workspace-undo :index] index)))))
(defn- reset-undo
[index]
(ptk/reify ::reset-undo
ptk/UpdateEvent
(update [_ state]
(-> state
(update :workspace-undo dissoc :undo-index)
(update-in [:workspace-undo :items] (fn [queue] (into [] (take (inc index) queue))))))))
(defn- add-undo-entry
[state entry]
(if (and entry
(not-empty (:undo-changes entry))
(not-empty (:redo-changes entry)))
(let [index (get-in state [:workspace-undo :index] -1)
items (get-in state [:workspace-undo :items] [])
items (->> items (take (inc index)) (into []))
items (conj-undo-entry items entry)]
(-> state
(update :workspace-undo assoc :items items
:index (min (inc index)
(dec MAX-UNDO-SIZE)))))
state))
(defn- accumulate-undo-entry
[state {:keys [undo-changes redo-changes]}]
(-> state
(update-in [:workspace-undo :transaction :undo-changes] #(into undo-changes %))
(update-in [:workspace-undo :transaction :redo-changes] #(into % redo-changes))))
(defn- append-undo
[entry]
(us/assert ::undo-entry entry)
(ptk/reify ::append-undo
ptk/UpdateEvent
(update [_ state]
(if (get-in state [:workspace-undo :transaction])
(accumulate-undo-entry state entry)
(add-undo-entry state entry)))))
(defonce empty-tx {:undo-changes [] :redo-changes []})
(defn start-undo-transaction []
(ptk/reify ::start-undo-transaction
ptk/UpdateEvent
(update [_ state]
;; We commit the old transaction before starting the new one
(let [current-tx (get-in state [:workspace-undo :transaction])]
(cond-> state
(nil? current-tx) (assoc-in [:workspace-undo :transaction] empty-tx))))))
(defn discard-undo-transaction []
(ptk/reify ::discard-undo-transaction
ptk/UpdateEvent
(update [_ state]
(update state :workspace-undo dissoc :transaction))))
(defn commit-undo-transaction []
(ptk/reify ::commit-undo-transaction
ptk/UpdateEvent
(update [_ state]
(-> state
(add-undo-entry (get-in state [:workspace-undo :transaction]))
(update :workspace-undo dissoc :transaction)))))
(def pop-undo-into-transaction
(ptk/reify ::last-undo-into-transaction
ptk/UpdateEvent
(update [_ state]
(let [index (get-in state [:workspace-undo :index] -1)]
(cond-> state
(>= index 0) (accumulate-undo-entry (get-in state [:workspace-undo :items index]))
(>= index 0) (update-in [:workspace-undo :index] dec))))))
(def reinitialize-undo
(ptk/reify ::reset-undo
ptk/UpdateEvent
(update [_ state]
(assoc state :workspace-undo {}))))

View file

@ -11,8 +11,8 @@
[app.main.store :as st]
[app.main.refs :as refs]
[app.common.geom.point :as gpt]
[app.util.globals :as globals])
(:import goog.events.KeyCodes))
[app.util.globals :as globals]
[app.util.keyboard :as kbd]))
;; --- User Events
@ -113,8 +113,7 @@
ob (->> (rx/merge
(->> st/stream
(rx/filter keyboard-event?)
(rx/filter #(let [key (:key %)]
(= key KeyCodes.ALT)))
(rx/filter kbd/altKey?)
(rx/map #(= :down (:type %))))
;; Fix a situation caused by using `ctrl+alt` kind of shortcuts,
;; that makes keyboard-alt stream registring the key pressed but
@ -130,10 +129,7 @@
ob (->> (rx/merge
(->> st/stream
(rx/filter keyboard-event?)
(rx/filter #(let [key (:key %)]
(or
(= key KeyCodes.CTRL)
(= key KeyCodes.META))))
(rx/filter kbd/ctrlKey?)
(rx/map #(= :down (:type %))))
;; Fix a situation caused by using `ctrl+alt` kind of shortcuts,
;; that makes keyboard-alt stream registring the key pressed but

View file

@ -46,7 +46,7 @@
(mf/use-callback
(mf/deps top (:offset @local))
(fn [node]
(when (and node (not fixed?))
(when (some? node)
(let [{node-height :height} (dom/get-bounding-rect node)
{window-height :height} (dom/get-window-size)
target-offset (if (> (+ top node-height) window-height)

View file

@ -3,10 +3,10 @@
[rumext.alpha :as mf]
[app.common.uuid :as uuid]
[app.util.dom :as dom]
[app.util.keyboard :as kbd]
[goog.events :as events]
[goog.object :as gobj])
(:import goog.events.EventType
goog.events.KeyCodes))
(:import goog.events.EventType))
(mf/defc dropdown'
{::mf/wrap-props false}
@ -27,7 +27,7 @@
on-keyup
(fn [event]
(when (= (.-keyCode event) 27) ; ESC
(when (kbd/esc? event)
(on-close)))
on-mount

View file

@ -200,7 +200,7 @@
(when selected?
[:& file-menu {:files selected-file-objs
:show? (:menu-open @local)
:left (:x (:menu-pos @local))
:left (+ 24 (:x (:menu-pos @local)))
:top (:y (:menu-pos @local))
:navigate? navigate?
:on-edit on-edit

View file

@ -31,9 +31,10 @@
(if (:is-default team)
(t locale "dashboard.your-penpot")
(:name team))))
(st/emit! (dd/search-files {:team-id (:id team)
:search-term search-term})
(dd/clear-selected-files))))
(when search-term
(st/emit! (dd/search-files {:team-id (:id team)
:search-term search-term})
(dd/clear-selected-files)))))
[:*
[:header.dashboard-header

View file

@ -14,6 +14,7 @@
[app.main.data.dashboard :as dd]
[app.main.data.messages :as dm]
[app.main.data.modal :as modal]
[app.main.data.users :as du]
[app.main.refs :as refs]
[app.main.repo :as rp]
[app.main.store :as st]
@ -241,6 +242,8 @@
::mf/register-as ::leave-and-reassign}
[{:keys [members profile team accept]}]
(let [form (fm/use-form :spec ::leave-modal-form :initial {})
not-current-user? (fn [{:keys [id]}] (not= id (:id profile)))
members (->> members (filterv not-current-user?))
options (into [{:value "" :label (tr "modals.leave-and-reassign.select-memeber-to-promote")}]
(map #(hash-map :label (:name %) :value (str (:id %))) members))
@ -264,11 +267,14 @@
[:div.modal-content.generic-form
[:p (tr "modals.leave-and-reassign.hint1" (:name team))]
[:p (tr "modals.leave-and-reassign.hint2")]
[:& fm/form {:form form}
[:& fm/select {:name :member-id
:options options}]]]
(if (empty? members)
[:p (tr "modals.leave-and-reassign.forbiden")]
[:*
[:p (tr "modals.leave-and-reassign.hint2")]
[:& fm/form {:form form}
[:& fm/select {:name :member-id
:options options}]]])]
[:div.modal-footer
[:div.action-buttons
@ -279,7 +285,7 @@
[:input.accept-button
{:type "button"
:class (when-not (:valid @form) "btn-disabled")
:class (if (:valid @form) "primary" "btn-disabled")
:disabled (not (:valid @form))
:value (tr "modals.leave-and-reassign.promote-and-leave")
:on-click on-accept}]]]]]))
@ -314,7 +320,9 @@
(fn []
(let [team-id (:default-team-id profile)]
(da/set-current-team! team-id)
(st/emit! (rt/nav :dashboard-projects {:team-id team-id})))))
(st/emit! (modal/hide)
(du/fetch-teams)
(rt/nav :dashboard-projects {:team-id team-id})))))
leave-fn
(mf/use-callback

View file

@ -303,7 +303,7 @@
(if (:is-default team)
(tr "dashboard.your-penpot")
(:name team))))
(st/emitf (dd/fetch-team-members team)
(st/emit! (dd/fetch-team-members team)
(dd/fetch-team-stats team))))
[:*

View file

@ -121,10 +121,7 @@
(defn add-layer-props [attrs shape]
(cond-> attrs
(:opacity shape)
(obj/set! "opacity" (:opacity shape))
(and (:blend-mode shape) (not= (:blend-mode shape) :normal))
(obj/set! "mixBlendMode" (d/name (:blend-mode shape)))))
(obj/set! "opacity" (:opacity shape))))
(defn extract-svg-attrs
[render-id svg-defs svg-attrs]

View file

@ -6,6 +6,7 @@
(ns app.main.ui.shapes.shape
(:require
[app.common.data :as d]
[app.common.uuid :as uuid]
[app.main.ui.context :as muc]
[app.main.ui.shapes.filters :as filters]
@ -23,7 +24,10 @@
render-id (mf/use-memo #(str (uuid/next)))
filter-id (str "filter_" render-id)
styles (-> (obj/new)
(obj/set! "pointerEvents" pointer-events))
(obj/set! "pointerEvents" pointer-events)
(cond-> (and (:blend-mode shape) (not= (:blend-mode shape) :normal))
(obj/set! "mixBlendMode" (d/name (:blend-mode shape)))))
{:keys [x y width height type]} shape
frame? (= :frame type)

View file

@ -65,15 +65,15 @@
:frame frame
:childs childs
:is-child-selected? true}]
(when (and (:interactions shape) show-interactions?)
(when (:interactions shape)
[:rect {:x (- x 1)
:y (- y 1)
:width (+ width 2)
:height (+ height 2)
:fill "#31EFB8"
:stroke "#31EFB8"
:stroke-width 1
:fill-opacity 0.2}])]
:stroke-width (if show-interactions? 1 0)
:fill-opacity (if show-interactions? 0.2 0)}])]
;; Don't wrap svg elements inside a <g> otherwise some can break
[:& component {:shape shape

View file

@ -24,8 +24,7 @@
[app.common.math :as mth]
[app.util.router :as rt]
[app.main.data.viewer :as vd])
(:import goog.events.EventType
goog.events.KeyCodes))
(:import goog.events.EventType))
(mf/defc thumbnails-content
[{:keys [children expanded? total] :as props}]

View file

@ -164,8 +164,12 @@
on-select-library-color
(fn [color]
(reset! state (data->state color))
(on-change color))
(let [editing-stop (:editing-stop @state)
is-gradient? (some? (:gradient color))]
(if (and (some? editing-stop) (not is-gradient?))
(handle-change-color (color->components (:color color) (:opacity color)))
(do (reset! state (data->state color))
(on-change color)))))
on-add-library-color
(fn [color]

View file

@ -10,6 +10,7 @@
[app.main.data.modal :as modal]
[app.main.data.workspace :as dw]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.undo :as dwu]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.shortcuts :as sc]
[app.main.refs :as refs]
@ -83,10 +84,10 @@
;; We defer the execution so the mouse event won't close the editor
(timers/schedule #(st/emit! (dw/start-editing-selected))))
do-update-component (st/emitf
(dwc/start-undo-transaction)
(dwu/start-undo-transaction)
(dwl/update-component id)
(dwl/sync-file current-file-id (:component-file shape))
(dwc/commit-undo-transaction))
(dwu/commit-undo-transaction))
confirm-update-remote-component (st/emitf
(dwl/update-component id)
(dwl/sync-file current-file-id

View file

@ -12,7 +12,8 @@
(defn generic-wrapper-factory
[component]
(mf/fnc generic-wrapper
{::mf/wrap-props false}
{::mf/wrap [#(mf/memo' % (mf/check-props ["shape"]))]
::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")]
[:> shape-container {:shape shape}

View file

@ -28,6 +28,28 @@
(contains? (:selected local) id)))]
(l/derived check-moving refs/workspace-local)))
(defn check-props
([props] (check-props props =))
([props eqfn?]
(fn [np op]
(every? #(eqfn? (unchecked-get np %)
(unchecked-get op %))
props))))
(defn check-frame-props
"Checks for changes in the props of a frame"
[new-props old-props]
(let [new-shape (unchecked-get new-props "shape")
old-shape (unchecked-get old-props "shape")
new-objects (unchecked-get new-props "objects")
old-objects (unchecked-get old-props "objects")
new-children (->> new-shape :shapes (mapv #(get new-objects %)))
old-children (->> old-shape :shapes (mapv #(get old-objects %)))]
(and (= new-shape old-shape)
(= new-children old-children))))
;; This custom deffered don't deffer rendering when ghost rendering is
;; used.
(defn custom-deferred
@ -48,7 +70,7 @@
[shape-wrapper]
(let [frame-shape (frame/frame-shape shape-wrapper)]
(mf/fnc frame-wrapper
{::mf/wrap [#(mf/memo' % (mf/check-props ["shape" "objects"])) custom-deferred]
{::mf/wrap [#(mf/memo' % check-frame-props) custom-deferred]
::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")

View file

@ -30,7 +30,7 @@
[shape-wrapper]
(let [group-shape (group/group-shape shape-wrapper)]
(mf/fnc group-wrapper
{::mf/wrap [#(mf/memo' % (mf/check-props ["shape" "frame"]))]
{::mf/wrap [#(mf/memo' % (mf/check-props ["shape"]))]
::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")

View file

@ -18,6 +18,7 @@
[app.main.ui.cursors :as cur]
[app.main.ui.shapes.text.styles :as sts]
[app.util.dom :as dom]
[app.util.keyboard :as kbd]
[app.util.object :as obj]
[app.util.text-editor :as ted]
[cuerdas.core :as str]
@ -25,8 +26,7 @@
[okulary.core :as l]
[rumext.alpha :as mf])
(:import
goog.events.EventType
goog.events.KeyCodes))
goog.events.EventType))
;; --- Text Editor Rendering
@ -84,7 +84,7 @@
on-key-up
(fn [event]
(dom/stop-propagation event)
(when (= (.-keyCode event) 27) ; ESC
(when (kbd/esc? event)
(do
(st/emit! :interrupt)
(st/emit! dw/clear-edition-mode))))
@ -163,10 +163,18 @@
::mf/wrap-props false
::mf/forward-ref true}
[props ref]
(let [{:keys [id x y width height grow-type] :as shape} (obj/get props "shape")]
[:foreignObject {:transform (gsh/transform-matrix shape)
:x x :y y
:width (if (#{:auto-width} grow-type) 100000 width)
:height (if (#{:auto-height :auto-width} grow-type) 100000 height)}
(let [{:keys [id x y width height grow-type] :as shape} (obj/get props "shape")
clip-id (str "clip-" id)]
[:g.text-editor {:clip-path (str "url(#" clip-id ")")}
[:defs
;; This clippath will cut the huge foreign object we use to calculate the automatic resize
[:clipPath {:id clip-id}
[:rect {:x x :y y
:width (+ width 8) :height (+ height 8)
:transform (gsh/transform-matrix shape)}]]]
[:foreignObject {:transform (gsh/transform-matrix shape)
:x x :y y
:width (if (#{:auto-width} grow-type) 100000 width)
:height (if (#{:auto-height :auto-width} grow-type) 100000 height)}
[:& text-shape-edit-html {:shape shape :key (str id)}]]))
[:& text-shape-edit-html {:shape shape :key (str id)}]]]))

View file

@ -19,6 +19,7 @@
[app.main.data.modal :as modal]
[app.main.data.workspace :as dw]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.undo :as dwu]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.texts :as dwt]
[app.main.exports :as exports]
@ -45,38 +46,6 @@
[okulary.core :as l]
[rumext.alpha :as mf]))
;; ---- Assets selection management
(def empty-selection #{})
(defn toggle-select
[selected asset-id]
(if (contains? selected asset-id)
(disj selected asset-id)
(conj selected asset-id)))
(defn replace-select
[selected asset-id]
#{asset-id})
(defn extend-select
[selected asset-id groups]
(let [assets (->> groups vals flatten)
clicked-idx (d/index-of-pred assets #(= (:id %) asset-id))
selected-idx (->> selected
(map (fn [id] (d/index-of-pred assets
#(= (:id %) id)))))
min-idx (apply min (conj selected-idx clicked-idx))
max-idx (apply max (conj selected-idx clicked-idx))]
(->> assets
d/enumerate
(filter #(<= min-idx (first %) max-idx))
(map #(-> % second :id))
set)))
;; ---- Group assets management ----
(s/def ::asset-name ::us/not-empty-string)
@ -149,40 +118,42 @@
;; ---- Components box ----
(mf/defc components-box
[{:keys [file-id local? components listing-thumbs? open? change-selected] :as props}]
[{:keys [file-id local? components listing-thumbs? open? selected-assets
on-asset-click on-assets-delete on-clear-selection] :as props}]
(let [state (mf/use-state {:menu-open false
:renaming nil
:top nil
:left nil
:component-id nil
:selected empty-selection
:folded-groups empty-folded-groups})
selected-components (:components selected-assets)
multi-components? (> (count selected-components) 1)
multi-assets? (or (not (empty? (:graphics selected-assets)))
(not (empty? (:colors selected-assets)))
(not (empty? (:typographies selected-assets))))
groups (group-assets components)
selected (:selected @state)
folded-groups (:folded-groups @state)
on-duplicate
(mf/use-callback
(mf/deps @state)
(fn []
(if (empty? selected)
(if (empty? selected-components)
(st/emit! (dwl/duplicate-component {:id (:component-id @state)}))
(do
(st/emit! (dwc/start-undo-transaction))
(apply st/emit! (map #(dwl/duplicate-component {:id %}) selected))
(st/emit! (dwc/commit-undo-transaction))))))
(st/emit! (dwu/start-undo-transaction))
(apply st/emit! (map #(dwl/duplicate-component {:id %}) selected-components))
(st/emit! (dwu/commit-undo-transaction))))))
on-delete
(mf/use-callback
(mf/deps @state)
(mf/deps @state file-id multi-components? multi-assets?)
(fn []
(if (empty? selected)
(st/emit! (dwl/delete-component {:id (:component-id @state)}))
(do
(st/emit! (dwc/start-undo-transaction))
(apply st/emit! (map #(dwl/delete-component {:id %}) selected))
(st/emit! (dwc/commit-undo-transaction))))
(if (or multi-components? multi-assets?)
(on-assets-delete)
(st/emit! (dwl/delete-component {:id (:component-id @state)})))
(st/emit! (dwl/sync-file file-id file-id))))
on-rename
@ -205,51 +176,35 @@
on-context-menu
(mf/use-callback
(fn [component-id]
(fn [event]
(when local?
(let [pos (dom/get-client-position event)
top (:y pos)
left (- (:x pos) 20)]
(dom/prevent-default event)
(swap! state assoc :menu-open true
:top top
:left left
:component-id component-id))))))
unselect-all
(mf/use-callback
(fn [event]
(swap! state assoc :selected empty-selection)))
on-select
(mf/use-callback
(mf/deps selected-components on-clear-selection)
(fn [component-id]
(fn [event]
(dom/stop-propagation event)
(swap! state update :selected
(fn [selected]
(cond
(kbd/ctrl? event)
(toggle-select selected component-id)
(kbd/shift? event)
(extend-select selected component-id groups)))))))
(when local?
(let [pos (dom/get-client-position event)
top (:y pos)
left (- (:x pos) 20)]
(dom/prevent-default event)
(when-not (contains? selected-components component-id)
(on-clear-selection))
(swap! state assoc :menu-open true
:top top
:left left
:component-id component-id))))))
create-group
(mf/use-callback
(mf/deps components selected)
(mf/deps components selected-components on-clear-selection)
(fn [name]
(swap! state assoc :selected empty-selection)
(st/emit! (dwc/start-undo-transaction))
(on-clear-selection)
(st/emit! (dwu/start-undo-transaction))
(apply st/emit!
(->> components
(filter #(contains? selected (:id %)))
(filter #(contains? selected-components (:id %)))
(map #(dwl/rename-component
(:id %)
(str name " / "
(cp/merge-path-item (:path %) (:name %)))))))
(st/emit! (dwc/commit-undo-transaction))))
(st/emit! (dwu/commit-undo-transaction))))
on-fold-group
(mf/use-callback
@ -262,7 +217,7 @@
on-group
(mf/use-callback
(mf/deps components selected)
(mf/deps components selected-components)
(fn [event]
(dom/stop-propagation event)
(modal/show! :create-group-dialog {:create create-group})))
@ -274,11 +229,7 @@
:component component})
(dnd/set-allowed-effect! event "move")))]
(mf/use-effect
(mf/deps [change-selected selected])
#(change-selected (count selected)))
[:div.asset-section {:on-click unselect-all}
[:div.asset-section
[:div.asset-title {:class (when (not open?) "closed")}
[:span {:on-click (st/emitf (dwl/set-assets-box-open file-id :components (not open?)))}
i/arrow-slide (tr "workspace.assets.components")]
@ -308,11 +259,11 @@
(let [renaming? (= (:renaming @state)(:id component))]
[:div {:key (:id component)
:class-name (dom/classnames
:selected (contains? selected (:id component))
:selected (contains? selected-components (:id component))
:grid-cell @listing-thumbs?
:enum-item (not @listing-thumbs?))
:draggable true
:on-click (on-select (:id component))
:on-click #(on-asset-click % (:id component) groups nil)
:on-context-menu (on-context-menu (:id component))
:on-drag-start (partial on-drag-start component)}
[:& exports/component-svg {:group (get-in component [:objects (:id component)])
@ -340,28 +291,35 @@
:on-close #(swap! state assoc :menu-open false)
:top (:top @state)
:left (:left @state)
:options [(when (<= (count selected) 1)
:options [(when-not (or multi-components? multi-assets?)
[(tr "workspace.assets.rename") on-rename])
[(tr "workspace.assets.duplicate") on-duplicate]
(when-not multi-assets?
[(tr "workspace.assets.duplicate") on-duplicate])
[(tr "workspace.assets.delete") on-delete]
[(tr "workspace.assets.group") on-group]]}])]))
(when-not multi-assets?
[(tr "workspace.assets.group") on-group])]}])]))
;; ---- Graphics box ----
(mf/defc graphics-box
[{:keys [file-id local? objects listing-thumbs? open? change-selected] :as props}]
[{:keys [file-id local? objects listing-thumbs? open? selected-assets
on-asset-click on-assets-delete on-clear-selection] :as props}]
(let [input-ref (mf/use-ref nil)
state (mf/use-state {:menu-open false
:renaming nil
:top nil
:left nil
:object-id nil
:selected empty-selection
:folded-groups empty-folded-groups})
selected-objects (:graphics selected-assets)
multi-objects? (> (count selected-objects) 1)
multi-assets? (or (not (empty? (:components selected-assets)))
(not (empty? (:colors selected-assets)))
(not (empty? (:typographies selected-assets))))
groups (group-assets objects)
selected (:selected @state)
folded-groups (:folded-groups @state)
add-graphic
@ -370,7 +328,7 @@
(st/emitf (dwl/set-assets-box-open file-id :graphics true))
(dom/click (mf/ref-val input-ref))))
on-selected
on-file-selected
(mf/use-callback
(mf/deps file-id)
(fn [blobs]
@ -380,14 +338,11 @@
on-delete
(mf/use-callback
(mf/deps @state)
(mf/deps @state multi-objects? multi-assets?)
(fn []
(if (empty? selected)
(st/emit! (dwl/delete-media {:id (:object-id @state)}))
(do
(st/emit! (dwc/start-undo-transaction))
(apply st/emit! (map #(dwl/delete-media {:id %}) selected))
(st/emit! (dwc/commit-undo-transaction))))))
(if (or multi-objects? multi-assets?)
(on-assets-delete)
(st/emit! (dwl/delete-media {:id (:object-id @state)})))))
on-rename
(mf/use-callback
@ -409,51 +364,35 @@
on-context-menu
(mf/use-callback
(fn [object-id]
(fn [event]
(when local?
(let [pos (dom/get-client-position event)
top (:y pos)
left (- (:x pos) 20)]
(dom/prevent-default event)
(swap! state assoc :menu-open true
:top top
:left left
:object-id object-id))))))
unselect-all
(mf/use-callback
(fn [event]
(swap! state assoc :selected empty-selection)))
on-select
(mf/use-callback
(mf/deps selected-objects on-clear-selection)
(fn [object-id]
(fn [event]
(dom/stop-propagation event)
(swap! state update :selected
(fn [selected]
(cond
(kbd/ctrl? event)
(toggle-select selected object-id)
(kbd/shift? event)
(extend-select selected object-id groups)))))))
(when local?
(let [pos (dom/get-client-position event)
top (:y pos)
left (- (:x pos) 20)]
(dom/prevent-default event)
(when-not (contains? selected-objects object-id)
(on-clear-selection))
(swap! state assoc :menu-open true
:top top
:left left
:object-id object-id))))))
create-group
(mf/use-callback
(mf/deps objects selected)
(mf/deps objects selected-objects on-clear-selection)
(fn [name]
(swap! state assoc :selected empty-selection)
(st/emit! (dwc/start-undo-transaction))
(on-clear-selection)
(st/emit! (dwu/start-undo-transaction))
(apply st/emit!
(->> objects
(filter #(contains? selected (:id %)))
(filter #(contains? selected-objects (:id %)))
(map #(dwl/rename-media
(:id %)
(str name " / "
(cp/merge-path-item (:path %) (:name %)))))))
(st/emit! (dwc/commit-undo-transaction))))
(st/emit! (dwu/commit-undo-transaction))))
on-fold-group
(mf/use-callback
@ -466,7 +405,7 @@
on-group
(mf/use-callback
(mf/deps objects selected)
(mf/deps objects selected-objects)
(fn [event]
(dom/stop-propagation event)
(modal/show! :create-group-dialog {:create create-group})))
@ -479,11 +418,7 @@
(dnd/set-data! event "text/asset-type" mtype)
(dnd/set-allowed-effect! event "move")))]
(mf/use-effect
(mf/deps [change-selected selected])
#(change-selected (count selected)))
[:div.asset-section {:on-click unselect-all}
[:div.asset-section
[:div.asset-title {:class (when (not open?) "closed")}
[:span {:on-click (st/emitf (dwl/set-assets-box-open file-id :graphics (not open?)))}
i/arrow-slide (tr "workspace.assets.graphics")]
@ -494,7 +429,7 @@
[:& file-uploader {:accept cm/str-media-types
:multi true
:input-ref input-ref
:on-selected on-selected}]])]
:on-selected on-file-selected}]])]
(when open?
(for [group groups]
(let [path (first group)
@ -518,11 +453,11 @@
(for [object objects]
[:div {:key (:id object)
:class-name (dom/classnames
:selected (contains? selected (:id object))
:selected (contains? selected-objects (:id object))
:grid-cell @listing-thumbs?
:enum-item (not @listing-thumbs?))
:draggable true
:on-click (on-select (:id object))
:on-click #(on-asset-click % (:id object) groups nil)
:on-context-menu (on-context-menu (:id object))
:on-drag-start (partial on-drag-start object)}
[:img {:src (cfg/resolve-file-media object true)
@ -552,16 +487,18 @@
:on-close #(swap! state assoc :menu-open false)
:top (:top @state)
:left (:left @state)
:options [(when (<= (count selected) 1)
:options [(when-not (or multi-objects? multi-assets?)
[(tr "workspace.assets.rename") on-rename])
[(tr "workspace.assets.delete") on-delete]
[(tr "workspace.assets.group") on-group]]}])]))
(when-not multi-assets?
[(tr "workspace.assets.group") on-group])]}])]))
;; ---- Colors box ----
(mf/defc color-item
[{:keys [color local? file-id selected on-select locale] :as props}]
[{:keys [color local? file-id selected-colors multi-colors? multi-assets?
on-asset-click on-assets-delete on-clear-selection colors locale] :as props}]
(let [rename? (= (:color-for-rename @refs/workspace-local) (:id color))
id (:id color)
input-ref (mf/use-ref)
@ -575,10 +512,8 @@
(:color color) (:color color)
:else (:value color))
click-color
(fn [event]
(when on-select
((on-select (:id color)) event))
apply-color
(fn [color-id event]
(let [ids (get-in @st/state [:workspace-local :selected])]
(if (kbd/shift? event)
(st/emit! (dc/change-stroke ids color))
@ -594,13 +529,12 @@
(st/emit! (dwl/update-color updated-color file-id))))
delete-color
(fn []
(if (empty? selected)
(st/emit! (dwl/delete-color color))
(do
(st/emit! (dwc/start-undo-transaction))
(apply st/emit! (map #(dwl/delete-color {:id %}) selected))
(st/emit! (dwc/commit-undo-transaction)))))
(mf/use-callback
(mf/deps @state multi-colors? multi-assets?)
(fn []
(if (or multi-colors? multi-assets?)
(on-assets-delete)
(st/emit! (dwl/delete-color color)))))
rename-color-clicked
(fn [event]
@ -634,16 +568,20 @@
:position :right}))
on-context-menu
(fn [event]
(when local?
(let [pos (dom/get-client-position event)
top (:y pos)
left (+ 10 (:x pos))]
(dom/prevent-default event)
(swap! state assoc
:menu-open true
:top top
:left left))))]
(mf/use-callback
(mf/deps color selected-colors on-clear-selection)
(fn [event]
(when local?
(let [pos (dom/get-client-position event)
top (:y pos)
left (+ 10 (:x pos))]
(dom/prevent-default event)
(when-not (contains? selected-colors (:id color))
(on-clear-selection))
(swap! state assoc
:menu-open true
:top top
:left left)))))]
(mf/use-effect
(mf/deps (:editing @state))
@ -653,10 +591,11 @@
nil))
[:div.asset-list-item {:class-name (dom/classnames
:selected (contains? selected (:id color)))
:selected (contains? selected-colors (:id color)))
:on-context-menu on-context-menu
:on-click (when-not (:editing @state)
click-color)}
#(on-asset-click % (:id color) {"" colors}
(partial apply-color (:id color))))}
[:& bc/color-bullet {:color color}]
(if (:editing @state)
@ -679,17 +618,20 @@
:on-close #(swap! state assoc :menu-open false)
:top (:top @state)
:left (:left @state)
:options [(when (<= (count selected) 1)
:options [(when-not (or multi-colors? multi-assets?)
[(t locale "workspace.assets.rename") rename-color-clicked])
(when (<= (count selected) 1)
(when-not (or multi-colors? multi-assets?)
[(t locale "workspace.assets.edit") edit-color-clicked])
[(t locale "workspace.assets.delete") delete-color]]}])]))
(mf/defc colors-box
[{:keys [file-id local? colors locale open? change-selected] :as props}]
(let [state (mf/use-state {:selected empty-selection})
selected (:selected @state)
[{:keys [file-id local? colors locale open? selected-assets
on-asset-click on-assets-delete on-clear-selection] :as props}]
(let [selected-colors (:colors selected-assets)
multi-colors? (> (count selected-colors) 1)
multi-assets? (or (not (empty? (:components selected-assets)))
(not (empty? (:graphics selected-assets)))
(not (empty? (:typographies selected-assets))))
add-color
(mf/use-callback
@ -708,32 +650,9 @@
:on-accept add-color
:data {:color "#406280"
:opacity 1}
:position :right})))
:position :right})))]
unselect-all
(mf/use-callback
(fn [event]
(swap! state assoc :selected empty-selection)))
on-select
(mf/use-callback
(fn [color-id]
(fn [event]
(dom/stop-propagation event)
(swap! state update :selected
(fn [selected]
(cond
(kbd/ctrl? event)
(toggle-select selected color-id)
(kbd/shift? event)
(extend-select selected color-id {"" colors})))))))]
(mf/use-effect
(mf/deps [change-selected selected])
#(change-selected (count selected)))
[:div.asset-section {:on-click unselect-all}
[:div.asset-section
[:div.asset-title {:class (when (not open?) "closed")}
[:span {:on-click (st/emitf (dwl/set-assets-box-open file-id :colors (not open?)))}
i/arrow-slide (t locale "workspace.assets.colors")]
@ -751,24 +670,34 @@
:color color
:file-id file-id
:local? local?
:selected selected
:on-select on-select
:selected-colors selected-colors
:multi-colors? multi-colors?
:multi-assets? multi-assets?
:on-asset-click on-asset-click
:on-assets-delete on-assets-delete
:on-clear-selection on-clear-selection
:colors colors
:locale locale}]))])]))
;; ---- Typography box ----
(mf/defc typography-box
[{:keys [file file-id local? typographies locale open? change-selected] :as props}]
[{:keys [file file-id local? typographies locale open? selected-assets
on-asset-click on-assets-delete on-clear-selection] :as props}]
(let [state (mf/use-state {:detail-open? false
:menu-open? false
:top nil
:left nil
:selected empty-selection})
:id nil})
local (deref refs/workspace-local)
selected (:selected @state)
selected-typographies (:typographies selected-assets)
multi-typographies? (> (count selected-typographies) 1)
multi-assets? (or (not (empty? (:graphics selected-assets)))
(not (empty? (:colors selected-assets)))
(not (empty? (:typographies selected-assets))))
add-typography
(mf/use-callback
@ -782,19 +711,8 @@
(fn [typography changes]
(st/emit! (dwl/update-typography (merge typography changes) file-id))))
handle-typography-selection
apply-typography
(fn [typography event]
(dom/stop-propagation event)
(swap! state update :selected
(fn [selected]
(cond
(kbd/ctrl? event)
(toggle-select selected (:id typography))
(kbd/shift? event)
(extend-select selected (:id typography) {"" typographies}))))
(let [ids (get-in @st/state [:workspace-local :selected])
attrs (merge
{:typography-ref-file file-id
@ -804,23 +722,21 @@
ids)))
on-context-menu
(fn [id event]
(when local?
(let [pos (dom/get-client-position event)
top (:y pos)
left (- (:x pos) 20)]
(dom/prevent-default event)
(swap! state assoc
:menu-open? true
:top top
:left left
:id id))))
unselect-all
(mf/use-callback
(fn [event]
(swap! state assoc :selected empty-selection)))
(mf/deps selected-typographies on-clear-selection)
(fn [id event]
(when local?
(let [pos (dom/get-client-position event)
top (:y pos)
left (- (:x pos) 20)]
(dom/prevent-default event)
(when-not (contains? selected-typographies id)
(on-clear-selection))
(swap! state assoc
:menu-open? true
:top top
:left left
:id id)))))
closed-typography-edit
(mf/use-callback
@ -836,13 +752,12 @@
(st/emit! #(assoc-in % [:workspace-local :edit-typography] (:id @state))))
handle-delete-typography
(fn []
(if (empty? selected)
(st/emit! (dwl/delete-typography (:id @state)))
(do
(st/emit! (dwc/start-undo-transaction))
(apply st/emit! (map #(dwl/delete-typography %) selected))
(st/emit! (dwc/commit-undo-transaction)))))
(mf/use-callback
(mf/deps @state multi-typographies? multi-assets?)
(fn []
(if (or multi-typographies? multi-assets?)
(on-assets-delete)
(st/emit! (dwl/delete-typography (:id @state))))))
editting-id (or (:rename-typography local) (:edit-typography local))]
@ -854,11 +769,7 @@
(when (:edit-typography local)
(st/emit! #(update % :workspace-local dissoc :edit-typography)))))
(mf/use-effect
(mf/deps [change-selected selected])
#(change-selected (count selected)))
[:div.asset-section {:on-click unselect-all}
[:div.asset-section
[:div.asset-title {:class (when (not open?) "closed")}
[:span {:on-click (st/emitf (dwl/set-assets-box-open file-id :typographies (not open?)))}
i/arrow-slide (t locale "workspace.assets.typography")]
@ -872,9 +783,9 @@
:on-close #(swap! state assoc :menu-open? false)
:top (:top @state)
:left (:left @state)
:options [(when (<= (count selected) 1)
:options [(when-not (or multi-typographies? multi-assets?)
[(t locale "workspace.assets.rename") handle-rename-typography-clicked])
(when (<= (count selected) 1)
(when-not (or multi-typographies? multi-assets?)
[(t locale "workspace.assets.edit") handle-edit-typography-clicked])
[(t locale "workspace.assets.delete") handle-delete-typography]]}]
(when open?
@ -887,8 +798,9 @@
:read-only? (not local?)
:on-context-menu #(on-context-menu (:id typography) %)
:on-change #(handle-change typography %)
:selected? (contains? selected (:id typography))
:on-select #(handle-typography-selection typography %)
:selected? (contains? selected-typographies (:id typography))
:on-click #(on-asset-click % (:id typography) {"" typographies}
(partial apply-typography typography))
:editting? (= editting-id (:id typography))
:focus-name? (= (:rename-typography local) (:id typography))}])])]))
@ -961,16 +873,19 @@
reverse-sort? (mf/use-state false)
listing-thumbs? (mf/use-state true)
selected-count (mf/use-state {:components 0
:graphics 0
:colors 0
:typographies 0})
selected-assets (mf/use-state {:components #{}
:graphics #{}
:colors #{}
:typographies #{}})
selected-count (+ (count (:components @selected-assets))
(count (:graphics @selected-assets))
(count (:colors @selected-assets))
(count (:typographies @selected-assets)))
toggle-open (st/emitf (dwl/set-assets-box-open (:id file) :library (not open?)))
change-selected-count (fn [asset-type cnt]
(swap! selected-count assoc asset-type cnt))
url (rt/resolve router :workspace
{:project-id (:project-id file)
:file-id (:id file)}
@ -996,9 +911,84 @@
toggle-listing
(mf/use-callback
(fn [event]
(swap! listing-thumbs? not)))]
(swap! listing-thumbs? not)))
[:div.tool-window {:on-context-menu #(dom/prevent-default %)}
toggle-selected-asset
(mf/use-callback
(mf/deps @selected-assets)
(fn [asset-type asset-id]
(swap! selected-assets update asset-type
(fn [selected]
(if (contains? selected asset-id)
(disj selected asset-id)
(conj selected asset-id))))))
extend-selected-assets
(mf/use-callback
(mf/deps @selected-assets)
(fn [asset-type asset-id asset-groups]
(swap! selected-assets update asset-type
(fn [selected]
(let [all-assets (-> asset-groups vals flatten)
clicked-idx (d/index-of-pred all-assets #(= (:id %) asset-id))
selected-idx (->> selected
(map (fn [id]
(d/index-of-pred all-assets
#(= (:id %) id)))))
min-idx (apply min (conj selected-idx clicked-idx))
max-idx (apply max (conj selected-idx clicked-idx))]
(->> all-assets
d/enumerate
(filter #(<= min-idx (first %) max-idx))
(map #(-> % second :id))
set))))))
unselect-all
(mf/use-callback
(fn []
(swap! selected-assets {:components #{}
:graphics #{}
:colors #{}
:typographies #{}})))
on-asset-click
(mf/use-callback
(mf/deps toggle-selected-asset extend-selected-assets)
(fn [asset-type event asset-id all-assets default-click]
(cond
(kbd/ctrl? event)
(do
(dom/stop-propagation event)
(toggle-selected-asset asset-type asset-id))
(kbd/shift? event)
(do
(dom/stop-propagation event)
(extend-selected-assets asset-type asset-id all-assets))
:else
(when default-click
(default-click event)))))
on-assets-delete
(mf/use-callback
(mf/deps @selected-assets)
(fn []
(do
(st/emit! (dwu/start-undo-transaction))
(apply st/emit! (map #(dwl/delete-component {:id %})
(:components @selected-assets)))
(apply st/emit! (map #(dwl/delete-media {:id %})
(:graphics @selected-assets)))
(apply st/emit! (map #(dwl/delete-color {:id %})
(:colors @selected-assets)))
(apply st/emit! (map #(dwl/delete-typography %)
(:typographies @selected-assets)))
(st/emit! (dwu/commit-undo-transaction)))))]
[:div.tool-window {:on-context-menu #(dom/prevent-default %)
:on-click unselect-all}
[:div.tool-window-bar.library-bar
{:on-click toggle-open}
[:div.collapse-library
@ -1037,14 +1027,9 @@
(str/empty? (:term filters))))]
[:div.tool-window-content
[:div.listing-options
[:span.selected-count
(let [selected-count @selected-count
total (+ (:components selected-count)
(:graphics selected-count)
(:colors selected-count)
(:typographies selected-count))]
(when (> total 0)
(tr "workspace.assets.selected-count" (i18n/c total))))]
(when (> selected-count 0)
[:span.selected-count
(tr "workspace.assets.selected-count" (i18n/c selected-count))])
[:div.listing-option-btn.first {:on-click toggle-sort}
(if @reverse-sort?
i/sort-descending
@ -1053,30 +1038,38 @@
(if @listing-thumbs?
i/listing-enum
i/listing-thumbs)]]
(when show-components?
[:& components-box {:file-id (:id file)
:local? local?
:components components
:listing-thumbs? listing-thumbs?
:change-selected (partial change-selected-count
:components)
:open? (open-box? :components)}])
:open? (open-box? :components)
:selected-assets @selected-assets
:on-asset-click (partial on-asset-click :components)
:on-assets-delete on-assets-delete
:on-clear-selection unselect-all}])
(when show-graphics?
[:& graphics-box {:file-id (:id file)
:local? local?
:objects media
:listing-thumbs? listing-thumbs?
:change-selected (partial change-selected-count
:graphics)
:open? (open-box? :graphics)}])
:open? (open-box? :graphics)
:selected-assets @selected-assets
:on-asset-click (partial on-asset-click :graphics)
:on-assets-delete on-assets-delete
:on-clear-selection unselect-all}])
(when show-colors?
[:& colors-box {:file-id (:id file)
:local? local?
:locale locale
:colors colors
:open? (open-box? :colors)
:change-selected (partial change-selected-count
:colors)}])
:selected-assets @selected-assets
:on-asset-click (partial on-asset-click :colors)
:on-assets-delete on-assets-delete
:on-clear-selection unselect-all}])
(when show-typography?
[:& typography-box {:file file
@ -1085,8 +1078,10 @@
:locale locale
:typographies typographies
:open? (open-box? :typographies)
:change-selected (partial change-selected-count
:typographies)}])
:selected-assets @selected-assets
:on-asset-click (partial on-asset-click :typographies)
:on-assets-delete on-assets-delete
:on-clear-selection unselect-all}])
(when (and (not show-components?) (not show-graphics?) (not show-colors?))
[:div.asset-section

View file

@ -285,27 +285,30 @@
:objects objects
:key id}])))]]))
(defn- strip-obj-data [obj]
(select-keys obj [:id
:name
:blocked
:hidden
:shapes
:type
:content
:parent-id
:component-id
:component-file
:shape-ref
:touched
:metadata
:masked-group?]))
(defn- strip-objects
[objects]
(let [strip-data #(select-keys % [:id
:name
:blocked
:hidden
:shapes
:type
:content
:parent-id
:component-id
:component-file
:shape-ref
:touched
:metadata
:masked-group?])]
(persistent!
(reduce-kv (fn [res id obj]
(assoc! res id (strip-data obj)))
(transient {})
objects))))
(persistent!
(->> objects
(reduce-kv
(fn [res id obj]
(assoc! res id (strip-obj-data obj)))
(transient {})))))
(mf/defc layers-tree-wrapper
{::mf/wrap-props false

View file

@ -10,6 +10,7 @@
[app.common.data :as d]
[app.common.uuid :as uuid]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.changes :as dch]
[app.main.store :as st]
[app.main.ui.icons :as i]
[app.main.ui.workspace.sidebar.options.common :refer [advanced-options]]
@ -32,7 +33,7 @@
has-value? (not (nil? blur))
multiple? (= blur :multiple)
change! (fn [update-fn] (st/emit! (dwc/update-shapes ids update-fn)))
change! (fn [update-fn] (st/emit! (dch/update-shapes ids update-fn)))
handle-add
(fn []

View file

@ -16,6 +16,7 @@
[app.main.ui.components.context-menu :refer [context-menu]]
[app.main.data.workspace :as dw]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.undo :as dwu]
[app.main.data.workspace.libraries :as dwl]
[app.util.i18n :as i18n :refer [t]]
[app.util.dom :as dom]))
@ -50,10 +51,10 @@
do-detach-component (st/emitf (dwl/detach-component id))
do-reset-component (st/emitf (dwl/reset-component id))
do-update-component (st/emitf
(dwc/start-undo-transaction)
(dwu/start-undo-transaction)
(dwl/update-component id)
(dwl/sync-file current-file-id current-file-id)
(dwc/commit-undo-transaction))
(dwu/commit-undo-transaction))
confirm-update-remote-component (st/emitf
(dwl/update-component id)
(dwl/sync-file current-file-id

View file

@ -9,6 +9,7 @@
[app.common.pages :as cp]
[app.main.data.workspace.colors :as dc]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.undo :as dwu]
[app.main.data.workspace.texts :as dwt]
[app.main.refs :as refs]
[app.main.store :as st]
@ -78,13 +79,13 @@
(mf/use-callback
(mf/deps ids)
(fn [value opacity id file-id]
(st/emit! (dwc/start-undo-transaction))))
(st/emit! (dwu/start-undo-transaction))))
on-close-picker
(mf/use-callback
(mf/deps ids)
(fn [value opacity id file-id]
(st/emit! (dwc/commit-undo-transaction))))]
(st/emit! (dwu/commit-undo-transaction))))]
(if show?
[:div.element-set

View file

@ -9,6 +9,7 @@
[app.common.data :as d]
[app.common.math :as mth]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.changes :as dch]
[app.main.store :as st]
[app.main.ui.components.numeric-input :refer [numeric-input]]
[app.main.ui.icons :as i]
@ -34,7 +35,7 @@
(mf/use-callback
(mf/deps ids)
(fn [prop value]
(st/emit! (dwc/update-shapes ids #(assoc % prop value)))))
(st/emit! (dch/update-shapes ids #(assoc % prop value)))))
handle-change-blend-mode
(mf/use-callback

View file

@ -17,6 +17,7 @@
[app.common.geom.point :as gpt]
[app.main.data.workspace :as udw]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.changes :as dch]
[app.main.ui.components.numeric-input :refer [numeric-input]]
[app.common.math :as math]
[app.util.i18n :refer [t] :as i18n]))
@ -106,7 +107,7 @@
(:r1 shape)
(-> (assoc :rx 0 :ry 0)
(dissoc :r1 :r2 :r3 :r4))))]
(st/emit! (dwc/update-shapes ids-with-children radius-update)))))
(st/emit! (dch/update-shapes ids-with-children radius-update)))))
on-switch-to-radius-4
(mf/use-callback
@ -118,7 +119,7 @@
(:rx shape)
(-> (assoc :r1 0 :r2 0 :r3 0 :r4 0)
(dissoc :rx :ry))))]
(st/emit! (dwc/update-shapes ids-with-children radius-update)))))
(st/emit! (dch/update-shapes ids-with-children radius-update)))))
on-radius-1-change
(mf/use-callback
@ -134,7 +135,7 @@
(or (:rx shape) (:r1 shape))
(assoc :rx value :ry value)))]
(st/emit! (dwc/update-shapes ids-with-children radius-update)))))
(st/emit! (dch/update-shapes ids-with-children radius-update)))))
on-radius-4-change
(mf/use-callback
@ -150,7 +151,7 @@
(attr shape)
(assoc attr value)))]
(st/emit! (dwc/update-shapes ids-with-children radius-update)))))
(st/emit! (dch/update-shapes ids-with-children radius-update)))))
on-width-change #(on-size-change % :width)
on-height-change #(on-size-change % :height)

View file

@ -10,6 +10,8 @@
[app.common.data :as d]
[app.common.uuid :as uuid]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.undo :as dwu]
[app.main.store :as st]
[app.main.ui.icons :as i]
[app.main.ui.components.numeric-input :refer [numeric-input]]
@ -56,7 +58,7 @@
on-remove-shadow
(fn [index]
(fn []
(st/emit! (dwc/update-shapes ids #(update % :shadow remove-shadow-by-index index) ))))
(st/emit! (dch/update-shapes ids #(update % :shadow remove-shadow-by-index index) ))))
select-text
(fn [ref] (fn [event] (dom/select-text! (mf/ref-val ref))))
@ -69,14 +71,14 @@
([index attr valid? update-ref]
(fn [value]
(when (or (not valid?) (valid? value))
(do (st/emit! (dwc/update-shapes ids #(assoc-in % [:shadow index attr] value)))
(do (st/emit! (dch/update-shapes ids #(assoc-in % [:shadow index attr] value)))
(when update-ref (dom/set-value! (mf/ref-val update-ref) value)))))))
update-color
(fn [index]
(fn [color opacity]
(let [color (d/without-keys color [:id :file-id :gradient])]
(st/emit! (dwc/update-shapes
(st/emit! (dch/update-shapes
ids
#(-> %
(assoc-in [:shadow index :color] color)
@ -86,7 +88,7 @@
(fn [index]
(fn [color opacity]
(if-not (string? (:color value))
(st/emit! (dwc/update-shapes
(st/emit! (dch/update-shapes
ids
#(assoc-in % [:shadow index :color]
(dissoc (:color value) :id :file-id)))))))
@ -94,7 +96,7 @@
toggle-visibility
(fn [index]
(fn []
(st/emit! (dwc/update-shapes ids #(update-in % [:shadow index :hidden] not)))))]
(st/emit! (dch/update-shapes ids #(update-in % [:shadow index :hidden] not)))))]
[:*
[:div.element-set-options-group
@ -129,7 +131,7 @@
{:default-value (str (:style value))
:on-change (fn [event]
(let [value (-> event dom/get-target dom/get-value d/read-string)]
(st/emit! (dwc/update-shapes ids #(assoc-in % [:shadow index :style] value)))))}
(st/emit! (dch/update-shapes ids #(assoc-in % [:shadow index :style] value)))))}
[:option {:value ":drop-shadow"} (t locale "workspace.options.shadow-options.drop-shadow")]
[:option {:value ":inner-shadow"} (t locale "workspace.options.shadow-options.inner-shadow")]]]
@ -181,18 +183,18 @@
:disable-gradient true
:on-change (update-color index)
:on-detach (detach-color index)
:on-open #(st/emit! (dwc/start-undo-transaction))
:on-close #(st/emit! (dwc/commit-undo-transaction))}]]]]))
:on-open #(st/emit! (dwu/start-undo-transaction))
:on-close #(st/emit! (dwu/commit-undo-transaction))}]]]]))
(mf/defc shadow-menu
[{:keys [ids type values] :as props}]
(let [locale (i18n/use-locale)
on-remove-all-shadows
(fn [event]
(st/emit! (dwc/update-shapes ids #(dissoc % :shadow) )))
(st/emit! (dch/update-shapes ids #(dissoc % :shadow) )))
on-add-shadow
(fn []
(st/emit! (dwc/update-shapes ids #(update % :shadow (fnil conj []) (create-shadow)) )))]
(st/emit! (dch/update-shapes ids #(update % :shadow (fnil conj []) (create-shadow)) )))]
[:div.element-set.shadow-options
[:div.element-set-title
[:span

View file

@ -11,6 +11,8 @@
[app.common.data :as d]
[app.common.math :as math]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.undo :as dwu]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.colors :as dc]
[app.main.store :as st]
[app.main.ui.icons :as i]
@ -78,7 +80,7 @@
(let [value (-> (dom/get-target event)
(dom/get-value)
(d/read-string))]
(st/emit! (dwc/update-shapes ids #(assoc % :stroke-style value)))))
(st/emit! (dch/update-shapes ids #(assoc % :stroke-style value)))))
on-stroke-alignment-change
(fn [event]
@ -86,7 +88,7 @@
(dom/get-value)
(d/read-string))]
(when-not (str/empty? value)
(st/emit! (dwc/update-shapes ids #(assoc % :stroke-alignment value))))))
(st/emit! (dch/update-shapes ids #(assoc % :stroke-alignment value))))))
on-stroke-width-change
(fn [event]
@ -94,11 +96,11 @@
(dom/get-value)
(d/parse-integer 0))]
(when-not (str/empty? value)
(st/emit! (dwc/update-shapes ids #(assoc % :stroke-width value))))))
(st/emit! (dch/update-shapes ids #(assoc % :stroke-width value))))))
on-add-stroke
(fn [event]
(st/emit! (dwc/update-shapes ids #(assoc %
(st/emit! (dch/update-shapes ids #(assoc %
:stroke-style :solid
:stroke-color "#000000"
:stroke-opacity 1
@ -106,19 +108,19 @@
on-del-stroke
(fn [event]
(st/emit! (dwc/update-shapes ids #(assoc % :stroke-style :none))))
(st/emit! (dch/update-shapes ids #(assoc % :stroke-style :none))))
on-open-picker
(mf/use-callback
(mf/deps ids)
(fn [value opacity id file-id]
(st/emit! (dwc/start-undo-transaction))))
(st/emit! (dwu/start-undo-transaction))))
on-close-picker
(mf/use-callback
(mf/deps ids)
(fn [value opacity id file-id]
(st/emit! (dwc/commit-undo-transaction))))]
(st/emit! (dwu/commit-undo-transaction))))]
(if show-options
[:div.element-set

View file

@ -9,6 +9,7 @@
[cuerdas.core :as str]
[app.common.data :as d]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.changes :as dch]
[app.main.store :as st]
[app.main.ui.workspace.sidebar.options.rows.input-row :refer [input-row]]
[app.util.dom :as dom]
@ -61,7 +62,7 @@
(fn [attr value]
(let [update-fn
(fn [shape] (assoc-in shape (concat [:svg-attrs] attr) value))]
(st/emit! (dwc/update-shapes ids update-fn)))))
(st/emit! (dch/update-shapes ids update-fn)))))
handle-delete
(mf/use-callback
@ -76,7 +77,7 @@
(empty? (get-in shape [:svg-attrs :style]))
(update :svg-attrs dissoc :style))]
shape))]
(st/emit! (dwc/update-shapes ids update-fn)))))
(st/emit! (dch/update-shapes ids update-fn)))))
]

View file

@ -10,6 +10,7 @@
[app.common.uuid :as uuid]
[app.common.text :as txt]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.texts :as dwt]
[app.main.fonts :as fonts]
@ -159,7 +160,7 @@
grow-type (->> values :grow-type)
handle-change-grow
(fn [event grow-type]
(st/emit! (dwc/update-shapes ids #(assoc % :grow-type grow-type))))]
(st/emit! (dch/update-shapes ids #(assoc % :grow-type grow-type))))]
[:div.align-icons
[:span.tooltip.tooltip-bottom

View file

@ -207,7 +207,7 @@
(mf/defc typography-entry
[{:keys [typography read-only? selected? on-select on-change on-detach on-context-menu editting? focus-name? file]}]
[{:keys [typography read-only? selected? on-click on-change on-detach on-context-menu editting? focus-name? file]}]
(let [locale (mf/deref i18n/locale)
open? (mf/use-state editting?)
hover-detach (mf/use-state false)
@ -241,8 +241,8 @@
[:div.element-set-options-group.typography-entry
{:class (when selected? "selected")}
[:div.typography-selection-wrapper
{:class (when on-select "is-selectable")
:on-click on-select
{:class (when on-click "is-selectable")
:on-click on-click
:on-context-menu on-context-menu}
[:div.typography-sample
{:style {:font-family (:font-family typography)

View file

@ -13,6 +13,7 @@
[app.main.store :as st]
[app.main.data.workspace :as dw]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.undo :as dwu]
[app.util.i18n :as i18n :refer [t]]
[app.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row]]))
@ -31,12 +32,12 @@
on-open
(mf/use-callback
(mf/deps page-id)
#(st/emit! (dwc/start-undo-transaction)))
#(st/emit! (dwu/start-undo-transaction)))
on-close
(mf/use-callback
(mf/deps page-id)
#(st/emit! (dwc/commit-undo-transaction)))]
#(st/emit! (dwu/commit-undo-transaction)))]
[:div.element-set
[:div.element-set-title (t locale "workspace.options.canvas-background")]

View file

@ -102,6 +102,13 @@
:index index
:name (:name page)})]
(mf/use-effect
(mf/deps selected?)
(fn []
(when selected?
(let [node (mf/ref-val dref)]
(.scrollIntoViewIfNeeded ^js node)))))
(mf/use-layout-effect
(mf/deps (:edition @local))
(fn []

View file

@ -95,10 +95,12 @@
;; Only when we have all the selected shapes in one frame
selected-frame (when (= (count selected-frames) 1) (get objects (first selected-frames)))
create-comment? (= :comments drawing-tool)
drawing-path? (or (and edition (= :draw (get-in edit-path [edition :edit-mode])))
(and (some? drawing-obj) (= :path (:type drawing-obj))))
text-editing? (and edition (= :text (get-in objects [edition :type])))
path-editing? (and edition (= :path (get-in objects [edition :type])))
text-editing? (and edition (= :text (get-in objects [edition :type])))
on-click (actions/on-click hover selected edition drawing-path? drawing-tool)
on-context-menu (actions/on-context-menu hover)
@ -106,7 +108,7 @@
on-drag-enter (actions/on-drag-enter)
on-drag-over (actions/on-drag-over)
on-drop (actions/on-drop file viewport-ref zoom)
on-mouse-down (actions/on-mouse-down @hover drawing-tool text-editing? edition edit-path selected)
on-mouse-down (actions/on-mouse-down @hover selected edition drawing-tool text-editing? path-editing? drawing-path? create-comment?)
on-mouse-up (actions/on-mouse-up disable-paste)
on-pointer-down (actions/on-pointer-down)
on-pointer-enter (actions/on-pointer-enter in-viewport?)

View file

@ -24,13 +24,12 @@
[beicon.core :as rx]
[cuerdas.core :as str]
[rumext.alpha :as mf])
(:import goog.events.WheelEvent
goog.events.KeyCodes))
(:import goog.events.WheelEvent))
(defn on-mouse-down
[{:keys [id blocked hidden type]} drawing-tool text-editing? edition edit-path selected]
[{:keys [id blocked hidden type]} selected edition drawing-tool text-editing? path-editing? drawing-path? create-comment?]
(mf/use-callback
(mf/deps id blocked hidden type drawing-tool text-editing? edition edit-path selected)
(mf/deps id blocked hidden type selected edition drawing-tool text-editing? path-editing? drawing-path? create-comment?)
(fn [bevent]
(when (or (dom/class? (dom/get-target bevent) "viewport-controls")
(dom/class? (dom/get-target bevent) "viewport-selrect"))
@ -45,9 +44,7 @@
middle-click? (= 2 (.-which event))
frame? (= :frame type)
selected? (contains? selected id)
drawing-path? (= :draw (get-in edit-path [edition :edit-mode]))]
selected? (contains? selected id)]
(when middle-click?
(dom/prevent-default bevent)
@ -62,13 +59,13 @@
(when (and (not text-editing?)
(not blocked)
(not hidden)
(not (#{:comments :path} drawing-tool))
(not create-comment?)
(not drawing-path?))
(cond
drawing-tool
(st/emit! (dd/start-drawing drawing-tool))
(and edit-path (contains? edit-path edition))
path-editing?
;; Handle path node area selection
(st/emit! (dwdp/handle-selection shift?))
@ -263,8 +260,7 @@
(mf/use-callback
(fn [event]
(let [bevent (.getBrowserEvent ^js event)
key (.-keyCode ^js event)
key (.normalizeKeyCode KeyCodes key)
key (.-key ^js event)
ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
alt? (kbd/alt? event)
@ -284,8 +280,7 @@
(defn on-key-up []
(mf/use-callback
(fn [event]
(let [key (.-keyCode event)
key (.normalizeKeyCode KeyCodes key)
(let [key (.-key event)
ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
alt? (kbd/alt? event)

View file

@ -100,7 +100,8 @@
{:cmd :selection/query
:page-id page-id
:rect rect
:include-frames? true}))))
:include-frames? true
:reverse? true})))) ;; we want the topmost shape to be selected first
;; We use ref so we don't recreate the stream on a change
transform-ref (mf/use-ref nil)

View file

@ -19,7 +19,6 @@
[app.main.streams :as ms]
[app.main.ui.cursors :as cur]
[app.main.ui.hooks :as hooks]
[app.main.ui.workspace.viewport.outline :refer [outline]]
[app.main.ui.workspace.shapes.path.editor :refer [path-editor]]
[app.util.data :as d]
[app.util.debug :refer [debug?]]
@ -255,7 +254,6 @@
:zoom zoom
:color color
:on-move-selected on-move-selected}]
[:& outline {:shape shape :color color}]
;; Handlers
(for [{:keys [type position props]} (handlers-for-selection selrect shape zoom)]

View file

@ -6,10 +6,10 @@
(ns app.util.keyboard)
(defn is-keycode?
[keycode]
(defn is-key?
[key]
(fn [e]
(= (.-keyCode e) keycode)))
(= (.-key e) key)))
(defn ^boolean alt?
[event]
@ -27,8 +27,11 @@
[event]
(.-shiftKey event))
(def esc? (is-keycode? 27))
(def enter? (is-keycode? 13))
(def space? (is-keycode? 32))
(def up-arrow? (is-keycode? 38))
(def down-arrow? (is-keycode? 40))
(def esc? (is-key? "Escape"))
(def enter? (is-key? "Enter"))
(def space? (is-key? " "))
(def up-arrow? (is-key? "ArrowUp"))
(def down-arrow? (is-key? "ArrowDown"))
(def altKey? (is-key? "Alt"))
(def ctrlKey? (or (is-key? "Control")
(is-key? "Meta")))

View file

@ -41,8 +41,8 @@
nil))
(defmethod impl/handler :selection/query
[{:keys [page-id rect frame-id include-frames? include-groups? disabled-masks] :or {include-groups? true
disabled-masks #{}} :as message}]
[{:keys [page-id rect frame-id include-frames? include-groups? disabled-masks reverse?]
:or {include-groups? true disabled-masks #{} reverse? false} :as message}]
(when-let [index (get @state page-id)]
(let [result (-> (qdt/search index (clj->js rect))
(es6-iterator-seq))
@ -76,11 +76,13 @@
(filter (comp overlaps? :frame))
(filter (comp overlaps-masks? :masks))
(filter overlaps?))
result)]
result)
keyfn (if reverse? (comp - :z) :z)]
(into (d/ordered-set)
(->> matching-shapes
(sort-by (comp - :z))
(sort-by keyfn)
(map :id))))))
(defn create-mask-index

View file

@ -1194,6 +1194,11 @@ msgstr "Send invitation"
msgid "modals.invite-member.title"
msgstr "Invite to join the team"
msgid "modals.leave-and-reassign.forbiden"
msgstr ""
"You can not leave the team if there is no other member to promote to owner. "
"You might want to delete the team."
#: src/app/main/ui/dashboard/sidebar.cljs
msgid "modals.leave-and-reassign.hint1"
msgstr "You are %s owner."

View file

@ -1184,6 +1184,11 @@ msgstr "Enviar invitacion"
msgid "modals.invite-member.title"
msgstr "Invitar a unirse al equipo"
msgid "modals.leave-and-reassign.forbiden"
msgstr ""
"No puede abandonar el equipo si no hay otro miembro al que promocionar a "
"dueño. Quizás quiere borrar el equipo."
#: src/app/main/ui/dashboard/sidebar.cljs
msgid "modals.leave-and-reassign.hint1"
msgstr "Eres %s dueño."
@ -2505,4 +2510,4 @@ msgid "workspace.updates.update"
msgstr "Actualizar"
msgid "workspace.viewport.click-to-close-path"
msgstr "Pulsar para cerrar la ruta"
msgstr "Pulsar para cerrar la ruta"