diff --git a/CHANGES.md b/CHANGES.md index e6d837b477..ee17077ba6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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 diff --git a/common/src/app/common/files/changes_builder.cljc b/common/src/app/common/files/changes_builder.cljc index 01e7b22215..9dd4da715d 100644 --- a/common/src/app/common/files/changes_builder.cljc +++ b/common/src/app/common/files/changes_builder.cljc @@ -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))] diff --git a/common/src/app/common/logic/shapes.cljc b/common/src/app/common/logic/shapes.cljc index 29505b53f4..f26b9f95a8 100644 --- a/common/src/app/common/logic/shapes.cljc +++ b/common/src/app/common/logic/shapes.cljc @@ -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))) diff --git a/common/src/app/common/test_helpers/compositions.cljc b/common/src/app/common/test_helpers/compositions.cljc index fb30bfb595..ca6bd064ba 100644 --- a/common/src/app/common/test_helpers/compositions.cljc +++ b/common/src/app/common/test_helpers/compositions.cljc @@ -37,8 +37,6 @@ (merge shape text-params)))) - - (defn add-frame [file frame-label & {:keys [] :as params}] ;; Generated shape tree: diff --git a/common/src/app/common/test_helpers/shapes.cljc b/common/src/app/common/test_helpers/shapes.cljc index 0479efa082..f8fc5aae65 100644 --- a/common/src/app/common/test_helpers/shapes.cljc +++ b/common/src/app/common/test_helpers/shapes.cljc @@ -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) diff --git a/common/src/app/common/types/path/segment.cljc b/common/src/app/common/types/path/segment.cljc index 4f599f87ca..fbe90b7c92 100644 --- a/common/src/app/common/types/path/segment.cljc +++ b/common/src/app/common/types/path/segment.cljc @@ -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 diff --git a/common/src/app/common/types/text.cljc b/common/src/app/common/types/text.cljc index 4d097c48a8..927c2ca36f 100644 --- a/common/src/app/common/types/text.cljc +++ b/common/src/app/common/types/text.cljc @@ -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" diff --git a/common/test/common_tests/logic/token_apply_test.cljc b/common/test/common_tests/logic/token_apply_test.cljc index be7be1e5f6..8b028e5ebf 100644 --- a/common/test/common_tests/logic/token_apply_test.cljc +++ b/common/test/common_tests/logic/token_apply_test.cljc @@ -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)))) \ No newline at end of file + (t/is (= (count applied-tokens-frame') 0)) + (t/is (= (count applied-tokens-text') 0)))) \ No newline at end of file diff --git a/common/test/common_tests/types/text_test.cljc b/common/test/common_tests/types/text_test.cljc index b72f1387c3..df1cf4c680 100644 --- a/common/test/common_tests/types/text_test.cljc +++ b/common/test/common_tests/types/text_test.cljc @@ -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))) diff --git a/frontend/src/app/main/data/workspace/drawing.cljs b/frontend/src/app/main/data/workspace/drawing.cljs index 1b1d96beee..a3b77f82ed 100644 --- a/frontend/src/app/main/data/workspace/drawing.cljs +++ b/frontend/src/app/main/data/workspace/drawing.cljs @@ -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)))))) diff --git a/frontend/src/app/main/data/workspace/path.cljs b/frontend/src/app/main/data/workspace/path.cljs index 13009db9c3..0fedbd5b57 100644 --- a/frontend/src/app/main/data/workspace/path.cljs +++ b/frontend/src/app/main/data/workspace/path.cljs @@ -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) diff --git a/frontend/src/app/main/data/workspace/path/common.cljs b/frontend/src/app/main/data/workspace/path/common.cljs index 3bb60ed721..b90762790b 100644 --- a/frontend/src/app/main/data/workspace/path/common.cljs +++ b/frontend/src/app/main/data/workspace/path/common.cljs @@ -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] diff --git a/frontend/src/app/main/data/workspace/path/drawing.cljs b/frontend/src/app/main/data/workspace/path/drawing.cljs index 7d9eb3cdb1..ce2f2ab240 100644 --- a/frontend/src/app/main/data/workspace/path/drawing.cljs +++ b/frontend/src/app/main/data/workspace/path/drawing.cljs @@ -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))))))) diff --git a/frontend/src/app/main/data/workspace/path/helpers.cljs b/frontend/src/app/main/data/workspace/path/helpers.cljs index 6c697a87d3..c904e388f5 100644 --- a/frontend/src/app/main/data/workspace/path/helpers.cljs +++ b/frontend/src/app/main/data/workspace/path/helpers.cljs @@ -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." diff --git a/frontend/src/app/main/data/workspace/path/state.cljs b/frontend/src/app/main/data/workspace/path/state.cljs index efe34a0044..6a89ed127a 100644 --- a/frontend/src/app/main/data/workspace/path/state.cljs +++ b/frontend/src/app/main/data/workspace/path/state.cljs @@ -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] diff --git a/frontend/src/app/main/data/workspace/viewport.cljs b/frontend/src/app/main/data/workspace/viewport.cljs index 97e88bc29a..d9f523003a 100644 --- a/frontend/src/app/main/data/workspace/viewport.cljs +++ b/frontend/src/app/main/data/workspace/viewport.cljs @@ -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) diff --git a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs index dede7d3315..f6d308183b 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs @@ -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) diff --git a/frontend/src/app/main/ui/workspace/top_toolbar.cljs b/frontend/src/app/main/ui/workspace/top_toolbar.cljs index a904761f69..5d8abacdad 100644 --- a/frontend/src/app/main/ui/workspace/top_toolbar.cljs +++ b/frontend/src/app/main/ui/workspace/top_toolbar.cljs @@ -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"} diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 0e225599d4..87f4504c84 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -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* diff --git a/frontend/src/app/main/ui/workspace/viewport/drawarea.cljs b/frontend/src/app/main/ui/workspace/viewport/drawarea.cljs index 725d2ec8e6..c7ccc2c72c 100644 --- a/frontend/src/app/main/ui/workspace/viewport/drawarea.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/drawarea.cljs @@ -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])]) diff --git a/frontend/src/app/main/ui/workspace/viewport/path_actions.cljs b/frontend/src/app/main/ui/workspace/viewport/path_actions.cljs index 0f9553d652..5930c74c12 100644 --- a/frontend/src/app/main/ui/workspace/viewport/path_actions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/path_actions.cljs @@ -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) diff --git a/frontend/src/app/main/ui/workspace/viewport/top_bar.cljs b/frontend/src/app/main/ui/workspace/viewport/top_bar.cljs index 570eaad0b0..b9f4f69cb4 100644 --- a/frontend/src/app/main/ui/workspace/viewport/top_bar.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/top_bar.cljs @@ -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}]) diff --git a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs index 8b1eef6307..ab5c05b849 100644 --- a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs +++ b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs @@ -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*