diff --git a/CHANGES.md b/CHANGES.md index 6a4833f240..626f472d7a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,6 +15,7 @@ - Add woff2 support on user uploaded fonts (by @Nivl) [Github #8248](https://github.com/penpot/penpot/pull/8248) - Option to download custom fonts (by @dfelinto) [Github #8320](https://github.com/penpot/penpot/issues/8320) - Add copy as image to clipboard option to workspace context menu (by @dfelinto) [Github #8313](https://github.com/penpot/penpot/pull/8313) +- Import Tokens from linked library [Github #8391](https://github.com/penpot/penpot/pull/8391) ### :bug: Bugs fixed diff --git a/common/src/app/common/flags.cljc b/common/src/app/common/flags.cljc index 64cb7f9d68..6d7d49a98d 100644 --- a/common/src/app/common/flags.cljc +++ b/common/src/app/common/flags.cljc @@ -119,12 +119,13 @@ :strict-session-cookies :telemetry :terms-and-privacy-checkbox - ;; Only for developtment. :tiered-file-data-storage :token-base-font-size :token-color :token-shadow :token-tokenscript + :token-import-from-library + ;; Only for developtment. :transit-readable-response :user-feedback ;; TODO: remove this flag. @@ -180,7 +181,8 @@ :enable-token-color :enable-token-shadow :enable-inspect-styles - :enable-feature-fdata-objects-map]) + :enable-feature-fdata-objects-map + :enable-token-import-from-library]) (defn parse [& flags] diff --git a/frontend/src/app/main/ui/workspace/libraries.cljs b/frontend/src/app/main/ui/workspace/libraries.cljs index 18fd87e7c8..99d0e9c3de 100644 --- a/frontend/src/app/main/ui/workspace/libraries.cljs +++ b/frontend/src/app/main/ui/workspace/libraries.cljs @@ -13,8 +13,10 @@ [app.common.types.components-list :as ctkl] [app.common.types.file :as ctf] [app.common.types.library :as ctl] + [app.common.types.tokens-lib :as ctob] [app.common.types.typographies-list :as ctyl] [app.common.uuid :as uuid] + [app.config :as cf] [app.main.data.dashboard :as dd] [app.main.data.modal :as modal] [app.main.data.notifications :as ntf] @@ -36,6 +38,7 @@ [app.main.ui.ds.product.empty-state :refer [empty-state*]] [app.main.ui.hooks :as h] [app.main.ui.icons :as deprecated-icon] + [app.main.ui.workspace.tokens.import-from-library] [app.util.color :as uc] [app.util.dom :as dom] [app.util.i18n :refer [c tr]] @@ -180,6 +183,12 @@ [summary] (boolean (:is-empty summary))) +(defn- has-tokens? + "Check if library has tokens to be imported" + [{:keys [data]}] + (when-let [tokens-lib (get data :tokens-lib)] + (not (ctob/empty-lib? tokens-lib)))) + (mf/defc libraries-tab* {::mf/props :obj ::mf/private true} @@ -230,14 +239,18 @@ (keep library-names)))) (sort-by (comp str/lower :name)))) - linked-libraries-ids (mf/with-memo [linked-libraries] - (into #{} (map :id) linked-libraries)) + linked-libraries-ids + (mf/with-memo [linked-libraries] + (into #{} d/xf:map-id linked-libraries)) + importing* + (mf/use-state nil) - importing* (mf/use-state nil) - sample-libraries [{:id "penpot-design-system", :name "Design system example"} - {:id "wireframing-kit", :name "Wireframe library"} - {:id "whiteboarding-kit", :name "Whiteboarding Kit"}] + sample-libraries + (mf/with-memo [] + [{:id "penpot-design-system", :name "Design system example"} + {:id "wireframing-kit", :name "Wireframe library"} + {:id "whiteboarding-kit", :name "Whiteboarding Kit"}]) change-search-term @@ -267,6 +280,17 @@ (st/emit! (dwl/unlink-file-from-library file-id library-id) (dwl/sync-file file-id library-id))))) + import-tokens + (mf/use-fn + (mf/deps file-id) + (fn [event] + (let [library-id (some-> (dom/get-current-target event) + (dom/get-data "library-id") + (uuid/parse))] + (st/emit! (modal/show + :tokens/import-from-library {:file-id file-id + :library-id library-id}))))) + on-delete-accept (mf/use-fn (mf/deps file-id) @@ -332,8 +356,12 @@ :on-click publish}])] (for [{:keys [id name data connected-to connected-to-names] :as library} linked-libraries] - (let [disabled? (some #(contains? linked-libraries-ids %) connected-to)] - [:div {:class (stl/css :section-list-item) + (let [disabled? (some #(contains? linked-libraries-ids %) connected-to) + has-tokens? (and (has-tokens? library) + (contains? cf/flags :token-import-from-library))] + [:div {:class (if has-tokens? + (stl/css :section-list-item-double-icon) + (stl/css :section-list-item)) :key (dm/str id) :data-testid "library-item"} [:div {:class (stl/css :item-content)} @@ -348,6 +376,15 @@ [:span {:class (stl/css :connected-to-values)} (str/join ", " connected-to-names)] [:span ")"]])])]] + (when ^boolean has-tokens? + [:> icon-button* + {:type "button" + :aria-label (tr "workspace.tokens.import-tokens") + :icon i/import-export + :data-library-id (dm/str id) + :variant "secondary" + :on-click import-tokens}]) + [:> icon-button* {:type "button" :aria-label (tr "workspace.libraries.unlink-library-btn") :icon i/detach diff --git a/frontend/src/app/main/ui/workspace/libraries.scss b/frontend/src/app/main/ui/workspace/libraries.scss index 9776c08bad..978c2cbcba 100644 --- a/frontend/src/app/main/ui/workspace/libraries.scss +++ b/frontend/src/app/main/ui/workspace/libraries.scss @@ -116,6 +116,11 @@ border-radius: $br-8; } +.section-list-item-double-icon { + @extend .section-list-item; + grid-template-columns: 1fr auto auto; +} + .item-content { height: fit-content; } diff --git a/frontend/src/app/main/ui/workspace/tokens/import_from_library.cljs b/frontend/src/app/main/ui/workspace/tokens/import_from_library.cljs new file mode 100644 index 0000000000..4481e1306d --- /dev/null +++ b/frontend/src/app/main/ui/workspace/tokens/import_from_library.cljs @@ -0,0 +1,92 @@ +;; 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.import-from-library + (:require-macros [app.main.style :as stl]) + (:require + [app.common.data.macros :as dm] + [app.main.data.modal :as modal] + [app.main.data.workspace.tokens.library-edit :as dwtl] + [app.main.store :as st] + [app.main.ui.ds.buttons.button :refer [button*]] + [app.main.ui.ds.buttons.icon-button :refer [icon-button*]] + [app.main.ui.ds.foundations.assets.icon :as i] + [app.main.ui.ds.foundations.typography :as t] + [app.main.ui.ds.foundations.typography.heading :refer [heading*]] + [app.main.ui.ds.foundations.typography.text :refer [text*]] + [app.main.ui.ds.notifications.context-notification :refer [context-notification*]] + [app.util.i18n :refer [tr]] + [okulary.core :as l] + [rumext.v2 :as mf])) + + +(mf/defc import-modal-library* + {::mf/register modal/components + ::mf/register-as :tokens/import-from-library} + [all-props] + (let [{:keys [file-id library-id]} + (js->clj all-props :keywordize-keys true) + + library-file-ref (mf/with-memo [library-id] + (l/derived (fn [state] + (dm/get-in state [:files library-id :data])) + st/state)) + library-data (mf/deref library-file-ref) + + show-libraries-dialog + (mf/use-fn + (mf/deps file-id) + (fn [] + (modal/hide!) + (modal/show! :libraries-dialog {:file-id file-id}))) + + cancel + (mf/use-fn + (fn [] + (show-libraries-dialog))) + + import + (mf/use-fn + (mf/deps file-id library-id library-data) + (fn [] + (let [tokens-lib (:tokens-lib library-data)] + (st/emit! (dwtl/import-tokens-lib tokens-lib))) + (show-libraries-dialog)))] + + [:div {:class (stl/css :modal-overlay)} + [:div {:class (stl/css :modal-dialog)} + [:> icon-button* {:class (stl/css :close-btn) + :on-click cancel + :aria-label (tr "labels.close") + :variant "ghost" + :icon i/close}] + + [:div {:class (stl/css :modal-header)} + [:> heading* {:level 2 + :id "modal-title" + :typography "headline-large" + :class (stl/css :modal-title)} + (tr "modals.import-library-tokens.title")]] + + [:div {:class (stl/css :modal-content)} + [:> text* {:as "p" :typography t/body-medium} (tr "modals.import-library-tokens.description")]] + + [:> context-notification* {:type :context + :appearance "neutral" + :level "default" + :is-html true} + (tr "workspace.tokens.import-warning")] + + [:div {:class (stl/css :modal-footer)} + [:div {:class (stl/css :action-buttons)} + [:> button* {:on-click cancel + :type "button" + :variant "secondary"} + (tr "labels.cancel")] + [:> button* {:on-click import + :type "button" + :variant "primary"} + (tr "modals.import-library-tokens.import")]]]]])) diff --git a/frontend/src/app/main/ui/workspace/tokens/import_from_library.scss b/frontend/src/app/main/ui/workspace/tokens/import_from_library.scss new file mode 100644 index 0000000000..d1394861db --- /dev/null +++ b/frontend/src/app/main/ui/workspace/tokens/import_from_library.scss @@ -0,0 +1,70 @@ +// 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; + +@use "ds/typography.scss" as t; +@use "ds/_borders.scss" as *; +@use "ds/_sizes.scss" as *; + +.close-btn { + position: absolute; + inset-block-start: var(--sp-s); + inset-inline-end: var(--sp-s); +} + +.modal-overlay { + --modal-title-foreground-color: var(--color-foreground-primary); + --modal-text-foreground-color: var(--color-foreground-secondary); + + @extend .modal-overlay-base; + display: flex; + justify-content: center; + align-items: center; + position: fixed; + inset-inline-start: 0; + inset-block-start: 0; + block-size: 100%; + inline-size: 100%; + background-color: var(--overlay-color); +} + +.modal-dialog { + @extend .modal-container-base; + inline-size: 100%; + max-inline-size: 32rem; + max-block-size: unset; + user-select: none; + position: relative; +} + +.modal-header { + margin-block-end: var(--sp-xxl); + display: flex; + justify-content: space-between; + align-items: center; +} + +.modal-title { + @include t.use-typography("headline-medium"); + color: var(--modal-title-foreground-color); + word-break: break-word; +} + +.modal-content { + @include t.use-typography("body-large"); + color: var(--modal-text-foreground-color); +} + +.modal-footer { + margin-block-start: var(--sp-xxl); + gap: var(--sp-s); +} + +.action-buttons { + @extend .modal-action-btns; + gap: var(--sp-s); +} diff --git a/frontend/translations/en.po b/frontend/translations/en.po index e2f0073eb5..ab676f7ce6 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -1153,6 +1153,20 @@ msgstr "Type to search results" msgid "dashboard.unpublish-shared" msgstr "Unpublish Library" +#:src/app/main/ui/workspace/tokens/import_from_library.cljs +msgid "modals.import-library-tokens.title" +msgstr "Import tokens from library?" + +#:src/app/main/ui/workspace/tokens/import_from_library.cljs +msgid "modals.import-library-tokens.description" +msgstr "" +"The library has tokens and themes which " +"are likely used by its components." + +#:src/app/main/ui/workspace/tokens/import_from_library.cljs +msgid "modals.import-library-tokens.import" +msgstr "Import tokens" + #: src/app/main/ui/settings/options.cljs:74 msgid "dashboard.update-settings" msgstr "Update settings"