diff --git a/common/src/app/common/logic/libraries.cljc b/common/src/app/common/logic/libraries.cljc index 12da8a88d6..9cae5dfb90 100644 --- a/common/src/app/common/logic/libraries.cljc +++ b/common/src/app/common/logic/libraries.cljc @@ -490,7 +490,7 @@ `:root-main`, and `:reset?`. The main shapes of the children of a copy shape that keeps its own geometry are placed like it first, so they get their expected geometry." - [changes {:keys [container main-container library component reset?] :as ctx} shape-inst shape-main] + [changes {:keys [container main-container component reset?] :as ctx} shape-inst shape-main] (shape-log :debug (:id shape-inst) container :msg "Sync shape direct recursive" :shape-inst (str (:name shape-inst) " " (pretty-uuid (:id shape-inst))) @@ -500,6 +500,7 @@ changes (let [;; a copy shape that keeps its own geometry passes it on to its ;; children, which follow it as they follow their main's + shape-before shape-inst placed (when (and (not reset?) (ctk/touched-group? shape-inst :geometry-group)) (in-main-frame shape-inst (:root-inst ctx) (:root-main ctx))) main-container (cond-> main-container @@ -533,7 +534,7 @@ (reorder-copy-children shape-inst children-main container)) (ctsl/grid-layout? shape-inst) - (update-grid-copy-attrs (:id shape-inst) shape-main library component (not reset?)))))) + (update-grid-copy-attrs shape-before shape-main (not reset?)))))) (defn generate-rename-component "Generate the changes for rename the component with the given id, in the current file library." @@ -1542,13 +1543,30 @@ %) {:ignore-touched true})))) +(defn- add-inherited-grid-cells + "Add default cells in newly inherited tracks without replacing authored cells." + [shape before] + (let [old-rows (count (:layout-grid-rows before)) + old-cols (count (:layout-grid-columns before)) + rows (count (:layout-grid-rows shape)) + cols (count (:layout-grid-columns shape)) + added (concat (for [row (range (inc old-rows) (inc rows)) + column (range 1 (inc cols))] [row column]) + (for [row (range 1 (inc (min old-rows rows))) + column (range (inc old-cols) (inc cols))] [row column]))] + (reduce (fn [shape [row column]] + (if (ctsl/cell-by-row-column shape row column) + shape + (ctsl/create-cells shape [column row 1 1]))) + shape added))) + (defn- update-grid-copy-attrs "Synchronizes the `layout-grid-cells` property from the main shape to the copies" - [changes shape-copy-id shape-main _library _component omit-touched?] + [changes shape-before shape-main omit-touched?] (-> changes (pcb/apply-changes-local) (pcb/update-shapes - [shape-copy-id] + [(:id shape-before)] (fn [shape-copy objects] (let [ids-map (into {} @@ -1570,14 +1588,15 @@ (fn [cell] (update cell :shapes #(filterv child? %))))))) ;; Take cells from main and remap the shapes to assign it to the copy - copy-cells (-> shape-copy :layout-grid-cells (remove-orphan-cells shape-copy)) - main-cells (-> shape-main (ctsl/remap-grid-cells ids-map) :layout-grid-cells)] + copy-cells (-> shape-copy :layout-grid-cells (remove-orphan-cells shape-copy)) + main-cells (-> shape-main (ctsl/remap-grid-cells ids-map) :layout-grid-cells) + keep-cells? (and omit-touched? (contains? (:touched shape-copy) :layout-grid-cells))] (-> shape-copy (assoc :layout-grid-cells - (if (and omit-touched? - (contains? (:touched shape-copy) :layout-grid-cells)) + (if keep-cells? copy-cells main-cells)) + (cond-> keep-cells? (add-inherited-grid-cells shape-before)) (ctsl/assign-cells objects)))) {:ignore-touched true :with-objects? true}))) diff --git a/common/test/common_tests/logic/comp_sync_test.cljc b/common/test/common_tests/logic/comp_sync_test.cljc index 9a32d62012..4cc2fbdaa8 100644 --- a/common/test/common_tests/logic/comp_sync_test.cljc +++ b/common/test/common_tests/logic/comp_sync_test.cljc @@ -18,7 +18,9 @@ [app.common.test-helpers.composable.comp.nodes :as n] [app.common.test-helpers.composable.comp.runner :as r] [app.common.test-helpers.composable.comp.setups :as setup] + [app.common.test-helpers.composable.comp.undo-check :as uc] [app.common.test-helpers.composable.core :as tm] + [app.common.test-helpers.composable.gen :as cg] [app.common.test-helpers.compositions :as tho] [app.common.test-helpers.files :as thf] [app.common.test-helpers.ids-map :as thi] @@ -33,6 +35,50 @@ (t/use-fixtures :each thi/test-fixture) +(t/deftest grid-growth-fills-inherited-tracks-before-assigning-copy-children + (doseq [direction [:row :column] + turn-copy? [false true]] + (let [initial (cg/world [1 0 {:layout :grid :layout-children true :grid-square true}]) + main (n/main-head-of "random-0") + copy (n/nth-copy-head-of "random-0" 0) + other (n/nth-copy-head-of "random-0" 1) + snapshot #(uc/snapshot (tm/file %)) + ops (concat (when (= direction :column) [(n/edit-grid main :direction :column)]) + [(n/edit-grid copy :cell {:row 1 :column 1 :props {:align-self :end}})] + (when turn-copy? [(n/edit-grid copy :direction (if (= direction :row) :column :row))]) + [(n/add-child main :extra-a :width 20 :height 20) + (n/add-child main :extra-b :width 20 :height 20)]) + grown (reduce (fn [before op] + (let [after (-> before (r/step op) r/settle) + undone (r/step after (n/undo)) + redone (r/step undone (n/redo))] + (t/is (< (r/undo-depth before) (r/undo-depth after))) + (t/is (uc/same-state? (snapshot before) (snapshot undone))) + (t/is (uc/same-state? (snapshot after) (snapshot redone))) + after)) initial ops) + shapes (mapv #(tm/shape-by-id grown (% grown)) [main copy other]) + dims (if (= direction :row) [2 3] [3 2]) + coords (set (for [row (range 1 (inc (first dims))) + column (range 1 (inc (second dims)))] [row column])) + objects (:objects (thf/current-page (tm/file grown))) + main-ids (set (:shapes (first shapes))) + component (:component-id (first shapes)) + synced (r/settle grown #{component})] + (doseq [shape shapes] + (t/is (= dims (mapv #(count (get shape %)) [:layout-grid-rows :layout-grid-columns]))) + (t/is (= coords (set (map (juxt :row :column) (vals (:layout-grid-cells shape)))))) + (t/is (= main-ids (set (map #(or (:shape-ref (get objects %)) %) + (mapcat :shapes (vals (:layout-grid-cells shape))))))) + (t/is (= (frequencies main-ids) + (frequencies (map #(or (:shape-ref (get objects %)) %) + (mapcat :shapes (vals (:layout-grid-cells shape)))))))) + (t/is (= :end (:align-self (first (filter #(= [1 1] [(:row %) (:column %)]) + (vals (:layout-grid-cells (second shapes)))))))) + (t/is (= (cond-> #{:layout-grid-cells} turn-copy? (conj :layout-grid-dir)) + (:touched (second shapes)))) + (t/is (empty? (:touched (nth shapes 2)))) + (t/is (uc/same-state? (snapshot grown) (snapshot synced)))))) + (t/deftest sync-keeps-a-real-subpixel-position-change (let [initial (setup/simple-component-with-labeled-copy) original (tm/resolve-shape initial :copy-child) diff --git a/frontend/test/frontend_tests/composable_tests/grid_layout_test.cljs b/frontend/test/frontend_tests/composable_tests/grid_layout_test.cljs index 17c69f6460..6291e7fe09 100644 --- a/frontend/test/frontend_tests/composable_tests/grid_layout_test.cljs +++ b/frontend/test/frontend_tests/composable_tests/grid_layout_test.cljs @@ -8,6 +8,7 @@ (:require [app.common.logic.shapes :as cls] [app.common.test-helpers.components :as thc] + [app.common.test-helpers.composable.comp.nodes :as n] [app.common.test-helpers.composable.comp.undo-check :as uc] [app.common.test-helpers.composable.core :as tm] [app.common.test-helpers.compositions :as tho] @@ -95,6 +96,46 @@ [(ptk/data-event :layout/update {:ids [(thi/id :main) (thi/id :copy) (thi/id :other)]})])))) (t/is (= (get-cells :main) (get-cells :copy) (get-cells :other)))) +(t/deftest ^:async grid-growth-keeps-overrides-within-inherited-tracks + (await + (mock/with-mocks* {rp/cmd! mock/rpc-cmd-mock rx/timer mock/timer-mock} + (doseq [direction [:row :column] + turn-copy? [false true]] + (await (install-grid)) + (let [initial (tm/make-situation (ftm/current-file)) + _ (ftm/uninstall!) + ops (concat (when (= direction :column) [(n/edit-grid :main :direction :column)]) + [(n/edit-grid :copy :cell {:row 1 :column 1 :props {:align-self :end}})] + (when turn-copy? [(n/edit-grid :copy :direction (if (= direction :row) :column :row))]) + [(n/add-child :main :extra-a :width 20 :height 20) + (n/add-child :main :extra-b :width 20 :height 20)]) + grown (loop [live (ftm/start-generated initial) ops ops] + (if-let [op (first ops)] + (let [next (await (ftm/run-generated-step live op))] + (t/is (some? next)) + (t/is (empty? (uc/failures next))) + (recur next (rest ops))) + live)) + dims (if (= direction :row) [2 3] [3 2]) + coords (set (for [row (range 1 (inc (first dims))) + column (range 1 (inc (second dims)))] [row column])) + members (set (:shapes (get-shape :main)))] + (doseq [label [:main :copy :other]] + (t/is (= dims (mapv #(count (get (get-shape label) %)) [:layout-grid-rows :layout-grid-columns]))) + (t/is (= coords (set (map (juxt :row :column) (get-cells label))))) + (t/is (= members (set (mapcat :shapes (get-cells label))))) + (t/is (= (frequencies members) (frequencies (mapcat :shapes (get-cells label)))))) + (t/is (= :end (:align-self (first (get-cells :copy))))) + (t/is (= (cond-> #{:layout-grid-cells} turn-copy? (conj :layout-grid-dir)) + (:touched (get-shape :copy)))) + (t/is (empty? (:touched (get-shape :other)))) + (let [before (ftm/snapshot) + file-id (:id (ftm/current-file))] + (t/is (= :settled (await (ftm/await-step [(dwl/sync-file file-id file-id :components (thi/id :component))])))) + (t/is (uc/same-state? before (ftm/snapshot)))) + (t/is (empty? (uc/failures (await (ftm/finish-generated (ftm/refresh-generated grown)))))) + (ftm/uninstall!)))))) + (t/deftest ^:async grid-direction-overrides-survive-main-edits-update-main-and-reset (await (mock/with-mocks* {rp/cmd! mock/rpc-cmd-mock rx/timer mock/timer-mock}