diff --git a/frontend/src/app/main/data/tokens.cljs b/frontend/src/app/main/data/tokens.cljs index af5b103ae..de295d071 100644 --- a/frontend/src/app/main/data/tokens.cljs +++ b/frontend/src/app/main/data/tokens.cljs @@ -312,7 +312,7 @@ (ptk/reify ::set-token-type-section-open ptk/UpdateEvent (update [_ state] - (assoc-in state [:workspace-tokens :open-status token-type] open?)))) + (assoc-in state [:workspace-local :token-type-open-status token-type] open?)))) ;; === Token Context Menu diff --git a/frontend/src/app/main/data/workspace/tokens/common.cljs b/frontend/src/app/main/data/workspace/tokens/common.cljs deleted file mode 100644 index 7cb07aeae..000000000 --- a/frontend/src/app/main/data/workspace/tokens/common.cljs +++ /dev/null @@ -1,8 +0,0 @@ -(ns app.main.data.workspace.tokens.common - (:require - [app.main.data.helpers :as dsh])) - -(defn get-workspace-tokens-lib - [state] - (-> (dsh/lookup-file-data state) - (get :tokens-lib))) diff --git a/frontend/src/app/main/data/workspace/tokens/selected_set.cljs b/frontend/src/app/main/data/workspace/tokens/selected_set.cljs index ece47fca0..748a8acee 100644 --- a/frontend/src/app/main/data/workspace/tokens/selected_set.cljs +++ b/frontend/src/app/main/data/workspace/tokens/selected_set.cljs @@ -1,24 +1,29 @@ +;; 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.data.workspace.tokens.selected-set "The user selected token set in the ui, stored by the `:name` of the set. Will default to the first set." (:require [app.common.types.tokens-lib :as ctob] - [app.main.data.workspace.tokens.common :as dwtc] + [app.main.data.helpers :as dsh] [potok.v2.core :as ptk])) -(defn assoc-selected-token-set-name [state set-name] - (assoc-in state [:workspace-local :selected-token-set-name] set-name)) - (defn get-selected-token-set-name [state] (or (get-in state [:workspace-local :selected-token-set-name]) - (some-> (dwtc/get-workspace-tokens-lib state) + (some-> (dsh/lookup-file-data state) + (get :tokens-lib) (ctob/get-sets) (first) :name))) (defn get-selected-token-set [state] (when-let [set-name (get-selected-token-set-name state)] - (some-> (dwtc/get-workspace-tokens-lib state) + (some-> (dsh/lookup-file-data state) + (get :tokens-lib) (ctob/get-set set-name)))) (defn get-selected-token-set-token [state token-name] @@ -30,8 +35,8 @@ :tokens)) (defn set-selected-token-set-name - [set-name] + [name] (ptk/reify ::set-selected-token-set-path-from-name ptk/UpdateEvent (update [_ state] - (assoc-selected-token-set-name state set-name)))) + (update state :workspace-local assoc :selected-token-set-name name)))) diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index b0e5c1642..c5de2cd79 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -144,7 +144,7 @@ (l/derived (fn [{:keys [objects selected]}] (dsh/process-selected-shapes objects selected)) - selected-shapes-data)) + selected-shapes-data =)) (defn make-selected-ref [id] @@ -479,6 +479,7 @@ (def workspace-active-set-names (l/derived (d/nilf ctob/get-active-themes-set-names) tokens-lib)) +;; FIXME: deprecated, it should not be implemented with ref (def workspace-active-theme-sets-tokens (l/derived #(or (some-> % ctob/get-active-themes-set-tokens) {}) tokens-lib)) diff --git a/frontend/src/app/main/ui/workspace/sidebar.cljs b/frontend/src/app/main/ui/workspace/sidebar.cljs index f28f04ac2..5b2222f44 100644 --- a/frontend/src/app/main/ui/workspace/sidebar.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar.cljs @@ -31,7 +31,7 @@ [app.main.ui.workspace.sidebar.shortcuts :refer [shortcuts-container]] [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.main.ui.workspace.tokens.sidebar :refer [tokens-sidebar-tab*]] [app.util.debug :as dbg] [app.util.i18n :refer [tr]] [potok.v2.core :as ptk] @@ -120,7 +120,7 @@ tokens-tab (when design-tokens? - (mf/html [:& tokens-sidebar-tab])) + (mf/html [:> tokens-sidebar-tab*])) tabs (if ^boolean mode-inspect? diff --git a/frontend/src/app/main/ui/workspace/tokens/changes.cljs b/frontend/src/app/main/ui/workspace/tokens/changes.cljs index 3b1ab1797..b707957dc 100644 --- a/frontend/src/app/main/ui/workspace/tokens/changes.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/changes.cljs @@ -6,6 +6,8 @@ (ns app.main.ui.workspace.tokens.changes (:require + [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.types.shape.layout :as ctsl] [app.common.types.shape.radius :as ctsr] [app.common.types.token :as ctt] @@ -26,6 +28,8 @@ [clojure.set :as set] [potok.v2.core :as ptk])) +(declare token-properties) + ;; Token Updates --------------------------------------------------------------- (defn apply-token @@ -76,12 +80,16 @@ (update shape :applied-tokens remove-token)))))))) (defn toggle-token - [{:keys [token-type-props token shapes] :as _props}] + [{:keys [token shapes]}] (ptk/reify ::on-toggle-token ptk/WatchEvent (watch [_ _ _] - (let [{:keys [attributes all-attributes on-update-shape]} token-type-props - unapply-tokens? (wtt/shapes-token-applied? token shapes (or all-attributes attributes)) + (let [{:keys [attributes all-attributes on-update-shape]} + (get token-properties (:type token)) + + unapply-tokens? + (wtt/shapes-token-applied? token shapes (or all-attributes attributes)) + shape-ids (map :id shapes)] (if unapply-tokens? (rx/of @@ -272,3 +280,88 @@ (select-keys attributes))] (dwsl/update-layout-child shape-ids props {:ignore-touched true :page-id page-id})))))) + +;; Token Types ----------------------------------------------------------------- + +;; FIXME: the values should be lazy evaluated, probably a function, +;; becasue on future we will need to translate that labels and that +;; can not be done statically + +(def token-properties + "A map of default properties by token type" + (d/ordered-map + :border-radius + {:title "Border Radius" + :attributes ctt/border-radius-keys + :on-update-shape update-shape-radius-all + :modal {:key :tokens/border-radius + :fields [{:label "Border Radius" + :key :border-radius}]}} + + :color + {:title "Color" + :attributes #{:fill} + :all-attributes ctt/color-keys + :on-update-shape update-fill-stroke + :modal {:key :tokens/color + :fields [{:label "Color" :key :color}]}} + + :stroke-width + {:title "Stroke Width" + :attributes ctt/stroke-width-keys + :on-update-shape update-stroke-width + :modal {:key :tokens/stroke-width + :fields [{:label "Stroke Width" + :key :stroke-width}]}} + + :sizing + {:title "Sizing" + :attributes #{:width :height} + :all-attributes ctt/sizing-keys + :on-update-shape update-shape-dimensions + :modal {:key :tokens/sizing + :fields [{:label "Sizing" + :key :sizing}]}} + :dimensions + {:title "Dimensions" + :attributes #{:width :height} + :all-attributes (set/union + ctt/spacing-keys + ctt/sizing-keys + ctt/border-radius-keys + ctt/stroke-width-keys) + :on-update-shape update-shape-dimensions + :modal {:key :tokens/dimensions + :fields [{:label "Dimensions" + :key :dimensions}]}} + + :opacity + {:title "Opacity" + :attributes ctt/opacity-keys + :on-update-shape update-opacity + :modal {:key :tokens/opacity + :fields [{:label "Opacity" + :key :opacity}]}} + + :rotation + {:title "Rotation" + :attributes ctt/rotation-keys + :on-update-shape update-rotation + :modal {:key :tokens/rotation + :fields [{:label "Rotation" + :key :rotation}]}} + :spacing + {:title "Spacing" + :attributes #{:column-gap :row-gap} + :all-attributes ctt/spacing-keys + :on-update-shape update-layout-spacing + :modal {:key :tokens/spacing + :fields [{:label "Spacing" + :key :spacing}]}})) + +(defn get-token-properties [token] + (get token-properties (:type token))) + +(defn token-attributes [token-type] + (dm/get-in token-properties [token-type :attributes])) + diff --git a/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs b/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs index fba2a7bc8..187e544ed 100644 --- a/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs @@ -18,7 +18,6 @@ [app.main.ui.ds.foundations.assets.icon :refer [icon*]] [app.main.ui.workspace.tokens.changes :as wtch] [app.main.ui.workspace.tokens.token :as wtt] - [app.main.ui.workspace.tokens.token-types :as wtty] [app.util.dom :as dom] [app.util.i18n :refer [tr]] [app.util.timers :as timers] @@ -38,7 +37,7 @@ (defn generic-attribute-actions [attributes title {:keys [token selected-shapes on-update-shape]}] (let [on-update-shape-fn (or on-update-shape - (-> (wtty/get-token-properties token) + (-> (wtch/get-token-properties token) (:on-update-shape))) {:keys [selected-pred shape-ids]} (attribute-actions token selected-shapes attributes)] (map (fn [attribute] @@ -236,7 +235,7 @@ (generic-attribute-actions #{:y} "Y" (assoc context-data :on-update-shape wtch/update-shape-position))))})) (defn default-actions [{:keys [token selected-token-set-name]}] - (let [{:keys [modal]} (wtty/get-token-properties token)] + (let [{:keys [modal]} (wtch/get-token-properties token)] [{:title (tr "workspace.token.edit") :no-selectable true :action (fn [event] diff --git a/frontend/src/app/main/ui/workspace/tokens/form.cljs b/frontend/src/app/main/ui/workspace/tokens/form.cljs index 46406903a..85c8530c3 100644 --- a/frontend/src/app/main/ui/workspace/tokens/form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/form.cljs @@ -21,13 +21,13 @@ [app.main.ui.notifications.context-notification :refer [context-notification]] [app.main.ui.workspace.colorpicker :as colorpicker] [app.main.ui.workspace.colorpicker.ramp :refer [ramp-selector*]] + [app.main.ui.workspace.tokens.changes :as wtch] [app.main.ui.workspace.tokens.components.controls.input-token-color-bullet :refer [input-token-color-bullet*]] [app.main.ui.workspace.tokens.components.controls.input-tokens :refer [input-tokens*]] [app.main.ui.workspace.tokens.errors :as wte] [app.main.ui.workspace.tokens.style-dictionary :as sd] [app.main.ui.workspace.tokens.tinycolor :as tinycolor] [app.main.ui.workspace.tokens.token :as wtt] - [app.main.ui.workspace.tokens.token-types :as wtty] [app.main.ui.workspace.tokens.update :as wtu] [app.main.ui.workspace.tokens.warnings :as wtw] [app.util.dom :as dom] @@ -223,7 +223,7 @@ [{:keys [token token-type action selected-token-set-name on-display-colorpicker]}] (let [create? (not (instance? ctob/Token token)) token (or token {:type token-type}) - token-properties (wtty/get-token-properties token) + token-properties (wtch/get-token-properties token) color? (wtt/color-token? token) selected-set-tokens (mf/deref refs/workspace-selected-token-set-tokens) active-theme-tokens (mf/deref refs/workspace-active-theme-sets-tokens) diff --git a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs index 86e6157b9..54397f0d9 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs @@ -8,6 +8,7 @@ (:require-macros [app.main.style :as stl]) (:require [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.types.tokens-lib :as ctob] [app.main.data.event :as ev] [app.main.data.modal :as modal] @@ -18,6 +19,7 @@ [app.main.ui.components.dropdown-menu :refer [dropdown-menu dropdown-menu-item*]] [app.main.ui.components.title-bar :refer [title-bar]] + [app.main.ui.context :as ctx] [app.main.ui.ds.buttons.button :refer [button*]] [app.main.ui.ds.buttons.icon-button :refer [icon-button*]] [app.main.ui.ds.foundations.typography.text :refer [text*]] @@ -32,9 +34,8 @@ [app.main.ui.workspace.tokens.sets-context-menu :refer [sets-context-menu]] [app.main.ui.workspace.tokens.style-dictionary :as sd] [app.main.ui.workspace.tokens.theme-select :refer [theme-select]] - [app.main.ui.workspace.tokens.token :as wtt] - [app.main.ui.workspace.tokens.token-pill :refer [token-pill]] - [app.main.ui.workspace.tokens.token-types :as wtty] + [app.main.ui.workspace.tokens.token-pill :refer [token-pill*]] + [app.util.array :as array] [app.util.dom :as dom] [app.util.i18n :refer [tr]] [app.util.webapi :as wapi] @@ -44,8 +45,8 @@ [rumext.v2 :as mf] [shadow.resource])) -(def lens:token-type-open-status - (l/derived (l/in [:workspace-tokens :open-status]) st/state)) +(def ref:token-type-open-status + (l/derived #(dm/get-in % [:workspace-local :token-type-open-status]) st/state)) ;; Components ------------------------------------------------------------------ @@ -65,62 +66,64 @@ :sizing "expand" "add")) -(defn attribute-actions [token selected-shapes attributes] - (let [ids-by-attributes (wtt/shapes-ids-by-applied-attributes token selected-shapes attributes) - shape-ids (into #{} (map :id selected-shapes))] - {:all-selected? (wtt/shapes-applied-all? ids-by-attributes shape-ids attributes) - :shape-ids shape-ids - :selected-pred #(seq (% ids-by-attributes))})) +(mf/defc token-group* + {::mf/private true} + [{:keys [type tokens selected-shapes active-theme-tokens is-open]}] + (let [{:keys [modal title]} + (get wtch/token-properties type) -(mf/defc token-component - [{:keys [type tokens selected-shapes token-type-props active-theme-tokens]}] - (let [open? (mf/deref (-> (l/key type) - (l/derived lens:token-type-open-status))) - {:keys [modal attributes all-attributes title]} token-type-props + tokens + (mf/with-memo [tokens] + (vec (sort-by :name tokens))) - on-context-menu (mf/use-fn - (fn [event token] - (dom/prevent-default event) - (dom/stop-propagation event) - (st/emit! (dt/show-token-context-menu - {:type :token - :position (dom/get-client-position event) - :errors (:errors token) - :token-name (:name token)})))) + on-context-menu + (mf/use-fn + (fn [event token] + (dom/prevent-default event) + (dom/stop-propagation event) + (st/emit! (dt/show-token-context-menu + {:type :token + :position (dom/get-client-position event) + :errors (:errors token) + :token-name (:name token)})))) - on-toggle-open-click (mf/use-fn - (mf/deps open? tokens) - #(st/emit! (dt/set-token-type-section-open type (not open?)))) - on-popover-open-click (mf/use-fn - (fn [event] - (mf/deps type title) - (let [{:keys [key fields]} modal] - (dom/stop-propagation event) - (st/emit! (dt/set-token-type-section-open type true)) - (modal/show! key {:x (.-clientX ^js event) - :y (.-clientY ^js event) - :position :right - :fields fields - :title title - :action "create" - :token-type type})))) + on-toggle-open-click + (mf/use-fn + (mf/deps is-open type) + #(st/emit! (dt/set-token-type-section-open type (not is-open)))) - on-token-pill-click (mf/use-fn - (mf/deps selected-shapes token-type-props) - (fn [event token] - (dom/stop-propagation event) - (when (seq selected-shapes) - (st/emit! - (wtch/toggle-token {:token token - :shapes selected-shapes - :token-type-props token-type-props}))))) + on-popover-open-click + (mf/use-fn + (mf/deps type title modal) + (fn [event] + (dom/stop-propagation event) + (st/emit! (dt/set-token-type-section-open type true) + ;; FIXME: use dom/get-client-position + (modal/show (:key modal) + {:x (.-clientX ^js event) + :y (.-clientY ^js event) + :position :right + :fields (:fields modal) + :title title + :action "create" + :token-type type})))) + + on-token-pill-click + (mf/use-fn + (mf/deps selected-shapes) + (fn [event token] + (dom/stop-propagation event) + (when (seq selected-shapes) + (st/emit! (wtch/toggle-token {:token token + :shapes selected-shapes}))))) tokens-count (count tokens) can-edit? (:can-edit (deref refs/permissions))] + [:div {:on-click on-toggle-open-click} [:& cmm/asset-section {:icon (token-section-icon type) :title title :assets-count tokens-count - :open? open?} + :open? is-open} [:& cmm/asset-section-block {:role :title-button} (when can-edit? [:> icon-button* {:on-click on-popover-open-click @@ -128,56 +131,55 @@ :icon "add" ;; TODO: This needs translation :aria-label (str "Add token: " title)}])] - (when open? + (when is-open [:& cmm/asset-section-block {:role :content} [:div {:class (stl/css :token-pills-wrapper)} - (for [token (sort-by :name tokens)] - (let [theme-token (get active-theme-tokens (wtt/token-identifier token)) - multiple-selection (< 1 (count selected-shapes)) - full-applied (:all-selected? (attribute-actions token selected-shapes (or all-attributes attributes))) - applied (wtt/shapes-token-applied? token selected-shapes (or all-attributes attributes)) - on-token-click (fn [e] - (on-token-pill-click e token)) - on-context-menu (fn [e] (on-context-menu e token))] - [:& token-pill - {:key (:name token) - :token-type-props token-type-props - :token token - :selected-shapes selected-shapes - :active-theme-tokens active-theme-tokens - :theme-token theme-token - :half-applied (or (and applied multiple-selection) - (and applied (not full-applied))) - :full-applied (if multiple-selection - false - applied) - :on-click on-token-click - :on-context-menu on-context-menu}]))]])]])) + (for [token tokens] + [:> token-pill* + {:key (:name token) + :token token + :selected-shapes selected-shapes + :active-theme-tokens active-theme-tokens + :on-click on-token-pill-click + :on-context-menu on-context-menu}])]])]])) -(defn sorted-token-groups - "Separate token-types into groups of `:empty` or `:filled` depending if tokens exist for that type. - Sort each group alphabetically (by their `:token-key`)." - [tokens] - (let [tokens-by-type (ctob/group-by-type tokens) - {:keys [empty filled]} (->> wtty/token-types - (map (fn [[token-key token-type-props]] - {:token-key token-key - :token-type-props token-type-props - :tokens (get tokens-by-type token-key [])})) - (group-by (fn [{:keys [tokens]}] - (if (empty? tokens) :empty :filled))))] - {:empty (sort-by :token-key empty) - :filled (sort-by :token-key filled)})) +(defn- get-sorted-token-groups + "Separate token-types into groups of `empty` or `filled` depending if + tokens exist for that type. Sort each group alphabetically (by + their type)." + [tokens-by-type] + (loop [empty #js [] + filled #js [] + types (-> wtch/token-properties keys seq)] + (if-let [type (first types)] + (if (not-empty (get tokens-by-type type)) + (recur empty + (array/conj! filled type) + (rest types)) + (recur (array/conj! empty type) + filled + (rest types))) + [(seq (array/sort! empty)) + (seq (array/sort! filled))]))) + +(mf/defc themes-header* + {::mf/private true} + [] + (let [ordered-themes + (mf/deref refs/workspace-token-themes-no-hidden) + + permissions + (mf/use-ctx ctx/permissions) + + can-edit? + (get permissions :can-edit) -(mf/defc themes-header - [_props] - (let [ordered-themes (mf/deref refs/workspace-token-themes-no-hidden) - can-edit? (:can-edit (deref refs/permissions)) open-modal (mf/use-fn (fn [e] (dom/stop-propagation e) (modal/show! :tokens/themes {})))] + [:div {:class (stl/css :themes-wrapper)} [:span {:class (stl/css :themes-header)} (tr "labels.themes")] (if (empty? ordered-themes) @@ -199,13 +201,24 @@ (tr "workspace.token.no-permission-themes"))} [:& theme-select]]))])) -(mf/defc add-set-button - [{:keys [on-open style]}] - (let [{:keys [on-create new-path]} (sets-context/use-context) - on-click #(do - (on-open) - (on-create [])) - can-edit? (:can-edit (deref refs/permissions))] +(mf/defc add-set-button* + {::mf/private true} + [{:keys [style]}] + (let [{:keys [on-create new-path]} + (sets-context/use-context) + + permissions + (mf/use-ctx ctx/permissions) + + can-edit? + (get permissions :can-edit) + + on-click + (mf/use-fn + (mf/deps on-create) + (fn [] + (on-create [])))] + (if (= style "inline") (when-not new-path (if can-edit? @@ -224,67 +237,96 @@ :on-click on-click :aria-label (tr "workspace.token.add set")}])))) -(mf/defc theme-sets-list - [{:keys [on-open]}] +(mf/defc theme-sets-list* + {::mf/private true} + [] (let [token-sets (mf/deref refs/workspace-ordered-token-sets) {:keys [new-path] :as ctx} (sets-context/use-context)] (if (and (empty? token-sets) (not new-path)) - [:& add-set-button {:on-open on-open - :style "inline"}] + [:> add-set-button* {:style "inline"}] [:& h/sortable-container {} [:& sets-list]]))) -(mf/defc themes-sets-tab +(mf/defc themes-sets-tab* + {::mf/private true} [{:keys [resize-height]}] - (let [open? (mf/use-state true) - on-open (mf/use-fn #(reset! open? true)) - can-edit? (:can-edit (deref refs/permissions))] + (let [permissions + (mf/use-ctx ctx/permissions) + + can-edit? + (get permissions :can-edit)] + [:& sets-context/provider {} [:& sets-context-menu] [:article {:data-testid "token-themes-sets-sidebar" :class (stl/css :sets-section-wrapper) :style {"--resize-height" (str resize-height "px")}} [:div {:class (stl/css :sets-sidebar)} - [:& themes-header] + [:> themes-header*] [:div {:class (stl/css :sidebar-header)} [:& title-bar {:title (tr "labels.sets")} (when can-edit? - [:& add-set-button {:on-open on-open - :style "header"}])]] - [:& theme-sets-list {:on-open on-open}]]]])) + [:> add-set-button* {:style "header"}])]] -(mf/defc tokens-tab - [_props] - (let [objects (mf/deref refs/workspace-page-objects) + [:> theme-sets-list* {}]]]])) - selected (mf/deref refs/selected-shapes) - selected-shapes (into [] (keep (d/getf objects)) selected) +(mf/defc tokens-tab* + [] + (let [objects (mf/deref refs/workspace-page-objects) + selected (mf/deref refs/selected-shapes) + open-status (mf/deref ref:token-type-open-status) - active-theme-tokens (sd/use-active-theme-sets-tokens) + selected-shapes + (mf/with-memo [selected objects] + (into [] (keep (d/getf objects)) selected)) - tokens (sd/use-resolved-workspace-tokens) + active-theme-tokens + (sd/use-active-theme-tokens) - selected-token-set-tokens (mf/deref refs/workspace-selected-token-set-tokens) + tokens + (sd/use-resolved-workspace-tokens) - selected-token-set-name (mf/deref refs/workspace-selected-token-set-name) + selected-token-set-tokens + (mf/deref refs/workspace-selected-token-set-tokens) + + selected-token-set-name + (mf/deref refs/workspace-selected-token-set-name) + + tokens-by-type + (mf/with-memo [tokens selected-token-set-tokens] + (let [tokens (reduce-kv (fn [tokens k _] + (if (contains? selected-token-set-tokens k) + tokens + (dissoc tokens k))) + tokens + tokens)] + (ctob/group-by-type tokens))) + + [empty-group filled-group] + (mf/with-memo [tokens-by-type] + (get-sorted-token-groups tokens-by-type))] - token-groups (mf/with-memo [tokens selected-token-set-tokens] - (-> (select-keys tokens (keys selected-token-set-tokens)) - (sorted-token-groups)))] [:* [:& token-context-menu] [:& title-bar {:all-clickable true :title (tr "workspace.token.tokens-section-title" selected-token-set-name)}] - (for [{:keys [token-key token-type-props tokens]} (concat (:filled token-groups) - (:empty token-groups))] - [:& token-component {:key token-key - :type token-key - :selected-shapes selected-shapes - :active-theme-tokens active-theme-tokens - :tokens tokens - :token-type-props token-type-props}])])) + (for [type filled-group] + (let [tokens (get tokens-by-type type)] + [:> token-group* {:key (name type) + :is-open (get open-status type false) + :type type + :selected-shapes selected-shapes + :active-theme-tokens active-theme-tokens + :tokens tokens}])) + + (for [type empty-group] + [:> token-group* {:key (name type) + :type type + :selected-shapes selected-shapes + :active-theme-tokens active-theme-tokens + :tokens []}])])) (mf/defc import-export-button {::mf/wrap-props false} @@ -357,22 +399,21 @@ :on-click on-export} (tr "labels.export")]]])) -(mf/defc tokens-sidebar-tab - {::mf/wrap [mf/memo] - ::mf/wrap-props false} - [_props] +(mf/defc tokens-sidebar-tab* + {::mf/wrap [mf/memo]} + [] (let [{on-pointer-down-pages :on-pointer-down on-lost-pointer-capture-pages :on-lost-pointer-capture on-pointer-move-pages :on-pointer-move size-pages-opened :size} (use-resize-hook :tokens 200 38 "0.6" :y false nil)] [:div {:class (stl/css :sidebar-wrapper)} - [:& themes-sets-tab {:resize-height size-pages-opened}] + [:> themes-sets-tab* {:resize-height size-pages-opened}] [:article {:class (stl/css :tokens-section-wrapper) :data-testid "tokens-sidebar"} [:div {:class (stl/css :resize-area-horiz) :on-pointer-down on-pointer-down-pages :on-lost-pointer-capture on-lost-pointer-capture-pages :on-pointer-move on-pointer-move-pages}] - [:& tokens-tab]] + [:> tokens-tab*]] [:& import-export-button]])) diff --git a/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs b/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs index 2a1618e19..28d72e34e 100644 --- a/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs @@ -295,6 +295,8 @@ prefer-selected-token-set-tokens (merge active-theme-tokens selected-token-set-tokens)] (use-resolved-tokens prefer-selected-token-set-tokens))) -(defn use-active-theme-sets-tokens [] +(defn use-active-theme-tokens + "A hook that returns active tokens for the current active theme" + [] (-> (mf/deref refs/workspace-active-theme-sets-tokens) (use-resolved-tokens {:cache-atom !theme-tokens-cache}))) diff --git a/frontend/src/app/main/ui/workspace/tokens/theme_select.cljs b/frontend/src/app/main/ui/workspace/tokens/theme_select.cljs index 397bbb70b..f62ebadfa 100644 --- a/frontend/src/app/main/ui/workspace/tokens/theme_select.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/theme_select.cljs @@ -95,7 +95,6 @@ is-open? (:is-open? state) ;; Dropdown - dropdown-element* (mf/use-ref nil) on-close-dropdown (mf/use-fn #(swap! state* assoc :is-open? false)) on-open-dropdown @@ -118,8 +117,7 @@ current-label] [:> icon* {:icon-id i/arrow-down :class (stl/css :dropdown-button) :aria-hidden true}] [:& dropdown {:show is-open? - :on-close on-close-dropdown - :ref dropdown-element*} + :on-close on-close-dropdown} [:& theme-options {:active-theme-paths active-theme-paths :themes themes :on-close on-close-dropdown}]]])) diff --git a/frontend/src/app/main/ui/workspace/tokens/token.cljs b/frontend/src/app/main/ui/workspace/tokens/token.cljs index 97f9c7701..6056d0966 100644 --- a/frontend/src/app/main/ui/workspace/tokens/token.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/token.cljs @@ -1,6 +1,7 @@ (ns app.main.ui.workspace.tokens.token (:require [app.common.data :as d] + [app.common.data.macros :as dm] [app.main.ui.workspace.tokens.tinycolor :as tinycolor] [clojure.set :as set] [cuerdas.core :as str])) @@ -21,7 +22,9 @@ {:value parsed-value :unit unit})))) -(defn token-identifier [{:keys [name] :as _token}] +;; FIXME: looks very redundant function +(defn token-identifier + [{:keys [name] :as _token}] name) (defn attributes-map @@ -43,7 +46,7 @@ (defn token-attribute-applied? "Test if `token` is applied to a `shape` on single `token-attribute`." [token shape token-attribute] - (when-let [id (get-in shape [:applied-tokens token-attribute])] + (when-let [id (dm/get-in shape [:applied-tokens token-attribute])] (= (token-identifier token) id))) (defn token-applied? @@ -56,15 +59,18 @@ [token shapes token-attributes] (some #(token-applied? token % token-attributes) shapes)) -(defn shapes-ids-by-applied-attributes [token shapes token-attributes] - (reduce (fn [acc shape] - (let [applied-ids-by-attribute (->> (map #(when (token-attribute-applied? token shape %) - [% #{(:id shape)}]) - token-attributes) - (filter some?) - (into {}))] - (merge-with into acc applied-ids-by-attribute))) - {} shapes)) +(defn shapes-ids-by-applied-attributes + [token shapes token-attributes] + (let [conj* (fnil conj #{})] + (reduce (fn [result shape] + (let [shape-id (dm/get-prop shape :id)] + (->> token-attributes + (filter #(token-attribute-applied? token shape %)) + (reduce (fn [result attr] + (update result attr conj* shape-id)) + result)))) + {} + shapes))) (defn shapes-applied-all? [ids-by-attributes shape-ids attributes] (every? #(set/superset? (get ids-by-attributes %) shape-ids) attributes)) @@ -122,6 +128,11 @@ (defn color-token? [token] (= (:type token) :color)) + +;; FIXME: this should be precalculated ? +(defn is-reference? [token] + (str/includes? (:value token) "{")) + (defn color-bullet-color [token-color-value] (when-let [tc (tinycolor/valid-color token-color-value)] (if (tinycolor/alpha tc) diff --git a/frontend/src/app/main/ui/workspace/tokens/token_pill.cljs b/frontend/src/app/main/ui/workspace/tokens/token_pill.cljs index 209aaed62..2b7730a56 100644 --- a/frontend/src/app/main/ui/workspace/tokens/token_pill.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/token_pill.cljs @@ -1,14 +1,21 @@ +;; 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.tokens.token-pill (:require-macros [app.common.data.macros :as dm] [app.main.style :as stl]) (:require + [app.common.data :as d] [app.common.files.helpers :as cfh] - [app.common.types.tokens-lib :as ctob] [app.main.refs :as refs] [app.main.ui.components.color-bullet :refer [color-bullet]] [app.main.ui.ds.foundations.assets.icon :refer [icon*]] [app.main.ui.ds.foundations.utilities.token.token-status :refer [token-status-icon*]] + [app.main.ui.workspace.tokens.changes :as wtch] [app.main.ui.workspace.tokens.token :as wtt] [app.util.dom :as dom] [app.util.i18n :refer [tr]] @@ -75,9 +82,8 @@ ;; Helper functions (defn partially-applied-attr "Translates partially applied attributes based on the dictionary." - [app-token-keys is-applied token-type-props] - (let [{:keys [attributes all-attributes]} token-type-props - filtered-keys (if all-attributes + [app-token-keys is-applied {:keys [attributes all-attributes]}] + (let [filtered-keys (if all-attributes (filter #(contains? all-attributes %) app-token-keys) (filter #(contains? attributes %) app-token-keys))] (when is-applied @@ -94,11 +100,11 @@ (str/join ", " (map attribute-dictionary values)) "."))) grouped-values))) -(defn token-pill-tooltip - "Generates a tooltip for a given token." - [is-viewer shape token-type-props token half-applied no-valid-value ref-not-in-active-set] +(defn- generate-tooltip + "Generates a tooltip for a given token" + [is-viewer shape token half-applied no-valid-value ref-not-in-active-set] (let [{:keys [name value resolved-value type]} token - {:keys [title]} token-type-props + {:keys [title] :as token-props} (wtch/get-token-properties token) applied-tokens (:applied-tokens shape) app-token-vals (set (vals applied-tokens)) app-token-keys (keys applied-tokens) @@ -106,7 +112,7 @@ applied-to (if half-applied - (partially-applied-attr app-token-keys is-applied? token-type-props) + (partially-applied-attr app-token-keys is-applied? token-props) (tr "labels.all")) grouped-values (group-by dimensions-dictionary app-token-keys) @@ -134,54 +140,94 @@ ;; Otherwise only show the base title :else base-title))) +;; FIXME: the token thould already have precalculated references, so +;; we don't need to perform this regex operation on each rerender (defn contains-reference-value? "Extracts the value between `{}` in a string and checks if it's in the provided vector." - [text values] + [text active-tokens] (let [match (second (re-find #"\{([^}]+)\}" text))] - (boolean (some #(= % match) values)))) + (contains? active-tokens match))) -(mf/defc token-pill - {::mf/wrap-props false} - [{:keys [on-click token theme-token full-applied on-context-menu half-applied selected-shapes token-type-props active-theme-tokens]}] +(def ^:private + xf:map-id + (map :id)) + +(defn- applied-all-attributes? + [token selected-shapes attributes] + (let [ids-by-attributes (wtt/shapes-ids-by-applied-attributes token selected-shapes attributes) + shape-ids (into #{} xf:map-id selected-shapes)] + (wtt/shapes-applied-all? ids-by-attributes shape-ids attributes))) + +(mf/defc token-pill* + {::mf/wrap [mf/memo]} + [{:keys [on-click token on-context-menu selected-shapes active-theme-tokens]}] (let [{:keys [name value errors]} token - is-reference (some #(= % "{") value) + + has-selected? (pos? (count selected-shapes)) + is-reference? (wtt/is-reference? token) + contains-path? (str/includes? name ".") + + {:keys [attributes all-attributes]} + (get wtch/token-properties (:type token)) + + full-applied? + (if has-selected? + (applied-all-attributes? token selected-shapes (d/nilv all-attributes attributes)) + true) + + applied? + (if has-selected? + (wtt/shapes-token-applied? token selected-shapes (d/nilv all-attributes attributes)) + false) + + half-applied? + (and applied? (not full-applied?)) + + ;; FIXME: move to context or props can-edit? (:can-edit (deref refs/permissions)) - is-viewer (not can-edit?) - ref-not-in-active-set (and is-reference - (not (contains-reference-value? value (keys active-theme-tokens)))) + is-viewer? (not can-edit?) + + ref-not-in-active-set + (and is-reference? + (not (contains-reference-value? value active-theme-tokens))) + no-valid-value (seq errors) - errors? (or ref-not-in-active-set - no-valid-value) - color (when (seq (ctob/find-token-value-references value)) - (wtt/resolved-token-bullet-color theme-token)) - contains-path? (str/includes? name ".") - splitted-name (cfh/split-by-last-period name) - color (or color (wtt/resolved-token-bullet-color token)) + + errors? + (or ref-not-in-active-set + no-valid-value) + + color + (when (wtt/color-token? token) + (let [theme-token (get active-theme-tokens (:name token))] + (or (wtt/resolved-token-bullet-color theme-token) + (wtt/resolved-token-bullet-color token)))) on-click - (mf/use-callback - (mf/deps errors? on-click) + (mf/use-fn + (mf/deps errors? on-click token) (fn [event] (dom/stop-propagation event) (when (and (not (seq errors)) on-click) - (on-click event)))) + (on-click event token)))) - token-status-id (cond - half-applied - "token-status-partial" - full-applied - "token-status-full" - :else - "token-status-non-applied") + token-status-id + (cond + half-applied? + "token-status-partial" + full-applied? + "token-status-full" + :else + "token-status-non-applied") on-context-menu (mf/use-fn - (mf/deps can-edit? on-context-menu) + (mf/deps can-edit? on-context-menu token) (fn [e] (dom/stop-propagation e) (when can-edit? - (on-context-menu e)))) + (on-context-menu e token)))) on-click (mf/use-fn @@ -191,26 +237,29 @@ (when (and can-edit? (not (seq errors)) on-click) (on-click event)))) + ;; FIXME: missing deps on-hover (mf/use-fn - (mf/deps selected-shapes is-viewer) + (mf/deps selected-shapes is-viewer?) (fn [event] - (let [node (dom/get-current-target event) - title (token-pill-tooltip is-viewer (first selected-shapes) token-type-props token half-applied no-valid-value ref-not-in-active-set)] + (let [node (dom/get-current-target event) + title (generate-tooltip is-viewer? (first selected-shapes) token + half-applied? no-valid-value ref-not-in-active-set)] (dom/set-attribute! node "title" title))))] - [:button {:class (stl/css-case :token-pill true - :token-pill-default can-edit? - :token-pill-applied (and can-edit? (or half-applied full-applied)) - :token-pill-invalid (and can-edit? errors?) - :token-pill-invalid-applied (and full-applied errors? can-edit?) - :token-pill-viewer is-viewer - :token-pill-applied-viewer (and is-viewer - (or half-applied full-applied)) - :token-pill-invalid-viewer (and is-viewer - errors?) - :token-pill-invalid-applied-viewer (and is-viewer - (and full-applied errors?))) + [:button {:class (stl/css-case + :token-pill true + :token-pill-default can-edit? + :token-pill-applied (and can-edit? has-selected? (or half-applied? full-applied?)) + :token-pill-invalid (and can-edit? errors?) + :token-pill-invalid-applied (and full-applied? errors? can-edit?) + :token-pill-viewer is-viewer? + :token-pill-applied-viewer (and is-viewer? has-selected? + (or half-applied? full-applied?)) + :token-pill-invalid-viewer (and is-viewer? + errors?) + :token-pill-invalid-applied-viewer (and is-viewer? + (and full-applied? errors?))) :type "button" :on-click on-click :on-mouse-enter on-hover @@ -220,6 +269,7 @@ [:> icon* {:icon-id "broken-link" :class (stl/css :token-pill-icon)}] + color [:& color-bullet {:color color :mini true}] @@ -227,13 +277,13 @@ [:> token-status-icon* {:icon-id token-status-id :class (stl/css :token-pill-icon)}]) + (if contains-path? - [:span {:class (stl/css :divided-name-wrapper) - :aria-label name} - [:span {:class (stl/css :first-name-wrapper)} - (first splitted-name)] - [:span {:class (stl/css :last-name-wrapper)} - (last splitted-name)]] + (let [[first-part last-part] (cfh/split-by-last-period name)] + [:span {:class (stl/css :divided-name-wrapper) + :aria-label name} + [:span {:class (stl/css :first-name-wrapper)} first-part] + [:span {:class (stl/css :last-name-wrapper)} last-part]]) [:span {:class (stl/css :name-wrapper) :aria-label name} - name])])) \ No newline at end of file + name])])) diff --git a/frontend/src/app/main/ui/workspace/tokens/token_types.cljs b/frontend/src/app/main/ui/workspace/tokens/token_types.cljs deleted file mode 100644 index f5979add4..000000000 --- a/frontend/src/app/main/ui/workspace/tokens/token_types.cljs +++ /dev/null @@ -1,89 +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.tokens.token-types - (:require - [app.common.data :as d :refer [ordered-map]] - [app.common.types.token :as ctt] - [app.main.ui.workspace.tokens.changes :as wtch] - [clojure.set :as set])) - -(def token-types - (ordered-map - :border-radius - {:title "Border Radius" - :attributes ctt/border-radius-keys - :on-update-shape wtch/update-shape-radius-all - :modal {:key :tokens/border-radius - :fields [{:label "Border Radius" - :key :border-radius}]}} - - :color - {:title "Color" - :attributes #{:fill} - :all-attributes ctt/color-keys - :on-update-shape wtch/update-fill-stroke - :modal {:key :tokens/color - :fields [{:label "Color" :key :color}]}} - - :stroke-width - {:title "Stroke Width" - :attributes ctt/stroke-width-keys - :on-update-shape wtch/update-stroke-width - :modal {:key :tokens/stroke-width - :fields [{:label "Stroke Width" - :key :stroke-width}]}} - - :sizing - {:title "Sizing" - :attributes #{:width :height} - :all-attributes ctt/sizing-keys - :on-update-shape wtch/update-shape-dimensions - :modal {:key :tokens/sizing - :fields [{:label "Sizing" - :key :sizing}]}} - :dimensions - {:title "Dimensions" - :attributes #{:width :height} - :all-attributes (set/union - ctt/spacing-keys - ctt/sizing-keys - ctt/border-radius-keys - ctt/stroke-width-keys) - :on-update-shape wtch/update-shape-dimensions - :modal {:key :tokens/dimensions - :fields [{:label "Dimensions" - :key :dimensions}]}} - - :opacity - {:title "Opacity" - :attributes ctt/opacity-keys - :on-update-shape wtch/update-opacity - :modal {:key :tokens/opacity - :fields [{:label "Opacity" - :key :opacity}]}} - - :rotation - {:title "Rotation" - :attributes ctt/rotation-keys - :on-update-shape wtch/update-rotation - :modal {:key :tokens/rotation - :fields [{:label "Rotation" - :key :rotation}]}} - :spacing - {:title "Spacing" - :attributes #{:column-gap :row-gap} - :all-attributes ctt/spacing-keys - :on-update-shape wtch/update-layout-spacing - :modal {:key :tokens/spacing - :fields [{:label "Spacing" - :key :spacing}]}})) - -(defn get-token-properties [token] - (get token-types (:type token))) - -(defn token-attributes [token-type] - (get-in token-types [token-type :attributes])) diff --git a/frontend/src/app/util/array.cljs b/frontend/src/app/util/array.cljs index 1e56d99b3..18ef45aef 100644 --- a/frontend/src/app/util/array.cljs +++ b/frontend/src/app/util/array.cljs @@ -6,9 +6,10 @@ (ns app.util.array "A collection of helpers for work with javascript arrays." - (:refer-clojure :exclude [conj! conj filter map reduce find]) + (:refer-clojure :exclude [conj! conj filter map reduce find sort]) (:require - [cljs.core :as c])) + [cljs.core :as c] + [goog.array :as garray])) (defn conj "A conj like function for js arrays." @@ -67,3 +68,9 @@ (defn find [f v] (.find ^js/Array v f)) + +(defn sort! + [a] + (garray/sort a compare) + a) +