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 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 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 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 ## 2.7.2

View File

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

View File

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

View File

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

View File

@ -11,6 +11,7 @@
[app.common.files.helpers :as cfh] [app.common.files.helpers :as cfh]
[app.common.test-helpers.files :as thf] [app.common.test-helpers.files :as thf]
[app.common.test-helpers.ids-map :as thi] [app.common.test-helpers.ids-map :as thi]
[app.common.text :as txt]
[app.common.types.color :as ctc] [app.common.types.color :as ctc]
[app.common.types.container :as ctn] [app.common.types.container :as ctn]
[app.common.types.pages-list :as ctpl] [app.common.types.pages-list :as ctpl]
@ -81,6 +82,21 @@
(:id page) (:id page)
#(ctst/set-shape % (ctn/set-shape-attr shape attr val))))))) #(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 (defn sample-library-color
[label & {:keys [name path color opacity gradient image]}] [label & {:keys [name path color opacity gradient image]}]
(-> {:id (thi/new-id! label) (-> {: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 "Given a content and a set of points return all the segments in the path
that uses the points" that uses the points"
[content points] [content points]
(assert (impl/path-data? content) "expected path data instance")
(let [point-set (set points)] (let [point-set (set points)]
(loop [result (transient []) (loop [result (transient [])
prev-point nil prev-point nil

View File

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

View File

@ -6,6 +6,7 @@
(ns common-tests.logic.token-apply-test (ns common-tests.logic.token-apply-test
(:require (:require
[app.common.data :as d]
[app.common.files.changes-builder :as pcb] [app.common.files.changes-builder :as pcb]
[app.common.logic.shapes :as cls] [app.common.logic.shapes :as cls]
[app.common.test-helpers.compositions :as tho] [app.common.test-helpers.compositions :as tho]
@ -13,6 +14,7 @@
[app.common.test-helpers.ids-map :as thi] [app.common.test-helpers.ids-map :as thi]
[app.common.test-helpers.shapes :as ths] [app.common.test-helpers.shapes :as ths]
[app.common.test-helpers.tokens :as tht] [app.common.test-helpers.tokens :as tht]
[app.common.text :as txt]
[app.common.types.container :as ctn] [app.common.types.container :as ctn]
[app.common.types.token :as cto] [app.common.types.token :as cto]
[app.common.types.tokens-lib :as ctob] [app.common.types.tokens-lib :as ctob]
@ -53,7 +55,8 @@
(ctob/make-token :name "token-dimensions" (ctob/make-token :name "token-dimensions"
:type :dimensions :type :dimensions
:value 100)))) :value 100))))
(tho/add-frame :frame1))) (tho/add-frame :frame1)
(tho/add-text :text1 "Hello World")))
(defn- apply-all-tokens (defn- apply-all-tokens
[file] [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-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" [:stroke-color] [:stroke-color] "#00ff00")
(tht/apply-token-to-shape :frame1 "token-color" [:fill] [:fill] "#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 (t/deftest apply-tokens-to-shape
(let [;; ==== Setup (let [;; ==== Setup
@ -171,6 +175,7 @@
(apply-all-tokens)) (apply-all-tokens))
page (thf/current-page file) page (thf/current-page file)
frame1 (ths/get-shape file :frame1) frame1 (ths/get-shape file :frame1)
text1 (ths/get-shape file :text1)
;; ==== Action ;; ==== Action
changes (-> (-> (pcb/empty-changes nil) changes (-> (-> (pcb/empty-changes nil)
@ -190,13 +195,25 @@
(ctn/set-shape-attr :width 0) (ctn/set-shape-attr :width 0)
(ctn/set-shape-attr :height 0))) (ctn/set-shape-attr :height 0)))
(:objects page) (: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) file' (thf/apply-changes file changes)
;; ==== Get ;; ==== Get
frame1' (ths/get-shape file' :frame1) frame1' (ths/get-shape file' :frame1)
applied-tokens' (:applied-tokens frame1')] text1' (ths/get-shape file' :text1)
applied-tokens-frame' (:applied-tokens frame1')
applied-tokens-text' (:applied-tokens text1')]
;; ==== Check ;; ==== 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))) #(conj % line)))
(t/deftest test-get-diff-type (t/deftest test-get-diff-type
(let [diff-text (cttx/get-diff-type content-base content-changed-text) (let [diff-text (cttx/get-diff-type content-base content-changed-text)
diff-attr (cttx/get-diff-type content-base content-changed-attr) diff-attr (cttx/get-diff-type content-base content-changed-attr)
diff-both (cttx/get-diff-type content-base content-changed-both) diff-both (cttx/get-diff-type content-base content-changed-both)
diff-structure (cttx/get-diff-type content-base content-changed-structure) 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)] 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-text} diff-text))
(t/is (= #{:text-content-attribute} diff-attr)) (t/is (= #{:text-content-attribute} diff-attr))
(t/is (= #{:text-content-text :text-content-attribute} diff-both)) (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/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/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-text)))
(t/is (true? (cttx/equal-structure? content-base content-changed-attr))) (t/is (true? (cttx/equal-structure? content-base content-changed-attr)))

View File

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

View File

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

View File

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

View File

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

View File

@ -10,22 +10,7 @@
[app.common.math :as mth] [app.common.math :as mth]
[app.common.types.path :as path] [app.common.types.path :as path]
[app.common.types.path.helpers :as path.helpers] [app.common.types.path.helpers :as path.helpers]
[app.common.types.path.segment :as path.segment] [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)))))
(defn append-node (defn append-node
"Creates a new node in the path. Usually used when drawing." "Creates a new node in the path. Usually used when drawing."

View File

@ -7,42 +7,8 @@
(ns app.main.data.workspace.path.state (ns app.main.data.workspace.path.state
(:require (:require
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.files.helpers :as cph]
[app.common.types.path.shape-to-path :as stp])) [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 (defn get-path-id
"Retrieves the currently editing path id" "Retrieves the currently editing path id"
[state] [state]

View File

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

View File

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

View File

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

View File

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

View File

@ -7,13 +7,19 @@
(ns app.main.ui.workspace.viewport.drawarea (ns app.main.ui.workspace.viewport.drawarea
"Drawing components." "Drawing components."
(:require (:require
[app.common.data.macros :as dm]
[app.common.math :as mth] [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.shapes.path :refer [path-shape]]
[app.main.ui.workspace.shapes :as shapes] [app.main.ui.workspace.shapes :as shapes]
[app.main.ui.workspace.shapes.path.editor :refer [path-editor*]] [app.main.ui.workspace.shapes.path.editor :refer [path-editor*]]
[okulary.core :as l]
[rumext.v2 :as mf])) [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/defc generic-draw-area*
{::mf/private true} {::mf/private true}
[{:keys [shape zoom]}] [{:keys [shape zoom]}]
@ -29,17 +35,32 @@
:fill "none" :fill "none"
:stroke-width (/ 1 zoom)}}]))) :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* (mf/defc draw-area*
[{:keys [shape zoom tool] :as props}] [{: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. (case tool
(when (cts/shape? shape) :path [:> path-draw-area* props]
[:g.draw-area :curve [:& path-shape {:shape shape :zoom zoom}]
[:g {:style {:pointer-events "none"}} #_:default [:> generic-draw-area* props])])
[:& shapes/shape-wrapper {:shape shape}]]
(case tool
:path [:> path-editor* props]
:curve [:& path-shape {:shape shape :zoom zoom}]
#_:default [:> generic-draw-area* props])]))

View File

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

View File

@ -7,19 +7,20 @@
(ns app.main.ui.workspace.viewport.top-bar (ns app.main.ui.workspace.viewport.top-bar
(:require-macros [app.main.style :as stl]) (:require-macros [app.main.style :as stl])
(:require (: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 :as dw]
[app.main.data.workspace.common :as dwc] [app.main.data.workspace.common :as dwc]
[app.main.store :as st] [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.grid-layout-editor :refer [grid-edition-actions]]
[app.main.ui.workspace.viewport.path-actions :refer [path-actions*]] [app.main.ui.workspace.viewport.path-actions :refer [path-actions*]]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
[rumext.v2 :as mf])) [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 (let [handle-close-view-mode
(mf/use-fn (mf/use-fn
@ -37,44 +38,15 @@
:on-click handle-close-view-mode} :on-click handle-close-view-mode}
(tr "workspace.top-bar.read-only.done")]]])) (tr "workspace.top-bar.read-only.done")]]]))
(mf/defc top-bar* (mf/defc path-edition-bar*
[{:keys [layout drawing is-read-only edition selected edit-path]}] [{:keys [layout edit-path-state shape]}]
(let [rulers? (contains? layout :rulers) (let [rulers? (contains? layout :rulers)
hide-ui? (contains? layout :hide-ui) 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) (mf/defc grid-edition-bar*
shape (or drawing-obj (-> selected first)) [{:keys [shape]}]
shape-id (dm/get-prop shape :id) [:& grid-edition-actions {:shape shape}])
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}])]))

View File

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