From 95cfbf5f7c4f21766dbfb87a5ff21b02fd7c83e8 Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Tue, 14 Jul 2026 12:42:19 +0200 Subject: [PATCH] :sparkles: Improve workspace debug sidebar workflow (#10678) --- frontend/src/app/main/data/workspace.cljs | 1 + .../app/main/data/workspace/clipboard.cljs | 9 ++++ .../src/app/main/data/workspace/layout.cljs | 2 +- .../app/main/ui/workspace/context_menu.cljs | 6 +++ .../src/app/main/ui/workspace/sidebar.cljs | 26 ++++------- .../app/main/ui/workspace/sidebar/debug.cljs | 17 ++++++- .../workspace/sidebar/debug_shape_info.cljs | 5 -- .../main/ui/workspace/sidebar/layer_name.cljs | 6 ++- .../main/ui/workspace/sidebar/options.cljs | 46 +++++++++++++++---- frontend/src/app/util/debug.cljs | 21 +++++++-- frontend/translations/en.po | 3 ++ frontend/translations/es.po | 5 +- 12 files changed, 109 insertions(+), 38 deletions(-) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 67621f3754..fb6245c31c 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -1568,6 +1568,7 @@ (dm/export dwcp/paste-shapes) (dm/export dwcp/paste-data-valid?) (dm/export dwcp/copy-link-to-clipboard) +(dm/export dwcp/copy-id-to-clipboard) (dm/export dwcp/copy-as-image) ;; Drawing diff --git a/frontend/src/app/main/data/workspace/clipboard.cljs b/frontend/src/app/main/data/workspace/clipboard.cljs index c87a5707f9..25c51f2257 100644 --- a/frontend/src/app/main/data/workspace/clipboard.cljs +++ b/frontend/src/app/main/data/workspace/clipboard.cljs @@ -1138,6 +1138,15 @@ (watch [_ _ _] (clipboard/to-clipboard (rt/get-current-href))))) +(defn copy-id-to-clipboard + [id] + (ptk/reify ::copy-id-to-clipboard + ptk/WatchEvent + (watch [_ _ _] + (->> (rx/from (clipboard/to-clipboard id)) + (rx/map (fn [_] + (ntf/info "The id has been copied to the clipboard"))))))) + (defn copy-as-image [] (ptk/reify ::copy-as-image diff --git a/frontend/src/app/main/data/workspace/layout.cljs b/frontend/src/app/main/data/workspace/layout.cljs index 2f04bcfa53..fa8208c0ff 100644 --- a/frontend/src/app/main/data/workspace/layout.cljs +++ b/frontend/src/app/main/data/workspace/layout.cljs @@ -53,7 +53,7 @@ :add #{:tokens}}}) (def valid-options-mode - #{:design :prototype :inspect}) + #{:design :prototype :inspect :debug}) (def default-layout #{:sitemap diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs index 6931253cd4..6d1384790c 100644 --- a/frontend/src/app/main/ui/workspace/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/context_menu.cljs @@ -37,6 +37,7 @@ [app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i] [app.main.ui.workspace.sidebar.assets.common :as cmm] [app.util.clipboard :as clipboard] + [app.util.debug :as dbg] [app.util.dom :as dom] [app.util.i18n :refer [tr] :as i18n] [app.util.shape-icon :as usi] @@ -152,6 +153,7 @@ do-copy #(st/emit! (dw/copy-selected)) do-copy-link #(st/emit! (dw/copy-link-to-clipboard)) + do-copy-id #(st/emit! (dw/copy-id-to-clipboard (-> shapes first :id str))) do-cut #(st/emit! (dw/copy-selected) (dw/delete-selected)) @@ -200,6 +202,10 @@ (reset! enabled-paste-props* false))))))] [:* + (when (and (not multiple?) + (dbg/enabled? :show-ids)) + [:> menu-entry* {:title (tr "workspace.shape.menu.copy-id") + :on-click do-copy-id}]) [:> menu-entry* {:title (tr "workspace.shape.menu.copy") :shortcut (sc/get-tooltip :copy) :on-click do-copy}] diff --git a/frontend/src/app/main/ui/workspace/sidebar.cljs b/frontend/src/app/main/ui/workspace/sidebar.cljs index 6e17e7f0c7..c918288270 100644 --- a/frontend/src/app/main/ui/workspace/sidebar.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar.cljs @@ -33,7 +33,6 @@ [app.main.ui.workspace.right-header :refer [right-header*]] [app.main.ui.workspace.sidebar.assets :refer [assets-toolbox*]] [app.main.ui.workspace.sidebar.debug :refer [debug-panel*]] - [app.main.ui.workspace.sidebar.debug-shape-info :refer [debug-shape-info*]] [app.main.ui.workspace.sidebar.history :refer [history-toolbox*]] [app.main.ui.workspace.sidebar.layers :refer [layers-toolbox*]] [app.main.ui.workspace.sidebar.options :refer [options-toolbox*]] @@ -41,7 +40,6 @@ [app.main.ui.workspace.sidebar.sitemap :refer [sitemap*]] [app.main.ui.workspace.sidebar.versions :refer [versions-toolbox*]] [app.main.ui.workspace.tokens.sidebar :refer [tokens-sidebar-tab*]] - [app.util.debug :as dbg] [app.util.dom :as dom] [app.util.i18n :refer [tr]] [rumext.v2 :as mf])) @@ -285,18 +283,16 @@ (let [is-comments? (= drawing-tool :comments) is-history? (contains? layout :document-history) is-inspect? (= section :inspect) - - dbg-shape-panel? (dbg/enabled? :shape-panel) + is-debug? (= section :debug) current-section* (mf/use-state :info) current-section (deref current-section*) can-be-expanded? - (or dbg-shape-panel? - (and (not is-comments?) - (not is-history?) - is-inspect? - (= current-section :code))) + (and (not is-comments?) + (not is-history?) + is-inspect? + (= current-section :code)) {on-pointer-down :on-pointer-down on-lost-pointer-capture :on-lost-pointer-capture @@ -325,15 +321,16 @@ [:aside {:class (stl/css-case :right-settings-bar true :not-expand (not can-be-expanded?) - :expanded (> width right-sidebar-default-width)) + :expanded (or is-debug? (> width right-sidebar-default-width))) :id "right-sidebar-aside" :data-testid "right-sidebar" :data-size (str width) :on-context-menu dom/prevent-default-context-menu - :style {:--right-sidebar-width (if can-be-expanded? - (dm/str width "px") - (dm/str right-sidebar-default-width "px"))}} + :style {:--right-sidebar-width (cond + is-debug? (dm/str right-sidebar-default-max-width "px") + can-be-expanded? (dm/str width "px") + :else (dm/str right-sidebar-default-width "px"))}} (when can-be-expanded? [:div {:class (stl/css :resize-area) @@ -348,9 +345,6 @@ [:div {:class (stl/css :settings-bar-inside)} (cond - dbg-shape-panel? - [:> debug-shape-info*] - is-comments? [:> comments-sidebar* {}] diff --git a/frontend/src/app/main/ui/workspace/sidebar/debug.cljs b/frontend/src/app/main/ui/workspace/sidebar/debug.cljs index 5fbe361490..9e46f48c41 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/debug.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/debug.cljs @@ -18,15 +18,28 @@ [app.util.i18n :refer [tr]] [rumext.v2 :as mf])) +(def ^:private no-reload-options + "Debug options that don't require a page reload to take effect. + These options are handled reactively via okulary subscriptions." + #{:shape-panel + :show-ids + :show-touched}) + (mf/defc debug-panel* [{:keys [class]}] - (let [on-toggle-enabled + (let [;; dbg/state is an okulary atom; deref'ing it makes this component + ;; re-render whenever any debug option is toggled, so checkboxes + ;; reflect the current state without a page reload. + _dbg (mf/deref dbg/state) + + on-toggle-enabled (mf/use-fn (fn [event option] (dom/prevent-default event) (dom/stop-propagation event) (dbg/toggle! option) - (js* "app.main.reinit(true)"))) + (when-not (contains? no-reload-options option) + (js* "app.main.reinit(true)")))) handle-close (mf/use-fn diff --git a/frontend/src/app/main/ui/workspace/sidebar/debug_shape_info.cljs b/frontend/src/app/main/ui/workspace/sidebar/debug_shape_info.cljs index f4b30eea5f..f7c1e84ca9 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/debug_shape_info.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/debug_shape_info.cljs @@ -13,7 +13,6 @@ [app.main.data.workspace :as dw] [app.main.refs :as refs] [app.main.store :as st] - [app.main.ui.ds.product.panel-title :refer [panel-title*]] [debug :as dbg] [rumext.v2 :as mf])) @@ -125,10 +124,6 @@ (map (d/getf objects)))] [:div {:class (stl/css :shape-info)} - [:> panel-title* {:class (stl/css :shape-info-title) - :text "Debug" - :on-close #(dbg/disable! :shape-panel)}] - (if (empty? selected) [:div {:class (stl/css :attrs-container)} "No shapes selected"] (for [[idx current] (d/enumerate selected)] diff --git a/frontend/src/app/main/ui/workspace/sidebar/layer_name.cljs b/frontend/src/app/main/ui/workspace/sidebar/layer_name.cljs index 91e8a1c605..3e4b1ee570 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/layer_name.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/layer_name.cljs @@ -26,7 +26,11 @@ type-comp type-frame component-id is-hidden is-blocked variant-id variant-name variant-properties variant-error on-tab-press ref]}] - (let [edition* (mf/use-state false) + (let [;; Subscribe to dbg/state so the component re-renders when + ;; :show-ids or :show-touched are toggled without a page reload. + _dbg (mf/deref dbg/state) + + edition* (mf/use-state false) edition? (deref edition*) local-ref (mf/use-ref) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options.cljs b/frontend/src/app/main/ui/workspace/sidebar/options.cljs index 5b4619710e..f07dce3891 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options.cljs @@ -21,6 +21,7 @@ [app.main.ui.context :as ctx] [app.main.ui.ds.layout.tab-switcher :refer [tab-switcher*]] [app.main.ui.inspect.right-sidebar :as hrs] + [app.main.ui.workspace.sidebar.debug-shape-info :refer [debug-shape-info*]] [app.main.ui.workspace.sidebar.options.drawing :as drawing] [app.main.ui.workspace.sidebar.options.menus.align :refer [align-options*]] [app.main.ui.workspace.sidebar.options.menus.bool :refer [bool-options*]] @@ -38,6 +39,7 @@ [app.main.ui.workspace.sidebar.options.shapes.rect :as rect] [app.main.ui.workspace.sidebar.options.shapes.svg-raw :as svg-raw] [app.main.ui.workspace.sidebar.options.shapes.text :as text] + [app.util.debug :as dbg] [app.util.i18n :as i18n :refer [tr]] [okulary.core :as l] [rumext.v2 :as mf])) @@ -199,13 +201,17 @@ [:> hrs/right-sidebar* props])) -(def ^:private options-tabs - [{:label (tr "workspace.options.design") - :id "design"} - {:label (tr "workspace.options.prototype") - :id "prototype"} - {:label (tr "workspace.options.inspect") - :id "inspect"}]) +(defn- generate-options-tabs + [] + (cond-> [{:label (tr "workspace.options.design") + :id "design"} + {:label (tr "workspace.options.prototype") + :id "prototype"} + {:label (tr "workspace.options.inspect") + :id "inspect"}] + (dbg/enabled? :shape-panel) + (conj {:label "Debug" + :id "debug"}))) (defn- on-option-tab-change [mode] @@ -225,9 +231,28 @@ options-mode (mf/deref refs/options-mode-global) + ;; dbg/state is an okulary atom; deref'ing it makes this component + ;; re-render when debug options change (e.g. :shape-panel toggle) + ;; so the tabs list is regenerated reactively without a page reload. + dbg-state + (mf/deref dbg/state) + shapes (mf/with-memo [selected objects] - (sequence (keep (d/getf objects)) selected))] + (sequence (keep (d/getf objects)) selected)) + + options-tabs + (generate-options-tabs)] + + (mf/use-effect + (mf/deps dbg-state) + (fn [] + (if (contains? dbg-state :shape-panel) + ;; shape-panel was just enabled: select the debug tab + (on-option-tab-change "debug") + ;; shape-panel was just disabled: if debug tab is active, go back to design + (when (= options-mode :debug) + (on-option-tab-change "design"))))) [:div {:class (stl/css :tool-window)} (if (and (:can-edit permissions) (not render-context-lost?)) @@ -255,7 +280,10 @@ :objects objects :page-id page-id :file-id file-id - :shapes shapes}])] + :shapes shapes}] + + :debug + [:> debug-shape-info*])] [:div {:class (stl/css :element-options :inspect-options :read-only)} [:> inspect-tab* {:page-id page-id diff --git a/frontend/src/app/util/debug.cljs b/frontend/src/app/util/debug.cljs index 6eea87a758..0f8d4471f2 100644 --- a/frontend/src/app/util/debug.cljs +++ b/frontend/src/app/util/debug.cljs @@ -6,9 +6,11 @@ (ns app.util.debug (:require - [app.main.store :as st])) + [app.main.store :as st] + [app.util.storage :as storage] + [okulary.core :as l])) -(defonce state (atom #{#_:events})) +(def ^:private storage-key :app.util.debug/enabled-options) (def options #{;; Displays the bounding box for the shapes @@ -105,6 +107,17 @@ ;; Event times :events-times}) +(defn- load-state + [] + (let [stored (get storage/user storage-key #{})] + (into #{} (filter options) stored))) + +(defonce state (l/atom (load-state))) + +(defn- persist-state! + [state] + (swap! storage/user assoc storage-key state)) + (defn handle-change [] (set! st/*debug-events* (contains? @state :events)) @@ -112,7 +125,9 @@ (when *assert* (handle-change) - (add-watch state :watcher handle-change)) + (add-watch state :watcher handle-change) + (add-watch state :persistence (fn [_ _ _ new-state] + (persist-state! new-state)))) (defn enable! [option] diff --git a/frontend/translations/en.po b/frontend/translations/en.po index e05426f58e..1e1eb52478 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -8779,6 +8779,9 @@ msgstr "Copy as CSS (nested layers)" msgid "workspace.shape.menu.copy-link" msgstr "Copy link" +msgid "workspace.shape.menu.copy-id" +msgstr "Copy id" + #: src/app/main/ui/workspace/context_menu.cljs:219 msgid "workspace.shape.menu.copy-paste-as" msgstr "Copy/Paste as ..." diff --git a/frontend/translations/es.po b/frontend/translations/es.po index cceda3bf52..78c4c51465 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -4629,7 +4629,7 @@ msgid "onboarding.choice.team-up.invite-members" msgstr "Invitar integrantes" msgid "onboarding.choice.team-up.invite-members-desc" -msgstr "" +msgstr "" "Personas de desarrollo, diseño, gestión... todo el mundo es bienvenido. " "Podrás invitar a más gente posteriormente." @@ -8551,6 +8551,9 @@ msgstr "Copiar como CSS (capas anidadas)" msgid "workspace.shape.menu.copy-link" msgstr "Copiar enlace" +msgid "workspace.shape.menu.copy-id" +msgstr "Copiar id" + #: src/app/main/ui/workspace/context_menu.cljs:219 msgid "workspace.shape.menu.copy-paste-as" msgstr "Copiar/Pegar como ..."