diff --git a/CHANGES.md b/CHANGES.md index 2b9511db5e..b872f0c4d6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -32,6 +32,7 @@ - Fix viewer can update library [Taiga #13186](https://tree.taiga.io/project/penpot/issue/13186) - Fix remove fill affects different element than selected [Taiga #13128](https://tree.taiga.io/project/penpot/issue/13128) - Fix 45 rotated board titles rendered incorrectly [Taiga #13306](https://tree.taiga.io/project/penpot/issue/13306) +- Fix cannot apply second token after creation while shape is selected [Taiga #13513](https://tree.taiga.io/project/penpot/issue/13513) ## 2.13.3 diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index e89f7c91da..4cb6cedc60 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -760,6 +760,21 @@ default v)))) +(defn percent? + [v] + (str/numeric? (str/rtrim v "%"))) + +(defn parse-percent + ([v] + (parse-percent v nil)) + ([v default] + (if (str/ends-with? v "%") + (let [v (impl-parse-double (str/trim v "%"))] + (if (or (nil? v) (nan? v)) + default + (/ v 100))) + (parse-double v default)))) + (defn parse-uuid [v] (try diff --git a/common/src/app/common/files/tokens.cljc b/common/src/app/common/files/tokens.cljc index 2da869cccf..e8f1208058 100644 --- a/common/src/app/common/files/tokens.cljc +++ b/common/src/app/common/files/tokens.cljc @@ -31,18 +31,56 @@ (def schema:token-value-generic [::sm/text {:error/fn token-value-empty-fn}]) +(def schema:token-value-numeric + [:and + [::sm/text {:error/fn token-value-empty-fn}] + [:fn {:error/fn #(tr "workspace.tokens.invalid-value" (:value %))} + (fn [value] + (if (str/numeric? value) + (let [n (d/parse-double value)] + (some? n)) + true))]]) ;; Leave references or formulas to be checked by the resolver + +(def schema:token-value-percent + [:and + [::sm/text {:error/fn token-value-empty-fn}] + [:fn {:error/fn #(tr "workspace.tokens.value-with-percent" (:value %))} + (fn [value] + (if (d/percent? value) + (let [v (d/parse-percent value)] + (some? v)) + true))]]) ;; Leave references or formulas to be checked by the resolver + (def schema:token-value-composite-ref [::sm/text {:error/fn token-value-empty-fn}]) +(def schema:token-value-opacity + [:and + [::sm/text {:error/fn token-value-empty-fn}] + [:fn {:error/fn #(tr "workspace.tokens.opacity-range")} + (fn [opacity] + (if (str/numeric? opacity) + (let [n (d/parse-percent opacity)] + (and (some? n) (<= 0 n 1))) + true))]]) ;; Leave references or formulas to be checked by the resolver + (def schema:token-value-font-family - [:vector ::sm/text]) + [:or + [:vector ::sm/text] + cto/schema:token-ref]) + +(def schema:token-value-font-weight + [:or + [:fn {:error/fn #(tr "workspace.tokens.invalid-font-weight-token-value")} + cto/valid-font-weight-variant] + ::sm/text]) ;; Leave references or formulas to be checked by the resolver (def schema:token-value-typography-map [:map [:font-family {:optional true} schema:token-value-font-family] - [:font-weight {:optional true} schema:token-value-generic] - [:font-size {:optional true} schema:token-value-generic] - [:line-height {:optional true} schema:token-value-generic] + [:font-size {:optional true} schema:token-value-numeric] + [:font-weight {:optional true} schema:token-value-font-weight] + [:line-height {:optional true} schema:token-value-percent] [:letter-spacing {:optional true} schema:token-value-generic] [:paragraph-spacing {:optional true} schema:token-value-generic] [:text-decoration {:optional true} schema:token-value-generic] @@ -84,7 +122,10 @@ [token-type] [:multi {:dispatch (constantly token-type) :title "Token Value"} + [:opacity schema:token-value-opacity] [:font-family schema:token-value-font-family] + [:font-size schema:token-value-numeric] + [:font-weight schema:token-value-font-weight] [:typography schema:token-value-typography] [:shadow schema:token-value-shadow] [::m/default schema:token-value-generic]]) @@ -169,7 +210,7 @@ [tokens-lib set-id] [:and [:string {:min 1 :max 255 :error/fn #(str (:value %) (tr "workspace.tokens.token-name-length-validation-error"))}] - [:fn {:error/fn #(tr "errors.token-set-already-exists" (:value %))} + [:fn {:error/fn #(tr "errors.token-set-already-exists")} (fn [name] (or (nil? tokens-lib) (let [set (ctob/get-set-by-name tokens-lib name)] @@ -196,7 +237,7 @@ [tokens-lib name theme-id] [:and [:string {:min 0 :max 255 :error/fn #(str (:value %) (tr "workspace.tokens.token-name-length-validation-error"))}] - [:fn {:error/fn #(tr "errors.token-theme-already-exists" (:value %))} + [:fn {:error/fn #(tr "errors.token-theme-already-exists")} (fn [group] (or (nil? tokens-lib) (let [theme (ctob/get-theme-by-name tokens-lib group name)] diff --git a/common/src/app/common/types/token.cljc b/common/src/app/common/types/token.cljc index 5f307ed223..e3e541da33 100644 --- a/common/src/app/common/types/token.cljc +++ b/common/src/app/common/types/token.cljc @@ -143,6 +143,15 @@ :gen/gen sg/text} token-name-validation-regex]) +(def token-ref-validation-regex + #"^\{[a-zA-Z0-9_-][a-zA-Z0-9$_-]*(\.[a-zA-Z0-9$_-]+)*\}$") + +(def schema:token-ref + "A token reference is a token name enclosed in {}." + [:re {:title "TokenRef" + :gen/gen sg/text} + token-ref-validation-regex]) + (def schema:token-type [::sm/one-of {:decode/json (fn [type] (if (string? type) diff --git a/exporter/src/app/browser.cljs b/exporter/src/app/browser.cljs index 526ae77380..0da27c2609 100644 --- a/exporter/src/app/browser.cljs +++ b/exporter/src/app/browser.cljs @@ -100,12 +100,14 @@ (def browser-pool-factory (letfn [(create [] - (p/let [opts #js {:args #js ["--allow-insecure-localhost" "--font-render-hinting=none"]} - browser (.launch pw/chromium opts) - id (swap! pool-browser-id inc)] - (l/info :origin "factory" :action "create" :browser-id id) - (unchecked-set browser "__id" id) - browser)) + (-> (p/let [opts #js {:args #js ["--allow-insecure-localhost" "--font-render-hinting=none"]} + browser (.launch pw/chromium opts) + id (swap! pool-browser-id inc)] + (l/info :origin "factory" :action "create" :browser-id id) + (unchecked-set browser "__id" id) + browser) + (p/catch (fn [cause] + (l/error :hint "Cannot launch the headless browser" :cause cause))))) (destroy [obj] (let [id (unchecked-get obj "__id")] diff --git a/exporter/src/app/handlers/export_shapes.cljs b/exporter/src/app/handlers/export_shapes.cljs index 29a92df61e..49913fd011 100644 --- a/exporter/src/app/handlers/export_shapes.cljs +++ b/exporter/src/app/handlers/export_shapes.cljs @@ -47,12 +47,13 @@ (s/def ::params (s/keys :req-un [::exports ::profile-id] - :opt-un [::wait ::name ::skip-children])) + :opt-un [::wait ::name ::skip-children ::force-multiple])) (defn handler - [{:keys [:request/auth-token] :as exchange} {:keys [exports] :as params}] + [{:keys [:request/auth-token] :as exchange} {:keys [exports force-multiple] :as params}] (let [exports (prepare-exports exports auth-token)] - (if (and (= 1 (count exports)) + (if (and (not force-multiple) + (= 1 (count exports)) (= 1 (count (-> exports first :objects)))) (handle-single-export exchange (-> params (assoc :export (first exports)) diff --git a/frontend/resources/images/features/2.14-api.gif b/frontend/resources/images/features/2.14-api.gif new file mode 100644 index 0000000000..714c8e5328 Binary files /dev/null and b/frontend/resources/images/features/2.14-api.gif differ diff --git a/frontend/resources/images/features/2.14-icons.gif b/frontend/resources/images/features/2.14-icons.gif new file mode 100644 index 0000000000..b7f93c3277 Binary files /dev/null and b/frontend/resources/images/features/2.14-icons.gif differ diff --git a/frontend/resources/images/features/2.14-remap.jpg b/frontend/resources/images/features/2.14-remap.jpg new file mode 100644 index 0000000000..cac9bfabe7 Binary files /dev/null and b/frontend/resources/images/features/2.14-remap.jpg differ diff --git a/frontend/resources/images/features/2.14-slide-0.jpg b/frontend/resources/images/features/2.14-slide-0.jpg new file mode 100644 index 0000000000..1ea9e1d8aa Binary files /dev/null and b/frontend/resources/images/features/2.14-slide-0.jpg differ diff --git a/frontend/resources/images/features/2.14-tokens-fold.gif b/frontend/resources/images/features/2.14-tokens-fold.gif new file mode 100644 index 0000000000..25fc333559 Binary files /dev/null and b/frontend/resources/images/features/2.14-tokens-fold.gif differ diff --git a/frontend/src/app/main/data/exports/assets.cljs b/frontend/src/app/main/data/exports/assets.cljs index 4355ad7ef9..f2c8315a90 100644 --- a/frontend/src/app/main/data/exports/assets.cljs +++ b/frontend/src/app/main/data/exports/assets.cljs @@ -195,7 +195,7 @@ params {:exports exports :cmd cmd :profile-id profile-id - :wait false} + :force-multiple true} progress-stream (->> (ws/get-rcv-stream ws-conn) diff --git a/frontend/src/app/main/data/plugins.cljs b/frontend/src/app/main/data/plugins.cljs index 5bdcdb776e..e9f5266c1b 100644 --- a/frontend/src/app/main/data/plugins.cljs +++ b/frontend/src/app/main/data/plugins.cljs @@ -14,6 +14,7 @@ [app.main.data.modal :as modal] [app.main.data.notifications :as ntf] [app.main.store :as st] + [app.plugins.flags :as pflag] [app.plugins.register :as preg] [app.util.globals :as ug] [app.util.http :as http] @@ -44,20 +45,6 @@ (update [_ state] (update-in state [:workspace-local :open-plugins] (fnil conj #{}) id)))) -(defn reset-plugin-flags - [id] - (ptk/reify ::reset-plugin-flags - ptk/UpdateEvent - (update [_ state] - (update-in state [:workspace-local :plugin-flags] assoc id {})))) - -(defn set-plugin-flag - [id key value] - (ptk/reify ::set-plugin-flag - ptk/UpdateEvent - (update [_ state] - (update-in state [:workspace-local :plugin-flags id] assoc key value)))) - (defn remove-current-plugin [id] (ptk/reify ::remove-current-plugin @@ -68,8 +55,8 @@ (defn- load-plugin! [{:keys [plugin-id name description host code icon permissions]}] (try - (st/emit! (save-current-plugin plugin-id) - (reset-plugin-flags plugin-id)) + (st/emit! (pflag/clear plugin-id) + (save-current-plugin plugin-id)) (.ɵloadPlugin ^js ug/global diff --git a/frontend/src/app/main/data/tokenscript.cljs b/frontend/src/app/main/data/tokenscript.cljs index 83a09d35e8..22e07efc66 100644 --- a/frontend/src/app/main/data/tokenscript.cljs +++ b/frontend/src/app/main/data/tokenscript.cljs @@ -69,6 +69,10 @@ (and (number-with-unit-symbol? v) (= (.-unit v) "rem"))) +(defn percent-number-with-unit? [v] + (and (number-with-unit-symbol? v) + (= (.-unit v) "%"))) + (defn rem->px [^js v] (* (.-value v) 16)) @@ -87,10 +91,12 @@ (defn tokenscript-symbols->penpot-unit [^js v] (cond + (nil? v) nil (structured-token? v) (structured-token->penpot-map v) (list-symbol? v) (structured-token->penpot-map v) (color-symbol? v) (.-value (.to v "hex")) (rem-number-with-unit? v) (rem->px v) + (percent-number-with-unit? v) (/ (.-value v) 100) :else (.-value v))) ;; Processors ------------------------------------------------------------------ diff --git a/frontend/src/app/main/data/workspace/tokens/application.cljs b/frontend/src/app/main/data/workspace/tokens/application.cljs index a761328eb2..95b0a2cf2c 100644 --- a/frontend/src/app/main/data/workspace/tokens/application.cljs +++ b/frontend/src/app/main/data/workspace/tokens/application.cljs @@ -626,8 +626,8 @@ objects (dsh/lookup-page-objects state) text-editing? (and (some? edition) (= :text (:type (get objects edition))))] - (when (and (empty? (get state :workspace-editor-state)) - (not text-editing?)) + (if (and (empty? (get state :workspace-editor-state)) + (not text-editing?)) (let [attributes-to-remove ;; Remove atomic typography tokens when applying composite and vice-verca (cond @@ -681,7 +681,12 @@ (if (rx/observable? res) res (rx/of res)))) - (rx/of (dwu/commit-undo-transaction undo-id)))))))))))))) + (rx/of (dwu/commit-undo-transaction undo-id)))))))))) + + (rx/of (ntf/show {:content (tr "workspace.tokens.error-text-edition") + :type :toast + :level :warning + :timeout 3000})))))) (defn apply-spacing-token-separated "Handles edge-case for spacing token when applying token via toggle button. diff --git a/frontend/src/app/main/data/workspace/tokens/import_export.cljs b/frontend/src/app/main/data/workspace/tokens/import_export.cljs index 7370da7921..32ba61fd70 100644 --- a/frontend/src/app/main/data/workspace/tokens/import_export.cljs +++ b/frontend/src/app/main/data/workspace/tokens/import_export.cljs @@ -8,10 +8,11 @@ (:require [app.common.json :as json] [app.common.path-names :as cpn] - [app.common.types.tokens-lib :as ctob] + [app.config :as cf] [app.main.data.notifications :as ntf] [app.main.data.style-dictionary :as sd] + [app.main.data.tokenscript :as ts] [app.main.data.workspace.tokens.errors :as wte] [app.main.store :as st] [app.util.i18n :refer [tr]] @@ -74,15 +75,18 @@ (when unknown-tokens (st/emit! (show-unknown-types-warning unknown-tokens))) (try - (->> (ctob/get-all-tokens-map tokens-lib) - (sd/resolve-tokens-with-verbose-errors) - (rx/map (fn [_] - tokens-lib)) - (rx/catch (fn [sd-error] - (let [reference-errors (extract-reference-errors sd-error)] - (if reference-errors - (rx/of tokens-lib) - (throw (wte/error-ex-info :error.import/style-dictionary-unknown-error sd-error sd-error))))))) + (let [tokens-tree (ctob/get-all-tokens-map tokens-lib) + resolved-tokens (if (contains? cf/flags :tokenscript) + (rx/of (ts/resolve-tokens tokens-tree)) + (sd/resolve-tokens-with-verbose-errors tokens-tree))] + (->> resolved-tokens + (rx/map (fn [_] + tokens-lib)) + (rx/catch (fn [sd-error] + (let [reference-errors (extract-reference-errors sd-error)] + (if reference-errors + (rx/of tokens-lib) + (throw (wte/error-ex-info :error.import/style-dictionary-unknown-error sd-error sd-error)))))))) (catch js/Error e (throw (wte/error-ex-info :error.import/style-dictionary-unknown-error "" e))))) diff --git a/frontend/src/app/main/data/workspace/tokens/propagation.cljs b/frontend/src/app/main/data/workspace/tokens/propagation.cljs index 7ba5c3970c..97e54a8f0b 100644 --- a/frontend/src/app/main/data/workspace/tokens/propagation.cljs +++ b/frontend/src/app/main/data/workspace/tokens/propagation.cljs @@ -6,13 +6,16 @@ (ns app.main.data.workspace.tokens.propagation (:require + [app.common.data :as d] [app.common.files.helpers :as cfh] [app.common.logging :as l] [app.common.time :as ct] [app.common.types.token :as ctt] [app.common.types.tokens-lib :as ctob] + [app.config :as cf] [app.main.data.helpers :as dsh] [app.main.data.style-dictionary :as sd] + [app.main.data.tokenscript :as ts] [app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.thumbnails :as dwt] [app.main.data.workspace.tokens.application :as dwta] @@ -210,10 +213,13 @@ (ptk/reify ::propagate-workspace-tokens ptk/WatchEvent (watch [_ state _] - (when-let [tokens-lib (-> (dsh/lookup-file-data state) - (get :tokens-lib))] - (->> (ctob/get-tokens-in-active-sets tokens-lib) - (sd/resolve-tokens) + (when-let [tokens-tree (-> (dsh/lookup-file-data state) + (get :tokens-lib) + (ctob/get-tokens-in-active-sets))] + (->> (if (contains? cf/flags :tokenscript) + (rx/of (-> (ts/resolve-tokens tokens-tree) + (d/update-vals #(update % :resolved-value ts/tokenscript-symbols->penpot-unit)))) + (sd/resolve-tokens tokens-tree)) (rx/mapcat (fn [sd-tokens] (let [undo-id (js/Symbol)] (rx/concat diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index b59f6f8bce..58982b33f0 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -1173,7 +1173,8 @@ (when add-component-to-variant? (rx/of (ev/event {::ev/name "add-component-to-variant"}))) (when add-new-variant? - (rx/of (ev/event {::ev/name "add-new-variant" ::ev/origin "workspace:move-shapes-to-frame"})))))))) + (rx/of (ev/event {::ev/name "add-new-variant" + ::ev/origin "workspace:move-shapes-to-frame"})))))))) (defn- get-displacement "Retrieve the correct displacement delta point for the diff --git a/frontend/src/app/main/ui/ds/controls/utilities/input_field.cljs b/frontend/src/app/main/ui/ds/controls/utilities/input_field.cljs index 53ffe32317..56ceedc848 100644 --- a/frontend/src/app/main/ui/ds/controls/utilities/input_field.cljs +++ b/frontend/src/app/main/ui/ds/controls/utilities/input_field.cljs @@ -84,6 +84,7 @@ :on-click on-icon-click}]) (if aria-label [:> tooltip* {:content aria-label + :class (stl/css :tooltip-wrapper) :id tooltip-id} [:> "input" props]] [:> "input" props]) diff --git a/frontend/src/app/main/ui/ds/controls/utilities/input_field.scss b/frontend/src/app/main/ui/ds/controls/utilities/input_field.scss index a141bb3914..80068f0c2b 100644 --- a/frontend/src/app/main/ui/ds/controls/utilities/input_field.scss +++ b/frontend/src/app/main/ui/ds/controls/utilities/input_field.scss @@ -120,3 +120,7 @@ color: var(--color-foreground-secondary); min-inline-size: var(--sp-l); } + +.tooltip-wrapper { + inline-size: 100%; +} diff --git a/frontend/src/app/main/ui/releases.cljs b/frontend/src/app/main/ui/releases.cljs index 477b3c2ee0..a5d2f5610b 100644 --- a/frontend/src/app/main/ui/releases.cljs +++ b/frontend/src/app/main/ui/releases.cljs @@ -32,6 +32,7 @@ [app.main.ui.releases.v2-11] [app.main.ui.releases.v2-12] [app.main.ui.releases.v2-13] + [app.main.ui.releases.v2-14] [app.main.ui.releases.v2-2] [app.main.ui.releases.v2-3] [app.main.ui.releases.v2-4] @@ -104,4 +105,4 @@ (defmethod rc/render-release-notes "0.0" [params] - (rc/render-release-notes (assoc params :version "2.13"))) + (rc/render-release-notes (assoc params :version "2.14"))) diff --git a/frontend/src/app/main/ui/releases/v2_14.cljs b/frontend/src/app/main/ui/releases/v2_14.cljs new file mode 100644 index 0000000000..b424d4bfa8 --- /dev/null +++ b/frontend/src/app/main/ui/releases/v2_14.cljs @@ -0,0 +1,178 @@ +;; 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.releases.v2-14 + (:require-macros [app.main.style :as stl]) + (:require + [app.common.data.macros :as dm] + [app.main.ui.releases.common :as c] + [rumext.v2 :as mf])) + +(defmethod c/render-release-notes "2.14" + [{:keys [slide klass next finish navigate version]}] + (mf/html + (case slide + :start + [:div {:class (stl/css-case :modal-overlay true)} + [:div.animated {:class klass} + [:div {:class (stl/css :modal-container)} + [:img {:src "images/features/2.14-slide-0.jpg" + :class (stl/css :start-image) + :border "0" + :alt "Penpot 2.14 is here!"}] + + [:div {:class (stl/css :modal-content)} + [:div {:class (stl/css :modal-header)} + [:h1 {:class (stl/css :modal-title)} + "What’s new in Penpot?"] + + [:div {:class (stl/css :version-tag)} + (dm/str "Version " version)]] + + [:div {:class (stl/css :features-block)} + [:span {:class (stl/css :feature-title)} + "Design tokens, but friendlier (and a bit faster, too)"] + + [:p {:class (stl/css :feature-content)} + "This release keeps pushing Penpot’s design system foundations forward, with a big focus on design tokens. We’re making long token names easier to navigate, opening up tokens in the plugins API, and tackling one of the trickiest moments in token workflows: renaming (without breaking everything)."] + + [:p {:class (stl/css :feature-content)} + "On top of that, you’ll find a handful of quality-of-life improvements and some performance work in the sidebar to keep things feeling smooth as your files grow. Let’s dive in."] + + [:p {:class (stl/css :feature-content)} + "Let’s dive in!"]] + + [:div {:class (stl/css :navigation)} + [:button {:class (stl/css :next-btn) + :on-click next} "Continue"]]]]]] + + 0 + [:div {:class (stl/css-case :modal-overlay true)} + [:div.animated {:class klass} + [:div {:class (stl/css :modal-container)} + [:img {:src "images/features/2.14-tokens-fold.gif" + :class (stl/css :start-image) + :border "0" + :alt "Token groups: Navigating long names, finally"}] + + [:div {:class (stl/css :modal-content)} + [:div {:class (stl/css :modal-header)} + [:h1 {:class (stl/css :modal-title)} + "Token groups: Navigating long names, finally"]] + + [:div {:class (stl/css :feature)} + [:p {:class (stl/css :feature-content)} + "Token names are rarely short and sweet. Most of the time they carry a lot of meaning (type, state, property, variant… and more), which is great for consistency, but not so great for browsing. In 2.14 we’re introducing token groups, a new way to navigate dotted token paths as nested, collapsible sections."] + + [:p {:class (stl/css :feature-content)} + "Token segments before the final name are displayed as groups, and only the last segment stays as a pill (so you keep the familiar token “chip” where it matters). If you unfold a path, it stays open while you move around the app (it resets only when the page reloads). And when you create a new token, Penpot automatically unfolds the path needed to reveal it (even if it overrides a previously opened one)."] + + [:p {:class (stl/css :feature-content)} + "One extra detail: if you edit the path and change group segments, the token is moved to its new group (creating it if needed), and empty groups are automatically cleaned up."]] + + [:div {:class (stl/css :navigation)} + [:& c/navigation-bullets + {:slide slide + :navigate navigate + :total 4}] + + [:button {:on-click next + :class (stl/css :next-btn)} "Continue"]]]]]] + + 1 + [:div {:class (stl/css-case :modal-overlay true)} + [:div.animated {:class klass} + [:div {:class (stl/css :modal-container)} + [:img {:src "images/features/2.14-api.gif" + :class (stl/css :start-image) + :border "0" + :alt "Design tokens in the plugins API: Automation unlocked"}] + + [:div {:class (stl/css :modal-content)} + [:div {:class (stl/css :modal-header)} + [:h1 {:class (stl/css :modal-title)} + "Design tokens in the plugins API: Automation unlocked"]] + + [:div {:class (stl/css :feature)} + [:p {:class (stl/css :feature-content)} + "Design tokens are now available in the Penpot plugins API. That means plugins (and external tools built around Penpot, like AI clients or Penpot MCP) can finally work with tokens programmatically and automate token workflows that used to be purely manual."] + + [:p {:class (stl/css :feature-content)} + "If you’ve been waiting to generate tokens, sync them, or manipulate them from your own tools, this is the missing piece. And yes, this one has been requested a lot."]] + + [:div {:class (stl/css :navigation)} + [:& c/navigation-bullets + {:slide slide + :navigate navigate + :total 4}] + + [:button {:on-click next + :class (stl/css :next-btn)} "Continue"]]]]]] + + 2 + [:div {:class (stl/css-case :modal-overlay true)} + [:div.animated {:class klass} + [:div {:class (stl/css :modal-container)} + [:img {:src "images/features/2.14-remap.jpg" + :class (stl/css :start-image) + :border "0" + :alt "Rename tokens without breaking everything"}] + + [:div {:class (stl/css :modal-content)} + [:div {:class (stl/css :modal-header)} + [:h1 {:class (stl/css :modal-title)} + "Rename tokens without breaking everything"]] + + [:div {:class (stl/css :feature)} + [:p {:class (stl/css :feature-content)} + "Renaming tokens sounds simple until you remember the references. One change can ripple through aliases, applied tokens, tooltips, math operations… and suddenly you’re left with a broken chain. In 2.14, renaming a token can optionally remap its references, keeping connections intact and updating the design with the new token name."] + + [:p {:class (stl/css :feature-content)} + "Remapping is always optional, because sometimes you don’t want to keep the current connections. When enabled, it affects all tokens in the file and also takes libraries into account, so main components can propagate changes to child components, and applied tokens update on the elements using them."]] + + [:div {:class (stl/css :navigation)} + [:& c/navigation-bullets + {:slide slide + :navigate navigate + :total 4}] + + [:button {:on-click next + :class (stl/css :next-btn)} "Continue"]]]]]] + + 3 + [:div {:class (stl/css-case :modal-overlay true)} + [:div.animated {:class klass} + [:div {:class (stl/css :modal-container)} + [:img {:src "images/features/2.14-icons.gif" + :class (stl/css :start-image) + :border "0" + :alt "Quality-of-life improvements"}] + + [:div {:class (stl/css :modal-content)} + [:div {:class (stl/css :modal-header)} + [:h1 {:class (stl/css :modal-title)} + "Quality-of-life improvements"]] + [:div {:class (stl/css :feature)} + + [:p {:class (stl/css :feature-content)} + "Lock and hide controls in the layer panel are getting a usability boost. The lock and visibility icons stay fixed in a right-aligned column regardless of indentation, and scrolling won’t make them awkward to click (even in deeply nested files)."] + + [:p {:class (stl/css :feature-content)} + "We’re also improving sidebar performance, with a focus on keeping interactions fluent. The goal is to lazy-load the shape list on-demand and avoid UI stalls when clicking or hovering around the sidebar."] + + [:p {:class (stl/css :feature-content)} + "And one more: you can now use Shift/Alt arrow key stepping in color picker inputs (a community contribution by @eureka928. ❤️)"]] + + [:div {:class (stl/css :navigation)} + + [:& c/navigation-bullets + {:slide slide + :navigate navigate + :total 4}] + + [:button {:on-click finish + :class (stl/css :next-btn)} "Let's go"]]]]]]))) + diff --git a/frontend/src/app/main/ui/releases/v2_14.scss b/frontend/src/app/main/ui/releases/v2_14.scss new file mode 100644 index 0000000000..e5d13841eb --- /dev/null +++ b/frontend/src/app/main/ui/releases/v2_14.scss @@ -0,0 +1,102 @@ +// 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 + +@use "refactor/common-refactor.scss" as deprecated; + +.modal-overlay { + @extend .modal-overlay-base; +} + +.modal-container { + display: grid; + grid-template-columns: deprecated.$s-324 1fr; + height: deprecated.$s-500; + width: deprecated.$s-888; + border-radius: deprecated.$br-8; + background-color: var(--modal-background-color); + border: deprecated.$s-2 solid var(--modal-border-color); +} + +.start-image { + width: deprecated.$s-324; + border-radius: deprecated.$br-8 0 0 deprecated.$br-8; +} + +.modal-content { + padding: deprecated.$s-40; + display: grid; + grid-template-rows: auto 1fr deprecated.$s-32; + gap: deprecated.$s-24; + + a { + color: var(--button-primary-background-color-rest); + } +} + +.modal-header { + display: grid; + gap: deprecated.$s-8; +} + +.version-tag { + @include deprecated.flexCenter; + @include deprecated.headlineSmallTypography; + height: deprecated.$s-32; + width: deprecated.$s-96; + background-color: var(--communication-tag-background-color); + color: var(--communication-tag-foreground-color); + border-radius: deprecated.$br-8; +} + +.modal-title { + @include deprecated.headlineLargeTypography; + color: var(--modal-title-foreground-color); +} + +.features-block { + display: flex; + flex-direction: column; + gap: deprecated.$s-16; + width: deprecated.$s-440; +} + +.feature { + display: flex; + flex-direction: column; + gap: deprecated.$s-8; +} + +.feature-title { + @include deprecated.bodyLargeTypography; + color: var(--modal-title-foreground-color); +} + +.feature-content { + @include deprecated.bodyMediumTypography; + margin: 0; + color: var(--modal-text-foreground-color); +} + +.feature-list { + @include deprecated.bodyMediumTypography; + color: var(--modal-text-foreground-color); + list-style: disc; + display: grid; + gap: deprecated.$s-8; +} + +.navigation { + width: 100%; + display: grid; + grid-template-areas: "bullets button"; +} + +.next-btn { + @extend .button-primary; + width: deprecated.$s-100; + justify-self: flex-end; + grid-area: button; +} diff --git a/frontend/src/app/main/ui/workspace/tokens/management.cljs b/frontend/src/app/main/ui/workspace/tokens/management.cljs index 1f124b7b8b..0ff4deafa4 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management.cljs @@ -143,8 +143,7 @@ (let [token-ids (set tokens-in-path-ids) remaining-tokens (filter (fn [token] (not (contains? token-ids (:id token)))) - selected-token-set-tokens) - _ (prn "Remaining tokens:" remaining-tokens)] + selected-token-set-tokens)] (seq remaining-tokens)))) delete-token diff --git a/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/color_input.cljs b/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/color_input.cljs index b87bb0d675..c9c2f0aaf0 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/color_input.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/color_input.cljs @@ -13,8 +13,10 @@ [app.common.types.color :as cl] [app.common.types.token :as cto] [app.common.types.tokens-lib :as ctob] + [app.config :as cf] [app.main.data.style-dictionary :as sd] [app.main.data.tinycolor :as tinycolor] + [app.main.data.tokenscript :as ts] [app.main.data.workspace.tokens.format :as dwtf] [app.main.refs :as refs] [app.main.ui.ds.controls.input :as ds] @@ -70,11 +72,15 @@ (dissoc (:name prev-token)) (update (:name token) #(ctob/make-token (merge % prev-token token))))] - (->> tokens - (sd/resolve-tokens-interactive) + (->> (if (contains? cf/flags :tokenscript) + (rx/of (ts/resolve-tokens tokens)) + (sd/resolve-tokens-interactive tokens)) (rx/mapcat (fn [resolved-tokens] - (let [{:keys [errors resolved-value] :as resolved-token} (get resolved-tokens (:name token))] + (let [{:keys [errors resolved-value] :as resolved-token} (get resolved-tokens (:name token)) + resolved-value (if (contains? cf/flags :tokenscript) + (ts/tokenscript-symbols->penpot-unit resolved-value) + resolved-value)] (if resolved-value (rx/of {:value resolved-value}) (rx/of {:error (first errors)})))))))) diff --git a/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/fonts_combobox.cljs b/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/fonts_combobox.cljs index df76d47113..057419dc9c 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/fonts_combobox.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/fonts_combobox.cljs @@ -10,7 +10,9 @@ [app.common.data :as d] [app.common.types.token :as cto] [app.common.types.tokens-lib :as ctob] + [app.config :as cf] [app.main.data.style-dictionary :as sd] + [app.main.data.tokenscript :as ts] [app.main.fonts :as fonts] [app.main.ui.ds.buttons.icon-button :refer [icon-button*]] [app.main.ui.ds.controls.input :refer [input*]] @@ -49,28 +51,30 @@ ;; validate data within the form state. (defn- resolve-value - [tokens prev-token token-name value] - (let [valid-token-name? - (and (string? token-name) - (re-matches cto/token-name-validation-regex token-name)) + [tokens prev-token _token-name value] + (let [tmp-value (cto/split-font-family value) + tmp-name "__PENPOT__FONT_FAMILY__PLACEHOLDER__" + ;; Create a temporary font-family token to validate the value token - {:value (cto/split-font-family value) - :name (if (or (not valid-token-name?) (str/blank? token-name)) - "__PENPOT__TOKEN__NAME__PLACEHOLDER__" - token-name)} + {:name tmp-name + :type :font-family + :value (if (= (:type prev-token) :typography) + (assoc (:value prev-token) :font-family tmp-value) + tmp-value)} tokens - (-> tokens - ;; Remove previous token when renaming a token - (dissoc (:name prev-token)) - (update (:name token) #(ctob/make-token (merge % prev-token token))))] + (update tokens (:name token) #(ctob/make-token (merge % prev-token token)))] - (->> tokens - (sd/resolve-tokens-interactive) + (->> (if (contains? cf/flags :tokenscript) + (rx/of (ts/resolve-tokens tokens)) + (sd/resolve-tokens-interactive tokens)) (rx/mapcat (fn [resolved-tokens] - (let [{:keys [errors resolved-value] :as resolved-token} (get resolved-tokens (:name token))] + (let [{:keys [errors resolved-value] :as resolved-token} (get resolved-tokens (:name token)) + resolved-value (if (contains? cf/flags :tokenscript) + (ts/tokenscript-symbols->penpot-unit resolved-value) + resolved-value)] (if resolved-value (rx/of {:value resolved-value}) (rx/of {:error (first errors)})))))))) @@ -176,7 +180,6 @@ (let [message (tr "workspace.tokens.resolved-value" value)] (swap! form update :extra-errors dissoc input-name) (reset! hint* {:message message :type "hint"})))))))] - (fn [] (rx/dispose! subs)))) diff --git a/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs b/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs index 6b177aa243..6ab3f2786b 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs @@ -175,7 +175,10 @@ (sd/resolve-tokens-interactive) (rx/mapcat (fn [resolved-tokens] - (let [{:keys [errors resolved-value] :as resolved-token} (get resolved-tokens (:name token))] + (let [{:keys [errors resolved-value] :as resolved-token} (get resolved-tokens (:name token)) + resolved-value (if (contains? cf/flags :tokenscript) + (ts/tokenscript-symbols->penpot-unit resolved-value) + resolved-value)] (if resolved-value (rx/of {:value resolved-value}) (rx/of {:error (first errors)})))))))) diff --git a/frontend/src/app/main/ui/workspace/tokens/management/forms/generic_form.cljs b/frontend/src/app/main/ui/workspace/tokens/management/forms/generic_form.cljs index b0f36ec06c..f5541f8a40 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/forms/generic_form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/forms/generic_form.cljs @@ -36,9 +36,9 @@ [rumext.v2 :as mf])) (defn get-value-for-validator - [active-tab value value-subfield form-type] + [active-tab value value-subfield value-type] - (case form-type + (case value-type :indexed (if (= active-tab :reference) (:reference value) @@ -62,7 +62,7 @@ make-schema input-component initial - type + value-type value-subfield input-value-placeholder] :as props}] @@ -178,13 +178,13 @@ on-submit (mf/use-fn - (mf/deps validate-token token tokens token-type value-subfield type active-tab on-remap-token on-rename-token is-create) + (mf/deps validate-token token tokens token-type value-subfield value-type active-tab on-remap-token on-rename-token is-create) (fn [form _event] (let [name (get-in @form [:clean-data :name]) path (str (d/name token-type) "." name) description (get-in @form [:clean-data :description]) value (get-in @form [:clean-data :value]) - value-for-validation (get-value-for-validator active-tab value value-subfield type)] + value-for-validation (get-value-for-validator active-tab value value-subfield value-type)] (->> (validate-token {:token-value value-for-validation :token-name name :token-description description @@ -245,7 +245,7 @@ :auto-focus true}]] [:div {:class (stl/css :input-row)} - (case type + (case value-type :indexed [:> input-component {:token token diff --git a/frontend/src/app/main/ui/workspace/tokens/management/forms/shadow.cljs b/frontend/src/app/main/ui/workspace/tokens/management/forms/shadow.cljs index 9bf6fa32a2..722683d54a 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/forms/shadow.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/forms/shadow.cljs @@ -365,7 +365,7 @@ :token-type token-type :initial initial :make-schema make-schema - :type :indexed + :value-type :indexed :value-subfield :shadow :input-component tabs-wrapper* :validator validate-shadow-token})] diff --git a/frontend/src/app/main/ui/workspace/tokens/management/forms/typography.cljs b/frontend/src/app/main/ui/workspace/tokens/management/forms/typography.cljs index 37a7fda7ca..cb926f72fd 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/forms/typography.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/forms/typography.cljs @@ -300,6 +300,6 @@ :make-schema make-schema :token token :validator validate-typography-token - :type :composite + :value-type :composite :input-component tabs-wrapper*})] [:> generic/form* props])) diff --git a/frontend/src/app/main/ui/workspace/tokens/management/forms/validators.cljs b/frontend/src/app/main/ui/workspace/tokens/management/forms/validators.cljs index ab04acbd64..4fb9be3e4f 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/forms/validators.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/forms/validators.cljs @@ -4,7 +4,9 @@ [app.common.schema :as sm] [app.common.types.token :as cto] [app.common.types.tokens-lib :as ctob] + [app.config :as cf] [app.main.data.style-dictionary :as sd] + [app.main.data.tokenscript :as ts] [app.main.data.workspace.tokens.errors :as wte] [beicon.v2.core :as rx] [cuerdas.core :as str])) @@ -36,14 +38,20 @@ :always (update (:name token) #(ctob/make-token (merge % prev-token token))))] - (->> tokens' - (sd/resolve-tokens-interactive) + + (->> (if (contains? cf/flags :tokenscript) + (rx/of (ts/resolve-tokens tokens')) + (sd/resolve-tokens-interactive tokens')) (rx/mapcat (fn [resolved-tokens] - (let [{:keys [errors resolved-value] :as resolved-token} (get resolved-tokens (:name token))] + (let [resolved-token (cond-> (get resolved-tokens (:name token)) + (contains? cf/flags :tokenscript) + (update :resolved-value ts/tokenscript-symbols->penpot-unit))] (cond - resolved-value (rx/of resolved-token) - :else (rx/throw {:errors (or (seq errors) + (:resolved-value resolved-token) + (rx/of resolved-token) + + :else (rx/throw {:errors (or (seq (:errors resolved-token)) [(wte/get-error-code :error/unknown-error)])})))))))) (defn- validate-token-with [token validators] diff --git a/frontend/src/app/main/ui/workspace/tokens/management/group.cljs b/frontend/src/app/main/ui/workspace/tokens/management/group.cljs index 7500f45db3..fb37dc9240 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/group.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/group.cljs @@ -12,6 +12,7 @@ [app.common.data.macros :as dm] [app.common.types.tokens-lib :as ctob] [app.main.data.modal :as modal] + [app.main.data.notifications :as ntf] [app.main.data.workspace.tokens.application :as dwta] [app.main.data.workspace.tokens.library-edit :as dwtl] [app.main.refs :as refs] @@ -136,13 +137,17 @@ on-token-pill-click (mf/use-fn - (mf/deps not-editing? selected-ids) + (mf/deps not-editing? selected-ids tokens-lib) (fn [event token] (let [token (ctob/get-token tokens-lib selected-token-set-id (:id token))] (dom/stop-propagation event) - (when (and not-editing? (seq selected-shapes) (not= (:type token) :number)) + (if (and not-editing? (seq selected-shapes) (not= (:type token) :number)) (st/emit! (dwta/toggle-token {:token token - :shape-ids selected-ids}))))))] + :shape-ids selected-ids})) + (st/emit! (ntf/show {:content (tr "workspace.tokens.error-text-edition") + :type :toast + :level :warning + :timeout 3000}))))))] [:div {:class (stl/css :token-section-wrapper) :data-testid (dm/str "section-" (name type))} diff --git a/frontend/src/app/main/ui/workspace/tokens/management/token_pill.cljs b/frontend/src/app/main/ui/workspace/tokens/management/token_pill.cljs index 94ac737ed8..c76651c151 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/token_pill.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/token_pill.cljs @@ -176,9 +176,10 @@ (mf/defc token-pill* {::mf/wrap [mf/memo]} [{:keys [on-click token on-context-menu selected-shapes is-selected-inside-layout active-theme-tokens]}] - (let [{:keys [name value errors type]} token + (let [{:keys [name value type]} token resolved-token (get active-theme-tokens (:name token)) + errors (:errors resolved-token) has-selected? (pos? (count selected-shapes)) is-reference? (cfo/is-reference? token) diff --git a/frontend/src/app/plugins/flags.cljs b/frontend/src/app/plugins/flags.cljs index 0e8a10a5da..a9f1a6dce7 100644 --- a/frontend/src/app/plugins/flags.cljs +++ b/frontend/src/app/plugins/flags.cljs @@ -6,10 +6,30 @@ (ns app.plugins.flags (:require - [app.main.data.plugins :as dp] + [app.common.data.macros :as dm] [app.main.store :as st] [app.plugins.utils :as u] - [app.util.object :as obj])) + [app.util.object :as obj] + [potok.v2.core :as ptk])) + +(defn natural-child-ordering? + [plugin-id] + (boolean + (dm/get-in @st/state [:plugins :flags plugin-id :natural-child-ordering]))) + +(defn clear + [id] + (ptk/reify ::reset + ptk/UpdateEvent + (update [_ state] + (update-in state [:plugins :flags] assoc id {})))) + +(defn- set-flag + [id key value] + (ptk/reify ::set-flag + ptk/UpdateEvent + (update [_ state] + (update-in state [:plugins :flags id] assoc key value)))) (defn flags-proxy [plugin-id] @@ -17,11 +37,7 @@ :naturalChildOrdering {:this false :get - (fn [] - (boolean - (get-in - @st/state - [:workspace-local :plugin-flags plugin-id :natural-child-ordering]))) + (fn [] (natural-child-ordering? plugin-id)) :set (fn [value] @@ -30,4 +46,4 @@ (u/display-not-valid :naturalChildOrdering value) :else - (st/emit! (dp/set-plugin-flag plugin-id :natural-child-ordering value))))})) + (st/emit! (set-flag plugin-id :natural-child-ordering value))))})) diff --git a/frontend/src/app/plugins/flex.cljs b/frontend/src/app/plugins/flex.cljs index 8f52299bd7..9ae4525a5f 100644 --- a/frontend/src/app/plugins/flex.cljs +++ b/frontend/src/app/plugins/flex.cljs @@ -10,12 +10,12 @@ [app.common.schema :as sm] [app.common.types.shape.layout :as ctl] [app.main.data.workspace.shape-layout :as dwsl] - [app.main.data.workspace.transforms :as dwt] + [app.main.data.workspace.shapes :as dwsh] [app.main.store :as st] + [app.plugins.flags :refer [natural-child-ordering?]] [app.plugins.register :as r] [app.plugins.utils :as u] - [app.util.object :as obj] - [potok.v2.core :as ptk])) + [app.util.object :as obj])) ;; Define in `app.plugins.shape` we do this way to prevent circular dependency (def shape-proxy? nil) @@ -259,10 +259,13 @@ (u/display-not-valid :appendChild child) :else - (let [child-id (obj/get child "$id")] - (st/emit! (dwt/move-shapes-to-frame #{child-id} id nil nil) - (ptk/data-event :layout/update {:ids [id]}))))))) - + (let [child-id (obj/get child "$id") + shape (u/locate-shape file-id page-id id) + index + (if (and (natural-child-ordering? plugin-id) (not (ctl/reverse? shape))) + 0 + (count (:shapes shape)))] + (st/emit! (dwsh/relocate-shapes #{child-id} id index))))))) (defn layout-child-proxy? [p] (obj/type-of? p "LayoutChildProxy")) diff --git a/frontend/src/app/plugins/shape.cljs b/frontend/src/app/plugins/shape.cljs index c409df8b16..5a0c8f6634 100644 --- a/frontend/src/app/plugins/shape.cljs +++ b/frontend/src/app/plugins/shape.cljs @@ -47,13 +47,13 @@ [app.main.data.workspace.variants :as dwv] [app.main.repo :as rp] [app.main.store :as st] + [app.plugins.flags :refer [natural-child-ordering?]] [app.plugins.flex :as flex] [app.plugins.format :as format] [app.plugins.grid :as grid] [app.plugins.parser :as parser] [app.plugins.register :as r] [app.plugins.ruler-guides :as rg] - [app.plugins.state :refer [natural-child-ordering?]] [app.plugins.text :as text] [app.plugins.utils :as u] [app.util.http :as http] @@ -960,9 +960,12 @@ (u/display-not-valid :appendChild "Plugin doesn't have 'content:write' permission") :else - (let [child-id (obj/get child "$id") + (let [child-id (obj/get child "$id") is-reversed? (ctl/flex-layout? shape) - index (if (and (natural-child-ordering? plugin-id) is-reversed?) 0 (count (:shapes shape)))] + index + (if (or (not (natural-child-ordering? plugin-id)) is-reversed?) + 0 + (count (:shapes shape)))] (st/emit! (dwsh/relocate-shapes #{child-id} id index)))))) :insertChild @@ -985,7 +988,7 @@ (let [child-id (obj/get child "$id") is-reversed? (ctl/flex-layout? shape) index - (if (and (natural-child-ordering? plugin-id) is-reversed?) + (if (or (not (natural-child-ordering? plugin-id)) is-reversed?) (- (count (:shapes shape)) index) index)] (st/emit! (dwsh/relocate-shapes #{child-id} id index)))))) diff --git a/frontend/src/app/plugins/state.cljs b/frontend/src/app/plugins/state.cljs deleted file mode 100644 index 25c931571f..0000000000 --- a/frontend/src/app/plugins/state.cljs +++ /dev/null @@ -1,16 +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.plugins.state - (:require - [app.main.store :as st])) - -(defn natural-child-ordering? - [plugin-id] - (boolean - (get-in - @st/state - [:workspace-local :plugin-flags plugin-id :natural-child-ordering]))) diff --git a/frontend/src/app/plugins/tokens.cljs b/frontend/src/app/plugins/tokens.cljs index dd4c18c592..268652e334 100644 --- a/frontend/src/app/plugins/tokens.cljs +++ b/frontend/src/app/plugins/tokens.cljs @@ -8,18 +8,17 @@ (:require [app.common.data.macros :as dm] [app.common.files.tokens :as cfo] + [app.common.json :as json] [app.common.schema :as sm] [app.common.types.token :as cto] [app.common.types.tokens-lib :as ctob] [app.common.uuid :as uuid] - [app.main.data.style-dictionary :as sd] [app.main.data.tokenscript :as ts] [app.main.data.workspace.tokens.application :as dwta] [app.main.data.workspace.tokens.library-edit :as dwtl] [app.main.store :as st] [app.plugins.utils :as u] [app.util.object :as obj] - [beicon.v2.core :as rx] [clojure.datafy :refer [datafy]])) ;; === Token @@ -87,7 +86,7 @@ :get (fn [_] (let [token (u/locate-token file-id set-id id)] - (:value token))) + (json/->js (:value token)))) :schema (let [token (u/locate-token file-id set-id id)] (cfo/make-token-value-schema (:type token))) :set @@ -260,20 +259,19 @@ :decode/options {:key-fn identity} :fn (fn [attrs] (let [tokens-lib (u/locate-tokens-lib file-id) - tokens-tree (ctob/get-tokens-in-active-sets tokens-lib) - token (ctob/make-token attrs)] - (->> (assoc tokens-tree (:name token) token) - (sd/resolve-tokens-interactive) - (rx/subs! - (fn [resolved-tokens] - (let [{:keys [errors resolved-value] :as resolved-token} (get resolved-tokens (:name token))] - (if resolved-value - (st/emit! (dwtl/create-token id token)) - (u/display-not-valid :addToken (str errors))))))) - ;; TODO: as the addToken function is synchronous, we must return the newly created - ;; token even if the validator will throw it away if the resolution fails. - ;; This will be solved with the TokenScript resolver, that is syncronous. - (token-proxy plugin-id file-id id (:id token))))} + token (ctob/make-token attrs) + tokens-tree (-> (ctob/get-tokens-in-active-sets tokens-lib) + (assoc (:name token) token)) + resolved-tokens (ts/resolve-tokens tokens-tree) + + {:keys [errors resolved-value] :as resolved-token} + (get resolved-tokens (:name token))] + + (if resolved-value + (do (st/emit! (dwtl/create-token id token)) + (token-proxy plugin-id file-id id (:id token))) + (do (u/display-not-valid :addToken (str errors)) + nil))))} :duplicate (fn [] @@ -354,7 +352,17 @@ (st/emit! (dwtl/toggle-token-theme-active id))) :activeSets - {:this true :get (fn [_])} + {:this true + :get (fn [_] + (let [tokens-lib (u/locate-tokens-lib file-id) + theme (u/locate-token-theme file-id id)] + (->> theme + :sets + (map #(->> % + (ctob/get-set-by-name tokens-lib) + (ctob/get-id) + (token-set-proxy plugin-id file-id))) + (apply array))))} :addSet {:enumerable false diff --git a/frontend/translations/en.po b/frontend/translations/en.po index f4928f67f8..495fd1b9c2 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -8366,6 +8366,10 @@ msgstr "Invalid value: Units are not allowed." msgid "workspace.tokens.warning-name-change" msgstr "Renaming this token will break any reference to its old name" +#: src/app/main/data/workspace/tokens/application.cljs +msgid "workspace.tokens.error-text-edition" +msgstr "Tokens can't be applied while editing text. Select the text layer instead." + #: src/app/main/ui/workspace/sidebar.cljs:159, src/app/main/ui/workspace/sidebar.cljs:166 msgid "workspace.toolbar.assets" msgstr "Assets" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 683f73d6c4..f30b9e12e3 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -8246,6 +8246,10 @@ msgstr "" "Cambiar el nombre de este token romperá cualquier referencia a su nombre " "anterior." +#: src/app/main/data/workspace/tokens/application.cljs +msgid "workspace.tokens.error-text-edition" +msgstr "No se pueden aplicar tokens mientras se edita texto. Seleccione la capa de texto en su lugar." + #: src/app/main/ui/workspace/sidebar.cljs:159, src/app/main/ui/workspace/sidebar.cljs:166 msgid "workspace.toolbar.assets" msgstr "Recursos" diff --git a/plugins/apps/poc-tokens-plugin/src/app/app.component.html b/plugins/apps/poc-tokens-plugin/src/app/app.component.html index f5ef5ff940..9bb6fc8b9a 100644 --- a/plugins/apps/poc-tokens-plugin/src/app/app.component.html +++ b/plugins/apps/poc-tokens-plugin/src/app/app.component.html @@ -18,7 +18,12 @@