diff --git a/CHANGES.md b/CHANGES.md index b7c91bc52c..385c693efe 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -40,6 +40,7 @@ - Fix typo [Taiga #11970](https://tree.taiga.io/project/penpot/issue/11970) - Fix typos [Taiga #11971](https://tree.taiga.io/project/penpot/issue/11971) - Fix inconsistent naming for "Flatten" [Taiga #8371](https://tree.taiga.io/project/penpot/issue/8371) +- Layout item tokens should be unapplied when moving out of a layout [Taiga #11012](https://tree.taiga.io/project/penpot/issue/11012) ## 2.9.0 diff --git a/common/src/app/common/logic/shapes.cljc b/common/src/app/common/logic/shapes.cljc index 60913d56f5..bad3065ef4 100644 --- a/common/src/app/common/logic/shapes.cljc +++ b/common/src/app/common/logic/shapes.cljc @@ -405,9 +405,10 @@ (remove #(= % parent-id) all-parents))] (-> changes - ;; Remove layout-item properties when moving a shape outside a layout + ;; Remove layout-item properties and tokens when moving a shape outside a layout (cond-> (not (ctl/any-layout? parent)) - (pcb/update-shapes ids ctl/remove-layout-item-data)) + (-> (pcb/update-shapes ids ctl/remove-layout-item-data) + (pcb/update-shapes ids cto/unapply-layout-item-tokens))) ;; Remove the hide in viewer flag (cond-> (and (not= uuid/zero parent-id) (cfh/frame-shape? parent)) diff --git a/common/src/app/common/test_helpers/tokens.cljc b/common/src/app/common/test_helpers/tokens.cljc index cf23a805ff..e2c6cee4d1 100644 --- a/common/src/app/common/test_helpers/tokens.cljc +++ b/common/src/app/common/test_helpers/tokens.cljc @@ -77,22 +77,25 @@ [file shape-label token-name token-attrs shape-attrs resolved-value] (let [page (thf/current-page file) shape (ths/get-shape file shape-label) - shape' (as-> shape $ - (cto/apply-token-to-shape {:shape $ - :token {:name token-name} - :attributes token-attrs}) - (reduce (fn [shape attr] - (case attr - :stroke-width (set-stroke-width shape resolved-value) - :stroke-color (set-stroke-color shape resolved-value) - :fill (set-fill-color shape resolved-value) - (ctn/set-shape-attr shape attr resolved-value {:ignore-touched true}))) - $ - shape-attrs))] + shape' (when shape + (as-> shape $ + (cto/apply-token-to-shape {:shape $ + :token {:name token-name} + :attributes token-attrs}) + (reduce (fn [shape attr] + (case attr + :stroke-width (set-stroke-width shape resolved-value) + :stroke-color (set-stroke-color shape resolved-value) + :fill (set-fill-color shape resolved-value) + (ctn/set-shape-attr shape attr resolved-value {:ignore-touched true}))) + $ + shape-attrs)))] - (ctf/update-file-data - file - (fn [file-data] - (ctpl/update-page file-data - (:id page) - #(ctst/set-shape % shape')))))) + (if shape' + (ctf/update-file-data + file + (fn [file-data] + (ctpl/update-page file-data + (:id page) + #(ctst/set-shape % shape')))) + file))) diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index cd53eea6ce..384029a688 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -14,22 +14,22 @@ [app.common.schema :as sm] [app.common.uuid :as uuid])) -;; :layout ;; :flex, :grid in the future -;; :layout-flex-dir ;; :row, :row-reverse, :column, :column-reverse -;; :layout-gap-type ;; :simple, :multiple -;; :layout-gap ;; {:row-gap number , :column-gap number} +;; :layout ;; :flex, :grid in the future +;; :layout-flex-dir ;; :row, :row-reverse, :column, :column-reverse +;; :layout-gap-type ;; :simple, :multiple +;; :layout-gap ;; {:row-gap number , :column-gap number} -;; :layout-align-items ;; :start :end :center :stretch -;; :layout-align-content ;; :start :center :end :space-between :space-around :space-evenly :stretch (by default) +;; :layout-align-items ;; :start :end :center :stretch +;; :layout-align-content ;; :start :center :end :space-between :space-around :space-evenly :stretch (by default) ;; :layout-justify-items ;; :start :center :end :space-between :space-around :space-evenly -;; :layout-justify-content ;; :start :center :end :space-between :space-around :space-evenly -;; :layout-wrap-type ;; :wrap, :nowrap -;; :layout-padding-type ;; :simple, :multiple -;; :layout-padding ;; {:p1 num :p2 num :p3 num :p4 num} number could be negative +;; :layout-justify-content ;; :start :center :end :space-between :space-around :space-evenly +;; :layout-wrap-type ;; :wrap, :nowrap +;; :layout-padding-type ;; :simple, :multiple +;; :layout-padding ;; {:p1 num :p2 num :p3 num :p4 num} number could be negative -;; layout-grid-rows ;; vector of grid-track -;; layout-grid-columns ;; vector of grid-track -;; layout-grid-cells ;; map of id->grid-cell +;; layout-grid-rows ;; vector of grid-track +;; layout-grid-columns ;; vector of grid-track +;; layout-grid-cells ;; map of id->grid-cell ;; ITEMS ;; :layout-item-margin ;; {:m1 0 :m2 0 :m3 0 :m4 0} diff --git a/common/src/app/common/types/token.cljc b/common/src/app/common/types/token.cljc index 841d0bc29d..4f0447d449 100644 --- a/common/src/app/common/types/token.cljc +++ b/common/src/app/common/types/token.cljc @@ -83,15 +83,25 @@ (def stroke-width-keys (schema-keys schema:stroke-width)) -(def ^:private schema:sizing - [:map {:title "SizingTokenAttrs"} +(def ^:private schema:sizing-base + [:map {:title "SizingBaseTokenAttrs"} [:width {:optional true} token-name-ref] - [:height {:optional true} token-name-ref] + [:height {:optional true} token-name-ref]]) + +(def ^:private schema:sizing-layout-item + [:map {:title "SizingLayoutItemTokenAttrs"} [:layout-item-min-w {:optional true} token-name-ref] [:layout-item-max-w {:optional true} token-name-ref] [:layout-item-min-h {:optional true} token-name-ref] [:layout-item-max-h {:optional true} token-name-ref]]) +(def ^:private schema:sizing + (-> (reduce mu/union [schema:sizing-base + schema:sizing-layout-item]) + (mu/update-properties assoc :title "SizingTokenAttrs"))) + +(def sizing-layout-item-keys (schema-keys schema:sizing-layout-item)) + (def sizing-keys (schema-keys schema:sizing)) (def ^:private schema:opacity @@ -378,6 +388,13 @@ (defn unapply-token-id [shape attributes] (update shape :applied-tokens d/without-keys attributes)) +(defn unapply-layout-item-tokens + "Unapplies all layout item related tokens from shape." + [shape] + (let [layout-item-attrs (set/union sizing-layout-item-keys + spacing-margin-keys)] + (unapply-token-id shape layout-item-attrs))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; TYPOGRAPHY ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/common/test/common_tests/logic/move_shapes_test.cljc b/common/test/common_tests/logic/move_shapes_test.cljc index 74f04d5394..657b624623 100644 --- a/common/test/common_tests/logic/move_shapes_test.cljc +++ b/common/test/common_tests/logic/move_shapes_test.cljc @@ -12,6 +12,8 @@ [app.common.test-helpers.files :as thf] [app.common.test-helpers.ids-map :as thi] [app.common.test-helpers.shapes :as ths] + [app.common.test-helpers.tokens :as tht] + [app.common.types.tokens-lib :as ctob] [app.common.uuid :as uuid] [clojure.test :as t])) @@ -47,7 +49,6 @@ (t/is (= (:parent-id frame-to-move) uuid/zero)) (t/is (= (:parent-id frame-to-move') (:id frame-parent'))))) - (t/deftest test-relocate-shape-out-of-group (let [;; ==== Setup file (-> (thf/sample-file :file1) @@ -68,7 +69,6 @@ 0 ;; to-index #{(:id circle)}) ;; ids - file' (thf/apply-changes file changes) ;; ==== Get @@ -81,4 +81,134 @@ (t/is (= (:parent-id circle) (:id group))) (t/is (= (:parent-id circle') uuid/zero)) (t/is group) - (t/is (nil? group')))) \ No newline at end of file + (t/is (nil? group')))) + +(t/deftest test-relocate-shape-out-of-layout-manual + (let [;; ==== Setup + file (-> (thf/sample-file :file1) + (tho/add-frame :frame-1 + :layout :flex ;; TODO: those values come from main.data.workspace.shape_layout/default-layout-params + :layout-flex-dir :row ;; it should be good to use it directly, but first it should be moved to common.logic + :layout-gap-type :multiple + :layout-gap {:row-gap 0 :column-gap 0} + :layout-align-items :start + :layout-justify-content :start + :layout-align-content :stretch + :layout-wrap-type :nowrap + :layout-padding-type :simple + :layout-padding {:p1 0 :p2 0 :p3 0 :p4 0}) + (ths/add-sample-shape :circle-1 :parent-label :frame-1 + :layout-item-margin {:m1 10 :m2 10 :m3 10 :m4 10} + :layout-item-margin-type :multiple + :layout-item-h-sizing :auto + :layout-item-v-sizing :auto + :layout-item-max-h 1000 + :layout-item-min-h 100 + :layout-item-max-w 2000 + :layout-item-min-w 200 + :layout-item-absolute false + :layout-item-z-index 10)) + + page (thf/current-page file) + circle (ths/get-shape file :circle-1) + + ;; ==== Action + + changes (cls/generate-relocate (-> (pcb/empty-changes nil) + (pcb/with-page-id (:id page)) + (pcb/with-objects (:objects page))) + uuid/zero ;; parent-id + 0 ;; to-index + #{(:id circle)}) ;; ids + + ;; ==== Get + file' (thf/apply-changes file changes) + circle' (ths/get-shape file' :circle-1)] + + ;; ==== Check + + ;; the layout item attributes are removed + (t/is (nil? (:layout-item-margin circle'))) + (t/is (nil? (:layout-item-margin-type circle'))) + (t/is (nil? (:layout-item-h-sizing circle'))) + (t/is (nil? (:layout-item-v-sizing circle'))) + (t/is (nil? (:layout-item-max-h circle'))) + (t/is (nil? (:layout-item-min-h circle'))) + (t/is (nil? (:layout-item-max-w circle'))) + (t/is (nil? (:layout-item-min-w circle'))) + (t/is (nil? (:layout-item-absolute circle'))) + (t/is (nil? (:layout-item-z-index circle'))))) + +(t/deftest test-relocate-shape-out-of-layout-with-tokens + (let [;; ==== Setup + file (-> (thf/sample-file :file1) + (tht/add-tokens-lib) + (tht/update-tokens-lib #(-> % + (ctob/add-set (ctob/make-token-set :name "test-token-set")) + (ctob/add-theme (ctob/make-token-theme :name "test-theme" + :sets #{"test-token-set"})) + (ctob/set-active-themes #{"/test-theme"}) + (ctob/add-token-in-set "test-token-set" + (ctob/make-token :id (thi/new-id! :token-sizing) + :name "token-sizing" + :type :sizing + :value 10)) + (ctob/add-token-in-set "test-token-set" + (ctob/make-token :id (thi/new-id! :token-spacing) + :name "token-spacing" + :type :spacing + :value 30)))) + (tho/add-frame :frame-1 + :layout :flex ;; TODO: those values come from main.data.workspace.shape_layout/default-layout-params + :layout-flex-dir :row ;; it should be good to use it directly, but first it should be moved to common.logic + :layout-gap-type :multiple + :layout-gap {:row-gap 0 :column-gap 0} + :layout-align-items :start + :layout-justify-content :start + :layout-align-content :stretch + :layout-wrap-type :nowrap + :layout-padding-type :simple + :layout-padding {:p1 0 :p2 0 :p3 0 :p4 0}) + (ths/add-sample-shape :circle-1 :parent-label :frame-1) + (tht/apply-token-to-shape :circle-1 + "token-sizing" + [:layout-item-max-h :layout-item-max-w :layout-item-min-h :layout-item-min-w] + [:layout-item-max-h :layout-item-max-w :layout-item-min-h :layout-item-min-w] + 10) + (tht/apply-token-to-shape :circle-1 + "token-spacing" + [:m1 :m2 :m3 :m4] + [:layout-item-margin] + {:m1 30 :m2 30 :m3 30 :m4 30})) + + page (thf/current-page file) + circle (ths/get-shape file :circle-1) + + ;; ==== Action + + changes (cls/generate-relocate (-> (pcb/empty-changes nil) + (pcb/with-page-id (:id page)) + (pcb/with-objects (:objects page))) + uuid/zero ;; parent-id + 0 ;; to-index + #{(:id circle)}) ;; ids + + + ;; ==== Get + file' (thf/apply-changes file changes) + circle' (ths/get-shape file' :circle-1)] + + ;; ==== Check + + ;; the layout item attributes and tokens are removed + (t/is (empty? (:applied-tokens circle'))) + (t/is (nil? (:layout-item-margin circle'))) + (t/is (nil? (:layout-item-margin-type circle'))) + (t/is (nil? (:layout-item-h-sizing circle'))) + (t/is (nil? (:layout-item-v-sizing circle'))) + (t/is (nil? (:layout-item-max-h circle'))) + (t/is (nil? (:layout-item-min-h circle'))) + (t/is (nil? (:layout-item-max-w circle'))) + (t/is (nil? (:layout-item-min-w circle'))) + (t/is (nil? (:layout-item-absolute circle'))) + (t/is (nil? (:layout-item-z-index circle'))))) \ No newline at end of file