From 1f793e9c15ec4ecac8d36f4579fcb145091be82e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Mon, 27 Jul 2026 17:52:25 +0200 Subject: [PATCH] :tada: Make tokens library readonly when it's in an external file --- .../app/main/data/workspace/libraries.cljs | 8 +- frontend/src/app/main/ui/context.cljs | 1 + .../tokens/management/context_menu.cljs | 55 +++-- .../ui/workspace/tokens/management/group.cljs | 6 +- .../tokens/management/token_tree.cljs | 15 +- .../app/main/ui/workspace/tokens/sets.cljs | 13 +- .../main/ui/workspace/tokens/sets/lists.cljs | 43 ++-- .../app/main/ui/workspace/tokens/sidebar.cljs | 57 +++-- .../app/main/ui/workspace/tokens/themes.cljs | 10 +- frontend/src/app/plugins/tokens.cljs | 211 +++++++++++------- frontend/src/app/plugins/utils.cljs | 20 +- .../src/app/app.component.html | 130 ++++++----- .../src/app/app.component.ts | 7 + plugins/apps/poc-tokens-plugin/src/plugin.ts | 8 + plugins/libs/plugin-types/index.d.ts | 8 + 15 files changed, 356 insertions(+), 236 deletions(-) diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index b41bc8531c..36547e9d18 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -1682,10 +1682,10 @@ (conj $ library-id) (map #(load-library-file file-id %) $)))) (rx/catch (fn [cause] - (let [error (ex-data cause)] - (if (= (:code error) :circular-library-reference) - (rx/of (ntf/error (tr "errors.circular-library-reference"))) - (rx/throw cause)))))) + (let [error (ex-data cause)] + (if (= (:code error) :circular-library-reference) + (rx/of (ntf/error (tr "errors.circular-library-reference"))) + (rx/throw cause)))))) (rx/of (ptk/reify ::link-file-to-library-finished)) (when (pos? variants-count) (->> (rp/cmd! :get-library-usage {:file-id library-id}) diff --git a/frontend/src/app/main/ui/context.cljs b/frontend/src/app/main/ui/context.cljs index d6ea89cc4e..9ec62358d7 100644 --- a/frontend/src/app/main/ui/context.cljs +++ b/frontend/src/app/main/ui/context.cljs @@ -47,6 +47,7 @@ (def permissions (mf/create-context nil)) (def can-edit? (mf/create-context nil)) +(def can-edit-tokens? (mf/create-context nil)) (def active-tokens-by-type "Active tokens by type, used mainly for provide tokens data to the diff --git a/frontend/src/app/main/ui/workspace/tokens/management/context_menu.cljs b/frontend/src/app/main/ui/workspace/tokens/management/context_menu.cljs index 99af4965fd..b9c114bf67 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/context_menu.cljs @@ -19,6 +19,7 @@ [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.dropdown :refer [dropdown]] + [app.main.ui.context :as ctx] [app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i] [app.main.ui.hooks :as hooks] [app.util.clipboard :as clipboard] @@ -346,31 +347,39 @@ (defn default-actions [{:keys [token selected-token-set-id on-delete-token errors]}] (let [{:keys [modal]} (dwta/get-token-properties token) + + can-edit-tokens? + (mf/use-ctx ctx/can-edit-tokens?) + on-copy-name #(clipboard/to-clipboard (:name token)) on-duplicate-token #(st/emit! (dwtl/duplicate-token (:id token)))] - [{:title (tr "workspace.tokens.edit") - :no-selectable true - :action (fn [event] - (let [{:keys [key fields]} modal] - (dom/stop-propagation event) - (st/emit! (dwtl/assign-token-context-menu nil) - (modal/show key {:x (.-clientX ^js event) - :y (.-clientY ^js event) - :position :right - :fields fields - :initial-errors errors - :action "edit" - :selected-token-set-id selected-token-set-id - :token token}))))} - {:title (tr "workspace.tokens.duplicate") - :no-selectable true - :action on-duplicate-token} - {:title (tr "workspace.tokens.copy-name") - :no-selectable true - :action on-copy-name} - {:title (tr "workspace.tokens.delete") - :no-selectable true - :action #(on-delete-token token)}])) + (concat + [] + (when can-edit-tokens? + [{:title (tr "workspace.tokens.edit") + :no-selectable true + :action (fn [event] + (let [{:keys [key fields]} modal] + (dom/stop-propagation event) + (st/emit! (dwtl/assign-token-context-menu nil) + (modal/show key {:x (.-clientX ^js event) + :y (.-clientY ^js event) + :position :right + :fields fields + :initial-errors errors + :action "edit" + :selected-token-set-id selected-token-set-id + :token token}))))} + {:title (tr "workspace.tokens.duplicate") + :no-selectable true + :action on-duplicate-token}]) + [{:title (tr "workspace.tokens.copy-name") + :no-selectable true + :action on-copy-name}] + (when can-edit-tokens? + [{:title (tr "workspace.tokens.delete") + :no-selectable true + :action #(on-delete-token token)}])))) (defn- allowed-shape-attributes [shapes] 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 4872c116d9..a658001be1 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/group.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/group.cljs @@ -92,8 +92,8 @@ (not (and (some? edition) (= :text (:type (get objects edition)))))) - can-edit? - (mf/use-ctx ctx/can-edit?) + can-edit-tokens? + (mf/use-ctx ctx/can-edit-tokens?) is-selected-inside-layout (d/nilv is-selected-inside-layout false) @@ -177,7 +177,7 @@ :aria-controls (dm/str "token-tree-" (name type)) :on-toggle-expand on-toggle-open-click :icon (token-section-icon type)} - (when can-edit? + (when can-edit-tokens? [:> icon-button* {:id (str "add-token-button-" title) :icon "add" :aria-label (tr "workspace.tokens.add-token" title) diff --git a/frontend/src/app/main/ui/workspace/tokens/management/token_tree.cljs b/frontend/src/app/main/ui/workspace/tokens/management/token_tree.cljs index c7528941bb..7930f784fc 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/token_tree.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/token_tree.cljs @@ -140,14 +140,17 @@ on-pill-context-menu on-node-context-menu]}] (let [separator "." - raw-tree (mf/with-memo [tokens] - (cpn/build-tree-root tokens separator)) - permissions (mf/use-ctx ctx/permissions) - can-edit? (:can-edit permissions) + raw-tree + (mf/with-memo [tokens] + (cpn/build-tree-root tokens separator)) + + can-edit-file? + (mf/use-ctx ctx/can-edit?) + on-node-context-menu (mf/use-fn - (mf/deps can-edit? on-node-context-menu) + (mf/deps can-edit-file? on-node-context-menu) (fn [event node] - (when can-edit? + (when can-edit-file? (on-node-context-menu event node)))) ordered-nodes (mf/with-memo [raw-tree] diff --git a/frontend/src/app/main/ui/workspace/tokens/sets.cljs b/frontend/src/app/main/ui/workspace/tokens/sets.cljs index af610aefb9..fd5f41f6ed 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sets.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sets.cljs @@ -38,8 +38,8 @@ token-sets (some-> tokens-lib (ctob/get-set-tree)) - can-edit? - (mf/use-ctx ctx/can-edit?) + can-edit-tokens? + (mf/use-ctx ctx/can-edit-tokens?) token-set-active? (mf/use-fn @@ -55,17 +55,17 @@ on-reset-edition (mf/use-fn - (mf/deps can-edit?) + (mf/deps can-edit-tokens?) (fn [_] - (when can-edit? + (when can-edit-tokens? (st/emit! (dwtl/clear-token-set-edition) (dwtl/clear-token-set-creation))))) on-start-edition (mf/use-fn - (mf/deps can-edit?) + (mf/deps can-edit-tokens?) (fn [id] - (when can-edit? + (when can-edit-tokens? (st/emit! (dwtl/start-token-set-edition id)))))] [:> controlled-sets-list* @@ -81,7 +81,6 @@ :edition-id edition-id :origin "set-panel" - :can-edit can-edit? :on-start-edition on-start-edition :on-reset-edition on-reset-edition diff --git a/frontend/src/app/main/ui/workspace/tokens/sets/lists.cljs b/frontend/src/app/main/ui/workspace/tokens/sets/lists.cljs index 96fd61eadf..0004502661 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sets/lists.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sets/lists.cljs @@ -86,8 +86,8 @@ (mf/defc inline-add-button* [] - (let [can-edit? (mf/use-ctx ctx/can-edit?)] - (if can-edit? + (let [can-edit-tokens? (mf/use-ctx ctx/can-edit-tokens?)] + (if can-edit-tokens? [:div {:class (stl/css :empty-sets-wrapper)} [:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message)} (tr "workspace.tokens.no-sets-yet")] @@ -110,7 +110,10 @@ [{:keys [id label is-editing is-active is-selected is-draggable is-collapsed path depth index on-toggle on-drop on-start-edition on-reset-edition on-edit-submit on-toggle-collapse]}] - (let [can-edit? + (let [can-edit-tokens? + (mf/use-ctx ctx/can-edit-tokens?) + + can-edit-file? (mf/use-ctx ctx/can-edit?) label-id @@ -118,11 +121,11 @@ on-context-menu (mf/use-fn - (mf/deps is-editing id path can-edit?) + (mf/deps is-editing id path can-edit-tokens?) (fn [event] (dom/prevent-default event) (dom/stop-propagation event) - (when (and can-edit? (not is-editing)) + (when (and can-edit-tokens? (not is-editing)) (st/emit! (dwtl/assign-token-set-context-menu {:position (dom/get-client-position event) :is-group true @@ -141,12 +144,12 @@ on-checkbox-click (mf/use-fn - (mf/deps on-toggle path can-edit?) + (mf/deps on-toggle path can-edit-file?) #(on-toggle path)) on-edit-submit' (mf/use-fn - (mf/deps path on-edit-submit can-edit?) + (mf/deps path on-edit-submit can-edit-tokens?) #(on-edit-submit path %)) on-drop @@ -196,7 +199,7 @@ label] [:> checkbox* {:on-click on-checkbox-click - :disabled (not can-edit?) + :disabled (not can-edit-file?) :checked (case is-active :all true :partial "mixed" @@ -207,7 +210,11 @@ [{:keys [id set label is-editing is-active is-selected is-draggable is-new path depth index on-select on-toggle on-drop on-start-edition on-reset-edition on-edit-submit]}] - (let [can-edit? (mf/use-ctx ctx/can-edit?) + (let [can-edit-tokens? + (mf/use-ctx ctx/can-edit-tokens?) + + can-edit-file? + (mf/use-ctx ctx/can-edit?) on-click (mf/use-fn @@ -220,11 +227,11 @@ on-context-menu (mf/use-fn - (mf/deps is-editing id path can-edit?) + (mf/deps is-editing id path can-edit-tokens?) (fn [event] (dom/prevent-default event) (dom/stop-propagation event) - (when (and can-edit? (not is-editing)) + (when (and can-edit-tokens? (not is-editing)) (st/emit! (dwtl/assign-token-set-context-menu {:position (dom/get-client-position event) :is-group false @@ -305,7 +312,7 @@ label] [:> checkbox* {:on-click on-checkbox-click - :disabled (not can-edit?) + :disabled (not can-edit-file?) :arial-label (tr "workspace.tokens.select-set") :checked is-active}]])])) @@ -455,12 +462,12 @@ (assert (fn? is-token-set-active) "expected a function for `is-token-set-active` prop") (assert (fn? is-token-set-group-active) "expected a function for `is-token-set-group-active` prop") - (let [theme-modal? (= origin "theme-modal") - can-edit? (mf/use-ctx ctx/can-edit?) - draggable? (and (not theme-modal?) can-edit?) - empty-state? (and theme-modal? - (empty? token-sets) - (not new-path)) + (let [theme-modal? (= origin "theme-modal") + can-edit-tokens? (mf/use-ctx ctx/can-edit-tokens?) + draggable? (and (not theme-modal?) can-edit-tokens?) + empty-state? (and theme-modal? + (empty? token-sets) + (not new-path)) ;; NOTE: on-reset-edition and on-start-edition function can ;; come as nil, in this case we need to provide a safe diff --git a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs index 4d2f77863f..1cdec9b7f2 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs @@ -7,6 +7,7 @@ (ns app.main.ui.workspace.tokens.sidebar (:require-macros [app.main.style :as stl]) (:require + [app.common.files.tokens :as cfo] [app.common.types.tokens-lib :as ctob] [app.config :as cf] [app.main.data.modal :as modal] @@ -62,9 +63,8 @@ {::mf/private true} [{:keys [resize-height] :as props}] - (let [can-edit? - (mf/use-ctx ctx/can-edit?)] - + (let [can-edit-tokens? + (mf/use-ctx ctx/can-edit-tokens?)] [:* [:> token-set-context-menu*] [:section {:data-testid "token-management-sidebar" @@ -73,7 +73,7 @@ [:> themes-header*] [:div {:class (stl/css :sidebar-header)} [:> title-bar* {:title (tr "labels.sets")} - (when can-edit? + (when can-edit-tokens? [:> tsetslist/add-button*])]] [:> token-sets-list* props]]])) @@ -83,8 +83,8 @@ (let [show-menu* (mf/use-state false) show-menu? (deref show-menu*) - can-edit? - (mf/use-ctx ctx/can-edit?) + can-edit-tokens? + (mf/use-ctx ctx/can-edit-tokens?) open-menu (mf/use-fn @@ -124,7 +124,7 @@ :on-close close-menu :id "tokens-menu" :class (stl/css :import-export-menu)} - (when can-edit? + (when can-edit-tokens? [:> dropdown-menu-item* {:class (stl/css :import-export-menu-item) :on-click on-modal-show} [:div {:class (stl/css :import-menu-item)} @@ -133,8 +133,7 @@ :on-click on-export} (tr "labels.export")]] - - (when (and can-edit? (contains? cf/flags :token-base-font-size)) + (when (and can-edit-tokens? (contains? cf/flags :token-base-font-size)) [:> icon-button* {:variant "secondary" :icon i/settings :aria-label "Settings" @@ -146,18 +145,30 @@ 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)] + (use-resize-hook :tokens 200 38 "0.6" :y false nil) - [:div {:class (stl/css :sidebar-wrapper)} - [:> token-management-section* - {:resize-height size-pages-opened - :tokens-lib tokens-lib}] - [: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} - [:div {:class (stl/css :resize-handle-horiz)}]] - [:> tokens-section* props]] - [:> import-export-button*]])) + current-file-data + (mf/deref refs/workspace-data) + + can-edit-file? + (mf/use-ctx ctx/can-edit?) + + can-edit-tokens? + (mf/with-memo [can-edit-file? current-file-data] + (and can-edit-file? + (cfo/editable-tokens? current-file-data)))] + + [:> (mf/provider ctx/can-edit-tokens?) {:value can-edit-tokens?} + [:div {:class (stl/css :sidebar-wrapper)} + [:> token-management-section* + {:resize-height size-pages-opened + :tokens-lib tokens-lib}] + [: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} + [:div {:class (stl/css :resize-handle-horiz)}]] + [:> tokens-section* props]] + [:> import-export-button*]]])) diff --git a/frontend/src/app/main/ui/workspace/tokens/themes.cljs b/frontend/src/app/main/ui/workspace/tokens/themes.cljs index 7661287472..652df89e43 100644 --- a/frontend/src/app/main/ui/workspace/tokens/themes.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/themes.cljs @@ -23,8 +23,8 @@ (let [ordered-themes (mf/deref refs/workspace-token-themes-no-hidden) - can-edit? - (mf/use-ctx ctx/can-edit?) + can-edit-tokens? + (mf/use-ctx ctx/can-edit-tokens?) open-modal (mf/use-fn @@ -38,11 +38,11 @@ [:div {:class (stl/css :empty-theme-wrapper)} [:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message)} (tr "workspace.tokens.no-themes")] - (when can-edit? + (when can-edit-tokens? [:button {:on-click open-modal :class (stl/css :create-theme-button)} (tr "workspace.tokens.create-one")])] - (if can-edit? + (if can-edit-tokens? [:div {:class (stl/css :theme-selector-wrapper)} [:> theme-selector*] [:> button* {:variant "secondary" @@ -50,6 +50,6 @@ :class (stl/css :edit-theme-button) :on-click open-modal} (tr "labels.edit")]] - [:div {:title (when-not can-edit? + [:div {:title (when-not can-edit-tokens? (tr "workspace.tokens.no-permission-themes"))} [:> theme-selector*]]))])) diff --git a/frontend/src/app/plugins/tokens.cljs b/frontend/src/app/plugins/tokens.cljs index b53525813a..3a7f6d1620 100644 --- a/frontend/src/app/plugins/tokens.cljs +++ b/frontend/src/app/plugins/tokens.cljs @@ -10,6 +10,7 @@ [app.common.files.tokens :as cfo] [app.common.json :as json] [app.common.schema :as sm] + [app.common.types.file :as ctf] [app.common.types.token :as cto] [app.common.types.tokens-lib :as ctob] [app.common.types.tokens-status :as ctos] @@ -217,8 +218,10 @@ (u/not-valid plugin-id :name "Plugin doesn't have 'content:write' permission") :else - (st/emit! (-> (dwtl/update-token set-id id {:name value}) - (se/add-event plugin-id)))))} + (do + (u/check-editable-tokens file-id) + (st/emit! (-> (dwtl/update-token set-id id {:name value}) + (se/add-event plugin-id))))))} :type {:this true @@ -248,11 +251,13 @@ (u/not-valid plugin-id :value "Plugin doesn't have 'content:write' permission") :else - (let [token (u/locate-token file-id set-id id) - value (cond-> value - (= :font-family (:type token)) - (ctob/convert-dtcg-font-family))] - (st/emit! (dwtl/update-token set-id id {:value value})))))} + (do + (u/check-editable-tokens file-id) + (let [token (u/locate-token file-id set-id id) + value (cond-> value + (= :font-family (:type token)) + (ctob/convert-dtcg-font-family))] + (st/emit! (dwtl/update-token set-id id {:value value}))))))} :resolvedValue {:this true @@ -290,8 +295,10 @@ (u/not-valid plugin-id :description "Plugin doesn't have 'content:write' permission") :else - (st/emit! (-> (dwtl/update-token set-id id {:description value}) - (se/add-event plugin-id)))))} + (do + (u/check-editable-tokens file-id) + (st/emit! (-> (dwtl/update-token set-id id {:description value}) + (se/add-event plugin-id))))))} :duplicate (fn [] @@ -305,13 +312,15 @@ ;; - use this function in dwtl/duplicate-token ;; - return the new token proxy using the locally forced id ;; - do the same with sets and themes - (let [token (u/locate-token file-id set-id id) - token' (ctob/make-token (-> (datafy token) - (dissoc :id - :modified-at)))] - (st/emit! (-> (dwtl/create-token set-id token') - (se/add-event plugin-id))) - (token-proxy plugin-id file-id set-id (:id token'))))) + (do + (u/check-editable-tokens file-id) + (let [token (u/locate-token file-id set-id id) + token' (ctob/make-token (-> (datafy token) + (dissoc :id + :modified-at)))] + (st/emit! (-> (dwtl/create-token set-id token') + (se/add-event plugin-id))) + (token-proxy plugin-id file-id set-id (:id token')))))) :remove (fn [] @@ -320,8 +329,10 @@ (u/not-valid plugin-id :remove "Plugin doesn't have 'content:write' permission") :else - (st/emit! (-> (dwtl/delete-token set-id id) - (se/add-event plugin-id))))) + (do + (u/check-editable-tokens file-id) + (st/emit! (-> (dwtl/delete-token set-id id) + (se/add-event plugin-id)))))) :applyToShapes {:enumerable false @@ -377,8 +388,10 @@ (u/not-valid plugin-id :name "Plugin doesn't have 'content:write' permission") :else - (let [set (u/locate-token-set file-id id)] - (st/emit! (dwtl/rename-token-set set name)))))} + (do + (u/check-editable-tokens file-id) + (let [set (u/locate-token-set file-id id)] + (st/emit! (dwtl/rename-token-set set name))))))} :active {:this true @@ -395,7 +408,7 @@ (u/not-valid plugin-id :active "Plugin doesn't have 'content:write' permission") :else - (st/emit! (dwtl/set-enabled-token-set id value))))} + (st/emit! (dwtl/set-enabled-token-set id value))))} ;; This can be done even with tokens in an external library :toggleActive (fn [] @@ -468,28 +481,30 @@ (u/not-valid plugin-id :addToken "Plugin doesn't have 'content:write' permission") :else - (let [tokens-lib (u/locate-tokens-lib file-id) - token (ctob/make-token attrs) - ;; Resolve against all tokens in the library (including those - ;; in inactive sets) so that references to structurally - ;; existing tokens resolve even if their set is not active. - ;; The target set's tokens take precedence over equally named - ;; tokens in other sets, and the new token takes precedence - ;; over all. - tokens-tree (-> (merge (ctob/get-all-tokens-map tokens-lib) - (ctob/get-tokens tokens-lib id)) - (assoc (:name token) token)) - resolved-tokens (ts/resolve-tokens tokens-tree) + (do + (u/check-editable-tokens file-id) + (let [tokens-lib (u/locate-tokens-lib file-id) + token (ctob/make-token attrs) + ;; Resolve against all tokens in the library (including those + ;; in inactive sets) so that references to structurally + ;; existing tokens resolve even if their set is not active. + ;; The target set's tokens take precedence over equally named + ;; tokens in other sets, and the new token takes precedence + ;; over all. + tokens-tree (-> (merge (ctob/get-all-tokens-map tokens-lib) + (ctob/get-tokens tokens-lib id)) + (assoc (:name token) token)) + resolved-tokens (ts/resolve-tokens tokens-tree) - {:keys [errors resolved-value] :as resolved-token} - (get resolved-tokens (:name token))] + {:keys [errors resolved-value] :as resolved-token} + (get resolved-tokens (:name token))] - (if resolved-value - (do (st/emit! (-> (dwtl/create-token id token) - (se/add-event plugin-id))) - (token-proxy plugin-id file-id id (:id token))) - (do (u/not-valid plugin-id :addToken (str errors)) - nil)))))} + (if resolved-value + (do (st/emit! (-> (dwtl/create-token id token) + (se/add-event plugin-id))) + (token-proxy plugin-id file-id id (:id token))) + (do (u/not-valid plugin-id :addToken (str errors)) + nil))))))} :duplicate (fn [] @@ -498,10 +513,12 @@ (u/not-valid plugin-id :duplicate "Plugin doesn't have 'content:write' permission") :else - (let [id-ref (atom nil)] - (st/emit! (dwtl/duplicate-token-set id {:id-ref id-ref})) - (when (some? @id-ref) - (token-set-proxy plugin-id file-id @id-ref))))) + (do + (u/check-editable-tokens file-id) + (let [id-ref (atom nil)] + (st/emit! (dwtl/duplicate-token-set id {:id-ref id-ref})) + (when (some? @id-ref) + (token-set-proxy plugin-id file-id @id-ref)))))) :remove (fn [] @@ -510,7 +527,9 @@ (u/not-valid plugin-id :remove "Plugin doesn't have 'content:write' permission") :else - (st/emit! (dwtl/delete-token-set id))))))) + (do + (u/check-editable-tokens file-id) + (st/emit! (dwtl/delete-token-set id)))))))) (defn token-theme-proxy? [p] (obj/type-of? p "TokenThemeProxy")) @@ -568,8 +587,10 @@ (u/not-valid plugin-id :group "Plugin doesn't have 'content:write' permission") :else - (let [theme (u/locate-token-theme file-id id)] - (st/emit! (dwtl/update-token-theme id (assoc theme :group group))))))} + (do + (u/check-editable-tokens file-id) + (let [theme (u/locate-token-theme file-id id)] + (st/emit! (dwtl/update-token-theme id (assoc theme :group group)))))))} :name {:this true @@ -589,9 +610,11 @@ (u/not-valid plugin-id :name "Plugin doesn't have 'content:write' permission") :else - (let [theme (u/locate-token-theme file-id id)] - (when name - (st/emit! (dwtl/update-token-theme id (assoc theme :name name)))))))} + (do + (u/check-editable-tokens file-id) + (let [theme (u/locate-token-theme file-id id)] + (when name + (st/emit! (dwtl/update-token-theme id (assoc theme :name name))))))))} :active {:this true @@ -641,10 +664,12 @@ (u/not-valid plugin-id :addSet "Plugin doesn't have 'content:write' permission") :else - (let [set-name (token-set-name (resolve-token-set file-id set-arg)) - theme (u/locate-token-theme file-id id)] - (when (and set-name theme) - (st/emit! (dwtl/update-token-theme id (ctob/enable-set theme set-name)))))))} + (do + (u/check-editable-tokens file-id) + (let [set-name (token-set-name (resolve-token-set file-id set-arg)) + theme (u/locate-token-theme file-id id)] + (when (and set-name theme) + (st/emit! (dwtl/update-token-theme id (ctob/enable-set theme set-name))))))))} :removeSet {:enumerable false @@ -655,10 +680,12 @@ (u/not-valid plugin-id :removeSet "Plugin doesn't have 'content:write' permission") :else - (let [set-name (token-set-name (resolve-token-set file-id set-arg)) - theme (u/locate-token-theme file-id id)] - (when (and set-name theme) - (st/emit! (dwtl/update-token-theme id (ctob/disable-set theme set-name)))))))} + (do + (u/check-editable-tokens file-id) + (let [set-name (token-set-name (resolve-token-set file-id set-arg)) + theme (u/locate-token-theme file-id id)] + (when (and set-name theme) + (st/emit! (dwtl/update-token-theme id (ctob/disable-set theme set-name))))))))} :duplicate (fn [] @@ -667,12 +694,14 @@ (u/not-valid plugin-id :duplicate "Plugin doesn't have 'content:write' permission") :else - (let [theme (u/locate-token-theme file-id id) - theme' (ctob/make-token-theme (-> (datafy theme) - (dissoc :id - :modified-at)))] - (st/emit! (dwtl/create-token-theme theme')) - (token-theme-proxy plugin-id file-id (:id theme'))))) + (do + (u/check-editable-tokens file-id) + (let [theme (u/locate-token-theme file-id id) + theme' (ctob/make-token-theme (-> (datafy theme) + (dissoc :id + :modified-at)))] + (st/emit! (dwtl/create-token-theme theme')) + (token-theme-proxy plugin-id file-id (:id theme')))))) :remove (fn [] @@ -681,7 +710,9 @@ (u/not-valid plugin-id :remove "Plugin doesn't have 'content:write' permission") :else - (st/emit! (dwtl/delete-token-theme id)))))) + (do + (u/check-editable-tokens file-id) + (st/emit! (dwtl/delete-token-theme id))))))) (defn tokens-catalog [plugin-id file-id] @@ -690,6 +721,14 @@ :$plugin {:enumerable false :get (constantly plugin-id)} :$id {:enumerable false :get (constantly file-id)} + :isEditableTokens + {:this false + :enumerable false + :get + (fn [] + (let [file (u/locate-file file-id)] + (cfo/editable-tokens? (ctf/file-data file))))} + :themes {:this true :enumerable false @@ -726,9 +765,11 @@ (u/not-valid plugin-id :addTheme "Plugin doesn't have 'content:write' permission") :else - (let [theme (ctob/make-token-theme attrs)] - (st/emit! (dwtl/create-token-theme theme)) - (token-theme-proxy plugin-id file-id (:id theme)))))} + (do + (u/check-editable-tokens file-id) + (let [theme (ctob/make-token-theme attrs)] + (st/emit! (dwtl/create-token-theme theme)) + (token-theme-proxy plugin-id file-id (:id theme))))))} :addSet {:enumerable false @@ -750,21 +791,23 @@ (u/not-valid plugin-id :addSet "Plugin doesn't have 'content:write' permission") :else - (let [active? (boolean (:active attrs)) - attrs (-> attrs - (dissoc :active) - (update :name ctob/normalize-set-name)) - set (ctob/make-token-set attrs)] - (st/emit! (dwtl/create-token-set set)) - ;; Newly created sets are inactive by default; activate it when - ;; requested. Enabling only adds the set name to the hidden theme, - ;; so it does not depend on the create event having propagated yet. - (when active? - (st/emit! (dwtl/set-enabled-token-set (ctob/get-name set) true))) - ;; Pass the set name as `initial-name` so the proxy can resolve - ;; it immediately, before the async `st/emit!` above propagates - ;; the new set into `@st/state`. - (token-set-proxy plugin-id file-id (ctob/get-id set) (ctob/get-name set)))))} + (do + (u/check-editable-tokens file-id) + (let [active? (boolean (:active attrs)) + attrs (-> attrs + (dissoc :active) + (update :name ctob/normalize-set-name)) + set (ctob/make-token-set attrs)] + (st/emit! (dwtl/create-token-set set)) + ;; Newly created sets are inactive by default; activate it when + ;; requested. Enabling only adds the set name to the hidden theme, + ;; so it does not depend on the create event having propagated yet. + (when active? + (st/emit! (dwtl/set-enabled-token-set (ctob/get-name set) true))) + ;; Pass the set name as `initial-name` so the proxy can resolve + ;; it immediately, before the async `st/emit!` above propagates + ;; the new set into `@st/state`. + (token-set-proxy plugin-id file-id (ctob/get-id set) (ctob/get-name set))))))} :getThemeById {:enumerable false diff --git a/frontend/src/app/plugins/utils.cljs b/frontend/src/app/plugins/utils.cljs index 844111090b..0869aab4b1 100644 --- a/frontend/src/app/plugins/utils.cljs +++ b/frontend/src/app/plugins/utils.cljs @@ -9,6 +9,7 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] + [app.common.files.tokens :as cfo] [app.common.i18n :as i18n :refer [tr]] [app.common.schema :as sm] [app.common.schema.messages :as csm] @@ -67,13 +68,18 @@ (defn locate-tokens-lib [file-id] - (let [file (locate-file file-id)] - (->> file :data :tokens-lib))) + (let [file (locate-file file-id) + file-data (ctf/file-data file) + tokens-source-id (cfo/get-tokens-source file-data) + tokens-file (locate-file tokens-source-id) + tokens-file-data (ctf/file-data tokens-file)] + (cfo/get-tokens-lib tokens-file-data))) (defn locate-tokens-status [file-id] - (let [file (locate-file file-id)] - (->> file :data :tokens-status))) + (let [file (locate-file file-id) + file-data (ctf/file-data file)] + (cfo/get-tokens-status file-data))) (defn locate-token-theme [file-id id] @@ -271,6 +277,12 @@ (boolean (dm/get-in @st/state [:plugins :flags plugin-id :natural-child-ordering]))) +(defn check-editable-tokens + [file-id] + (let [file (locate-file file-id)] + (when-not (cfo/editable-tokens? (ctf/file-data file)) + (throw (js/Error. (dm/str "[PENPOT PLUGIN] Cannot modify tokens in an external library")))))) + (defn throw-validation-errors? [plugin-id] (boolean 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 9bb6fc8b9a..1c4f6ae4ea 100644 --- a/plugins/apps/poc-tokens-plugin/src/app/app.component.html +++ b/plugins/apps/poc-tokens-plugin/src/app/app.component.html @@ -6,13 +6,15 @@

THEMES

- + @if (isEditableTokens) { + + }