🎉 Component swap

This commit is contained in:
Pablo Alba 2023-09-28 12:47:01 +02:00 committed by Andrés Moya
parent 917e6425d1
commit fac72a5874
26 changed files with 837 additions and 348 deletions

View file

@ -463,20 +463,22 @@
(d/update-in-when data [:components component-id :objects] reg-objects)))) (d/update-in-when data [:components component-id :objects] reg-objects))))
(defmethod process-change :mov-objects (defmethod process-change :mov-objects
[data {:keys [parent-id shapes index page-id component-id ignore-touched after-shape]}] [data {:keys [parent-id shapes index page-id component-id ignore-touched after-shape component-swap]}]
(letfn [(calculate-invalid-targets [objects shape-id] (letfn [(calculate-invalid-targets [objects shape-id]
(let [reduce-fn #(into %1 (calculate-invalid-targets objects %2))] (let [reduce-fn #(into %1 (calculate-invalid-targets objects %2))]
(->> (get-in objects [shape-id :shapes]) (->> (get-in objects [shape-id :shapes])
(reduce reduce-fn #{shape-id})))) (reduce reduce-fn #{shape-id}))))
;; Avoid placing a shape as a direct or indirect child of itself, ;; Avoid placing a shape as a direct or indirect child of itself,
;; or inside its main component if it's in a copy. ;; or inside its main component if it's in a copy,
;; or inside a copy
(is-valid-move? [objects shape-id] (is-valid-move? [objects shape-id]
(let [invalid-targets (calculate-invalid-targets objects shape-id)] (let [invalid-targets (calculate-invalid-targets objects shape-id)]
(and (contains? objects shape-id) (and (contains? objects shape-id)
(not (invalid-targets parent-id)) (not (invalid-targets parent-id))
(not (cph/components-nesting-loop? objects shape-id parent-id)) (not (cph/components-nesting-loop? objects shape-id parent-id))
#_(cph/valid-frame-target? objects parent-id shape-id)))) (or component-swap
(not (ctk/in-component-copy? (get objects parent-id))))))) ;; We don't want to change the structure of component copies
(insert-items [prev-shapes index shapes] (insert-items [prev-shapes index shapes]
(let [prev-shapes (or prev-shapes [])] (let [prev-shapes (or prev-shapes [])]

View file

@ -308,9 +308,12 @@
(defn change-parent (defn change-parent
([changes parent-id shapes] ([changes parent-id shapes]
(change-parent changes parent-id shapes nil)) (change-parent changes parent-id shapes nil {}))
([changes parent-id shapes index] ([changes parent-id shapes index]
(change-parent changes parent-id shapes index {}))
([changes parent-id shapes index options]
(assert-page-id! changes) (assert-page-id! changes)
(assert-objects! changes) (assert-objects! changes)
(let [objects (lookup-objects changes) (let [objects (lookup-objects changes)
@ -323,7 +326,9 @@
:shapes (->> shapes reverse (mapv :id))} :shapes (->> shapes reverse (mapv :id))}
(some? index) (some? index)
(assoc :index index)) (assoc :index index)
(:component-swap options)
(assoc :component-swap true))
mk-undo-change mk-undo-change
(fn [undo-changes shape] (fn [undo-changes shape]

View file

@ -531,6 +531,14 @@
(merge-path other-path item)))) (merge-path other-path item))))
[other-path last-item false])))) [other-path last-item false]))))
(defn prev-path
"Remove the last item of the path."
[path]
(let [split (split-path path)]
(if (= 1 (count split))
""
(join-path (butlast split)))))
(defn compact-name (defn compact-name
"Append the first item of the path and the name." "Append the first item of the path and the name."
[path name] [path name]

View file

@ -134,12 +134,19 @@
[shape] [shape]
(some? (:shape-ref shape))) (some? (:shape-ref shape)))
(defn in-component-copy-not-head?
"Check if the shape is inside a component non-main instance and
is not the head of a subinstance."
[shape]
(and (some? (:shape-ref shape))
(nil? (:component-id shape))))
(defn in-component-copy-not-root? (defn in-component-copy-not-root?
"Check if the shape is inside a component non-main instance and "Check if the shape is inside a component non-main instance and
is not the root shape." is not the root shape."
[shape] [shape]
(and (some? (:shape-ref shape)) (and (some? (:shape-ref shape))
(nil? (:component-id shape)))) (nil? (:component-root shape))))
(defn main-instance-of? (defn main-instance-of?
"Check if this shape is the root of the main instance of the given component." "Check if this shape is the root of the main instance of the given component."

View file

@ -344,3 +344,22 @@
(if (= parent-id uuid/zero) (if (= parent-id uuid/zero)
current-top current-top
(get-top-instance objects parent current-top)))) (get-top-instance objects parent current-top))))
(defn get-first-not-copy-parent
"Go trough the parents until we find a shape that is not a copy of a component."
[objects id]
(let [shape (get objects id)]
(if (ctk/in-component-copy? shape)
(get-first-not-copy-parent objects (:parent-id shape))
shape)))
(defn has-any-copy-parent?
"Check if the shape has any parent that is a copy of a component."
[objects shape]
(let [parent (get objects (:parent-id shape))]
(if (nil? parent)
false
(if (ctk/in-component-copy? parent)
true
(has-any-copy-parent? objects (:parent-id shape))))))

View file

@ -73,6 +73,18 @@
width: 100%; width: 100%;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
svg {
height: 8px;
width: 8px;
fill: $color-gray-20;
margin-right: 1rem;
transform: rotate(180deg);
}
&.back {
cursor: pointer;
}
} }
} }
@ -585,6 +597,26 @@
} }
} }
} }
&.copy {
flex-wrap: wrap;
border-radius: 8px;
border: 1px solid $color-gray-60;
padding: 0.5rem;
cursor: pointer;
.component-name {
width: 80%;
color: $color-white;
}
.component-parent-name {
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
padding-left: calc(0.5rem + 16px);
color: $color-gray-40;
}
}
} }
.grid-option .custom-select { .grid-option .custom-select {
@ -2554,3 +2586,125 @@
} }
} }
} }
.component-swap {
.search-block {
margin: 0.7rem 0.5rem 0.2rem 0.2rem;
height: 2.1rem;
width: 100%;
}
svg {
fill: $color-gray-20;
height: 0.7rem;
width: 0.7rem;
cursor: pointer;
}
.search-block {
border: 1px solid $color-gray-30;
margin: 0.6rem 0.5rem 0.2rem 0.2rem;
padding: $size-1 $size-2;
display: flex;
align-items: center;
&:hover {
border-color: $color-gray-20;
}
&:focus-within {
border-color: $color-primary;
}
& .search-input {
background-color: $color-gray-50;
border: none;
color: $color-gray-10;
font-size: $fs12;
margin: 0;
padding: 0;
flex-grow: 1;
&:focus {
color: lighten($color-gray-10, 8%);
outline: none;
}
}
& .search-icon {
display: flex;
align-items: center;
svg {
fill: $color-gray-30;
height: 16px;
width: 16px;
}
&.close {
transform: rotate(45deg);
cursor: pointer;
}
}
}
.component-path {
display: flex;
margin: 0.4rem 0 0 0.4rem;
cursor: pointer;
svg {
height: 8px;
width: 8px;
margin-right: 0.5rem;
transform: rotate(180deg);
}
}
.component-list {
margin: 0.7rem 0.5rem 0.5rem 0.5rem;
}
.component-item {
display: flex;
align-items: center;
margin-bottom: 0.5rem;
cursor: pointer;
svg {
background-color: $color-canvas;
border-radius: $br4;
border: 2px solid transparent;
height: 24px;
width: 24px;
margin-right: $size-2;
}
.selected {
color: $color-primary;
}
&:hover {
color: $color-primary;
}
&.disabled {
cursor: auto;
color: $color-gray-30;
}
}
.component-group {
display: flex;
align-items: center;
margin-bottom: 0.5rem;
justify-content: space-between;
cursor: pointer;
height: 24px;
svg {
height: 8px;
width: 8px;
}
&:hover {
color: $color-primary;
}
}
}

View file

@ -921,7 +921,7 @@
component-shape (ctn/get-component-shape objects shape) component-shape (ctn/get-component-shape objects shape)
component-shape-parent (ctn/get-component-shape objects parent) component-shape-parent (ctn/get-component-shape objects parent)
detach? (and (ctk/in-component-copy-not-root? shape) detach? (and (ctk/in-component-copy-not-head? shape)
(not= (:id component-shape) (not= (:id component-shape)
(:id component-shape-parent))) (:id component-shape-parent)))
deroot? (and (ctk/instance-root? shape) deroot? (and (ctk/instance-root? shape)
@ -1834,6 +1834,11 @@
;; Calculate position for the pasted elements ;; Calculate position for the pasted elements
[frame-id parent-id delta index] (calculate-paste-position state mouse-pos in-viewport?) [frame-id parent-id delta index] (calculate-paste-position state mouse-pos in-viewport?)
;; We don't want to change the structure of component copies
;; If the parent-id or the frame-id are component-copies, we need to get the first not copy parent
parent-id (:id (ctn/get-first-not-copy-parent page-objects parent-id))
frame-id (:id (ctn/get-first-not-copy-parent page-objects frame-id))
process-shape process-shape
(fn [_ shape] (fn [_ shape]
(let [parent (get page-objects parent-id) (let [parent (get page-objects parent-id)
@ -1841,7 +1846,7 @@
component-shape-parent (ctn/get-component-shape page-objects parent) component-shape-parent (ctn/get-component-shape page-objects parent)
;; if foreign instance, or a shape belonging to another component, detach the shape ;; if foreign instance, or a shape belonging to another component, detach the shape
detach? (or (foreign-instance? shape paste-objects state) detach? (or (foreign-instance? shape paste-objects state)
(and (ctk/in-component-copy-not-root? shape) (and (ctk/in-component-copy-not-head? shape)
(not= (:id component-shape) (not= (:id component-shape)
(:id component-shape-parent)))) (:id component-shape-parent))))
assign-shapes? (and (or (cph/group-shape? shape) assign-shapes? (and (or (cph/group-shape? shape)

View file

@ -101,7 +101,7 @@
;; Shapes that are in a component, but are not root, must be detached, ;; Shapes that are in a component, but are not root, must be detached,
;; because they will be now children of a non instance group. ;; because they will be now children of a non instance group.
shapes-to-detach (filter ctk/in-component-copy-not-root? shapes) shapes-to-detach (filter ctk/in-component-copy-not-head? shapes)
;; Look at the `get-empty-groups-after-group-creation` ;; Look at the `get-empty-groups-after-group-creation`
;; docstring to understand the real purpose of this code ;; docstring to understand the real purpose of this code

View file

@ -32,6 +32,7 @@
[app.main.data.workspace.notifications :as-alias dwn] [app.main.data.workspace.notifications :as-alias dwn]
[app.main.data.workspace.selection :as dws] [app.main.data.workspace.selection :as dws]
[app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.specialized-panel :as dwsp]
[app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.state-helpers :as wsh]
[app.main.data.workspace.thumbnails :as dwt] [app.main.data.workspace.thumbnails :as dwt]
[app.main.data.workspace.undo :as dwu] [app.main.data.workspace.undo :as dwu]
@ -744,6 +745,57 @@
(rx/map #(update-component-sync (:id %) file-id (uuid/next)) (rx/from shapes)) (rx/map #(update-component-sync (:id %) file-id (uuid/next)) (rx/from shapes))
(rx/of (dwu/commit-undo-transaction undo-id))))))) (rx/of (dwu/commit-undo-transaction undo-id)))))))
(defn- find-shape-index
[objects id shape-id]
(let [object (get objects id)]
(when object
(let [shapes (:shapes object)]
(or (->> shapes
(map-indexed (fn [index shape] [shape index]))
(filter #(= shape-id (first %)))
first
second)
0)))))
(defn component-swap
"Swaps a component with another one"
[shape file-id id-new-component]
(dm/assert! (uuid? id-new-component))
(dm/assert! (uuid? file-id))
(ptk/reify ::component-swap
ptk/WatchEvent
(watch [it state _]
(let [page (wsh/lookup-page state)
libraries (wsh/get-libraries state)
objects (:objects page)
index (find-shape-index objects (:parent-id shape) (:id shape))
position (gpt/point (:x shape) (:y shape))
changes (-> (pcb/empty-changes it (:id page))
(pcb/with-objects objects))
[new-shape changes]
(dwlh/generate-instantiate-component changes
objects
file-id
id-new-component
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)
(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))]))))))
(def valid-asset-types (def valid-asset-types
#{:colors :components :typographies}) #{:colors :components :typographies})

View file

@ -123,7 +123,8 @@
(update [_ state] (update [_ state]
(-> state (-> state
(update-in [:workspace-local :selected] d/toggle-selection id toggle?) (update-in [:workspace-local :selected] d/toggle-selection id toggle?)
(assoc-in [:workspace-local :last-selected] id))) (assoc-in [:workspace-local :last-selected] id)
(dissoc :specialized-panel)))
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
@ -188,7 +189,8 @@
(update [_ state] (update [_ state]
(-> state (-> state
(update-in [:workspace-local :selected] disj id) (update-in [:workspace-local :selected] disj id)
(update :workspace-local dissoc :last-selected))))) (update :workspace-local dissoc :last-selected)
(dissoc :specialized-panel)))))
(defn shift-select-shapes (defn shift-select-shapes
([id] ([id]
@ -206,7 +208,8 @@
(-> state (-> state
(assoc-in [:workspace-local :selected] (assoc-in [:workspace-local :selected]
(set/union selection append-to-selection)) (set/union selection append-to-selection))
(update :workspace-local assoc :last-selected id))))))) (update :workspace-local assoc :last-selected id)
(dissoc :specialized-panel)))))))
(defn select-shapes (defn select-shapes
[ids] [ids]
@ -223,7 +226,9 @@
ids (if (d/not-empty? focus) ids (if (d/not-empty? focus)
(cpf/filter-not-focus objects focus ids) (cpf/filter-not-focus objects focus ids)
ids)] ids)]
(assoc-in state [:workspace-local :selected] ids))) (-> state
(assoc-in [:workspace-local :selected] ids)
(dissoc :specialized-panel))))
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
@ -277,7 +282,9 @@
(update :workspace-local (update :workspace-local
#(-> % #(-> %
(assoc :selected (d/ordered-set)) (assoc :selected (d/ordered-set))
(dissoc :selected-frame)))))))) (dissoc :selected-frame)))
:allways
(dissoc :specialized-panel))))))
;; --- Select Shapes (By selrect) ;; --- Select Shapes (By selrect)
@ -631,7 +638,11 @@
(when (or (not move-delta?) (nil? (get-in state [:workspace-local :transform]))) (when (or (not move-delta?) (nil? (get-in state [:workspace-local :transform])))
(let [page (wsh/lookup-page state) (let [page (wsh/lookup-page state)
objects (:objects page) objects (:objects page)
selected (wsh/lookup-selected state)] selected (->> (wsh/lookup-selected state)
(map #(get objects %))
(remove #(ctk/in-component-copy-not-root? %)) ;; We don't want to change the structure of component copies
(map :id)
set)]
(when (seq selected) (when (seq selected)
(let [obj (get objects (first selected)) (let [obj (get objects (first selected))
delta (if move-delta? delta (if move-delta?

View file

@ -12,7 +12,6 @@
[app.common.pages.changes-builder :as pcb] [app.common.pages.changes-builder :as pcb]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.common.schema :as sm] [app.common.schema :as sm]
[app.common.types.component :as ctk]
[app.common.types.container :as ctn] [app.common.types.container :as ctn]
[app.common.types.page :as ctp] [app.common.types.page :as ctp]
[app.common.types.shape :as cts] [app.common.types.shape :as cts]
@ -133,8 +132,9 @@
(declare update-shape-flags) (declare update-shape-flags)
(defn delete-shapes (defn delete-shapes
([ids] (delete-shapes nil ids)) ([ids] (delete-shapes nil ids {}))
([page-id ids] ([page-id ids] (delete-shapes page-id ids {}))
([page-id ids options]
(dm/assert! (sm/set-of-uuid? ids)) (dm/assert! (sm/set-of-uuid? ids))
(ptk/reify ::delete-shapes (ptk/reify ::delete-shapes
ptk/WatchEvent ptk/WatchEvent
@ -154,11 +154,11 @@
;; Look for shapes that are inside a component copy, but are ;; Look for shapes that are inside a component copy, but are
;; not the root. In this case, they must not be deleted, ;; not the root. In this case, they must not be deleted,
;; but hidden (to be able to recover them more easily). ;; but hidden (to be able to recover them more easily).
(let [shape (get objects shape-id) ;; Unless we are doing a component swap, in which case we want
component-shape (ctn/get-component-shape objects shape)] ;; to delete the old shape
(and (ctk/in-component-copy? shape) (let [shape (get objects shape-id)]
(not= shape component-shape) (and (ctn/has-any-copy-parent? objects shape)
(not (ctk/main-instance? component-shape))))) (not (:component-swap options)))))
[ids-to-delete ids-to-hide] [ids-to-delete ids-to-hide]
(if components-v2 (if components-v2
@ -347,6 +347,11 @@
frame-id frame-id
(:parent-id base)) (:parent-id base))
;; If the parent-id or the frame-id are component-copies, we need to get the first not copy parent
parent-id (:id (ctn/get-first-not-copy-parent objects parent-id)) ;; We don't want to change the structure of component copies
frame-id (:id (ctn/get-first-not-copy-parent objects frame-id))
shape (cts/setup-shape shape (cts/setup-shape
(-> attrs (-> attrs
(assoc :type type) (assoc :type type)

View file

@ -68,7 +68,8 @@
:cut {:tooltip (ds/meta "X") :cut {:tooltip (ds/meta "X")
:command (ds/c-mod "x") :command (ds/c-mod "x")
:subsections [:edit] :subsections [:edit]
:fn #(emit-when-no-readonly (dw/copy-selected) :fn #(emit-when-no-readonly
(dw/copy-selected)
(dw/delete-selected))} (dw/delete-selected))}
:paste {:tooltip (ds/meta "V") :paste {:tooltip (ds/meta "V")

View file

@ -0,0 +1,31 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.data.workspace.specialized-panel
(:require
[beicon.core :as rx]
[potok.core :as ptk]))
(defn interrupt? [e] (= e :interrupt))
(def clear-specialized-panel
(ptk/reify ::clear-specialized-panel
ptk/UpdateEvent
(update [_ state]
(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))))))

View file

@ -513,10 +513,11 @@
:else :else
[move-vector nil]) [move-vector nil])
nesting-loop? (some #(cph/components-nesting-loop? objects (:id %) target-frame) shapes)] nesting-loop? (some #(cph/components-nesting-loop? objects (:id %) target-frame) shapes)
is-component-copy? (ctk/in-component-copy? (get objects target-frame))]
(cond-> (dwm/create-modif-tree ids (ctm/move-modifiers move-vector)) (cond-> (dwm/create-modif-tree ids (ctm/move-modifiers move-vector))
(not nesting-loop?) (and (not nesting-loop?) (not is-component-copy?))
(dwm/build-change-frame-modifiers objects selected target-frame drop-index cell-data) (dwm/build-change-frame-modifiers objects selected target-frame drop-index cell-data)
:always :always
(dwm/set-modifiers false false {:snap-ignore-axis snap-ignore-axis})))))) (dwm/set-modifiers false false {:snap-ignore-axis snap-ignore-axis}))))))
@ -820,7 +821,7 @@
shape-ids-to-detach shape-ids-to-detach
(reduce (fn [result shape] (reduce (fn [result shape]
(if (and (some? shape) (ctk/in-component-copy-not-root? shape)) (if (and (some? shape) (ctk/in-component-copy-not-head? shape))
(let [shape-component (ctn/get-component-shape objects shape)] (let [shape-component (ctn/get-component-shape objects shape)]
(if (= (:id frame-component) (:id shape-component)) (if (= (:id frame-component) (:id shape-component))
result result

View file

@ -573,3 +573,6 @@
(defn workspace-preview-blend-by-id [id] (defn workspace-preview-blend-by-id [id]
(l/derived (l/key id) workspace-preview-blend =)) (l/derived (l/key id) workspace-preview-blend =))
(def specialized-panel
(l/derived :specialized-panel st/state))

View file

@ -118,7 +118,9 @@
on-drag-start on-drag-start
(fn [event] (fn [event]
(if (or disabled (not draggable?)) (if (or disabled (not draggable?))
(dom/prevent-default event) (do
(dom/stop-propagation event)
(dom/prevent-default event))
(do (do
(dom/stop-propagation event) (dom/stop-propagation event)
(dnd/set-data! event data-type data) (dnd/set-data! event data-type data)
@ -178,7 +180,8 @@
on-mount on-mount
(fn [] (fn []
(let [dom (mf/ref-val ref)] (let [dom (mf/ref-val ref)]
(.setAttribute dom "draggable" draggable?) (.setAttribute dom "draggable" true) ;; In firefox it needs to be draggable for problems with event handling.
;; It will stop the drag operation in on-drag-start
;; Register all events in the (default) bubble mode, so that they ;; Register all events in the (default) bubble mode, so that they
;; are captured by the most leaf item. The handler will stop ;; are captured by the most leaf item. The handler will stop

View file

@ -444,7 +444,7 @@
has-component? (some true? (map #(ctk/instance-head? %) shapes)) has-component? (some true? (map #(ctk/instance-head? %) shapes))
is-component? (and single? (-> shapes first :component-id some?)) is-component? (and single? (-> shapes first :component-id some?))
in-copy-not-root? (some true? (map #(ctk/in-component-copy-not-root? %) shapes)) in-copy-not-root? (some true? (map #(ctk/in-component-copy-not-head? %) shapes))
objects (deref refs/workspace-page-objects) objects (deref refs/workspace-page-objects)
touched? (and single? (cph/component-touched? objects (:id (first shapes)))) touched? (and single? (cph/component-touched? objects (:id (first shapes))))

View file

@ -10,6 +10,8 @@
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.common.types.component :as ctk]
[app.common.types.container :as ctn]
[app.common.types.shape.layout :as ctl] [app.common.types.shape.layout :as ctl]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.main.data.workspace :as dw] [app.main.data.workspace :as dw]
@ -145,10 +147,15 @@
(mf/use-fn (mf/use-fn
(mf/deps id index objects) (mf/deps id index objects)
(fn [side _data] (fn [side _data]
(if (= side :center) (let [to-index (cond
(st/emit! (dw/relocate-selected-shapes id 0)) (= side :center) 0
(let [to-index (if (= side :top) (inc index) index) (= side :top) (inc index)
parent-id (cph/get-parent-id objects id)] :else index)
parent-id (if (= side :center)
id
(cph/get-parent-id objects id))
parent (get objects parent-id)]
(when-not (ctk/in-component-copy? parent) ;; We don't want to change the structure of component copies
(st/emit! (dw/relocate-selected-shapes parent-id to-index)))))) (st/emit! (dw/relocate-selected-shapes parent-id to-index))))))
on-hold on-hold
@ -176,7 +183,10 @@
:data {:id (:id item) :data {:id (:id item)
:index index :index index
:name (:name item)} :name (:name item)}
:draggable? (and sortable? (not read-only?))) :draggable? (and
sortable?
(not read-only?)
(not (ctn/has-any-copy-parent? objects item)))) ;; We don't want to change the structure of component copies
ref (mf/use-ref) ref (mf/use-ref)
depth (+ depth 1) depth (+ depth 1)

View file

@ -21,6 +21,7 @@
[app.main.ui.viewer.inspect.right-sidebar :as hrs] [app.main.ui.viewer.inspect.right-sidebar :as hrs]
[app.main.ui.workspace.sidebar.options.menus.align :refer [align-options]] [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.bool :refer [bool-options]]
[app.main.ui.workspace.sidebar.options.menus.component :refer [component-menu]]
[app.main.ui.workspace.sidebar.options.menus.exports :refer [exports-menu]] [app.main.ui.workspace.sidebar.options.menus.exports :refer [exports-menu]]
[app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell] [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.interactions :refer [interactions-menu]]
@ -67,6 +68,13 @@
:page-id page-id :page-id page-id
:file-id file-id}]])) :file-id file-id}]]))
(mf/defc specialized-panel
{::mf/wrap [mf/memo]}
[{:keys [panel]}]
(when (= (:type panel) :component-swap)
[:& component-menu {:shape (first (:shapes panel)) :swap-opened? true}]))
(mf/defc options-content (mf/defc options-content
{::mf/wrap [mf/memo]} {::mf/wrap [mf/memo]}
[{:keys [selected section shapes shapes-with-children page-id file-id on-change-section on-expand]}] [{:keys [selected section shapes shapes-with-children page-id file-id on-change-section on-expand]}]
@ -76,6 +84,7 @@
shared-libs (mf/deref refs/workspace-libraries) shared-libs (mf/deref refs/workspace-libraries)
edition (mf/deref refs/selected-edition) edition (mf/deref refs/selected-edition)
grid-edition (mf/deref refs/workspace-grid-edition) grid-edition (mf/deref refs/workspace-grid-edition)
sp-panel (mf/deref refs/specialized-panel)
selected-shapes (into [] (keep (d/getf objects)) selected) selected-shapes (into [] (keep (d/getf objects)) selected)
first-selected-shape (first selected-shapes) first-selected-shape (first selected-shapes)
@ -116,6 +125,9 @@
{:ids [edition] {:ids [edition]
:values (get objects edition)}] :values (get objects edition)}]
(not (nil? sp-panel))
[:& specialized-panel {:panel sp-panel}]
(d/not-empty? drawing) (d/not-empty? drawing)
[:& shape-options [:& shape-options
{:shape (:object drawing) {:shape (:object drawing)
@ -177,6 +189,10 @@
{:ids [edition] {:ids [edition]
:values (get objects edition)}] :values (get objects edition)}]
sp-panel
[:& specialized-panel {:panel sp-panel}]
(d/not-empty? drawing) (d/not-empty? drawing)
[:& shape-options [:& shape-options
{:shape (:object drawing) {:shape (:object drawing)

View file

@ -11,10 +11,13 @@
[app.common.types.component :as ctk] [app.common.types.component :as ctk]
[app.common.types.components-list :as ctkl] [app.common.types.components-list :as ctkl]
[app.common.types.file :as ctf] [app.common.types.file :as ctf]
[app.common.uuid :as uuid]
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
[app.main.data.workspace :as dw] [app.main.data.workspace :as dw]
[app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.specialized-panel :as dwsp]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.render :refer [component-svg]]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.context-menu :refer [context-menu]] [app.main.ui.components.context-menu :refer [context-menu]]
[app.main.ui.components.dropdown :refer [dropdown]] [app.main.ui.components.dropdown :refer [dropdown]]
@ -23,6 +26,7 @@
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
[app.util.keyboard :as kbd]
[cuerdas.core :as str] [cuerdas.core :as str]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
@ -144,9 +148,153 @@
(when (or @editing? creating?) (when (or @editing? creating?)
[:div.counter (str @size "/300")])]]))) [:div.counter (str @size "/300")])]])))
(mf/defc component-swap
[{:keys [shapes] :as props}]
(let [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)
workspace-data (deref refs/workspace-data)
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))
filters* (mf/use-state
{:term ""
:file-id (:component-file shape)
:path (cph/prev-path (:name shape))})
filters (deref filters*)
components (-> (get-in libraries [(:file-id filters) :data :components])
vals)
components (if (str/empty? (:term filters))
components
(filter #(str/includes? (str/lower (:name %)) (str/lower (:term filters))) components))
groups (->> (map :path components)
(filter #(= (cph/prev-path (:path %)) (:path filters)))
(remove str/empty?)
distinct
(map #(hash-map :name %)))
components (filter #(= (:path %) (:path filters)) components)
items (->> (concat groups components)
(sort-by :name))
;; Get the ids of the components and its root-shapes that are parents of the current shape, to avoid loops
get-comps-ids (fn get-comps-ids [shape ids]
(if (uuid/zero? (:id shape))
ids
(let [ids (if (ctk/instance-head? shape)
(conj ids (:id shape) (:component-id shape))
ids)]
(get-comps-ids (get objects (:parent-id shape)) ids))))
parent-components (set (get-comps-ids (get objects (:parent-id shape)) []))
on-library-change
(mf/use-fn
(fn [event]
(let [value (or (-> (dom/get-target event)
(dom/get-value))
(as-> (dom/get-current-target event) $
(dom/get-attribute $ "data-test")))
value (uuid/uuid value)]
(swap! filters* assoc :file-id value :term "" :path ""))))
on-search-term-change
(mf/use-fn
(mf/deps new-css-system)
(fn [event]
;; NOTE: When old-css-system is removed this function will recibe value and event
;; Let won't be necessary any more
(let [value (if ^boolean new-css-system
event
(dom/get-target-val event))]
(swap! filters* assoc :term value))))
on-search-clear-click
(mf/use-fn #(swap! filters* assoc :term ""))
on-go-back
(mf/use-fn
(mf/deps (:path filters))
#(swap! filters* assoc :path (cph/prev-path (:path filters))))
on-enter-group
(mf/use-fn #(swap! filters* assoc :path %))
handle-key-down
(mf/use-fn
(fn [event]
(let [enter? (kbd/enter? event)
esc? (kbd/esc? event)
node (dom/event->target event)]
(when ^boolean enter? (dom/blur! node))
(when ^boolean esc? (dom/blur! node)))))]
[:div.component-swap
[:div.element-set-title
[:span (tr "workspace.options.component.swap")]]
[:div.component-swap-content
[:div.search-block
[:input.search-input
{:placeholder (str (tr "labels.search") " " (get-in libraries [(:file-id filters) :name]))
:type "text"
:value (:term filters)
:on-change on-search-term-change
:on-key-down handle-key-down}]
(if ^boolean (str/empty? (:term filters))
[:div.search-icon
i/search]
[:div.search-icon.close
{:on-click on-search-clear-click}
i/close])]
[:select.input-select {:value (:file-id filters)
:data-mousetrap-dont-stop true
:on-change on-library-change}
(for [library (vals libraries)]
[:option {:key (:id library) :value (:id library)} (:name library)])]
(when-not (str/empty? (:path filters))
[:div.component-path {:on-click on-go-back}
[:span i/arrow-slide]
[:span (-> (cph/split-path (:path filters))
last)]])
[:div.component-list
(for [item items]
(if (:id item)
(let [data (get-in libraries [(:file-id filters) :data])
container (ctf/get-component-page data item)
root-shape (ctf/get-component-root data item)
loop? (or (contains? parent-components (:main-instance-id item))
(contains? parent-components (:id item)))]
[:div.component-item
{:class (stl/css-case :disabled loop?)
:key (:id item)
:on-click #(when-not loop?
(st/emit!
(dwl/component-swap shape (:file-id filters) (:id item))))}
[:& component-svg {:root-shape root-shape
:objects (:objects container)}]
[:span.component-name
{:class (stl/css-case :selected (= (:id item) (:component-id shape)))}
(:name item)]])
[:div.component-group {:key (uuid/next) :on-click #(on-enter-group (:name item))}
[:span (:name item)]
[:span i/arrow-slide]]))]]]))
(mf/defc component-menu (mf/defc component-menu
[{:keys [ids values shape] :as props}] [{:keys [shape swap-opened?] :as props}]
(let [new-css-system (mf/use-ctx ctx/new-css-system) (let [[ids values] [[(:id shape)] (select-keys shape component-attrs)]
new-css-system (mf/use-ctx ctx/new-css-system)
current-file-id (mf/use-ctx ctx/current-file-id) current-file-id (mf/use-ctx ctx/current-file-id)
components-v2 (mf/use-ctx ctx/components-v2) components-v2 (mf/use-ctx ctx/components-v2)
@ -171,6 +319,7 @@
main-instance? (if components-v2 main-instance? (if components-v2
(ctk/main-instance? values) (ctk/main-instance? values)
true) true)
can-swap? (and components-v2 (not main-instance?))
main-component? (:main-instance values) main-component? (:main-instance values)
lacks-annotation? (nil? (:annotation values)) lacks-annotation? (nil? (:annotation values))
@ -342,13 +491,20 @@
[:& component-annotation {:id id :values values :shape shape :component component}])])] [:& component-annotation {:id id :values values :shape shape :component component}])])]
[:div.element-set [:div.element-set
[:div.element-set-title [:div.element-set-title {:class (stl/css-case :back swap-opened?)
[:span (tr "workspace.options.component")] :on-click #(when swap-opened? (st/emit! :interrupt))}
[:div
(when swap-opened?
[:span
i/arrow-slide])
[:span (tr "workspace.options.component")]]
[:span (if main-instance? [:span (if main-instance?
(tr "workspace.options.component.main") (tr "workspace.options.component.main")
(tr "workspace.options.component.copy"))]] (tr "workspace.options.component.copy"))]]
[:div.element-set-content [:div.element-set-content
[:div.row-flex.component-row [: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? (if main-instance?
i/component i/component
i/component-copy) i/component-copy)
@ -391,7 +547,14 @@
[(tr "workspace.shape.menu.reset-overrides") do-reset-component]) [(tr "workspace.shape.menu.reset-overrides") do-reset-component])
(when can-update-main? (when can-update-main?
[(tr "workspace.shape.menu.update-main") do-update-remote-component]) [(tr "workspace.shape.menu.update-main") do-update-remote-component])
[(tr "workspace.shape.menu.go-main") do-navigate-component-file]])))}]]] [(tr "workspace.shape.menu.go-main") do-navigate-component-file]])))}]]
(when components-v2 (when can-swap?
[:div.component-parent-name
(cph/merge-path-item (:path component) (:name component))])]
(when swap-opened?
[:& component-swap {:shapes [shape]}])
(when (and (not swap-opened?) components-v2)
[:& component-annotation {:id id :values values :shape shape :component component}])]])))) [:& component-annotation {:id id :values values :shape shape :component component}])]]))))

View file

@ -12,7 +12,7 @@
[app.main.ui.hooks :as hooks] [app.main.ui.hooks :as hooks]
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [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.color-selection :refer [color-selection-menu]]
[app.main.ui.workspace.sidebar.options.menus.component :refer [component-attrs 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.constraints :refer [constraint-attrs constraints-menu]]
[app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs-shape fill-menu]] [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs-shape fill-menu]]
[app.main.ui.workspace.sidebar.options.menus.frame-grid :refer [frame-grid]] [app.main.ui.workspace.sidebar.options.menus.frame-grid :refer [frame-grid]]
@ -36,7 +36,6 @@
constraint-values (select-keys shape constraint-attrs) constraint-values (select-keys shape constraint-attrs)
layout-container-values (select-keys shape layout-container-flex-attrs) layout-container-values (select-keys shape layout-container-flex-attrs)
layout-item-values (select-keys shape layout-item-attrs) layout-item-values (select-keys shape layout-item-attrs)
[comp-ids comp-values] [[(:id shape)] (select-keys shape component-attrs)]
ids (hooks/use-equal-memo ids) ids (hooks/use-equal-memo ids)
@ -63,9 +62,7 @@
:values measure-values :values measure-values
:type type :type type
:shape shape}] :shape shape}]
[:& component-menu {:ids comp-ids [:& component-menu {:shape shape}]
:values comp-values
:shape shape}]
(when (or (not is-layout-child?) is-layout-child-absolute?) (when (or (not is-layout-child?) is-layout-child-absolute?)
[:& constraints-menu {:ids ids [:& constraints-menu {:ids ids
:values constraint-values}]) :values constraint-values}])

View file

@ -14,7 +14,7 @@
[app.main.ui.hooks :as hooks] [app.main.ui.hooks :as hooks]
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [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.color-selection :refer [color-selection-menu]]
[app.main.ui.workspace.sidebar.options.menus.component :refer [component-attrs component-menu]] [app.main.ui.workspace.sidebar.options.menus.component :refer [component-menu]]
[app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraints-menu]] [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraints-menu]]
[app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-menu]] [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-menu]]
[app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell] [app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell]
@ -66,7 +66,6 @@
[stroke-ids stroke-values] (get-attrs [shape] objects :stroke) [stroke-ids stroke-values] (get-attrs [shape] objects :stroke)
[text-ids text-values] (get-attrs [shape] objects :text) [text-ids text-values] (get-attrs [shape] objects :text)
[svg-ids svg-values] [[(:id shape)] (select-keys shape [:svg-attrs])] [svg-ids svg-values] [[(:id shape)] (select-keys shape [:svg-attrs])]
[comp-ids comp-values] [[(:id shape)] (select-keys shape component-attrs)]
[layout-item-ids layout-item-values] (get-attrs [shape] objects :layout-item)] [layout-item-ids layout-item-values] (get-attrs [shape] objects :layout-item)]
@ -74,7 +73,7 @@
:options true)} :options true)}
[:& layer-menu {:type type :ids layer-ids :values layer-values}] [:& layer-menu {:type type :ids layer-ids :values layer-values}]
[:& measures-menu {:type type :ids measure-ids :values measure-values :shape shape}] [:& measures-menu {:type type :ids measure-ids :values measure-values :shape shape}]
[:& component-menu {:ids comp-ids :values comp-values :shape shape}] ;;remove this in components-v2 [:& component-menu {:shape shape}] ;;remove this in components-v2
[:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values :multiple false}] [:& 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?) (when (and (= (count ids) 1) is-layout-child? is-grid-parent?)

View file

@ -18,6 +18,7 @@
[app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.media :as dwm] [app.main.data.workspace.media :as dwm]
[app.main.data.workspace.path :as dwdp] [app.main.data.workspace.path :as dwdp]
[app.main.data.workspace.specialized-panel :as dwsp]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.streams :as ms] [app.main.streams :as ms]
@ -84,7 +85,8 @@
left-click? left-click?
(do (do
(st/emit! (ms/->MouseEvent :down ctrl? shift? alt? meta?)) (st/emit! (ms/->MouseEvent :down ctrl? shift? alt? meta?)
dwsp/clear-specialized-panel)
(when (and (not= edition id) (or text-editing? grid-editing?)) (when (and (not= edition id) (or text-editing? grid-editing?))
(st/emit! dw/clear-edition-mode)) (st/emit! dw/clear-edition-mode))

View file

@ -109,32 +109,27 @@
store (the/prepare-store state done store (the/prepare-store state done
(fn [new-state] (fn [new-state]
;; Expected shape tree: ;; Expected shape tree:
;; ;; [Page: Page 1]
;; [Page]
;; Root Frame ;; Root Frame
;; Rect 1 ;; {Rect 1}
;; Rect 1 ;; Rect1
;; Rect 1* #--> Rect 1 ;; Rect 1 #--> Rect 1
;; #{:shapes-group}
;; Circle 1
;; Rect 1 ---> Rect 1 ;; Rect 1 ---> Rect 1
;; Circle 1
;; ;;
;; [Rect 1] ;; [Component: Rect 1] core.cljs:200:23
;; page1 / Rect 1 ;; --> [Page 1] Rect 1
;;
(let [[[group shape1 shape2] [c-group c-shape1] _component] (let [[[group shape1] [c-group c-shape1] _component]
(thl/resolve-instance-and-main-allow-dangling (thl/resolve-instance-and-main-allow-dangling
new-state new-state
(thp/id :instance1))] (thp/id :instance1))]
(t/is (= (:name group) "Rect 1")) (t/is (= (:name group) "Rect 1"))
(t/is (= (:touched group) #{:shapes-group})) (t/is (nil? (:touched group)))
(t/is (= (:name shape1) "Circle 1")) (t/is (= (:name shape1) "Rect 1"))
(t/is (= (:touched shape1) nil)) (t/is (= (:touched shape1) nil))
(t/is (= (:shape-ref shape1) nil)) (t/is (not= (:shape-ref shape1) nil))
(t/is (= (:name shape2) "Rect 1"))
(t/is (= (:touched shape2) nil))
(t/is (not= (:shape-ref shape2) nil))
(t/is (= (:name c-group) "Rect 1")) (t/is (= (:name c-group) "Rect 1"))
(t/is (= (:touched c-group) nil)) (t/is (= (:touched c-group) nil))
@ -145,7 +140,7 @@
(ptk/emit! (ptk/emit!
store store
(dw/relocate-shapes #{(:id shape2)} (:id instance1) 0) (dw/relocate-shapes #{(:id shape2)} (:id instance1) 0) ;; We cant't change the structure of component copies, so this operation will do nothing
:the/end)))) :the/end))))
(t/deftest test-touched-children-delete (t/deftest test-touched-children-delete
@ -236,22 +231,22 @@
store (the/prepare-store state done store (the/prepare-store state done
(fn [new-state] (fn [new-state]
;; Expected shape tree: ;; Expected shape tree:
;; ;; [Page: Page 1]
;; [Page]
;; Root Frame ;; Root Frame
;; Component 1 ;; {Component 1} #
;; Rect 1 ;; Rect 1
;; Rect 2 ;; Rect 2
;; Rect 3 ;; Rect 3
;; Component 1* #--> Component 1 ;; Component 1 #--> Component 1
;; #{:shapes-group}
;; Rect 2 ---> Rect 2
;; Rect 1 ---> Rect 1 ;; Rect 1 ---> Rect 1
;; Rect 2 ---> Rect 2
;; Rect 3 ---> Rect 3 ;; Rect 3 ---> Rect 3
;; ;;
;; [Component 1] ;; ========= Local library
;; page1 / Component 1
;; ;;
;; [Component: Component 1]
;; --> [Page 1] Component 1
(let [[[group shape1 shape2 shape3] (let [[[group shape1 shape2 shape3]
[c-group c-shape1 c-shape2 c-shape3] _component] [c-group c-shape1 c-shape2 c-shape3] _component]
(thl/resolve-instance-and-main-allow-dangling (thl/resolve-instance-and-main-allow-dangling
@ -259,11 +254,11 @@
(thp/id :instance1))] (thp/id :instance1))]
(t/is (= (:name group) "Component 1")) (t/is (= (:name group) "Component 1"))
(t/is (= (:touched group) #{:shapes-group})) (t/is (nil? (:touched group)))
(t/is (= (:name shape1) "Rect 2")) (t/is (= (:name shape1) "Rect 1"))
(t/is (= (:touched shape1) nil)) (t/is (= (:touched shape1) nil))
(t/is (not= (:shape-ref shape1) nil)) (t/is (not= (:shape-ref shape1) nil))
(t/is (= (:name shape2) "Rect 1")) (t/is (= (:name shape2) "Rect 2"))
(t/is (= (:touched shape2) nil)) (t/is (= (:touched shape2) nil))
(t/is (not= (:shape-ref shape2) nil)) (t/is (not= (:shape-ref shape2) nil))
(t/is (= (:name shape3) "Rect 3")) (t/is (= (:name shape3) "Rect 3"))
@ -285,7 +280,7 @@
(ptk/emit! (ptk/emit!
store store
(dw/relocate-shapes #{(:id shape1')} (:id group1') 2) (dw/relocate-shapes #{(:id shape1')} (:id group1') 2) ;; We cant't change the structure of component copies, so this operation will do nothing
:the/end)))) :the/end))))
(t/deftest test-touched-from-lib (t/deftest test-touched-from-lib
@ -1531,35 +1526,35 @@
(fn [new-state] (fn [new-state]
;; Expected shape tree: ;; Expected shape tree:
;; ;;
;; [Page] ;; [Page: Page 1]
;; Root Frame ;; Root Frame
;; {Rect 1} #
;; Rect 1 ;; Rect 1
;; Rect 1 #--> Rect 1
;; Rect 1 ---> Rect 1
;; Rect 1 #--> Rect 1
;; Rect 1 ---> Rect 1
;; Circle 1 ;; Circle 1
;; Rect 1
;; Rect 1 #--> Rect 1
;; Circle 1 ---> Circle 1
;; Rect 1 ---> Rect 1
;; Rect 1 #--> Rect 1
;; Circle 1 ---> Circle 1
;; Rect 1 ---> Rect 1
;; ;;
;; [Rect 1] ;; ========= Local library
;; page1 / Rect 1
;; ;;
(let [[[main1 shape1 shape2] ;; [Component: Rect 1]
[c-main1 c-shape1 c-shape2] component1] ;; --> [Page 1] Rect 1
;;
(let [[[main1 shape1]
[c-main1 c-shape1] component1]
(thl/resolve-instance-and-main (thl/resolve-instance-and-main
new-state new-state
(thp/id :main1)) (thp/id :main1))
[[instance1 shape3 shape4] [[instance1 shape2]
[c-instance1 c-shape3 c-shape4] component2] [c-instance1 c-shape2] component2]
(thl/resolve-instance-and-main (thl/resolve-instance-and-main
new-state new-state
(thp/id :instance1)) (thp/id :instance1))
[[instance2 shape5 shape6] [[instance2 shape3]
[c-instance2 c-shape5 c-shape6] component3] [c-instance2 c-shape3] component3]
(thl/resolve-instance-and-main (thl/resolve-instance-and-main
new-state new-state
(thp/id :instance2))] (thp/id :instance2))]
@ -1567,47 +1562,35 @@
(t/is (= (:name main1) "Rect 1")) (t/is (= (:name main1) "Rect 1"))
(t/is (= (:touched main1) nil)) (t/is (= (:touched main1) nil))
(t/is (= (:shape-ref main1) nil)) (t/is (= (:shape-ref main1) nil))
(t/is (= (:name shape1) "Circle 1")) (t/is (= (:name shape1) "Rect 1"))
(t/is (= (:touched shape1) nil)) (t/is (= (:touched shape1) nil))
(t/is (= (:shape-ref shape1) nil)) (t/is (= (:shape-ref shape1) nil))
(t/is (= (:name shape2) "Rect 1"))
(t/is (= (:touched shape2) nil))
(t/is (= (:shape-ref shape2) nil))
(t/is (= (:name instance1) "Rect 1")) (t/is (= (:name instance1) "Rect 1"))
(t/is (= (:touched instance1) nil)) (t/is (= (:touched instance1) nil))
(t/is (= (:shape-ref instance1) (:id c-main1))) (t/is (= (:shape-ref instance1) (:id c-main1)))
(t/is (= (:name shape3) "Circle 1")) (t/is (= (:name shape2) "Rect 1"))
(t/is (= (:touched shape3) nil)) (t/is (= (:touched shape2) nil))
(t/is (= (:shape-ref shape3) (:id c-shape1))) (t/is (= (:shape-ref shape2) (:id c-shape1)))
(t/is (= (:name shape4) "Rect 1"))
(t/is (= (:touched shape4) nil))
(t/is (= (:shape-ref shape4) (:id c-shape2)))
(t/is (= (:name instance2) "Rect 1")) (t/is (= (:name instance2) "Rect 1"))
(t/is (= (:touched instance2) nil)) (t/is (= (:touched instance2) nil))
(t/is (= (:shape-ref instance2) (:id c-main1))) (t/is (= (:shape-ref instance2) (:id c-main1)))
(t/is (= (:name shape5) "Circle 1")) (t/is (= (:name shape3) "Rect 1"))
(t/is (= (:touched shape5) nil)) (t/is (= (:touched shape3) nil))
(t/is (= (:shape-ref shape5) (:id c-shape1))) (t/is (= (:shape-ref shape2) (:id c-shape1)))
(t/is (= (:name shape6) "Rect 1"))
(t/is (= (:touched shape6) nil))
(t/is (= (:shape-ref shape4) (:id c-shape2)))
(t/is (= component1 component2 component3)) (t/is (= component1 component2 component3))
(t/is (= c-main1 main1)) (t/is (= c-main1 main1))
(t/is (= c-shape1 shape1)) (t/is (= c-shape1 shape1))
(t/is (= c-shape2 shape2))
(t/is (= c-instance1 c-main1)) (t/is (= c-instance1 c-main1))
(t/is (= c-shape3 c-shape1)) (t/is (= c-shape2 c-shape1))
(t/is (= c-shape4 c-shape2))
(t/is (= c-instance2 c-main1)) (t/is (= c-instance2 c-main1))
(t/is (= c-shape5 c-shape1)) (t/is (= c-shape3 c-shape1)))))]
(t/is (= c-shape6 c-shape2)))))]
(ptk/emit! (ptk/emit!
store store
(dw/relocate-shapes #{(:id shape2)} (:id instance1) 0) (dw/relocate-shapes #{(:id shape2)} (:id instance1) 0) ;; We cant't change the structure of component copies, so this operation will do nothing
(dwl/update-component-sync (:id instance1) (:id file)) (dwl/update-component-sync (:id instance1) (:id file))
:the/end)))) :the/end))))
@ -1753,16 +1736,16 @@
;; [Page] ;; [Page]
;; Root Frame ;; Root Frame
;; Component 1 ;; Component 1
;; Rect 2
;; Rect 1 ;; Rect 1
;; Rect 2
;; Rect 3 ;; Rect 3
;; Component 1 #--> Component 1 ;; Component 1 #--> Component 1
;; Rect 2 ---> Rect 2
;; Rect 1 ---> Rect 1 ;; Rect 1 ---> Rect 1
;; Rect 2 ---> Rect 2
;; Rect 3 ---> Rect 3 ;; Rect 3 ---> Rect 3
;; Component 1 #--> Component 1 ;; Component 1 #--> Component 1
;; Rect 2 ---> Rect 2
;; Rect 1 ---> Rect 1 ;; Rect 1 ---> Rect 1
;; Rect 2 ---> Rect 2
;; Rect 3 ---> Rect 3 ;; Rect 3 ---> Rect 3
;; ;;
;; [Component 1] ;; [Component 1]
@ -1789,10 +1772,10 @@
(t/is (= (:name main1) "Component 1")) (t/is (= (:name main1) "Component 1"))
(t/is (= (:touched main1) nil)) (t/is (= (:touched main1) nil))
(t/is (= (:shape-ref main1) nil)) (t/is (= (:shape-ref main1) nil))
(t/is (= (:name shape1) "Rect 2")) (t/is (= (:name shape1) "Rect 1"))
(t/is (= (:touched shape1) nil)) (t/is (= (:touched shape1) nil))
(t/is (= (:shape-ref shape1) nil)) (t/is (= (:shape-ref shape1) nil))
(t/is (= (:name shape2) "Rect 1")) (t/is (= (:name shape2) "Rect 2"))
(t/is (= (:touched shape2) nil)) (t/is (= (:touched shape2) nil))
(t/is (= (:shape-ref shape2) nil)) (t/is (= (:shape-ref shape2) nil))
(t/is (= (:name shape3) "Rect 3")) (t/is (= (:name shape3) "Rect 3"))
@ -1802,10 +1785,10 @@
(t/is (= (:name instance1) "Component 1")) (t/is (= (:name instance1) "Component 1"))
(t/is (= (:touched instance1) nil)) (t/is (= (:touched instance1) nil))
(t/is (= (:shape-ref instance1) (:id c-main1))) (t/is (= (:shape-ref instance1) (:id c-main1)))
(t/is (= (:name shape4) "Rect 2")) (t/is (= (:name shape4) "Rect 1"))
(t/is (= (:touched shape4) nil)) (t/is (= (:touched shape4) nil))
(t/is (= (:shape-ref shape4) (:id c-shape1))) (t/is (= (:shape-ref shape4) (:id c-shape1)))
(t/is (= (:name shape5) "Rect 1")) (t/is (= (:name shape5) "Rect 2"))
(t/is (= (:touched shape5) nil)) (t/is (= (:touched shape5) nil))
(t/is (= (:shape-ref shape5) (:id c-shape2))) (t/is (= (:shape-ref shape5) (:id c-shape2)))
(t/is (= (:name shape6) "Rect 3")) (t/is (= (:name shape6) "Rect 3"))
@ -1815,10 +1798,10 @@
(t/is (= (:name instance2) "Component 1")) (t/is (= (:name instance2) "Component 1"))
(t/is (= (:touched instance2) nil)) (t/is (= (:touched instance2) nil))
(t/is (= (:shape-ref instance2) (:id c-main1))) (t/is (= (:shape-ref instance2) (:id c-main1)))
(t/is (= (:name shape7) "Rect 2")) (t/is (= (:name shape7) "Rect 1"))
(t/is (= (:touched shape7) nil)) (t/is (= (:touched shape7) nil))
(t/is (= (:shape-ref shape7) (:id c-shape1))) (t/is (= (:shape-ref shape7) (:id c-shape1)))
(t/is (= (:name shape8) "Rect 1")) (t/is (= (:name shape8) "Rect 2"))
(t/is (= (:touched shape8) nil)) (t/is (= (:touched shape8) nil))
(t/is (= (:shape-ref shape8) (:id c-shape2))) (t/is (= (:shape-ref shape8) (:id c-shape2)))
(t/is (= (:name shape9) "Rect 3")) (t/is (= (:name shape9) "Rect 3"))

View file

@ -1673,6 +1673,9 @@ msgstr "(you)"
msgid "labels.your-account" msgid "labels.your-account"
msgstr "Your account" msgstr "Your account"
msgid "labels.search"
msgstr "Search"
#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs #: src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs
msgid "media.loading" msgid "media.loading"
msgstr "Loading image…" msgstr "Loading image…"
@ -3590,6 +3593,9 @@ msgstr "Edit an annotation"
msgid "workspace.options.component.main" msgid "workspace.options.component.main"
msgstr "Main" msgstr "Main"
msgid "workspace.options.component.swap"
msgstr "Swap component"
#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs #: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
msgid "workspace.options.constraints" msgid "workspace.options.constraints"
msgstr "Constraints" msgstr "Constraints"

View file

@ -1725,6 +1725,9 @@ msgstr "(tú)"
msgid "labels.your-account" msgid "labels.your-account"
msgstr "Tu cuenta" msgstr "Tu cuenta"
msgid "labels.search"
msgstr "Buscar"
#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs #: src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs
msgid "media.loading" msgid "media.loading"
msgstr "Cargando imagen…" msgstr "Cargando imagen…"
@ -3666,6 +3669,9 @@ msgstr "Editar una nota"
msgid "workspace.options.component.main" msgid "workspace.options.component.main"
msgstr "Principal" msgstr "Principal"
msgid "workspace.options.component.swap"
msgstr "Intercambiar componente"
#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs #: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
msgid "workspace.options.constraints" msgid "workspace.options.constraints"
msgstr "Restricciones" msgstr "Restricciones"