From 326822594103dd79712d9f1875d6c0c9ead7bc6c Mon Sep 17 00:00:00 2001 From: Pablo Alba <pablo.alba@kaleidos.net> Date: Mon, 17 Feb 2025 16:47:25 +0100 Subject: [PATCH] :tada: Add variations POC --- backend/src/app/binfile/common.clj | 5 + common/src/app/common/features.cljc | 4 +- .../src/app/common/files/changes_builder.cljc | 57 ++-- common/src/app/common/logic/libraries.cljc | 5 +- common/src/app/common/logic/variants.cljc | 62 +++++ common/src/app/common/types/component.cljc | 13 + .../src/app/common/types/components_list.cljc | 18 +- common/src/app/common/types/container.cljc | 2 +- .../logic/comp_creation_test.cljc | 2 + frontend/resources/images/icons/variant.svg | 3 + .../app/main/data/workspace/libraries.cljs | 24 +- .../src/app/main/data/workspace/variants.cljs | 197 ++++++++++++++ .../app/main/ui/components/shape_icon.cljs | 7 +- .../ds/controls/shared/options_dropdown.scss | 1 + frontend/src/app/main/ui/icons.cljs | 1 + .../ui/workspace/sidebar/assets/common.cljs | 13 +- .../main/ui/workspace/sidebar/layer_item.cljs | 7 +- .../main/ui/workspace/sidebar/layer_name.cljs | 4 +- .../sidebar/options/menus/component.cljs | 251 ++++++++++++++++-- .../sidebar/options/menus/component.scss | 71 ++++- .../sidebar/options/shapes/frame.cljs | 101 +++---- .../main/ui/workspace/viewport/widgets.cljs | 8 +- frontend/translations/en.po | 9 + frontend/translations/es.po | 10 + 24 files changed, 751 insertions(+), 124 deletions(-) create mode 100644 common/src/app/common/logic/variants.cljc create mode 100644 frontend/resources/images/icons/variant.svg create mode 100644 frontend/src/app/main/data/workspace/variants.cljs diff --git a/backend/src/app/binfile/common.clj b/backend/src/app/binfile/common.clj index a0b256660..c068128a8 100644 --- a/backend/src/app/binfile/common.clj +++ b/backend/src/app/binfile/common.clj @@ -556,6 +556,11 @@ "fdata/shape-data-type" nil + ;; There is no migration needed, but we don't want to allow + ;; copy paste nor import of variant files into no-variant teams + "variants/v1" + nil + (ex/raise :type :internal :code :no-migration-defined :hint (str/ffmt "no migation for feature '%' on file importation" feature) diff --git a/common/src/app/common/features.cljc b/common/src/app/common/features.cljc index 0ced5b1d8..fbb7d2785 100644 --- a/common/src/app/common/features.cljc +++ b/common/src/app/common/features.cljc @@ -52,7 +52,8 @@ "plugins/runtime" "design-tokens/v1" "text-editor/v2" - "render-wasm/v1"}) + "render-wasm/v1" + "variants/v1"}) ;; A set of features enabled by default (def default-features @@ -111,6 +112,7 @@ :feature-design-tokens "design-tokens/v1" :feature-text-editor-v2 "text-editor/v2" :feature-render-wasm "render-wasm/v1" + :feature-variants "variants/v1" nil)) (defn migrate-legacy-features diff --git a/common/src/app/common/files/changes_builder.cljc b/common/src/app/common/files/changes_builder.cljc index 2fb307e92..95a781090 100644 --- a/common/src/app/common/files/changes_builder.cljc +++ b/common/src/app/common/files/changes_builder.cljc @@ -861,7 +861,6 @@ (defn move-token-set-group-before [changes {:keys [from-path to-path before-path before-group? prev-before-path prev-before-group?]}] - (prn prev-before-path prev-before-group?) (-> changes (update :redo-changes conj {:type :move-token-set-group-before :from-path from-path @@ -971,31 +970,37 @@ (apply-changes-local))))) (defn update-component - [changes id update-fn] - (assert-library! changes) - (let [library-data (::library-data (meta changes)) - prev-component (get-in library-data [:components id]) - new-component (update-fn prev-component)] - (if prev-component - (-> changes - (update :redo-changes conj {:type :mod-component - :id id - :name (:name new-component) - :path (:path new-component) - :main-instance-id (:main-instance-id new-component) - :main-instance-page (:main-instance-page new-component) - :annotation (:annotation new-component) - :objects (:objects new-component) ;; this won't exist in components-v2 (except for deleted components) - :modified-at (:modified-at new-component)}) - (update :undo-changes conj {:type :mod-component - :id id - :name (:name prev-component) - :path (:path prev-component) - :main-instance-id (:main-instance-id prev-component) - :main-instance-page (:main-instance-page prev-component) - :annotation (:annotation prev-component) - :objects (:objects prev-component)})) - changes))) + ([changes id update-fn] + (let [library-data (::library-data (meta changes)) + prev-component (get-in library-data [:components id])] + (update-component changes id prev-component update-fn))) + ([changes id prev-component update-fn] + (assert-library! changes) + (let [new-component (update-fn prev-component)] + (if prev-component + (-> changes + (update :redo-changes conj {:type :mod-component + :id id + :name (:name new-component) + :path (:path new-component) + :main-instance-id (:main-instance-id new-component) + :main-instance-page (:main-instance-page new-component) + :annotation (:annotation new-component) + :variant-id (:variant-id new-component) + :variant-properties (:variant-properties new-component) + :objects (:objects new-component) ;; this won't exist in components-v2 (except for deleted components) + :modified-at (:modified-at new-component)}) + (update :undo-changes conj {:type :mod-component + :id id + :name (:name prev-component) + :path (:path prev-component) + :main-instance-id (:main-instance-id prev-component) + :main-instance-page (:main-instance-page prev-component) + :annotation (:annotation prev-component) + :variant-id (:variant-id prev-component) + :variant-properties (:variant-properties prev-component) + :objects (:objects prev-component)})) + changes)))) (defn delete-component [changes id page-id] diff --git a/common/src/app/common/logic/libraries.cljc b/common/src/app/common/logic/libraries.cljc index a090fe690..efb0ba3fb 100644 --- a/common/src/app/common/logic/libraries.cljc +++ b/common/src/app/common/logic/libraries.cljc @@ -152,7 +152,7 @@ (defn generate-duplicate-component "Create a new component copied from the one with the given id." - [changes library component-id components-v2] + [changes library component-id new-component-id components-v2] (let [component (ctkl/get-component (:data library) component-id) new-name (:name component) @@ -160,7 +160,7 @@ (ctf/get-component-page (:data library) component)) new-component-id (when components-v2 - (uuid/next)) + new-component-id) [new-component-shape new-component-shapes ; <- null in components-v2 new-main-instance-shape new-main-instance-shapes] @@ -181,6 +181,7 @@ (:id main-instance-page) (:annotation component))))) + (defn generate-instantiate-component "Generate changes to create a new instance from a component." ([changes objects file-id component-id position page libraries] diff --git a/common/src/app/common/logic/variants.cljc b/common/src/app/common/logic/variants.cljc new file mode 100644 index 000000000..5c07a90aa --- /dev/null +++ b/common/src/app/common/logic/variants.cljc @@ -0,0 +1,62 @@ +;; 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.common.logic.variants + (:require + [app.common.files.changes-builder :as pcb] + [cuerdas.core :as str])) + + +(defn properties-to-name + [properties] + (->> properties + (map :value) + (str/join ", "))) + +(defn generate-update-property-name + [changes related-components pos new-name] + (reduce (fn [changes component] + (pcb/update-component + changes (:id component) + #(assoc-in % [:variant-properties pos :name] new-name))) + changes + related-components)) + + +(defn generate-remove-property + [changes related-components pos] + (reduce (fn [changes component] + (let [props (:variant-properties component) + props (vec (concat (subvec props 0 pos) (subvec props (inc pos)))) + main-id (:main-instance-id component) + name (properties-to-name props)] + (-> changes + (pcb/update-component (:id component) #(assoc % :variant-properties props)) + (pcb/update-shapes [main-id] #(assoc % :variant-name name))))) + changes + related-components)) + + +(defn generate-update-property-value + [changes component-id main-id pos value name] + (-> changes + (pcb/update-component component-id #(assoc-in % [:variant-properties pos :value] value)) + (pcb/update-shapes [main-id] #(assoc % :variant-name name)))) + +(defn generate-add-new-property + [changes related-components property-name] + (let [[_ changes] + (reduce (fn [[num changes] component] + (let [props (-> (or (:variant-properties component) []) + (conj {:name property-name :value (str "Value" num)})) + main-id (:main-instance-id component) + variant-name (properties-to-name props)] + [(inc num) + (-> changes + (pcb/update-component (:id component) #(assoc % :variant-properties props)) + (pcb/update-shapes [main-id] #(assoc % :variant-name variant-name)))])) + [1 changes] + related-components)] + changes)) diff --git a/common/src/app/common/types/component.cljc b/common/src/app/common/types/component.cljc index b26461f2b..aba6e673a 100644 --- a/common/src/app/common/types/component.cljc +++ b/common/src/app/common/types/component.cljc @@ -215,6 +215,19 @@ (and (= shape-id (:main-instance-id component)) (= page-id (:main-instance-page component)))) + +(defn is-variant? + "Check if this shape or component is a variant component" + [item] + (some? (:variant-id item))) + + +(defn is-variant-container? + "Check if this shape is a variant container" + [shape] + (:is-variant-container shape)) + + (defn set-touched-group [touched group] (when group diff --git a/common/src/app/common/types/components_list.cljc b/common/src/app/common/types/components_list.cljc index 8165c2d23..1a2edd786 100644 --- a/common/src/app/common/types/components_list.cljc +++ b/common/src/app/common/types/components_list.cljc @@ -48,7 +48,7 @@ (wrap-object-fn))))))) (defn mod-component - [file-data {:keys [id name path main-instance-id main-instance-page objects annotation modified-at]}] + [file-data {:keys [id name path main-instance-id main-instance-page objects annotation variant-id variant-properties modified-at]}] (let [wrap-objects-fn cfeat/*wrap-with-objects-map-fn*] (d/update-in-when file-data [:components id] (fn [component] @@ -76,10 +76,22 @@ (assoc :annotation annotation) (nil? annotation) - (dissoc :annotation)) + (dissoc :annotation) + + (some? variant-id) + (assoc :variant-id variant-id) + + (nil? variant-id) + (dissoc :variant-id) + + (some? variant-properties) + (assoc :variant-properties variant-properties) + + (nil? variant-properties) + (dissoc :variant-properties)) diff (set/difference (ctk/diff-components component new-comp) - #{:annotation :modified-at})] ;; The set of properties that doesn't mark a component as touched + #{:annotation :modified-at :variant-id :variant-properties})] ;; The set of properties that doesn't mark a component as touched (if (empty? diff) new-comp diff --git a/common/src/app/common/types/container.cljc b/common/src/app/common/types/container.cljc index ae9ac3648..38d222735 100644 --- a/common/src/app/common/types/container.cljc +++ b/common/src/app/common/types/container.cljc @@ -406,7 +406,7 @@ (cond-> new-shape :always (-> (gsh/move delta) - (dissoc :touched)) + (dissoc :touched :variant-id :variant-name)) (and main-instance? root?) (assoc :main-instance true) diff --git a/common/test/common_tests/logic/comp_creation_test.cljc b/common/test/common_tests/logic/comp_creation_test.cljc index c59c14bc1..a57aa7693 100644 --- a/common/test/common_tests/logic/comp_creation_test.cljc +++ b/common/test/common_tests/logic/comp_creation_test.cljc @@ -20,6 +20,7 @@ [app.common.types.component :as ctk] [app.common.types.components-list :as ctkl] [app.common.types.shape-tree :as ctst] + [app.common.uuid :as uuid] [clojure.test :as t])) (t/use-fixtures :each thi/test-fixture) @@ -288,6 +289,7 @@ changes (cll/generate-duplicate-component (pcb/empty-changes) file (:id component) + (uuid/next) true) file' (thf/apply-changes file changes) diff --git a/frontend/resources/images/icons/variant.svg b/frontend/resources/images/icons/variant.svg new file mode 100644 index 000000000..5b12adc57 --- /dev/null +++ b/frontend/resources/images/icons/variant.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" stroke-linecap="round" stroke-linejoin="round"> + <path d="M8.75 1.25a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Zm-1.5 13.5a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0ZM8.75 8a.75.75 0 1 0-1.5 0 .75.75 0 0 0 1.5 0ZM5.375 4.625a.75.75 0 1 0-1.5 0 .75.75 0 0 0 1.5 0Zm0 6.75a.75.75 0 1 0-1.5 0 .75.75 0 0 0 1.5 0Zm5.25 0a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0Zm0-6.75a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0ZM1.25 8.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Zm13.5-1.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Z"/> +</svg> diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 2cf8ad292..1efe71409 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -520,17 +520,19 @@ (defn duplicate-component "Create a new component copied from the one with the given id." - [library-id component-id] - (ptk/reify ::duplicate-component - ptk/WatchEvent - (watch [it state _] - (let [libraries (dsh/lookup-libraries state) - library (get libraries library-id) - components-v2 (features/active-feature? state "components/v2") - changes (-> (pcb/empty-changes it nil) - (cll/generate-duplicate-component library component-id components-v2))] + ([library-id component-id] + (duplicate-component library-id component-id (uuid/next))) + ([library-id component-id new-component-id] + (ptk/reify ::duplicate-component + ptk/WatchEvent + (watch [it state _] + (let [libraries (dsh/lookup-libraries state) + library (get libraries library-id) + components-v2 (features/active-feature? state "components/v2") + changes (-> (pcb/empty-changes it nil) + (cll/generate-duplicate-component library component-id new-component-id components-v2))] - (rx/of (dch/commit-changes changes)))))) + (rx/of (dch/commit-changes changes))))))) (defn delete-component "Delete the component with the given id, from the current file library." @@ -984,7 +986,7 @@ second) 0))))) -(defn- component-swap +(defn component-swap "Swaps a component with another one" [shape file-id id-new-component] (dm/assert! (uuid? id-new-component)) diff --git a/frontend/src/app/main/data/workspace/variants.cljs b/frontend/src/app/main/data/workspace/variants.cljs new file mode 100644 index 000000000..e7d965340 --- /dev/null +++ b/frontend/src/app/main/data/workspace/variants.cljs @@ -0,0 +1,197 @@ +;; 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.variants + (:require + [app.common.colors :as clr] + [app.common.data.macros :as dm] + [app.common.files.changes-builder :as pcb] + [app.common.logic.variants :as clv] + [app.common.types.components-list :as ctcl] + [app.common.uuid :as uuid] + [app.main.data.changes :as dch] + [app.main.data.helpers :as dsh] + [app.main.data.workspace.colors :as cl] + [app.main.data.workspace.libraries :as dwl] + [app.main.data.workspace.shape-layout :as dwsl] + [app.main.data.workspace.shapes :as dwsh] + [app.main.data.workspace.undo :as dwu] + [beicon.v2.core :as rx] + [potok.v2.core :as ptk])) + +(defn find-related-components + [data objects variant-id] + (->> (dm/get-in objects [variant-id :shapes]) + (map #(dm/get-in objects [% :component-id])) + (map #(ctcl/get-component data % true)))) + +(defn update-property-name + "Update the variant property name on the position pos + in all the components with this variant-id" + [variant-id pos new-name] + (ptk/reify ::update-property-name + ptk/WatchEvent + (watch [it state _] + (let [page-id (:current-page-id state) + data (dsh/lookup-file-data state) + objects (-> (dsh/get-page data page-id) + (get :objects)) + related-components (find-related-components data objects variant-id) + changes (-> (pcb/empty-changes it page-id) + (pcb/with-library-data data) + (clv/generate-update-property-name related-components pos new-name)) + undo-id (js/Symbol)] + (rx/of + (dwu/start-undo-transaction undo-id) + (dch/commit-changes changes) + (dwu/commit-undo-transaction undo-id)))))) + +(defn update-property-value + "Updates the variant property value on the position pos in a component" + [component-id pos value] + (ptk/reify ::update-property-value + ptk/WatchEvent + (watch [it state _] + (let [page-id (:current-page-id state) + data (dsh/lookup-file-data state) + objects (-> (dsh/get-page data page-id) + (get :objects)) + component (ctcl/get-component data component-id true) + main-id (:main-instance-id component) + properties (-> (:variant-properties component) + (update pos assoc :value value)) + + name (clv/properties-to-name properties) + + changes (-> (pcb/empty-changes it page-id) + (pcb/with-library-data data) + (pcb/with-objects objects) + (clv/generate-update-property-value component-id main-id pos value name)) + undo-id (js/Symbol)] + (rx/of + (dwu/start-undo-transaction undo-id) + (dch/commit-changes changes) + (dwu/commit-undo-transaction undo-id)))))) + + +(defn remove-property + "Remove the variant property on the position pos + in all the components with this variant-id" + [variant-id pos] + (ptk/reify ::remove-property + ptk/WatchEvent + (watch [it state _] + (let [page-id (:current-page-id state) + data (dsh/lookup-file-data state) + objects (-> (dsh/get-page data page-id) + (get :objects)) + related-components (find-related-components data objects variant-id) + + changes (-> (pcb/empty-changes it page-id) + (pcb/with-library-data data) + (pcb/with-objects objects) + (clv/generate-remove-property related-components pos)) + + undo-id (js/Symbol)] + (rx/of + (dwu/start-undo-transaction undo-id) + (dch/commit-changes changes) + (dwu/commit-undo-transaction undo-id)))))) + + + +(defn add-new-property + "Add a new variant property to all the components with this variant-id" + [variant-id] + (ptk/reify ::add-new-property + ptk/WatchEvent + (watch [it state _] + (let [page-id (:current-page-id state) + data (dsh/lookup-file-data state) + objects (-> (dsh/get-page data page-id) + (get :objects)) + + related-components (find-related-components data objects variant-id) + + + property-name (str "Property" (-> related-components + first + :variant-properties + count + inc)) + + changes (-> (pcb/empty-changes it page-id) + (pcb/with-library-data data) + (pcb/with-objects objects) + (clv/generate-add-new-property related-components property-name)) + + + undo-id (js/Symbol)] + (rx/of + (dwu/start-undo-transaction undo-id) + (dch/commit-changes changes) + (dwu/commit-undo-transaction undo-id)))))) + +(defn set-variant-id + "Sets the variant-id on a component" + [component-id variant-id] + (ptk/reify ::set-variant-id + ptk/WatchEvent + (watch [it state _] + (let [page-id (:current-page-id state) + data (dsh/lookup-file-data state) + changes (-> (pcb/empty-changes it page-id) + (pcb/with-library-data data) + (pcb/update-component component-id #(assoc % :variant-id variant-id))) + undo-id (js/Symbol)] + (rx/of + (dwu/start-undo-transaction undo-id) + (dch/commit-changes changes) + (dwu/commit-undo-transaction undo-id)))))) + +(defn transform-in-variant + "Given the id of a main shape of a component, creates a variant structure for + that component" + [id] + (ptk/reify ::transform-in-variant + ptk/WatchEvent + (watch [_ state _] + (let [variant-id (uuid/next) + variant-vec [variant-id] + new-component-id (uuid/next) + file-id (:current-file-id state) + page-id (:current-page-id state) + objects (dsh/lookup-page-objects state file-id page-id) + main (get objects id) + main-id (:id main) + undo-id (js/Symbol)] + + (rx/of + (dwu/start-undo-transaction undo-id) + (dwsh/create-artboard-from-selection variant-id) + (cl/remove-all-fills variant-vec {:color clr/black :opacity 1}) + (dwsl/create-layout-from-id variant-id :flex) + (dwsh/update-shapes variant-vec #(assoc % :layout-item-h-sizing :auto + :layout-item-v-sizing :auto + :layout-padding {:p1 30 :p2 30 :p3 30 :p4 30} + :layout-gap {:row-gap 0 :column-gap 20} + :name (:name main) + :r1 20 + :r2 20 + :r3 20 + :r4 20 + :is-variant-container true)) + (dwsh/update-shapes [main-id] #(assoc % :layout-item-h-sizing :fix :layout-item-v-sizing :fix :variant-id variant-id)) + (cl/add-stroke variant-vec {:stroke-alignment :inner + :stroke-style :solid + :stroke-color "#bb97d8" ;; todo use color var? + :stroke-opacity 1 + :stroke-width 2}) + (dwl/duplicate-component file-id (:component-id main) new-component-id) + (set-variant-id (:component-id main) variant-id) + (set-variant-id new-component-id variant-id) + (add-new-property variant-id) + (dwu/commit-undo-transaction undo-id)))))) diff --git a/frontend/src/app/main/ui/components/shape_icon.cljs b/frontend/src/app/main/ui/components/shape_icon.cljs index 060213681..f53154b03 100644 --- a/frontend/src/app/main/ui/components/shape_icon.cljs +++ b/frontend/src/app/main/ui/components/shape_icon.cljs @@ -17,10 +17,15 @@ [{:keys [shape main-instance?]}] (if (ctk/instance-head? shape) (if main-instance? - i/component + (if (ctk/is-variant? shape) + i/variant + i/component) i/component-copy) (case (:type shape) :frame (cond + (ctk/is-variant-container? shape) + i/component + (and (ctl/flex-layout? shape) (ctl/col? shape)) i/flex-horizontal diff --git a/frontend/src/app/main/ui/ds/controls/shared/options_dropdown.scss b/frontend/src/app/main/ui/ds/controls/shared/options_dropdown.scss index 7ca4d5b36..f86dc6dde 100644 --- a/frontend/src/app/main/ui/ds/controls/shared/options_dropdown.scss +++ b/frontend/src/app/main/ui/ds/controls/shared/options_dropdown.scss @@ -26,6 +26,7 @@ max-height: $sz-400; overflow-y: auto; overflow-x: hidden; + z-index: var(--z-index-dropdown); } .option { diff --git a/frontend/src/app/main/ui/icons.cljs b/frontend/src/app/main/ui/icons.cljs index c73c7d1bd..3c23a7c0e 100644 --- a/frontend/src/app/main/ui/icons.cljs +++ b/frontend/src/app/main/ui/icons.cljs @@ -257,6 +257,7 @@ (def ^:icon v2-icon-2 (icon-xref :v2-icon-2)) (def ^:icon v2-icon-3 (icon-xref :v2-icon-3)) (def ^:icon v2-icon-4 (icon-xref :v2-icon-4)) +(def ^:icon variant (icon-xref :variant)) (def ^:icon vertical-align-items-center (icon-xref :vertical-align-items-center)) (def ^:icon vertical-align-items-end (icon-xref :vertical-align-items-end)) (def ^:icon vertical-align-items-start (icon-xref :vertical-align-items-start)) diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets/common.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets/common.cljs index 371cfc624..a03c3d6e1 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets/common.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets/common.cljs @@ -20,6 +20,8 @@ [app.main.data.workspace :as dw] [app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.undo :as dwu] + [app.main.data.workspace.variants :as dwv] + [app.main.features :as features] [app.main.refs :as refs] [app.main.render :refer [component-svg component-svg-thumbnail]] [app.main.store :as st] @@ -372,6 +374,8 @@ can-detach? (and (seq copies) (every? #(not (ctn/has-any-copy-parent? objects %)) copies)) + variants? (features/use-feature "variants/v1") + do-detach-component #(st/emit! (dwl/detach-components (map :id copies))) @@ -405,6 +409,10 @@ do-create-annotation #(st/emit! (dw/set-annotations-id-for-create id)) + do-add-variant + #(when variants? + (st/emit! (dwv/transform-in-variant id))) + do-show-local-component #(st/emit! (dwl/go-to-local-component :id component-id)) @@ -454,5 +462,8 @@ :action do-show-component}) (when can-update-main? {:title (tr "workspace.shape.menu.update-main") - :action do-update-component})]] + :action do-update-component}) + (when (and variants? (not multi) main-instance?) + {:title (tr "workspace.shape.menu.add-variant") + :action do-add-variant})]] (filter (complement nil?) menu-entries))) diff --git a/frontend/src/app/main/ui/workspace/sidebar/layer_item.cljs b/frontend/src/app/main/ui/workspace/sidebar/layer_item.cljs index 0ebb9849c..81d1ef59d 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/layer_item.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/layer_item.cljs @@ -16,6 +16,7 @@ [app.common.uuid :as uuid] [app.main.data.workspace :as dw] [app.main.data.workspace.collapse :as dwc] + [app.main.features :as features] [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.shape-icon :as sic] @@ -53,7 +54,10 @@ (= uuid/zero (:parent-id item))) absolute? (ctl/item-absolute? item) components-v2 (mf/use-ctx ctx/components-v2) - main-instance? (or (not components-v2) (:main-instance item))] + main-instance? (or (not components-v2) (:main-instance item)) + variants? (features/use-feature "variants/v1") + is-variant? (when variants? (ctk/is-variant? item)) + variant-name (when is-variant? (:variant-name item))] [:* [:div {:id id :ref dref @@ -130,6 +134,7 @@ :is-selected selected? :type-comp component-tree? :type-frame (cfh/frame-shape? item) + :variant-name variant-name :is-hidden hidden?}] (when (not read-only?) diff --git a/frontend/src/app/main/ui/workspace/sidebar/layer_name.cljs b/frontend/src/app/main/ui/workspace/sidebar/layer_name.cljs index 5765041a6..41d8240f4 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/layer_name.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/layer_name.cljs @@ -29,7 +29,7 @@ ::mf/forward-ref true} [{:keys [shape-id shape-name is-shape-touched disabled-double-click on-start-edit on-stop-edit depth parent-size is-selected - type-comp type-frame is-hidden is-blocked]} external-ref] + type-comp type-frame variant-name is-hidden is-blocked]} external-ref] (let [edition* (mf/use-state false) edition? (deref edition*) @@ -38,6 +38,8 @@ shape-for-rename (mf/deref lens:shape-for-rename) + shape-name (d/nilv variant-name shape-name) + has-path? (str/includes? shape-name "/") start-edit diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs index 86fb956a2..53fd9dc65 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs @@ -11,10 +11,13 @@ [app.common.files.helpers :as cfh] [app.common.types.component :as ctk] [app.common.types.file :as ctf] + [app.main.data.helpers :as dsh] [app.main.data.modal :as modal] [app.main.data.workspace :as dw] [app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.specialized-panel :as dwsp] + [app.main.data.workspace.variants :as dwv] + [app.main.features :as features] [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.dropdown :refer [dropdown]] @@ -23,6 +26,9 @@ [app.main.ui.components.select :refer [select]] [app.main.ui.components.title-bar :refer [title-bar]] [app.main.ui.context :as ctx] + [app.main.ui.ds.buttons.icon-button :refer [icon-button*]] + [app.main.ui.ds.controls.combobox :refer [combobox*]] + [app.main.ui.ds.controls.input-with-values :refer [input-with-values*]] [app.main.ui.hooks :as h] [app.main.ui.icons :as i] [app.main.ui.workspace.sidebar.assets.common :as cmm] @@ -225,6 +231,78 @@ (when (or editing? creating?) [:div {:class (stl/css :counter)} (str size "/300")])]]))) + +(mf/defc component-variant* + [{:keys [component shape data page-id]}] + (let [id-component (:id component) + properties (:variant-properties component) + variant-id (:variant-id component) + objects (-> (dsh/get-page data page-id) + (get :objects)) + + related-components (dwv/find-related-components data objects variant-id) + + flat-comps ;; Get a list like [{:id 0 :prop1 "v1" :prop2 "v2"} {:id 1, :prop1 "v3" :prop2 "v4"}] + (map (fn [{:keys [id variant-properties]}] + (into {:id id} + (map (fn [{:keys [name value]}] [(keyword name) value]) + variant-properties))) + related-components) + + get-options + (mf/use-fn + (mf/deps related-components) + (fn [prop-name] + (->> related-components + (mapcat (fn [item] + (map :value (filter (fn [prop] (= (:name prop) prop-name)) + (:variant-properties item))))) + (filter some?) + distinct + (map (fn [val] {:label val :id val}))))) + + filter-matching + (mf/use-fn + (mf/deps flat-comps) + (fn [id exclude-key] + (let [reference-item (first (filter #(= (:id %) id) flat-comps)) + reference-values (dissoc reference-item :id exclude-key)] + + (->> flat-comps + (filter (fn [item] + (= (dissoc item :id exclude-key) reference-values))) + (map (fn [item] {:label (get item exclude-key) :value (:id item)})))))) + + + change-property-value + (mf/use-fn + (mf/deps id-component) + (fn [pos value] + (when-not (str/empty? value) + (st/emit! (dwv/update-property-value id-component pos value))))) + + switch-component + (mf/use-fn + (mf/deps shape) + (fn [id] + (st/emit! (dwl/component-swap shape (:component-file shape) id))))] + [:* + (for [[pos prop] (map vector (range) properties)] + + [:div {:key (str (:id shape) (:name prop)) :class (stl/css :variant-property-container)} + (if (ctk/main-instance? shape) + [:* + [:span {:class (stl/css :variant-property-name :variant-property-name-bg)} (:name prop)] + [:> combobox* {:default-selected (str (or (:value prop) "")) + :options (clj->js (get-options (:name prop))) + :on-change (partial change-property-value pos)}]] + + [:* + [:span {:class (stl/css :variant-property-name)} (:name prop)] + [:& select {:default-value id-component + :options (filter-matching id-component (keyword (:name prop))) + :on-change switch-component}]])])])) + (mf/defc component-swap-item {::mf/props :obj} [{:keys [item loop shapes file-id root-shape container component-id is-search listing-thumbs]}] @@ -508,36 +586,43 @@ :on-click (partial do-action action)} [:span {:class (stl/css :dropdown-label)} title]]))]])) + + (mf/defc component-menu {::mf/props :obj} [{:keys [shapes swap-opened?]}] - (let [current-file-id (mf/use-ctx ctx/current-file-id) + (let [current-file-id (mf/use-ctx ctx/current-file-id) + current-page-id (mf/use-ctx ctx/current-page-id) - libraries (deref refs/libraries) - current-file (get libraries current-file-id) + libraries (deref refs/libraries) + current-file (get libraries current-file-id) + data (get-in libraries [current-file-id :data]) - state* (mf/use-state - #(do {:show-content true - :menu-open false})) - state (deref state*) - open? (:show-content state) - menu-open? (:menu-open state) + state* (mf/use-state + #(do {:show-content true + :menu-open false})) + state (deref state*) + open? (:show-content state) + menu-open? (:menu-open state) - shapes (filter ctk/instance-head? shapes) - multi (> (count shapes) 1) - copies (filter ctk/in-component-copy? shapes) - can-swap? (boolean (seq copies)) + shapes (filter ctk/instance-head? shapes) + multi (> (count shapes) 1) + copies (filter ctk/in-component-copy? shapes) + can-swap? (boolean (seq copies)) ;; For when it's only one shape - shape (first shapes) - id (:id shape) - shape-name (:name shape) + shape (first shapes) + id (:id shape) + shape-name (:name shape) - component (ctf/resolve-component shape - current-file - libraries - {:include-deleted? true}) - main-instance? (ctk/main-instance? shape) + component (ctf/resolve-component shape + current-file + libraries + {:include-deleted? true}) + + variants? (features/use-feature "variants/v1") + is-variant? (when variants? (ctk/is-variant? component)) + main-instance? (ctk/main-instance? shape) toggle-content (mf/use-fn #(swap! state* update :show-content not)) @@ -576,9 +661,9 @@ (fn [] (swap! state* update :render inc))) - menu-entries (cmm/generate-components-menu-entries shapes true) - show-menu? (seq menu-entries) - path (->> component (:path) (cfh/split-path) (cfh/join-path-with-dot))] + menu-entries (cmm/generate-components-menu-entries shapes true) + show-menu? (seq menu-entries) + path (->> component (:path) (cfh/split-path) (cfh/join-path-with-dot))] (when (seq shapes) [:div {:class (stl/css :element-set)} @@ -612,7 +697,9 @@ [:span {:class (stl/css :component-icon)} (if main-instance? - i/component + (if is-variant? + i/variant + i/component) i/component-copy)] [:div {:class (stl/css :name-wrapper)} @@ -643,5 +730,119 @@ (when (and (not swap-opened?) (not multi)) [:& component-annotation {:id id :shape shape :component component :rerender-fn rerender-fn}]) + + (when (and is-variant? (not swap-opened?) (not multi)) + [:> component-variant* {:component component :shape shape :data data :page-id current-page-id}]) + (when (dbg/enabled? :display-touched) [:div ":touched " (str (:touched shape))])])]))) + + +(mf/defc variant-menu* + [{:keys [shapes]}] + (let [;; TODO check multi. What is shown? User can change properties like width? + multi (> (count shapes) 1) + + shape (first shapes) + shape-name (:name shape) + + libraries (deref refs/libraries) + current-file-id (mf/use-ctx ctx/current-file-id) + current-page-id (mf/use-ctx ctx/current-page-id) + data (get-in libraries [current-file-id :data]) + + objects (-> (dsh/get-page data current-page-id) + (get :objects)) + + first-variant (get objects (first (:shapes shape))) + variant-id (:variant-id first-variant) + + properties (->> (dwv/find-related-components data objects variant-id) + (mapcat :variant-properties) + (group-by :name) + (map (fn [[k v]] {:name k :values (map :value v)}))) + + menu-open* (mf/use-state false) + menu-open? (deref menu-open*) + + + menu-entries [{:title (tr "workspace.shape.menu.add-variant-property") + :action #(st/emit! (dwv/add-new-property variant-id))}] + + show-menu? (seq menu-entries) + + on-menu-click + (mf/use-fn + (mf/deps menu-open* menu-open?) + (fn [event] + (dom/prevent-default event) + (dom/stop-propagation event) + (reset! menu-open* (not menu-open?)))) + + on-menu-close + (mf/use-fn + (mf/deps menu-open*) + #(reset! menu-open* false)) + + update-property-name + (mf/use-fn + (mf/deps variant-id) + (fn [pos new-name] + (st/emit! (dwv/update-property-name variant-id pos new-name)))) + + remove-property + (mf/use-fn + (mf/deps variant-id) + (fn [pos] + (when (> (count properties) 1) + (st/emit! (dwv/remove-property variant-id pos)))))] + (when (seq shapes) + [:div {:class (stl/css :element-set)} + [:div {:class (stl/css :element-title)} + + + [:& title-bar {:collapsable false + :title (tr "workspace.options.component") + :class (stl/css :title-spacing-component)} + [:span {:class (stl/css :copy-text)} + (tr "workspace.options.component.main")]]] + + [:div {:class (stl/css :element-content)} + [:div {:class (stl/css-case :component-wrapper true + :with-actions show-menu? + :without-actions (not show-menu?))} + [:button {:class (stl/css-case :component-name-wrapper true + :with-main true + :swappeable false)} + + [:span {:class (stl/css :component-icon)} i/component] + + [:div {:class (stl/css :name-wrapper)} + [:div {:class (stl/css :component-name)} + [:span {:class (stl/css :component-name-inside)} + (if multi + (tr "settings.multiple") + (cfh/last-path shape-name))]]]] + + + (when show-menu? + [:div {:class (stl/css :component-actions)} + [:button {:class (stl/css-case :menu-btn true + :selected menu-open?) + :on-click on-menu-click} + i/menu] + + [:& component-ctx-menu {:show menu-open? + :on-close on-menu-close + :menu-entries menu-entries + :main-instance true}]])] + [:* + (for [[pos property] (map vector (range) properties)] + (let [val (str/join ", " (:values property))] + [:div {:key (str (:id shape) (:name property)) :class (stl/css :variant-property-row)} + [:> input-with-values* {:name (:name property) :values val :on-blur (partial update-property-name pos)}] + [:> icon-button* {:variant "ghost" + :aria-label (tr "workspace.shape.menu.remove-variant-property") + :on-click (partial remove-property pos) + :icon "remove" + :disabled (<= (count properties) 1)}]]))]]]))) \ No newline at end of file diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.scss b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.scss index 030d64cac..4c210a876 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.scss +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.scss @@ -4,7 +4,9 @@ // // Copyright (c) KALEIDOS INC +@use "../../../../ds/typography.scss" as t; @import "refactor/common-refactor.scss"; + .element-set { margin: 0; padding-top: $s-8; @@ -33,6 +35,7 @@ @include flexCenter; width: $s-12; height: 100%; + svg { height: $s-12; width: $s-12; @@ -54,6 +57,7 @@ &.without-actions { padding-right: 0.5rem; + .component-name-wrapper { width: 100%; border-radius: $br-8; @@ -71,6 +75,7 @@ border-radius: $br-8 0 0 $br-8; background-color: var(--assets-item-background-color); color: var(--assets-item-name-foreground-color-hover); + &:hover { background-color: var(--assets-item-background-color-hover); color: var(--assets-item-name-foreground-color-hover); @@ -81,6 +86,7 @@ @include flexCenter; height: $s-32; width: $s-12; + svg { @extend .button-icon-small; stroke: var(--icon-foreground); @@ -129,14 +135,17 @@ border-radius: 0 $br-8 $br-8 0; background-color: var(--assets-item-background-color); color: var(--assets-item-name-foreground-color-hover); + svg { @extend .button-icon; min-height: $s-16; min-width: $s-16; } + &:hover { background-color: var(--assets-item-background-color-hover); color: var(--assets-item-name-foreground-color-hover); + &.selected { @extend .button-icon-selected; } @@ -177,6 +186,7 @@ .icon-wrapper { display: flex; + svg { @extend .button-icon-small; stroke: var(--icon-foreground); @@ -192,9 +202,11 @@ border: 0; font-size: $fs-12; color: var(--input-foreground-color-active); + &::placeholder { color: var(--input-foreground-color-disabled); } + &:focus-visible { border-color: var(--input-border-outline-color-active); } @@ -205,8 +217,10 @@ @include flexCenter; height: $s-16; width: $s-16; + .clear-icon { @include flexCenter; + svg { @extend .button-icon-small; stroke: var(--icon-foreground); @@ -218,6 +232,7 @@ @include flexCenter; width: $s-12; margin-left: $s-8; + svg { @extend .button-icon-small; stroke: var(--icon-foreground); @@ -240,6 +255,7 @@ .back-arrow { @include flexCenter; height: $s-32; + svg { height: $s-12; width: $s-12; @@ -329,6 +345,7 @@ --assets-component-current-border-color: var(--assets-component-border-color); border: $s-4 solid var(--assets-component-current-border-color); cursor: pointer; + img { height: auto; width: auto; @@ -337,12 +354,14 @@ pointer-events: none; border: 0; } + svg { height: 100%; width: 100%; stroke: none; object-fit: contain; } + .component-name { @include bodySmallTypography; @include textEllipsis; @@ -358,6 +377,7 @@ &:hover { background-color: var(--assets-item-background-color-hover); + .component-name { display: block; color: var(--assets-item-name-foreground-color-hover); @@ -367,6 +387,7 @@ &.selected { --assets-component-current-border-color: var(--assets-item-border-color); + .component-name { color: var(--assets-item-name-foreground-color-hover); } @@ -375,9 +396,11 @@ &.disabled { background: var(--assets-component-background-color-disabled); cursor: auto; + svg { cursor: auto; } + .component-name { background: linear-gradient( to top, @@ -461,8 +484,10 @@ @include textEllipsis; color: var(--assets-item-name-foreground-color); } + &:hover { color: var(--assets-item-name-foreground-color-hover); + .component-group-name { color: var(--assets-item-name-foreground-color-hover); } @@ -472,6 +497,7 @@ .arrow-icon { @include flexCenter; height: $s-32; + svg { height: $s-12; width: $s-12; @@ -525,12 +551,14 @@ margin: 0; padding: 0; cursor: pointer; + svg { @extend .button-icon; stroke: var(--icon-foreground); width: $s-16; height: $s-16; } + &.expanded svg { transform: rotate(90deg); } @@ -562,6 +590,7 @@ &.icon-tick.invalid:hover { cursor: default; + svg { stroke: var(--icon-foreground); } @@ -589,8 +618,10 @@ &.editing { border: $s-1 solid var(--input-border-color-success); + .annotation-title { border-bottom: $s-1 solid var(--entry-border-color-disabled); + .icon { display: flex; } @@ -603,9 +634,11 @@ &.creating { border: $s-1 solid var(--input-border-color-success); + .annotation-title .icon { display: flex; } + textarea { min-height: $s-252; } @@ -613,6 +646,7 @@ .hidden { display: none; + svg { display: none; } @@ -650,7 +684,8 @@ -moz-box-shadow: none; box-shadow: none; - resize: none; /*remove the resize handle on the bottom right*/ + resize: none; + /*remove the resize handle on the bottom right*/ } textarea, @@ -666,3 +701,37 @@ } } } + +.variant-property-row { + @include flexRow; + justify-content: space-between; + width: 100%; + margin-block-start: $s-12; +} + +.variant-property-container { + @include t.use-typography("body-small"); + width: 100%; + display: flex; + gap: var(--sp-xs); +} + +.variant-property-name-bg { + border-radius: $br-8; + background-color: var(--assets-item-background-color); +} + +.variant-property-name { + color: var(--color-foreground-primary); + + width: $s-104; + + display: flex; + align-items: center; + justify-content: center; + height: var(--sp-xxxl); + + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs index 6d7a0b433..cad5009a7 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs @@ -8,10 +8,11 @@ (:require [app.common.data.macros :as dm] [app.common.types.shape.layout :as ctl] + [app.main.features :as features] [app.main.refs :as refs] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [app.main.ui.workspace.sidebar.options.menus.color-selection :refer [color-selection-menu*]] - [app.main.ui.workspace.sidebar.options.menus.component :refer [component-menu]] + [app.main.ui.workspace.sidebar.options.menus.component :refer [component-menu variant-menu*]] [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]] [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs-shape fill-menu]] [app.main.ui.workspace.sidebar.options.menus.frame-grid :refer [frame-grid]] @@ -66,58 +67,62 @@ is-layout-container? (ctl/any-layout? shape) is-flex-layout? (ctl/flex-layout? shape) is-grid-layout? (ctl/grid-layout? shape) - is-layout-child-absolute? (ctl/item-absolute? shape)] + is-layout-child-absolute? (ctl/item-absolute? shape) + variants? (features/use-feature "variants/v1") + is-variant? (when variants? (:is-variant-container shape))] - [:* - [:& layer-menu {:ids ids - :type shape-type - :values layer-values}] - [:> measures-menu* {:ids ids - :values measure-values - :type shape-type - :shape shape}] + (if is-variant? + [:> variant-menu* {:shapes [shape]}] + [:* + [:& layer-menu {:ids ids + :type shape-type + :values layer-values}] + [:> measures-menu* {:ids ids + :values measure-values + :type shape-type + :shape shape}] - [:& component-menu {:shapes [shape]}] + [:& component-menu {:shapes [shape]}] - [:& layout-container-menu - {:type shape-type - :ids [(:id shape)] - :values layout-container-values - :multiple false}] + [:& layout-container-menu + {:type shape-type + :ids [(:id shape)] + :values layout-container-values + :multiple false}] - (when (and (= (count ids) 1) is-layout-child? is-grid-parent?) - [:& grid-cell/options - {:shape (first parents) - :cell (ctl/get-cell-by-shape-id (first parents) (first ids))}]) + (when (and (= (count ids) 1) is-layout-child? is-grid-parent?) + [:& grid-cell/options + {:shape (first parents) + :cell (ctl/get-cell-by-shape-id (first parents) (first ids))}]) - (when (or is-layout-child? is-layout-container?) - [:& layout-item-menu - {:ids ids - :type shape-type - :values layout-item-values - :is-flex-parent? is-flex-parent? - :is-grid-parent? is-grid-parent? - :is-flex-layout? is-flex-layout? - :is-grid-layout? is-grid-layout? - :is-layout-child? is-layout-child? - :is-layout-container? is-layout-container? - :shape shape}]) + (when (or is-layout-child? is-layout-container?) + [:& layout-item-menu + {:ids ids + :type shape-type + :values layout-item-values + :is-flex-parent? is-flex-parent? + :is-grid-parent? is-grid-parent? + :is-flex-layout? is-flex-layout? + :is-grid-layout? is-grid-layout? + :is-layout-child? is-layout-child? + :is-layout-container? is-layout-container? + :shape shape}]) - (when (or (not ^boolean is-layout-child?) ^boolean is-layout-child-absolute?) - [:& constraints-menu {:ids ids - :values constraint-values}]) + (when (or (not ^boolean is-layout-child?) ^boolean is-layout-child-absolute?) + [:& constraints-menu {:ids ids + :values constraint-values}]) - [:& fill-menu {:ids ids - :type shape-type - :values (select-keys shape fill-attrs-shape)}] - [:& stroke-menu {:ids ids + [:& fill-menu {:ids ids :type shape-type - :values stroke-values}] - [:> color-selection-menu* {:type shape-type - :shapes shapes-with-children - :file-id file-id - :libraries shared-libs}] - [:> shadow-menu* {:ids ids :values (get shape :shadow)}] - [:& blur-menu {:ids ids - :values (select-keys shape [:blur])}] - [:& frame-grid {:shape shape}]])) + :values (select-keys shape fill-attrs-shape)}] + [:& stroke-menu {:ids ids + :type shape-type + :values stroke-values}] + [:> color-selection-menu* {:type shape-type + :shapes shapes-with-children + :file-id file-id + :libraries shared-libs}] + [:> shadow-menu* {:ids ids :values (get shape :shadow)}] + [:& blur-menu {:ids ids + :values (select-keys shape [:blur])}] + [:& frame-grid {:shape shape}]]))) diff --git a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs index 3386d3b9a..dcc3f1eef 100644 --- a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs @@ -18,6 +18,7 @@ [app.main.data.common :as dcm] [app.main.data.workspace :as dw] [app.main.data.workspace.interactions :as dwi] + [app.main.features :as features] [app.main.refs :as refs] [app.main.store :as st] [app.main.streams :as ms] @@ -131,9 +132,11 @@ (on-frame-leave (:id frame)))) main-instance? (ctk/main-instance? frame) + variants? (features/use-feature "variants/v1") + is-variant? (when variants? (:is-variant-container frame)) text-width (* (:width frame) zoom) - show-icon? (and (or (:use-for-thumbnail frame) grid-edition? main-instance?) + show-icon? (and (or (:use-for-thumbnail frame) grid-edition? main-instance? is-variant?) (not (<= text-width 15))) text-pos-x (if show-icon? 15 0) @@ -196,7 +199,8 @@ (cond (:use-for-thumbnail frame) [:use {:href "#icon-boards-thumbnail"}] grid-edition? [:use {:href "#icon-grid"}] - main-instance? [:use {:href "#icon-component"}])]) + main-instance? [:use {:href "#icon-component"}] + is-variant? [:use {:href "#icon-component"}])]) (if ^boolean edition? ;; Case when edition? is true diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 3db863a14..7f77c3944 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -6210,6 +6210,15 @@ msgstr "Selection to board" msgid "workspace.shape.menu.create-component" msgstr "Create component" +msgid "workspace.shape.menu.add-variant" +msgstr "Add variant" + +msgid "workspace.shape.menu.add-variant-property" +msgstr "Add new property" + +msgid "workspace.shape.menu.remove-variant-property" +msgstr "Remove property" + #: src/app/main/ui/workspace/context_menu.cljs:565 msgid "workspace.shape.menu.create-multiple-components" msgstr "Create multiple components" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index c30a0976d..57b34b36b 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -6224,6 +6224,16 @@ msgstr "Tablero de selección" msgid "workspace.shape.menu.create-component" msgstr "Crear componente" +msgid "workspace.shape.menu.add-variant" +msgstr "Añadir variante" + +msgid "workspace.shape.menu.add-variant-property" +msgstr "Añadir nueva propiedad" + +msgid "workspace.shape.menu.remove-variant-property" +msgstr "Eliminar propiedad" + + #: src/app/main/ui/workspace/context_menu.cljs:565 msgid "workspace.shape.menu.create-multiple-components" msgstr "Crear múltiples componentes"