0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-03-25 06:01:46 -05:00

Allow swap over multiple components

This commit is contained in:
Pablo Alba 2023-10-11 10:04:58 +02:00 committed by Andrés Moya
parent 93fbb0655f
commit 39ed665b93
10 changed files with 390 additions and 423 deletions

View file

@ -539,6 +539,11 @@
""
(join-path (butlast split)))))
(defn last-path
"Returns the last item of the path."
[path]
(last (split-path path)))
(defn compact-name
"Append the first item of the path and the name."
[path name]

View file

@ -324,7 +324,8 @@
(watch [_ state _]
(let [objects (wsh/lookup-page-objects state)
selected (->> (wsh/lookup-selected state)
(cph/clean-loops objects))
(cph/clean-loops objects)
(remove #(ctn/has-any-copy-parent? objects (get objects %)))) ;; We don't want to change the structure of component copies
components-v2 (features/active-feature? state :components-v2)]
(rx/of (add-component2 selected components-v2))))))
@ -337,7 +338,8 @@
(let [components-v2 (features/active-feature? state :components-v2)
objects (wsh/lookup-page-objects state)
selected (->> (wsh/lookup-selected state)
(cph/clean-loops objects))
(cph/clean-loops objects)
(remove #(ctn/has-any-copy-parent? objects (get objects %)))) ;; We don't want to change the structure of component copies
added-components (map
#(add-component2 [%] components-v2)
selected)
@ -483,6 +485,20 @@
(rx/of (dch/commit-changes (assoc changes :file-id library-id)))))))
(defn restore-components
"Restore multiple deleted component definded by a map with the component id as key and the component library as value"
[components-data]
(dm/assert! (map? components-data))
(ptk/reify ::restore-components
ptk/WatchEvent
(watch [_ _ _]
(let [undo-id (js/Symbol)]
(rx/concat
(rx/of (dwu/start-undo-transaction undo-id))
(rx/map #(restore-component (val %) (key %)) (rx/from components-data))
(rx/of (dwu/commit-undo-transaction undo-id)))))))
(defn instantiate-component
"Create a new shape in the current page, from the component with the given id
in the given file library. Then selects the newly created instance."
@ -534,6 +550,19 @@
(rx/of (dch/commit-changes changes))))))
(defn detach-components
"Remove all references to components in the shapes with the given ids"
[ids]
(dm/assert! (seq ids))
(ptk/reify ::detach-components
ptk/WatchEvent
(watch [_ _ _]
(let [undo-id (js/Symbol)]
(rx/concat
(rx/of (dwu/start-undo-transaction undo-id))
(rx/map #(detach-component %) (rx/from ids))
(rx/of (dwu/commit-undo-transaction undo-id)))))))
(def detach-selected-components
(ptk/reify ::detach-selected-components
ptk/WatchEvent
@ -621,6 +650,20 @@
file))
(rx/of (dch/commit-changes changes))))))
(defn reset-components
"Cancels all modifications in the shapes with the given ids"
[ids]
(dm/assert! (seq ids))
(ptk/reify ::reset-components
ptk/WatchEvent
(watch [_ _ _]
(let [undo-id (js/Symbol)]
(rx/concat
(rx/of (dwu/start-undo-transaction undo-id))
(rx/map #(reset-component %) (rx/from ids))
(rx/of (dwu/commit-undo-transaction undo-id)))))))
(defn update-component
"Modify the component linked to the shape with the given id, in the
current page, so that all attributes of its shapes are equal to the
@ -734,17 +777,6 @@
root-id (:main-instance-id component)]
(rx/of (dwt/update-thumbnail file-id page-id root-id))))))
(defn update-component-in-bulk
[shapes file-id]
(ptk/reify ::update-component-in-bulk
ptk/WatchEvent
(watch [_ _ _]
(let [undo-id (js/Symbol)]
(rx/concat
(rx/of (dwu/start-undo-transaction undo-id))
(rx/map #(update-component-sync (:id %) file-id (uuid/next)) (rx/from shapes))
(rx/of (dwu/commit-undo-transaction undo-id)))))))
(defn- find-shape-index
[objects id shape-id]
(let [object (get objects id)]
@ -757,7 +789,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))
@ -782,18 +814,29 @@
position
page
libraries)
changes (pcb/change-parent changes (:parent-id shape) [new-shape] index {:component-swap true})
undo-id (js/Symbol)]
(rx/of (dwu/start-undo-transaction undo-id)
(dch/commit-changes changes)
changes (pcb/change-parent changes (:parent-id shape) [new-shape] index {:component-swap true})]
(rx/of (dch/commit-changes changes)
(ptk/data-event :layout/update [(:id new-shape)])
(dws/select-shapes (d/ordered-set (:id new-shape)))
(dwsh/delete-shapes nil (d/ordered-set (:id shape)) {:component-swap true})
(dwu/commit-undo-transaction undo-id)
(dwsp/open-specialized-panel :component-swap [(assoc new-shape :parent-id (:parent-id shape))]))))))
(dws/select-shape (:id new-shape) true)
(dwsh/delete-shapes nil (d/ordered-set (:id shape)) {:component-swap true}))))))
(defn component-multi-swap
"Swaps several components with another one"
[shapes file-id id-new-component]
(dm/assert! (seq shapes))
(dm/assert! (uuid? id-new-component))
(dm/assert! (uuid? file-id))
(ptk/reify ::component-multi-swap
ptk/WatchEvent
(watch [_ _ _]
(let [undo-id (js/Symbol)]
(rx/concat
(rx/of (dwu/start-undo-transaction undo-id))
(rx/map #(component-swap % file-id id-new-component) (rx/from shapes))
(rx/of (dwu/commit-undo-transaction undo-id))
(rx/of (dwsp/open-specialized-panel :component-swap)))))))
(def valid-asset-types

View file

@ -6,6 +6,8 @@
(ns app.main.data.workspace.specialized-panel
(:require
[app.common.data :as d]
[app.main.data.workspace.state-helpers :as wsh]
[beicon.core :as rx]
[potok.core :as ptk]))
@ -18,14 +20,25 @@
(dissoc state :specialized-panel))))
(defn open-specialized-panel
[type shapes]
(ptk/reify ::open-specialized-panel
ptk/UpdateEvent
(update [_ state]
(assoc state :specialized-panel {:type type :shapes shapes}))
ptk/WatchEvent
(watch [_ _ stream]
(->> stream
(rx/filter interrupt?)
(rx/take 1)
(rx/map (constantly clear-specialized-panel))))))
([type]
(ptk/reify ::open-specialized-panel-1
ptk/WatchEvent
(watch [_ state _]
(let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
selected-ids (wsh/lookup-selected state)
selected-shapes (map (d/getf objects) selected-ids)]
(rx/of (open-specialized-panel type selected-shapes))))))
([type shapes]
(ptk/reify ::open-specialized-panel-2
ptk/UpdateEvent
(update [_ state]
(assoc state :specialized-panel {:type type :shapes shapes}))
ptk/WatchEvent
(watch [_ _ stream]
(->> stream
(rx/filter interrupt?)
(rx/take 1)
(rx/map (constantly clear-specialized-panel)))))))

View file

@ -12,9 +12,8 @@
[app.common.data.macros :as dm]
[app.common.pages.helpers :as cph]
[app.common.types.component :as ctk]
[app.common.types.components-list :as ctkl]
[app.common.types.file :as ctf]
[app.common.types.page :as ctp]
[app.common.uuid :as uuid]
[app.main.data.events :as ev]
[app.main.data.modal :as modal]
[app.main.data.shortcuts :as scd]
@ -25,7 +24,6 @@
[app.main.data.workspace.shape-layout :as dwsl]
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.shortcuts :as sc]
[app.main.data.workspace.undo :as dwu]
[app.main.features :as features]
[app.main.refs :as refs]
[app.main.store :as st]
@ -33,6 +31,7 @@
[app.main.ui.components.shape-icon-refactor :as sic]
[app.main.ui.context :as ctx]
[app.main.ui.icons :as i]
[app.main.ui.workspace.sidebar.assets.common :as cmm]
[app.util.dom :as dom]
[app.util.i18n :refer [tr] :as i18n]
[app.util.timers :as timers]
@ -440,180 +439,40 @@
(mf/defc context-menu-component
[{:keys [shapes]}]
(let [single? (= (count shapes) 1)
shapes (filter ctk/instance-head? shapes)
components-v2 (features/use-feature :components-v2)
has-component? (some true? (map #(ctk/instance-head? %) shapes))
is-component? (and single? (-> shapes first :component-id some?))
in-copy-not-root? (some true? (map #(ctk/in-component-copy-not-head? %) shapes))
objects (deref refs/workspace-page-objects)
touched? (and single? (cph/component-touched? objects (:id (first shapes))))
can-update-main? (or (not components-v2) touched?)
first-shape (first shapes)
{:keys [id component-id component-file]} first-shape
main-instance? (ctk/main-instance? first-shape)
component-shapes (filter #(ctk/instance-head? %) shapes)
touched-components (filter #(cph/component-touched? objects (:id %)) component-shapes)
can-update-main-of-any? (or (not components-v2) (not-empty touched-components))
current-file-id (mf/use-ctx ctx/current-file-id)
local-component? (= component-file current-file-id)
remote-components (filter #(not= (:component-file %) current-file-id)
component-shapes)
workspace-data (deref refs/workspace-data)
workspace-libraries (deref refs/workspace-libraries)
component (if local-component?
(ctkl/get-component workspace-data component-id)
(ctf/get-component workspace-libraries component-file component-id))
is-dangling? (nil? component)
lacks-annotation? (nil? (:annotation component))
lib-exists? (and (not local-component?)
(some? (get workspace-libraries component-file)))
in-main? (some true? (map #(ctk/main-instance? %) shapes))
components-menu-entries (cmm/generate-components-menu-entries shapes components-v2)
do-add-component #(st/emit! (dwl/add-component))
do-add-multiple-components #(st/emit! (dwl/add-multiple-components))
do-detach-component #(st/emit! (dwl/detach-component id))
do-detach-component-in-bulk #(st/emit! dwl/detach-selected-components)
do-reset-component #(st/emit! (dwl/reset-component id))
do-reset-component-in-bulk (fn []
(let [undo-id (js/Symbol)]
(st/emit! (dwu/start-undo-transaction undo-id))
(apply st/emit!
(map #(dwl/reset-component (:id %)) touched-components))
(st/emit! (dwu/commit-undo-transaction undo-id))))
do-show-component #(st/emit! (dw/go-to-component component-id))
do-show-in-assets #(st/emit! (if components-v2
(dw/show-component-in-assets component-id)
(dw/go-to-component component-id)))
create-annotation #(when components-v2
(st/emit! (dw/set-annotations-id-for-create (:id first-shape))))
do-navigate-component-file #(st/emit! (dwl/nav-to-component-file component-file))
do-update-component #(st/emit! (dwl/update-component-sync id component-file))
do-update-component-in-bulk #(st/emit! (dwl/update-component-in-bulk touched-components component-file))
do-restore-component #(st/emit! (dwl/restore-component component-file component-id)
(dw/go-to-main-instance nil component-id))
do-update-remote-component
#(st/emit! (modal/show
{:type :confirm
:message ""
:title (tr "modals.update-remote-component.message")
:hint (tr "modals.update-remote-component.hint")
:cancel-label (tr "modals.update-remote-component.cancel")
:accept-label (tr "modals.update-remote-component.accept")
:accept-style :primary
:on-accept do-update-component}))
do-update-in-bulk (fn []
(if (empty? remote-components)
(do-update-component-in-bulk)
#(st/emit! (modal/show
{:type :confirm
:message ""
:title (tr "modals.update-remote-component-in-bulk.message")
:hint (tr "modals.update-remote-component-in-bulk.hint")
:items remote-components
:cancel-label (tr "modals.update-remote-component.cancel")
:accept-label (tr "modals.update-remote-component.accept")
:accept-style :primary
:on-accept do-update-component-in-bulk}))))]
do-add-multiple-components #(st/emit! (dwl/add-multiple-components))]
[:*
[:*
(when (or (not in-copy-not-root?) (and has-component? (not single?)))
[:& menu-separator])
(when-not in-copy-not-root?
[:& menu-entry {:title (tr "workspace.shape.menu.create-component")
:shortcut (sc/get-tooltip :create-component)
:on-click do-add-component}])
(when-not (or single? in-copy-not-root?)
[:& menu-entry {:title (tr "workspace.shape.menu.create-multiple-components")
:on-click do-add-multiple-components}])
(when (and has-component? (not single?))
[:& menu-entry {:title (tr "workspace.shape.menu.detach-instances-in-bulk")
:shortcut (sc/get-tooltip :detach-component)
:on-click do-detach-component-in-bulk}])
(when (and has-component? can-update-main-of-any? (not single?))
[:* [:& menu-entry {:title (tr "workspace.shape.menu.update-components-in-bulk")
:on-click do-update-in-bulk}]
[:& menu-entry {:title (tr "workspace.shape.menu.reset-overrides")
:on-click do-reset-component-in-bulk}]]
)]
(when is-component?
;; WARNING: this menu is the same as the context menu at the sidebar.
;; If you change it, you must change equally the file
;; app/main/ui/workspace/sidebar/options/menus/component.cljs
(when-not in-main?
[:*
[:& menu-separator]
(if main-instance?
[:*
[:& menu-entry {:title (tr "workspace.shape.menu.show-in-assets")
:on-click do-show-in-assets}]
(when (and components-v2 local-component? lacks-annotation?)
[:& menu-entry {:title (tr "workspace.shape.menu.create-annotation")
:on-click create-annotation}])]
(if local-component?
(if is-dangling?
[:*
[:& menu-entry {:title (tr "workspace.shape.menu.detach-instance")
:shortcut (sc/get-tooltip :detach-component)
:on-click do-detach-component}]
(when can-update-main?
[:& menu-entry {:title (tr "workspace.shape.menu.reset-overrides")
:on-click do-reset-component}])
(when components-v2
[:& menu-entry {:title (tr "workspace.shape.menu.restore-main")
:on-click do-restore-component}])]
[:*
[:& menu-entry {:title (tr "workspace.shape.menu.detach-instance")
:shortcut (sc/get-tooltip :detach-component)
:on-click do-detach-component}]
(when can-update-main?
[:*
[:& menu-entry {:title (tr "workspace.shape.menu.reset-overrides")
:on-click do-reset-component}]
[:& menu-entry {:title (tr "workspace.shape.menu.update-main")
:on-click do-update-component}]])
[:& menu-entry {:title (tr "workspace.shape.menu.show-main")
:on-click do-show-component}]])
(if is-dangling?
[:*
[:& menu-entry {:title (tr "workspace.shape.menu.detach-instance")
:shortcut (sc/get-tooltip :detach-component)
:on-click do-detach-component}]
(when can-update-main?
[:& menu-entry {:title (tr "workspace.shape.menu.reset-overrides")
:on-click do-reset-component}])
(when (and components-v2 lib-exists?)
[:& menu-entry {:title (tr "workspace.shape.menu.restore-main")
:on-click do-restore-component}])]
[:*
[:& menu-entry {:title (tr "workspace.shape.menu.detach-instance")
:shortcut (sc/get-tooltip :detach-component)
:on-click do-detach-component}]
(when can-update-main?
[:*
[:& menu-entry {:title (tr "workspace.shape.menu.reset-overrides")
:on-click do-reset-component}]
[:& menu-entry {:title (tr "workspace.shape.menu.update-main")
:on-click do-update-remote-component}]])
[:& menu-entry {:title (tr "workspace.shape.menu.go-main")
:on-click do-navigate-component-file}]])))])
[:& menu-separator]]))
(if single?
[:& menu-entry {:title (tr "workspace.shape.menu.create-component")
:shortcut (sc/get-tooltip :create-component)
:on-click do-add-component}]
[:& menu-entry {:title (tr "workspace.shape.menu.create-multiple-components")
:on-click do-add-multiple-components}])])
(when (seq components-menu-entries)
[:*
[:& menu-separator]
(for [entry components-menu-entries :when (not (nil? entry))]
[:& menu-entry {:key (uuid/next)
:title (tr (:msg entry))
:shortcut (when (contains? entry :shortcut) (sc/get-tooltip (:shortcut entry)))
:on-click (:action entry)}])])]))
(mf/defc context-menu-delete
[]
(let [do-delete #(st/emit! (dw/delete-selected))]
[:& menu-entry {:title (tr "workspace.shape.menu.delete")
[:*
[:& menu-separator]
[:& menu-entry {:title (tr "workspace.shape.menu.delete")
:shortcut (sc/get-tooltip :delete)
:on-click do-delete}]))
:on-click do-delete}]]))
(mf/defc shape-context-menu
{::mf/wrap [mf/memo]}

View file

@ -11,8 +11,11 @@
[app.common.data.macros :as dm]
[app.common.pages.helpers :as cph]
[app.common.spec :as us]
[app.common.types.component :as ctk]
[app.common.types.file :as ctf]
[app.main.data.modal :as modal]
[app.main.data.workspace :as dw]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.thumbnails :as dwt]
[app.main.data.workspace.undo :as dwu]
[app.main.refs :as refs]
@ -25,6 +28,7 @@
[app.main.ui.icons :as i]
[app.util.dom :as dom]
[app.util.dom.dnd :as dnd]
[app.util.i18n :as i18n :refer [tr]]
[app.util.strings :refer [matches-search]]
[app.util.timers :as ts]
[cljs.spec.alpha :as s]
@ -290,3 +294,131 @@
:class (dom/classnames (css :thumbnail) true)}]
[:& component-svg {:root-shape root-shape
:objects (:objects container)}])))
(defn generate-components-menu-entries
[shapes components-v2]
(let [multi (> (count shapes) 1)
copies (filter ctk/in-component-copy? shapes)
current-file-id (mf/use-ctx ctx/current-file-id)
objects (deref refs/workspace-page-objects)
workspace-data (deref refs/workspace-data)
workspace-libraries (deref refs/workspace-libraries)
current-file {:id current-file-id :data workspace-data}
find-component #(ctf/resolve-component % current-file workspace-libraries)
local-or-exists (fn [shape]
(let [library-id (:component-file shape)]
(or (= library-id current-file-id)
(some? (get workspace-libraries library-id)))))
restorable-copies (->> copies
(filter #(nil? (find-component %)))
(filter #(local-or-exists %)))
touched-components (filter #(cph/component-touched? objects (:id %)) copies)
can-reset-overrides? (or (not components-v2) (seq touched-components))
;; For when it's only one shape
shape (first shapes)
id (:id shape)
main-instance? (if components-v2 (ctk/main-instance? shape) true)
component-id (:component-id shape)
library-id (:component-file shape)
local-component? (= library-id current-file-id)
component (find-component shape)
lacks-annotation? (nil? (:annotation shape))
is-dangling? (nil? component)
can-update-main? (or (not components-v2)
(and
(not main-instance?)
(cph/component-touched? objects (:id shape))))
do-detach-component
#(st/emit! (dwl/detach-components (map :id copies)))
do-reset-component
#(st/emit! (dwl/reset-components (map :id touched-components)))
do-restore-component
#(let [;; Extract a map of component-id -> component-file in order to avoid duplicates
comps-to-restore (reduce (fn [id-file-map {:keys [component-id component-file]}]
(assoc id-file-map component-id component-file))
{}
restorable-copies)]
(st/emit! (dwl/restore-components comps-to-restore)
(when (= 1 (count comps-to-restore))
(dw/go-to-main-instance (val (first comps-to-restore)) (key (first comps-to-restore))))))
do-update-component-sync
#(st/emit! (dwl/update-component-sync id library-id))
do-update-remote-component
(fn []
(st/emit! (modal/show
{:type :confirm
:message ""
:title (tr "modals.update-remote-component.message")
:hint (tr "modals.update-remote-component.hint")
:cancel-label (tr "modals.update-remote-component.cancel")
:accept-label (tr "modals.update-remote-component.accept")
:accept-style :primary
:on-accept do-update-component-sync})))
do-update-component
#(if local-component?
(do-update-component-sync)
(do-update-remote-component))
do-show-local-component
#(st/emit! (dw/go-to-component component-id))
do-show-in-assets
#(st/emit! (if components-v2
(dw/show-component-in-assets component-id)
(dw/go-to-component component-id)))
do-create-annotation
#(st/emit! (dw/set-annotations-id-for-create id))
do-navigate-component-file
#(st/emit! (dwl/nav-to-component-file library-id))
do-show-component
#(if local-component?
(do-show-local-component)
(do-navigate-component-file))
menu-entries [(when (and (not multi) main-instance?)
{:msg "workspace.shape.menu.show-in-assets"
:action do-show-in-assets})
(when (and (not multi) main-instance? local-component? lacks-annotation? components-v2)
{:msg "workspace.shape.menu.create-annotation"
:action do-create-annotation})
(when (seq copies)
{:msg (if (> (count copies) 1)
"workspace.shape.menu.detach-instances-in-bulk"
"workspace.shape.menu.detach-instance")
:action do-detach-component
:shortcut :detach-component})
(when can-reset-overrides?
{:msg "workspace.shape.menu.reset-overrides"
:action do-reset-component})
(when (and (seq restorable-copies) components-v2)
{:msg "workspace.shape.menu.restore-main"
:action do-restore-component})
(when (and (not multi) (not main-instance?) (not is-dangling?))
{:msg "workspace.shape.menu.show-main"
:action do-show-component})
(when (and (not multi) can-update-main? (not is-dangling?))
{:msg "workspace.shape.menu.update-main"
:action do-update-component})]]
(filter (complement nil?) menu-entries)))

View file

@ -72,7 +72,7 @@
{::mf/wrap [mf/memo]}
[{:keys [panel]}]
(when (= (:type panel) :component-swap)
[:& component-menu {:shape (first (:shapes panel)) :swap-opened? true}]))
[:& component-menu {:shapes (:shapes panel) :swap-opened? true}]))
(mf/defc options-content

View file

@ -9,7 +9,6 @@
(:require
[app.common.pages.helpers :as cph]
[app.common.types.component :as ctk]
[app.common.types.components-list :as ctkl]
[app.common.types.file :as ctf]
[app.common.uuid :as uuid]
[app.main.data.modal :as modal]
@ -30,13 +29,10 @@
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(def component-attrs [:component-id :component-file :shape-ref :main-instance :annotation])
(mf/defc component-annotation
[{:keys [id values shape component] :as props}]
(let [main-instance? (:main-instance values)
component-id (:component-id values)
[{:keys [id shape component] :as props}]
(let [main-instance? (:main-instance shape)
component-id (:component-id shape)
annotation (:annotation component)
editing? (mf/use-state false)
invalid-text? (mf/use-state (or (nil? annotation)(str/empty? annotation)))
@ -150,7 +146,8 @@
(mf/defc component-swap
[{:keys [shapes] :as props}]
(let [shape (first shapes)
(let [single? (= 1 (count shapes))
shape (first shapes)
new-css-system (mf/use-ctx ctx/new-css-system)
current-file-id (mf/use-ctx ctx/current-file-id)
workspace-file (deref refs/workspace-file)
@ -158,10 +155,35 @@
workspace-libraries (deref refs/workspace-libraries)
objects (deref refs/workspace-page-objects)
libraries (assoc workspace-libraries current-file-id (assoc workspace-file :data workspace-data))
every-same-file? (every? #(= (:component-file shape) (:component-file %)) shapes)
current-comp-id (when (every? #(= (:component-id shape) (:component-id %)) shapes)
(:component-id shape))
file-id (if every-same-file?
(:component-file shape)
current-file-id)
paths (->> shapes
(map :name)
(map cph/split-path)
(map butlast))
find-common-path (fn common-path [path n]
(let [current (nth (first paths) n nil)]
(if (or (nil? current)
(not (every? #(= current (nth % n nil)) paths)))
path
(common-path (conj path current) (inc n)))))
path (if single?
(cph/butlast-path (:name shape))
(cph/join-path (if (not every-same-file?)
""
(find-common-path [] 0))))
filters* (mf/use-state
{:term ""
:file-id (:component-file shape)
:path (cph/butlast-path (:name shape))})
:file-id file-id
:path path})
filters (deref filters*)
components (-> (get-in libraries [(:file-id filters) :data :components])
@ -171,11 +193,21 @@
components
(filter #(str/includes? (str/lower (:name %)) (str/lower (:term filters))) components))
groups (->> (map :path components)
(filter #(= (cph/butlast-path (:path %)) (:path filters)))
(remove str/empty?)
distinct
(map #(hash-map :name %)))
get-subgroups (fn [path]
(let [split-path (cph/split-path path)]
(reduce (fn [acc dir]
(conj acc (str (last acc) " / " dir)))
[(first split-path)] (rest split-path))))
groups (->> components
(map :path)
(map get-subgroups)
(apply concat)
(remove str/empty?)
(remove nil?)
distinct
(filter #(= (cph/butlast-path %) (:path filters)))
sort)
components (filter #(= (:path %) (:path filters)) components)
@ -191,7 +223,13 @@
ids)]
(get-comps-ids (get objects (:parent-id shape)) ids))))
parent-components (set (get-comps-ids (get objects (:parent-id shape)) []))
parent-components (->> shapes
(map :parent-id)
(map #(get objects %))
(map #(get-comps-ids % []))
(apply concat)
set)
on-library-change
(mf/use-fn
@ -279,63 +317,67 @@
:key (:id item)
:on-click #(when-not loop?
(st/emit!
(dwl/component-swap shape (:file-id filters) (:id item))))}
(dwl/component-multi-swap shapes (:file-id filters) (:id item))))}
[:& cmm/component-item-thumbnail {:file-id (:file-id item)
:root-shape root-shape
:component item
:container container}]
[:span.component-name
{:class (stl/css-case :selected (= (:id item) (:component-id shape)))}
{:class (stl/css-case :selected (= (:id item) current-comp-id))}
(:name item)]])
[:div.component-group {:key (uuid/next) :on-click #(on-enter-group (:name item))}
[:span (:name item)]
[:div.component-group {:key (uuid/next) :on-click #(on-enter-group item)}
[:span (cph/last-path item)]
[:span i/arrow-slide]]))]]]))
(mf/defc component-ctx-menu
[{:keys [menu-entries on-close show type] :as props}]
(case type
:context-menu
[:& context-menu {:on-close on-close
:show show
:options
(vec (for [entry menu-entries :when (not (nil? entry))]
[(tr (:msg entry)) (:action entry)]))}]
:dropdown
[:& dropdown {:show show :on-close on-close}
[:ul {:class (stl/css :custom-select-dropdown)}
(for [entry menu-entries :when (not (nil? entry))]
[:li {:key (uuid/next)
:class (stl/css :dropdown-element)
:on-click (:action entry)}
[:span {:class (stl/css :dropdown-label)}
(tr (:msg entry))]])]]))
(mf/defc component-menu
[{:keys [shape swap-opened?] :as props}]
(let [[ids values] [[(:id shape)] (select-keys shape component-attrs)]
new-css-system (mf/use-ctx ctx/new-css-system)
[{:keys [shapes swap-opened?] :as props}]
(let [new-css-system (mf/use-ctx ctx/new-css-system)
current-file-id (mf/use-ctx ctx/current-file-id)
components-v2 (mf/use-ctx ctx/components-v2)
workspace-data (deref refs/workspace-data)
workspace-libraries (deref refs/workspace-libraries)
objects (deref refs/workspace-page-objects)
touched? (cph/component-touched? objects (:id shape))
can-update-main? (or (not components-v2) touched?)
id (first ids)
state* (mf/use-state {: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? (and components-v2 (seq copies))
;; For when it's only one shape
shape (first shapes)
id (:id shape)
shape-name (:name shape)
component (ctf/resolve-component shape {:id current-file-id :data workspace-data} workspace-libraries)
main-instance? (if components-v2 (ctk/main-instance? shape) true)
toggle-content
(mf/use-fn #(swap! state* update :show-content not))
shape-name (:name shape)
component-id (:component-id values)
library-id (:component-file values)
show? (some? component-id)
main-instance? (if components-v2
(ctk/main-instance? values)
true)
can-swap? (and components-v2 (not main-instance?))
main-component? (:main-instance values)
lacks-annotation? (nil? (:annotation values))
local-component? (= library-id current-file-id)
workspace-data (deref refs/workspace-data)
workspace-libraries (deref refs/workspace-libraries)
component (if local-component?
(ctkl/get-component workspace-data component-id)
(ctf/get-component workspace-libraries library-id component-id))
is-dangling? (nil? component)
lib-exists? (and (not local-component?)
(some? (get workspace-libraries library-id)))
on-menu-click
(mf/use-fn
(fn [event]
@ -347,37 +389,10 @@
(mf/use-callback
#(swap! state* assoc :menu-open false))
do-detach-component
#(st/emit! (dwl/detach-component id))
menu-entries (cmm/generate-components-menu-entries shapes components-v2)
show-menu? (seq menu-entries)]
do-reset-component
#(st/emit! (dwl/reset-component id))
do-update-component
#(st/emit! (dwl/update-component-sync id library-id))
do-restore-component
#(st/emit! (dwl/restore-component library-id component-id)
(dw/go-to-main-instance nil component-id))
do-update-remote-component
#(st/emit! (modal/show
{:type :confirm
:message ""
:title (tr "modals.update-remote-component.message")
:hint (tr "modals.update-remote-component.hint")
:cancel-label (tr "modals.update-remote-component.cancel")
:accept-label (tr "modals.update-remote-component.accept")
:accept-style :primary
:on-accept do-update-component}))
do-show-component #(st/emit! (dw/go-to-component component-id))
do-show-in-assets #(st/emit! (if components-v2
(dw/show-component-in-assets component-id)
(dw/go-to-component component-id)))
do-create-annotation #(st/emit! (dw/set-annotations-id-for-create id))
do-navigate-component-file #(st/emit! (dwl/nav-to-component-file library-id))]
(when show?
(when (seq shapes)
(if new-css-system
[:div {:class (stl/css :element-set)}
[:div {:class (stl/css :element-title)}
@ -403,94 +418,12 @@
:on-click on-menu-click}
i/menu-refactor]
[:& dropdown {:show menu-open?
:on-close on-menu-close}
[:ul {:class (stl/css :custom-select-dropdown)}
(if main-component?
[:*
[:li {:class (stl/css :dropdown-element)
:on-click do-show-in-assets}
[:span {:class (stl/css :dropdown-label)}
(tr "workspace.shape.menu.show-in-assets")]]
(when (and components-v2 local-component? lacks-annotation?)
[:li {:class (stl/css :dropdown-element)
:on-click do-create-annotation}
[:span {:class (stl/css :dropdown-label)}
(tr "workspace.shape.menu.create-annotation")]])]
(if local-component?
(if is-dangling?
[:*
[:li {:class (stl/css :dropdown-element)
:on-click do-detach-component}
[:span {:class (stl/css :dropdown-label)}
(tr "workspace.shape.menu.detach-instance")]]
(when can-update-main?
[:li {:class (stl/css :dropdown-element)
:on-click do-reset-component}
[:span {:class (stl/css :dropdown-label)}
(tr "workspace.shape.menu.reset-overrides")]])
(when components-v2
[:li {:class (stl/css :dropdown-element)
:on-click do-restore-component}
[:span {:class (stl/css :dropdown-label)}
(tr "workspace.shape.menu.restore-main")]])]
[:*
[:li {:class (stl/css :dropdown-element)
:on-click do-detach-component}
[:span {:class (stl/css :dropdown-label)}
(tr "workspace.shape.menu.detach-instance")]]
(when can-update-main?
[:li {:class (stl/css :dropdown-element)
:on-click do-reset-component}
[:span {:class (stl/css :dropdown-label)}
(tr "workspace.shape.menu.reset-overrides")]]
[:li {:class (stl/css :dropdown-element)
:on-click do-update-component}
[:span {:class (stl/css :dropdown-label)}
(tr "workspace.shape.menu.update-main")]])
[:li {:class (stl/css :dropdown-element)
:on-click do-show-component}
[:span {:class (stl/css :dropdown-label)}
(tr "workspace.shape.menu.show-main")]]])
(if is-dangling?
[:*
[:li {:class (stl/css :dropdown-element)
:on-click do-detach-component}
[:span {:class (stl/css :dropdown-label)}
(tr "workspace.shape.menu.detach-instance")]]
(when can-update-main?
[:li {:class (stl/css :dropdown-element)
:on-click do-reset-component}
[:span {:class (stl/css :dropdown-label)}
(tr "workspace.shape.menu.reset-overrides")]])
(when (and components-v2 lib-exists?)
[:li {:class (stl/css :dropdown-element)
:on-click do-restore-component}
[:span {:class (stl/css :dropdown-label)}
(tr "workspace.shape.menu.restore-main")]])]
[:*
[:li {:class (stl/css :dropdown-element)
:on-click do-detach-component}
[:span {:class (stl/css :dropdown-label)}
(tr "workspace.shape.menu.detach-instance")]]
(when can-update-main?
[:li {:class (stl/css :dropdown-element)
:on-click do-reset-component}
[:span {:class (stl/css :dropdown-label)}
(tr "workspace.shape.menu.reset-overrides")]]
[:li {:class (stl/css :dropdown-element)
:on-click do-update-remote-component}
[:span {:class (stl/css :dropdown-label)}
(tr "workspace.shape.menu.update-main")]])
[:li {:class (stl/css :dropdown-element)
:on-click do-navigate-component-file}
[:span {:class (stl/css :dropdown-label)}
(tr "workspace.shape.menu.go-main")]]])))]]]]
(when components-v2
[:& component-annotation {:id id :values values :shape shape :component component}])])]
[:& component-ctx-menu {:show menu-open?
:on-close on-menu-close
:menu-entries menu-entries
:type :dropdown}]]]
(when (and (not multi) components-v2)
[:& component-annotation {:id id :shape shape :component component}])])]
[:div.element-set
[:div.element-set-title {:class (stl/css-case :back swap-opened?)
@ -500,63 +433,37 @@
[:span
i/arrow-slide])
[:span (tr "workspace.options.component")]]
[:span (if main-instance?
(tr "workspace.options.component.main")
(tr "workspace.options.component.copy"))]]
(when-not multi
[:span (if main-instance?
(tr "workspace.options.component.main")
(tr "workspace.options.component.copy"))])]
[:div.element-set-content
[:div.row-flex.component-row
{:class (stl/css-case :copy can-swap?)
:on-click #(when can-swap? (st/emit! (dwsp/open-specialized-panel :component-swap [shape])))}
(if main-instance?
i/component
i/component-copy)
[:div.component-name shape-name]
[:div.row-actions
{:on-click on-menu-click}
i/actions
;; WARNING: this menu is the same as the shape context menu.
;; If you change it, you must change equally the file
;; app/main/ui/workspace/context_menu.cljs
[:& context-menu {:on-close on-menu-close
:show menu-open?
:options (if main-component?
[[(tr "workspace.shape.menu.show-in-assets") do-show-in-assets]
(when (and components-v2 local-component? lacks-annotation?)
[(tr "workspace.shape.menu.create-annotation") do-create-annotation])]
(if local-component?
(if is-dangling?
[[(tr "workspace.shape.menu.detach-instance") do-detach-component]
(when can-update-main?
[(tr "workspace.shape.menu.reset-overrides") do-reset-component])
(when components-v2
[(tr "workspace.shape.menu.restore-main") do-restore-component])]
:on-click #(when can-swap? (st/emit! (dwsp/open-specialized-panel :component-swap shapes)))}
(if multi
i/component-copy
(if main-instance?
i/component
i/component-copy))
[:div.component-name (if multi
(tr "settings.multiple")
shape-name)]
(when show-menu?
[:div.row-actions
{:on-click on-menu-click}
i/actions
[:& component-ctx-menu {:on-close on-menu-close
:show menu-open?
:menu-entries menu-entries
:type :context-menu}]])
[[(tr "workspace.shape.menu.detach-instance") do-detach-component]
(when can-update-main?
[(tr "workspace.shape.menu.reset-overrides") do-reset-component])
(when can-update-main?
[(tr "workspace.shape.menu.update-main") do-update-component])
[(tr "workspace.shape.menu.show-main") do-show-component]])
(if is-dangling?
[[(tr "workspace.shape.menu.detach-instance") do-detach-component]
(when can-update-main?
[(tr "workspace.shape.menu.reset-overrides") do-reset-component])
(when (and components-v2 lib-exists?)
[(tr "workspace.shape.menu.restore-main") do-restore-component])]
[[(tr "workspace.shape.menu.detach-instance") do-detach-component]
(when can-update-main?
[(tr "workspace.shape.menu.reset-overrides") do-reset-component])
(when can-update-main?
[(tr "workspace.shape.menu.update-main") do-update-remote-component])
[(tr "workspace.shape.menu.go-main") do-navigate-component-file]])))}]]
(when can-swap?
(when (and can-swap? (not multi))
[:div.component-parent-name
(cph/merge-path-item (:path component) (:name component))])]
(when swap-opened?
[:& component-swap {:shapes [shape]}])
[:& component-swap {:shapes shapes}])
(when (and (not swap-opened?) components-v2)
[:& component-annotation {:id id :values values :shape shape :component component}])]]))))
(when (and (not swap-opened?) (not multi) components-v2)
[:& component-annotation {:id id :shape shape :component component}])]]))))

View file

@ -62,7 +62,7 @@
:values measure-values
:type type
:shape shape}]
[:& component-menu {:shape shape}]
[:& component-menu {:shapes [shape]}]
(when (or (not is-layout-child?) is-layout-child-absolute?)
[:& constraints-menu {:ids ids
:values constraint-values}])

View file

@ -73,7 +73,7 @@
:options true)}
[:& layer-menu {:type type :ids layer-ids :values layer-values}]
[:& measures-menu {:type type :ids measure-ids :values measure-values :shape shape}]
[:& component-menu {:shape shape}] ;;remove this in components-v2
[:& component-menu {:shapes [shape]}] ;;remove this in components-v2
[:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values :multiple false}]
(when (and (= (count ids) 1) is-layout-child? is-grid-parent?)

View file

@ -11,6 +11,7 @@
[app.common.data :as d]
[app.common.geom.shapes :as gsh]
[app.common.text :as txt]
[app.common.types.component :as ctk]
[app.common.types.shape.attrs :refer [editable-attrs]]
[app.common.types.shape.layout :as ctl]
[app.main.data.workspace.texts :as dwt]
@ -19,6 +20,7 @@
[app.main.ui.hooks :as hooks]
[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.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 :refer [fill-attrs fill-menu]]
@ -342,7 +344,9 @@
(get-attrs shapes objects-no-measures :stroke)
(get-attrs shapes objects-no-measures :exports)
(get-attrs shapes objects-no-measures :layout-container)
(get-attrs shapes objects-no-measures :layout-item)])))]
(get-attrs shapes objects-no-measures :layout-item)])))
components (filter ctk/instance-head? shapes)]
[:div {:class (stl/css-case new-css-system
:options true)}
@ -352,8 +356,12 @@
(when-not (empty? measure-ids)
[:& measures-menu {:type type :all-types all-types :ids measure-ids :values measure-values :shape shapes}])
(when-not (empty? components)
[:& component-menu {:shapes components}])
[:& layout-container-menu {:type type :ids layout-container-ids :values layout-container-values :multiple true}]
(when (or is-layout-child? has-flex-layout-container?)
[:& layout-item-menu
{:type type