From f1a557c372a596b0443c20c16627beeb1bf584e1 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 29 Apr 2025 11:19:53 +0200 Subject: [PATCH] :sparkles: Add minor performance enhacements to viewport top-bar --- .../src/app/main/data/workspace/edition.cljs | 8 +-- .../app/main/data/workspace/path/edition.cljs | 21 +++---- .../app/main/ui/workspace/shapes/path.cljs | 12 +++- .../main/ui/workspace/shapes/path/common.cljs | 43 ------------- .../main/ui/workspace/shapes/path/editor.cljs | 50 ++++++++++------ .../src/app/main/ui/workspace/viewport.cljs | 22 ++++--- .../main/ui/workspace/viewport/actions.cljs | 5 +- .../ui/workspace/viewport/path_actions.cljs | 27 ++++----- .../main/ui/workspace/viewport/top_bar.cljs | 60 +++++++++---------- .../app/main/ui/workspace/viewport_wasm.cljs | 21 +++++-- 10 files changed, 131 insertions(+), 138 deletions(-) delete mode 100644 frontend/src/app/main/ui/workspace/shapes/path/common.cljs diff --git a/frontend/src/app/main/data/workspace/edition.cljs b/frontend/src/app/main/data/workspace/edition.cljs index e4fdf6cce..b3aec4485 100644 --- a/frontend/src/app/main/data/workspace/edition.cljs +++ b/frontend/src/app/main/data/workspace/edition.cljs @@ -6,7 +6,6 @@ (ns app.main.data.workspace.edition (:require - [app.common.data.macros :as dm] [app.main.data.helpers :as dsh] [app.main.data.workspace.path.common :as dwpc] [beicon.v2.core :as rx] @@ -17,8 +16,10 @@ (declare clear-edition-mode) (defn start-edition-mode + "Mark a shape in edition mode" [id] - (dm/assert! (uuid? id)) + (assert (uuid? id) "expected valid uuid for `id`") + (ptk/reify ::start-edition-mode ptk/UpdateEvent (update [_ state] @@ -26,8 +27,7 @@ ;; Can only edit objects that exist (if (contains? objects id) (-> state - (assoc-in [:workspace-local :selected] #{id}) - (assoc-in [:workspace-local :edition] id) + (update :workspace-local assoc :edition id) (dissoc :workspace-grid-edition)) state))) diff --git a/frontend/src/app/main/data/workspace/path/edition.cljs b/frontend/src/app/main/data/workspace/path/edition.cljs index fc041582f..883994770 100644 --- a/frontend/src/app/main/data/workspace/path/edition.cljs +++ b/frontend/src/app/main/data/workspace/path/edition.cljs @@ -17,7 +17,6 @@ [app.main.data.helpers :as dsh] [app.main.data.workspace.edition :as dwe] [app.main.data.workspace.path.changes :as changes] - [app.main.data.workspace.path.drawing :as drawing] [app.main.data.workspace.path.helpers :as helpers] [app.main.data.workspace.path.selection :as selection] [app.main.data.workspace.path.state :as st] @@ -309,23 +308,21 @@ (assoc-in [:workspace-local :edit-path id] {:edit-mode :move :selected #{} :snap-toggled false}) - (and (some? edit-path) (= :move (:edit-mode edit-path))) (assoc-in [:workspace-local :edit-path id :edit-mode] :draw)))) ptk/WatchEvent - (watch [_ state stream] - (let [mode (dm/get-in state [:workspace-local :edit-path id :edit-mode]) - stopper (->> stream - (rx/filter #(or - (= (ptk/type %) ::dwe/clear-edition-mode) - (= (ptk/type %) ::start-path-edit)))) - interrupt (->> stream (rx/filter #(= % :interrupt)) (rx/take 1))] + (watch [_ _ stream] + (let [stopper (->> stream + (rx/filter #(let [type (ptk/type %)] + (= type ::dwe/clear-edition-mode) + (= type ::start-path-edit))))] (rx/concat - (rx/of (undo/start-path-undo) - (drawing/change-edit-mode mode)) - (->> interrupt + (rx/of (undo/start-path-undo)) + (->> stream + (rx/filter #(= % :interrupt)) + (rx/take 1) (rx/map #(stop-path-edit id)) (rx/take-until stopper))))))) diff --git a/frontend/src/app/main/ui/workspace/shapes/path.cljs b/frontend/src/app/main/ui/workspace/shapes/path.cljs index caa5a2e2f..0f8da115b 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path.cljs @@ -12,9 +12,15 @@ [app.main.ui.shapes.path :as path] [app.main.ui.shapes.shape :refer [shape-container]] [app.main.ui.workspace.shapes.debug :as wsd] - [app.main.ui.workspace.shapes.path.common :as pc] + [okulary.core :as l] [rumext.v2 :as mf])) +(defn- make-content-modifiers-ref + [id] + (l/derived (fn [local] + (dm/get-in local [:edit-path id :content-modifiers])) + refs/workspace-local)) + (defn- apply-content-modifiers [shape content-modifiers] (let [shape (update shape :content types.path/apply-content-modifiers content-modifiers)] @@ -26,11 +32,13 @@ (let [shape-id (dm/get-prop shape :id) content-modifiers-ref - (pc/make-content-modifiers-ref shape-id) + (mf/with-memo [shape-id] + (make-content-modifiers-ref shape-id)) content-modifiers (mf/deref content-modifiers-ref) + ;; FIXME: this should be provided by react context instead of using refs editing-id (mf/deref refs/selected-edition) diff --git a/frontend/src/app/main/ui/workspace/shapes/path/common.cljs b/frontend/src/app/main/ui/workspace/shapes/path/common.cljs deleted file mode 100644 index e6dae1f62..000000000 --- a/frontend/src/app/main/ui/workspace/shapes/path/common.cljs +++ /dev/null @@ -1,43 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) KALEIDOS INC - -(ns app.main.ui.workspace.shapes.path.common - (:require - [app.common.data.macros :as dm] - [app.main.data.workspace.path.state :as pst] - [app.main.refs :as refs] - [app.main.store :as st] - [okulary.core :as l] - [rumext.v2 :as mf])) - -(def accent-color "var(--color-accent-tertiary)") -(def secondary-color "var(--color-accent-quaternary)") -(def black-color "var(--app-black)") -(def white-color "var(--app-white)") -(def gray-color "var(--df-secondary)") - -(def current-edit-path-ref - (l/derived - (fn [state] - (let [id (pst/get-path-id state)] - (dm/get-in state [:workspace-local :edit-path id]))) - st/state)) - -(defn make-edit-path-ref [id] - (mf/use-memo - (mf/deps id) - (let [selfn #(get-in % [:edit-path id])] - #(l/derived selfn refs/workspace-local)))) - -(defn content-modifiers-ref - [id] - (l/derived #(get-in % [:edit-path id :content-modifiers]) refs/workspace-local)) - -(defn make-content-modifiers-ref [id] - (mf/use-memo - (mf/deps id) - #(content-modifiers-ref id))) - 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 a19f26581..b166f7db1 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs @@ -13,16 +13,17 @@ [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] [app.main.ui.css-cursors :as cur] [app.main.ui.hooks :as hooks] - [app.main.ui.workspace.shapes.path.common :as pc] [app.util.dom :as dom] [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) @@ -36,6 +37,12 @@ (def path-preview-dasharray 4) (def path-snap-stroke-width 1) +(def accent-color "var(--color-accent-tertiary)") +(def secondary-color "var(--color-accent-quaternary)") +(def black-color "var(--app-black)") +(def white-color "var(--app-white)") +(def gray-color "var(--df-secondary)") + (mf/defc path-point* {::mf/private true} [{:keys [position zoom edit-mode is-hover is-selected is-preview is-start-path is-last is-new is-curve]}] @@ -97,11 +104,11 @@ (/ point-radius zoom) (/ point-radius-selected zoom)) :style {:stroke-width (/ point-radius-stroke-width zoom) - :stroke (cond ^boolean is-active pc/black-color - ^boolean is-preview pc/secondary-color - :else pc/accent-color) - :fill (cond is-selected pc/accent-color - :else pc/white-color)}}] + :stroke (cond ^boolean is-active black-color + ^boolean is-preview secondary-color + :else accent-color) + :fill (cond is-selected accent-color + :else white-color)}}] [:circle {:cx x :cy y :r (/ point-radius-active-area zoom) @@ -155,8 +162,8 @@ :x2 x :y2 y :style {:stroke (if ^boolean is-hover - pc/black-color - pc/gray-color) + black-color + gray-color) :stroke-width (/ point-radius-stroke-width zoom)}}] (when ^boolean snap-angle @@ -165,7 +172,7 @@ :y1 (:y point) :x2 x :y2 y - :style {:stroke pc/secondary-color + :style {:stroke secondary-color :stroke-width (/ point-radius-stroke-width zoom)}}]) [:rect @@ -175,10 +182,10 @@ :height (/ handler-side zoom) :style {:stroke-width (/ handler-stroke-width zoom) - :stroke (cond ^boolean is-active pc/black-color - :else pc/accent-color) - :fill (cond ^boolean is-selected pc/accent-color - :else pc/white-color)}}] + :stroke (cond ^boolean is-active black-color + :else accent-color) + :fill (cond ^boolean is-selected accent-color + :else white-color)}}] [:circle {:cx x :cy y :r (/ point-radius-active-area zoom) @@ -209,7 +216,7 @@ [:g.preview {:style {:pointer-events "none"}} (when (some? path) [:path {:style {:fill "none" - :stroke pc/black-color + :stroke black-color :stroke-width (/ handler-stroke-width zoom) :stroke-dasharray (/ path-preview-dasharray zoom)} :d (str path)}]) @@ -238,7 +245,7 @@ :y1 (:y from) :x2 (:x to) :y2 (:y to) - :style {:stroke pc/secondary-color + :style {:stroke secondary-color :stroke-width (/ path-snap-stroke-width zoom)}}])])) (defn- matching-handler? [content node handlers] @@ -253,12 +260,19 @@ 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 [editor-ref (mf/use-ref nil) - edit-path-ref (pc/make-edit-path-ref (:id shape)) + (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) {:keys [edit-mode drag-handler @@ -333,7 +347,7 @@ [:g.path-editor {:ref editor-ref} [:path {:d (.toString content) :style {:fill "none" - :stroke pc/accent-color + :stroke accent-color :strokeWidth (/ 1 zoom)}}] (when (and preview (not drag-handler)) [:> path-preview* {:segment preview diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 760d1a23a..24802656e 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -164,15 +164,18 @@ editing-shape (when edition (get base-objects edition)) - create-comment? (= :comments drawing-tool) - drawing-path? (or (and edition (= :draw (get-in edit-path [edition :edit-mode]))) - (and (some? drawing-obj) (= :path (:type drawing-obj)))) - node-editing? (and edition (= :path (get-in base-objects [edition :type]))) - text-editing? (and edition (= :text (get-in base-objects [edition :type]))) + edit-path (get edit-path edition) + edit-path-mode (get edit-path :edit-mode) + 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) + mode-inspect? (= options-mode :inspect) on-click (actions/on-click hover selected edition drawing-path? drawing-tool space? selrect z?) on-context-menu (actions/on-context-menu hover hover-ids read-only?) @@ -282,7 +285,12 @@ [:div {:class (stl/css :viewport) :style #js {"--zoom" zoom} :data-testid "viewport"} (when (:can-edit permissions) - [:& top-bar/top-bar {:layout layout}]) + [:> top-bar/top-bar* {:layout layout + :selected selected-shapes + :edit-path edit-path + :drawing drawing + :edition edition + :is-read-only read-only?}]) [: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 diff --git a/frontend/src/app/main/ui/workspace/viewport/actions.cljs b/frontend/src/app/main/ui/workspace/viewport/actions.cljs index 42d23a2bf..6ff1e381e 100644 --- a/frontend/src/app/main/ui/workspace/viewport/actions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/actions.cljs @@ -227,8 +227,9 @@ (dw/start-editing-selected)) (some? selected-shape) - (do (reset! hover selected-shape) - (st/emit! (dw/select-shape (:id selected-shape)))) + (do + (reset! hover selected-shape) + (st/emit! (dw/select-shape (:id selected-shape)))) (and (not selected-shape) (some? grid-layout-id) (not read-only?)) (st/emit! (dw/start-edition-mode grid-layout-id))))))))))) 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 fd6340bbf..b79a6ff61 100644 --- a/frontend/src/app/main/ui/workspace/viewport/path_actions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/path_actions.cljs @@ -12,7 +12,6 @@ [app.main.data.workspace.path.shortcuts :as sc] [app.main.store :as st] [app.main.ui.icons :as i] - [app.main.ui.workspace.shapes.path.common :as pc] [app.util.i18n :as i18n :refer [tr]] [rumext.v2 :as mf])) @@ -46,7 +45,6 @@ (def ^:private snap-nodes-icon (i/icon-xref :snap-nodes (stl/css :snap-nodes-icon :pathbar-icon))) - (defn check-enabled [content selected-points] (let [segments (path.segm/get-segments-with-points content selected-points) num-segments (count segments) @@ -67,9 +65,10 @@ :join-nodes (and points-selected? (>= num-points 2) (< num-segments max-segments)) :separate-nodes segments-selected?})) +(mf/defc path-actions* + [{:keys [shape edit-path]}] + (let [{:keys [edit-mode selected-points snap-toggled]} edit-path -(mf/defc path-actions [{:keys [shape]}] - (let [{:keys [edit-mode selected-points snap-toggled] :as all} (mf/deref pc/current-edit-path-ref) content (:content shape) enabled-buttons @@ -78,66 +77,66 @@ #(check-enabled content selected-points)) on-select-draw-mode - (mf/use-callback + (mf/use-fn (fn [_] (st/emit! (drp/change-edit-mode :draw)))) on-select-edit-mode - (mf/use-callback + (mf/use-fn (fn [_] (st/emit! (drp/change-edit-mode :move)))) on-add-node - (mf/use-callback + (mf/use-fn (mf/deps (:add-node enabled-buttons)) (fn [_] (when (:add-node enabled-buttons) (st/emit! (drp/add-node))))) on-remove-node - (mf/use-callback + (mf/use-fn (mf/deps (:remove-node enabled-buttons)) (fn [_] (when (:remove-node enabled-buttons) (st/emit! (drp/remove-node))))) on-merge-nodes - (mf/use-callback + (mf/use-fn (mf/deps (:merge-nodes enabled-buttons)) (fn [_] (when (:merge-nodes enabled-buttons) (st/emit! (drp/merge-nodes))))) on-join-nodes - (mf/use-callback + (mf/use-fn (mf/deps (:join-nodes enabled-buttons)) (fn [_] (when (:join-nodes enabled-buttons) (st/emit! (drp/join-nodes))))) on-separate-nodes - (mf/use-callback + (mf/use-fn (mf/deps (:separate-nodes enabled-buttons)) (fn [_] (when (:separate-nodes enabled-buttons) (st/emit! (drp/separate-nodes))))) on-make-corner - (mf/use-callback + (mf/use-fn (mf/deps (:make-corner enabled-buttons)) (fn [_] (when (:make-corner enabled-buttons) (st/emit! (drp/make-corner))))) on-make-curve - (mf/use-callback + (mf/use-fn (mf/deps (:make-curve enabled-buttons)) (fn [_] (when (:make-curve enabled-buttons) (st/emit! (drp/make-curve))))) on-toggle-snap - (mf/use-callback + (mf/use-fn (fn [_] (st/emit! (drp/toggle-snap))))] 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 6767735db..570eaad0b 100644 --- a/frontend/src/app/main/ui/workspace/viewport/top_bar.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/top_bar.cljs @@ -7,23 +7,22 @@ (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.refs :as refs] [app.main.store :as st] - [app.main.ui.context :as ctx] [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.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 +(mf/defc view-only-actions* [] (let [handle-close-view-mode - (mf/use-callback + (mf/use-fn (fn [] (st/emit! :interrupt (dw/set-options-mode :design) @@ -38,43 +37,44 @@ :on-click handle-close-view-mode} (tr "workspace.top-bar.read-only.done")]]])) -(mf/defc top-bar - {::mf/wrap [mf/memo]} - [{:keys [layout]}] - (let [edition (mf/deref refs/selected-edition) - selected (mf/deref refs/selected-objects) - drawing (mf/deref refs/workspace-drawing) - rulers? (mf/deref refs/rulers?) - drawing-obj (:object drawing) +(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) + + drawing-obj (get drawing :object) shape (or drawing-obj (-> selected first)) + shape-id (dm/get-prop shape :id) - single? (= (count selected) 1) - editing? (= (:id shape) edition) - draw-path? (and (some? drawing-obj) - (cfh/path-shape? drawing-obj) - (not= :curve (:tool drawing))) + single? (= (count selected) 1) + editing? (= shape-id edition) - workspace-read-only? (mf/use-ctx ctx/workspace-read-only?) - hide-ui? (:hide-ui layout) + draw-path? (and (some? drawing-obj) + (cfh/path-shape? drawing-obj) + (not= :curve (:tool drawing))) - path-edition? (or (and single? editing? - (and (not (cfh/text-shape? shape)) - (not (cfh/frame-shape? shape)))) - draw-path?) + 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))] + grid-edition? + (and single? editing? (ctl/grid-layout? shape))] [:* - (when-not hide-ui? + (when-not ^boolean hide-ui? [:& top-toolbar {:layout layout}]) (cond - workspace-read-only? - [:& view-only-actions] + ^boolean + is-read-only + [:> view-only-actions*] - path-edition? + ^boolean + is-path-edition [:div {:class (stl/css-case :viewport-actions-path true :viewport-actions-no-rulers (not rulers?))} - [:& path-actions {:shape shape}]] + [:> path-actions* {:shape shape :edit-path edit-path}]] grid-edition? [:& 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 011a8c576..da5263e1b 100644 --- a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs +++ b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs @@ -173,14 +173,18 @@ editing-shape (when edition (get base-objects edition)) + edit-path (get edit-path edition) + edit-path-mode (get edit-path :edit-mode) + create-comment? (= :comments drawing-tool) - drawing-path? (or (and edition (= :draw (get-in edit-path [edition :edit-mode]))) - (and (some? drawing-obj) (= :path (:type drawing-obj)))) - node-editing? (and edition (= :path (get-in base-objects [edition :type]))) - text-editing? (and edition (= :text (get-in base-objects [edition :type]))) + 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) + mode-inspect? (= options-mode :inspect) on-click (actions/on-click hover selected edition drawing-path? drawing-tool space? selrect z?) on-context-menu (actions/on-context-menu hover hover-ids read-only?) @@ -338,7 +342,12 @@ [:div {:class (stl/css :viewport) :style #js {"--zoom" zoom} :data-testid "viewport"} (when (:can-edit permissions) - [:& top-bar/top-bar {:layout layout}]) + [:> top-bar/top-bar* {:layout layout + :selected selected-shapes + :edit-path edit-path + :drawing drawing + :edition edition + :is-read-only read-only?}]) [:div {:class (stl/css :viewport-overlays)} (when show-comments? [:> comments/comments-layer* {:vbox vbox