From 351f7fd1bb893075de7a3c901b5ad5e6944b8bc8 Mon Sep 17 00:00:00 2001
From: "alonso.torres" <alonso.torres@kaleidos.net>
Date: Tue, 24 Oct 2023 16:58:28 +0200
Subject: [PATCH] :sparkles: Create grid from selection

---
 .../common/geom/{shapes => }/modifiers.cljc   |   2 +-
 common/src/app/common/geom/shapes.cljc        |   4 -
 .../app/common/geom/shapes/flex_layout.cljc   |   4 +-
 .../geom/shapes/flex_layout/params.cljc       |  97 +++++++
 .../app/common/geom/shapes/grid_layout.cljc   |   2 +
 .../geom/shapes/grid_layout/layout_data.cljc  |   2 +-
 .../geom/shapes/grid_layout/params.cljc       | 166 +++++++++++
 common/src/app/common/pages/changes.cljc      |   2 +-
 common/src/app/common/types/shape_tree.cljc   |   3 +-
 .../app/main/data/workspace/modifiers.cljs    |  24 +-
 .../app/main/data/workspace/shape_layout.cljs | 262 +++++-------------
 .../app/main/data/workspace/transforms.cljs   |   5 +-
 .../app/main/ui/workspace/context_menu.cljs   |  18 +-
 .../options/menus/layout_container.cljs       |  31 ++-
 .../viewport/grid_layout_editor.cljs          |   1 -
 frontend/src/app/util/code_gen/style_css.cljs |   1 +
 .../app/util/code_gen/style_css_values.cljs   |   5 +
 17 files changed, 395 insertions(+), 234 deletions(-)
 rename common/src/app/common/geom/{shapes => }/modifiers.cljc (99%)
 create mode 100644 common/src/app/common/geom/shapes/flex_layout/params.cljc
 create mode 100644 common/src/app/common/geom/shapes/grid_layout/params.cljc

diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/modifiers.cljc
similarity index 99%
rename from common/src/app/common/geom/shapes/modifiers.cljc
rename to common/src/app/common/geom/modifiers.cljc
index 40727cf44..6a93c6af9 100644
--- a/common/src/app/common/geom/shapes/modifiers.cljc
+++ b/common/src/app/common/geom/modifiers.cljc
@@ -4,7 +4,7 @@
 ;;
 ;; Copyright (c) KALEIDOS INC
 
-(ns app.common.geom.shapes.modifiers
+(ns app.common.geom.modifiers
   (:require
    [app.common.data :as d]
    [app.common.data.macros :as dm]
diff --git a/common/src/app/common/geom/shapes.cljc b/common/src/app/common/geom/shapes.cljc
index 1bf496b99..5555f9386 100644
--- a/common/src/app/common/geom/shapes.cljc
+++ b/common/src/app/common/geom/shapes.cljc
@@ -15,7 +15,6 @@
    [app.common.geom.shapes.constraints :as gct]
    [app.common.geom.shapes.corners :as gsc]
    [app.common.geom.shapes.intersect :as gsi]
-   [app.common.geom.shapes.modifiers :as gsm]
    [app.common.geom.shapes.path :as gsp]
    [app.common.geom.shapes.transforms :as gtr]
    [app.common.math :as mth]))
@@ -203,8 +202,5 @@
 (dm/export gsc/shape-corners-1)
 (dm/export gsc/shape-corners-4)
 
-;; Modifiers
-(dm/export gsm/set-objects-modifiers)
-
 ;; Rect
 (dm/export grc/rect->points)
diff --git a/common/src/app/common/geom/shapes/flex_layout.cljc b/common/src/app/common/geom/shapes/flex_layout.cljc
index 3be7382da..52eb913ab 100644
--- a/common/src/app/common/geom/shapes/flex_layout.cljc
+++ b/common/src/app/common/geom/shapes/flex_layout.cljc
@@ -10,7 +10,8 @@
    [app.common.geom.shapes.flex-layout.bounds :as fbo]
    [app.common.geom.shapes.flex-layout.drop-area :as fdr]
    [app.common.geom.shapes.flex-layout.layout-data :as fld]
-   [app.common.geom.shapes.flex-layout.modifiers :as fmo]))
+   [app.common.geom.shapes.flex-layout.modifiers :as fmo]
+   [app.common.geom.shapes.flex-layout.params :as fp]))
 
 (dm/export fbo/layout-content-bounds)
 (dm/export fbo/layout-content-points)
@@ -19,3 +20,4 @@
 (dm/export fdr/get-drop-areas)
 (dm/export fld/calc-layout-data)
 (dm/export fmo/layout-child-modifiers)
+(dm/export fp/calculate-params)
diff --git a/common/src/app/common/geom/shapes/flex_layout/params.cljc b/common/src/app/common/geom/shapes/flex_layout/params.cljc
new file mode 100644
index 000000000..4293dd53d
--- /dev/null
+++ b/common/src/app/common/geom/shapes/flex_layout/params.cljc
@@ -0,0 +1,97 @@
+;; 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.common.geom.shapes.flex-layout.params
+  (:require
+   [app.common.data :as d]
+   [app.common.geom.point :as gpt]
+   [app.common.geom.shapes.common :as gco]
+   [app.common.math :as mth]
+   [app.common.types.shape-tree :as ctt]))
+
+(defn calculate-params
+  "Given the shapes calculate its flex parameters (horizontal vs vertical, gaps, etc)"
+  ([objects shapes]
+   (calculate-params objects shapes nil))
+
+  ([objects shapes parent]
+   (when (d/not-empty? shapes)
+     (let [points
+           (->> shapes
+                (map :id)
+                (ctt/sort-z-index objects)
+                (map (comp gco/shape->center (d/getf objects))))
+
+           start (first points)
+           end (reduce (fn [acc p] (gpt/add acc (gpt/to-vec start p))) points)
+
+           angle (gpt/signed-angle-with-other
+                  (gpt/to-vec start end)
+                  (gpt/point 1 0))
+
+           angle (mod angle 360)
+
+           t1 (min (abs (-  angle 0)) (abs (-  angle 360)))
+           t2 (abs (- angle 90))
+           t3 (abs (- angle 180))
+           t4 (abs (- angle 270))
+
+           tmin (min t1 t2 t3 t4)
+
+           direction
+           (cond
+             (mth/close? tmin t1) :row
+             (mth/close? tmin t2) :column-reverse
+             (mth/close? tmin t3) :row-reverse
+             (mth/close? tmin t4) :column)
+
+           selrects (->> shapes
+                         (mapv :selrect))
+           min-x (->> selrects
+                      (mapv #(min (:x1 %) (:x2 %)))
+                      (apply min))
+           max-x (->> selrects
+                      (mapv #(max (:x1 %) (:x2 %)))
+                      (apply max))
+           all-width (->> selrects
+                          (map :width)
+                          (reduce +))
+           column-gap (if (and (> (count shapes) 1)
+                               (or (= direction :row) (= direction :row-reverse)))
+                        (/ (- (- max-x min-x) all-width)
+                           (dec (count shapes)))
+                        0)
+
+           min-y (->> selrects
+                      (mapv #(min (:y1 %) (:y2 %)))
+                      (apply min))
+           max-y (->> selrects
+                      (mapv #(max (:y1 %) (:y2 %)))
+                      (apply max))
+           all-height (->> selrects
+                           (map :height)
+                           (reduce +))
+           row-gap (if (and (> (count shapes) 1)
+                            (or (= direction :column) (= direction :column-reverse)))
+                     (/ (- (- max-y min-y) all-height)
+                        (dec (count shapes)))
+                     0)
+
+           layout-gap {:row-gap (max row-gap 0)
+                       :column-gap (max column-gap 0)}
+
+           parent-selrect (:selrect parent)
+
+           padding (when (and (not (nil? parent)) (> (count shapes) 0))
+                     {:p1 (min (- min-y (:y1 parent-selrect)) (- (:y2 parent-selrect) max-y))
+                      :p2 (min (- min-x (:x1 parent-selrect)) (- (:x2 parent-selrect) max-x))})]
+
+       (cond-> {:layout-flex-dir direction :layout-gap layout-gap}
+         (not (nil? padding))
+         (assoc :layout-padding {:p1 (:p1 padding)
+                                 :p2 (:p2 padding)
+                                 :p3 (:p1 padding)
+                                 :p4 (:p2 padding)}))))))
diff --git a/common/src/app/common/geom/shapes/grid_layout.cljc b/common/src/app/common/geom/shapes/grid_layout.cljc
index 5de5a5ce3..29eef9699 100644
--- a/common/src/app/common/geom/shapes/grid_layout.cljc
+++ b/common/src/app/common/geom/shapes/grid_layout.cljc
@@ -9,6 +9,7 @@
    [app.common.data.macros :as dm]
    [app.common.geom.shapes.grid-layout.bounds :as glpb]
    [app.common.geom.shapes.grid-layout.layout-data :as glld]
+   [app.common.geom.shapes.grid-layout.params :as glpr]
    [app.common.geom.shapes.grid-layout.positions :as glp]))
 
 (dm/export glld/calc-layout-data)
@@ -19,3 +20,4 @@
 (dm/export glp/cell-bounds)
 (dm/export glpb/layout-content-points)
 (dm/export glpb/layout-content-bounds)
+(dm/export glpr/calculate-params)
diff --git a/common/src/app/common/geom/shapes/grid_layout/layout_data.cljc b/common/src/app/common/geom/shapes/grid_layout/layout_data.cljc
index 881bf1c8e..51ce068aa 100644
--- a/common/src/app/common/geom/shapes/grid_layout/layout_data.cljc
+++ b/common/src/app/common/geom/shapes/grid_layout/layout_data.cljc
@@ -590,7 +590,7 @@
   [{:keys [origin row-tracks column-tracks shape-cells]} _transformed-parent-bounds [_ child]]
 
   (let [grid-cell (get shape-cells (:id child))]
-    (when (some? grid-cell)
+    (when (and (some? grid-cell) (d/not-empty? grid-cell))
       (let [column (nth column-tracks (dec (:column grid-cell)) nil)
             row (nth row-tracks (dec (:row grid-cell)) nil)
 
diff --git a/common/src/app/common/geom/shapes/grid_layout/params.cljc b/common/src/app/common/geom/shapes/grid_layout/params.cljc
new file mode 100644
index 000000000..b50fbf448
--- /dev/null
+++ b/common/src/app/common/geom/shapes/grid_layout/params.cljc
@@ -0,0 +1,166 @@
+;; 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.common.geom.shapes.grid-layout.params
+  (:require
+   [app.common.geom.point :as gpt]
+   [app.common.geom.rect :as grc]
+   [app.common.geom.shapes.common :as gco]
+   [app.common.geom.shapes.points :as gpo]
+   [app.common.math :as mth]
+   [app.common.types.shape.layout :as ctl]
+   [clojure.set :as set]))
+
+;; Small functions to help with ranges
+(defn rect->range
+  "Creates ranges"
+  [axis rect]
+  (let [start (get (gpt/point rect) axis)]
+    (if (= axis :x)
+      [start (+ start (:width rect))]
+      [start (+ start (:height rect))])))
+
+(defn overlaps-range?
+  "Return true if the ranges overlaps in the given axis"
+  [axis [start-a end-a] rect]
+  (let [[start-b end-b] (rect->range axis rect)]
+    (or (< start-a start-b end-a)
+        (< start-b start-a end-b)
+        (mth/close? start-a start-b)
+        (mth/close? end-a end-b))))
+
+(defn join-range
+  "Creates a new range given the rect"
+  [axis [start-a end-a :as range] rect]
+  (if (not range)
+    (rect->range axis rect)
+    (let [[start-b end-b] (rect->range axis rect)]
+      [(min start-a start-b) (max end-a end-b)])))
+
+(defn size-range
+  [[start end]]
+  (- end start))
+
+(defn calculate-tracks
+  "Given the geometry and the axis calculates the tracks for the given shapes"
+  [axis shapes-by-axis]
+  (loop [pending (seq shapes-by-axis)
+         result []
+         index 1
+         current-track #{}
+         current-range nil]
+    (if pending
+      (let [[next-shape rect :as next-shape+rects] (first pending)]
+        
+        (if (or (not current-range) (overlaps-range? axis current-range rect))
+          ;; Add shape to current row
+          (let [current-track (conj current-track (:id next-shape))
+                current-range (join-range axis current-range rect)]
+            (recur (next pending) result index current-track current-range))
+          
+          ;; New row
+          (recur (next pending)
+                 (conj result {:index index
+                               :shapes current-track
+                               :size (size-range current-range)})
+                 (inc index)
+                 #{(:id next-shape)}
+                 (rect->range axis rect))))
+
+      ;; Add the still ongoing row
+      (conj result {:index index
+                    :shapes current-track
+                    :size (size-range current-range)}))))
+
+(defn assign-shape-cells
+  "Create cells for the defined tracks and assign the shapes to these cells"
+  [params rows cols]
+
+  (let [assign-cell
+        (fn [[params auto?] row column]
+          (let [row-num    (:index row)
+                column-num (:index column)
+                cell       (ctl/cell-by-row-column params row-num column-num)
+                shape      (first (set/intersection (:shapes row) (:shapes column)))
+                auto?      (and auto? (some? shape))]
+
+            [(cond-> params
+               (some? shape)
+               (assoc-in [:layout-grid-cells (:id cell) :shapes] [shape])
+
+               (not auto?)
+               (assoc-in [:layout-grid-cells (:id cell) :position] :manual))
+             auto?]))
+
+        [params _]
+        (->> rows
+             (reduce
+              (fn [result row]
+                (->> cols
+                     (reduce
+                      #(assign-cell %1 row %2)
+                      result)))
+              [params true]))]
+    params))
+
+(defn calculate-params
+  "Given the shapes calculate its grid parameters (horizontal vs vertical, gaps, etc)"
+  ([objects shapes]
+   (calculate-params objects shapes nil))
+
+  ([_objects shapes parent]
+   (if (empty? shapes)
+     (-> {:layout-grid-columns [{:type :auto} {:type :auto}]
+          :layout-grid-rows [{:type :auto} {:type :auto}]}
+         (ctl/create-cells [1 1 2 2]))
+
+     (let [all-shapes-rect (gco/shapes->rect shapes)
+           shapes+bounds
+           (->> shapes
+                (map #(vector % (grc/points->rect (get % :points)))))
+
+           shapes-by-x
+           (->> shapes+bounds
+                (sort-by (comp :x second)))
+
+           shapes-by-y
+           (->> shapes+bounds
+                (sort-by (comp :y second)))
+
+           cols (calculate-tracks :x shapes-by-x)
+           rows (calculate-tracks :y shapes-by-y)
+
+           num-cols (count cols)
+           num-rows (count rows)
+
+           total-cols-width (->> cols (reduce #(+ %1 (:size %2)) 0))
+           total-rows-height (->> rows (reduce #(+ %1 (:size %2)) 0))
+
+           column-gap
+           (if (= num-cols 1)
+             0
+             (/ (- (:width all-shapes-rect) total-cols-width) (dec num-cols)))
+
+           row-gap
+           (if (= num-rows 1)
+             0
+             (/ (- (:height all-shapes-rect) total-rows-height) (dec num-rows)))
+
+           layout-grid-rows (mapv (constantly (array-map :type :auto)) rows)
+           layout-grid-columns (mapv (constantly (array-map :type :auto)) cols)
+
+           parent-childs-vector (gpt/to-vec (gpo/origin (:points parent)) (gpt/point all-shapes-rect))
+           p-left (:x parent-childs-vector)
+           p-top  (:y parent-childs-vector)]
+
+       (-> {:layout-grid-columns layout-grid-columns
+            :layout-grid-rows layout-grid-rows
+            :layout-gap {:row-gap row-gap
+                         :column-gap column-gap}
+            :layout-padding {:p1 p-top :p2 p-left :p3 p-top :p4 p-left}
+            :layout-grid-dir (if (> num-cols num-rows) :row :column)}
+           (ctl/create-cells [1 1 num-cols num-rows])
+           (assign-shape-cells rows cols))))))
diff --git a/common/src/app/common/pages/changes.cljc b/common/src/app/common/pages/changes.cljc
index 8a32ef120..f8e6ad811 100644
--- a/common/src/app/common/pages/changes.cljc
+++ b/common/src/app/common/pages/changes.cljc
@@ -373,7 +373,7 @@
 
             (cond-> objects
               @changed?
-              (assoc-in [parent-id :shapes] new-shapes))))
+              (d/assoc-in-when [parent-id :shapes] new-shapes))))
 
         check-modify-component
         (fn [data]
diff --git a/common/src/app/common/types/shape_tree.cljc b/common/src/app/common/types/shape_tree.cljc
index 2798c9ff8..adefe1d07 100644
--- a/common/src/app/common/types/shape_tree.cljc
+++ b/common/src/app/common/types/shape_tree.cljc
@@ -227,8 +227,7 @@
 
   ([objects ids {:keys [bottom-frames?] :as options
                  :or   {bottom-frames? false}}]
-   (letfn [
-           (comp [id-a id-b]
+   (letfn [(comp [id-a id-b]
              (cond
                (= id-a id-b)
                0
diff --git a/frontend/src/app/main/data/workspace/modifiers.cljs b/frontend/src/app/main/data/workspace/modifiers.cljs
index 2c11d0a8a..dfb031e5d 100644
--- a/frontend/src/app/main/data/workspace/modifiers.cljs
+++ b/frontend/src/app/main/data/workspace/modifiers.cljs
@@ -9,6 +9,7 @@
   (:require
    [app.common.data :as d]
    [app.common.data.macros :as dm]
+   [app.common.geom.modifiers :as gm]
    [app.common.geom.point :as gpt]
    [app.common.geom.rect :as grc]
    [app.common.geom.shapes :as gsh]
@@ -328,11 +329,11 @@
      (as-> objects $
        (apply-text-modifiers $ (get state :workspace-text-modifier))
        ;;(apply-path-modifiers $ (get-in state [:workspace-local :edit-path]))
-       (gsh/set-objects-modifiers modif-tree $ (merge
-                                                params
-                                                {:ignore-constraints ignore-constraints
-                                                 :snap-pixel? snap-pixel?
-                                                 :snap-precision snap-precision}))))))
+       (gm/set-objects-modifiers modif-tree $ (merge
+                                               params
+                                               {:ignore-constraints ignore-constraints
+                                                :snap-pixel? snap-pixel?
+                                                :snap-precision snap-precision}))))))
 
 (defn- calculate-update-modifiers
   [old-modif-tree state ignore-constraints ignore-snap-pixel modif-tree]
@@ -348,7 +349,14 @@
         objects
         (-> objects
             (apply-text-modifiers (get state :workspace-text-modifier)))]
-    (gsh/set-objects-modifiers old-modif-tree modif-tree objects {:ignore-constraints ignore-constraints :snap-pixel? snap-pixel? :snap-precision snap-precision})))
+
+    (gm/set-objects-modifiers
+     old-modif-tree
+     modif-tree
+     objects
+     {:ignore-constraints ignore-constraints
+      :snap-pixel? snap-pixel?
+      :snap-precision snap-precision})))
 
 (defn update-modifiers
   ([modif-tree]
@@ -405,7 +413,7 @@
 
              modif-tree
              (-> (build-modif-tree ids objects get-modifier)
-                 (gsh/set-objects-modifiers objects))]
+                 (gm/set-objects-modifiers objects))]
 
          (assoc state :workspace-modifiers modif-tree))))))
 
@@ -432,7 +440,7 @@
 
              modif-tree
              (-> (build-modif-tree ids objects get-modifier)
-                 (gsh/set-objects-modifiers objects))]
+                 (gm/set-objects-modifiers objects))]
 
          (assoc state :workspace-modifiers modif-tree))))))
 
diff --git a/frontend/src/app/main/data/workspace/shape_layout.cljs b/frontend/src/app/main/data/workspace/shape_layout.cljs
index e69d6c914..3f55718a1 100644
--- a/frontend/src/app/main/data/workspace/shape_layout.cljs
+++ b/frontend/src/app/main/data/workspace/shape_layout.cljs
@@ -9,13 +9,11 @@
    [app.common.colors :as clr]
    [app.common.data :as d]
    [app.common.data.macros :as dm]
-   [app.common.geom.point :as gpt]
-   [app.common.geom.shapes :as gsh]
-   [app.common.math :as mth]
+   [app.common.geom.shapes.flex-layout :as flex]
+   [app.common.geom.shapes.grid-layout :as grid]
    [app.common.pages.helpers :as cph]
    [app.common.types.component :as ctc]
    [app.common.types.modifiers :as ctm]
-   [app.common.types.shape-tree :as ctt]
    [app.common.types.shape.layout :as ctl]
    [app.common.uuid :as uuid]
    [app.main.data.workspace.changes :as dwc]
@@ -70,140 +68,27 @@
    :layout-grid-columns    []})
 
 (defn get-layout-initializer
-  [type from-frame?]
-  (let [initial-layout-data
+  [type from-frame? objects]
+  (let [[initial-layout-data calculate-params]
         (case type
-          :flex initial-flex-layout
-          :grid initial-grid-layout)]
+          :flex [initial-flex-layout flex/calculate-params]
+          :grid [initial-grid-layout grid/calculate-params])]
+
     (fn [shape]
-      (-> shape
-          (merge initial-layout-data)
-          (cond-> (= type :grid) ctl/assign-cells)
-          ;; If the original shape is not a frame we set clip content and show-viewer to false
-          (cond-> (not from-frame?)
-            (assoc :show-content true :hide-in-viewer true))))))
-
-
-(defn shapes->flex-params
-  "Given the shapes calculate its flex parameters (horizontal vs vertical, gaps, etc)"
-  ([objects shapes]
-   (shapes->flex-params objects shapes nil))
-  ([objects shapes parent]
-   (when (d/not-empty? shapes)
-     (let [points
-           (->> shapes
-                (map :id)
-                (ctt/sort-z-index objects)
-                (map (comp gsh/shape->center (d/getf objects))))
-
-           start (first points)
-           end (reduce (fn [acc p] (gpt/add acc (gpt/to-vec start p))) points)
-
-           angle (gpt/signed-angle-with-other
-                  (gpt/to-vec start end)
-                  (gpt/point 1 0))
-
-           angle (mod angle 360)
-
-           t1 (min (abs (-  angle 0)) (abs (-  angle 360)))
-           t2 (abs (- angle 90))
-           t3 (abs (- angle 180))
-           t4 (abs (- angle 270))
-
-           tmin (min t1 t2 t3 t4)
-
-           direction
-           (cond
-             (mth/close? tmin t1) :row
-             (mth/close? tmin t2) :column-reverse
-             (mth/close? tmin t3) :row-reverse
-             (mth/close? tmin t4) :column)
-
-           selrects (->> shapes
-                         (mapv :selrect))
-           min-x (->> selrects
-                      (mapv #(min (:x1 %) (:x2 %)))
-                      (apply min))
-           max-x (->> selrects
-                      (mapv #(max (:x1 %) (:x2 %)))
-                      (apply max))
-           all-width (->> selrects
-                          (map :width)
-                          (reduce +))
-           column-gap (if (and (> (count shapes) 1)
-                               (or (= direction :row) (= direction :row-reverse)))
-                        (/ (- (- max-x min-x) all-width)
-                           (dec (count shapes)))
-                        0)
-
-           min-y (->> selrects
-                      (mapv #(min (:y1 %) (:y2 %)))
-                      (apply min))
-           max-y (->> selrects
-                      (mapv #(max (:y1 %) (:y2 %)))
-                      (apply max))
-           all-height (->> selrects
-                           (map :height)
-                           (reduce +))
-           row-gap (if (and (> (count shapes) 1)
-                            (or (= direction :column) (= direction :column-reverse)))
-                     (/ (- (- max-y min-y) all-height)
-                        (dec (count shapes)))
-                     0)
-
-           layout-gap {:row-gap (max row-gap 0) :column-gap (max column-gap 0)}
-
-           parent-selrect (:selrect parent)
-           padding (when (and (not (nil? parent)) (> (count shapes) 0))
-                     {:p1 (min (- min-y (:y1 parent-selrect)) (- (:y2 parent-selrect) max-y))
-                      :p2 (min (- min-x (:x1 parent-selrect)) (- (:x2 parent-selrect) max-x))})]
-
-       (cond-> {:layout-flex-dir direction :layout-gap layout-gap}
-         (not (nil? padding))
-         (assoc :layout-padding {:p1 (:p1 padding) :p2 (:p2 padding) :p3 (:p1 padding) :p4 (:p2 padding)}))))))
-
-(defn shapes->grid-params
-  "Given the shapes calculate its flex parameters (horizontal vs vertical, gaps, etc)"
-  ([objects shapes]
-   (shapes->flex-params objects shapes nil))
-  ([_objects shapes _parent]
-   (if (empty? shapes)
-     (ctl/create-cells
-      {:layout-grid-columns [{:type :auto} {:type :auto}]
-       :layout-grid-rows [{:type :auto} {:type :auto}]}
-      [1 1 2 2])
-     {})))
-
-(defn create-layout-from-id
-  [ids type from-frame?]
-  (ptk/reify ::create-layout-from-id
-    ptk/WatchEvent
-    (watch [_ state _]
-      (let [objects         (wsh/lookup-page-objects state)
-            children-ids    (into [] (mapcat #(get-in objects [% :shapes])) ids)
-            children-shapes (map (d/getf objects) children-ids)
-            parent          (get objects (first ids))
-            layout-params   (case type
-                              :flex (shapes->flex-params objects children-shapes parent)
-                              :grid (shapes->grid-params objects children-shapes parent))
-            undo-id         (js/Symbol)]
-        (rx/of (dwu/start-undo-transaction undo-id)
-               (dwc/update-shapes ids (get-layout-initializer type from-frame?))
-               (dwc/update-shapes
-                ids
-                (fn [shape]
-                  (-> shape
-                      (cond-> (not from-frame?)
-                        (assoc :layout-item-h-sizing :auto
-                               :layout-item-v-sizing :auto))
-                      (merge layout-params)
-                      (cond-> (= type :grid)
-                        (-> (ctl/assign-cells)
-                            (ctl/reorder-grid-children))))))
-               (ptk/data-event :layout/update ids)
-               (dwc/update-shapes children-ids #(dissoc % :constraints-h :constraints-v))
-               (dwu/commit-undo-transaction undo-id))))))
+      (let [shape
+            (-> shape
+                (merge initial-layout-data)
+                
+                ;; (cond-> (= type :grid) (-> ctl/assign-cells ctl/reorder-grid-children))
+                ;; If the original shape is not a frame we set clip content and show-viewer to false
+                (cond-> (not from-frame?)
+                  (assoc :show-content true :hide-in-viewer true)))
+            
+            params (calculate-params objects (cph/get-immediate-children objects (:id shape)) shape)]
 
+        (cond-> (merge shape params)
+          (= type :grid) (-> ctl/assign-cells ctl/reorder-grid-children)))
+      )))
 
 ;; Never call this directly but through the data-event `:layout/update`
 ;; Otherwise a lot of cycle dependencies could be generated
@@ -236,6 +121,22 @@
   []
   (ptk/reify ::finalize))
 
+(defn create-layout-from-id
+  [id type from-frame?]
+  (assert (uuid? id) (str id))
+  (ptk/reify ::create-layout-from-id
+    ptk/WatchEvent
+    (watch [_ state _]
+      (let [objects            (wsh/lookup-page-objects state)
+            parent             (get objects id)
+            undo-id            (js/Symbol)
+            layout-initializer (get-layout-initializer type from-frame? objects)]
+
+        (rx/of (dwu/start-undo-transaction undo-id)
+               (dwc/update-shapes [id] layout-initializer)
+               (dwc/update-shapes (dm/get-prop parent :shapes) #(dissoc % :constraints-h :constraints-v))
+               (ptk/data-event :layout/update [id])
+               (dwu/commit-undo-transaction undo-id))))))
 
 (defn create-layout-from-selection
   [type]
@@ -253,66 +154,41 @@
             has-mask?       (->> selected-shapes (d/seek cph/mask-shape?))
             is-mask?        (and single? has-mask?)
             has-component?  (some true? (map ctc/instance-root? selected-shapes))
-            is-component?   (and single? has-component?)]
+            is-component?   (and single? has-component?)
 
-        (if (and (not is-component?) is-group? (not is-mask?))
-          (let [new-shape-id (uuid/next)
-                parent-id    (:parent-id (first selected-shapes))
-                shapes-ids   (:shapes (first selected-shapes))
-                ordered-ids  (into (d/ordered-set) shapes-ids)
-                undo-id      (js/Symbol)
-                group-index  (cph/get-index-replacement selected objects)]
-            (rx/of
-             (dwu/start-undo-transaction undo-id)
-             (dwse/select-shapes ordered-ids)
-             (dws/create-artboard-from-selection new-shape-id parent-id group-index)
-             (cl/remove-all-fills [new-shape-id] {:color clr/black
-                                                  :opacity 1})
-             (create-layout-from-id [new-shape-id] type false)
-             (dwc/update-shapes
-              [new-shape-id]
-              (fn [shape]
-                (-> shape
-                    (assoc :layout-item-h-sizing :auto
-                           :layout-item-v-sizing :auto))))
-             ;; Set the children to fixed to remove strange interactions
-             (dwc/update-shapes
-              selected
-              (fn [shape]
-                (-> shape
-                    (assoc :layout-item-h-sizing :fix
-                           :layout-item-v-sizing :fix))))
+            new-shape-id (uuid/next)
+            undo-id      (js/Symbol)]
 
-             (ptk/data-event :layout/update [new-shape-id])
-             (dws/delete-shapes page-id selected)
-             (dwu/commit-undo-transaction undo-id)))
+        (rx/concat
+         (rx/of (dwu/start-undo-transaction undo-id))
+         (if (and is-group? (not is-component?) (not is-mask?))
+           ;; Create layout from a group:
+           ;;  When creating a layout from a group we remove the group and create the layout with its children
+           (let [parent-id    (:parent-id (first selected-shapes))
+                 shapes-ids   (:shapes (first selected-shapes))
+                 ordered-ids  (into (d/ordered-set) shapes-ids)
+                 group-index  (cph/get-index-replacement selected objects)]
+             (rx/of
+              (dwse/select-shapes ordered-ids)
+              (dws/create-artboard-from-selection new-shape-id parent-id group-index)
+              (cl/remove-all-fills [new-shape-id] {:color clr/black :opacity 1})
+              (create-layout-from-id new-shape-id type false)
+              (dwc/update-shapes [new-shape-id] #(assoc % :layout-item-h-sizing :auto :layout-item-v-sizing :auto))
+              (dwc/update-shapes selected #(assoc % :layout-item-h-sizing :fix :layout-item-v-sizing :fix))
+              (dws/delete-shapes page-id selected)
+              (ptk/data-event :layout/update [new-shape-id])
+              (dwu/commit-undo-transaction undo-id)))
 
-          (let [new-shape-id (uuid/next)
-                undo-id      (js/Symbol)
-                flex-params  (shapes->flex-params objects selected-shapes)]
-            (rx/of
-             (dwu/start-undo-transaction undo-id)
-             (dws/create-artboard-from-selection new-shape-id)
-             (cl/remove-all-fills [new-shape-id] {:color clr/black
-                                                  :opacity 1})
-             (create-layout-from-id [new-shape-id] type false)
-             (dwc/update-shapes
-              [new-shape-id]
-              (fn [shape]
-                (-> shape
-                    (merge flex-params)
-                    (assoc :layout-item-h-sizing :auto
-                           :layout-item-v-sizing :auto))))
-             ;; Set the children to fixed to remove strange interactions
-             (dwc/update-shapes
-              selected
-              (fn [shape]
-                (-> shape
-                    (assoc :layout-item-h-sizing :fix
-                           :layout-item-v-sizing :fix))))
-
-             (ptk/data-event :layout/update [new-shape-id])
-             (dwu/commit-undo-transaction undo-id))))))))
+           ;; Create Layout from selection
+           (rx/of
+            (dws/create-artboard-from-selection new-shape-id)
+            (cl/remove-all-fills [new-shape-id] {:color clr/black :opacity 1})
+            (create-layout-from-id new-shape-id type false)
+            (dwc/update-shapes [new-shape-id] #(assoc % :layout-item-h-sizing :auto :layout-item-v-sizing :auto))
+            (dwc/update-shapes selected #(assoc % :layout-item-h-sizing :fix :layout-item-v-sizing :fix))))
+         
+         (rx/of (ptk/data-event :layout/update [new-shape-id])
+                (dwu/commit-undo-transaction undo-id)))))))
 
 (defn remove-layout
   [ids]
@@ -342,7 +218,7 @@
         (rx/of
          (dwu/start-undo-transaction undo-id)
          (if (and single? is-frame?)
-           (create-layout-from-id [(first selected)] type true)
+           (create-layout-from-id (first selected) type true)
            (create-layout-from-selection type))
          (dwu/commit-undo-transaction undo-id))))))
 
diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs
index 6c004325d..f4b2f87b3 100644
--- a/frontend/src/app/main/data/workspace/transforms.cljs
+++ b/frontend/src/app/main/data/workspace/transforms.cljs
@@ -10,6 +10,7 @@
    [app.common.data :as d]
    [app.common.data.macros :as dm]
    [app.common.geom.matrix :as gmt]
+   [app.common.geom.modifiers :as gm]
    [app.common.geom.point :as gpt]
    [app.common.geom.rect :as grc]
    [app.common.geom.shapes :as gsh]
@@ -254,7 +255,7 @@
 
             modif-tree
             (-> (dwm/build-modif-tree ids objects get-modifier)
-                (gsh/set-objects-modifiers objects))]
+                (gm/set-objects-modifiers objects))]
 
         (assoc state :workspace-modifiers modif-tree)))
 
@@ -283,7 +284,7 @@
 
             modif-tree
             (-> (dwm/build-modif-tree ids objects get-modifier)
-                (gsh/set-objects-modifiers objects))]
+                (gm/set-objects-modifiers objects))]
 
         (assoc state :workspace-modifiers modif-tree)))
 
diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs
index f9fc514eb..ada7c3369 100644
--- a/frontend/src/app/main/ui/workspace/context_menu.cljs
+++ b/frontend/src/app/main/ui/workspace/context_menu.cljs
@@ -410,14 +410,18 @@
   [{:keys [shapes]}]
   (let [single?            (= (count shapes) 1)
         has-frame?         (->> shapes (d/seek cph/frame-shape?))
-        is-frame?          (and single? has-frame?)
-        is-flex-container? (and is-frame? (= :flex (:layout (first shapes))))
+        is-flex-container? (and single? has-frame? (= :flex (:layout (first shapes))))
         ids                (->> shapes (map :id))
-        add-layout         (fn [type]
-                             (st/emit! (if is-frame?
-                                         (dwsl/create-layout-from-id ids type true)
-                                         (dwsl/create-layout-from-selection type))))
-        remove-flex        #(st/emit! (dwsl/remove-layout ids))]
+
+        add-layout
+        (fn [type]
+          (if (and single? has-frame?)
+            (st/emit! (dwsl/create-layout-from-id (first ids) type true))
+            (st/emit! (dwsl/create-layout-from-selection type))))
+
+        remove-flex
+        (fn []
+          (st/emit! (dwsl/remove-layout ids)))]
 
     [:*
      (when (not is-flex-container?)
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs
index a91fbfbce..95f766ab2 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs
@@ -1316,20 +1316,25 @@
        [:div.element-set-title
         [:*
          [:span "Layout"]
-         (if (and (not multiple) (:layout values))
-           [:div.title-actions
-            (when (features/active-feature? :grid-layout)
-              [:div.layout-btns
-               [:button {:on-click set-flex
-                         :class (dom/classnames
-                                 :active (= :flex layout-type))} "Flex"]
-               [:button {:on-click set-grid
-                         :class (dom/classnames
-                                 :active (= :grid layout-type))} "Grid"]])
-            [:button.remove-layout {:on-click on-remove-layout} i/minus]]
 
-           [:button.add-page {:data-value :flex
-                              :on-click on-set-layout} i/close])]]
+         (if (features/active-feature? :grid-layout)
+           [:div.title-actions
+            [:div.layout-btns
+             [:button {:on-click set-flex
+                       :class (dom/classnames
+                               :active (= :flex layout-type))} "Flex"]
+             [:button {:on-click set-grid
+                       :class (dom/classnames
+                               :active (= :grid layout-type))} "Grid"]]
+
+            (when (and (not multiple) (:layout values))
+              [:button.remove-layout {:on-click on-remove-layout} i/minus])]
+
+           [:div.title-actions
+            (if (and (not multiple) (:layout values))
+              [:button.remove-layout {:on-click on-remove-layout} i/minus]
+              [:button.add-page {:data-value :flex
+                                 :on-click on-set-layout} i/close])])]]
 
        (when (:layout values)
          (when (not= :multiple layout-type)
diff --git a/frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.cljs b/frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.cljs
index 8d7f64886..4076b8131 100644
--- a/frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.cljs
@@ -283,7 +283,6 @@
         hover?      (unchecked-get props "hover?")
         selected?   (unchecked-get props "selected?")
 
-
         cell-bounds (gsg/cell-bounds layout-data cell)
         cell-origin (gpo/origin cell-bounds)
         cell-width  (gpo/width-points cell-bounds)
diff --git a/frontend/src/app/util/code_gen/style_css.cljs b/frontend/src/app/util/code_gen/style_css.cljs
index eb51ddc04..0175faae7 100644
--- a/frontend/src/app/util/code_gen/style_css.cljs
+++ b/frontend/src/app/util/code_gen/style_css.cljs
@@ -101,6 +101,7 @@ body {
    :grid-template-rows
    :grid-template-columns
    :grid-template-areas
+   :grid-auto-flow
 
    ;; Flex/grid self properties
    :flex-shrink
diff --git a/frontend/src/app/util/code_gen/style_css_values.cljs b/frontend/src/app/util/code_gen/style_css_values.cljs
index 175d185e1..cdca2cbbc 100644
--- a/frontend/src/app/util/code_gen/style_css_values.cljs
+++ b/frontend/src/app/util/code_gen/style_css_values.cljs
@@ -420,6 +420,11 @@
           justify-self (:justify-self cell)]
       (when (not= justify-self :auto) justify-self))))
 
+(defmethod get-value :grid-auto-flow
+  [_ shape _]
+  (when (and (ctl/grid-layout? shape) (= (:layout-grid-dir shape) :column))
+    "column"))
+
 (defmethod get-value :default
   [property shape _]
   (get shape property))