mirror of
https://github.com/penpot/penpot.git
synced 2025-12-12 06:24:17 +01:00
🎉 Add the ability to create variants from a selection (#7045)
* 🎉 Add the ability to create variants from a selection * 📎 Add PR feedback changes * 💄 Add minor cosmetic changes --------- Co-authored-by: Andrey Antukh <niwi@niwi.nz>
This commit is contained in:
@@ -17,7 +17,6 @@
|
|||||||
[app.common.geom.proportions :as gpp]
|
[app.common.geom.proportions :as gpp]
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes :as gsh]
|
||||||
[app.common.logging :as log]
|
[app.common.logging :as log]
|
||||||
[app.common.logic.shapes :as cls]
|
|
||||||
[app.common.transit :as t]
|
[app.common.transit :as t]
|
||||||
[app.common.types.component :as ctc]
|
[app.common.types.component :as ctc]
|
||||||
[app.common.types.fills :as types.fills]
|
[app.common.types.fills :as types.fills]
|
||||||
@@ -39,7 +38,6 @@
|
|||||||
[app.main.data.project :as dpj]
|
[app.main.data.project :as dpj]
|
||||||
[app.main.data.workspace.bool :as dwb]
|
[app.main.data.workspace.bool :as dwb]
|
||||||
[app.main.data.workspace.clipboard :as dwcp]
|
[app.main.data.workspace.clipboard :as dwcp]
|
||||||
[app.main.data.workspace.collapse :as dwco]
|
|
||||||
[app.main.data.workspace.colors :as dwcl]
|
[app.main.data.workspace.colors :as dwcl]
|
||||||
[app.main.data.workspace.comments :as dwcm]
|
[app.main.data.workspace.comments :as dwcm]
|
||||||
[app.main.data.workspace.common :as dwc]
|
[app.main.data.workspace.common :as dwc]
|
||||||
@@ -683,52 +681,13 @@
|
|||||||
|
|
||||||
;; --- Change Shape Order (D&D Ordering)
|
;; --- Change Shape Order (D&D Ordering)
|
||||||
|
|
||||||
(defn relocate-shapes
|
|
||||||
[ids parent-id to-index & [ignore-parents?]]
|
|
||||||
(dm/assert! (every? uuid? ids))
|
|
||||||
(dm/assert! (set? ids))
|
|
||||||
(dm/assert! (uuid? parent-id))
|
|
||||||
(dm/assert! (number? to-index))
|
|
||||||
|
|
||||||
(ptk/reify ::relocate-shapes
|
|
||||||
ptk/WatchEvent
|
|
||||||
(watch [it state _]
|
|
||||||
(let [page-id (:current-page-id state)
|
|
||||||
objects (dsh/lookup-page-objects state page-id)
|
|
||||||
data (dsh/lookup-file-data state)
|
|
||||||
|
|
||||||
;; Ignore any shape whose parent is also intended to be moved
|
|
||||||
ids (cfh/clean-loops objects ids)
|
|
||||||
|
|
||||||
;; If we try to move a parent into a child we remove it
|
|
||||||
ids (filter #(not (cfh/is-parent? objects parent-id %)) ids)
|
|
||||||
|
|
||||||
all-parents (into #{parent-id} (map #(cfh/get-parent-id objects %)) ids)
|
|
||||||
|
|
||||||
changes (-> (pcb/empty-changes it)
|
|
||||||
(pcb/with-page-id page-id)
|
|
||||||
(pcb/with-objects objects)
|
|
||||||
(pcb/with-library-data data)
|
|
||||||
(cls/generate-relocate
|
|
||||||
parent-id
|
|
||||||
to-index
|
|
||||||
ids
|
|
||||||
:ignore-parents? ignore-parents?))
|
|
||||||
undo-id (js/Symbol)]
|
|
||||||
|
|
||||||
(rx/of (dwu/start-undo-transaction undo-id)
|
|
||||||
(dch/commit-changes changes)
|
|
||||||
(dwco/expand-collapse parent-id)
|
|
||||||
(ptk/data-event :layout/update {:ids (concat all-parents ids)})
|
|
||||||
(dwu/commit-undo-transaction undo-id))))))
|
|
||||||
|
|
||||||
(defn relocate-selected-shapes
|
(defn relocate-selected-shapes
|
||||||
[parent-id to-index]
|
[parent-id to-index]
|
||||||
(ptk/reify ::relocate-selected-shapes
|
(ptk/reify ::relocate-selected-shapes
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state _]
|
(watch [_ state _]
|
||||||
(let [selected (dsh/lookup-selected state)]
|
(let [selected (dsh/lookup-selected state)]
|
||||||
(rx/of (relocate-shapes selected parent-id to-index))))))
|
(rx/of (dwsh/relocate-shapes selected parent-id to-index))))))
|
||||||
|
|
||||||
(defn start-editing-selected
|
(defn start-editing-selected
|
||||||
[]
|
[]
|
||||||
@@ -1169,7 +1128,7 @@
|
|||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state _]
|
(watch [_ state _]
|
||||||
(let [orphans (set (into [] (keys (find-orphan-shapes state))))]
|
(let [orphans (set (into [] (keys (find-orphan-shapes state))))]
|
||||||
(rx/of (relocate-shapes orphans uuid/zero 0 true))))))
|
(rx/of (dwsh/relocate-shapes orphans uuid/zero 0 true))))))
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; Sitemap
|
;; Sitemap
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
[app.main.data.comments :as dc]
|
[app.main.data.comments :as dc]
|
||||||
[app.main.data.event :as ev]
|
[app.main.data.event :as ev]
|
||||||
[app.main.data.helpers :as dsh]
|
[app.main.data.helpers :as dsh]
|
||||||
|
[app.main.data.workspace.collapse :as dwco]
|
||||||
[app.main.data.workspace.edition :as dwe]
|
[app.main.data.workspace.edition :as dwe]
|
||||||
[app.main.data.workspace.selection :as dws]
|
[app.main.data.workspace.selection :as dws]
|
||||||
[app.main.data.workspace.undo :as dwu]
|
[app.main.data.workspace.undo :as dwu]
|
||||||
@@ -249,6 +250,44 @@
|
|||||||
;; Artboard
|
;; Artboard
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
(defn create-artboard-from-shapes
|
||||||
|
([shapes id parent-id index name delta]
|
||||||
|
(create-artboard-from-shapes shapes id parent-id index name delta true))
|
||||||
|
([shapes id parent-id index name delta layout-update?]
|
||||||
|
(ptk/reify ::create-artboard-from-shapes
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [it state _]
|
||||||
|
(let [page-id (:current-page-id state)
|
||||||
|
objects (dsh/lookup-page-objects state page-id)
|
||||||
|
|
||||||
|
changes (-> (pcb/empty-changes it page-id)
|
||||||
|
(pcb/with-objects objects))
|
||||||
|
|
||||||
|
[frame-shape changes]
|
||||||
|
(cfsh/prepare-create-artboard-from-selection changes
|
||||||
|
id
|
||||||
|
parent-id
|
||||||
|
objects
|
||||||
|
shapes
|
||||||
|
index
|
||||||
|
name
|
||||||
|
false
|
||||||
|
nil
|
||||||
|
delta)
|
||||||
|
|
||||||
|
undo-id (js/Symbol)]
|
||||||
|
|
||||||
|
(when changes
|
||||||
|
(rx/of
|
||||||
|
(dwu/start-undo-transaction undo-id)
|
||||||
|
(dch/commit-changes changes)
|
||||||
|
(dws/select-shapes (d/ordered-set (:id frame-shape)))
|
||||||
|
(when layout-update? (ptk/data-event :layout/update {:ids [(:id frame-shape)]}))
|
||||||
|
(ev/event {::ev/name "create-board"
|
||||||
|
:converted-from (cfh/get-selected-type objects shapes)
|
||||||
|
:parent-type (cfh/get-shape-type objects (:parent-id frame-shape))})
|
||||||
|
(dwu/commit-undo-transaction undo-id))))))))
|
||||||
|
|
||||||
(defn create-artboard-from-selection
|
(defn create-artboard-from-selection
|
||||||
([]
|
([]
|
||||||
(create-artboard-from-selection nil))
|
(create-artboard-from-selection nil))
|
||||||
@@ -263,7 +302,7 @@
|
|||||||
([id parent-id index name delta]
|
([id parent-id index name delta]
|
||||||
(ptk/reify ::create-artboard-from-selection
|
(ptk/reify ::create-artboard-from-selection
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [it state _]
|
(watch [_ state _]
|
||||||
(let [page-id (:current-page-id state)
|
(let [page-id (:current-page-id state)
|
||||||
objects (dsh/lookup-page-objects state page-id)
|
objects (dsh/lookup-page-objects state page-id)
|
||||||
selected (->> (dsh/lookup-selected state)
|
selected (->> (dsh/lookup-selected state)
|
||||||
@@ -271,35 +310,10 @@
|
|||||||
(remove #(ctn/has-any-copy-parent? objects (get objects %)))
|
(remove #(ctn/has-any-copy-parent? objects (get objects %)))
|
||||||
(remove #(->> %
|
(remove #(->> %
|
||||||
(get objects)
|
(get objects)
|
||||||
(ctc/is-variant?))))
|
(ctc/is-variant?))))]
|
||||||
|
|
||||||
changes (-> (pcb/empty-changes it page-id)
|
|
||||||
(pcb/with-objects objects))
|
|
||||||
|
|
||||||
[frame-shape changes]
|
(rx/of (create-artboard-from-shapes selected id parent-id index name delta)))))))
|
||||||
(cfsh/prepare-create-artboard-from-selection changes
|
|
||||||
id
|
|
||||||
parent-id
|
|
||||||
objects
|
|
||||||
selected
|
|
||||||
index
|
|
||||||
name
|
|
||||||
false
|
|
||||||
nil
|
|
||||||
delta)
|
|
||||||
|
|
||||||
undo-id (js/Symbol)]
|
|
||||||
|
|
||||||
(when changes
|
|
||||||
(rx/of
|
|
||||||
(dwu/start-undo-transaction undo-id)
|
|
||||||
(dch/commit-changes changes)
|
|
||||||
(dws/select-shapes (d/ordered-set (:id frame-shape)))
|
|
||||||
(ptk/data-event :layout/update {:ids [(:id frame-shape)]})
|
|
||||||
(ev/event {::ev/name "create-board"
|
|
||||||
:converted-from (cfh/get-selected-type objects selected)
|
|
||||||
:parent-type (cfh/get-shape-type objects (:parent-id frame-shape))})
|
|
||||||
(dwu/commit-undo-transaction undo-id))))))))
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; Shape Flags
|
;; Shape Flags
|
||||||
@@ -379,3 +393,45 @@
|
|||||||
;; And finally: toggle the flag value on all the selected shapes
|
;; And finally: toggle the flag value on all the selected shapes
|
||||||
(rx/of (update-shapes selected #(update % :use-for-thumbnail not))
|
(rx/of (update-shapes selected #(update % :use-for-thumbnail not))
|
||||||
(dwu/commit-undo-transaction undo-id)))))))
|
(dwu/commit-undo-transaction undo-id)))))))
|
||||||
|
|
||||||
|
|
||||||
|
;; --- Change Shape Order (D&D Ordering)
|
||||||
|
|
||||||
|
(defn relocate-shapes
|
||||||
|
[ids parent-id to-index & [ignore-parents?]]
|
||||||
|
(dm/assert! (every? uuid? ids))
|
||||||
|
(dm/assert! (set? ids))
|
||||||
|
(dm/assert! (uuid? parent-id))
|
||||||
|
(dm/assert! (number? to-index))
|
||||||
|
|
||||||
|
(ptk/reify ::relocate-shapes
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [it state _]
|
||||||
|
(let [page-id (:current-page-id state)
|
||||||
|
objects (dsh/lookup-page-objects state page-id)
|
||||||
|
data (dsh/lookup-file-data state)
|
||||||
|
|
||||||
|
;; Ignore any shape whose parent is also intended to be moved
|
||||||
|
ids (cfh/clean-loops objects ids)
|
||||||
|
|
||||||
|
;; If we try to move a parent into a child we remove it
|
||||||
|
ids (filter #(not (cfh/is-parent? objects parent-id %)) ids)
|
||||||
|
|
||||||
|
all-parents (into #{parent-id} (map #(cfh/get-parent-id objects %)) ids)
|
||||||
|
|
||||||
|
changes (-> (pcb/empty-changes it)
|
||||||
|
(pcb/with-page-id page-id)
|
||||||
|
(pcb/with-objects objects)
|
||||||
|
(pcb/with-library-data data)
|
||||||
|
(cls/generate-relocate
|
||||||
|
parent-id
|
||||||
|
to-index
|
||||||
|
ids
|
||||||
|
:ignore-parents? ignore-parents?))
|
||||||
|
undo-id (js/Symbol)]
|
||||||
|
|
||||||
|
(rx/of (dwu/start-undo-transaction undo-id)
|
||||||
|
(dch/commit-changes changes)
|
||||||
|
(dwco/expand-collapse parent-id)
|
||||||
|
(ptk/data-event :layout/update {:ids (concat all-parents ids)})
|
||||||
|
(dwu/commit-undo-transaction undo-id))))))
|
||||||
|
|||||||
@@ -20,9 +20,11 @@
|
|||||||
[app.common.types.variant :as ctv]
|
[app.common.types.variant :as ctv]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[app.main.data.changes :as dch]
|
[app.main.data.changes :as dch]
|
||||||
|
[app.main.data.common :as dcm]
|
||||||
[app.main.data.helpers :as dsh]
|
[app.main.data.helpers :as dsh]
|
||||||
[app.main.data.workspace.colors :as cl]
|
[app.main.data.workspace.colors :as cl]
|
||||||
[app.main.data.workspace.libraries :as dwl]
|
[app.main.data.workspace.libraries :as dwl]
|
||||||
|
[app.main.data.workspace.pages :as dwpg]
|
||||||
[app.main.data.workspace.selection :as dws]
|
[app.main.data.workspace.selection :as dws]
|
||||||
[app.main.data.workspace.shape-layout :as dwsl]
|
[app.main.data.workspace.shape-layout :as dwsl]
|
||||||
[app.main.data.workspace.shapes :as dwsh]
|
[app.main.data.workspace.shapes :as dwsh]
|
||||||
@@ -338,83 +340,101 @@
|
|||||||
(defn transform-in-variant
|
(defn transform-in-variant
|
||||||
"Given the id of a main shape of a component, creates a variant structure for
|
"Given the id of a main shape of a component, creates a variant structure for
|
||||||
that component"
|
that component"
|
||||||
[main-instance-id]
|
([main-instance-id]
|
||||||
(ptk/reify ::transform-in-variant
|
(transform-in-variant main-instance-id nil nil [] true true))
|
||||||
ptk/WatchEvent
|
([main-instance-id variant-id delta prefix duplicate? flex?]
|
||||||
(watch [_ state _]
|
(ptk/reify ::transform-in-variant
|
||||||
(let [variant-id (uuid/next)
|
ptk/WatchEvent
|
||||||
variant-vec [variant-id]
|
(watch [_ state _]
|
||||||
file-id (:current-file-id state)
|
(let [variant-id (or variant-id (uuid/next))
|
||||||
page-id (:current-page-id state)
|
variant-vec [variant-id]
|
||||||
objects (dsh/lookup-page-objects state file-id page-id)
|
file-id (:current-file-id state)
|
||||||
main (get objects main-instance-id)
|
page-id (:current-page-id state)
|
||||||
parent (get objects (:parent-id main))
|
objects (dsh/lookup-page-objects state file-id page-id)
|
||||||
component-id (:component-id main)
|
main (get objects main-instance-id)
|
||||||
cpath (cfh/split-path (:name main))
|
parent (get objects (:parent-id main))
|
||||||
name (first cpath)
|
component-id (:component-id main)
|
||||||
num-props (max 1 (dec (count cpath)))
|
;; If there is a prefix, set is as first item of path
|
||||||
cont-props {:layout-item-h-sizing :auto
|
cpath (-> (:name main)
|
||||||
:layout-item-v-sizing :auto
|
cfh/split-path
|
||||||
:layout-padding {:p1 30 :p2 30 :p3 30 :p4 30}
|
(cond->
|
||||||
:layout-gap {:row-gap 0 :column-gap 20}
|
(seq prefix)
|
||||||
:name name
|
(->> (drop (count prefix))
|
||||||
:r1 20
|
(cons (cfh/join-path prefix))
|
||||||
:r2 20
|
vec)))
|
||||||
:r3 20
|
|
||||||
:r4 20
|
name (first cpath)
|
||||||
:is-variant-container true}
|
num-props (max 1 (dec (count cpath)))
|
||||||
main-props {:layout-item-h-sizing :fix
|
base-props {:is-variant-container true
|
||||||
:layout-item-v-sizing :fix
|
:name name
|
||||||
:variant-id variant-id
|
:r1 20
|
||||||
:name name}
|
:r2 20
|
||||||
stroke-props {:stroke-alignment :inner
|
:r3 20
|
||||||
:stroke-style :solid
|
:r4 20}
|
||||||
:stroke-color "#bb97d8" ;; todo use color var?
|
flex-props {:layout-item-h-sizing :auto
|
||||||
:stroke-opacity 1
|
:layout-item-v-sizing :auto
|
||||||
:stroke-width 2}
|
:layout-padding {:p1 30 :p2 30 :p3 30 :p4 30}
|
||||||
|
:layout-gap {:row-gap 0 :column-gap 20}}
|
||||||
|
cont-props (if flex?
|
||||||
|
(into base-props flex-props)
|
||||||
|
base-props)
|
||||||
|
m-base-props {:name name
|
||||||
|
:variant-id variant-id}
|
||||||
|
m-flex-props {:layout-item-h-sizing :fix
|
||||||
|
:layout-item-v-sizing :fix}
|
||||||
|
main-props (if flex?
|
||||||
|
(into m-base-props m-flex-props)
|
||||||
|
m-base-props)
|
||||||
|
stroke-props {:stroke-alignment :inner
|
||||||
|
:stroke-style :solid
|
||||||
|
:stroke-color "#bb97d8" ;; todo use color var?
|
||||||
|
:stroke-opacity 1
|
||||||
|
:stroke-width 2}
|
||||||
|
|
||||||
;; Move the position of the variant container so the main shape doesn't
|
;; Move the position of the variant container so the main shape doesn't
|
||||||
;; change its position
|
;; change its position
|
||||||
delta (if (ctsl/any-layout? parent)
|
delta (or delta
|
||||||
(gpt/point 0 0)
|
(if (ctsl/any-layout? parent)
|
||||||
(gpt/point -30 -30))
|
(gpt/point 0 0)
|
||||||
undo-id (js/Symbol)]
|
(gpt/point -30 -30)))
|
||||||
|
undo-id (js/Symbol)]
|
||||||
|
|
||||||
|
|
||||||
;;TODO Refactor all called methods in order to be able to
|
;;TODO Refactor all called methods in order to be able to
|
||||||
;;generate changes instead of call the events
|
;;generate changes instead of call the events
|
||||||
(rx/concat
|
(rx/concat
|
||||||
(rx/of
|
(rx/of
|
||||||
(dwu/start-undo-transaction undo-id)
|
(dwu/start-undo-transaction undo-id)
|
||||||
|
|
||||||
(when (not= name (:name main))
|
(when (not= name (:name main))
|
||||||
(dwl/rename-component component-id name))
|
(dwl/rename-component component-id name))
|
||||||
|
|
||||||
;; Create variant container
|
;; Create variant container
|
||||||
(dwsh/create-artboard-from-selection variant-id nil nil nil delta)
|
(dwsh/create-artboard-from-shapes [main-instance-id] variant-id nil nil nil delta flex?)
|
||||||
(cl/remove-all-fills variant-vec {:color clr/black :opacity 1})
|
(cl/remove-all-fills variant-vec {:color clr/black :opacity 1})
|
||||||
(dwsl/create-layout-from-id variant-id :flex)
|
(when flex? (dwsl/create-layout-from-id variant-id :flex))
|
||||||
(dwsh/update-shapes variant-vec #(merge % cont-props))
|
(dwsh/update-shapes variant-vec #(merge % cont-props))
|
||||||
(dwsh/update-shapes [main-instance-id] #(merge % main-props))
|
(dwsh/update-shapes [main-instance-id] #(merge % main-props))
|
||||||
(cl/add-stroke variant-vec stroke-props)
|
(cl/add-stroke variant-vec stroke-props)
|
||||||
(set-variant-id component-id variant-id))
|
(set-variant-id component-id variant-id))
|
||||||
|
|
||||||
;; Add the necessary number of new properties, with default values
|
;; Add the necessary number of new properties, with default values
|
||||||
(rx/from
|
(rx/from
|
||||||
(repeatedly num-props
|
(repeatedly num-props
|
||||||
#(add-new-property variant-id {:fill-values? true})))
|
#(add-new-property variant-id {:fill-values? true})))
|
||||||
|
|
||||||
;; When the component has path, set the path items as properties values
|
;; When the component has path, set the path items as properties values
|
||||||
(when (> (count cpath) 1)
|
(when (> (count cpath) 1)
|
||||||
(rx/from
|
(rx/from
|
||||||
(map
|
(map
|
||||||
#(update-property-value component-id % (nth cpath (inc %)))
|
#(update-property-value component-id % (nth cpath (inc %)))
|
||||||
(range num-props))))
|
(range num-props))))
|
||||||
|
|
||||||
(rx/of
|
(rx/of
|
||||||
(add-new-variant main-instance-id)
|
(when duplicate? (add-new-variant main-instance-id))
|
||||||
(dwu/commit-undo-transaction undo-id)
|
(dwu/commit-undo-transaction undo-id)
|
||||||
(ptk/data-event :layout/update {:ids [variant-id]})))))))
|
(when flex?
|
||||||
|
(ptk/data-event :layout/update {:ids [variant-id]})))))))))
|
||||||
|
|
||||||
(defn add-component-or-variant
|
(defn add-component-or-variant
|
||||||
"Manage the shared shortcut, and do the pertinent action"
|
"Manage the shared shortcut, and do the pertinent action"
|
||||||
@@ -509,3 +529,91 @@
|
|||||||
(rx/of (rename-variant (:variant-id component) name))
|
(rx/of (rename-variant (:variant-id component) name))
|
||||||
(rx/of (dwl/rename-component-and-main-instance component-id name)))))))
|
(rx/of (dwl/rename-component-and-main-instance component-id name)))))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn- bounding-rect
|
||||||
|
"Receives a list of frames (with X, y, width and height) and
|
||||||
|
calculates a rect that contains them all"
|
||||||
|
[frames]
|
||||||
|
(let [xs (map :x frames)
|
||||||
|
ys (map :y frames)
|
||||||
|
x2s (map #(+ (:x %) (:width %)) frames)
|
||||||
|
y2s (map #(+ (:y %) (:height %)) frames)
|
||||||
|
min-x (apply min xs)
|
||||||
|
min-y (apply min ys)
|
||||||
|
max-x (apply max x2s)
|
||||||
|
max-y (apply max y2s)]
|
||||||
|
{:x min-x
|
||||||
|
:y min-y
|
||||||
|
:width (- max-x min-x)
|
||||||
|
:height (- max-y min-y)}))
|
||||||
|
|
||||||
|
(defn- common-prefix
|
||||||
|
[paths]
|
||||||
|
(->> (apply map vector paths)
|
||||||
|
(take-while #(apply = %))
|
||||||
|
(map first)
|
||||||
|
vec))
|
||||||
|
|
||||||
|
(defn combine-as-variants
|
||||||
|
([]
|
||||||
|
(combine-as-variants nil {}))
|
||||||
|
([selected {:keys [page-id]}]
|
||||||
|
(ptk/reify ::combine-as-variants
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state stream]
|
||||||
|
(let [current-page (:current-page-id state)
|
||||||
|
|
||||||
|
combine
|
||||||
|
(fn [current-page]
|
||||||
|
(let [objects (dsh/lookup-page-objects state current-page)
|
||||||
|
selected (or selected
|
||||||
|
(->> (dsh/lookup-selected state)
|
||||||
|
(cfh/clean-loops objects)
|
||||||
|
(remove (fn [id]
|
||||||
|
(let [shape (get objects id)]
|
||||||
|
(or (not (ctc/main-instance? shape))
|
||||||
|
(ctc/is-variant? shape)))))))
|
||||||
|
shapes (mapv #(get objects %) selected)
|
||||||
|
rect (bounding-rect shapes)
|
||||||
|
prefix (->> shapes
|
||||||
|
(mapv #(cfh/split-path (:name %)))
|
||||||
|
(common-prefix))
|
||||||
|
first-shape (first shapes)
|
||||||
|
delta (gpt/point (- (:x rect) (:x first-shape) 30)
|
||||||
|
(- (:y rect) (:y first-shape) 30))
|
||||||
|
common-parent (->> selected
|
||||||
|
(mapv #(-> (cfh/get-parent-ids objects %) reverse))
|
||||||
|
common-prefix
|
||||||
|
last)
|
||||||
|
index (-> (get objects common-parent)
|
||||||
|
:shapes
|
||||||
|
count
|
||||||
|
inc)
|
||||||
|
variant-id (uuid/next)
|
||||||
|
undo-id (js/Symbol)]
|
||||||
|
(rx/concat
|
||||||
|
(rx/of
|
||||||
|
(when (and page-id (not= current-page page-id))
|
||||||
|
(dcm/go-to-workspace :page-id page-id))
|
||||||
|
(dwu/start-undo-transaction undo-id)
|
||||||
|
(transform-in-variant (first selected) variant-id delta prefix false false)
|
||||||
|
(dwsh/relocate-shapes (into #{} (-> selected rest reverse)) variant-id 0)
|
||||||
|
(dwt/update-dimensions [variant-id] :width (+ (:width rect) 60))
|
||||||
|
(dwt/update-dimensions [variant-id] :height (+ (:height rect) 60))
|
||||||
|
(dwsh/relocate-shapes #{variant-id} common-parent index)
|
||||||
|
(dwu/commit-undo-transaction undo-id)))))
|
||||||
|
|
||||||
|
redirect-to-page
|
||||||
|
(fn [page-id]
|
||||||
|
(rx/merge
|
||||||
|
(->> stream
|
||||||
|
(rx/filter (ptk/type? ::dwpg/initialize-page))
|
||||||
|
(rx/take 1)
|
||||||
|
(rx/observe-on :async)
|
||||||
|
(rx/mapcat (fn [_] (combine page-id))))
|
||||||
|
(rx/of (dcm/go-to-workspace :page-id page-id))))]
|
||||||
|
|
||||||
|
(if (and page-id (not= page-id current-page))
|
||||||
|
(redirect-to-page page-id)
|
||||||
|
(combine current-page)))))))
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,8 @@
|
|||||||
[:map
|
[:map
|
||||||
[:name :string]
|
[:name :string]
|
||||||
[:id :string]
|
[:id :string]
|
||||||
|
[:title {:optional true} [:maybe :string]]
|
||||||
|
[:disabled {:optional true} [:maybe :boolean]]
|
||||||
[:handler {:optional true} fn?]
|
[:handler {:optional true} fn?]
|
||||||
[:options {:optional true}
|
[:options {:optional true}
|
||||||
[:sequential [:ref ::option]]]]
|
[:sequential [:ref ::option]]]]
|
||||||
@@ -258,7 +260,9 @@
|
|||||||
(let [name (:name option)
|
(let [name (:name option)
|
||||||
id (:id option)
|
id (:id option)
|
||||||
sub-options (:options option)
|
sub-options (:options option)
|
||||||
handler (:handler option)]
|
handler (:handler option)
|
||||||
|
title (:title option)
|
||||||
|
disabled (:disabled option)]
|
||||||
(when name
|
(when name
|
||||||
(if (= name :separator)
|
(if (= name :separator)
|
||||||
[:li {:key (dm/str "context-item-" index)
|
[:li {:key (dm/str "context-item-" index)
|
||||||
@@ -273,10 +277,12 @@
|
|||||||
:role "menuitem"
|
:role "menuitem"
|
||||||
:on-key-down dom/prevent-default}
|
:on-key-down dom/prevent-default}
|
||||||
(if-not sub-options
|
(if-not sub-options
|
||||||
[:a {:class (stl/css :context-menu-action)
|
[:a {:class (stl/css-case :context-menu-action true :context-menu-action-disabled disabled)
|
||||||
|
:title title
|
||||||
:on-click #(do (dom/stop-propagation %)
|
:on-click #(do (dom/stop-propagation %)
|
||||||
(on-close %)
|
(when-not disabled
|
||||||
(handler %))
|
(on-close %)
|
||||||
|
(handler %)))
|
||||||
:data-testid id}
|
:data-testid id}
|
||||||
(if (and in-dashboard? (= name "Default"))
|
(if (and in-dashboard? (= name "Default"))
|
||||||
(tr "dashboard.default-team-name")
|
(tr "dashboard.default-team-name")
|
||||||
|
|||||||
@@ -105,6 +105,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.context-menu-action-disabled,
|
||||||
|
&:hover .context-menu-action-disabled {
|
||||||
|
color: var(--color-foreground-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -570,9 +570,14 @@
|
|||||||
heads (filter ctk/instance-head? shapes)
|
heads (filter ctk/instance-head? shapes)
|
||||||
components-menu-entries (cmm/generate-components-menu-entries heads)
|
components-menu-entries (cmm/generate-components-menu-entries heads)
|
||||||
variant-container? (and single? (ctk/is-variant-container? (first shapes)))
|
variant-container? (and single? (ctk/is-variant-container? (first shapes)))
|
||||||
do-add-component #(st/emit! (dwl/add-component))
|
all-main? (every? ctk/main-instance? shapes)
|
||||||
do-add-multiple-components #(st/emit! (dwl/add-multiple-components))
|
any-variant? (some ctk/is-variant? shapes)
|
||||||
do-add-variant #(st/emit! (dwv/add-new-variant (:id (first shapes))))]
|
do-add-component (mf/use-fn #(st/emit! (dwl/add-component)))
|
||||||
|
do-add-multiple-components (mf/use-fn #(st/emit! (dwl/add-multiple-components)))
|
||||||
|
do-combine-as-variants (mf/use-fn #(st/emit! (dwv/combine-as-variants)))
|
||||||
|
do-add-variant (mf/use-fn
|
||||||
|
(mf/deps shapes)
|
||||||
|
#(st/emit! (dwv/add-new-variant (:id (first shapes)))))]
|
||||||
[:*
|
[:*
|
||||||
(when can-make-component ;; We don't want to change the structure of component copies
|
(when can-make-component ;; We don't want to change the structure of component copies
|
||||||
[:*
|
[:*
|
||||||
@@ -596,10 +601,17 @@
|
|||||||
:on-click (:action entry)}])])
|
:on-click (:action entry)}])])
|
||||||
|
|
||||||
(when variant-container?
|
(when variant-container?
|
||||||
[:> menu-separator*]
|
[:*
|
||||||
[:> menu-entry* {:title (tr "workspace.shape.menu.add-variant")
|
[:> menu-separator*]
|
||||||
:shortcut (sc/get-tooltip :create-component)
|
[:> menu-entry* {:title (tr "workspace.shape.menu.add-variant")
|
||||||
:on-click do-add-variant}])]))
|
:shortcut (sc/get-tooltip :create-component)
|
||||||
|
:on-click do-add-variant}]])
|
||||||
|
|
||||||
|
(when (and (not single?) all-main? (not any-variant?))
|
||||||
|
[:*
|
||||||
|
[:> menu-separator*]
|
||||||
|
[:> menu-entry* {:title (tr "workspace.shape.menu.combine-as-variants")
|
||||||
|
:on-click do-combine-as-variants}]])]))
|
||||||
|
|
||||||
(mf/defc context-menu-delete*
|
(mf/defc context-menu-delete*
|
||||||
{::mf/props :obj
|
{::mf/props :obj
|
||||||
|
|||||||
@@ -317,13 +317,21 @@
|
|||||||
(seq (:colors selected))
|
(seq (:colors selected))
|
||||||
(seq (:typographies selected)))
|
(seq (:typographies selected)))
|
||||||
|
|
||||||
any-variant? (mf/with-memo [selected components current-component-id]
|
selected-and-current (mf/with-memo [selected components current-component-id]
|
||||||
(let [selected-and-current (-> (d/nilv selected [])
|
(-> (d/nilv selected [])
|
||||||
(conj current-component-id)
|
(conj current-component-id)
|
||||||
set)]
|
set))
|
||||||
(->> components
|
|
||||||
(filter #(contains? selected-and-current (:id %)))
|
selected-and-current-full (mf/with-memo [selected-and-current]
|
||||||
(some ctc/is-variant?))))
|
(->> components
|
||||||
|
(filter #(contains? selected-and-current (:id %)))))
|
||||||
|
|
||||||
|
any-variant? (mf/with-memo [selected-and-current]
|
||||||
|
(some ctc/is-variant? selected-and-current-full))
|
||||||
|
|
||||||
|
all-same-page? (mf/with-memo [selected-and-current]
|
||||||
|
(let [page (:main-instance-page (first selected-and-current-full))]
|
||||||
|
(every? #(= page (:main-instance-page %)) selected-and-current-full)))
|
||||||
|
|
||||||
groups (mf/with-memo [components reverse-sort?]
|
groups (mf/with-memo [components reverse-sort?]
|
||||||
(grp/group-assets components reverse-sort?))
|
(grp/group-assets components reverse-sort?))
|
||||||
@@ -497,7 +505,17 @@
|
|||||||
(st/emit! (dwl/go-to-component-file file-id component))))))
|
(st/emit! (dwl/go-to-component-file file-id component))))))
|
||||||
|
|
||||||
on-asset-click
|
on-asset-click
|
||||||
(mf/use-fn (mf/deps groups on-asset-click) (partial on-asset-click groups))]
|
(mf/use-fn (mf/deps groups on-asset-click) (partial on-asset-click groups))
|
||||||
|
|
||||||
|
on-combine-as-variants
|
||||||
|
(mf/use-fn
|
||||||
|
(mf/deps selected-and-current-full)
|
||||||
|
(fn [event]
|
||||||
|
(dom/stop-propagation event)
|
||||||
|
(let [page-id (->> selected-and-current-full first :main-instance-page)
|
||||||
|
ids (into #{} (map :main-instance-id selected-full))]
|
||||||
|
|
||||||
|
(st/emit! (dwv/combine-as-variants ids {:page-id page-id})))))]
|
||||||
|
|
||||||
[:& cmm/asset-section {:file-id file-id
|
[:& cmm/asset-section {:file-id file-id
|
||||||
:title (tr "workspace.assets.components")
|
:title (tr "workspace.assets.components")
|
||||||
@@ -575,4 +593,10 @@
|
|||||||
(when (not multi-assets?)
|
(when (not multi-assets?)
|
||||||
{:name (tr "workspace.shape.menu.show-main")
|
{:name (tr "workspace.shape.menu.show-main")
|
||||||
:id "assets-show-main-component"
|
:id "assets-show-main-component"
|
||||||
:handler on-show-main})]}]]]))
|
:handler on-show-main})
|
||||||
|
(when (and is-local multi-components? (not any-variant?))
|
||||||
|
{:name (tr "workspace.shape.menu.combine-as-variants")
|
||||||
|
:id "assets-combine-as-variants"
|
||||||
|
:title (when-not all-same-page? (tr "workspace.shape.menu.combine-as-variants-error"))
|
||||||
|
:disabled (not all-same-page?)
|
||||||
|
:handler on-combine-as-variants})]}]]]))
|
||||||
|
|||||||
@@ -778,6 +778,9 @@
|
|||||||
copies (filter ctk/in-component-copy? shapes)
|
copies (filter ctk/in-component-copy? shapes)
|
||||||
can-swap? (boolean (seq copies))
|
can-swap? (boolean (seq copies))
|
||||||
|
|
||||||
|
all-main? (every? ctk/main-instance? shapes)
|
||||||
|
any-variant? (some ctk/is-variant? shapes)
|
||||||
|
|
||||||
;; For when it's only one shape
|
;; For when it's only one shape
|
||||||
shape (first shapes)
|
shape (first shapes)
|
||||||
id (:id shape)
|
id (:id shape)
|
||||||
@@ -790,6 +793,7 @@
|
|||||||
data (dm/get-in libraries [(:component-file shape) :data])
|
data (dm/get-in libraries [(:component-file shape) :data])
|
||||||
variants? (features/use-feature "variants/v1")
|
variants? (features/use-feature "variants/v1")
|
||||||
is-variant? (when variants? (ctk/is-variant? component))
|
is-variant? (when variants? (ctk/is-variant? component))
|
||||||
|
|
||||||
main-instance? (ctk/main-instance? shape)
|
main-instance? (ctk/main-instance? shape)
|
||||||
|
|
||||||
components (mapv #(ctf/resolve-component %
|
components (mapv #(ctf/resolve-component %
|
||||||
@@ -830,6 +834,9 @@
|
|||||||
(when can-swap? (st/emit! (dwsp/open-specialized-panel :component-swap)))
|
(when can-swap? (st/emit! (dwsp/open-specialized-panel :component-swap)))
|
||||||
(tm/schedule-on-idle #(dom/focus! (dom/get-element search-id))))))
|
(tm/schedule-on-idle #(dom/focus! (dom/get-element search-id))))))
|
||||||
|
|
||||||
|
on-combine-as-variants
|
||||||
|
#(st/emit! (dwv/combine-as-variants))
|
||||||
|
|
||||||
;; NOTE: function needed for force rerender from the bottom
|
;; NOTE: function needed for force rerender from the bottom
|
||||||
;; components. This is because `component-annotation`
|
;; components. This is because `component-annotation`
|
||||||
;; component changes the component but that has no direct
|
;; component changes the component but that has no direct
|
||||||
@@ -937,6 +944,11 @@
|
|||||||
:shapes shapes
|
:shapes shapes
|
||||||
:data data}])
|
:data data}])
|
||||||
|
|
||||||
|
(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")]])
|
||||||
|
|
||||||
(when (dbg/enabled? :display-touched)
|
(when (dbg/enabled? :display-touched)
|
||||||
[:div ":touched " (str (:touched shape))])])])))
|
[:div ":touched " (str (:touched shape))])])])))
|
||||||
|
|
||||||
|
|||||||
@@ -808,3 +808,22 @@
|
|||||||
right: $s-2;
|
right: $s-2;
|
||||||
top: $s-2;
|
top: $s-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.combine-variant-button {
|
||||||
|
@include buttonStyle;
|
||||||
|
@include uppercaseTitleTipography;
|
||||||
|
cursor: default;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: $s-8;
|
||||||
|
border-radius: $br-8;
|
||||||
|
background-color: var(--assets-item-background-color);
|
||||||
|
color: var(--color-foreground-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--assets-item-background-color-hover);
|
||||||
|
color: var(--color-foreground-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -933,7 +933,7 @@
|
|||||||
|
|
||||||
:else
|
:else
|
||||||
(let [child-id (obj/get child "$id")]
|
(let [child-id (obj/get child "$id")]
|
||||||
(st/emit! (dw/relocate-shapes #{child-id} id 0))))))
|
(st/emit! (dwsh/relocate-shapes #{child-id} id 0))))))
|
||||||
|
|
||||||
:insertChild
|
:insertChild
|
||||||
(fn [index child]
|
(fn [index child]
|
||||||
@@ -953,7 +953,7 @@
|
|||||||
|
|
||||||
:else
|
:else
|
||||||
(let [child-id (obj/get child "$id")]
|
(let [child-id (obj/get child "$id")]
|
||||||
(st/emit! (dw/relocate-shapes #{child-id} id index))))))
|
(st/emit! (dwsh/relocate-shapes #{child-id} id index))))))
|
||||||
|
|
||||||
;; Only for frames
|
;; Only for frames
|
||||||
:addFlexLayout
|
:addFlexLayout
|
||||||
|
|||||||
@@ -6872,6 +6872,13 @@ msgstr "Create annotation"
|
|||||||
msgid "workspace.shape.menu.create-artboard-from-selection"
|
msgid "workspace.shape.menu.create-artboard-from-selection"
|
||||||
msgstr "Selection to board"
|
msgstr "Selection to board"
|
||||||
|
|
||||||
|
#: src/app/main/ui/workspace/context_menu.cljs:385
|
||||||
|
msgid "workspace.shape.menu.combine-as-variants"
|
||||||
|
msgstr "Combine as variants"
|
||||||
|
|
||||||
|
msgid "workspace.shape.menu.combine-as-variants-error"
|
||||||
|
msgstr "Components need to be in the same page"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/context_menu.cljs:573
|
#: src/app/main/ui/workspace/context_menu.cljs:573
|
||||||
msgid "workspace.shape.menu.create-component"
|
msgid "workspace.shape.menu.create-component"
|
||||||
msgstr "Create component"
|
msgstr "Create component"
|
||||||
|
|||||||
@@ -6850,6 +6850,13 @@ msgstr "Crear una nota"
|
|||||||
msgid "workspace.shape.menu.create-artboard-from-selection"
|
msgid "workspace.shape.menu.create-artboard-from-selection"
|
||||||
msgstr "Tablero de selección"
|
msgstr "Tablero de selección"
|
||||||
|
|
||||||
|
#: src/app/main/ui/workspace/context_menu.cljs:385
|
||||||
|
msgid "workspace.shape.menu.combine-as-variants"
|
||||||
|
msgstr "Combinar como variantes"
|
||||||
|
|
||||||
|
msgid "workspace.shape.menu.combine-as-variants-error"
|
||||||
|
msgstr "Los componentes tienen que estar en la misma página"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/context_menu.cljs:573
|
#: src/app/main/ui/workspace/context_menu.cljs:573
|
||||||
msgid "workspace.shape.menu.create-component"
|
msgid "workspace.shape.menu.create-component"
|
||||||
msgstr "Crear componente"
|
msgstr "Crear componente"
|
||||||
|
|||||||
Reference in New Issue
Block a user