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

This commit is contained in:
Andrey Antukh 2025-06-20 11:37:50 +02:00
commit ec021d944d
23 changed files with 436 additions and 343 deletions

View file

@ -72,6 +72,7 @@ on-premises instances** that want to keep up to date.
- Fix button width [Taiga #11394](https://tree.taiga.io/project/penpot/issue/11394)
- Fix mixed letter spacing and line height [Taiga #11178](https://tree.taiga.io/project/penpot/issue/11178)
- Fix snap nodes shortcut [Taiga #11054](https://tree.taiga.io/project/penpot/issue/11054)
- Fix changing a text property in a text layer does not unapply the previously applied token in the same property [Taiga #11337}(https://tree.taiga.io/project/penpot/issue/11337)
## 2.7.2

View file

@ -161,7 +161,6 @@
(contains? (meta changes) ::file-data)
"Call (with-file-data) before using this function"))
(defn- lookup-objects
[changes]
(let [data (::file-data (meta changes))]

View file

@ -16,19 +16,30 @@
[app.common.types.pages-list :as ctpl]
[app.common.types.shape.interactions :as ctsi]
[app.common.types.shape.layout :as ctl]
[app.common.types.text :as ctt]
[app.common.types.token :as cto]
[app.common.uuid :as uuid]))
[app.common.uuid :as uuid]
[clojure.set :as set]))
(defn- generate-unapply-tokens
"When updating attributes that have a token applied, we must unapply it, because the value
of the attribute now has been given directly, and does not come from the token."
[changes objects changed-sub-attr]
(let [mod-obj-changes (->> (:redo-changes changes)
(let [new-objects (pcb/get-objects changes)
mod-obj-changes (->> (:redo-changes changes)
(filter #(= (:type %) :mod-obj)))
text-changed-attrs
(fn [shape]
(let [new-shape (get new-objects (:id shape))
attrs (ctt/get-diff-attrs (:content shape) (:content new-shape))]
(apply set/union (map cto/shape-attr->token-attrs attrs))))
check-attr (fn [shape changes attr]
(let [tokens (get shape :applied-tokens {})
token-attrs (cto/shape-attr->token-attrs attr changed-sub-attr)]
token-attrs (if (or (not= (:type shape) :text) (not= attr :content))
(cto/shape-attr->token-attrs attr changed-sub-attr)
(text-changed-attrs shape))]
(if (some #(contains? tokens %) token-attrs)
(pcb/update-shapes changes [(:id shape)] #(cto/unapply-token-id % token-attrs))
changes)))

View file

@ -37,8 +37,6 @@
(merge shape
text-params))))
(defn add-frame
[file frame-label & {:keys [] :as params}]
;; Generated shape tree:

View file

@ -11,6 +11,7 @@
[app.common.files.helpers :as cfh]
[app.common.test-helpers.files :as thf]
[app.common.test-helpers.ids-map :as thi]
[app.common.text :as txt]
[app.common.types.color :as ctc]
[app.common.types.container :as ctn]
[app.common.types.pages-list :as ctpl]
@ -81,6 +82,21 @@
(:id page)
#(ctst/set-shape % (ctn/set-shape-attr shape attr val)))))))
(defn update-shape-text
[file shape-label attr val & {:keys [page-label]}]
(let [page (if page-label
(thf/get-page file page-label)
(thf/current-page file))
shape (ctst/get-shape page (thi/id shape-label))]
(update file :data
(fn [file-data]
(ctpl/update-page file-data
(:id page)
#(ctst/set-shape % (txt/update-text-content shape
txt/is-content-node?
d/txt-merge
{attr val})))))))
(defn sample-library-color
[label & {:keys [name path color opacity gradient image]}]
(-> {:id (thi/new-id! label)

View file

@ -470,8 +470,6 @@
"Given a content and a set of points return all the segments in the path
that uses the points"
[content points]
(assert (impl/path-data? content) "expected path data instance")
(let [point-set (set points)]
(loop [result (transient [])
prev-point nil

View file

@ -11,9 +11,12 @@
(defn- compare-text-content
"Given two content text structures, conformed by maps and vectors,
compare them, and returns a set with the type of differences.
The possibilities are :text-content-text :text-content-attribute and :text-content-structure."
[a b]
compare them, and returns a set with the differences info.
If the structures are equal, it returns an empty set. If the structure
has changed, it returns :text-content-structure. There are two
callbacks to specify what to return when there is a text change with
the same structure, and when attributes change."
[a b {:keys [text-cb attribute-cb] :as callbacks}]
(cond
;; If a and b are equal, there is no diff
(= a b)
@ -38,18 +41,18 @@
#{:text-content-structure}
(into acc
(apply set/union
(map #(compare-text-content %1 %2) v1 v2))))
(map #(compare-text-content %1 %2 callbacks) v1 v2))))
;; If the key is :text, and they are different, it is a text differece
(= k :text)
(if (not= v1 v2)
(conj acc :text-content-text)
(text-cb acc)
acc)
:else
;; If the key is not :text, and they are different, it is an attribute differece
(if (not= v1 v2)
(conj acc :text-content-attribute)
(attribute-cb acc k)
acc))))
#{}
keys))
@ -57,7 +60,6 @@
:else
#{:text-content-structure}))
(defn equal-attrs?
"Given a text structure, and a map of attrs, check that all the internal attrs in
paragraphs and sentences have the same attrs"
@ -79,10 +81,15 @@
(defn get-diff-type
"Given two content text structures, conformed by maps and vectors,
compare them, and returns a set with the type of differences.
The possibilities are :text-content-text :text-content-attribute,
:text-content-structure and :text-content-structure-same-attrs."
The possibilities are
:text-content-text
:text-content-attribute,
:text-content-structure
:text-content-structure-same-attrs."
[a b]
(let [diff-type (compare-text-content a b)]
(let [diff-type (compare-text-content a b
{:text-cb (fn [acc] (conj acc :text-content-text))
:attribute-cb (fn [acc _] (conj acc :text-content-attribute))})]
(if-not (contains? diff-type :text-content-structure)
diff-type
(let [;; get attrs of the first paragraph of the first paragraph-set
@ -92,6 +99,24 @@
#{:text-content-structure :text-content-structure-same-attrs}
diff-type)))))
(defn get-diff-attrs
"Given two content text structures, conformed by maps and vectors,
compare them, and returns a set with the attributes that have changed.
This is independent of the text structure, so if the structure changes
but the attributes are the same, it will return an empty set."
[a b]
(let [diff-attrs (compare-text-content a b
{:text-cb identity
:attribute-cb (fn [acc attr] (conj acc attr))})]
(if-not (contains? diff-attrs :text-content-structure)
diff-attrs
(let [;; get attrs of the first paragraph of the first paragraph-set
attrs (get-first-paragraph-text-attrs a)]
(if (and (equal-attrs? a attrs)
(equal-attrs? b attrs))
#{}
(disj diff-attrs :text-content-structure))))))
;; TODO We know that there are cases that the blocks of texts are separated
;; differently: ["one" " " "two"], ["one " "two"], ["one" " two"]
;; so this won't work for 100% of the situations. But it's good enough for now,
@ -116,7 +141,6 @@
:else
true))
(defn copy-text-keys
"Given two equal content text structures, deep copy all the keys :text
from origin to destiny"

View file

@ -6,6 +6,7 @@
(ns common-tests.logic.token-apply-test
(:require
[app.common.data :as d]
[app.common.files.changes-builder :as pcb]
[app.common.logic.shapes :as cls]
[app.common.test-helpers.compositions :as tho]
@ -13,6 +14,7 @@
[app.common.test-helpers.ids-map :as thi]
[app.common.test-helpers.shapes :as ths]
[app.common.test-helpers.tokens :as tht]
[app.common.text :as txt]
[app.common.types.container :as ctn]
[app.common.types.token :as cto]
[app.common.types.tokens-lib :as ctob]
@ -53,7 +55,8 @@
(ctob/make-token :name "token-dimensions"
:type :dimensions
:value 100))))
(tho/add-frame :frame1)))
(tho/add-frame :frame1)
(tho/add-text :text1 "Hello World")))
(defn- apply-all-tokens
[file]
@ -64,7 +67,8 @@
(tht/apply-token-to-shape :frame1 "token-stroke-width" [:stroke-width] [:stroke-width] 2)
(tht/apply-token-to-shape :frame1 "token-color" [:stroke-color] [:stroke-color] "#00ff00")
(tht/apply-token-to-shape :frame1 "token-color" [:fill] [:fill] "#00ff00")
(tht/apply-token-to-shape :frame1 "token-dimensions" [:width :height] [:width :height] 100)))
(tht/apply-token-to-shape :frame1 "token-dimensions" [:width :height] [:width :height] 100)
(tht/apply-token-to-shape :text1 "token-color" [:fill] [:fill] "#00ff00")))
(t/deftest apply-tokens-to-shape
(let [;; ==== Setup
@ -171,6 +175,7 @@
(apply-all-tokens))
page (thf/current-page file)
frame1 (ths/get-shape file :frame1)
text1 (ths/get-shape file :text1)
;; ==== Action
changes (-> (-> (pcb/empty-changes nil)
@ -190,13 +195,25 @@
(ctn/set-shape-attr :width 0)
(ctn/set-shape-attr :height 0)))
(:objects page)
{})
(cls/generate-update-shapes [(:id text1)]
(fn [shape]
(txt/update-text-content
shape
txt/is-content-node?
d/txt-merge
{:fills (ths/sample-fills-color :fill-color "#fabada")}))
(:objects page)
{}))
file' (thf/apply-changes file changes)
;; ==== Get
frame1' (ths/get-shape file' :frame1)
applied-tokens' (:applied-tokens frame1')]
frame1' (ths/get-shape file' :frame1)
text1' (ths/get-shape file' :text1)
applied-tokens-frame' (:applied-tokens frame1')
applied-tokens-text' (:applied-tokens text1')]
;; ==== Check
(t/is (= (count applied-tokens') 0))))
(t/is (= (count applied-tokens-frame') 0))
(t/is (= (count applied-tokens-text') 0))))

View file

@ -29,11 +29,12 @@
#(conj % line)))
(t/deftest test-get-diff-type
(let [diff-text (cttx/get-diff-type content-base content-changed-text)
diff-attr (cttx/get-diff-type content-base content-changed-attr)
diff-both (cttx/get-diff-type content-base content-changed-both)
diff-structure (cttx/get-diff-type content-base content-changed-structure)
(let [diff-text (cttx/get-diff-type content-base content-changed-text)
diff-attr (cttx/get-diff-type content-base content-changed-attr)
diff-both (cttx/get-diff-type content-base content-changed-both)
diff-structure (cttx/get-diff-type content-base content-changed-structure)
diff-structure-same-attrs (cttx/get-diff-type content-base content-changed-structure-same-attrs)]
(t/is (= #{:text-content-text} diff-text))
(t/is (= #{:text-content-attribute} diff-attr))
(t/is (= #{:text-content-text :text-content-attribute} diff-both))
@ -41,6 +42,20 @@
(t/is (= #{:text-content-structure :text-content-structure-same-attrs} diff-structure-same-attrs))))
(t/deftest test-get-diff-attrs
(let [attrs-text (cttx/get-diff-attrs content-base content-changed-text)
attrs-attr (cttx/get-diff-attrs content-base content-changed-attr)
attrs-both (cttx/get-diff-attrs content-base content-changed-both)
attrs-structure (cttx/get-diff-attrs content-base content-changed-structure)
attrs-structure-same-attrs (cttx/get-diff-attrs content-base content-changed-structure-same-attrs)]
(t/is (= #{} attrs-text))
(t/is (= #{:font-size} attrs-attr))
(t/is (= #{:font-size} attrs-both))
(t/is (= #{} attrs-structure))
(t/is (= #{} attrs-structure-same-attrs))))
(t/deftest test-equal-structure
(t/is (true? (cttx/equal-structure? content-base content-changed-text)))
(t/is (true? (cttx/equal-structure? content-base content-changed-attr)))

View file

@ -96,7 +96,7 @@
(watch [_ _ _]
(rx/of
(case type
:path (path/handle-new-shape)
:path (path/handle-drawing)
:curve (curve/handle-drawing)
(box/handle-drawing type))))))

View file

@ -14,7 +14,7 @@
[app.main.data.workspace.path.undo :as undo]))
;; Drawing
(dm/export drawing/handle-new-shape)
(dm/export drawing/handle-drawing)
(dm/export drawing/start-path-from-point)
(dm/export drawing/close-path-drag-start)
(dm/export drawing/change-edit-mode)

View file

@ -11,7 +11,7 @@
[potok.v2.core :as ptk]))
(defn init-path []
(ptk/reify ::init-path))
(ptk/data-event ::init-path {}))
(defn clean-edit-state
[state]

View file

@ -6,6 +6,7 @@
(ns app.main.data.workspace.path.drawing
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.geom.point :as gpt]
[app.common.geom.shapes.flex-layout :as gsl]
@ -30,21 +31,40 @@
[beicon.v2.core :as rx]
[potok.v2.core :as ptk]))
(declare close-path-drag-end)
(declare check-changed-content)
(declare change-edit-mode)
(defn preview-next-point [{:keys [x y shift?]}]
(defn- end-path-event?
[event]
(let [type (ptk/type event)]
(or
(= type ::common/finish-path)
(= type :app.main.data.workspace.path.shortcuts/esc-pressed)
(= type :app.main.data.workspace.common/clear-edition-mode)
(= type :app.main.data.workspace.edition/clear-edition-mode)
(= type :app.main.data.workspace/finalize-page)
(= event :interrupt) ;; ESC
(and ^boolean (mse/mouse-event? event)
^boolean (mse/mouse-double-click-event? event)))))
(defn preview-next-point
[{:keys [x y shift?]}]
(ptk/reify ::preview-next-point
ptk/UpdateEvent
(update [_ state]
(let [id (st/get-path-id state)
(let [id (st/get-path-id state)
fix-angle? shift?
last-point (get-in state [:workspace-local :edit-path id :last-point])
position (cond-> (gpt/point x y)
fix-angle? (path.helpers/position-fixed-angle last-point))
shape (st/get-path state)
{:keys [last-point prev-handler]} (get-in state [:workspace-local :edit-path id])
command (path.segment/next-node shape position last-point prev-handler)]
(assoc-in state [:workspace-local :edit-path id :preview] command)))))
position (cond-> (gpt/point x y)
fix-angle? (path.helpers/position-fixed-angle last-point))
shape (st/get-path state)
{:keys [last-point prev-handler]}
(get-in state [:workspace-local :edit-path id])
segment (path.segment/next-node shape position last-point prev-handler)]
(assoc-in state [:workspace-local :edit-path id :preview] segment)))))
(defn add-node
[{:keys [x y shift?]}]
@ -121,8 +141,6 @@
(rx/of (preview-next-point handler)
(undo/merge-head))))))
(declare close-path-drag-end)
(defn close-path-drag-start
[position]
(ptk/reify ::close-path-drag-start
@ -141,8 +159,7 @@
(rx/take-until
(rx/merge
(mse/drag-stopper stream)
(->> stream
(rx/filter helpers/end-path-event?)))))]
(rx/filter end-path-event? stream))))]
(rx/concat
(rx/of (add-node position))
@ -166,8 +183,7 @@
(watch [_ state stream]
(let [stopper (rx/merge
(mse/drag-stopper stream)
(->> stream
(rx/filter helpers/end-path-event?)))
(rx/filter end-path-event? stream))
drag-events (->> (streams/position-stream state)
(rx/map #(drag-handler %))
@ -189,18 +205,17 @@
(defn make-drag-stream
[state stream down-event]
(dm/assert!
"should be a pointer"
(gpt/point? down-event))
(assert (gpt/point? down-event)
"should be a point instance")
(let [stopper (rx/merge
(mse/drag-stopper stream)
(->> stream
(rx/filter helpers/end-path-event?)))
(rx/filter end-path-event? stream))
drag-events (->> (streams/position-stream state)
(rx/map #(drag-handler %))
(rx/take-until stopper))]
drag-events
(->> (streams/position-stream state)
(rx/map #(drag-handler %))
(rx/take-until stopper))]
(rx/concat
(rx/of (add-node down-event))
(streams/drag-stream
@ -208,9 +223,9 @@
drag-events
(rx/of (finish-drag)))))))
(defn handle-drawing
(defn- start-edition
[_id]
(ptk/reify ::handle-drawing
(ptk/reify ::start-edition
ptk/UpdateEvent
(update [_ state]
(let [id (st/get-path-id state)]
@ -218,38 +233,47 @@
ptk/WatchEvent
(watch [_ state stream]
(let [mouse-down (->> stream
(rx/filter mse/mouse-event?)
(rx/filter mse/mouse-down-event?))
end-path-events (->> stream
(rx/filter helpers/end-path-event?))
(let [mouse-down
(->> stream
(rx/filter mse/mouse-event?)
(rx/filter mse/mouse-down-event?))
end-stream
(->> stream
(rx/filter end-path-event?)
(rx/share))
stoper-stream
(->> stream
(rx/filter (ptk/type? ::start-edition))
(rx/merge end-stream))
;; Mouse move preview
mousemove-events
(->> (streams/position-stream state)
(rx/take-until end-path-events)
(rx/map #(preview-next-point %)))
;; From mouse down we can have: click, drag and double click
mousedown-events
(->> mouse-down
(rx/take-until end-path-events)
;; We just ignore the mouse event and stream down the
;; last position event
(rx/with-latest-from #(-> %2) (streams/position-stream state))
;; We change to the stream that emits the first event
(rx/switch-map
#(rx/race (make-node-events-stream stream)
(make-drag-stream state stream %))))]
(make-drag-stream state stream %)))
(rx/take-until end-stream))]
(rx/concat
(rx/of (undo/start-path-undo))
(rx/of (common/init-path))
(rx/merge mousemove-events
mousedown-events)
(rx/of (common/finish-path)))))))
(->> (rx/concat
(rx/of (undo/start-path-undo))
(->> (rx/merge mousemove-events
mousedown-events)
(rx/take-until stoper-stream))
(rx/of (ptk/data-event ::end-edition))))))))
(defn setup-frame []
(defn setup-frame
[]
(ptk/reify ::setup-frame
ptk/UpdateEvent
(update [_ state]
@ -275,9 +299,9 @@
(cond-> (some? drop-index)
(with-meta {:index drop-index})))))))))
(defn handle-new-shape-result
(defn- handle-drawing-end
[shape-id]
(ptk/reify ::handle-new-shape-result
(ptk/reify ::handle-drawing-end
ptk/UpdateEvent
(update [_ state]
(let [content (dm/get-in state [:workspace-drawing :object :content])]
@ -299,8 +323,8 @@
(change-edit-mode :draw))
(rx/of (dwdc/handle-finish-drawing)))))))
(defn handle-new-shape
"Creates a new path shape"
(defn handle-drawing
"Hanndle the start of drawing new path shape"
[]
(ptk/reify ::handle-new-shape
ptk/UpdateEvent
@ -312,16 +336,17 @@
(watch [_ state stream]
(let [shape-id (dm/get-in state [:workspace-drawing :object :id])]
(rx/concat
(rx/of (handle-drawing shape-id))
(rx/of (start-edition shape-id))
(->> stream
(rx/filter (ptk/type? ::common/finish-path))
(rx/filter (ptk/type? ::end-edition))
(rx/take 1)
(rx/observe-on :async)
(rx/map #(handle-new-shape-result shape-id))))))))
(rx/map (partial handle-drawing-end shape-id))))))))
(declare check-changed-content)
(declare start-draw-mode*)
(defn start-draw-mode []
(defn start-draw-mode
[]
(ptk/reify ::start-draw-mode
ptk/UpdateEvent
(update [_ state]
@ -332,21 +357,59 @@
(update-in state [:workspace-local :edit-path id] assoc :old-content content)
state)))
ptk/WatchEvent
(watch [_ _ _]
(rx/of (start-draw-mode*)))))
(defn start-draw-mode*
[]
(ptk/reify ::start-draw-mode*
ptk/WatchEvent
(watch [_ state stream]
(let [id (get-in state [:workspace-local :edition])
edit-mode (get-in state [:workspace-local :edit-path id :edit-mode])]
(if (= :draw edit-mode)
(let [local (get state :workspace-local)
id (get local :edition)
mode (dm/get-in local [:edit-path id :edit-mode])]
(if (= :draw mode)
(rx/concat
(rx/of (dwsh/update-shapes [id] path/convert-to-path))
(rx/of (handle-drawing id))
(rx/of (start-edition id))
(->> stream
(rx/filter (ptk/type? ::common/finish-path))
(rx/filter (ptk/type? ::end-edition))
(rx/take 1)
(rx/map check-changed-content)))
(rx/mapcat (fn [_]
(rx/of (check-changed-content)
(start-draw-mode*))))))
(rx/empty))))))
(defn check-changed-content []
(defn change-edit-mode
[mode]
(ptk/reify ::change-edit-mode
ptk/UpdateEvent
(update [_ state]
(if-let [id (dm/get-in state [:workspace-local :edition])]
(d/update-in-when state [:workspace-local :edit-path id] assoc :edit-mode mode)
state))
ptk/WatchEvent
(watch [_ state _]
(when-let [id (dm/get-in state [:workspace-local :edition])]
(let [mode (dm/get-in state [:workspace-local :edit-path id :edit-mode])]
(case mode
:move (rx/of (common/finish-path))
:draw (rx/of (start-draw-mode))
(rx/empty)))))))
(defn reset-last-handler
[]
(ptk/reify ::reset-last-handler
ptk/UpdateEvent
(update [_ state]
(let [id (st/get-path-id state)]
(assoc-in state [:workspace-local :edit-path id :prev-handler] nil)))))
(defn check-changed-content
[]
(ptk/reify ::check-changed-content
ptk/WatchEvent
(watch [_ state _]
@ -355,36 +418,15 @@
old-content (get-in state [:workspace-local :edit-path id :old-content])
mode (get-in state [:workspace-local :edit-path id :edit-mode])
empty-content? (empty? content)]
(cond
(and (not= content old-content) (not empty-content?)) (rx/of (changes/save-path-content))
(= mode :draw) (rx/of :interrupt)
:else (rx/of
(common/finish-path)
(dwdc/clear-drawing)))))))
(and (not= content old-content) (not empty-content?))
(rx/of (changes/save-path-content))
(defn change-edit-mode
[mode]
(ptk/reify ::change-edit-mode
ptk/UpdateEvent
(update [_ state]
(let [id (get-in state [:workspace-local :edition])]
(cond-> state
id (assoc-in [:workspace-local :edit-path id :edit-mode] mode))))
ptk/WatchEvent
(watch [_ state _]
(let [id (st/get-path-id state)]
(cond
(and id (= :move mode)) (rx/of (common/finish-path))
(and id (= :draw mode)) (rx/of (start-draw-mode))
:else (rx/empty))))))
(defn reset-last-handler
[]
(ptk/reify ::reset-last-handler
ptk/UpdateEvent
(update [_ state]
(let [id (st/get-path-id state)]
(-> state
(assoc-in [:workspace-local :edit-path id :prev-handler] nil))))))
(= mode :draw)
(rx/of :interrupt)
:else
(rx/of
(common/finish-path)
(dwdc/clear-drawing)))))))

View file

@ -10,22 +10,7 @@
[app.common.math :as mth]
[app.common.types.path :as path]
[app.common.types.path.helpers :as path.helpers]
[app.common.types.path.segment :as path.segment]
[app.main.data.workspace.path.common :as common]
[app.util.mouse :as mse]
[potok.v2.core :as ptk]))
(defn end-path-event?
[event]
(let [type (ptk/type event)]
(or (= type ::common/finish-path)
(= type :app.main.data.workspace.path.shortcuts/esc-pressed)
(= type :app.main.data.workspace.common/clear-edition-mode)
(= type :app.main.data.workspace.edition/clear-edition-mode)
(= type :app.main.data.workspace/finalize-page)
(= event :interrupt) ;; ESC
(and ^boolean (mse/mouse-event? event)
^boolean (mse/mouse-double-click-event? event)))))
[app.common.types.path.segment :as path.segment]))
(defn append-node
"Creates a new node in the path. Usually used when drawing."

View file

@ -7,42 +7,8 @@
(ns app.main.data.workspace.path.state
(:require
[app.common.data.macros :as dm]
[app.common.files.helpers :as cph]
[app.common.types.path.shape-to-path :as stp]))
(defn path-editing?
"Returns true if we're editing a path or creating a new one."
[{local :workspace-local
drawing :workspace-drawing}]
(let [selected (:selected local)
edition (:edition local)
drawing-obj (:object drawing)
drawing-tool (:tool drawing)
edit-path? (dm/get-in local [:edit-path edition])
shape (or drawing-obj (first selected))
shape-id (:id shape)
single? (= (count selected) 1)
editing? (and (some? shape-id)
(some? edition)
(= shape-id edition))
;; we need to check if we're drawing a new object but we're
;; not using the pencil tool.
draw-path? (and (some? drawing-obj)
(cph/path-shape? drawing-obj)
(not= :curve drawing-tool))]
(or (and ^boolean single?
^boolean editing?
(and (not (cph/text-shape? shape))
(not (cph/frame-shape? shape))))
draw-path?
edit-path?)))
(defn get-path-id
"Retrieves the currently editing path id"
[state]

View file

@ -22,9 +22,8 @@
(defn initialize-viewport
[{:keys [width height] :as size}]
(dm/assert!
"expected `size` to be a rect instance"
(gpr/rect? size))
(assert (gpr/rect? size)
"expected `size` to be a rect instance")
(letfn [(update* [{:keys [vport] :as local}]
(let [wprop (/ (:width vport) width)

View file

@ -8,13 +8,11 @@
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.helpers :as cfh]
[app.common.geom.point :as gpt]
[app.common.types.path :as path]
[app.common.types.path.helpers :as path.helpers]
[app.common.types.path.segment :as path.segment]
[app.main.data.workspace.path :as drp]
[app.main.refs :as refs]
[app.main.snap :as snap]
[app.main.store :as st]
[app.main.streams :as ms]
@ -24,7 +22,6 @@
[app.util.keyboard :as kbd]
[clojure.set :refer [map-invert]]
[goog.events :as events]
[okulary.core :as l]
[rumext.v2 :as mf]))
(def point-radius 5)
@ -261,18 +258,9 @@
angle (gpt/angle-with-other v1 v2)]
(<= (- 180 angle) 0.1))))
(defn- make-edit-path-ref [id]
(let [get-fn #(dm/get-in % [:edit-path id])]
(l/derived get-fn refs/workspace-local)))
(mf/defc path-editor*
[{:keys [shape zoom]}]
(let [shape-id (dm/get-prop shape :id)
edit-path-ref (mf/with-memo [shape-id]
(make-edit-path-ref shape-id))
hover-point (mf/use-state nil)
[{:keys [shape zoom state]}]
(let [hover-point (mf/use-state nil)
editor-ref (mf/use-ref nil)
{:keys [edit-mode
@ -286,19 +274,12 @@
moving-handler
hover-handlers
hover-points
snap-toggled]
:as edit-path}
(mf/deref edit-path-ref)
snap-toggled]}
state
selected-points
(or selected-points #{})
shape
(mf/with-memo [shape]
(cond-> shape
(not (cfh/path-shape? shape))
(path/convert-to-path)))
base-content
(get shape :content)

View file

@ -7,14 +7,12 @@
(ns app.main.ui.workspace.top-toolbar
(:require-macros [app.main.style :as stl])
(:require
[app.common.data.macros :as dm]
[app.common.geom.point :as gpt]
[app.main.data.event :as ev]
[app.main.data.modal :as modal]
[app.main.data.workspace :as dw]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.media :as dwm]
[app.main.data.workspace.path.state :as pst]
[app.main.data.workspace.shortcuts :as sc]
[app.main.features :as features]
[app.main.refs :as refs]
@ -68,26 +66,31 @@
:ref ref
:on-selected on-selected}]]]))
(def toolbar-hidden
(l/derived
(fn [state]
(let [visibility (dm/get-in state [:workspace-local :hide-toolbar])
editing? (pst/path-editing? state)
hidden? (if editing? true visibility)]
hidden?))
st/state))
(def ^:private toolbar-hidden-ref
(l/derived (fn [state]
(let [visibility (get state :hide-toolbar)
path-edit-state (get state :edit-path)
(mf/defc top-toolbar
{::mf/wrap [mf/memo]
::mf/wrap-props false}
selected (get state :selected)
edition (get state :edition)
single? (= (count selected) 1)
path-editing? (and single? (some? (get path-edit-state edition)))]
(if path-editing? true visibility)))
refs/workspace-local))
(mf/defc top-toolbar*
{::mf/memo true}
[{:keys [layout]}]
(let [selected-drawtool (mf/deref refs/selected-drawing-tool)
edition (mf/deref refs/selected-edition)
(let [drawtool (mf/deref refs/selected-drawing-tool)
edition (mf/deref refs/selected-edition)
read-only? (mf/use-ctx ctx/workspace-read-only?)
profile (mf/deref refs/profile)
props (get profile :props)
rulers? (mf/deref refs/rulers?)
hide-toolbar? (mf/deref toolbar-hidden)
read-only? (mf/use-ctx ctx/workspace-read-only?)
rulers? (mf/deref refs/rulers?)
hide-toolbar? (mf/deref toolbar-hidden-ref)
interrupt
(mf/use-fn #(st/emit! :interrupt (dw/clear-edition-mode)))
@ -121,8 +124,6 @@
(dom/blur! (dom/get-target event))
(st/emit! (dwc/toggle-toolbar-visibility))))
profile (mf/deref refs/profile)
props (get profile :props)
test-tooltip-board-text
(if (not (:workspace-visited props))
(tr "workspace.toolbar.frame-first-time" (sc/get-tooltip :draw-frame))
@ -139,7 +140,7 @@
{:title (tr "workspace.toolbar.move" (sc/get-tooltip :move))
:aria-label (tr "workspace.toolbar.move" (sc/get-tooltip :move))
:class (stl/css-case :main-toolbar-options-button true
:selected (and (nil? selected-drawtool)
:selected (and (nil? drawtool)
(not edition)))
:on-click interrupt}
i/move]]
@ -148,7 +149,7 @@
[:button
{:title test-tooltip-board-text
:aria-label (tr "workspace.toolbar.frame" (sc/get-tooltip :draw-frame))
:class (stl/css-case :main-toolbar-options-button true :selected (= selected-drawtool :frame))
:class (stl/css-case :main-toolbar-options-button true :selected (= drawtool :frame))
:on-click select-drawtool
:data-tool "frame"
:data-testid "artboard-btn"}
@ -157,7 +158,7 @@
[:button
{:title (tr "workspace.toolbar.rect" (sc/get-tooltip :draw-rect))
:aria-label (tr "workspace.toolbar.rect" (sc/get-tooltip :draw-rect))
:class (stl/css-case :main-toolbar-options-button true :selected (= selected-drawtool :rect))
:class (stl/css-case :main-toolbar-options-button true :selected (= drawtool :rect))
:on-click select-drawtool
:data-tool "rect"
:data-testid "rect-btn"}
@ -166,7 +167,7 @@
[:button
{:title (tr "workspace.toolbar.ellipse" (sc/get-tooltip :draw-ellipse))
:aria-label (tr "workspace.toolbar.ellipse" (sc/get-tooltip :draw-ellipse))
:class (stl/css-case :main-toolbar-options-button true :selected (= selected-drawtool :circle))
:class (stl/css-case :main-toolbar-options-button true :selected (= drawtool :circle))
:on-click select-drawtool
:data-tool "circle"
:data-testid "ellipse-btn"}
@ -175,7 +176,7 @@
[:button
{:title (tr "workspace.toolbar.text" (sc/get-tooltip :draw-text))
:aria-label (tr "workspace.toolbar.text" (sc/get-tooltip :draw-text))
:class (stl/css-case :main-toolbar-options-button true :selected (= selected-drawtool :text))
:class (stl/css-case :main-toolbar-options-button true :selected (= drawtool :text))
:on-click select-drawtool
:data-tool "text"}
i/text]]
@ -186,7 +187,7 @@
[:button
{:title (tr "workspace.toolbar.curve" (sc/get-tooltip :draw-curve))
:aria-label (tr "workspace.toolbar.curve" (sc/get-tooltip :draw-curve))
:class (stl/css-case :main-toolbar-options-button true :selected (= selected-drawtool :curve))
:class (stl/css-case :main-toolbar-options-button true :selected (= drawtool :curve))
:on-click select-drawtool
:data-tool "curve"
:data-testid "curve-btn"}
@ -195,7 +196,7 @@
[:button
{:title (tr "workspace.toolbar.path" (sc/get-tooltip :draw-path))
:aria-label (tr "workspace.toolbar.path" (sc/get-tooltip :draw-path))
:class (stl/css-case :main-toolbar-options-button true :selected (= selected-drawtool :path))
:class (stl/css-case :main-toolbar-options-button true :selected (= drawtool :path))
:on-click select-drawtool
:data-tool "path"
:data-testid "path-btn"}

View file

@ -12,6 +12,8 @@
[app.common.data.macros :as dm]
[app.common.files.helpers :as cfh]
[app.common.geom.shapes :as gsh]
[app.common.types.path :as path]
[app.common.types.shape :as cts]
[app.common.types.shape-tree :as ctt]
[app.common.types.shape.layout :as ctl]
[app.main.data.workspace.modifiers :as dwm]
@ -29,6 +31,7 @@
[app.main.ui.workspace.shapes.text.text-edition-outline :refer [text-edition-outline]]
[app.main.ui.workspace.shapes.text.v2-editor :as editor-v2]
[app.main.ui.workspace.shapes.text.viewport-texts-html :as stvh]
[app.main.ui.workspace.top-toolbar :refer [top-toolbar*]]
[app.main.ui.workspace.viewport-wasm :as viewport.wasm]
[app.main.ui.workspace.viewport.actions :as actions]
[app.main.ui.workspace.viewport.comments :as comments]
@ -48,7 +51,7 @@
[app.main.ui.workspace.viewport.selection :as selection]
[app.main.ui.workspace.viewport.snap-distances :as snap-distances]
[app.main.ui.workspace.viewport.snap-points :as snap-points]
[app.main.ui.workspace.viewport.top-bar :as top-bar]
[app.main.ui.workspace.viewport.top-bar :refer [path-edition-bar* grid-edition-bar* view-only-bar*]]
[app.main.ui.workspace.viewport.utils :as utils]
[app.main.ui.workspace.viewport.viewport-ref :refer [create-viewport-ref]]
[app.main.ui.workspace.viewport.widgets :as widgets]
@ -88,12 +91,14 @@
vport
zoom
zoom-inverse
edition]} wlocal
edition]}
wlocal
{:keys [options-mode
tooltip
show-distances?
picking-color?]} wglobal
picking-color?]}
wglobal
vbox' (mf/use-debounce 100 vbox)
@ -122,23 +127,23 @@
(not-empty))
;; STATE
alt? (mf/use-state false)
shift? (mf/use-state false)
mod? (mf/use-state false)
space? (mf/use-state false)
z? (mf/use-state false)
cursor (mf/use-state (utils/get-cursor :pointer-inner))
hover-ids (mf/use-state nil)
hover (mf/use-state nil)
measure-hover (mf/use-state nil)
hover-disabled? (mf/use-state false)
alt? (mf/use-state false)
shift? (mf/use-state false)
mod? (mf/use-state false)
space? (mf/use-state false)
z? (mf/use-state false)
cursor (mf/use-state #(utils/get-cursor :pointer-inner))
hover-ids (mf/use-state nil)
hover (mf/use-state nil)
measure-hover (mf/use-state nil)
hover-disabled? (mf/use-state false)
hover-top-frame-id (mf/use-state nil)
frame-hover (mf/use-state nil)
active-frames (mf/use-state #{})
frame-hover (mf/use-state nil)
active-frames (mf/use-state #{})
;; REFS
[viewport-ref
on-viewport-ref] (create-viewport-ref)
[viewport-ref on-viewport-ref]
(create-viewport-ref)
;; VARS
disable-paste (mf/use-var false)
@ -163,34 +168,43 @@
selected-frames (into #{} (map :frame-id) selected-shapes)
;; Only when we have all the selected shapes in one frame
selected-frame (when (= (count selected-frames) 1) (get base-objects (first selected-frames)))
selected-frame (when (= (count selected-frames) 1)
(get base-objects (first selected-frames)))
editing-shape (when edition (get base-objects edition))
edit-path-state (get edit-path edition)
edit-path-mode (get edit-path-state :edit-mode)
edit-path (get edit-path edition)
edit-path-mode (get edit-path :edit-mode)
path-editing? (some? edit-path-state)
path-drawing? (or (= edit-path-mode :draw)
(and (= :path (get drawing-obj :type))
(not= :curve drawing-tool)))
editing-shape (when edition
(get base-objects edition))
editing-shape (mf/with-memo [editing-shape path-editing? base-objects]
(if path-editing?
(path/convert-to-path editing-shape base-objects)
editing-shape))
create-comment? (= :comments drawing-tool)
drawing-path? (or (= edit-path-mode :draw)
(= :path (get drawing-obj :type)))
node-editing? (cfh/path-shape? editing-shape)
text-editing? (cfh/text-shape? editing-shape)
grid-editing? (and edition (ctl/grid-layout? base-objects edition))
mode-inspect? (= options-mode :inspect)
on-click (actions/on-click hover selected edition drawing-path? drawing-tool space? selrect z?)
on-click (actions/on-click hover selected edition path-drawing? drawing-tool space? selrect z?)
on-context-menu (actions/on-context-menu hover hover-ids read-only?)
on-double-click (actions/on-double-click hover hover-ids hover-top-frame-id drawing-path? base-objects edition drawing-tool z? read-only?)
on-double-click (actions/on-double-click hover hover-ids hover-top-frame-id path-drawing? base-objects edition drawing-tool z? read-only?)
comp-inst-ref (mf/use-ref false)
on-drag-enter (actions/on-drag-enter comp-inst-ref)
on-drag-over (actions/on-drag-over move-stream)
on-drag-end (actions/on-drag-over comp-inst-ref)
on-drop (actions/on-drop file comp-inst-ref)
on-pointer-down (actions/on-pointer-down @hover selected edition drawing-tool text-editing? node-editing? grid-editing?
drawing-path? create-comment? space? panning z? read-only?)
on-pointer-down (actions/on-pointer-down @hover selected edition drawing-tool text-editing? path-editing? grid-editing?
path-drawing? create-comment? space? panning z? read-only?)
on-pointer-up (actions/on-pointer-up disable-paste)
@ -236,14 +250,14 @@
(or drawing-obj transform))
show-selrect? (and selrect (empty? drawing) (not text-editing?))
show-measures? (and (not transform)
(not node-editing?)
(not path-editing?)
(or show-distances? mode-inspect? read-only?))
show-artboard-names? (contains? layout :display-artboard-names)
hide-ui? (contains? layout :hide-ui)
show-rulers? (and (contains? layout :rulers) (not hide-ui?))
disabled-guides? (or drawing-tool transform drawing-path? node-editing?)
disabled-guides? (or drawing-tool transform path-drawing? path-editing?)
single-select? (= (count selected-shapes) 1)
@ -276,24 +290,34 @@
rule-area-size (/ rulers/ruler-area-size zoom)]
(hooks/setup-dom-events zoom disable-paste in-viewport? read-only? drawing-tool drawing-path?)
(hooks/setup-dom-events zoom disable-paste in-viewport? read-only? drawing-tool path-drawing?)
(hooks/setup-viewport-size vport viewport-ref)
(hooks/setup-cursor cursor alt? mod? space? panning drawing-tool drawing-path? node-editing? z? read-only?)
(hooks/setup-cursor cursor alt? mod? space? panning drawing-tool path-drawing? path-editing? z? read-only?)
(hooks/setup-keyboard alt? mod? space? z? shift?)
(hooks/setup-hover-shapes page-id move-stream base-objects transform selected mod? hover measure-hover
hover-ids hover-top-frame-id @hover-disabled? focus zoom show-measures?)
(hooks/setup-viewport-modifiers modifiers base-objects)
(hooks/setup-shortcuts node-editing? drawing-path? text-editing? grid-editing?)
(hooks/setup-shortcuts path-editing? path-drawing? text-editing? grid-editing?)
(hooks/setup-active-frames base-objects hover-ids selected active-frames zoom transform vbox)
[:div {:class (stl/css :viewport) :style #js {"--zoom" zoom} :data-testid "viewport"}
(when (:can-edit permissions)
[:> top-bar/top-bar* {:layout layout
:selected selected-shapes
:edit-path edit-path
:drawing drawing
:edition edition
:is-read-only read-only?}])
(if read-only?
[:> view-only-bar* {}]
[:*
(when-not hide-ui?
[:> top-toolbar* {:layout layout}])
(when (and ^boolean path-editing?
^boolean single-select?)
[:> path-edition-bar* {:shape editing-shape
:edit-path-state edit-path-state
:layout layout}])
(when (and ^boolean grid-editing?
^boolean single-select?)
[:> grid-edition-bar* {:shape editing-shape}])]))
[:div {:class (stl/css :viewport-overlays)}
;; The behaviour inside a foreign object is a bit different that in plain HTML so we wrap
;; inside a foreign object "dummy" so this awkward behaviour is take into account
@ -512,7 +536,8 @@
:on-frame-leave on-frame-leave
:on-frame-select on-frame-select}])
(when show-draw-area?
(when (and ^boolean show-draw-area?
^boolean (cts/shape? drawing-obj))
[:> drawarea/draw-area*
{:shape drawing-obj
:zoom zoom
@ -616,8 +641,9 @@
(when show-selection-handlers?
[:g.selection-handlers {:clipPath "url(#clip-handlers)"}
(when-not text-editing?
(if editing-shape
(if (and editing-shape path-editing?)
[:> path-editor* {:shape editing-shape
:state edit-path-state
:zoom zoom}]
(when selected-shapes
[:> selection/handlers*

View file

@ -7,13 +7,19 @@
(ns app.main.ui.workspace.viewport.drawarea
"Drawing components."
(:require
[app.common.data.macros :as dm]
[app.common.math :as mth]
[app.common.types.shape :as cts]
[app.main.refs :as refs]
[app.main.ui.shapes.path :refer [path-shape]]
[app.main.ui.workspace.shapes :as shapes]
[app.main.ui.workspace.shapes.path.editor :refer [path-editor*]]
[okulary.core :as l]
[rumext.v2 :as mf]))
(defn- make-edit-path-ref [id]
(let [get-fn #(dm/get-in % [:edit-path id])]
(l/derived get-fn refs/workspace-local)))
(mf/defc generic-draw-area*
{::mf/private true}
[{:keys [shape zoom]}]
@ -29,17 +35,32 @@
:fill "none"
:stroke-width (/ 1 zoom)}}])))
(mf/defc path-draw-area*
{::mf/private true}
[{:keys [shape] :as props}]
(let [shape-id
(dm/get-prop shape :id)
edit-path-ref
(mf/with-memo [shape-id]
(make-edit-path-ref shape-id))
edit-path-state
(mf/deref edit-path-ref)
props
(mf/spread-props props {:state edit-path-state})]
[:> path-editor* props]))
(mf/defc draw-area*
[{:keys [shape zoom tool] :as props}]
[:g.draw-area
[:g {:style {:pointer-events "none"}}
[:& shapes/shape-wrapper {:shape shape}]]
;; Prevent rendering something that it's not a shape.
(when (cts/shape? shape)
[:g.draw-area
[:g {:style {:pointer-events "none"}}
[:& shapes/shape-wrapper {:shape shape}]]
(case tool
:path [:> path-editor* props]
:curve [:& path-shape {:shape shape :zoom zoom}]
#_:default [:> generic-draw-area* props])]))
(case tool
:path [:> path-draw-area* props]
:curve [:& path-shape {:shape shape :zoom zoom}]
#_:default [:> generic-draw-area* props])])

View file

@ -67,8 +67,8 @@
:separate-nodes segments-selected?})))
(mf/defc path-actions*
[{:keys [shape edit-path]}]
(let [{:keys [edit-mode selected-points snap-toggled]} edit-path
[{:keys [shape state]}]
(let [{:keys [edit-mode selected-points snap-toggled]} state
content (:content shape)

View file

@ -7,19 +7,20 @@
(ns app.main.ui.workspace.viewport.top-bar
(:require-macros [app.main.style :as stl])
(:require
[app.common.data.macros :as dm]
[app.common.files.helpers :as cfh]
[app.common.types.shape.layout :as ctl]
[app.main.data.workspace :as dw]
[app.main.data.workspace.common :as dwc]
[app.main.store :as st]
[app.main.ui.workspace.top-toolbar :refer [top-toolbar]]
[app.main.ui.workspace.viewport.grid-layout-editor :refer [grid-edition-actions]]
[app.main.ui.workspace.viewport.path-actions :refer [path-actions*]]
[app.util.i18n :as i18n :refer [tr]]
[rumext.v2 :as mf]))
(mf/defc view-only-actions*
;; FIXME: this namespace should be renamed and all translation files
;; should also be renamed. But this should be done on development
;; branch.
(mf/defc view-only-bar*
{::mf/private true}
[]
(let [handle-close-view-mode
(mf/use-fn
@ -37,44 +38,15 @@
:on-click handle-close-view-mode}
(tr "workspace.top-bar.read-only.done")]]]))
(mf/defc top-bar*
[{:keys [layout drawing is-read-only edition selected edit-path]}]
(let [rulers? (contains? layout :rulers)
hide-ui? (contains? layout :hide-ui)
(mf/defc path-edition-bar*
[{:keys [layout edit-path-state shape]}]
(let [rulers? (contains? layout :rulers)
class (stl/css-case
:viewport-actions-path true
:viewport-actions-no-rulers (not rulers?))]
[:div {:class class}
[:> path-actions* {:shape shape :state edit-path-state}]]))
drawing-obj (get drawing :object)
shape (or drawing-obj (-> selected first))
shape-id (dm/get-prop shape :id)
single? (= (count selected) 1)
editing? (= shape-id edition)
draw-path? (and (some? drawing-obj)
(cfh/path-shape? drawing-obj)
(not= :curve (:tool drawing)))
is-path-edition
(or (and single? editing?
(and (not (cfh/text-shape? shape))
(not (cfh/frame-shape? shape))))
draw-path?)
grid-edition?
(and single? editing? (ctl/grid-layout? shape))]
[:*
(when-not ^boolean hide-ui?
[:& top-toolbar {:layout layout}])
(cond
^boolean
is-read-only
[:> view-only-actions*]
^boolean
is-path-edition
[:div {:class (stl/css-case :viewport-actions-path true :viewport-actions-no-rulers (not rulers?))}
[:> path-actions* {:shape shape :edit-path edit-path}]]
grid-edition?
[:& grid-edition-actions {:shape shape}])]))
(mf/defc grid-edition-bar*
[{:keys [shape]}]
[:& grid-edition-actions {:shape shape}])

View file

@ -12,6 +12,7 @@
[app.common.data.macros :as dm]
[app.common.files.helpers :as cfh]
[app.common.geom.shapes :as gsh]
[app.common.types.path :as path]
[app.common.types.shape :as cts]
[app.common.types.shape.layout :as ctl]
[app.main.data.workspace.transforms :as dwt]
@ -26,6 +27,7 @@
[app.main.ui.workspace.shapes.text.editor :as editor-v1]
[app.main.ui.workspace.shapes.text.text-edition-outline :refer [text-edition-outline]]
[app.main.ui.workspace.shapes.text.v2-editor :as editor-v2]
[app.main.ui.workspace.top-toolbar :refer [top-toolbar*]]
[app.main.ui.workspace.viewport.actions :as actions]
[app.main.ui.workspace.viewport.comments :as comments]
[app.main.ui.workspace.viewport.debug :as wvd]
@ -44,7 +46,7 @@
[app.main.ui.workspace.viewport.selection :as selection]
[app.main.ui.workspace.viewport.snap-distances :as snap-distances]
[app.main.ui.workspace.viewport.snap-points :as snap-points]
[app.main.ui.workspace.viewport.top-bar :as top-bar]
[app.main.ui.workspace.viewport.top-bar :refer [path-edition-bar* grid-edition-bar* view-only-bar*]]
[app.main.ui.workspace.viewport.utils :as utils]
[app.main.ui.workspace.viewport.viewport-ref :refer [create-viewport-ref]]
[app.main.ui.workspace.viewport.widgets :as widgets]
@ -161,38 +163,45 @@
drawing-tool (:tool drawing)
drawing-obj (:object drawing)
selected-frames (into #{} (map :frame-id) selected-shapes)
;; Only when we have all the selected shapes in one frame
selected-frame (when (= (count selected-frames) 1) (get base-objects (first selected-frames)))
editing-shape (when edition (get base-objects edition))
edit-path-state (get edit-path edition)
edit-path-mode (get edit-path-state :edit-mode)
edit-path (get edit-path edition)
edit-path-mode (get edit-path :edit-mode)
path-editing? (some? edit-path-state)
path-drawing? (or (= edit-path-mode :draw)
(and (= :path (get drawing-obj :type))
(not= :curve drawing-tool)))
editing-shape (when edition
(get base-objects edition))
editing-shape (mf/with-memo [editing-shape path-editing? base-objects]
(if path-editing?
(path/convert-to-path editing-shape base-objects)
editing-shape))
create-comment? (= :comments drawing-tool)
drawing-path? (or (= edit-path-mode :draw)
(= :path (get drawing-obj :type)))
node-editing? (cfh/path-shape? editing-shape)
text-editing? (cfh/text-shape? editing-shape)
grid-editing? (and edition (ctl/grid-layout? base-objects edition))
mode-inspect? (= options-mode :inspect)
on-click (actions/on-click hover selected edition drawing-path? drawing-tool space? selrect z?)
on-click (actions/on-click hover selected edition path-drawing? drawing-tool space? selrect z?)
on-context-menu (actions/on-context-menu hover hover-ids read-only?)
on-double-click (actions/on-double-click hover hover-ids hover-top-frame-id drawing-path? base-objects edition drawing-tool z? read-only?)
on-double-click (actions/on-double-click hover hover-ids hover-top-frame-id path-drawing? base-objects edition drawing-tool z? read-only?)
comp-inst-ref (mf/use-ref false)
on-drag-enter (actions/on-drag-enter comp-inst-ref)
on-drag-over (actions/on-drag-over move-stream)
on-drag-end (actions/on-drag-over comp-inst-ref)
on-drop (actions/on-drop file comp-inst-ref)
on-pointer-down (actions/on-pointer-down @hover selected edition drawing-tool text-editing? node-editing? grid-editing?
drawing-path? create-comment? space? panning z? read-only?)
on-pointer-down (actions/on-pointer-down @hover selected edition drawing-tool text-editing? path-editing? grid-editing?
path-drawing? create-comment? space? panning z? read-only?)
on-pointer-up (actions/on-pointer-up disable-paste)
@ -238,14 +247,14 @@
(or drawing-obj transform))
show-selrect? (and selrect (empty? drawing) (not text-editing?))
show-measures? (and (not transform)
(not node-editing?)
(not path-editing?)
(or show-distances? mode-inspect?))
show-artboard-names? (contains? layout :display-artboard-names)
hide-ui? (contains? layout :hide-ui)
show-rulers? (and (contains? layout :rulers) (not hide-ui?))
disabled-guides? (or drawing-tool transform drawing-path? node-editing?)
disabled-guides? (or drawing-tool transform path-drawing? path-editing?)
single-select? (= (count selected-shapes) 1)
@ -340,23 +349,33 @@
(wasm.api/show-grid @hover-top-frame-id)
(wasm.api/clear-grid))))
(hooks/setup-dom-events zoom disable-paste in-viewport? read-only? drawing-tool drawing-path?)
(hooks/setup-dom-events zoom disable-paste in-viewport? read-only? drawing-tool path-drawing?)
(hooks/setup-viewport-size vport viewport-ref)
(hooks/setup-cursor cursor alt? mod? space? panning drawing-tool drawing-path? node-editing? z? read-only?)
(hooks/setup-cursor cursor alt? mod? space? panning drawing-tool path-drawing? path-editing? z? read-only?)
(hooks/setup-keyboard alt? mod? space? z? shift?)
(hooks/setup-hover-shapes page-id move-stream base-objects transform selected mod? hover measure-hover
hover-ids hover-top-frame-id @hover-disabled? focus zoom show-measures?)
(hooks/setup-shortcuts node-editing? drawing-path? text-editing? grid-editing?)
(hooks/setup-shortcuts path-editing? path-drawing? text-editing? grid-editing?)
(hooks/setup-active-frames base-objects hover-ids selected active-frames zoom transform vbox)
[:div {:class (stl/css :viewport) :style #js {"--zoom" zoom} :data-testid "viewport"}
(when (:can-edit permissions)
[:> top-bar/top-bar* {:layout layout
:selected selected-shapes
:edit-path edit-path
:drawing drawing
:edition edition
:is-read-only read-only?}])
(if read-only?
[:> view-only-bar* {}]
[:*
(when-not hide-ui?
[:> top-toolbar* {:layout layout}])
(when (and ^boolean path-editing?
^boolean single-select?)
[:> path-edition-bar* {:shape editing-shape
:edit-path-state edit-path-state
:layout layout}])
(when (and ^boolean grid-editing?
^boolean single-select?)
[:> grid-edition-bar* {:shape editing-shape}])]))
[:div {:class (stl/css :viewport-overlays)}
(when show-comments?
[:> comments/comments-layer* {:vbox vbox
@ -521,7 +540,8 @@
:on-frame-leave on-frame-leave
:on-frame-select on-frame-select}])
(when show-draw-area?
(when (and ^boolean show-draw-area?
^boolean (cts/shape? drawing-obj))
[:> drawarea/draw-area*
{:shape drawing-obj
:zoom zoom
@ -625,8 +645,9 @@
(when show-selection-handlers?
[:g.selection-handlers {:clipPath "url(#clip-handlers)"}
(when-not text-editing?
(if editing-shape
(if (and editing-shape path-editing?)
[:> path-editor* {:shape editing-shape
:state edit-path-state
:zoom zoom}]
(when selected-shapes
[:> selection/handlers*