mirror of
https://github.com/penpot/penpot.git
synced 2026-04-25 11:18:36 +00:00
🎉 Reorder properties for a component (#7429)
* 🎉 Reorder properties when a component with variants is selected * 🎉 Reorder properties when a single variant is selected * ♻️ Refactor SCSS and component structure * 📚 Update changelog * 📎 PR changes (styling) * 📎 PR changes (functionality)
This commit is contained in:
parent
4937580585
commit
544bedf7c2
@ -37,6 +37,7 @@
|
||||
- Switch several variant copies at the same time [Taiga #11411](https://tree.taiga.io/project/penpot/us/11411)
|
||||
- Invitations management improvements [Taiga #3479](https://tree.taiga.io/project/penpot/us/3479)
|
||||
- Alternative ways of creating variants - Button Viewport [Taiga #11931](https://tree.taiga.io/project/penpot/us/11931)
|
||||
- Reorder properties for a component [Taiga #10225](https://tree.taiga.io/project/penpot/us/10225)
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
|
||||
@ -81,6 +81,26 @@
|
||||
#(assoc % :variant-error value))))))
|
||||
|
||||
|
||||
(defn generate-reorder-variant-poperties
|
||||
[changes variant-id from-pos to-space-between-pos]
|
||||
(let [data (pcb/get-library-data changes)
|
||||
objects (pcb/get-objects changes)
|
||||
related-components (cfv/find-variant-components data objects variant-id)]
|
||||
(reduce (fn [changes component]
|
||||
(let [props (:variant-properties component)
|
||||
props (ctv/reorder-by-moving-to-position props from-pos to-space-between-pos)
|
||||
main-id (:main-instance-id component)
|
||||
name (ctv/properties-to-name props)]
|
||||
(-> changes
|
||||
(pcb/update-component (:id component)
|
||||
#(assoc % :variant-properties props)
|
||||
{:apply-changes-local-library? true})
|
||||
(pcb/update-shapes [main-id]
|
||||
#(assoc % :variant-name name)))))
|
||||
changes
|
||||
related-components)))
|
||||
|
||||
|
||||
(defn generate-add-new-property
|
||||
[changes variant-id & {:keys [fill-values? editing? property-name property-value]}]
|
||||
(let [data (pcb/get-library-data changes)
|
||||
|
||||
@ -310,3 +310,27 @@
|
||||
the real name of the shape joined by the properties values separated by '/'"
|
||||
[variant]
|
||||
(cpn/merge-path-item (:name variant) (str/replace (:variant-name variant) #", " " / ")))
|
||||
|
||||
(defn reorder-by-moving-to-position
|
||||
"Reorder a vector by moving one of their items from some position to some space between positions.
|
||||
It clamps the position numbers to a valid range."
|
||||
[props from-pos to-space-between-pos]
|
||||
(let [max-space-pos (count props)
|
||||
max-prop-pos (dec max-space-pos)
|
||||
|
||||
from-pos (max 0 (min max-prop-pos from-pos))
|
||||
to-space-between-pos (max 0 (min max-space-pos to-space-between-pos))]
|
||||
|
||||
(if (= from-pos to-space-between-pos)
|
||||
props
|
||||
(let [elem (nth props from-pos)
|
||||
without-elem (-> []
|
||||
(into (subvec props 0 from-pos))
|
||||
(into (subvec props (inc from-pos))))
|
||||
insert-pos (if (< from-pos to-space-between-pos)
|
||||
(dec to-space-between-pos)
|
||||
to-space-between-pos)]
|
||||
(-> []
|
||||
(into (subvec without-elem 0 insert-pos))
|
||||
(into [elem])
|
||||
(into (subvec without-elem insert-pos)))))))
|
||||
|
||||
@ -159,3 +159,48 @@
|
||||
|
||||
(t/testing "update-number-in-repeated-prop-names"
|
||||
(t/is (= (ctv/update-number-in-repeated-prop-names props) numbered-props)))))
|
||||
|
||||
|
||||
(t/deftest reorder-by-moving-to-position
|
||||
(let [props [{:name "border" :value "no"}
|
||||
{:name "color" :value "blue"}
|
||||
{:name "shadow" :value "yes"}
|
||||
{:name "background" :value "none"}]]
|
||||
|
||||
(t/testing "reorder-by-moving-to-position"
|
||||
(t/is (= (ctv/reorder-by-moving-to-position props 0 2) [{:name "color" :value "blue"}
|
||||
{:name "border" :value "no"}
|
||||
{:name "shadow" :value "yes"}
|
||||
{:name "background" :value "none"}]))
|
||||
(t/is (= (ctv/reorder-by-moving-to-position props 0 3) [{:name "color" :value "blue"}
|
||||
{:name "shadow" :value "yes"}
|
||||
{:name "border" :value "no"}
|
||||
{:name "background" :value "none"}]))
|
||||
(t/is (= (ctv/reorder-by-moving-to-position props 0 4) [{:name "color" :value "blue"}
|
||||
{:name "shadow" :value "yes"}
|
||||
{:name "background" :value "none"}
|
||||
{:name "border" :value "no"}]))
|
||||
(t/is (= (ctv/reorder-by-moving-to-position props 3 0) [{:name "background" :value "none"}
|
||||
{:name "border" :value "no"}
|
||||
{:name "color" :value "blue"}
|
||||
{:name "shadow" :value "yes"}]))
|
||||
(t/is (= (ctv/reorder-by-moving-to-position props 3 2) [{:name "border" :value "no"}
|
||||
{:name "color" :value "blue"}
|
||||
{:name "background" :value "none"}
|
||||
{:name "shadow" :value "yes"}]))
|
||||
(t/is (= (ctv/reorder-by-moving-to-position props 0 5) [{:name "color" :value "blue"}
|
||||
{:name "shadow" :value "yes"}
|
||||
{:name "background" :value "none"}
|
||||
{:name "border" :value "no"}]))
|
||||
(t/is (= (ctv/reorder-by-moving-to-position props 3 -1) [{:name "background" :value "none"}
|
||||
{:name "border" :value "no"}
|
||||
{:name "color" :value "blue"}
|
||||
{:name "shadow" :value "yes"}]))
|
||||
(t/is (= (ctv/reorder-by-moving-to-position props 5 -1) [{:name "background" :value "none"}
|
||||
{:name "border" :value "no"}
|
||||
{:name "color" :value "blue"}
|
||||
{:name "shadow" :value "yes"}]))
|
||||
(t/is (= (ctv/reorder-by-moving-to-position props -1 5) [{:name "color" :value "blue"}
|
||||
{:name "shadow" :value "yes"}
|
||||
{:name "background" :value "none"}
|
||||
{:name "border" :value "no"}])))))
|
||||
|
||||
@ -271,7 +271,7 @@ test("Bug 9066 - Problem with grid layout", async ({ page }) => {
|
||||
await workspacePage.clickToggableLayer("Group");
|
||||
await page.getByText("A", { exact: true }).click();
|
||||
|
||||
await workspacePage.rightSidebar.getByTestId("swap-component-btn").click();
|
||||
await workspacePage.rightSidebar.getByTestId("component-pill-button").click();
|
||||
|
||||
await page.getByTitle("C", { exact: true }).click();
|
||||
|
||||
|
||||
@ -262,6 +262,28 @@
|
||||
(dch/commit-changes changes)
|
||||
(dwu/commit-undo-transaction undo-id))))))
|
||||
|
||||
(defn reorder-variant-poperties
|
||||
"Reorder properties by moving a property from some position to some space between positions"
|
||||
[variant-id from-pos to-space-between-pos]
|
||||
(ptk/reify ::reorder-variant-properties
|
||||
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))
|
||||
|
||||
changes (-> (pcb/empty-changes it page-id)
|
||||
(pcb/with-library-data data)
|
||||
(pcb/with-objects objects)
|
||||
(clvp/generate-reorder-variant-poperties variant-id from-pos to-space-between-pos))
|
||||
|
||||
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]
|
||||
|
||||
@ -65,10 +65,10 @@
|
||||
|
||||
(def sortable-ctx (mf/create-context nil))
|
||||
|
||||
(mf/defc sortable-container
|
||||
[{:keys [children] :as props}]
|
||||
(mf/defc sortable-container*
|
||||
[{:keys [children]}]
|
||||
(let [global-drag-end (mf/use-memo #(rx/subject))]
|
||||
[:& (mf/provider sortable-ctx) {:value global-drag-end}
|
||||
[:> (mf/provider sortable-ctx) {:value global-drag-end}
|
||||
children]))
|
||||
|
||||
|
||||
|
||||
@ -356,7 +356,7 @@
|
||||
:icon i/add}]]]
|
||||
|
||||
[:div {:class (stl/css :gradient-stops-list)}
|
||||
[:& h/sortable-container {}
|
||||
[:> h/sortable-container* {}
|
||||
(for [[index stop] (d/enumerate stops)]
|
||||
[:> stop-input-row*
|
||||
{:key index
|
||||
|
||||
@ -72,7 +72,7 @@
|
||||
highlighted (hooks/use-equal-memo highlighted)
|
||||
root (get objects uuid/zero)]
|
||||
[:div {:class (stl/css :element-list) :data-testid "layer-item"}
|
||||
[:& hooks/sortable-container {}
|
||||
[:> hooks/sortable-container* {}
|
||||
(for [[index id] (reverse (d/enumerate (:shapes root)))]
|
||||
(when-let [obj (get objects id)]
|
||||
(if (cfh/frame-shape? obj)
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
[app.main.ui.workspace.sidebar.options.drawing :as drawing]
|
||||
[app.main.ui.workspace.sidebar.options.menus.align :refer [align-options*]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.bool :refer [bool-options*]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.component :refer [component-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.component :refer [component-menu*]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell]
|
||||
[app.main.ui.workspace.sidebar.options.menus.interactions :refer [interactions-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.layout-container :as layout-container]
|
||||
@ -89,7 +89,7 @@
|
||||
{::mf/private true}
|
||||
[{:keys [panel]}]
|
||||
(when (= (:type panel) :component-swap)
|
||||
[:& component-menu {:shapes (:shapes panel) :swap-opened? true}]))
|
||||
[:> component-menu* {:shapes (:shapes panel) :is-swap-opened true}]))
|
||||
|
||||
(mf/defc design-menu*
|
||||
{::mf/private true}
|
||||
|
||||
@ -29,10 +29,12 @@
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.dropdown :refer [dropdown]]
|
||||
[app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]]
|
||||
[app.main.ui.components.reorder-handler :refer [reorder-handler*]]
|
||||
[app.main.ui.components.search-bar :refer [search-bar*]]
|
||||
[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.button :refer [button*]]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.controls.combobox :refer [combobox*]]
|
||||
[app.main.ui.ds.controls.select :refer [select*]]
|
||||
@ -149,12 +151,11 @@
|
||||
(dw/set-annotations-id-for-create nil))
|
||||
(dw/update-component-annotation component-id nil)
|
||||
(rerender-fn)))]
|
||||
(st/emit! (modal/show
|
||||
{:type :confirm
|
||||
:title (tr "modals.delete-component-annotation.title")
|
||||
:message (tr "modals.delete-component-annotation.message")
|
||||
:accept-label (tr "ds.confirm-ok")
|
||||
:on-accept on-accept})))))]
|
||||
(st/emit! (modal/show {:type :confirm
|
||||
:title (tr "modals.delete-component-annotation.title")
|
||||
:message (tr "modals.delete-component-annotation.message")
|
||||
:accept-label (tr "ds.confirm-ok")
|
||||
:on-accept on-accept})))))]
|
||||
|
||||
(mf/with-effect [shape-id state create-id creating?]
|
||||
(when-let [textarea (mf/ref-val textarea-ref)]
|
||||
@ -171,31 +172,28 @@
|
||||
(st/emit! (dw/set-annotations-id-for-create nil)))))
|
||||
|
||||
(when (or creating? annotation)
|
||||
[:div {:class (stl/css-case
|
||||
:component-annotation true
|
||||
:editing editing?
|
||||
:creating creating?)}
|
||||
[:div {:class (stl/css-case
|
||||
:annotation-title true
|
||||
:expandeable (not (or editing? creating?))
|
||||
:expanded expanded?)
|
||||
[:div {:class (stl/css-case :annotation true
|
||||
:editing editing?
|
||||
:creating creating?)}
|
||||
[:div {:class (stl/css-case :annotation-title true
|
||||
:expandeable (not (or editing? creating?))
|
||||
:expanded expanded?)
|
||||
:on-click on-toggle-expand}
|
||||
|
||||
(if (or editing? creating?)
|
||||
[:span {:class (stl/css :annotation-text)}
|
||||
[:span {:class (stl/css :annotation-title-name)}
|
||||
(if editing?
|
||||
(tr "workspace.options.component.edit-annotation")
|
||||
(tr "workspace.options.component.create-annotation"))]
|
||||
|
||||
[:*
|
||||
[:span {:class (stl/css-case
|
||||
:icon-arrow true
|
||||
:expanded expanded?)}
|
||||
deprecated-icon/arrow]
|
||||
[:span {:class (stl/css :annotation-text)}
|
||||
[:> icon* {:icon-id (if expanded? i/arrow-down i/arrow-right)
|
||||
:class (stl/css :annotation-title-icon-arrow)
|
||||
:size "s"}]
|
||||
[:span {:class (stl/css :annotation-title-name)}
|
||||
(tr "workspace.options.component.annotation")]])
|
||||
|
||||
[:div {:class (stl/css :icons-wrapper)}
|
||||
[:div {:class (stl/css :annotation-title-actions)}
|
||||
(when (and ^boolean main-instance?
|
||||
^boolean expanded?)
|
||||
(if (or ^boolean editing?
|
||||
@ -205,40 +203,41 @@
|
||||
(tr "labels.create")
|
||||
(tr "labels.save"))
|
||||
:on-click on-save
|
||||
:class (stl/css-case
|
||||
:icon true
|
||||
:icon-tick true
|
||||
:invalid invalid-text?)}
|
||||
deprecated-icon/tick]
|
||||
[:div {:class (stl/css :icon :icon-cross)
|
||||
:class (stl/css :annotation-title-icon-action)}
|
||||
[:> icon* {:icon-id i/tick
|
||||
:class (stl/css-case :annotation-title-icon-ok true
|
||||
:disabled invalid-text?)}]]
|
||||
[:div {:class (stl/css :annotation-title-icon-action)
|
||||
:title (tr "labels.discard")
|
||||
:on-click on-discard}
|
||||
deprecated-icon/close]]
|
||||
[:> icon* {:icon-id i/close
|
||||
:class (stl/css :annotation-title-icon-nok)}]]]
|
||||
|
||||
[:*
|
||||
[:div {:class (stl/css :icon :icon-edit)
|
||||
[:div {:class (stl/css :annotation-title-icon-action)
|
||||
:title (tr "labels.edit")
|
||||
:on-click on-edit}
|
||||
deprecated-icon/curve]
|
||||
[:div {:class (stl/css :icon :icon-trash)
|
||||
[:> icon* {:icon-id i/curve
|
||||
:class (stl/css :annotation-title-icon-ok)}]]
|
||||
[:div {:class (stl/css :annotation-title-icon-action)
|
||||
:title (tr "labels.delete")
|
||||
:on-click on-delete-annotation}
|
||||
deprecated-icon/delete]]))]]
|
||||
[:> icon* {:icon-id i/delete
|
||||
:class (stl/css :annotation-title-icon-nok)}]]]))]]
|
||||
|
||||
[:div {:class (stl/css-case :hidden (not expanded?))}
|
||||
[:div {:class (stl/css :grow-wrap)}
|
||||
[:div {:class (stl/css :texarea-copy)}]
|
||||
[:textarea
|
||||
{:ref textarea-ref
|
||||
:id "annotation-textarea"
|
||||
:data-debug annotation
|
||||
:auto-focus (or editing? creating?)
|
||||
:maxLength 300
|
||||
:on-input adjust-textarea-size
|
||||
:default-value annotation
|
||||
:read-only (not (or creating? editing?))}]]
|
||||
[:div {:class (stl/css-case :annotation-body-hidden (not expanded?))}
|
||||
[:div {:class (stl/css :annotation-body)}
|
||||
[:textarea {:ref textarea-ref
|
||||
:id "annotation-textarea"
|
||||
:class (stl/css :annotation-textarea)
|
||||
:data-debug annotation
|
||||
:auto-focus (or editing? creating?)
|
||||
:max-length 300
|
||||
:on-input adjust-textarea-size
|
||||
:default-value annotation
|
||||
:read-only (not (or creating? editing?))}]]
|
||||
(when (or editing? creating?)
|
||||
[:div {:class (stl/css :counter)} (str size "/300")])]])))
|
||||
[:div {:class (stl/css :annotation-counter)} (str size "/300")])]])))
|
||||
|
||||
(defn- get-variant-malformed-warning-message
|
||||
"Receive a list of booleans, one for each selected variant, indicating if that variant
|
||||
@ -300,7 +299,47 @@
|
||||
(mapv (fn [val] {:id val
|
||||
:label (if (str/blank? val) (str "(" (tr "labels.empty") ")") val)}))))
|
||||
|
||||
(mf/defc component-variant-main-instance*
|
||||
(mf/defc component-variant-property*
|
||||
[{:keys [pos prop options on-prop-name-blur on-prop-value-change on-reorder]}]
|
||||
(let [on-drop
|
||||
(mf/use-fn
|
||||
(fn [relative-pos data]
|
||||
(let [from-pos (:from-pos data)
|
||||
to-space-between-pos (if (= relative-pos :bot) (inc pos) pos)]
|
||||
(on-reorder from-pos to-space-between-pos))))
|
||||
|
||||
[dprops dref]
|
||||
(h/use-sortable
|
||||
:data-type "penpot/variant-property"
|
||||
:on-drop on-drop
|
||||
:draggable? true
|
||||
:data {:from-pos pos})]
|
||||
|
||||
[:div {:class (stl/css-case :variant-property true
|
||||
:dnd-over-top (= (:over dprops) :top)
|
||||
:dnd-over-bot (= (:over dprops) :bot))}
|
||||
(when (some? on-reorder)
|
||||
[:> reorder-handler* {:ref dref}])
|
||||
|
||||
[:div {:class (stl/css :variant-property-container)}
|
||||
[:div {:class (stl/css :variant-property-name-wrapper)}
|
||||
[:> input-with-meta* {:value (:name prop)
|
||||
:is-editing (:editing? (meta prop))
|
||||
:max-length ctv/property-max-length
|
||||
:data-position pos
|
||||
:on-blur on-prop-name-blur}]]
|
||||
|
||||
[:div {:class (stl/css :variant-property-value-wrapper)}
|
||||
(let [mixed-value? (= (:value prop) false)]
|
||||
[:> combobox* {:id (str "variant-prop-" pos)
|
||||
:placeholder (if mixed-value? (tr "settings.multiple") "--")
|
||||
:default-selected (if mixed-value? "" (:value prop))
|
||||
:options options
|
||||
:empty-to-end true
|
||||
:max-length ctv/property-max-length
|
||||
:on-change on-prop-value-change}])]]]))
|
||||
|
||||
(mf/defc component-variant*
|
||||
[{:keys [components shapes data]}]
|
||||
(let [component (first components)
|
||||
|
||||
@ -356,33 +395,28 @@
|
||||
int)]
|
||||
(when (seq value)
|
||||
(st/emit!
|
||||
(dwv/update-property-name variant-id pos value {:trigger "workspace:design-tab-variant"}))))))]
|
||||
(dwv/update-property-name variant-id pos value {:trigger "workspace:design-tab-variant"}))))))
|
||||
|
||||
reorder-properties
|
||||
(mf/use-fn
|
||||
(mf/deps variant-id)
|
||||
(fn [from-pos to-space-between-pos]
|
||||
(st/emit! (dwv/reorder-variant-poperties variant-id from-pos to-space-between-pos))))]
|
||||
|
||||
[:*
|
||||
[:div {:class (stl/css :variant-property-list)}
|
||||
(for [[pos prop] (map-indexed vector properties)]
|
||||
[:div {:key (str variant-id "-" pos)
|
||||
:class (stl/css :variant-property-container)}
|
||||
|
||||
[:div {:class (stl/css :variant-property-name-wrapper)}
|
||||
[:> input-with-meta* {:value (:name prop)
|
||||
:is-editing (:editing? (meta prop))
|
||||
:max-length ctv/property-max-length
|
||||
:data-position pos
|
||||
:on-blur update-property-name}]]
|
||||
|
||||
[:div {:class (stl/css :variant-property-value-wrapper)}
|
||||
(let [mixed-value? (= (:value prop) false)]
|
||||
[:> combobox* {:id (str "variant-prop-" variant-id "-" pos)
|
||||
:placeholder (if mixed-value? (tr "settings.multiple") "--")
|
||||
:default-selected (if mixed-value? "" (:value prop))
|
||||
:options (get-options (:name prop))
|
||||
:empty-to-end true
|
||||
:max-length ctv/property-max-length
|
||||
:on-change (partial update-property-value pos)}])]])]
|
||||
[:> h/sortable-container* {}
|
||||
[:div {:class (stl/css :variant-property-list)}
|
||||
(for [[pos prop] (map-indexed vector properties)]
|
||||
[:> component-variant-property* {:key (str variant-id "-" pos)
|
||||
:pos pos
|
||||
:prop prop
|
||||
:options (get-options (:name prop))
|
||||
:on-prop-name-blur update-property-name
|
||||
:on-prop-value-change (partial update-property-value pos)
|
||||
:on-reorder reorder-properties}])]]
|
||||
|
||||
(if malformed-msg
|
||||
[:div {:class (stl/css :variant-warning-wrapper)}
|
||||
[:div {:class (stl/css :variant-warning)}
|
||||
[:> icon* {:icon-id i/msg-neutral
|
||||
:class (stl/css :variant-warning-darken)}]
|
||||
[:div {:class (stl/css :variant-warning-highlight)}
|
||||
@ -391,7 +425,7 @@
|
||||
(tr "workspace.options.component.variant.malformed.structure.example")]]
|
||||
|
||||
(when duplicated-msg
|
||||
[:div {:class (stl/css :variant-warning-wrapper)}
|
||||
[:div {:class (stl/css :variant-warning)}
|
||||
[:> icon* {:icon-id i/msg-neutral
|
||||
:class (stl/css :variant-warning-darken)}]
|
||||
[:div {:class (stl/css :variant-warning-highlight)}
|
||||
@ -471,7 +505,7 @@
|
||||
|
||||
[:*
|
||||
[:div {:class (stl/css :variant-property-list)}
|
||||
(for [[pos prop] (map vector (range) props-first)]
|
||||
(for [[pos prop] (map-indexed vector props-first)]
|
||||
(let [mixed-value? (not-every? #(= (:value prop) (:value (nth % pos))) properties)
|
||||
options (cond-> (get-options (:name prop))
|
||||
mixed-value?
|
||||
@ -492,7 +526,7 @@
|
||||
:key (str (:value prop) "-" key)}]]]))]
|
||||
|
||||
(if (seq malformed-comps)
|
||||
[:div {:class (stl/css :variant-warning-wrapper)}
|
||||
[:div {:class (stl/css :variant-warning)}
|
||||
[:> icon* {:icon-id i/msg-neutral
|
||||
:class (stl/css :variant-warning-darken)}]
|
||||
[:div {:class (stl/css :variant-warning-highlight)}
|
||||
@ -502,7 +536,7 @@
|
||||
(tr "workspace.options.component.variant.malformed.locate")]]
|
||||
|
||||
(when (seq duplicated-comps)
|
||||
[:div {:class (stl/css :variant-warning-wrapper)}
|
||||
[:div {:class (stl/css :variant-warning)}
|
||||
[:> icon* {:icon-id i/msg-neutral
|
||||
:class (stl/css :variant-warning-darken)}]
|
||||
[:div {:class (stl/css :variant-warning-highlight)}
|
||||
@ -523,43 +557,40 @@
|
||||
|
||||
item-ref (mf/use-ref)
|
||||
visible? (h/use-visible item-ref :once? true)]
|
||||
[:div {:ref item-ref
|
||||
:class (stl/css-case :component-item (not listing-thumbs)
|
||||
:grid-cell listing-thumbs
|
||||
:selected (= (:id item) component-id)
|
||||
:disabled loop)
|
||||
:key (str "swap-item-" (:id item))
|
||||
:on-click on-select}
|
||||
[:button {:ref item-ref
|
||||
:key (str "swap-item-" (:id item))
|
||||
:class (stl/css-case :swap-item-list (not listing-thumbs)
|
||||
:swap-item-grid listing-thumbs
|
||||
:selected (= (:id item) component-id))
|
||||
:on-click on-select
|
||||
:disabled loop}
|
||||
(when visible?
|
||||
[:> cmm/component-item-thumbnail*
|
||||
{:file-id (:file-id item)
|
||||
:class (stl/css :component-img)
|
||||
:root-shape root-shape
|
||||
:component item
|
||||
:container container}])
|
||||
[:> cmm/component-item-thumbnail* {:file-id (:file-id item)
|
||||
:class (stl/css :swap-item-thumbnail)
|
||||
:root-shape root-shape
|
||||
:component item
|
||||
:container container}])
|
||||
[:span {:title (if is-search (:full-name item) (:name item))
|
||||
:class (stl/css-case :component-name true
|
||||
:selected (= (:id item) component-id))}
|
||||
:class (stl/css :swap-item-name)}
|
||||
(if is-search (:full-name item) (:name item))]
|
||||
(when (ctk/is-variant? item)
|
||||
[:span {:class (stl/css-case :variant-mark-cell listing-thumbs
|
||||
:variant-icon true)
|
||||
[:span {:class (stl/css :swap-item-variant-icon)
|
||||
:title (tr "workspace.assets.components.num-variants" num-variants)}
|
||||
[:> icon* {:icon-id i/variant
|
||||
:size "s"}]])]))
|
||||
|
||||
(mf/defc component-group-item*
|
||||
(mf/defc component-swap-group-title*
|
||||
[{:keys [item on-enter-group]}]
|
||||
(let [group-name (:name item)
|
||||
(let [group-name (:name item)
|
||||
on-group-click #(on-enter-group group-name)]
|
||||
[:div {:class (stl/css :component-group)
|
||||
[:div {:class (stl/css :swap-group)
|
||||
:on-click on-group-click
|
||||
:title group-name}
|
||||
|
||||
[:span {:class (stl/css :component-group-name)}
|
||||
[:span {:class (stl/css :swap-group-name)}
|
||||
(cpn/last-path group-name)]
|
||||
|
||||
[:> icon* {:class (stl/css :component-group-icon)
|
||||
[:> icon* {:class (stl/css :swap-group-icon)
|
||||
:variant "ghost"
|
||||
:icon-id i/arrow-right
|
||||
:size "s"}]]))
|
||||
@ -621,7 +652,7 @@
|
||||
|
||||
filters (deref filters*)
|
||||
|
||||
is-search? (not (str/blank? (:term filters)))
|
||||
search? (not (str/blank? (:term filters)))
|
||||
|
||||
current-library-id (if (contains? libraries (:file-id filters))
|
||||
(:file-id filters)
|
||||
@ -662,15 +693,15 @@
|
||||
(distinct)
|
||||
(filter #(= (cpn/butlast-path %) (:path filters))))
|
||||
|
||||
groups (when-not is-search?
|
||||
groups (when-not search?
|
||||
(->> (sort (sequence xform components))
|
||||
(map (fn [name] {:name name}))))
|
||||
|
||||
components (if is-search?
|
||||
components (if search?
|
||||
(filter #(str/includes? (str/lower (:full-name %)) (str/lower (:term filters))) components)
|
||||
(filter #(= (:path %) (:path filters)) components))
|
||||
|
||||
items (if (or is-search? (:listing-thumbs? filters))
|
||||
items (if (or search? (:listing-thumbs? filters))
|
||||
(sort-by :full-name components)
|
||||
(->> (concat groups components)
|
||||
(sort-by :name)))
|
||||
@ -686,7 +717,8 @@
|
||||
;; Get the ids of the components that are parents of the shapes, to avoid loops
|
||||
parent-components (mapcat find-parent-components shapes)
|
||||
|
||||
libraries-options (map (fn [library] {:value (:id library) :label (:name library)})
|
||||
libraries-options (map (fn [library] {:value (:id library)
|
||||
:label (:name library)})
|
||||
(vals libraries))
|
||||
|
||||
on-library-change
|
||||
@ -714,63 +746,60 @@
|
||||
(mf/use-fn
|
||||
(fn [style]
|
||||
(swap! filters* assoc :listing-thumbs? (= style "grid"))))
|
||||
filter-path-with-dots (->> (:path filters) (cpn/split-path) (cpn/join-path-with-dot))]
|
||||
|
||||
[:div {:class (stl/css :component-swap)}
|
||||
[:div {:class (stl/css :element-set-title)}
|
||||
filter-path-with-dots (->> (:path filters)
|
||||
(cpn/split-path)
|
||||
(cpn/join-path-with-dot))]
|
||||
|
||||
[:div {:class (stl/css :swap)}
|
||||
[:div {:class (stl/css :swap-title)}
|
||||
[:span (tr "workspace.options.component.swap")]]
|
||||
[:div {:class (stl/css :component-swap-content)}
|
||||
[:div {:class (stl/css :fields-wrapper)}
|
||||
[:div {:class (stl/css :search-field)}
|
||||
[:> search-bar* {:on-change on-search-term-change
|
||||
:on-clear on-search-clear-click
|
||||
:class (stl/css :search-wrapper)
|
||||
:id "swap-component-search-filter"
|
||||
:value (:term filters)
|
||||
:placeholder (str (tr "labels.search") " " (get-in libraries [current-library-id :name]))
|
||||
:icon-id i/search}]]
|
||||
|
||||
[:& select {:class (stl/css :select-library)
|
||||
:default-value current-library-id
|
||||
[:div {:class (stl/css :swap-content)}
|
||||
[:div {:class (stl/css :swap-filters)}
|
||||
[:> search-bar* {:id "swap-component-search-filter"
|
||||
:icon-id i/search
|
||||
:value (:term filters)
|
||||
:placeholder (str (tr "labels.search") " " (get-in libraries [current-library-id :name]))
|
||||
:on-change on-search-term-change
|
||||
:on-clear on-search-clear-click}]
|
||||
[:& select {:default-value current-library-id
|
||||
:options libraries-options
|
||||
:on-change on-library-change}]]
|
||||
|
||||
[:div {:class (stl/css :swap-wrapper)}
|
||||
[:div {:class (stl/css :library-name-wrapper)}
|
||||
[:div {:class (stl/css :library-name)} current-lib-name]
|
||||
[:div {:class (stl/css :swap-library)}
|
||||
[:div {:class (stl/css :swap-library-title)}
|
||||
[:div {:class (stl/css :swap-library-name)} current-lib-name]
|
||||
[:& radio-buttons {:selected (if (:listing-thumbs? filters) "grid" "list")
|
||||
:on-change toggle-list-style
|
||||
:name "swap-listing-style"}
|
||||
[:& radio-button {:icon deprecated-icon/view-as-list
|
||||
:value "list"
|
||||
:id "swap-opt-list"}]
|
||||
[:& radio-button {:icon deprecated-icon/flex-grid
|
||||
:value "grid"
|
||||
:id "swap-opt-grid"}]]]
|
||||
|
||||
[:div {:class (stl/css :listing-options-wrapper)}
|
||||
[:& radio-buttons {:class (stl/css :listing-options)
|
||||
:selected (if (:listing-thumbs? filters) "grid" "list")
|
||||
:on-change toggle-list-style
|
||||
:name "swap-listing-style"}
|
||||
[:& radio-button {:icon deprecated-icon/view-as-list
|
||||
:value "list"
|
||||
:id "swap-opt-list"}]
|
||||
[:& radio-button {:icon deprecated-icon/flex-grid
|
||||
:value "grid"
|
||||
:id "swap-opt-grid"}]]]]
|
||||
|
||||
(when-not (or is-search? (str/empty? (:path filters)))
|
||||
[:button {:class (stl/css :component-path)
|
||||
(when-not (or search? (str/empty? (:path filters)))
|
||||
[:button {:class (stl/css :swap-library-back)
|
||||
:on-click on-go-back
|
||||
:title filter-path-with-dots}
|
||||
[:> icon* {:icon-id i/arrow-left
|
||||
:size "s"}]
|
||||
[:span {:class (stl/css :path-name)}
|
||||
[:span {:class (stl/css :swap-library-back-name)}
|
||||
filter-path-with-dots]])
|
||||
|
||||
(when (empty? items)
|
||||
[:div {:class (stl/css :component-list-empty)}
|
||||
[:div {:class (stl/css :swap-library-empty)}
|
||||
(tr "workspace.options.component.swap.empty")]) ;;TODO review this empty space
|
||||
|
||||
(when (:listing-thumbs? filters)
|
||||
[:div {:class (stl/css :component-list)}
|
||||
[:div
|
||||
(for [item groups]
|
||||
[:> component-group-item* {:item item :on-enter-group on-enter-group}])])
|
||||
[:> component-swap-group-title* {:item item
|
||||
:on-enter-group on-enter-group}])])
|
||||
|
||||
[:div {:class (stl/css-case :component-grid (:listing-thumbs? filters)
|
||||
:component-list (not (:listing-thumbs? filters)))}
|
||||
[:div {:class (stl/css-case :swap-library-grid (:listing-thumbs? filters)
|
||||
:swap-library-list (not (:listing-thumbs? filters)))}
|
||||
;; FIXME: This could be in the thousands. We need to think about paginate this
|
||||
(for [item items]
|
||||
(if (:id item)
|
||||
@ -789,34 +818,78 @@
|
||||
:root-shape root-shape
|
||||
:container container
|
||||
:component-id component-id
|
||||
:is-search is-search?
|
||||
:is-search 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-swap-group-title* {:item item
|
||||
:key (:name item)
|
||||
:on-enter-group on-enter-group}]))]]]]))
|
||||
|
||||
(mf/defc component-ctx-menu*
|
||||
[{:keys [menu-entries on-close show main-instance]}]
|
||||
(let [do-action
|
||||
(mf/defc component-pill*
|
||||
[{:keys [icon text subtext menu-entries disabled on-click]}]
|
||||
(let [menu-open* (mf/use-state false)
|
||||
menu-open? (deref menu-open*)
|
||||
|
||||
menu-entries? (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))
|
||||
|
||||
do-action
|
||||
(fn [action event]
|
||||
(dom/stop-propagation event)
|
||||
(action)
|
||||
(on-close))]
|
||||
[:& dropdown {:show show :on-close on-close}
|
||||
[:ul {:class (stl/css-case :custom-select-dropdown true
|
||||
:not-main (not main-instance))}
|
||||
(for [{:keys [title action]} menu-entries]
|
||||
(when (some? title)
|
||||
[:li {:key title
|
||||
:class (stl/css :dropdown-element)
|
||||
:on-click (partial do-action action)}
|
||||
[:span {:class (stl/css :dropdown-label)} title]]))]]))
|
||||
(on-menu-close))]
|
||||
|
||||
(mf/defc component-menu
|
||||
{::mf/props :obj}
|
||||
[{:keys [shapes swap-opened?]}]
|
||||
[:div {:class (stl/css :pill)}
|
||||
[:button {:class (stl/css-case :pill-btn true
|
||||
:with-menu menu-entries?)
|
||||
:data-testid "component-pill-button"
|
||||
:on-click on-click
|
||||
:disabled disabled}
|
||||
|
||||
[:div {:class (stl/css :pill-btn-icon)}
|
||||
[:> icon* {:size "s"
|
||||
:icon-id icon}]]
|
||||
|
||||
[:div {:class (stl/css :pill-btn-name)}
|
||||
[:div {:class (stl/css :pill-btn-text)}
|
||||
text]
|
||||
(when subtext
|
||||
[:div {:class (stl/css :pill-btn-subtext)}
|
||||
subtext])]]
|
||||
|
||||
(when menu-entries?
|
||||
[:div {:class (stl/css :pill-actions)}
|
||||
[:button {:class (stl/css-case :pill-actions-btn true
|
||||
:selected menu-open?)
|
||||
:on-click on-menu-click}
|
||||
[:> icon* {:icon-id i/menu}]]
|
||||
|
||||
[:& dropdown {:show menu-open?
|
||||
:on-close on-menu-close}
|
||||
[:ul {:class (stl/css-case :pill-actions-dropdown true
|
||||
:extended subtext)}
|
||||
(for [{:keys [title action]} menu-entries]
|
||||
(when (some? title)
|
||||
[:li {:key title
|
||||
:class (stl/css :pill-actions-dropdown-item)
|
||||
:on-click (partial do-action action)}
|
||||
[:span title]]))]]])]))
|
||||
|
||||
(mf/defc component-menu*
|
||||
[{:keys [shapes is-swap-opened]}]
|
||||
(let [current-file-id (mf/use-ctx ctx/current-file-id)
|
||||
|
||||
libraries (mf/deref refs/files)
|
||||
@ -827,7 +900,6 @@
|
||||
: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)
|
||||
@ -855,18 +927,8 @@
|
||||
main-instance? (ctk/main-instance? shape)
|
||||
|
||||
toggle-content
|
||||
(mf/use-fn #(swap! state* update :show-content not))
|
||||
|
||||
on-menu-click
|
||||
(mf/use-fn
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(swap! state* update :menu-open not)))
|
||||
|
||||
on-menu-close
|
||||
(mf/use-fn
|
||||
#(swap! state* assoc :menu-open false))
|
||||
#(swap! state* update :show-content not))
|
||||
|
||||
on-click-variant-title-help
|
||||
(mf/use-fn
|
||||
@ -923,14 +985,13 @@
|
||||
(swap! state* update :render inc)))
|
||||
|
||||
menu-entries (cmm/generate-components-menu-entries shapes {:for-design-tab? true})
|
||||
show-menu? (seq menu-entries)
|
||||
path (->> component (:path) (cpn/split-path) (cpn/join-path-with-dot))]
|
||||
|
||||
(when (seq shapes)
|
||||
[:div {:class (stl/css :element-set)}
|
||||
[:div {:class (stl/css :element-title)}
|
||||
(if swap-opened?
|
||||
[:button {:class (stl/css :title-back)
|
||||
[:div {:class (stl/css :component-section)}
|
||||
[:div {:class (stl/css :component-title)}
|
||||
(if is-swap-opened
|
||||
[:button {:class (stl/css :component-title-swap)
|
||||
:on-click on-component-back}
|
||||
[:> icon* {:icon-id i/arrow-left
|
||||
:size "s"}]
|
||||
@ -941,9 +1002,9 @@
|
||||
:collapsed (not open?)
|
||||
:on-collapsed toggle-content
|
||||
:title (tr "workspace.options.component")
|
||||
:class (stl/css :title-spacing-component)
|
||||
:title-class (stl/css :title-bar-variant)}
|
||||
[:span {:class (stl/css :copy-text)}
|
||||
:class (stl/css :component-title-bar)
|
||||
:title-class (stl/css :component-title-bar-title)}
|
||||
[:span {:class (stl/css :component-title-bar-type)}
|
||||
(if main-instance?
|
||||
(if is-variant?
|
||||
(tr "labels.variant")
|
||||
@ -963,82 +1024,56 @@
|
||||
:icon i/variant}])])]
|
||||
|
||||
(when open?
|
||||
[:div {:class (stl/css :element-content)}
|
||||
[:div {:class (stl/css :component-line)}
|
||||
|
||||
[:div {:class (stl/css :component-wrapper)}
|
||||
|
||||
[:button {:class (stl/css-case :component-name-wrapper true
|
||||
:without-menu (not show-menu?))
|
||||
:data-testid "swap-component-btn"
|
||||
:on-click open-component-panel
|
||||
:disabled (or swap-opened? (not can-swap?))}
|
||||
|
||||
[:div {:class (stl/css :component-icon)}
|
||||
[:> icon* {:size "s"
|
||||
:icon-id (if main-instance?
|
||||
(if is-variant? i/variant i/component)
|
||||
i/component-copy)}]]
|
||||
|
||||
[: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")
|
||||
(cpn/last-path shape-name))]]
|
||||
|
||||
(when (and can-swap? (or (not multi) same-variant?))
|
||||
[:div {:class (stl/css :component-parent-name)}
|
||||
(if (:deleted component)
|
||||
(tr "workspace.options.component.unlinked")
|
||||
(cpn/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 i/menu}]]
|
||||
|
||||
[:> component-ctx-menu* {:show menu-open?
|
||||
:on-close on-menu-close
|
||||
:menu-entries menu-entries
|
||||
:main-instance main-instance?}]])]
|
||||
|
||||
[:div {:class (stl/css :component-content)}
|
||||
[:div {:class (stl/css :component-pill)}
|
||||
[:> component-pill* {:icon (if main-instance?
|
||||
(if is-variant? i/variant i/component)
|
||||
i/component-copy)
|
||||
:text (if (and multi (not same-variant?))
|
||||
(tr "settings.multiple")
|
||||
(cpn/last-path shape-name))
|
||||
:subtext (when (and can-swap? (or (not multi) same-variant?))
|
||||
(if (:deleted component)
|
||||
(tr "workspace.options.component.unlinked")
|
||||
(cpn/merge-path-item-with-dot path (:name component))))
|
||||
:on-click open-component-panel
|
||||
:disabled (or is-swap-opened (not can-swap?))
|
||||
:menu-entries menu-entries}]
|
||||
(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 i/add}])]
|
||||
|
||||
(when swap-opened?
|
||||
(when is-swap-opened
|
||||
[:> component-swap* {:shapes copies}])
|
||||
|
||||
(when (and is-variant?
|
||||
(not main-instance?)
|
||||
(not (:deleted component))
|
||||
(not swap-opened?)
|
||||
(not is-swap-opened)
|
||||
(or (not multi) same-variant?))
|
||||
[:> component-variant-copy* {:current-file-id current-file-id
|
||||
:components components
|
||||
:shapes shapes
|
||||
:component-file-data data}])
|
||||
|
||||
(when (and is-variant? main-instance? same-variant? (not swap-opened?))
|
||||
[:> component-variant-main-instance* {:components components
|
||||
:shapes shapes
|
||||
:data data}])
|
||||
(when (and is-variant? main-instance? same-variant? (not is-swap-opened))
|
||||
[:> component-variant* {:components components
|
||||
:shapes shapes
|
||||
:data data}])
|
||||
|
||||
(when (and (not swap-opened?) (not multi))
|
||||
(when (and (not is-swap-opened) (not multi))
|
||||
[:> component-annotation* {:id id
|
||||
:shape shape
|
||||
:component component
|
||||
:rerender-fn rerender-fn}])
|
||||
|
||||
(when (and multi all-main? (not any-variant?))
|
||||
[:button {:class (stl/css :combine-variant-button)
|
||||
:on-click on-combine-as-variants}
|
||||
[:span (tr "workspace.shape.menu.combine-as-variants")]])
|
||||
[:> button* {:variant "secondary"
|
||||
:class (stl/css :component-combine)
|
||||
:on-click on-combine-as-variants}
|
||||
(tr "workspace.shape.menu.combine-as-variants")])
|
||||
|
||||
(when (dbg/enabled? :display-touched)
|
||||
[:div ":touched " (str (:touched shape))])])])))
|
||||
@ -1050,7 +1085,50 @@
|
||||
(into (remove empty?) v)
|
||||
(into (filter empty?) v)))
|
||||
|
||||
(mf/defc variant-menu*
|
||||
(mf/defc component-variant-main-property*
|
||||
[{:keys [pos property is-remove-disabled on-remove on-blur on-reorder]}]
|
||||
(let [values (->> (:value property)
|
||||
(move-empty-items-to-end)
|
||||
(replace {"" "--"})
|
||||
(str/join ", "))
|
||||
|
||||
on-drop
|
||||
(mf/use-fn
|
||||
(fn [relative-pos data]
|
||||
(let [from-pos (:from-pos data)
|
||||
to-space-between-pos (if (= relative-pos :bot) (inc pos) pos)]
|
||||
(on-reorder from-pos to-space-between-pos))))
|
||||
|
||||
[dprops dref]
|
||||
(h/use-sortable
|
||||
:data-type "penpot/variant-main-property"
|
||||
:on-drop on-drop
|
||||
:draggable? true
|
||||
:data {:from-pos pos})]
|
||||
|
||||
[:div {:class (stl/css-case :variant-property true
|
||||
:dnd-over-top (= (:over dprops) :top)
|
||||
:dnd-over-bot (= (:over dprops) :bot))}
|
||||
(when (some? on-reorder)
|
||||
[:> reorder-handler* {:ref dref}])
|
||||
|
||||
[:div {:class (stl/css :variant-property-row)}
|
||||
[:> input-with-meta* {:value (:name property)
|
||||
:data-position pos
|
||||
:meta values
|
||||
:is-editing (:editing? (meta property))
|
||||
:max-length ctv/property-max-length
|
||||
:on-blur on-blur}]
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:icon i/remove
|
||||
:data-position pos
|
||||
:aria-label (if is-remove-disabled
|
||||
(tr "workspace.shape.menu.remove-variant-property.last-property")
|
||||
(tr "workspace.shape.menu.remove-variant-property"))
|
||||
:on-click on-remove
|
||||
:disabled is-remove-disabled}]]]))
|
||||
|
||||
(mf/defc component-variant-main*
|
||||
[{:keys [shapes]}]
|
||||
(let [multi? (> (count shapes) 1)
|
||||
|
||||
@ -1080,13 +1158,11 @@
|
||||
|
||||
properties (mf/with-memo [data objects variant-id]
|
||||
(cfv/extract-properties-values data objects (:id shape)))
|
||||
single-property? (= (count properties) 1)
|
||||
|
||||
open* (mf/use-state true)
|
||||
open? (deref open*)
|
||||
|
||||
menu-open* (mf/use-state false)
|
||||
menu-open? (deref menu-open*)
|
||||
|
||||
show-in-assets-panel
|
||||
(mf/use-fn
|
||||
(mf/deps variants)
|
||||
@ -1119,19 +1195,6 @@
|
||||
(mf/use-fn
|
||||
#(swap! open* not))
|
||||
|
||||
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))
|
||||
|
||||
on-click-variant-title-help
|
||||
(mf/use-fn
|
||||
(fn []
|
||||
@ -1162,6 +1225,12 @@
|
||||
(ev/event {::ev/name "variant-remove-property" ::ev/origin "workspace:button-design-tab"})
|
||||
(dwv/remove-property variant-id pos))))))
|
||||
|
||||
reorder-properties
|
||||
(mf/use-fn
|
||||
(mf/deps variant-id)
|
||||
(fn [from-pos to-space-between-pos]
|
||||
(st/emit! (dwv/reorder-variant-poperties variant-id from-pos to-space-between-pos))))
|
||||
|
||||
select-shapes-with-malformed
|
||||
(mf/use-fn
|
||||
(mf/deps malformed-ids)
|
||||
@ -1173,95 +1242,56 @@
|
||||
#(st/emit! (dw/select-shapes (into (d/ordered-set) duplicated-ids))))]
|
||||
|
||||
(when (seq shapes)
|
||||
[:div {:class (stl/css :element-set)}
|
||||
[:div {:class (stl/css :element-title)}
|
||||
[:div {:class (stl/css :component-section)}
|
||||
[:div {:class (stl/css :component-title)}
|
||||
|
||||
[:*
|
||||
[:> title-bar* {:collapsable true
|
||||
:collapsed (not open?)
|
||||
:on-collapsed toggle-content
|
||||
:title (tr "workspace.options.component")
|
||||
:class (stl/css :title-spacing-component)
|
||||
:title-class (stl/css :title-bar-variant)}
|
||||
[:span {:class (stl/css :copy-text)}
|
||||
:class (stl/css :component-title-bar)
|
||||
:title-class (stl/css :component-title-bar-title)}
|
||||
[:span {:class (stl/css :component-title-bar-type)}
|
||||
(tr "workspace.options.component.main")]]
|
||||
|
||||
[: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 i/help}]
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-label (tr "workspace.shape.menu.add-variant")
|
||||
:on-click (partial create-variant "workspace:button-design-tab-component")
|
||||
:icon i/variant}]]]]
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-label (tr "workspace.options.component.variants-help-modal.title")
|
||||
:on-click on-click-variant-title-help
|
||||
:icon i/help}]
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-label (tr "workspace.shape.menu.add-variant")
|
||||
:on-click (partial create-variant "workspace:button-design-tab-component")
|
||||
:icon i/variant}]]]
|
||||
|
||||
(when open?
|
||||
[:div {:class (stl/css :element-content)}
|
||||
[:div {:class (stl/css :component-line)}
|
||||
|
||||
[:div {:class (stl/css :component-wrapper)}
|
||||
|
||||
[:button {:class (stl/css :component-name-wrapper)
|
||||
:disabled true}
|
||||
|
||||
[:div {:class (stl/css :component-icon)}
|
||||
[:> icon* {:size "s"
|
||||
:icon-id i/component}]]
|
||||
|
||||
[: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")
|
||||
(cpn/last-path shape-name))]]]]
|
||||
|
||||
(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 i/menu}]]
|
||||
|
||||
[:> component-ctx-menu* {:show menu-open?
|
||||
:on-close on-menu-close
|
||||
:menu-entries menu-entries
|
||||
:main-instance true}]])]
|
||||
|
||||
[:div {:class (stl/css :component-content)}
|
||||
[:div {:class (stl/css :component-pill)}
|
||||
[:> component-pill* {:icon i/component
|
||||
:text (if multi?
|
||||
(tr "settings.multiple")
|
||||
(cpn/last-path shape-name))
|
||||
:disabled true
|
||||
:menu-entries menu-entries}]
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-label (tr "workspace.shape.menu.add-variant-property")
|
||||
:on-click (partial add-new-property "workspace:button-design-tab-component")
|
||||
:icon i/add}]]
|
||||
|
||||
(when-not multi?
|
||||
[:div {:class (stl/css :variant-property-list)}
|
||||
(for [[pos property] (map-indexed vector properties)]
|
||||
(let [last-prop? (<= (count properties) 1)
|
||||
values (->> (:value property)
|
||||
(move-empty-items-to-end)
|
||||
(replace {"" "--"})
|
||||
(str/join ", "))
|
||||
is-editing (:editing? (meta property))]
|
||||
[:div {:key (str (:id shape) pos)
|
||||
:class (stl/css :variant-property-row)}
|
||||
[:> input-with-meta* {:value (:name property)
|
||||
:meta values
|
||||
:is-editing is-editing
|
||||
:max-length ctv/property-max-length
|
||||
:data-position pos
|
||||
:on-blur update-property-name}]
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-label (if last-prop?
|
||||
(tr "workspace.shape.menu.remove-variant-property.last-property")
|
||||
(tr "workspace.shape.menu.remove-variant-property"))
|
||||
:on-click remove-property
|
||||
:data-position pos
|
||||
:icon i/remove
|
||||
:disabled last-prop?}]]))])
|
||||
[:> h/sortable-container* {}
|
||||
[:div {:class (stl/css :variant-property-list)}
|
||||
(for [[pos property] (map-indexed vector properties)]
|
||||
[:> component-variant-main-property* {:key (str (:id shape) pos)
|
||||
:pos pos
|
||||
:property property
|
||||
:is-remove-disabled single-property?
|
||||
:on-remove remove-property
|
||||
:on-blur update-property-name
|
||||
:on-reorder reorder-properties}])]])
|
||||
|
||||
(if malformed?
|
||||
[:div {:class (stl/css :variant-warning-wrapper)}
|
||||
[:div {:class (stl/css :variant-warning)}
|
||||
[:> icon* {:icon-id i/msg-neutral
|
||||
:class (stl/css :variant-warning-darken)}]
|
||||
[:div {:class (stl/css :variant-warning-highlight)}
|
||||
@ -1271,7 +1301,7 @@
|
||||
(tr "workspace.options.component.variant.malformed.group.locate")]]
|
||||
|
||||
(when duplicated?
|
||||
[:div {:class (stl/css :variant-warning-wrapper)}
|
||||
[:div {:class (stl/css :variant-warning)}
|
||||
[:> icon* {:icon-id i/msg-neutral
|
||||
:class (stl/css :variant-warning-darken)}]
|
||||
[:div {:class (stl/css :variant-warning-highlight)}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -250,7 +250,7 @@
|
||||
:icon i/remove}]]
|
||||
|
||||
(some? fills)
|
||||
[:& h/sortable-container {}
|
||||
[:> h/sortable-container* {}
|
||||
(for [value fills]
|
||||
(let [mdata (meta value)
|
||||
index (get mdata :index)
|
||||
|
||||
@ -812,7 +812,7 @@
|
||||
[:button {:class (stl/css :add-column) :on-click add-track} deprecated-icon/add]]
|
||||
|
||||
(when expanded?
|
||||
[:& h/sortable-container {}
|
||||
[:> h/sortable-container* {}
|
||||
[:div {:class (stl/css :grid-tracks-info-container)}
|
||||
(for [[index column] (d/enumerate column-values)]
|
||||
[:& grid-track-info {:key (dm/str index "-" (d/name type))
|
||||
|
||||
@ -356,7 +356,7 @@
|
||||
:icon i/remove}]]]]
|
||||
|
||||
(some? shadows)
|
||||
[:& h/sortable-container {}
|
||||
[:> h/sortable-container* {}
|
||||
[:div {:class (stl/css :element-set-content)}
|
||||
(for [{:keys [::index id] :as shadow} shadows]
|
||||
[:> shadow-entry*
|
||||
|
||||
@ -203,7 +203,7 @@
|
||||
:on-click handle-remove-all
|
||||
:icon i/remove}]]
|
||||
(seq strokes)
|
||||
[:& h/sortable-container {}
|
||||
[:> h/sortable-container* {}
|
||||
(for [[index value] (d/enumerate (:strokes values []))]
|
||||
[:& stroke-row {:key (dm/str "stroke-" index)
|
||||
:stroke value
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
[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 variant-menu*]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.component :refer [component-menu* component-variant-main*]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.exports :refer [exports-menu* exports-attrs]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.fill :as fill]
|
||||
@ -107,10 +107,10 @@
|
||||
:type shape-type
|
||||
:shapes shapes}]
|
||||
|
||||
[:& component-menu {:shapes shapes}]
|
||||
[:> component-menu* {:shapes shapes}]
|
||||
|
||||
(when is-variant?
|
||||
[:> variant-menu* {:shapes shapes}])
|
||||
[:> component-variant-main* {:shapes shapes}])
|
||||
|
||||
[:& layout-container-menu
|
||||
{:type shape-type
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
[app.main.refs :as refs]
|
||||
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-attrs 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*]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.exports :refer [exports-attrs exports-menu*]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.fill :as fill]
|
||||
@ -234,7 +234,7 @@
|
||||
|
||||
merge-token-values
|
||||
(fn [acc shape-attrs applied-tokens]
|
||||
"Merges token values across all shape attributes.
|
||||
"Merges token values across all shape attributes.
|
||||
For each shape attribute, its corresponding token attributes are merged
|
||||
into the accumulator. If applied tokens are empty, the accumulator is returned unchanged."
|
||||
(if (seq applied-tokens)
|
||||
@ -455,7 +455,7 @@
|
||||
:shapes shapes}])
|
||||
|
||||
(when (some? components)
|
||||
[:& component-menu {:shapes components}])
|
||||
[:> component-menu* {:shapes components}])
|
||||
|
||||
[:& layout-container-menu
|
||||
{:type type
|
||||
|
||||
@ -202,7 +202,7 @@
|
||||
editing-page-id (mf/deref refs/editing-page-item)
|
||||
current-page-id (mf/use-ctx ctx/current-page-id)]
|
||||
[:ul {:class (stl/css :page-list)}
|
||||
[:& hooks/sortable-container {}
|
||||
[:> hooks/sortable-container* {}
|
||||
(for [[index page-id] (d/enumerate pages)]
|
||||
[:& page-item-wrapper
|
||||
{:page-id page-id
|
||||
|
||||
@ -51,7 +51,7 @@
|
||||
(when-not token-set-new-path
|
||||
[:> tsetslist/inline-add-button*])
|
||||
|
||||
[:> h/sortable-container {}
|
||||
[:> h/sortable-container* {}
|
||||
[:> tsets/sets-list*
|
||||
{:tokens-lib tokens-lib
|
||||
:new-path token-set-new-path
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user