Add shortcuts for creating variants and properties (#7181)

*  Add shortcuts for creating variants and properties

* 📎 PR changes
This commit is contained in:
Luis de Dios 2025-08-26 09:32:41 +02:00 committed by GitHub
parent d80ef17623
commit f1e7149e88
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 178 additions and 219 deletions

View File

@ -50,9 +50,8 @@
(def ref:annotations-state
(l/derived :workspace-annotations st/state))
(mf/defc component-annotation
{::mf/props :obj
::mf/private true}
(mf/defc component-annotation*
{::mf/private true}
[{:keys [id shape component rerender-fn]}]
(let [main-instance? (:main-instance shape)
component-id (:component-id shape)
@ -500,8 +499,7 @@
(tr "workspace.options.component.variant.duplicated.copy.locate")]]))]))
(mf/defc component-swap-item
{::mf/props :obj}
(mf/defc component-swap-item*
[{:keys [item loop shapes file-id root-shape container component-id is-search listing-thumbs num-variants]}]
(let [on-select
(mf/use-fn
@ -535,8 +533,7 @@
[:span {:class (stl/css-case :variant-mark-cell listing-thumbs :variant-icon true)
:title (tr "workspace.assets.components.num-variants" num-variants)} i/variant])]))
(mf/defc component-group-item
{::mf/props :obj}
(mf/defc component-group-item*
[{:keys [item on-enter-group]}]
(let [group-name (:name item)
on-group-click #(on-enter-group group-name)]
@ -571,8 +568,7 @@
(= (:component-id shape-a)
(:component-id shape-b)))
(mf/defc component-swap
{::mf/props :obj}
(mf/defc component-swap*
[{:keys [shapes]}]
(let [single? (= 1 (count shapes))
shape (first shapes)
@ -753,7 +749,7 @@
(when (:listing-thumbs? filters)
[:div {:class (stl/css :component-list)}
(for [item groups]
[:& component-group-item {:item item :on-enter-group on-enter-group}])])
[:> component-group-item* {:item item :on-enter-group on-enter-group}])])
[:div {:class (stl/css-case :component-grid (:listing-thumbs? filters)
:component-list (not (:listing-thumbs? filters)))}
@ -766,24 +762,23 @@
(keep :component-id)
set)
loop? (some #(contains? components %) parent-components)]
[:& component-swap-item {:key (dm/str (:id item))
:item item
:loop loop?
:shapes shapes
:file-id current-library-id
:root-shape root-shape
:container container
:component-id component-id
:is-search is-search?
:listing-thumbs (:listing-thumbs? filters)
:num-variants (count-variants item)}])
[:> component-swap-item* {:key (dm/str (:id item))
:item item
:loop loop?
:shapes shapes
:file-id current-library-id
:root-shape root-shape
:container container
:component-id component-id
:is-search is-search?
:listing-thumbs (:listing-thumbs? filters)
:num-variants (count-variants item)}])
[:& component-group-item {:item item
:key (:name item)
:on-enter-group on-enter-group}]))]]]]))
[:> component-group-item* {:item item
:key (:name item)
:on-enter-group on-enter-group}]))]]]]))
(mf/defc component-ctx-menu
{::mf/props :obj}
(mf/defc component-ctx-menu*
[{:keys [menu-entries on-close show main-instance]}]
(let [do-action
(fn [action event]
@ -879,8 +874,20 @@
(when can-swap? (st/emit! (dwsp/open-specialized-panel :component-swap)))
(tm/schedule-on-idle #(dom/focus! (dom/get-element search-id))))))
create-variant
(mf/use-fn
(mf/deps id)
#(st/emit! (dwv/add-new-variant id)))
add-new-property
(mf/use-fn
(mf/deps shape)
#(st/emit! (dwv/add-new-property (:variant-id shape) {:property-value "Value 1"
:editing? true})))
on-combine-as-variants
#(st/emit! (dwv/combine-as-variants))
(mf/use-fn
#(st/emit! (dwv/combine-as-variants)))
;; NOTE: function needed for force rerender from the bottom
;; components. This is because `component-annotation`
@ -903,7 +910,8 @@
(if swap-opened?
[:button {:class (stl/css :title-back)
:on-click on-component-back}
[:span {:class (stl/css :icon-back)} i/arrow]
[:> icon* {:icon-id "arrow-left"
:size "s"}]
[:span (tr "workspace.options.component")]]
[:*
@ -921,60 +929,74 @@
(tr "workspace.options.component.copy"))]]
(when is-variant?
[:div {:class (stl/css :variants-help-modal-button)}
[:> icon-button* {:variant "ghost"
:aria-label (tr "workspace.options.component.variants-help-modal.title")
:on-click on-click-variant-title-help
:icon "help"}]])])]
[:> icon-button* {:variant "ghost"
:aria-label (tr "workspace.options.component.variants-help-modal.title")
:on-click on-click-variant-title-help
:icon "help"}])
(when (and is-variant? main-instance?)
[:> icon-button* {:variant "ghost"
:aria-label (tr "workspace.shape.menu.add-variant")
:on-click create-variant
:icon "variant"}])])]
(when open?
[: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 (and can-swap? (not multi))
:swappeable (and can-swap? (not swap-opened?)))
:data-testid "swap-component-btn"
:on-click open-component-panel}
[:div {:class (stl/css :component-line)}
[:span {:class (stl/css :component-icon)}
(if main-instance?
(if is-variant?
i/variant
i/component)
i/component-copy)]
[:div {:class (stl/css :component-wrapper)}
[:div {:class (stl/css :name-wrapper)}
[:div {:class (stl/css :component-name)}
[:span {:class (stl/css :component-name-inside)}
(if (and multi (not same-variant?))
(tr "settings.multiple")
(cfh/last-path shape-name))]]
[:button {:class (stl/css-case :component-name-wrapper true
:without-menu (not show-menu?)
:swappeable (and can-swap? (not swap-opened?)))
:data-testid "swap-component-btn"
:on-click open-component-panel}
(when (and can-swap? (not multi))
[:div {:class (stl/css :component-parent-name)}
(if (:deleted component)
(tr "workspace.options.component.unlinked")
(cfh/merge-path-item-with-dot path (:name component)))])]]
[:div {:class (stl/css :component-icon)}
[:> icon* {:size "s"
:icon-id (if main-instance?
(if is-variant? "variant" "component")
"component-copy")}]]
(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]
[:div {:class (stl/css :component-name-outside)}
[:div {:class (stl/css :component-name)}
[:span {:class (stl/css :component-name-inside)}
(if (and multi (not same-variant?))
(tr "settings.multiple")
(cfh/last-path shape-name))]]
[:& component-ctx-menu {:show menu-open?
:on-close on-menu-close
:menu-entries menu-entries
:main-instance main-instance?}]])]
(when (and can-swap? (not multi))
[:div {:class (stl/css :component-parent-name)}
(if (:deleted component)
(tr "workspace.options.component.unlinked")
(cfh/merge-path-item-with-dot path (:name component)))])]]
(when show-menu?
[:div {:class (stl/css :component-actions)}
[:button {:class (stl/css-case :component-menu-btn true
:selected menu-open?)
:on-click on-menu-click}
[:> icon* {:icon-id "menu"}]]
[:> component-ctx-menu* {:show menu-open?
:on-close on-menu-close
:menu-entries menu-entries
:main-instance main-instance?}]])]
(when (and is-variant? main-instance?)
[:> icon-button* {:variant "ghost"
:aria-label (tr "workspace.shape.menu.add-variant-property")
:on-click add-new-property
:icon "add"}])]
(when swap-opened?
[:& component-swap {:shapes copies}])
[:> component-swap* {:shapes copies}])
(when (and (not swap-opened?) (not multi))
[:& component-annotation {:id id :shape shape :component component :rerender-fn rerender-fn}])
[:> component-annotation* {:id id
:shape shape
:component component
:rerender-fn rerender-fn}])
(when (and is-variant?
(not main-instance?)
@ -1044,11 +1066,21 @@
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 {:property-value "Value 1"
:editing? true}))}
{:title (tr "workspace.shape.menu.add-variant")
:action #(st/emit! (dwv/add-new-variant (:id shape)))}]
create-variant
(mf/use-fn
(mf/deps shape)
#(st/emit! (dwv/add-new-variant (:id shape))))
add-new-property
(mf/use-fn
(mf/deps variant-id)
#(st/emit! (dwv/add-new-property variant-id {:property-value "Value 1"
:editing? true})))
menu-entries [{:title (tr "workspace.shape.menu.add-variant-property")
:action add-new-property}
{:title (tr "workspace.shape.menu.add-variant")
:action create-variant}]
toggle-content
(mf/use-fn
@ -1118,42 +1150,52 @@
[:span {:class (stl/css :copy-text)}
(tr "workspace.options.component.main")]]
[:div {:class (stl/css :variants-help-modal-button)}
[:div {:class (stl/css :title-actions)}
[:> icon-button* {:variant "ghost"
:aria-label (tr "workspace.options.component.variants-help-modal.title")
:on-click on-click-variant-title-help
:icon "help"}]]]]
:icon "help"}]
[:> icon-button* {:variant "ghost"
:aria-label (tr "workspace.shape.menu.add-variant")
:on-click create-variant
:icon "variant"}]]]]
(when open?
[:div {:class (stl/css :element-content)}
[:div {:class (stl/css-case :component-wrapper true
:with-actions (not multi?)
:without-actions multi?)}
[:button {:class (stl/css-case :component-name-wrapper true
:with-main true
:swappeable false)}
[:div {:class (stl/css :component-line)}
[:span {:class (stl/css :component-icon)} i/component]
[:div {:class (stl/css :component-wrapper)}
[: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))]]]]
[:button {:class (stl/css :component-name-wrapper)}
[:div {:class (stl/css :component-icon)}
[:> icon* {:size "s"
:icon-id "component"}]]
(when-not multi?
[:div {:class (stl/css :component-actions)}
[:button {:class (stl/css-case :menu-btn true
:selected menu-open?)
:on-click on-menu-click}
i/menu]
[:div {:class (stl/css :component-name-outside)}
[:div {:class (stl/css :component-name)}
[:span {:class (stl/css :component-name-inside)}
(if multi?
(tr "settings.multiple")
(cfh/last-path shape-name))]]]]
[:& component-ctx-menu {:show menu-open?
:on-close on-menu-close
:menu-entries menu-entries
:main-instance true}]])]
(when-not multi?
[:div {:class (stl/css :component-actions)}
[:button {:class (stl/css-case :component-menu-btn true
:selected menu-open?)
:on-click on-menu-click}
[:> icon* {:icon-id "menu"}]]
[:> component-ctx-menu* {:show menu-open?
:on-close on-menu-close
:menu-entries menu-entries
:main-instance true}]])]
[:> icon-button* {:variant "ghost"
:aria-label (tr "workspace.shape.menu.add-variant-property")
:on-click add-new-property
:icon "add"}]]
(when-not multi?
[:div {:class (stl/css :variant-property-list)}

View File

@ -9,6 +9,9 @@
.element-set {
margin: 0;
display: grid;
grid-template-columns: repeat(8, var(--sp-xxxl));
column-gap: var(--sp-xs);
}
.element-content {
@ -21,9 +24,9 @@
}
.element-title {
display: grid;
grid-template-columns: repeat(8, var(--sp-xxxl));
grid-column: span 8;
column-gap: var(--sp-xs);
display: flex;
}
.title-back {
@ -44,50 +47,31 @@
.title-spacing-component {
justify-content: flex-start;
gap: $s-8;
grid-column: span 7;
flex-grow: 1;
}
.title-bar-variant {
flex-grow: 0;
}
.variants-help-modal-button {
margin-inline-start: auto;
grid-column: span 1;
.title-actions {
display: flex;
gap: var(--sp-xs);
}
.icon-back {
@include flexCenter;
width: $s-12;
height: 100%;
svg {
height: $s-12;
width: $s-12;
stroke: var(--icon-foreground);
transform: rotate(180deg);
}
}
.component-wrapper {
.component-line {
grid-column: span 8;
width: 100%;
min-height: $s-32;
border-radius: $br-8;
display: flex;
gap: var(--sp-xs);
}
&.with-actions {
display: flex;
gap: $s-1;
}
&.without-actions {
padding-right: 0.5rem;
.component-name-wrapper {
width: 100%;
border-radius: $br-8;
}
}
.component-wrapper {
display: flex;
flex-grow: 1;
gap: $s-1;
}
.component-name-wrapper {
@ -102,24 +86,27 @@
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);
&.without-menu {
width: 100%;
border-radius: $br-8;
}
&.swappeable {
&:hover {
background-color: var(--assets-item-background-color-hover);
color: var(--assets-item-name-foreground-color-hover);
}
}
}
.component-icon {
@include flexCenter;
display: flex;
height: $s-32;
width: $s-16;
svg {
@extend .button-icon-small;
stroke: var(--icon-foreground);
}
align-items: center;
color: var(--icon-foreground);
}
.name-wrapper {
.component-name-outside {
@include flexColumn;
min-height: $s-32;
padding: $s-8 0 $s-8 $s-2;
@ -156,32 +143,16 @@
width: $s-32;
}
.menu-btn {
@extend .button-tertiary;
.component-menu-btn {
@extend .button-secondary;
cursor: unset;
height: 100%;
width: 100%;
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;
&.selected {
@extend .button-icon-selected;
}
&:hover {
background-color: var(--assets-item-background-color-hover);
color: var(--assets-item-name-foreground-color-hover);
&.selected {
@extend .button-icon-selected;
}
}
}
.menu-btn.selected {
@extend .button-icon-selected;
}
.copy-text {
@ -192,10 +163,6 @@
color: var(--title-foreground-color);
}
.swappeable {
cursor: pointer;
}
.custom-select-dropdown {
@extend .dropdown-wrapper;
right: 0;
@ -211,56 +178,6 @@
@extend .dropdown-element-base;
}
.icon-wrapper {
display: flex;
svg {
@extend .button-icon-small;
stroke: var(--icon-foreground);
}
}
.input-text {
@include removeInputStyle;
height: $s-32;
width: 100%;
margin: 0;
padding: $s-4;
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);
}
}
.clear-btn {
@include buttonStyle;
@include flexCenter;
height: $s-16;
width: $s-16;
.clear-icon {
@include flexCenter;
svg {
@extend .button-icon-small;
stroke: var(--icon-foreground);
}
}
}
.search-icon {
@include flexCenter;
margin-left: $s-8;
flex: 0 0 $s-16;
}
.component-path {
display: flex;
align-items: center;