From f3b856b2af4e1d77cd59d04b1b17795e7cf4ff4a Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 31 May 2023 11:07:07 +0200 Subject: [PATCH] :zap: Improve performance and usability of new css styles --- frontend/deps.edn | 4 +- frontend/src/app/main/style.clj | 104 ++++++++-- .../main/ui/workspace/sidebar/layer_item.cljs | 1 + .../main/ui/workspace/sidebar/layer_name.cljs | 32 +-- .../app/main/ui/workspace/sidebar/layers.cljs | 184 ++++++++---------- 5 files changed, 186 insertions(+), 139 deletions(-) diff --git a/frontend/deps.edn b/frontend/deps.edn index e30410620..71119fdfa 100644 --- a/frontend/deps.edn +++ b/frontend/deps.edn @@ -13,8 +13,8 @@ funcool/tubax {:mvn/version "2021.05.20-0"} funcool/rumext - {:git/tag "v2.3" - :git/sha "09942e7" + {:git/tag "v2.6" + :git/sha "97203a5" :git/url "https://github.com/funcool/rumext.git" } diff --git a/frontend/src/app/main/style.clj b/frontend/src/app/main/style.clj index 80cb4f7c2..4557e1711 100644 --- a/frontend/src/app/main/style.clj +++ b/frontend/src/app/main/style.clj @@ -7,25 +7,95 @@ (ns app.main.style "A fonts loading macros." (:require - [app.common.data :as d] - [clojure.data.json :as json])) + [clojure.core :as c] + [clojure.data.json :as json] + [clojure.java.io :as io] + [rumext.v2.util :as mfu])) + +(def ^:dynamic *css-data* nil) + +(def ^:private xform-css + (map (fn [k] + (let [cn (name k)] + (if (and (qualified-keyword? k) + (= "app.main.style" (namespace k))) + (or (get *css-data* (keyword cn)) cn) + cn))))) + +(defmacro css* + "Just coerces all params to strings and concats them with + space. Used mainly to set a set of classes together." + [& selectors] + (->> selectors + (map name) + (interpose " ") + (apply str))) (defmacro css - [selector] - (let [;; Get the associated styles will be module.cljs => module.css.json - filename (:file (meta *ns*)) - styles-file (str "./src/" (subs filename 0 (- (count filename) 4)) "css.json") - data (-> (slurp styles-file) - (json/read-str)) - result (get data (d/name selector))] - `~result)) + "Uses a css-modules defined data for real class lookup, then concat + all classes with space in the same way as `css*`." + [& selectors] + (let [fname (-> *ns* meta :file) + path (str (subs fname 0 (- (count fname) 4)) "css.json") + data (-> (slurp (io/resource path)) + (json/read-str :key-fn keyword))] + (if (symbol? (first selectors)) + `(if ~(with-meta (first selectors) {:tag 'boolean}) + (css* ~@(binding [*css-data* data] + (into [] xform-css (rest selectors)))) + (css* ~@(rest selectors))) + `(css* ~@(binding [*css-data* data] + (into [] xform-css selectors)))))) (defmacro styles [] - (let [;; Get the associated styles will be module.cljs => module.css.json - filename (:file (meta *ns*)) - styles-file (str "./src/" (subs filename 0 (- (count filename) 4)) "css.json") - data (-> (slurp styles-file) - (json/read-str)) - data (into {} (map (fn [[k v]] [(keyword k) v])) data)] - `~data)) \ No newline at end of file + ;; Get the associated styles will be module.cljs => module.css.json + (let [fname (-> *ns* meta :file) + path (str (subs fname 0 (- (count fname) 4)) "css.json")] + (-> (slurp (io/resource path)) + (json/read-str :key-fn keyword)))) + +(def ^:private xform-css-case + (comp + (partition-all 2) + (keep (fn [[k v]] + (let [cls (cond + (and (qualified-keyword? k) + (= "app.main.style" (namespace k))) + (let [cn (name k)] + (or (get *css-data* (keyword cn)) cn)) + + (simple-keyword? k) + (name k) + + (string? k) + k)] + (when cls + (cond + (true? v) cls + (false? v) nil + :else `(if ~v ~cls "")))))) + (interpose " "))) + +(defmacro css-case + [& params] + (let [fname (-> *ns* meta :file) + path (str (subs fname 0 (- (count fname) 4)) "css.json") + data (-> (slurp (io/resource path)) + (json/read-str :key-fn keyword))] + + (if (symbol? (first params)) + `(if ~(with-meta (first params) {:tag 'boolean}) + ~(binding [*css-data* data] + (-> (into [] xform-css-case (rest params)) + (mfu/compile-concat :safe? false))) + ~(-> (into [] xform-css-case (rest params)) + (mfu/compile-concat :safe? false))) + `~(binding [*css-data* data] + (-> (into [] xform-css-case params) + (mfu/compile-concat :safe? false)))))) + +(defmacro css-case* + [& params] + (-> (into [] xform-css-case params) + (mfu/compile-concat :safe? false))) diff --git a/frontend/src/app/main/ui/workspace/sidebar/layer_item.cljs b/frontend/src/app/main/ui/workspace/sidebar/layer_item.cljs index 505dc8810..25467fbf6 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/layer_item.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/layer_item.cljs @@ -314,6 +314,7 @@ [:& si/element-icon {:shape item :main-instance? main-instance?}]] [:& layer-name {:ref ref + :parent-size parent-size :shape-id (:id item) :shape-name (:name item) :shape-touched? (boolean (seq (:touched item))) 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 cb04feb0b..77e759051 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/layer_name.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/layer_name.cljs @@ -5,8 +5,9 @@ ;; Copyright (c) KALEIDOS INC (ns app.main.ui.workspace.sidebar.layer-name - (:require-macros [app.main.style :refer [css]]) + (:require-macros [app.main.style :as stl]) (:require + [app.common.data :as d] [app.common.data.macros :as dm] [app.main.data.workspace :as dw] [app.main.store :as st] @@ -33,7 +34,7 @@ edition? (deref edition*) local-ref (mf/use-ref) - ref (or external-ref local-ref) + ref (d/nilv external-ref local-ref) shape-for-rename (mf/deref lens:shape-for-rename) new-css-system (mf/use-ctx ctx/new-css-system) @@ -86,26 +87,25 @@ (if ^boolean edition? [:input - {:class (if new-css-system - (dom/classnames (css :element-name-input) true) - (dom/classnames :element-name true)) - :style #js {"--depth" depth "--parent-size" parent-size} + {:class (stl/css new-css-system ::stl/element-name ::stl/element-name-input) + :style {"--depth" depth "--parent-size" parent-size} :type "text" :ref ref :on-blur accept-edit :on-key-down on-key-down :auto-focus true - :default-value (or shape-name "")}] + :default-value (d/nilv shape-name "")}] [:span - {:class (if new-css-system - (dom/classnames (css :element-name) true - (css :selected) selected? - (css :hidden) hidden? - (css :type-comp) type-comp - (css :type-frame) type-frame) - (dom/classnames :element-name true)) - :style #js {"--depth" depth "--parent-size" parent-size} + {:class (if ^boolean new-css-system + (stl/css-case + ::stl/element-name true + ::stl/selected selected? + ::stl/hidden hidden? + ::stl/type-comp type-comp + ::stl/type-frame type-frame) + (stl/css* :element-name)) + :style {"--depth" depth "--parent-size" parent-size} :ref ref :on-double-click start-edit} - (or shape-name "") + (d/nilv shape-name "") (when ^boolean shape-touched? " *")]))) diff --git a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs index 89c9c0c02..9c3ab7560 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs @@ -5,7 +5,7 @@ ;; Copyright (c) KALEIDOS INC (ns app.main.ui.workspace.sidebar.layers - (:require-macros [app.main.style :refer [css]]) + (:require-macros [app.main.style :as stl :refer [css]]) (:require [app.common.data :as d] [app.common.data.macros :as dm] @@ -48,9 +48,7 @@ root (get objects uuid/zero) new-css-system (mf/use-ctx ctx/new-css-system)] [:ul - {:class (if new-css-system - (dom/classnames (css :element-list) true) - (dom/classnames :element-list true))} + {:class (stl/css new-css-system ::stl/element-list)} [:& hooks/sortable-container {} (for [[index id] (reverse (d/enumerate (:shapes root)))] (when-let [obj (get objects id)] @@ -84,9 +82,7 @@ selected (hooks/use-equal-memo selected) root (get objects uuid/zero) new-css-system (mf/use-ctx ctx/new-css-system)] - [:ul {:class (if new-css-system - (dom/classnames (css :element-list) true) - (dom/classnames :element-list true))} + [:ul {:class (stl/css new-css-system ::stl/element-list)} (for [[index id] (d/enumerate (:shapes root))] (when-let [obj (get objects id)] [:& layer-item @@ -130,8 +126,8 @@ (or (empty? filters) (and (contains? filters :component) (contains? shape :component-id)) - (let [direct-filters (filter #{:frame :rect :circle :path :bool :image :text} filters)] - (some #{(:type shape)} direct-filters)) + (let [direct-filters (into #{} (filter #{:frame :rect :circle :path :bool :image :text}) filters)] + (contains? direct-filters (:type shape))) (and (contains? filters :group) (and (cph/group-shape? shape) (not (contains? shape :component-id)) @@ -254,12 +250,7 @@ #(mf/html (if show-search? [:* - [:div {:class (if ^boolean new-css-system - (dom/classnames (css :tool-window-bar) true - (css :search) true) - (dom/classnames :tool-window-bar true - :search true))} - + [:div {:class (stl/css new-css-system ::stl/tool-window-bar ::stl/search)} (if ^boolean new-css-system [:& search-bar {:on-change update-search-text-v2 @@ -268,14 +259,15 @@ :placeholder (tr "workspace.sidebar.layers.search")} [:button {:on-click toggle-filters - :class (dom/classnames :active active? - (css :filter-button) true)} + :class (stl/css-case + ::stl/filters-button true + :active active?)} i/filter-refactor]] [:span.search-box [:button.filter {:on-click toggle-filters - :class (dom/classnames :active active?)} + :class (stl/css-case :active active?)} i/icon-filter] [:div [:input {:on-change update-search-text-v1 @@ -286,15 +278,13 @@ (when (not (= "" current-search)) [:button.clear {:on-click clear-search-text} i/exclude])]]) - [:button {:class (dom/classnames (css :close-search) new-css-system) + [:button {:class (stl/css-case ::stl/close-search new-css-system) :on-click toggle-search} (if ^boolean new-css-system i/close-refactor i/cross)]] - [:div {:class (if ^boolean new-css-system - (dom/classnames (css :active-filters) true) - (dom/classnames :active-filters true))} + [:div {:class (stl/css new-css-system ::stl/active-filters)} (for [fkey current-filters] (let [fname (d/name fkey) name (case fkey @@ -307,17 +297,17 @@ :shape (tr "workspace.sidebar.layers.shapes") (tr fkey))] (if ^boolean new-css-system - [:button {:class (dom/classnames (css :layer-filter) true) + [:button {:class (stl/css ::stl/layer-filter) :key fname :data-filter fname :on-click remove-filter} - [:span {:class (dom/classnames (css :layer-filter-icon) true)} + [:span {:class (stl/css ::stl/layer-filter-icon)} [:& sic/element-icon-refactor-by-type {:type fkey :main-instance? (= fkey :component)}]] - [:span {:class (dom/classnames (css :layer-filter-name) true)} + [:span {:class (stl/css ::stl/layer-filter-name)} name] - [:span {:class (dom/classnames (css :layer-filter-close) true)} + [:span {:class (stl/css ::stl/layer-filter-close)} i/close-small-refactor]] [:span {:on-click remove-filter @@ -327,89 +317,89 @@ (when ^boolean show-menu? (if ^boolean new-css-system - [:ul {:class (css :filters-container)} - [:li {:class (dom/classnames - (css :filter-menu-item) true - (css :selected) (contains? current-filters :frame)) + [:ul {:class (stl/css ::stl/filters-container)} + [:li {:class (stl/css-case + ::stl/filter-menu-item true + ::stl/selected (contains? current-filters :frame)) :data-filter "frame" :on-click add-filter} - [:div {:class (dom/classnames (css :filter-menu-item-name-wrapper) true)} - [:span {:class (dom/classnames (css :filter-menu-item-icon) true)} i/board-refactor] - [:span {:class (dom/classnames (css :filter-menu-item-name) true)} + [:div {:class (stl/css ::stl/filter-menu-item-name-wrapper)} + [:span {:class (stl/css ::stl/filter-menu-item-icon)} i/board-refactor] + [:span {:class (stl/css ::stl/filter-menu-item-name)} (tr "workspace.sidebar.layers.frames")]] (when (contains? current-filters :frame) - [:span {:class (dom/classnames (css :filter-menu-item-tick) true)} i/tick-refactor])] + [:span {:class (stl/css ::stl/filter-menu-item-tick)} i/tick-refactor])] - [:li {:class (dom/classnames - (css :filter-menu-item) true - (css :selected) (contains? current-filters :group)) + [:li {:class (stl/css-case + ::stl/filter-menu-item true + ::stl/selected (contains? current-filters :group)) :data-filter "group" :on-click add-filter} - [:div {:class (dom/classnames (css :filter-menu-item-name-wrapper) true)} - [:span {:class (dom/classnames (css :filter-menu-item-icon) true)} i/group-refactor] - [:span {:class (dom/classnames (css :filter-menu-item-name) true)} + [:div {:class (stl/css ::stl/filter-menu-item-name-wrapper)} + [:span {:class (stl/css ::stl/filter-menu-item-icon)} i/group-refactor] + [:span {:class (stl/css ::stl/filter-menu-item-name)} (tr "workspace.sidebar.layers.groups")]] (when (contains? current-filters :group) - [:span {:class (dom/classnames (css :filter-menu-item-tick) true)} i/tick-refactor])] + [:span {:class (stl/css ::stl/filter-menu-item-tick)} i/tick-refactor])] - [:li {:class (dom/classnames - (css :filter-menu-item) true - (css :selected) (contains? current-filters :mask)) + [:li {:class (stl/css-case + :filter-menu-item true + :selected (contains? current-filters :mask)) :data-filter "mask" :on-click add-filter} - [:div {:class (dom/classnames (css :filter-menu-item-name-wrapper) true)} - [:span {:class (dom/classnames (css :filter-menu-item-icon) true)} i/mask-refactor] - [:span {:class (dom/classnames (css :filter-menu-item-name) true)} + [:div {:class (stl/css ::stl/filter-menu-item-name-wrapper)} + [:span {:class (stl/css ::stl/filter-menu-item-icon)} i/mask-refactor] + [:span {:class (stl/css ::stl/filter-menu-item-name)} (tr "workspace.sidebar.layers.masks")]] (when (contains? current-filters :mask) - [:span {:class (dom/classnames (css :filter-menu-item-tick) true)} i/tick-refactor])] + [:span {:class (stl/css ::stl/filter-menu-item-tick)} i/tick-refactor])] - [:li {:class (dom/classnames - (css :filter-menu-item) true - (css :selected) (contains? current-filters :component)) + [:li {:class (stl/css-case + :filter-menu-item true + :selected (contains? current-filters :component)) :data-filter "component" :on-click add-filter} - [:div {:class (dom/classnames (css :filter-menu-item-name-wrapper) true)} - [:span {:class (dom/classnames (css :filter-menu-item-icon) true)} i/component-refactor] - [:span {:class (dom/classnames (css :filter-menu-item-name) true)} + [:div {:class (stl/css ::stl/filter-menu-item-name-wrapper)} + [:span {:class (stl/css ::stl/filter-menu-item-icon)} i/component-refactor] + [:span {:class (stl/css ::stl/filter-menu-item-name)} (tr "workspace.sidebar.layers.components")]] (when (contains? current-filters :component) - [:span {:class (dom/classnames (css :filter-menu-item-tick) true)} i/tick-refactor])] - [:li {:class (dom/classnames - (css :filter-menu-item) true - (css :selected) (contains? current-filters :text)) + [:span {:class (stl/css ::stl/filter-menu-item-tick)} i/tick-refactor])] + [:li {:class (stl/css-case + :filter-menu-item true + :selected (contains? current-filters :text)) :data-filter "text" :on-click add-filter} - [:div {:class (dom/classnames (css :filter-menu-item-name-wrapper) true)} - [:span {:class (dom/classnames (css :filter-menu-item-icon) true)} i/text-refactor] - [:span {:class (dom/classnames (css :filter-menu-item-name) true)} + [:div {:class (stl/css ::stl/filter-menu-item-name-wrapper)} + [:span {:class (stl/css ::stl/filter-menu-item-icon)} i/text-refactor] + [:span {:class (stl/css ::stl/filter-menu-item-name)} (tr "workspace.sidebar.layers.texts")]] (when (contains? current-filters :text) - [:span {:class (dom/classnames (css :filter-menu-item-tick) true)} i/tick-refactor])] + [:span {:class (stl/css ::stl/filter-menu-item-tick)} i/tick-refactor])] - [:li {:class (dom/classnames - (css :filter-menu-item) true - (css :selected) (contains? current-filters :image)) + [:li {:class (stl/css-case + :filter-menu-item true + :selected (contains? current-filters :image)) :data-filter "image" :on-click add-filter} - [:div {:class (dom/classnames (css :filter-menu-item-name-wrapper) true)} - [:span {:class (dom/classnames (css :filter-menu-item-icon) true)} i/img-refactor] - [:span {:class (dom/classnames (css :filter-menu-item-name) true)} + [:div {:class (stl/css ::stl/filter-menu-item-name-wrapper)} + [:span {:class (stl/css ::stl/filter-menu-item-icon)} i/img-refactor] + [:span {:class (stl/css ::stl/filter-menu-item-name)} (tr "workspace.sidebar.layers.images")]] (when (contains? current-filters :image) - [:span {:class (dom/classnames (css :filter-menu-item-tick) true)} i/tick-refactor])] - [:li {:class (dom/classnames - (css :filter-menu-item) true - (css :selected) (contains? current-filters :shape)) + [:span {:class (stl/css ::stl/filter-menu-item-tick)} i/tick-refactor])] + [:li {:class (stl/css-case + :filter-menu-item true + :selected (contains? current-filters :shape)) :data-filter "shape" :on-click add-filter} - [:div {:class (dom/classnames (css :filter-menu-item-name-wrapper) true)} - [:span {:class (dom/classnames (css :filter-menu-item-icon) true)} i/path-refactor] - [:span {:class (dom/classnames (css :filter-menu-item-name) true)} + [:div {:class (stl/css ::stl/filter-menu-item-name-wrapper)} + [:span {:class (stl/css ::stl/filter-menu-item-icon)} i/path-refactor] + [:span {:class (stl/css ::stl/filter-menu-item-name)} (tr "workspace.sidebar.layers.shapes")]] (when (contains? current-filters :shape) - [:span {:class (dom/classnames (css :filter-menu-item-tick) true)} i/tick-refactor])]] + [:span {:class (stl/css ::stl/filter-menu-item-tick)} i/tick-refactor])]] [:div.filters-container [:span {:data-filter "frame" @@ -435,7 +425,7 @@ i/curve (tr "workspace.sidebar.layers.shapes")]]))] (if ^boolean new-css-system - [:div {:class (dom/classnames (css :tool-window-bar) true)} + [:div {:class (stl/css :tool-window-bar)} [:& title-bar {:collapsable? false :title (:name page) :on-btn-click toggle-search @@ -516,38 +506,28 @@ (when sticky? (dom/add-class! last-hidden-frame "sticky"))))))] [:div#layers - {:class (if new-css-system - (dom/classnames (css :layers) true) - (dom/classnames :tool-window true))} + {:class (if ^boolean new-css-system + (stl/css ::stl/layers) + (stl/css* :tool-window))} (if (d/not-empty? focus) [:div - {:class (if new-css-system - (dom/classnames (css :tool-window-bar) true) - (dom/classnames :tool-window-bar true))} - [:button {:class (if new-css-system - (dom/classnames (css :focus-title) true) - (dom/classnames :focus-title true)) + {:class (stl/css new-css-system ::stl/tool-window-bar)} + [:button {:class (stl/css new-css-system :focus-title) :on-click #(st/emit! (dw/toggle-focus-mode))} - [:span {:class (if new-css-system - (dom/classnames (css :back-button) true) - (dom/classnames :back-button true))} + [:span {:class (stl/css new-css-system ::stl/back-button)} (if new-css-system i/arrow-refactor i/arrow-slide)] - [:div {:class (if new-css-system - (dom/classnames (css :focus-name) true) - (dom/classnames :focus-name true))} + [:div {:class (stl/css new-css-system ::stl/focus-name)} (or title (tr "workspace.sidebar.layers"))] - (if new-css-system - [:div {:class (dom/classnames (css :focus-mode-tag-wrapper) true)} - [:div {:class (dom/classnames (css :focus-mode-tag) true)} (tr "workspace.focus.focus-mode")]] + (if ^boolean new-css-system + [:div {:class (stl/css ::stl/focus-mode-tag-wrapper)} + [:div {:class (stl/css ::stl/focus-mode-tag)} (tr "workspace.focus.focus-mode")]] [:div.focus-mode (tr "workspace.focus.focus-mode")])]] (filter-component)) (if (some? filtered-objects) [:* - [:div {:class (if new-css-system - (dom/classnames (css :tool-window-content) true) - (dom/classnames :tool-window-content true)) + [:div {:class (stl/css new-css-system ::stl/tool-window-content) :ref on-render-container} [:& filters-tree {:objects filtered-objects :key (dm/str (:id page)) @@ -555,18 +535,14 @@ [:div.lazy {:ref lazy-load-ref :style {:min-height 16}}]] [:div {:on-scroll on-scroll - :class (if new-css-system - (dom/classnames (css :tool-window-content) true) - (dom/classnames :tool-window-content true)) + :class (stl/css new-css-system ::stl/tool-window-content) :style {:display (when (some? filtered-objects) "none")}} [:& layers-tree {:objects filtered-objects :key (dm/str (:id page)) :filtered? true :parent-size size-parent}]]] [:div {:on-scroll on-scroll - :class (if new-css-system - (dom/classnames (css :tool-window-content) true) - (dom/classnames :tool-window-content true)) + :class (stl/css new-css-system ::stl/tool-window-content) :style {:display (when (some? filtered-objects) "none")}} [:& layers-tree {:objects objects :key (dm/str (:id page))