mirror of
https://github.com/penpot/penpot.git
synced 2026-11-12 13:00:37 +00:00
🐛 Block grid track duplication and paste in component copies
This commit is contained in:
parent
3eebe92606
commit
bb672a3723
@ -436,6 +436,14 @@
|
||||
(ptk/data-event :layout/update {:ids ids})
|
||||
(dwu/commit-undo-transaction undo-id))))))
|
||||
|
||||
(defn grid-in-copy?
|
||||
"Check if the grid is a component copy or lives inside one. Copies
|
||||
must keep the structure of their main, so tracks that add shapes are
|
||||
not allowed there."
|
||||
[objects grid]
|
||||
(or (ctc/in-component-copy? grid)
|
||||
(ctn/has-any-copy-parent? objects grid)))
|
||||
|
||||
(defn duplicate-layout-track
|
||||
[ids type index]
|
||||
(assert (#{:row :column} type))
|
||||
@ -443,53 +451,53 @@
|
||||
(ptk/reify ::duplicate-layout-track
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(let [file-id (:current-file-id state)
|
||||
page (dsh/lookup-page state)
|
||||
objects (:objects page)
|
||||
libraries (dsh/lookup-libraries state)
|
||||
library-data (dsh/lookup-file state file-id)
|
||||
shape-id (first ids)
|
||||
base-shape (get objects shape-id)
|
||||
(let [objects (dsh/lookup-page-objects state)
|
||||
base-shape (get objects (first ids))]
|
||||
(when-not (grid-in-copy? objects base-shape)
|
||||
(let [file-id (:current-file-id state)
|
||||
page (dsh/lookup-page state)
|
||||
libraries (dsh/lookup-libraries state)
|
||||
library-data (dsh/lookup-file state file-id)
|
||||
|
||||
shapes-by-track
|
||||
(if (= type :column)
|
||||
(ctl/shapes-by-column base-shape index false)
|
||||
(ctl/shapes-by-row base-shape index false))
|
||||
shapes-by-track
|
||||
(if (= type :column)
|
||||
(ctl/shapes-by-column base-shape index false)
|
||||
(ctl/shapes-by-row base-shape index false))
|
||||
|
||||
;; Change to set in order to use auxiliary functions
|
||||
selected (set shapes-by-track)
|
||||
;; Change to set in order to use auxiliary functions
|
||||
selected (set shapes-by-track)
|
||||
|
||||
changes
|
||||
(-> (pcb/empty-changes it)
|
||||
(cll/generate-duplicate-changes objects page selected (gpt/point 0 0) libraries library-data file-id)
|
||||
(cll/generate-duplicate-changes-update-indices objects selected))
|
||||
changes
|
||||
(-> (pcb/empty-changes it)
|
||||
(cll/generate-duplicate-changes objects page selected (gpt/point 0 0) libraries library-data file-id)
|
||||
(cll/generate-duplicate-changes-update-indices objects selected))
|
||||
|
||||
;; Creates a map with shape-id => duplicated-shape-id
|
||||
ids-map
|
||||
(->> changes
|
||||
:redo-changes
|
||||
(filter #(= (:type %) :add-obj))
|
||||
(filter #(selected (:old-id %)))
|
||||
(map #(vector (:old-id %) (get-in % [:obj :id])))
|
||||
(into {}))
|
||||
;; Creates a map with shape-id => duplicated-shape-id
|
||||
ids-map
|
||||
(->> changes
|
||||
:redo-changes
|
||||
(filter #(= (:type %) :add-obj))
|
||||
(filter #(selected (:old-id %)))
|
||||
(map #(vector (:old-id %) (get-in % [:obj :id])))
|
||||
(into {}))
|
||||
|
||||
changes
|
||||
(-> changes
|
||||
(pcb/update-shapes
|
||||
ids
|
||||
(fn [shape objects]
|
||||
;; The duplication could have altered the grid so we restore the values, we'll calculate the good ones now
|
||||
(let [shape (merge shape (select-keys base-shape [:layout-grid-cells :layout-grid-columns :layout-grid-rows]))]
|
||||
(case type
|
||||
:row (ctl/duplicate-row shape objects index ids-map)
|
||||
:column (ctl/duplicate-column shape objects index ids-map))))
|
||||
{:with-objects? true}))
|
||||
changes
|
||||
(-> changes
|
||||
(pcb/update-shapes
|
||||
ids
|
||||
(fn [shape objects]
|
||||
;; The duplication could have altered the grid so we restore the values, we'll calculate the good ones now
|
||||
(let [shape (merge shape (select-keys base-shape [:layout-grid-cells :layout-grid-columns :layout-grid-rows]))]
|
||||
(case type
|
||||
:row (ctl/duplicate-row shape objects index ids-map)
|
||||
:column (ctl/duplicate-column shape objects index ids-map))))
|
||||
{:with-objects? true}))
|
||||
|
||||
undo-id (js/Symbol)]
|
||||
(rx/of (dwu/start-undo-transaction undo-id)
|
||||
(dch/commit-changes changes)
|
||||
(ptk/data-event :layout/update {:ids ids})
|
||||
(dwu/commit-undo-transaction undo-id))))))
|
||||
undo-id (js/Symbol)]
|
||||
(rx/of (dwu/start-undo-transaction undo-id)
|
||||
(dch/commit-changes changes)
|
||||
(ptk/data-event :layout/update {:ids ids})
|
||||
(dwu/commit-undo-transaction undo-id))))))))
|
||||
|
||||
(defn reorder-layout-track
|
||||
[ids type from-index to-index move-content?]
|
||||
@ -918,7 +926,9 @@
|
||||
type (:type copied)
|
||||
undo-id (js/Symbol)]
|
||||
|
||||
(when (and (seq track-indices) (some? type))
|
||||
(when (and (seq track-indices)
|
||||
(some? type)
|
||||
(not (grid-in-copy? objects grid)))
|
||||
(let [shapes-by-track-fn
|
||||
(if (= type :row)
|
||||
ctl/shapes-by-row
|
||||
|
||||
@ -873,6 +873,10 @@
|
||||
{::mf/private true}
|
||||
[{:keys [mdata]}]
|
||||
(let [{:keys [type index grid-id]} mdata
|
||||
|
||||
objects (deref refs/workspace-page-objects)
|
||||
in-copy? (dwsl/grid-in-copy? objects (get objects grid-id))
|
||||
|
||||
do-delete-track
|
||||
(mf/use-fn
|
||||
(mf/deps grid-id type index)
|
||||
@ -905,14 +909,14 @@
|
||||
|
||||
(if (= type :column)
|
||||
[:*
|
||||
[:> menu-entry* {:title (tr "workspace.context-menu.grid-track.column.duplicate") :on-click do-duplicate-track}]
|
||||
[:> menu-entry* {:title (tr "workspace.context-menu.grid-track.column.duplicate") :on-click do-duplicate-track :disabled in-copy?}]
|
||||
[:> menu-entry* {:title (tr "workspace.context-menu.grid-track.column.add-before") :on-click do-add-track-before}]
|
||||
[:> menu-entry* {:title (tr "workspace.context-menu.grid-track.column.add-after") :on-click do-add-track-after}]
|
||||
[:> menu-entry* {:title (tr "workspace.context-menu.grid-track.column.delete") :on-click do-delete-track}]
|
||||
[:> menu-entry* {:title (tr "workspace.context-menu.grid-track.column.delete-shapes") :on-click do-delete-track-shapes}]]
|
||||
|
||||
[:*
|
||||
[:> menu-entry* {:title (tr "workspace.context-menu.grid-track.row.duplicate") :on-click do-duplicate-track}]
|
||||
[:> menu-entry* {:title (tr "workspace.context-menu.grid-track.row.duplicate") :on-click do-duplicate-track :disabled in-copy?}]
|
||||
[:> menu-entry* {:title (tr "workspace.context-menu.grid-track.row.add-before") :on-click do-add-track-before}]
|
||||
[:> menu-entry* {:title (tr "workspace.context-menu.grid-track.row.add-after") :on-click do-add-track-after}]
|
||||
[:> menu-entry* {:title (tr "workspace.context-menu.grid-track.row.delete") :on-click do-delete-track}]
|
||||
@ -949,6 +953,9 @@
|
||||
grid-edition (mf/deref grid-edition-ref)
|
||||
has-copied-tracks? (some? (:copied-tracks grid-edition))
|
||||
|
||||
objects (deref refs/workspace-page-objects)
|
||||
in-copy? (dwsl/grid-in-copy? objects (get objects grid-id))
|
||||
|
||||
do-merge-cells
|
||||
(mf/use-fn
|
||||
(mf/deps grid-id cells)
|
||||
@ -1002,7 +1009,7 @@
|
||||
|
||||
[:> menu-entry* {:title (tr "workspace.context-menu.grid-cells.paste-tracks")
|
||||
:on-click do-paste-tracks
|
||||
:disabled (not has-copied-tracks?)}]]))
|
||||
:disabled (or (not has-copied-tracks?) in-copy?)}]]))
|
||||
|
||||
|
||||
(def guide-color-presets
|
||||
|
||||
@ -12,16 +12,19 @@
|
||||
[app.common.test-helpers.ids-map :as cthi]
|
||||
[app.common.test-helpers.shapes :as cths]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.colors :as dc]
|
||||
[app.main.data.workspace.libraries :as dwl]
|
||||
[app.main.data.workspace.pages :as dwp]
|
||||
[app.main.data.workspace.selection :as dws]
|
||||
[app.main.data.workspace.shape-layout :as dwsl]
|
||||
[cljs.test :as t :include-macros true]
|
||||
[frontend-tests.helpers.mock :as mock]
|
||||
[frontend-tests.helpers.pages :as thp]
|
||||
[frontend-tests.helpers.state :as ths]))
|
||||
[frontend-tests.helpers.state :as ths]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
(t/use-fixtures :each
|
||||
{:before thp/reset-idmap!})
|
||||
@ -653,3 +656,160 @@
|
||||
"Pasted text shapes have nil position-data"))))))
|
||||
done)))
|
||||
|
||||
|
||||
;; Grid tracks in component copies
|
||||
|
||||
(defn- add-grid-with-child
|
||||
"Adds a grid board with one rect child and assigns the child to a cell."
|
||||
[file grid-label rect-label & {:keys [parent-label params]}]
|
||||
(let [file (-> file
|
||||
(ctho/add-frame grid-label
|
||||
(merge {:name "grid"
|
||||
:parent-label parent-label
|
||||
:layout :grid
|
||||
:layout-grid-dir :row
|
||||
:layout-grid-columns []
|
||||
:layout-grid-rows []
|
||||
:layout-grid-cells {}}
|
||||
params))
|
||||
(cths/add-sample-shape rect-label :parent-label grid-label))
|
||||
grid (->> (cths/get-shape file grid-label)
|
||||
(#(ctl/assign-cells % (:objects (cthf/current-page file)))))]
|
||||
(reduce (fn [file attr] (cths/update-shape file grid-label attr (get grid attr)))
|
||||
file
|
||||
[:layout-grid-columns :layout-grid-rows :layout-grid-cells])))
|
||||
|
||||
(defn- grid-root-file
|
||||
"Main component whose root board is the grid, and a copy of it."
|
||||
[]
|
||||
(-> (cthf/sample-file :file1 :page-label :page-1)
|
||||
(add-grid-with-child :grid-main :rect-main)
|
||||
(cthc/make-component :comp :grid-main)
|
||||
(cthc/instantiate-component :comp :grid-copy :children-labels [:rect-copy])))
|
||||
|
||||
(defn- grid-nested-file
|
||||
"Main component with a wrapper board that contains the grid, and a copy."
|
||||
[]
|
||||
(-> (cthf/sample-file :file1 :page-label :page-1)
|
||||
(ctho/add-frame :wrapper-main :name "wrapper")
|
||||
(add-grid-with-child :grid-main :rect-main :parent-label :wrapper-main)
|
||||
(cthc/make-component :comp :wrapper-main)
|
||||
(cthc/instantiate-component :comp :wrapper-copy :children-labels [:grid-copy :rect-copy])))
|
||||
|
||||
(defn- grid-user-added-in-copy-file
|
||||
"Copy of a wrapper board, with a plain grid (no :shape-ref) added inside
|
||||
the copy. Only its parent is a copy."
|
||||
[]
|
||||
(let [file (-> (grid-nested-file)
|
||||
(add-grid-with-child :grid-user :rect-user :parent-label :wrapper-copy))]
|
||||
(assert (nil? (:shape-ref (cths/get-shape file :grid-user))))
|
||||
file))
|
||||
|
||||
(defn- grid-variant-file
|
||||
"Variant main whose root board is the grid. The root has :variant-id and
|
||||
no :shape-ref."
|
||||
[]
|
||||
(let [file (-> (cthf/sample-file :file1 :page-label :page-1)
|
||||
(cths/add-sample-shape :variant :type :frame :is-variant-container true))
|
||||
variant-id (cthi/id :variant)]
|
||||
(-> file
|
||||
(add-grid-with-child :grid-main :rect-main
|
||||
:parent-label :variant
|
||||
:params {:variant-id variant-id :variant-name "Value1"})
|
||||
(cthc/make-component :comp :grid-main)
|
||||
(cthc/update-component :comp {:variant-id variant-id
|
||||
:variant-properties [{:name "Property 1" :value "Value1"}]}))))
|
||||
|
||||
(defn- copied-tracks-event
|
||||
[grid-id]
|
||||
(ptk/reify ::set-copied-tracks
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:workspace-grid-edition grid-id :copied-tracks]
|
||||
{:track-indices [0] :type :column :grid-id grid-id}))))
|
||||
|
||||
(defn- run-grid-track-events
|
||||
[file make-events check-fn]
|
||||
(t/async done
|
||||
(mock/with-mocks {uuid/next cthi/next-uuid}
|
||||
(fn [done']
|
||||
(let [store (ths/setup-store file)
|
||||
events (make-events file)]
|
||||
(ths/run-store
|
||||
store done' events
|
||||
(fn [new-state]
|
||||
(check-fn (cthf/current-page file)
|
||||
(ths/get-file-from-state new-state))))))
|
||||
done)))
|
||||
|
||||
(defn- duplicate-events
|
||||
[grid-label]
|
||||
(fn [file]
|
||||
[(dwsl/duplicate-layout-track [(:id (cths/get-shape file grid-label))] :column 0)]))
|
||||
|
||||
(defn- paste-events
|
||||
[grid-label]
|
||||
(fn [file]
|
||||
(let [grid-id (:id (cths/get-shape file grid-label))]
|
||||
[(copied-tracks-event grid-id)
|
||||
(dwsl/paste-grid-tracks grid-id)])))
|
||||
|
||||
(defn- objects-of
|
||||
[file]
|
||||
(:objects (cthf/current-page file)))
|
||||
|
||||
(defn- column-count
|
||||
[file label]
|
||||
(count (:layout-grid-columns (cths/get-shape file label))))
|
||||
|
||||
(defn- check-blocked
|
||||
[file make-events grid-label]
|
||||
(run-grid-track-events
|
||||
file make-events
|
||||
(fn [page file']
|
||||
(t/is (= (:objects page) (objects-of file')))
|
||||
(t/is (= (column-count file grid-label) (column-count file' grid-label))))))
|
||||
|
||||
(defn- check-works
|
||||
[file make-events grid-label]
|
||||
(run-grid-track-events
|
||||
file make-events
|
||||
(fn [page file']
|
||||
(t/is (> (count (objects-of file')) (count (:objects page))))
|
||||
(t/is (> (column-count file' grid-label) (column-count file grid-label))))))
|
||||
|
||||
(t/deftest duplicate-track-blocked-in-copy-root-grid
|
||||
(check-blocked (grid-root-file) (duplicate-events :grid-copy) :grid-copy))
|
||||
|
||||
(t/deftest paste-tracks-blocked-in-copy-root-grid
|
||||
(check-blocked (grid-root-file) (paste-events :grid-copy) :grid-copy))
|
||||
|
||||
(t/deftest duplicate-track-blocked-in-nested-copy-grid
|
||||
(check-blocked (grid-nested-file) (duplicate-events :grid-copy) :grid-copy))
|
||||
|
||||
(t/deftest paste-tracks-blocked-in-nested-copy-grid
|
||||
(check-blocked (grid-nested-file) (paste-events :grid-copy) :grid-copy))
|
||||
|
||||
(t/deftest duplicate-track-blocked-in-user-added-grid-inside-copy
|
||||
(check-blocked (grid-user-added-in-copy-file) (duplicate-events :grid-user) :grid-user))
|
||||
|
||||
(t/deftest paste-tracks-blocked-in-user-added-grid-inside-copy
|
||||
(check-blocked (grid-user-added-in-copy-file) (paste-events :grid-user) :grid-user))
|
||||
|
||||
(t/deftest duplicate-track-works-in-main-grid
|
||||
(check-works (grid-root-file) (duplicate-events :grid-main) :grid-main))
|
||||
|
||||
(t/deftest paste-tracks-works-in-main-grid
|
||||
(check-works (grid-root-file) (paste-events :grid-main) :grid-main))
|
||||
|
||||
(t/deftest duplicate-track-works-in-main-nested-grid
|
||||
(check-works (grid-nested-file) (duplicate-events :grid-main) :grid-main))
|
||||
|
||||
(t/deftest paste-tracks-works-in-main-nested-grid
|
||||
(check-works (grid-nested-file) (paste-events :grid-main) :grid-main))
|
||||
|
||||
(t/deftest duplicate-track-works-in-variant-main-grid
|
||||
(check-works (grid-variant-file) (duplicate-events :grid-main) :grid-main))
|
||||
|
||||
(t/deftest paste-tracks-works-in-variant-main-grid
|
||||
(check-works (grid-variant-file) (paste-events :grid-main) :grid-main))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user