From 4bfe81f771fa1df60a1d0b3f26fc7e169de0ba4f Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 5 Apr 2023 11:55:08 +0200 Subject: [PATCH 01/52] :sparkles: Enable grid editor --- .../geom/shapes/grid_layout/layout_data.cljc | 2 +- .../src/app/common/geom/shapes/modifiers.cljc | 10 +- common/src/app/common/types/shape/layout.cljc | 134 ++++++++++++++++-- .../app/main/data/workspace/shape_layout.cljs | 8 +- .../options/menus/layout_container.cljs | 9 +- .../sidebar/options/shapes/grid_cell.cljs | 8 +- .../viewport/grid_layout_editor.cljs | 114 ++++++++++----- 7 files changed, 225 insertions(+), 60 deletions(-) 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 e797b1c643..73d500b57f 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 @@ -121,7 +121,7 @@ :shape-cells shape-cells})) (defn get-cell-data - [{:keys [row-tracks column-tracks shape-cells]} transformed-parent-bounds [_child-bounds child]] + [{:keys [row-tracks column-tracks shape-cells]} transformed-parent-bounds [_ child]] (let [origin (gpo/origin transformed-parent-bounds) hv #(gpo/start-hv transformed-parent-bounds %) diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index 14ffa6ddf5..9b0fd77227 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -204,6 +204,7 @@ [(-> (get-group-bounds objects bounds modif-tree child) (gpo/parent-coords-bounds @transformed-parent-bounds)) child]) + (set-child-modifiers [modif-tree cell-data [child-bounds child]] (let [modifiers (gcgl/child-modifiers parent transformed-parent-bounds child child-bounds cell-data) modif-tree @@ -217,13 +218,14 @@ (map apply-modifiers)) grid-data (gcgl/calc-layout-data parent children @transformed-parent-bounds)] (loop [modif-tree modif-tree - child (first children) + bound+child (first children) pending (rest children)] - (if (some? child) - (let [cell-data (gcgl/get-cell-data grid-data @transformed-parent-bounds child) + (if (some? bound+child) + (let [[_ child] bound+child + cell-data (gcgl/get-cell-data grid-data @transformed-parent-bounds bound+child) modif-tree (cond-> modif-tree (some? cell-data) - (set-child-modifiers cell-data child))] + (set-child-modifiers cell-data bound+child))] (recur modif-tree (first pending) (rest pending))) modif-tree))))) diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index 7f7814ef66..3ce36466f9 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -555,6 +555,10 @@ (declare assign-cells) +(def default-track-value + {:type :fixed + :value 100}) + (def grid-cell-defaults {:row-span 1 :column-span 1 @@ -573,7 +577,7 @@ (grid-definition? value)) (let [rows (:layout-grid-rows parent) - new-col-num (count (:layout-grid-columns parent)) + new-col-num (inc (count (:layout-grid-columns parent))) layout-grid-cells (->> (d/enumerate rows) @@ -614,14 +618,54 @@ (update :layout-grid-rows (fnil conj []) value) (assoc :layout-grid-cells layout-grid-cells)))) -;; TODO: Remove a track and its corresponding cells. We need to reassign the orphaned shapes into not-tracked cells + +;; TODO: SPAN NOT CHECK!! +(defn make-remove-track + [attr track-num] + (comp #(= % track-num) attr second)) + +(defn make-decrease-track-num + [attr track-num] + (fn [[key value]] + (let [new-val + (cond-> value + (> (get value attr) track-num) + (update attr dec))] + [key new-val]))) + (defn remove-grid-column - [parent _index] - parent) + [parent index] + (let [track-num (inc index) + + decrease-track-num (make-decrease-track-num :column track-num) + remove-track? (make-remove-track :column track-num) + + remove-cells + (fn [cells] + (into {} (comp + (remove remove-track?) + (map decrease-track-num)) cells))] + (-> parent + (update :layout-grid-columns d/remove-at-index index) + (update :layout-grid-cells remove-cells) + (assign-cells)))) (defn remove-grid-row - [parent _index] - parent) + [parent index] + (let [track-num (inc index) + + decrease-track-num (make-decrease-track-num :row track-num) + remove-track? (make-remove-track :row track-num) + + remove-cells + (fn [cells] + (into {} (comp + (remove remove-track?) + (map decrease-track-num)) cells))] + (-> parent + (update :layout-grid-rows d/remove-at-index index) + (update :layout-grid-cells remove-cells) + (assign-cells)))) ;; TODO: Mix the cells given as arguments leaving only one. It should move all the shapes in those cells in the direction for the grid ;; and lastly use assign-cells to reassing the orphaned shapes @@ -629,10 +673,38 @@ [parent _cells] parent) +(defn get-free-cells + ([parent] + (get-free-cells parent nil)) + + ([{:keys [layout-grid-cells layout-grid-dir]} {:keys [sort?] :or {sort? false}}] + (let [comp-fn (if (= layout-grid-dir :row) + (juxt :row :column) + (juxt :column :row)) + + maybe-sort? + (if sort? (partial sort-by (comp comp-fn second)) identity)] + + (->> layout-grid-cells + (filter (comp empty? :shapes second)) + (maybe-sort?) + (map first))))) + +(defn check-deassigned-cells + "Clean the cells whith shapes that are no longer in the layout" + [parent] + + (let [child? (into #{} (:shapes parent)) + cells (update-vals + (:layout-grid-cells parent) + (fn [cell] (update cell :shapes #(filterv child? %))))] + + (assoc parent :layout-grid-cells cells))) ;; TODO ;; Assign cells takes the children and move them into the allotted cells. If there are not enough cells it creates ;; not-tracked rows/columns and put the shapes there +;; Non-tracked tracks need to be deleted when they are empty and there are no more shapes unallocated ;; Should be caled each time a child can be added like: ;; - On shape creation ;; - When moving a child from layers @@ -641,9 +713,51 @@ ;; - (maybe) create group/frames. This case will assigna a cell that had one of its children (defn assign-cells [parent] - #_(let [allocated-shapes - (into #{} (mapcat :shapes) (:layout-grid-cells parent)) + (let [parent (-> parent check-deassigned-cells) + + shape-has-cell? + (into #{} (mapcat (comp :shapes second)) (:layout-grid-cells parent)) no-cell-shapes - (->> (:shapes parent) (remove allocated-shapes))]) - parent) + (->> (:shapes parent) (remove shape-has-cell?))] + + (if (empty? no-cell-shapes) + ;; All shapes are within a cell. No need to assign + parent + + (let [;; We need to have at least 1 col and 1 row otherwise we can't assign + parent + (cond-> parent + (empty? (:layout-grid-columns parent)) + (add-grid-column default-track-value) + + (empty? (:layout-grid-rows parent)) + (add-grid-row default-track-value)) + + ;; Free cells should be ordered columns/rows depending on the parameter + ;; in the parent + free-cells (get-free-cells parent) + + to-add-tracks + (if (= (:layout-grid-dir parent) :row) + (mth/ceil (/ (- (count no-cell-shapes) (count free-cells)) (count (:layout-grid-columns parent)))) + (mth/ceil (/ (- (count no-cell-shapes) (count free-cells)) (count (:layout-grid-rows parent))))) + + add-track (if (= (:layout-grid-dir parent) :row) add-grid-row add-grid-column) + + parent + (->> (range to-add-tracks) + (reduce #(add-track %1 default-track-value) parent)) + + [pending-shapes cells] + (loop [cells (:layout-grid-cells parent) + free-cells (get-free-cells parent {:sort? true}) + pending no-cell-shapes] + (if (or (empty? free-cells) (empty? pending)) + [pending cells] + (let [next-free (first free-cells) + current (first pending) + cells (update-in cells [next-free :shapes] conj current)] + (recur cells (rest free-cells) (rest pending)))))] + + (assoc parent :layout-grid-cells cells))))) diff --git a/frontend/src/app/main/data/workspace/shape_layout.cljs b/frontend/src/app/main/data/workspace/shape_layout.cljs index e235137a8e..fc1ec63956 100644 --- a/frontend/src/app/main/data/workspace/shape_layout.cljs +++ b/frontend/src/app/main/data/workspace/shape_layout.cljs @@ -63,6 +63,7 @@ :layout-justify-content :start :layout-padding-type :simple :layout-padding {:p1 0 :p2 0 :p3 0 :p4 0} + :layout-grid-cells {} :layout-grid-rows [] :layout-grid-columns []}) @@ -443,12 +444,13 @@ (defn change-layout-track [ids type index props] (assert (#{:row :column} type)) - (ptk/reify ::change-layout-column + (ptk/reify ::change-layout-track ptk/WatchEvent (watch [_ _ _] (let [undo-id (js/Symbol) - property (case :row :layout-grid-rows - :column :layout-grid-columns)] + property (case type + :row :layout-grid-rows + :column :layout-grid-columns)] (rx/of (dwu/start-undo-transaction undo-id) (dwc/update-shapes ids 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 1e987b4884..8e6f02eb6b 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 @@ -9,6 +9,7 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.math :as mth] + [app.common.types.shape.layout :as ctl] [app.main.data.workspace :as udw] [app.main.data.workspace.shape-layout :as dwsl] [app.main.refs :as refs] @@ -441,7 +442,7 @@ :on-click toggle} generated-name] [:button.add-column {:on-click #(do (when-not expanded? (toggle)) - (add-new-element type {:type :fixed :value 100}))} i/plus]] + (add-new-element type ctl/default-track-value))} i/plus]] (when expanded? [:div.columns-info-wrapper @@ -489,12 +490,12 @@ (st/emit! (dwsl/remove-layout ids)) (reset! open? false)) - _set-flex + set-flex (fn [] (st/emit! (dwsl/remove-layout ids)) (on-add-layout :flex)) - _set-grid + set-grid (fn [] (st/emit! (dwsl/remove-layout ids)) (on-add-layout :grid)) @@ -637,7 +638,7 @@ [:span "Layout"] (if (and (not multiple) (:layout values)) [:div.title-actions - #_[:div.layout-btns + [:div.layout-btns [:button {:on-click set-flex :class (dom/classnames :active (= :flex layout-type))} "Flex"] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/grid_cell.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/grid_cell.cljs index 5b4f96dd87..2d02bd6047 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/grid_cell.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/grid_cell.cljs @@ -98,7 +98,7 @@ (when (= :auto @position-mode) [:div.grid-auto [:div.grid-columns-auto - [:spam.icon i/layout-rows] + [:span.icon i/layout-rows] [:div.input-wrapper [:> numeric-input {:placeholder "--" @@ -106,7 +106,7 @@ :on-change (partial on-change :all :column) ;; TODO cambiar este on-change y el value :value column-start}]]] [:div.grid-rows-auto - [:spam.icon i/layout-columns] + [:span.icon i/layout-columns] [:div.input-wrapper [:> numeric-input {:placeholder "--" @@ -129,7 +129,7 @@ (when (or (= :manual @position-mode) (= :area @position-mode)) [:div.grid-manual [:div.grid-columns-auto - [:spam.icon i/layout-rows] + [:span.icon i/layout-rows] [:div.input-wrapper [:> numeric-input {:placeholder "--" @@ -142,7 +142,7 @@ :on-change (partial on-change :end :column) :value column-end}]]] [:div.grid-rows-auto - [:spam.icon i/layout-columns] + [:span.icon i/layout-columns] [:div.input-wrapper [:> numeric-input {:placeholder "--" 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 c9f351e903..897e03eaa5 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 @@ -12,7 +12,9 @@ [app.common.geom.shapes.grid-layout :as gsg] [app.common.geom.shapes.points :as gpo] [app.common.pages.helpers :as cph] + [app.common.types.shape.layout :as ctl] [app.main.data.workspace.grid-layout.editor :as dwge] + [app.main.data.workspace.shape-layout :as dwsl] [app.main.refs :as refs] [app.main.store :as st] [app.util.dom :as dom] @@ -92,9 +94,10 @@ {::mf/wrap-props false} [props] - (let [start-p (unchecked-get props "start-p") - zoom (unchecked-get props "zoom") - type (unchecked-get props "type") + (let [start-p (unchecked-get props "start-p") + zoom (unchecked-get props "zoom") + type (unchecked-get props "type") + on-click (unchecked-get props "on-click") [rect-x rect-y icon-x icon-y] (if (= type :column) @@ -106,9 +109,17 @@ [(- (:x start-p) (/ 40 zoom)) (:y start-p) (- (:x start-p) (/ 28 zoom)) - (+ (:y start-p) (/ 12 zoom))])] + (+ (:y start-p) (/ 12 zoom))]) - [:g.plus-button + handle-click + (mf/use-callback + (mf/deps on-click) + (fn [event] + (when on-click + (on-click))))] + + [:g.plus-button {:cursor "pointer" + :on-click handle-click} [:rect {:x rect-x :y rect-y :width (/ 40 zoom) @@ -152,25 +163,45 @@ end-p (-> start-p (gpt/add (hv (:value column-track))) - (gpt/add (vv (:value row-track))))] + (gpt/add (vv (:value row-track)))) - [:rect.cell-editor - {:x (:x start-p) - :y (:y start-p) - :width (- (:x end-p) (:x start-p)) - :height (- (:y end-p) (:y start-p)) + cell-width (- (:x end-p) (:x start-p)) + cell-height (- (:y end-p) (:y start-p))] - :on-pointer-enter #(st/emit! (dwge/hover-grid-cell (:id shape) row column true)) - :on-pointer-leave #(st/emit! (dwge/hover-grid-cell (:id shape) row column false)) + [:g.cell-editor + [:rect + {:x (:x start-p) + :y (:y start-p) + :width cell-width + :height cell-height - :on-click #(st/emit! (dwge/select-grid-cell (:id shape) row column)) + :on-pointer-enter #(st/emit! (dwge/hover-grid-cell (:id shape) row column true)) + :on-pointer-leave #(st/emit! (dwge/hover-grid-cell (:id shape) row column false)) - :style {:fill "transparent" - :stroke "var(--color-distance)" - :stroke-dasharray (when-not (or hover? selected?) - (str/join " " (map #(/ % zoom) [0 8]) )) - :stroke-linecap "round" - :stroke-width (/ 2 zoom)}}])) + :on-click #(st/emit! (dwge/select-grid-cell (:id shape) row column)) + + :style {:fill "transparent" + :stroke "var(--color-distance)" + :stroke-dasharray (when-not (or hover? selected?) + (str/join " " (map #(/ % zoom) [0 8]) )) + :stroke-linecap "round" + :stroke-width (/ 2 zoom)}}] + + (when selected? + (let [handlers + ;; Handlers positions, size and cursor + [[(:x start-p) (+ (:y start-p) (/ -10 zoom)) cell-width (/ 20 zoom) (cur/scale-ns 0)] + [(+ (:x start-p) cell-width (/ -10 zoom)) (:y start-p) (/ 20 zoom) cell-height (cur/scale-ew 0)] + [(:x start-p) (+ (:y start-p) cell-height (/ -10 zoom)) cell-width (/ 20 zoom) (cur/scale-ns 0)] + [(+ (:x start-p) (/ -10 zoom)) (:y start-p) (/ 20 zoom) cell-height (cur/scale-ew 0)]]] + [:* + (for [[x y width height cursor] handlers] + [:rect + {:x x + :y y + :height height + :width width + :style {:fill "transparent" :stroke-width 0 :cursor cursor}}])]))])) (mf/defc resize-handler {::mf/wrap-props false} @@ -265,7 +296,19 @@ origin (gpo/origin bounds) {:keys [row-tracks column-tracks] :as layout-data} - (gsg/calc-layout-data shape children bounds)] + (gsg/calc-layout-data shape children bounds) + + handle-add-column + (mf/use-callback + (mf/deps (:id shape)) + (fn [] + (st/emit! (st/emit! (dwsl/add-layout-track [(:id shape)] :column ctl/default-track-value))))) + + handle-add-row + (mf/use-callback + (mf/deps (:id shape)) + (fn [] + (st/emit! (st/emit! (dwsl/add-layout-track [(:id shape)] :row ctl/default-track-value)))))] (mf/use-effect (fn [] @@ -277,23 +320,16 @@ (let [start-p (-> origin (gpt/add (hv width)))] [:& plus-btn {:start-p start-p :zoom zoom - :type :column}]) + :type :column + :on-click handle-add-column}]) (let [start-p (-> origin (gpt/add (vv height)))] [:& plus-btn {:start-p start-p :zoom zoom - :type :row}]) + :type :row + :on-click handle-add-row}]) + - (for [[_ {:keys [column row]}] (:layout-grid-cells shape)] - [:& grid-cell {:shape shape - :layout-data layout-data - :row row - :column column - :bounds bounds - :zoom zoom - :hover? (contains? hover-cells [row column]) - :selected? (= selected-cells [row column]) - }]) (for [[idx column-data] (d/enumerate column-tracks)] (let [start-p (-> origin (gpt/add (hv (:distance column-data)))) @@ -320,4 +356,14 @@ [:& resize-handler {:type :row :start-p start-p :zoom zoom - :bounds bounds}]]))])) + :bounds bounds}]])) + + (for [[_ {:keys [column row]}] (:layout-grid-cells shape)] + [:& grid-cell {:shape shape + :layout-data layout-data + :row row + :column column + :bounds bounds + :zoom zoom + :hover? (contains? hover-cells [row column]) + :selected? (= selected-cells [row column])}])])) From 2df40ad767a9a8bd3fc184858e78d4c908cc54be Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 12 Apr 2023 16:26:40 +0200 Subject: [PATCH 02/52] :sparkles: Adds grid column/row sizing without spanned tracks --- .../common/geom/shapes/flex_layout/lines.cljc | 5 +- .../geom/shapes/grid_layout/layout_data.cljc | 302 ++++++++++++++---- .../src/app/common/geom/shapes/modifiers.cljc | 3 +- common/src/app/common/types/shape/layout.cljc | 22 +- .../viewport/grid_layout_editor.cljs | 215 ++++++++++--- 5 files changed, 424 insertions(+), 123 deletions(-) diff --git a/common/src/app/common/geom/shapes/flex_layout/lines.cljc b/common/src/app/common/geom/shapes/flex_layout/lines.cljc index 1aa12f6e90..14b4d110d8 100644 --- a/common/src/app/common/geom/shapes/flex_layout/lines.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/lines.cljc @@ -15,9 +15,8 @@ (def conjv (fnil conj [])) (defn layout-bounds - [{:keys [layout-padding] :as shape} shape-bounds] - (let [;; Add padding to the bounds - {pad-top :p1 pad-right :p2 pad-bottom :p3 pad-left :p4} layout-padding] + [parent shape-bounds] + (let [[pad-top pad-right pad-bottom pad-left] (ctl/paddings parent)] (gpo/pad-points shape-bounds pad-top pad-right pad-bottom pad-left))) (defn init-layout-lines 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 73d500b57f..f45fb73267 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 @@ -4,10 +4,63 @@ ;; ;; Copyright (c) KALEIDOS INC +;; Each track has specified minimum and maximum sizing functions (which may be the same) +;; - Fixed +;; - Percent +;; - Auto +;; - Flex +;; +;; +;; +;; Min functions: +;; - Fixed: value +;; - Percent: value to pixels +;; - Auto: auto +;; - Flex: auto +;; +;; Max functions: +;; - Fixed: value +;; - Percent: value to pixels +;; - Auto: max-content +;; - Flex: flex + +;; Algorithm +;; - Initialize tracks: +;; * base = size or 0 +;; * max = size or INF +;; +;; - Resolve intrinsic sizing +;; 1. Shim baseline-aligned items so their intrinsic size contributions reflect their baseline alignment +;; +;; 2. Size tracks to fit non-spanning items +;; base-size = max (children min contribution) floored 0 +;; +;; 3. Increase sizes to accommodate spanning items crossing content-sized tracks +;; +;; 4. Increase sizes to accommodate spanning items crossing flexible tracks: +;; +;; 5. If any track still has an infinite growth limit set its growth limit to its base size. + +;; - Distribute extra space accross spaned tracks +;; - Maximize tracks +;; +;; - Expand flexible tracks +;; - Find `fr` size +;; +;; - Stretch auto tracks + + + (ns app.common.geom.shapes.grid-layout.layout-data (:require [app.common.geom.point :as gpt] - [app.common.geom.shapes.points :as gpo])) + [app.common.geom.shapes.points :as gpo] + [app.common.types.shape.layout :as ctl])) + +(defn layout-bounds + [parent shape-bounds] + (let [[pad-top pad-right pad-bottom pad-left] (ctl/paddings parent)] + (gpo/pad-points shape-bounds pad-top pad-right pad-bottom pad-left))) #_(defn set-sample-data [parent children] @@ -50,91 +103,218 @@ [parent children])) -(defn calculate-initial-track-values - [{:keys [type value]} total-value] +(defn calculate-initial-track-size + [total-value {:keys [type value] :as track}] - (case type - :percent - (let [value (/ (* total-value value) 100) ] - value) + (let [[size max-size] + (case type + :percent + (let [value (/ (* total-value value) 100) ] + [value value]) - :fixed - value + :fixed + [value value] - :auto - 0 - )) + ;; flex, auto + [0.01 ##Inf])] + (assoc track :size size :max-size max-size))) + + +(defn set-auto-base-size + [track-list children shape-cells type] + + (let [[prop prop-span size-fn] + (if (= type :column) + [:column :column-span gpo/width-points] + [:row :row-span gpo/height-points])] + + (reduce (fn [tracks [child-bounds child-shape]] + (let [cell (get shape-cells (:id child-shape)) + idx (dec (get cell prop)) + track (nth tracks idx)] + (cond-> tracks + (and (= (get cell prop-span) 1) (= :auto (:type track))) + (update-in [idx :size] max (size-fn child-bounds))))) + track-list + children))) + +(defn tracks-total-size + [track-list] + (let [calc-tracks-total-size + (fn [acc {:keys [size]}] + (+ acc size))] + (->> track-list (reduce calc-tracks-total-size 0)))) + +(defn tracks-total-frs + [track-list] + (let [calc-tracks-total-frs + (fn [acc {:keys [type value]}] + (let [value (max 1 value)] + (cond-> acc + (= type :flex) + (+ value))))] + (->> track-list (reduce calc-tracks-total-frs 0)))) + +(defn tracks-total-autos + [track-list] + (let [calc-tracks-total-autos + (fn [acc {:keys [type]}] + (cond-> acc (= type :auto) (inc)))] + (->> track-list (reduce calc-tracks-total-autos 0)))) + +(defn set-fr-value + [track-list fr-value] + (->> track-list + (mapv (fn [{:keys [type value max-size] :as track}] + (cond-> track + (= :flex type) + (assoc :size (min (* value fr-value) max-size))))))) + +(defn add-auto-size + [track-list add-size] + (->> track-list + (mapv (fn [{:keys [type size max-size] :as track}] + (cond-> track + (= :auto type) + (assoc :size (min (+ size add-size) max-size))))))) (defn calc-layout-data - [parent _children transformed-parent-bounds] + [parent children transformed-parent-bounds] - (let [height (gpo/height-points transformed-parent-bounds) - width (gpo/width-points transformed-parent-bounds) + (let [hv #(gpo/start-hv transformed-parent-bounds %) + vv #(gpo/start-vv transformed-parent-bounds %) - ;; Initialize tracks - column-tracks - (->> (:layout-grid-columns parent) - (map (fn [track] - (let [initial (calculate-initial-track-values track width)] - (assoc track :value initial))))) + layout-bounds (layout-bounds parent transformed-parent-bounds) - row-tracks - (->> (:layout-grid-rows parent) - (map (fn [track] - (let [initial (calculate-initial-track-values track height)] - (assoc track :value initial))))) + bound-height (gpo/height-points layout-bounds) + bound-width (gpo/width-points layout-bounds) + bound-corner (gpo/origin layout-bounds) - ;; Go through cells to adjust auto sizes + grid-columns (:layout-grid-columns parent) + grid-rows (:layout-grid-rows parent) + [row-gap column-gap] (ctl/gaps parent) - ;; Once auto sizes have been calculated we get calculate the `fr` with the remainining size and adjust the size - - - ;; Adjust final distances - - acc-track-distance - (fn [[result next-distance] data] - (let [result (conj result (assoc data :distance next-distance)) - next-distance (+ next-distance (:value data))] - [result next-distance])) - - column-tracks - (->> column-tracks - (reduce acc-track-distance [[] 0]) - first) - - row-tracks - (->> row-tracks - (reduce acc-track-distance [[] 0]) - first) - + ;; Map shape->cell shape-cells (into {} (mapcat (fn [[_ cell]] - (->> (:shapes cell) - (map #(vector % cell))))) + (->> (:shapes cell) (map #(vector % cell))))) (:layout-grid-cells parent)) + + + ;; Initialize tracks + column-tracks + (->> grid-columns + (mapv (partial calculate-initial-track-size bound-width))) + + row-tracks + (->> grid-rows + (mapv (partial calculate-initial-track-size bound-height))) + + ;; Go through cells to adjust auto sizes for span=1. Base is the max of its children + column-tracks (set-auto-base-size column-tracks children shape-cells :column) + row-tracks (set-auto-base-size row-tracks children shape-cells :row) + + + ;; Adjust multi-spaned cells with no flex columns + ;; TODO + + + ;; Calculate the `fr` unit and adjust the size + column-total-size (tracks-total-size column-tracks) + row-total-size (tracks-total-size row-tracks) + + column-total-gap (* column-gap (dec (count column-tracks))) + row-total-gap (* row-gap (dec (count row-tracks))) + + column-frs (tracks-total-frs column-tracks) + row-frs (tracks-total-frs row-tracks) + + ;; Once auto sizes have been calculated we get calculate the `fr` unit with the remainining size and adjust the size + free-column-space (- bound-width (+ column-total-size column-total-gap)) + free-row-space (- bound-height (+ row-total-size row-total-gap)) + column-fr (/ free-column-space column-frs) + row-fr (/ free-row-space row-frs) + + column-tracks (set-fr-value column-tracks column-fr) + row-tracks (set-fr-value row-tracks row-fr) + + ;; Distribute free space between `auto` tracks + column-total-size (tracks-total-size column-tracks) + row-total-size (tracks-total-size row-tracks) + + free-column-space (- bound-width (+ column-total-size column-total-gap)) + free-row-space (- bound-height (+ row-total-size row-total-gap)) + column-autos (tracks-total-autos column-tracks) + row-autos (tracks-total-autos row-tracks) + column-add-auto (/ free-column-space column-autos) + row-add-auto (/ free-row-space row-autos) + + column-tracks (add-auto-size column-tracks column-add-auto) + row-tracks (add-auto-size row-tracks row-add-auto) + + start-p + (cond-> bound-corner + (= :end (:layout-align-content parent)) + (gpt/add (hv (- bound-width (+ column-total-size column-total-gap)))) + + (= :center (:layout-align-content parent)) + (gpt/add (hv (/ (- bound-width (+ column-total-size column-total-gap)) 2))) + + (= :end (:layout-justify-content parent)) + (gpt/add (vv (- bound-height (+ row-total-size row-total-gap)))) + + (= :center (:layout-justify-content parent)) + (gpt/add (vv (/ (- bound-height (+ row-total-size row-total-gap)) 2)))) + + column-tracks + (->> column-tracks + (reduce (fn [[tracks start-p] {:keys [size] :as track}] + [(conj tracks (assoc track :start-p start-p)) + (gpt/add start-p (hv (+ size column-gap)))]) + [[] start-p]) + (first)) + + row-tracks + (->> row-tracks + (reduce (fn [[tracks start-p] {:keys [size] :as track}] + [(conj tracks (assoc track :start-p start-p)) + (gpt/add start-p (vv (+ size row-gap)))]) + [[] start-p]) + (first)) + ] - {:row-tracks row-tracks + {:origin start-p + :layout-bounds layout-bounds + :row-tracks row-tracks :column-tracks column-tracks - :shape-cells shape-cells})) + :shape-cells shape-cells + + ;; Convenient informaton for visualization + :column-total-size column-total-size + :column-total-gap column-total-gap + :row-total-size row-total-size + :row-total-gap row-total-gap + })) (defn get-cell-data - [{:keys [row-tracks column-tracks shape-cells]} transformed-parent-bounds [_ child]] + [{:keys [origin row-tracks column-tracks shape-cells]} _transformed-parent-bounds [_ child]] - (let [origin (gpo/origin transformed-parent-bounds) - hv #(gpo/start-hv transformed-parent-bounds %) - vv #(gpo/start-vv transformed-parent-bounds %) - - grid-cell (get shape-cells (:id child))] + (let [grid-cell (get shape-cells (:id child))] (when (some? grid-cell) (let [column (nth column-tracks (dec (:column grid-cell)) nil) row (nth row-tracks (dec (:row grid-cell)) nil) - start-p (-> origin - (gpt/add (hv (:distance column))) - (gpt/add (vv (:distance row))))] + column-start-p (:start-p column) + row-start-p (:start-p row) + + start-p (gpt/add origin + (gpt/add + (gpt/to-vec origin column-start-p) + (gpt/to-vec origin row-start-p))) + ] (assoc grid-cell :start-p start-p))))) diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index 9b0fd77227..37dd7019a9 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -221,8 +221,7 @@ bound+child (first children) pending (rest children)] (if (some? bound+child) - (let [[_ child] bound+child - cell-data (gcgl/get-cell-data grid-data @transformed-parent-bounds bound+child) + (let [cell-data (gcgl/get-cell-data grid-data @transformed-parent-bounds bound+child) modif-tree (cond-> modif-tree (some? cell-data) (set-child-modifiers cell-data bound+child))] diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index 3ce36466f9..8fc029b430 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -298,6 +298,13 @@ layout-gap-col (or (-> layout-gap :column-gap (mth/finite 0)) 0)] [layout-gap-row layout-gap-col])) +(defn paddings + [{:keys [layout-padding-type layout-padding]}] + (let [{pad-top :p1 pad-right :p2 pad-bottom :p3 pad-left :p4} layout-padding] + (if (= :simple layout-padding-type) + [pad-top pad-right pad-top pad-right] + [pad-top pad-right pad-bottom pad-left]))) + (defn child-min-width [child] (if (and (fill-width? child) @@ -556,8 +563,7 @@ (declare assign-cells) (def default-track-value - {:type :fixed - :value 100}) + {:type :auto}) (def grid-cell-defaults {:row-span 1 @@ -740,21 +746,21 @@ to-add-tracks (if (= (:layout-grid-dir parent) :row) - (mth/ceil (/ (- (count no-cell-shapes) (count free-cells)) (count (:layout-grid-columns parent)))) - (mth/ceil (/ (- (count no-cell-shapes) (count free-cells)) (count (:layout-grid-rows parent))))) + (mth/ceil (/ (- (count no-cell-shapes) (count free-cells)) (count (:layout-grid-rows parent)))) + (mth/ceil (/ (- (count no-cell-shapes) (count free-cells)) (count (:layout-grid-columns parent))))) - add-track (if (= (:layout-grid-dir parent) :row) add-grid-row add-grid-column) + add-track (if (= (:layout-grid-dir parent) :row) add-grid-column add-grid-row) parent (->> (range to-add-tracks) - (reduce #(add-track %1 default-track-value) parent)) + (reduce (fn [parent _] (add-track parent default-track-value)) parent)) - [pending-shapes cells] + cells (loop [cells (:layout-grid-cells parent) free-cells (get-free-cells parent {:sort? true}) pending no-cell-shapes] (if (or (empty? free-cells) (empty? pending)) - [pending cells] + cells (let [next-free (first free-cells) current (first pending) cells (update-in cells [next-free :shapes] conj current)] 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 897e03eaa5..af78977178 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 @@ -24,6 +24,13 @@ (defn apply-to-point [result next-fn] (conj result (next-fn (last result)))) +(defn format-size [{:keys [type value]}] + (case type + :fixed (str value "PX") + :percent (str value "%") + :flex (str value "FR") + :auto "AUTO")) + (mf/defc track-marker {::mf/wrap-props false} [props] @@ -57,6 +64,8 @@ :width (/ 26.26 zoom) :height (/ 32 zoom) :font-size (/ 16 zoom) + :font-family "worksans" + :font-weight 600 :text-anchor "middle" :dominant-baseline "middle" :style {:fill "var(--color-distance)"}} @@ -114,9 +123,7 @@ handle-click (mf/use-callback (mf/deps on-click) - (fn [event] - (when on-click - (on-click))))] + #(when on-click (on-click)))] [:g.plus-button {:cursor "pointer" :on-click handle-click} @@ -135,12 +142,84 @@ :href (dm/str "#icon-plus") :fill "white"}]])) +(defn use-drag + [{:keys [on-drag-start on-drag-end on-drag-delta on-drag-position]}] + (let [ + dragging-ref (mf/use-ref false) + start-pos-ref (mf/use-ref nil) + current-pos-ref (mf/use-ref nil) + + handle-pointer-down + (mf/use-callback + (mf/deps on-drag-start) + (fn [event] + (let [position (dom/get-client-position event)] + (dom/capture-pointer event) + (mf/set-ref-val! dragging-ref true) + (mf/set-ref-val! start-pos-ref position) + (mf/set-ref-val! current-pos-ref position) + (when on-drag-start (on-drag-start position))))) + + handle-lost-pointer-capture + (mf/use-callback + (mf/deps on-drag-end) + (fn [event] + (let [position (mf/ref-val current-pos-ref)] + (dom/release-pointer event) + (mf/set-ref-val! dragging-ref false) + (mf/set-ref-val! start-pos-ref nil) + (when on-drag-end (on-drag-end position))))) + + handle-pointer-move + (mf/use-callback + (fn [event] + (when (mf/ref-val dragging-ref) + (let [start (mf/ref-val start-pos-ref) + pos (dom/get-client-position event)] + (mf/set-ref-val! current-pos-ref pos) + (when on-drag-delta (on-drag-delta (gpt/to-vec start pos))) + (when on-drag-position (on-drag-position pos))))))] + + {:handle-pointer-down handle-pointer-down + :handle-lost-pointer-capture handle-lost-pointer-capture + :handle-pointer-move handle-pointer-move})) + +(mf/defc resize-cell-handler + {::mf/wrap-props false} + [props] + (let [x (unchecked-get props "x") + y (unchecked-get props "y") + width (unchecked-get props "width") + height (unchecked-get props "height") + direction (unchecked-get props "direction") + cursor (if (= direction :row) (cur/scale-ns 0) (cur/scale-ew 0)) + + handle-drag-delta + (mf/use-callback + (fn [delta] + (prn ">>>" delta))) + + {:keys [handle-pointer-down handle-lost-pointer-capture handle-pointer-move]} + (use-drag {:on-drag-delta handle-drag-delta})] + + [:rect + {:x x + :y y + :height height + :width width + :style {:fill "transparent" :stroke-width 0 :cursor cursor} + + :on-pointer-down handle-pointer-down + :on-lost-pointer-capture handle-lost-pointer-capture + :on-pointer-move handle-pointer-move}])) + (mf/defc grid-cell {::mf/wrap-props false} [props] (let [shape (unchecked-get props "shape") - {:keys [row-tracks column-tracks]} (unchecked-get props "layout-data") - bounds (unchecked-get props "bounds") + + {:keys [origin row-tracks column-tracks layout-bounds]} (unchecked-get props "layout-data") + zoom (unchecked-get props "zoom") hover? (unchecked-get props "hover?") @@ -152,18 +231,17 @@ column-track (nth column-tracks (dec column) nil) row-track (nth row-tracks (dec row) nil) + hv #(gpo/start-hv layout-bounds %) + vv #(gpo/start-vv layout-bounds %) - origin (gpo/origin bounds) - hv #(gpo/start-hv bounds %) - vv #(gpo/start-vv bounds %) - - start-p (-> origin - (gpt/add (hv (:distance column-track))) - (gpt/add (vv (:distance row-track)))) + start-p (gpt/add origin + (gpt/add + (gpt/to-vec origin (:start-p column-track)) + (gpt/to-vec origin (:start-p row-track)))) end-p (-> start-p - (gpt/add (hv (:value column-track))) - (gpt/add (vv (:value row-track)))) + (gpt/add (hv (:size column-track))) + (gpt/add (vv (:size row-track)))) cell-width (- (:x end-p) (:x start-p)) cell-height (- (:y end-p) (:y start-p))] @@ -190,34 +268,33 @@ (when selected? (let [handlers ;; Handlers positions, size and cursor - [[(:x start-p) (+ (:y start-p) (/ -10 zoom)) cell-width (/ 20 zoom) (cur/scale-ns 0)] - [(+ (:x start-p) cell-width (/ -10 zoom)) (:y start-p) (/ 20 zoom) cell-height (cur/scale-ew 0)] - [(:x start-p) (+ (:y start-p) cell-height (/ -10 zoom)) cell-width (/ 20 zoom) (cur/scale-ns 0)] - [(+ (:x start-p) (/ -10 zoom)) (:y start-p) (/ 20 zoom) cell-height (cur/scale-ew 0)]]] + [[(:x start-p) (+ (:y start-p) (/ -10 zoom)) cell-width (/ 20 zoom) :row] + [(+ (:x start-p) cell-width (/ -10 zoom)) (:y start-p) (/ 20 zoom) cell-height :column] + [(:x start-p) (+ (:y start-p) cell-height (/ -10 zoom)) cell-width (/ 20 zoom) :row] + [(+ (:x start-p) (/ -10 zoom)) (:y start-p) (/ 20 zoom) cell-height :column]]] [:* - (for [[x y width height cursor] handlers] - [:rect - {:x x - :y y - :height height - :width width - :style {:fill "transparent" :stroke-width 0 :cursor cursor}}])]))])) + (for [[x y width height dir] handlers] + [:& resize-cell-handler {:x x + :y y + :width width + :height height + :direction dir}])]))])) (mf/defc resize-handler {::mf/wrap-props false} [props] - (let [start-p (unchecked-get props "start-p") + (let [shape (unchecked-get props "shape") + {:keys [column-total-size column-total-gap row-total-size row-total-gap]} (unchecked-get props "layout-data") + start-p (unchecked-get props "start-p") type (unchecked-get props "type") - bounds (unchecked-get props "bounds") zoom (unchecked-get props "zoom") - width (gpo/width-points bounds) - height (gpo/height-points bounds) - dragging-ref (mf/use-ref false) start-ref (mf/use-ref nil) + [layout-gap-row layout-gap-col] (ctl/gaps shape) + on-pointer-down (mf/use-callback (fn [event] @@ -247,15 +324,15 @@ [x y width height] (if (= type :column) - [(- (:x start-p) (/ 8 zoom)) + [(- (:x start-p) (max layout-gap-col (/ 8 zoom))) (- (:y start-p) (/ 40 zoom)) - (/ 16 zoom) - (+ height (/ 40 zoom))] + (max layout-gap-col (/ 16 zoom)) + (+ row-total-size row-total-gap (/ 40 zoom))] [(- (:x start-p) (/ 40 zoom)) - (- (:y start-p) (/ 8 zoom)) - (+ width (/ 40 zoom)) - (/ 16 zoom)])] + (- (:y start-p) (max layout-gap-row (/ 8 zoom))) + (+ column-total-size column-total-gap (/ 40 zoom)) + (max layout-gap-row (/ 16 zoom))])] [:rect.resize-handler {:x x @@ -295,6 +372,8 @@ height (gpo/height-points bounds) origin (gpo/origin bounds) + [layout-gap-row layout-gap-col] (ctl/gaps shape) + {:keys [row-tracks column-tracks] :as layout-data} (gsg/calc-layout-data shape children bounds) @@ -332,38 +411,76 @@ (for [[idx column-data] (d/enumerate column-tracks)] - (let [start-p (-> origin (gpt/add (hv (:distance column-data)))) - marker-p (-> start-p (gpt/subtract (vv (/ 20 zoom))))] + (let [start-p (:start-p column-data) + marker-p (-> start-p + (gpt/subtract (vv (/ 20 zoom))) + (cond-> (not= idx 0) + (gpt/subtract (hv (/ layout-gap-col 2))))) + + text-p (-> start-p + (gpt/subtract (vv (/ 20 zoom))) + (gpt/add (hv (/ (:size column-data) 2))))] [:* [:& track-marker {:center marker-p :value (dm/str (inc idx)) :zoom zoom}] - [:& resize-handler {:type :column - :start-p start-p - :zoom zoom - :bounds bounds}]])) + [:text {:x (:x text-p) + :y (:y text-p) + :font-size (/ 14 zoom) + :font-weight 600 + :font-family "worksans" + :dominant-baseline "central" + :text-anchor "middle" + :style {:fill "var(--color-distance)"}} + (format-size column-data)] + + (when (not= idx 0) + [:& resize-handler {:shape shape + :layout-data layout-data + :start-p start-p + :type :column + :zoom zoom}])])) (for [[idx row-data] (d/enumerate row-tracks)] - (let [start-p (-> origin (gpt/add (vv (:distance row-data)))) - marker-p (-> start-p (gpt/subtract (hv (/ 20 zoom))))] + (let [start-p (:start-p row-data) + marker-p (-> start-p + (gpt/subtract (hv (/ 20 zoom))) + (cond-> (not= idx 0) + (gpt/subtract (vv (/ layout-gap-row 2))))) + + text-p (-> start-p + (gpt/subtract (hv (/ 20 zoom))) + (gpt/add (vv (/ (:size row-data) 2))))] [:* [:g {:transform (dm/fmt "rotate(-90 % %)" (:x marker-p) (:y marker-p))} [:& track-marker {:center marker-p :value (dm/str (inc idx)) :zoom zoom}]] - [:& resize-handler {:type :row - :start-p start-p - :zoom zoom - :bounds bounds}]])) + [:g {:transform (dm/fmt "rotate(-90 % %)" (:x text-p) (:y text-p))} + [:text {:x (:x text-p) + :y (:y text-p) + :font-size (/ 14 zoom) + :font-weight 600 + :font-family "worksans" + :dominant-baseline "central" + :text-anchor "middle" + :style {:fill "var(--color-distance)"}} + (format-size row-data)]] + + (when (not= idx 0) + [:& resize-handler {:shape shape + :layout-data layout-data + :start-p start-p + :type :column + :zoom zoom}])])) (for [[_ {:keys [column row]}] (:layout-grid-cells shape)] [:& grid-cell {:shape shape :layout-data layout-data :row row :column column - :bounds bounds :zoom zoom :hover? (contains? hover-cells [row column]) :selected? (= selected-cells [row column])}])])) From 43d1f676efcb06f8875949496ab0d4a5cd119e68 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 25 Apr 2023 15:41:41 +0200 Subject: [PATCH 03/52] :sparkles: Move shapes in grid --- .../app/common/geom/shapes/grid_layout.cljc | 6 +- .../geom/shapes/grid_layout/layout_data.cljc | 9 +-- .../geom/shapes/grid_layout/positions.cljc | 80 +++++++++++++++++++ common/src/app/common/types/shape/layout.cljc | 78 +++++++++++++++++- .../app/main/data/workspace/modifiers.cljs | 49 ++++++++++-- .../app/main/data/workspace/transforms.cljs | 11 ++- .../viewport/grid_layout_editor.cljs | 31 ++++--- 7 files changed, 227 insertions(+), 37 deletions(-) diff --git a/common/src/app/common/geom/shapes/grid_layout.cljc b/common/src/app/common/geom/shapes/grid_layout.cljc index eb45960f89..cb28f3ab7b 100644 --- a/common/src/app/common/geom/shapes/grid_layout.cljc +++ b/common/src/app/common/geom/shapes/grid_layout.cljc @@ -13,7 +13,5 @@ (dm/export glld/calc-layout-data) (dm/export glld/get-cell-data) (dm/export glp/child-modifiers) - -(defn get-drop-index - [frame objects _position] - (dec (count (get-in objects [frame :shapes])))) +(dm/export glp/get-position-grid-coord) +(dm/export glp/get-drop-cell) 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 f45fb73267..ebc1b5b762 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 @@ -131,7 +131,7 @@ (reduce (fn [tracks [child-bounds child-shape]] (let [cell (get shape-cells (:id child-shape)) idx (dec (get cell prop)) - track (nth tracks idx)] + track (get tracks idx)] (cond-> tracks (and (= (get cell prop-span) 1) (= :auto (:type track))) (update-in [idx :size] max (size-fn child-bounds))))) @@ -190,9 +190,6 @@ bound-width (gpo/width-points layout-bounds) bound-corner (gpo/origin layout-bounds) - grid-columns (:layout-grid-columns parent) - grid-rows (:layout-grid-rows parent) - [row-gap column-gap] (ctl/gaps parent) ;; Map shape->cell @@ -205,11 +202,11 @@ ;; Initialize tracks column-tracks - (->> grid-columns + (->> (:layout-grid-columns parent) (mapv (partial calculate-initial-track-size bound-width))) row-tracks - (->> grid-rows + (->> (:layout-grid-rows parent) (mapv (partial calculate-initial-track-size bound-height))) ;; Go through cells to adjust auto sizes for span=1. Base is the max of its children diff --git a/common/src/app/common/geom/shapes/grid_layout/positions.cljc b/common/src/app/common/geom/shapes/grid_layout/positions.cljc index 3f81928b48..3dcdeba332 100644 --- a/common/src/app/common/geom/shapes/grid_layout/positions.cljc +++ b/common/src/app/common/geom/shapes/grid_layout/positions.cljc @@ -6,11 +6,91 @@ (ns app.common.geom.shapes.grid-layout.positions (:require + [app.common.data :as d] + [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] + [app.common.geom.shapes.common :as gco] + [app.common.geom.shapes.grid-layout.layout-data :as ld] [app.common.geom.shapes.points :as gpo] + [app.common.math :as mth] + [app.common.pages.helpers :as cph] [app.common.types.modifiers :as ctm])) (defn child-modifiers [_parent _transformed-parent-bounds _child child-bounds cell-data] (ctm/move-modifiers (gpt/subtract (:start-p cell-data) (gpo/origin child-bounds)))) + + +(defn line-value + [[{px :x py :y} {vx :x vy :y}] {:keys [x y]}] + (let [a vy + b (- vx) + c (+ (* (- vy) px) (* vx py))] + (+ (* a x) (* b y) c))) + +(defn is-inside-lines? + [line-1 line-2 pos] + (< (* (line-value line-1 pos) (line-value line-2 pos)) 0)) + +(defn get-position-grid-coord + [{:keys [layout-bounds row-tracks column-tracks]} position] + + (let [hv #(gpo/start-hv layout-bounds %) + vv #(gpo/start-vv layout-bounds %) + + ;;make-is-inside-track + ;;(fn [type] + ;; (let [[vfn ofn] (if (= type :column) [vv hv] [hv vv])] + ;; (fn is-inside-track? [{:keys [start-p size] :as track}] + ;; (let [unit-v (vfn 1) + ;; end-p (gpt/add start-p (ofn size))] + ;; (is-inside-lines? [start-p unit-v] [end-p unit-v] position))))) + + make-min-distance-track + (fn [type] + (let [[vfn ofn] (if (= type :column) [vv hv] [hv vv])] + (fn [[selected selected-dist] [cur-idx {:keys [start-p size] :as track}]] + (let [unit-v (vfn 1) + end-p (gpt/add start-p (ofn size)) + dist-1 (mth/abs (line-value [start-p unit-v] position)) + dist-2 (mth/abs (line-value [end-p unit-v] position))] + + (if (or (< dist-1 selected-dist) (< dist-2 selected-dist)) + [[cur-idx track] (min dist-1 dist-2)] + [selected selected-dist]))))) + + ;;[col-idx column] + ;;(->> (d/enumerate column-tracks) + ;; (d/seek (comp (make-is-inside-track :column) second))) + ;; + ;;[row-idx row] + ;;(->> (d/enumerate row-tracks) + ;; (d/seek (comp (make-is-inside-track :row) second))) + + + [col-idx column] + (->> (d/enumerate column-tracks) + (reduce (make-min-distance-track :column) [[nil nil] ##Inf]) + (first)) + + [row-idx row] + (->> (d/enumerate row-tracks) + (reduce (make-min-distance-track :row) [[nil nil] ##Inf]) + (first)) + ] + + (when (and (some? column) (some? row)) + [(inc row-idx) (inc col-idx)]))) + +(defn get-drop-cell + [frame-id objects position] + + (let [frame (get objects frame-id) + children (->> (cph/get-immediate-children objects (:id frame)) + (remove :hidden) + (map #(vector (gpo/parent-coords-bounds (:points %) (:points frame)) %))) + layout-data (ld/calc-layout-data frame children (:points frame)) + position (gmt/transform-point-center position (gco/center-shape frame) (:transform-inverse frame))] + + (get-position-grid-coord layout-data position))) diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index 8fc029b430..386a384c8d 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -679,6 +679,22 @@ [parent _cells] parent) +(defn get-cells + ([parent] + (get-cells parent nil)) + + ([{:keys [layout-grid-cells layout-grid-dir]} {:keys [sort?] :or {sort? false}}] + (let [comp-fn (if (= layout-grid-dir :row) + (juxt :row :column) + (juxt :column :row)) + + maybe-sort? + (if sort? (partial sort-by (comp comp-fn second)) identity)] + + (->> layout-grid-cells + (maybe-sort?) + (map (fn [[id cell]] (assoc cell :id id))))))) + (defn get-free-cells ([parent] (get-free-cells parent nil)) @@ -700,7 +716,7 @@ "Clean the cells whith shapes that are no longer in the layout" [parent] - (let [child? (into #{} (:shapes parent)) + (let [child? (set (:shapes parent)) cells (update-vals (:layout-grid-cells parent) (fn [cell] (update cell :shapes #(filterv child? %))))] @@ -767,3 +783,63 @@ (recur cells (rest free-cells) (rest pending)))))] (assoc parent :layout-grid-cells cells))))) + +(defn free-cell-push + "Frees the cell at index and push the shapes in the order given by the `cells` attribute" + [parent cells index] + + (let [start-cell (get cells index)] + (if (empty? (:shapes start-cell)) + [parent cells] + (let [[parent result-cells] + (loop [parent parent + result-cells cells + idx index] + + (if (> idx (- (count cells) 2)) + [parent result-cells] + + (let [cell-from (get cells idx) + cell-to (get cells (inc idx)) + cell (assoc cell-to :shapes (:shapes cell-from)) + parent (assoc-in parent [:layout-grid-cells (:id cell)] cell) + result-cells (assoc result-cells (inc idx) cell)] + + (if (empty? (:shapes cell-to)) + ;; to-cell was empty, so we've finished and every cell allocated + [parent result-cells] + + ;; otherwise keep pushing cells + (recur parent result-cells (inc idx))))))] + + [(assoc-in parent [:layout-grid-cells (get-in cells [index :id]) :shapes] []) + (assoc-in result-cells [index :shapes] [])])))) + +(defn seek-indexed-cell + [cells row column] + (let [cells+index (d/enumerate cells)] + (d/seek (fn [[_ {cell-row :row cell-column :column}]] + (and (= cell-row row) + (= cell-column column))) cells+index))) + +(defn push-into-cell + "Push the shapes into the row/column cell and moves the rest" + [parent shape-ids row column] + + (let [cells (vec (get-cells parent {:sort? true})) + cells+index (d/enumerate cells) + + [start-index _] (seek-indexed-cell cells row column) + + ;; start-index => to-index is the range where the shapes inserted will be added + to-index (min (+ start-index (count shape-ids)) (dec (count cells)))] + + ;; Move shift the `shapes` attribute between cells + (->> (range start-index (inc to-index)) + (map vector shape-ids) + (reduce (fn [[parent cells] [shape-id idx]] + (let [[parent cells] (free-cell-push parent cells idx)] + [(assoc-in parent [:layout-grid-cells (get-in cells [idx :id]) :shapes] [shape-id]) + cells])) + [parent cells]) + (first)))) diff --git a/frontend/src/app/main/data/workspace/modifiers.cljs b/frontend/src/app/main/data/workspace/modifiers.cljs index a27f9c4d1b..bf9d8c7285 100644 --- a/frontend/src/app/main/data/workspace/modifiers.cljs +++ b/frontend/src/app/main/data/workspace/modifiers.cljs @@ -175,8 +175,28 @@ (update-in modif-tree [parent-id :modifiers] ctm/remove-children [child-id]))) modif-tree))) +(defn add-grid-children-modifiers + [modifiers frame-id selected objects [row column :as cell]] + (let [frame (get objects frame-id) + + ;; Temporary remove the children when moving them + frame (-> frame + (update :shapes #(d/removev selected %)) + (ctl/assign-cells)) + + frame (-> frame + (update :shapes d/concat-vec selected) + (cond-> (some? cell) + (ctl/push-into-cell selected row column)) + (ctl/assign-cells))] + + (-> modifiers + (ctm/change-property :layout-grid-rows (:layout-grid-rows frame)) + (ctm/change-property :layout-grid-columns (:layout-grid-columns frame)) + (ctm/change-property :layout-grid-cells (:layout-grid-cells frame))))) + (defn build-change-frame-modifiers - [modif-tree objects selected target-frame-id drop-index] + [modif-tree objects selected target-frame-id drop-index cell-data] (let [origin-frame-ids (->> selected (group-by #(get-in objects [% :frame-id]))) child-set (set (get-in objects [target-frame-id :shapes])) @@ -209,8 +229,10 @@ children-ids (->> (dm/get-in objects [original-frame :shapes]) (remove (set selected))) - h-sizing? (ctl/change-h-sizing? original-frame objects children-ids) - v-sizing? (ctl/change-v-sizing? original-frame objects children-ids)] + h-sizing? (and (ctl/flex-layout? objects original-frame) + (ctl/change-h-sizing? original-frame objects children-ids)) + v-sizing? (and (ctl/flex-layout? objects original-frame) + (ctl/change-v-sizing? original-frame objects children-ids))] (cond-> modif-tree (not= original-frame target-frame-id) (-> (modifier-remove-from-parent objects shapes) @@ -227,11 +249,19 @@ (as-> modif-tree $ (reduce update-frame-modifiers $ origin-frame-ids) (cond-> $ - (ctl/change-h-sizing? target-frame-id objects children-ids) - (update-in [target-frame-id :modifiers] ctm/change-property :layout-item-h-sizing :fix)) - (cond-> $ - (ctl/change-v-sizing? target-frame-id objects children-ids) - (update-in [target-frame-id :modifiers] ctm/change-property :layout-item-v-sizing :fix))))) + ;; Set fix position to target frame (horizontal) + (and (ctl/flex-layout? objects target-frame-id) + (ctl/change-h-sizing? target-frame-id objects children-ids)) + (update-in [target-frame-id :modifiers] ctm/change-property :layout-item-h-sizing :fix) + + ;; Set fix position to target frame (vertical) + (and (ctl/flex-layout? objects target-frame-id) + (ctl/change-v-sizing? target-frame-id objects children-ids)) + (update-in [target-frame-id :modifiers] ctm/change-property :layout-item-v-sizing :fix) + + ;; Add the object to the cell + (ctl/grid-layout? objects target-frame-id) + (update-in [target-frame-id :modifiers] add-grid-children-modifiers target-frame-id selected objects cell-data))))) (defn modif->js [modif-tree objects] @@ -454,6 +484,9 @@ :layout-gap :layout-item-margin :layout-item-margin-type + :layout-grid-cells + :layout-grid-columns + :layout-grid-rows ]}) ;; We've applied the text-modifier so we can dissoc the temporary data (fn [state] diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 862cb4599c..125f12cea2 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -490,10 +490,9 @@ target-frame (ctst/top-nested-frame objects position exclude-frames) flex-layout? (ctl/flex-layout? objects target-frame) grid-layout? (ctl/grid-layout? objects target-frame) - drop-index (cond - flex-layout? (gslf/get-drop-index target-frame objects position) - grid-layout? (gslg/get-drop-index target-frame objects position))] - [move-vector target-frame drop-index]))) + drop-index (when flex-layout? (gslf/get-drop-index target-frame objects position)) + cell-data (when grid-layout? (gslg/get-drop-cell target-frame objects position))] + [move-vector target-frame drop-index cell-data]))) (rx/take-until stopper))] @@ -502,7 +501,7 @@ (->> move-stream (rx/with-latest-from ms/mouse-position-shift) (rx/map - (fn [[[move-vector target-frame drop-index] shift?]] + (fn [[[move-vector target-frame drop-index cell-data] shift?]] (let [x-disp? (> (mth/abs (:x move-vector)) (mth/abs (:y move-vector))) [move-vector snap-ignore-axis] (cond @@ -516,7 +515,7 @@ [move-vector nil])] (-> (dwm/create-modif-tree ids (ctm/move-modifiers move-vector)) - (dwm/build-change-frame-modifiers objects selected target-frame drop-index) + (dwm/build-change-frame-modifiers objects selected target-frame drop-index cell-data) (dwm/set-modifiers false false {:snap-ignore-axis snap-ignore-axis})))))) (->> move-stream 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 af78977178..995b67645a 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 @@ -17,6 +17,7 @@ [app.main.data.workspace.shape-layout :as dwsl] [app.main.refs :as refs] [app.main.store :as st] + [app.main.ui.workspace.viewport.viewport-ref :as uwvv] [app.util.dom :as dom] [cuerdas.core :as str] [rumext.v2 :as mf])) @@ -153,18 +154,20 @@ (mf/use-callback (mf/deps on-drag-start) (fn [event] - (let [position (dom/get-client-position event)] + (let [raw-pt (dom/get-client-position event) + position (uwvv/point->viewport raw-pt)] (dom/capture-pointer event) (mf/set-ref-val! dragging-ref true) - (mf/set-ref-val! start-pos-ref position) - (mf/set-ref-val! current-pos-ref position) + (mf/set-ref-val! start-pos-ref raw-pt) + (mf/set-ref-val! current-pos-ref raw-pt) (when on-drag-start (on-drag-start position))))) handle-lost-pointer-capture (mf/use-callback (mf/deps on-drag-end) (fn [event] - (let [position (mf/ref-val current-pos-ref)] + (let [raw-pt (mf/ref-val current-pos-ref) + position (uwvv/point->viewport raw-pt)] (dom/release-pointer event) (mf/set-ref-val! dragging-ref false) (mf/set-ref-val! start-pos-ref nil) @@ -175,10 +178,11 @@ (fn [event] (when (mf/ref-val dragging-ref) (let [start (mf/ref-val start-pos-ref) - pos (dom/get-client-position event)] + pos (dom/get-client-position event) + pt (uwvv/point->viewport pos)] (mf/set-ref-val! current-pos-ref pos) (when on-drag-delta (on-drag-delta (gpt/to-vec start pos))) - (when on-drag-position (on-drag-position pos))))))] + (when on-drag-position (on-drag-position pt))))))] {:handle-pointer-down handle-pointer-down :handle-lost-pointer-capture handle-lost-pointer-capture @@ -192,15 +196,16 @@ width (unchecked-get props "width") height (unchecked-get props "height") direction (unchecked-get props "direction") + layout-data (unchecked-get props "layout-data") cursor (if (= direction :row) (cur/scale-ns 0) (cur/scale-ew 0)) - handle-drag-delta + handle-drag-position (mf/use-callback - (fn [delta] - (prn ">>>" delta))) + (fn [position] + (prn ">>>" (gsg/get-position-grid-coord layout-data position)))) {:keys [handle-pointer-down handle-lost-pointer-capture handle-pointer-move]} - (use-drag {:on-drag-delta handle-drag-delta})] + (use-drag {:on-drag-position handle-drag-position})] [:rect {:x x @@ -218,7 +223,8 @@ [props] (let [shape (unchecked-get props "shape") - {:keys [origin row-tracks column-tracks layout-bounds]} (unchecked-get props "layout-data") + {:keys [origin row-tracks column-tracks layout-bounds] :as layout-data} + (unchecked-get props "layout-data") zoom (unchecked-get props "zoom") @@ -278,7 +284,8 @@ :y y :width width :height height - :direction dir}])]))])) + :direction dir + :layout-data layout-data}])]))])) (mf/defc resize-handler {::mf/wrap-props false} From 0eff2e88870a410d835739babc4374d038fee358 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 27 Apr 2023 10:35:06 +0200 Subject: [PATCH 04/52] :sparkles: Support for multi-track span in cells --- .../common/geom/shapes/grid_layout/areas.cljc | 95 ++++++++++ .../geom/shapes/grid_layout/layout_data.cljc | 52 +----- common/src/app/common/types/shape/layout.cljc | 136 +++++++++++--- .../data/workspace/grid_layout/editor.cljs | 10 +- .../main/ui/workspace/sidebar/options.cljs | 7 +- .../options/menus/layout_container.cljs | 2 +- .../sidebar/options/shapes/grid_cell.cljs | 10 +- .../src/app/main/ui/workspace/viewport.cljs | 16 +- .../viewport/grid_layout_editor.cljs | 171 ++++++++++++------ 9 files changed, 354 insertions(+), 145 deletions(-) create mode 100644 common/src/app/common/geom/shapes/grid_layout/areas.cljc diff --git a/common/src/app/common/geom/shapes/grid_layout/areas.cljc b/common/src/app/common/geom/shapes/grid_layout/areas.cljc new file mode 100644 index 0000000000..569c52470c --- /dev/null +++ b/common/src/app/common/geom/shapes/grid_layout/areas.cljc @@ -0,0 +1,95 @@ +;; 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 + +;; Based on the code in: +;; https://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Rectangle_difference +(ns app.common.geom.shapes.grid-layout.areas + (:refer-clojure :exclude [contains?])) + +(defn area->cell-props [[column row column-span row-span]] + {:row row + :column column + :row-span row-span + :column-span column-span}) + +(defn make-area + ([{:keys [column row column-span row-span]}] + (make-area column row column-span row-span)) + ([x y width height] + [x y width height])) + +(defn contains? + [[a-x a-y a-width a-height :as a] + [b-x b-y b-width b-height :as b]] + (and (>= b-x a-x) + (>= b-y a-y) + (<= (+ b-x b-width) (+ a-x a-width)) + (<= (+ b-y b-height) (+ a-y a-height)))) + +(defn intersects? + [[a-x a-y a-width a-height ] + [b-x b-y b-width b-height]] + (not (or (<= (+ b-x b-width) a-x) + (<= (+ b-y b-height) a-y) + (>= b-x (+ a-x a-width)) + (>= b-y (+ a-y a-height))))) + +(defn top-rect + [[a-x a-y a-width _] + [_ b-y _ _]] + (let [height (- b-y a-y)] + (when (> height 0) + (make-area a-x a-y a-width height)))) + +(defn bottom-rect + [[a-x a-y a-width a-height] + [_ b-y _ b-height]] + + (let [y (+ b-y b-height) + height (- a-height (- y a-y))] + (when (and (> height 0) (< y (+ a-y a-height))) + (make-area a-x y a-width height)))) + +(defn left-rect + [[a-x a-y _ a-height] + [b-x b-y _ b-height]] + + (let [rb-y (+ b-y b-height) + ra-y (+ a-y a-height) + y1 (max a-y b-y) + y2 (min ra-y rb-y) + height (- y2 y1) + width (- b-x a-x)] + (when (and (> width 0) (> height 0)) + (make-area a-x y1 width height)))) + +(defn right-rect + [[a-x a-y a-width a-height] + [b-x b-y b-width b-height]] + + (let [rb-y (+ b-y b-height) + ra-y (+ a-y a-height) + y1 (max a-y b-y) + y2 (min ra-y rb-y) + height (- y2 y1) + rb-x (+ b-x b-width) + width (- a-width (- rb-x a-x)) + ] + (when (and (> width 0) (> height 0)) + (make-area rb-x y1 width height))) + ) + +(defn difference + [area-a area-b] + (if (or (nil? area-b) + (not (intersects? area-a area-b)) + (contains? area-b area-a)) + [] + + (into [] + (keep #(% area-a area-b)) + [top-rect left-rect right-rect bottom-rect]))) + 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 ebc1b5b762..79de30499e 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 @@ -49,8 +49,6 @@ ;; ;; - Stretch auto tracks - - (ns app.common.geom.shapes.grid-layout.layout-data (:require [app.common.geom.point :as gpt] @@ -62,47 +60,6 @@ (let [[pad-top pad-right pad-bottom pad-left] (ctl/paddings parent)] (gpo/pad-points shape-bounds pad-top pad-right pad-bottom pad-left))) -#_(defn set-sample-data - [parent children] - - (let [parent (assoc parent - :layout-grid-columns - [{:type :percent :value 25} - {:type :percent :value 25} - {:type :fixed :value 100} - ;;{:type :auto} - ;;{:type :flex :value 1} - ] - - :layout-grid-rows - [{:type :percent :value 50} - {:type :percent :value 50} - ;;{:type :fixed :value 100} - ;;{:type :auto} - ;;{:type :flex :value 1} - ]) - - num-rows (count (:layout-grid-rows parent)) - num-columns (count (:layout-grid-columns parent)) - - layout-grid-cells - (into - {} - (for [[row-idx _row] (d/enumerate (:layout-grid-rows parent)) - [col-idx _col] (d/enumerate (:layout-grid-columns parent))] - (let [[_bounds shape] (nth children (+ (* row-idx num-columns) col-idx) nil) - cell-data {:id (uuid/next) - :row (inc row-idx) - :column (inc col-idx) - :row-span 1 - :col-span 1 - :shapes (when shape [(:id shape)])}] - [(:id cell-data) cell-data]))) - - parent (assoc parent :layout-grid-cells layout-grid-cells)] - - [parent children])) - (defn calculate-initial-track-size [total-value {:keys [type value] :as track}] @@ -119,7 +76,6 @@ [0.01 ##Inf])] (assoc track :size size :max-size max-size))) - (defn set-auto-base-size [track-list children shape-cells type] @@ -280,7 +236,6 @@ (gpt/add start-p (vv (+ size row-gap)))]) [[] start-p]) (first)) - ] {:origin start-p @@ -288,6 +243,8 @@ :row-tracks row-tracks :column-tracks column-tracks :shape-cells shape-cells + :column-gap column-gap + :row-gap row-gap ;; Convenient informaton for visualization :column-total-size column-total-size @@ -300,7 +257,6 @@ [{:keys [origin row-tracks column-tracks shape-cells]} _transformed-parent-bounds [_ child]] (let [grid-cell (get shape-cells (:id child))] - (when (some? grid-cell) (let [column (nth column-tracks (dec (:column grid-cell)) nil) row (nth row-tracks (dec (:row grid-cell)) nil) @@ -311,7 +267,7 @@ start-p (gpt/add origin (gpt/add (gpt/to-vec origin column-start-p) - (gpt/to-vec origin row-start-p))) - ] + (gpt/to-vec origin row-start-p)))] (assoc grid-cell :start-p start-p))))) + diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index 386a384c8d..165f555637 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -8,6 +8,7 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] + [app.common.geom.shapes.grid-layout.areas :as sga] [app.common.math :as mth] [app.common.schema :as sm] [app.common.uuid :as uuid])) @@ -673,12 +674,6 @@ (update :layout-grid-cells remove-cells) (assign-cells)))) -;; TODO: Mix the cells given as arguments leaving only one. It should move all the shapes in those cells in the direction for the grid -;; and lastly use assign-cells to reassing the orphaned shapes -(defn merge-cells - [parent _cells] - parent) - (defn get-cells ([parent] (get-cells parent nil)) @@ -815,31 +810,128 @@ [(assoc-in parent [:layout-grid-cells (get-in cells [index :id]) :shapes] []) (assoc-in result-cells [index :shapes] [])])))) + +(defn in-cell? + "Given a cell check if the row+column is inside this cell" + [{cell-row :row cell-column :column :keys [row-span column-span]} row column] + (and (>= row cell-row) + (>= column cell-column) + (<= row (+ cell-row row-span -1)) + (<= column (+ cell-column column-span -1)))) + +(defn cell-by-row-column + [parent row column] + (->> (:layout-grid-cells parent) + (vals) + (d/seek #(in-cell? % row column)))) + (defn seek-indexed-cell [cells row column] (let [cells+index (d/enumerate cells)] - (d/seek (fn [[_ {cell-row :row cell-column :column}]] - (and (= cell-row row) - (= cell-column column))) cells+index))) + (d/seek #(in-cell? (second %) row column) cells+index))) (defn push-into-cell "Push the shapes into the row/column cell and moves the rest" [parent shape-ids row column] (let [cells (vec (get-cells parent {:sort? true})) - cells+index (d/enumerate cells) + [start-index start-cell] (seek-indexed-cell cells row column)] - [start-index _] (seek-indexed-cell cells row column) + (if (some? start-cell) + (let [ ;; start-index => to-index is the range where the shapes inserted will be added + to-index (min (+ start-index (count shape-ids)) (dec (count cells)))] - ;; start-index => to-index is the range where the shapes inserted will be added - to-index (min (+ start-index (count shape-ids)) (dec (count cells)))] + ;; Move shift the `shapes` attribute between cells + (->> (range start-index (inc to-index)) + (map vector shape-ids) + (reduce (fn [[parent cells] [shape-id idx]] + (let [[parent cells] (free-cell-push parent cells idx)] + [(assoc-in parent [:layout-grid-cells (get-in cells [idx :id]) :shapes] [shape-id]) + cells])) + [parent cells]) + (first))) + parent))) - ;; Move shift the `shapes` attribute between cells - (->> (range start-index (inc to-index)) - (map vector shape-ids) - (reduce (fn [[parent cells] [shape-id idx]] - (let [[parent cells] (free-cell-push parent cells idx)] - [(assoc-in parent [:layout-grid-cells (get-in cells [idx :id]) :shapes] [shape-id]) - cells])) - [parent cells]) - (first)))) +(defn resize-cell-area + "Increases/decreases the cell size" + [parent row column new-row new-column new-row-span new-column-span] + + (let [cells (vec (get-cells parent {:sort? true})) + + prev-cell (cell-by-row-column parent row column) + prev-area (sga/make-area prev-cell) + + target-cell + (-> prev-cell + (assoc + :row new-row + :column new-column + :row-span new-row-span + :column-span new-column-span)) + + target-area (sga/make-area target-cell)] + + (if (sga/contains? prev-area target-area) + ;; The new area is smaller than the previous. We need to create cells in the empty space + (let [parent + (-> parent + (assoc-in [:layout-grid-cells (:id target-cell)] target-cell)) + + new-cells + (->> (sga/difference prev-area target-area) + (mapcat (fn [[column row column-span row-span]] + (for [new-col (range column (+ column column-span)) + new-row (range row (+ row row-span))] + (merge grid-cell-defaults + {:id (uuid/next) + :row new-row + :column new-col + :row-span 1 + :column-span 1}))))) + + parent + (->> new-cells + (reduce #(assoc-in %1 [:layout-grid-cells (:id %2)] %2) parent))] + + parent) + + ;; The new area is bigger we need to remove the cells filled and split the intersections + (let [remove-cells (->> cells + (filter #(and (not= (:id target-cell) (:id %)) + (sga/contains? target-area (sga/make-area %)))) + (into #{})) + + split-cells (->> cells (filter #(and (not= (:id target-cell) (:id %)) + (not (contains? remove-cells %)) + (sga/intersects? target-area (sga/make-area %))))) + + [parent _] + (->> (d/enumerate cells) + (reduce (fn [[parent cells] [index cur-cell]] + (if (contains? remove-cells cur-cell) + (let [[parent cells] (free-cell-push parent cells index)] + [parent (conj cells cur-cell)]) + [parent cells])) + [parent cells])) + + parent + (-> parent + (assoc-in [:layout-grid-cells (:id target-cell)] target-cell)) + + parent + (->> remove-cells + (reduce (fn [parent cell] + (update parent :layout-grid-cells dissoc (:id cell))) + parent)) + + parent + (->> split-cells + (reduce (fn [parent cell] + (let [new-areas (sga/difference (sga/make-area cell) target-area)] + (as-> parent $ + (update-in $ [:layout-grid-cells (:id cell)] merge (sga/area->cell-props (first new-areas))) + (reduce (fn [parent area] + (let [cell (merge (assoc grid-cell-defaults :id (uuid/next)) (sga/area->cell-props area))] + (assoc-in parent [:layout-grid-cells (:id cell)] cell))) $ new-areas)))) + parent))] + parent)))) diff --git a/frontend/src/app/main/data/workspace/grid_layout/editor.cljs b/frontend/src/app/main/data/workspace/grid_layout/editor.cljs index 6f9bf9a8eb..2a77103669 100644 --- a/frontend/src/app/main/data/workspace/grid_layout/editor.cljs +++ b/frontend/src/app/main/data/workspace/grid_layout/editor.cljs @@ -9,7 +9,7 @@ [potok.core :as ptk])) (defn hover-grid-cell - [grid-id row column add-to-set] + [grid-id cell-id add-to-set] (ptk/reify ::hover-grid-cell ptk/UpdateEvent (update [_ state] @@ -19,15 +19,15 @@ (fn [hover-set] (let [hover-set (or hover-set #{})] (if add-to-set - (conj hover-set [row column]) - (disj hover-set [row column])))))))) + (conj hover-set cell-id) + (disj hover-set cell-id)))))))) (defn select-grid-cell - [grid-id row column] + [grid-id cell-id] (ptk/reify ::select-grid-cell ptk/UpdateEvent (update [_ state] - (assoc-in state [:workspace-grid-edition grid-id :selected] [row column])))) + (assoc-in state [:workspace-grid-edition grid-id :selected] cell-id)))) (defn remove-selection [grid-id] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options.cljs b/frontend/src/app/main/ui/workspace/sidebar/options.cljs index efe244bcd5..16ce75b7b9 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options.cljs @@ -73,10 +73,10 @@ first-selected-shape (first selected-shapes) shape-parent-frame (cph/get-frame objects (:frame-id first-selected-shape)) - [grid-id {[row-selected col-selected] :selected}] + [grid-id {cell-id :selected}] (d/seek (fn [[_ {:keys [selected]}]] (some? selected)) grid-edition) - grid-cell-selected? (and (some? grid-id) (some? row-selected) (some? col-selected)) + grid-cell-selected? (and (some? grid-id) (some? cell-id)) on-change-tab (fn [options-mode] @@ -96,8 +96,7 @@ [:& bool-options] (cond grid-cell-selected? [:& grid-cell/options {:shape (get objects grid-id) - :row row-selected - :column col-selected}] + :cell (get-in objects [grid-id :layout-grid-cells cell-id])}] (d/not-empty? drawing) [:& shape-options {:shape (:object drawing) :page-id page-id 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 8e6f02eb6b..a4e9979e45 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 @@ -447,7 +447,7 @@ (when expanded? [:div.columns-info-wrapper (for [[index column] (d/enumerate column-values)] - [:div.column-info + [:div.column-info {:key (dm/str index "-" (name type) "-" column)} [:div.direction-grid-icon (if is-col? i/layout-rows diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/grid_cell.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/grid_cell.cljs index 2d02bd6047..1153005950 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/grid_cell.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/grid_cell.cljs @@ -31,7 +31,7 @@ (mf/defc options {::mf/wrap [mf/memo]} - [{:keys [_shape row column] :as props}] + [{:keys [_shape cell] :as props}] (let [position-mode (mf/use-state :auto) ;; TODO this should come from shape @@ -51,10 +51,10 @@ #_(if (= align-self value) (st/emit! (dwsl/update-layout-child ids {:layout-item-align-self nil})) (st/emit! (dwsl/update-layout-child ids {:layout-item-align-self value})))) - column-start column - column-end (inc column) - row-start row - row-end (inc row) + column-start (:column cell) + column-end (+ (:column cell) (:column-span cell)) + row-start (:row cell) + row-end (+ (:row cell) (:row-span cell)) on-change (fn [_side _orientation _value] diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index d84b525df1..48ebc40b7e 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -203,6 +203,9 @@ show-pixel-grid? (and (contains? layout :show-pixel-grid) (>= zoom 8)) show-text-editor? (and editing-shape (= :text (:type editing-shape))) + + hover-grid? (and (some? @hover-top-frame-id) (ctl/grid-layout? objects @hover-top-frame-id)) + show-grid-editor? (and editing-shape (ctl/grid-layout? editing-shape)) show-presence? page-id show-prototypes? (= options-mode :prototype) @@ -586,8 +589,15 @@ {:id (first selected) :zoom zoom}]) - (when show-grid-editor? + ;; TODO: Temporary showing on hover. Remove eventualy + (when (or show-grid-editor? hover-grid?) [:& grid-layout/editor {:zoom zoom - :objects base-objects - :shape (get base-objects edition)}])]]])) + :objects objects-modified + :shape (or (get objects-modified edition) + (gsh/transform-shape + (get base-objects @hover-top-frame-id) + (dm/get-in modifiers [@hover-top-frame-id :modifiers]))) + :view-only (not show-grid-editor?) + }]) + ]]])) 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 995b67645a..dee2f761ce 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 @@ -11,9 +11,10 @@ [app.common.geom.point :as gpt] [app.common.geom.shapes.grid-layout :as gsg] [app.common.geom.shapes.points :as gpo] - [app.common.pages.helpers :as cph] + [app.common.types.modifiers :as ctm] [app.common.types.shape.layout :as ctl] [app.main.data.workspace.grid-layout.editor :as dwge] + [app.main.data.workspace.modifiers :as dwm] [app.main.data.workspace.shape-layout :as dwsl] [app.main.refs :as refs] [app.main.store :as st] @@ -145,8 +146,7 @@ (defn use-drag [{:keys [on-drag-start on-drag-end on-drag-delta on-drag-position]}] - (let [ - dragging-ref (mf/use-ref false) + (let [dragging-ref (mf/use-ref false) start-pos-ref (mf/use-ref nil) current-pos-ref (mf/use-ref nil) @@ -175,6 +175,7 @@ handle-pointer-move (mf/use-callback + (mf/deps on-drag-delta on-drag-position) (fn [event] (when (mf/ref-val dragging-ref) (let [start (mf/ref-val start-pos-ref) @@ -191,22 +192,67 @@ (mf/defc resize-cell-handler {::mf/wrap-props false} [props] - (let [x (unchecked-get props "x") + (let [shape (unchecked-get props "shape") + + x (unchecked-get props "x") y (unchecked-get props "y") width (unchecked-get props "width") height (unchecked-get props "height") + handler (unchecked-get props "handler") + + {cell-id :id} (unchecked-get props "cell") + {:keys [row column row-span column-span]} (get-in shape [:layout-grid-cells cell-id]) + direction (unchecked-get props "direction") layout-data (unchecked-get props "layout-data") cursor (if (= direction :row) (cur/scale-ns 0) (cur/scale-ew 0)) handle-drag-position (mf/use-callback + (mf/deps shape row column row-span column-span) (fn [position] - (prn ">>>" (gsg/get-position-grid-coord layout-data position)))) + (let [[drag-row drag-column] (gsg/get-position-grid-coord layout-data position) + + [new-row new-column new-row-span new-column-span] + (case handler + :top + (let [new-row (min (+ row (dec row-span)) drag-row) + new-row-span (+ (- row new-row) row-span)] + [new-row column new-row-span column-span]) + + :left + (let [new-column (min (+ column (dec column-span)) drag-column) + new-column-span (+ (- column new-column) column-span)] + [row new-column row-span new-column-span]) + + :bottom + (let [new-row-span (max 1 (inc (- drag-row row)))] + [row column new-row-span column-span]) + + :right + (let [new-column-span (max 1 (inc (- drag-column column)))] + [row column row-span new-column-span])) + + shape + (-> (ctl/resize-cell-area shape row column new-row new-column new-row-span new-column-span) + (ctl/assign-cells)) + + modifiers + (-> (ctm/empty) + (ctm/change-property :layout-grid-rows (:layout-grid-rows shape)) + (ctm/change-property :layout-grid-columns (:layout-grid-columns shape)) + (ctm/change-property :layout-grid-cells (:layout-grid-cells shape)))] + (st/emit! (dwm/set-modifiers (dwm/create-modif-tree [(:id shape)] modifiers)))))) + + handle-drag-end + (mf/use-callback + (fn [] + (st/emit! (dwm/apply-modifiers)))) {:keys [handle-pointer-down handle-lost-pointer-capture handle-pointer-move]} - (use-drag {:on-drag-position handle-drag-position})] - + (use-drag {:on-drag-position handle-drag-position + ;;:on-drag-start handle-drag-start + :on-drag-end handle-drag-end})] [:rect {:x x :y y @@ -223,7 +269,8 @@ [props] (let [shape (unchecked-get props "shape") - {:keys [origin row-tracks column-tracks layout-bounds] :as layout-data} + {:keys [row column row-span column-span] :as cell} (unchecked-get props "cell") + {:keys [origin row-tracks column-tracks layout-bounds column-gap row-gap] :as layout-data} (unchecked-get props "layout-data") zoom (unchecked-get props "zoom") @@ -231,23 +278,23 @@ hover? (unchecked-get props "hover?") selected? (unchecked-get props "selected?") - row (unchecked-get props "row") - column (unchecked-get props "column") - - column-track (nth column-tracks (dec column) nil) - row-track (nth row-tracks (dec row) nil) + span-column-tracks (subvec column-tracks (dec column) (+ (dec column) column-span)) + span-row-tracks (subvec row-tracks (dec row) (+ (dec row) row-span)) hv #(gpo/start-hv layout-bounds %) vv #(gpo/start-vv layout-bounds %) start-p (gpt/add origin (gpt/add - (gpt/to-vec origin (:start-p column-track)) - (gpt/to-vec origin (:start-p row-track)))) + (gpt/to-vec origin (dm/get-in span-column-tracks [0 :start-p])) + (gpt/to-vec origin (dm/get-in span-row-tracks [0 :start-p])))) - end-p (-> start-p - (gpt/add (hv (:size column-track))) - (gpt/add (vv (:size row-track)))) + end-p + (as-> start-p $ + (reduce (fn [p track] (gpt/add p (hv (:size track)))) $ span-column-tracks) + (reduce (fn [p track] (gpt/add p (vv (:size track)))) $ span-row-tracks) + (gpt/add $ (hv (* column-gap (dec (count span-column-tracks))))) + (gpt/add $ (vv (* row-gap (dec (count span-row-tracks)))))) cell-width (- (:x end-p) (:x start-p)) cell-height (- (:y end-p) (:y start-p))] @@ -259,10 +306,9 @@ :width cell-width :height cell-height - :on-pointer-enter #(st/emit! (dwge/hover-grid-cell (:id shape) row column true)) - :on-pointer-leave #(st/emit! (dwge/hover-grid-cell (:id shape) row column false)) - - :on-click #(st/emit! (dwge/select-grid-cell (:id shape) row column)) + :on-pointer-enter #(st/emit! (dwge/hover-grid-cell (:id shape) (:id cell) true)) + :on-pointer-leave #(st/emit! (dwge/hover-grid-cell (:id shape) (:id cell) false)) + :on-click #(st/emit! (dwge/select-grid-cell (:id shape) (:id cell))) :style {:fill "transparent" :stroke "var(--color-distance)" @@ -274,14 +320,18 @@ (when selected? (let [handlers ;; Handlers positions, size and cursor - [[(:x start-p) (+ (:y start-p) (/ -10 zoom)) cell-width (/ 20 zoom) :row] - [(+ (:x start-p) cell-width (/ -10 zoom)) (:y start-p) (/ 20 zoom) cell-height :column] - [(:x start-p) (+ (:y start-p) cell-height (/ -10 zoom)) cell-width (/ 20 zoom) :row] - [(+ (:x start-p) (/ -10 zoom)) (:y start-p) (/ 20 zoom) cell-height :column]]] + [[:top (:x start-p) (+ (:y start-p) (/ -10 zoom)) cell-width (/ 20 zoom) :row] + [:right (+ (:x start-p) cell-width (/ -10 zoom)) (:y start-p) (/ 20 zoom) cell-height :column] + [:bottom (:x start-p) (+ (:y start-p) cell-height (/ -10 zoom)) cell-width (/ 20 zoom) :row] + [:left (+ (:x start-p) (/ -10 zoom)) (:y start-p) (/ 20 zoom) cell-height :column]]] [:* - (for [[x y width height dir] handlers] - [:& resize-cell-handler {:x x + (for [[handler x y width height dir] handlers] + [:& resize-cell-handler {:key (dm/str "resize-" (d/name handler) "-" (:id cell)) + :shape shape + :handler handler + :x x :y y + :cell cell :width width :height height :direction dir @@ -355,21 +405,28 @@ :style {:fill "transparent"}}])) (mf/defc editor - {::mf/wrap-props false} + {::mf/wrap [mf/memo] + ::mf/wrap-props false} [props] - (let [shape (unchecked-get props "shape") - objects (unchecked-get props "objects") - zoom (unchecked-get props "zoom") + (let [shape (unchecked-get props "shape") + objects (unchecked-get props "objects") + zoom (unchecked-get props "zoom") + view-only (unchecked-get props "view-only") bounds (:points shape) + ;; We need to know the state unmodified so we can create the modifiers + shape-ref (mf/use-memo (mf/deps (:id shape)) #(refs/object-by-id (:id shape))) + base-shape (mf/deref shape-ref) + grid-edition-id-ref (mf/use-memo #(refs/workspace-grid-edition-id (:id shape))) grid-edition (mf/deref grid-edition-id-ref) hover-cells (:hover grid-edition) selected-cells (:selected grid-edition) - children (->> (cph/get-immediate-children objects (:id shape)) + children (->> (:shapes shape) + (map (d/getf objects)) (remove :hidden) (map #(vector (gpo/parent-coords-bounds (:points %) (:points shape)) %))) @@ -400,22 +457,22 @@ (fn [] #(st/emit! (dwge/stop-grid-layout-editing (:id shape))))) - [:g.grid-editor - [:& grid-editor-frame {:zoom zoom - :bounds bounds}] - (let [start-p (-> origin (gpt/add (hv width)))] - [:& plus-btn {:start-p start-p - :zoom zoom - :type :column - :on-click handle-add-column}]) - - (let [start-p (-> origin (gpt/add (vv height)))] - [:& plus-btn {:start-p start-p - :zoom zoom - :type :row - :on-click handle-add-row}]) - + [:g.grid-editor {:pointer-events (when view-only "none")} + (when-not view-only + [:* + [:& grid-editor-frame {:zoom zoom + :bounds bounds}] + (let [start-p (-> origin (gpt/add (hv width)))] + [:& plus-btn {:start-p start-p + :zoom zoom + :type :column + :on-click handle-add-column}]) + (let [start-p (-> origin (gpt/add (vv height)))] + [:& plus-btn {:start-p start-p + :zoom zoom + :type :row + :on-click handle-add-row}])]) (for [[idx column-data] (d/enumerate column-tracks)] (let [start-p (:start-p column-data) @@ -427,7 +484,7 @@ text-p (-> start-p (gpt/subtract (vv (/ 20 zoom))) (gpt/add (hv (/ (:size column-data) 2))))] - [:* + [:* {:key (dm/str "column-" idx)} [:& track-marker {:center marker-p :value (dm/str (inc idx)) :zoom zoom}] @@ -454,12 +511,12 @@ marker-p (-> start-p (gpt/subtract (hv (/ 20 zoom))) (cond-> (not= idx 0) - (gpt/subtract (vv (/ layout-gap-row 2))))) + (gpt/subtract (vv (/ layout-gap-row 2))))) text-p (-> start-p (gpt/subtract (hv (/ 20 zoom))) (gpt/add (vv (/ (:size row-data) 2))))] - [:* + [:* {:key (dm/str "row-" idx)} [:g {:transform (dm/fmt "rotate(-90 % %)" (:x marker-p) (:y marker-p))} [:& track-marker {:center marker-p :value (dm/str (inc idx)) @@ -483,11 +540,11 @@ :type :column :zoom zoom}])])) - (for [[_ {:keys [column row]}] (:layout-grid-cells shape)] - [:& grid-cell {:shape shape + (for [[_ cell] (:layout-grid-cells shape)] + [:& grid-cell {:key (dm/str "cell-" (:id cell)) + :shape base-shape :layout-data layout-data - :row row - :column column + :cell cell :zoom zoom - :hover? (contains? hover-cells [row column]) - :selected? (= selected-cells [row column])}])])) + :hover? (contains? hover-cells (:id cell)) + :selected? (= selected-cells (:id cell))}])])) From cdebf245e3374b30070fe7278d2c097e9e83e722 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 3 May 2023 15:27:40 +0200 Subject: [PATCH 05/52] :sparkles: Multispan cells auto sizing --- .../geom/shapes/grid_layout/layout_data.cljc | 150 +++++++++++++++- common/src/app/common/geom/shapes/points.cljc | 6 +- .../src/app/main/ui/workspace/viewport.cljs | 2 +- .../viewport/grid_layout_editor.cljs | 169 +++++++++++------- .../viewport/grid_layout_editor.css.json | 1 + .../viewport/grid_layout_editor.scss | 64 +++++++ 6 files changed, 316 insertions(+), 76 deletions(-) create mode 100644 frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.css.json create mode 100644 frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.scss 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 79de30499e..b89b372b4b 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 @@ -51,6 +51,7 @@ (ns app.common.geom.shapes.grid-layout.layout-data (:require + [app.common.data :as d] [app.common.geom.point :as gpt] [app.common.geom.shapes.points :as gpo] [app.common.types.shape.layout :as ctl])) @@ -134,6 +135,137 @@ (= :auto type) (assoc :size (min (+ size add-size) max-size))))))) +(defn size-to-allocate + [type parent [child-bounds _] cell] + (let [[row-gap column-gap] (ctl/gaps parent) + [sfn gap prop-span] + (if (= type :column) + [gpo/width-points column-gap :column-span ] + [gpo/height-points row-gap :row-span ]) + span (get cell prop-span)] + (- (sfn child-bounds) (* gap (dec span))))) + +(defn allocate-size + [allocations indexed-tracks to-allocate] + (if (empty? indexed-tracks) + allocations + (let [[idx track] (first indexed-tracks) + old-allocated (get allocations idx 0.01) + auto-track? (= :auto (:type track)) + + allocated (if auto-track? + (max old-allocated + (/ to-allocate (count indexed-tracks)) + (:size track)) + (:size track))] + (recur (cond-> allocations auto-track? + (assoc idx allocated)) + (rest indexed-tracks) (- to-allocate allocated))))) + +(defn allocate-flex + [allocations indexed-tracks to-allocate fr-value] + (if (empty? indexed-tracks) + allocations + (let [[idx track] (first indexed-tracks) + old-allocated (get allocations idx 0.01) + + auto-track? (= :auto (:type track)) + flex-track? (= :flex (:type track)) + + fr (if flex-track? (:value track) 0) + + target-allocation (* fr-value fr) + + allocated (if (or auto-track? flex-track?) + (max target-allocation + old-allocated + (:size track)) + (:size track))] + (recur (cond-> allocations (or flex-track? auto-track?) + (assoc idx allocated)) + (rest indexed-tracks) + (- to-allocate allocated) + fr-value)))) + +(defn set-auto-multi-span + [parent track-list children-map shape-cells type] + + (let [[prop prop-span] + (if (= type :column) + [:column :column-span] + [:row :row-span]) + + ;; First calculate allocation without applying so we can modify them on the following tracks + allocate-auto-tracks + (->> shape-cells + (vals) + (filter #(> (get % prop-span) 1)) + (sort-by prop-span -) + (reduce + (fn [alloc cell] + (let [shape-id (first (:shapes cell)) + from-idx (dec (get cell prop)) + to-idx (+ (dec (get cell prop)) (get cell prop-span)) + indexed-tracks (subvec (d/enumerate track-list) from-idx to-idx) + has-flex? (->> indexed-tracks (d/seek #(= :flex (:type (second %)))) some?) + to-allocate (size-to-allocate type parent (get children-map shape-id) cell)] + (cond-> alloc + ;; skip cells with flex tracks + (and (not has-flex?) (some? to-allocate)) + (allocate-size indexed-tracks to-allocate)))) + {})) + + ;; Apply the allocations to the tracks + track-list + (into [] + (map-indexed #(update %2 :size max (get allocate-auto-tracks %1))) + track-list)] + track-list)) + +(defn set-flex-multi-span + [parent track-list children-map shape-cells type] + + (let [[prop prop-span] + (if (= type :column) + [:column :column-span] + [:row :row-span]) + + ;; First calculate allocation without applying so we can modify them on the following tracks + allocate-fr-tracks + (->> shape-cells + (vals) + (filter #(> (get % prop-span) 1)) + (sort-by prop-span -) + (reduce + (fn [alloc cell] + (let [shape-id (first (:shapes cell)) + from-idx (dec (get cell prop)) + to-idx (+ (dec (get cell prop)) (get cell prop-span)) + indexed-tracks (subvec (d/enumerate track-list) from-idx to-idx) + has-flex? (->> indexed-tracks (d/seek #(= :auto (:type (second %)))) some?) + total-frs (->> indexed-tracks (reduce (fn [tot [_ {:keys [type value]}]] + (cond-> tot + (= type :flex) + (+ value))) + 0)) + + + to-allocate (size-to-allocate type parent (get children-map shape-id) cell) + fr-value (when (some? to-allocate) (/ to-allocate total-frs))] + (cond-> alloc + ;; skip cells with no flex tracks + (and has-flex? (some? to-allocate)) + (allocate-flex indexed-tracks to-allocate fr-value)))) + {})) + + ;; Apply the allocations to the tracks + track-list + (into [] + (map-indexed #(update %2 :size max (get allocate-fr-tracks %1))) + track-list)] + track-list)) + + (defn calc-layout-data [parent children transformed-parent-bounds] @@ -155,6 +287,10 @@ (->> (:shapes cell) (map #(vector % cell))))) (:layout-grid-cells parent)) + children-map + (into {} + (map #(vector (:id (second %)) %)) + children) ;; Initialize tracks column-tracks @@ -171,8 +307,8 @@ ;; Adjust multi-spaned cells with no flex columns - ;; TODO - + column-tracks (set-auto-multi-span parent column-tracks children-map shape-cells :column) + row-tracks (set-auto-multi-span parent row-tracks children-map shape-cells :row) ;; Calculate the `fr` unit and adjust the size column-total-size (tracks-total-size column-tracks) @@ -184,6 +320,12 @@ column-frs (tracks-total-frs column-tracks) row-frs (tracks-total-frs row-tracks) + ;; Assign minimum size to the multi-span flex tracks. We do this after calculating + ;; the fr size because will affect only the minimum. The maximum will be set by the + ;; fracion + column-tracks (set-flex-multi-span parent column-tracks children-map shape-cells :column) + row-tracks (set-flex-multi-span parent row-tracks children-map shape-cells :row) + ;; Once auto sizes have been calculated we get calculate the `fr` unit with the remainining size and adjust the size free-column-space (- bound-width (+ column-total-size column-total-gap)) free-row-space (- bound-height (+ row-total-size row-total-gap)) @@ -235,8 +377,7 @@ [(conj tracks (assoc track :start-p start-p)) (gpt/add start-p (vv (+ size row-gap)))]) [[] start-p]) - (first)) - ] + (first))] {:origin start-p :layout-bounds layout-bounds @@ -270,4 +411,3 @@ (gpt/to-vec origin row-start-p)))] (assoc grid-cell :start-p start-p))))) - diff --git a/common/src/app/common/geom/shapes/points.cljc b/common/src/app/common/geom/shapes/points.cljc index 3722beb9e8..ff6bd15df8 100644 --- a/common/src/app/common/geom/shapes/points.cljc +++ b/common/src/app/common/geom/shapes/points.cljc @@ -55,11 +55,13 @@ (defn width-points [[p0 p1 _ _]] - (max 0.01 (gpt/length (gpt/to-vec p0 p1)))) + (when (and (some? p0) (some? p1)) + (max 0.01 (gpt/length (gpt/to-vec p0 p1))))) (defn height-points [[p0 _ _ p3]] - (max 0.01 (gpt/length (gpt/to-vec p0 p3)))) + (when (and (some? p0) (some? p3)) + (max 0.01 (gpt/length (gpt/to-vec p0 p3))))) (defn pad-points [[p0 p1 p2 p3 :as points] pad-top pad-right pad-bottom pad-left] diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 48ebc40b7e..8af5e3702a 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -262,7 +262,7 @@ (hooks/setup-shortcuts node-editing? drawing-path? text-editing?) (hooks/setup-active-frames base-objects hover-ids selected active-frames zoom transform vbox) - [:div.viewport + [:div.viewport {:style #js {"--zoom" zoom}} [:div.viewport-overlays ;; The behaviour inside a foreign object is a bit different that in plain HTML so we wrap ;; inside a foreign object "dummy" so this awkward behaviour is take into account 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 dee2f761ce..f9a6b1313e 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 @@ -5,6 +5,7 @@ ;; Copyright (c) KALEIDOS INC (ns app.main.ui.workspace.viewport.grid-layout-editor + (:require-macros [app.main.style :refer [css]]) (:require [app.common.data :as d] [app.common.data.macros :as dm] @@ -20,6 +21,8 @@ [app.main.store :as st] [app.main.ui.workspace.viewport.viewport-ref :as uwvv] [app.util.dom :as dom] + [app.util.keyboard :as kbd] + [app.util.object :as obj] [cuerdas.core :as str] [rumext.v2 :as mf])) @@ -41,36 +44,38 @@ value (unchecked-get props "value") zoom (unchecked-get props "zoom") + marker-width (/ 24 zoom) + marker-h1 (/ 22 zoom) + marker-h2 (/ 8 zoom) + + marker-half-width (/ marker-width 2) + marker-half-height (/ (+ marker-h1 marker-h2) 2) + marker-points (reduce apply-to-point [(gpt/subtract center - (gpt/point (/ 13 zoom) (/ 16 zoom)))] - [#(gpt/add % (gpt/point (/ 26 zoom) 0)) - #(gpt/add % (gpt/point 0 (/ 24 zoom))) - #(gpt/add % (gpt/point (- (/ 13 zoom)) (/ 8 zoom))) - #(gpt/subtract % (gpt/point (/ 13 zoom) (/ 8 zoom)))]) + (gpt/point marker-half-width marker-half-height))] + [#(gpt/add % (gpt/point marker-width 0)) + #(gpt/add % (gpt/point 0 marker-h1)) + #(gpt/add % (gpt/point (- marker-half-width) marker-h2)) + #(gpt/subtract % (gpt/point marker-half-width marker-h2))]) text-x (:x center) text-y (:y center)] - [:g.grid-track-marker - [:polygon {:points (->> marker-points + [:g {:class (css :grid-track-marker)} + [:polygon {:class (css :marker-shape) + :points (->> marker-points (map #(dm/fmt "%,%" (:x %) (:y %))) - (str/join " ")) - - :style {:fill "var(--color-distance)" - :fill-opacity 0.3}}] - [:text {:x text-x + (str/join " "))}] + [:text {:class (css :marker-text) + :x text-x :y text-y :width (/ 26.26 zoom) :height (/ 32 zoom) - :font-size (/ 16 zoom) - :font-family "worksans" - :font-weight 600 :text-anchor "middle" - :dominant-baseline "middle" - :style {:fill "var(--color-distance)"}} + :dominant-baseline "middle"} (dm/str value)]])) (mf/defc grid-editor-frame @@ -95,11 +100,11 @@ #(gpt/add % (vv (+ height (/ 40 zoom)))) #(gpt/add % (hv (/ 40 zoom)))])] - [:polygon {:points (->> frame-points - (map #(dm/fmt "%,%" (:x %) (:y %))) - (str/join " ")) - :style {:stroke "var(--color-distance)" - :stroke-width (/ 1 zoom)}}])) + [:polygon + {:class (css :grid-frame) + :points (->> frame-points + (map #(dm/fmt "%,%" (:x %) (:y %))) + (str/join " "))}])) (mf/defc plus-btn {::mf/wrap-props false} @@ -127,22 +132,21 @@ (mf/deps on-click) #(when on-click (on-click)))] - [:g.plus-button {:cursor "pointer" - :on-click handle-click} - [:rect {:x rect-x + [:g {:class (css :grid-plus-button) + :on-click handle-click} + + [:rect {:class (css :grid-plus-shape) + :x rect-x :y rect-y :width (/ 40 zoom) - :height (/ 40 zoom) - :style {:fill "var(--color-distance)" - :stroke "var(--color-distance)" - :stroke-width (/ 1 zoom)}}] + :height (/ 40 zoom)}] - [:use {:x icon-x + [:use {:class (css :grid-plus-icon) + :x icon-x :y icon-y :width (/ 16 zoom) :height (/ 16 zoom) - :href (dm/str "#icon-plus") - :fill "white"}]])) + :href (dm/str "#icon-plus")}]])) (defn use-drag [{:keys [on-drag-start on-drag-end on-drag-delta on-drag-position]}] @@ -259,7 +263,6 @@ :height height :width width :style {:fill "transparent" :stroke-width 0 :cursor cursor} - :on-pointer-down handle-pointer-down :on-lost-pointer-capture handle-lost-pointer-capture :on-pointer-move handle-pointer-move}])) @@ -301,21 +304,17 @@ [:g.cell-editor [:rect - {:x (:x start-p) + {:class (dom/classnames (css :grid-cell-outline) true + (css :hover) hover? + (css :selected) selected?) + :x (:x start-p) :y (:y start-p) :width cell-width :height cell-height :on-pointer-enter #(st/emit! (dwge/hover-grid-cell (:id shape) (:id cell) true)) :on-pointer-leave #(st/emit! (dwge/hover-grid-cell (:id shape) (:id cell) false)) - :on-click #(st/emit! (dwge/select-grid-cell (:id shape) (:id cell))) - - :style {:fill "transparent" - :stroke "var(--color-distance)" - :stroke-dasharray (when-not (or hover? selected?) - (str/join " " (map #(/ % zoom) [0 8]) )) - :stroke-linecap "round" - :stroke-width (/ 2 zoom)}}] + :on-click #(st/emit! (dwge/select-grid-cell (:id shape) (:id cell)))}] (when selected? (let [handlers @@ -451,7 +450,44 @@ (mf/use-callback (mf/deps (:id shape)) (fn [] - (st/emit! (st/emit! (dwsl/add-layout-track [(:id shape)] :row ctl/default-track-value)))))] + (st/emit! (st/emit! (dwsl/add-layout-track [(:id shape)] :row ctl/default-track-value))))) + + handle-blur-track-input + (mf/use-callback + (mf/deps (:id shape)) + (fn [track-type index event] + (let [target (-> event dom/get-target) + value (-> target dom/get-input-value str/upper) + value-int (d/parse-integer value) + + [type value] + (cond + (str/ends-with? value "%") + [:percent value-int] + + (str/ends-with? value "FR") + [:flex value-int] + + (some? value-int) + [:fixed value-int] + + (or (= value "AUTO") (= "" value)) + [:auto nil])] + (if (some? type) + (do (obj/set! target "value" (format-size {:type type :value value})) + (dom/set-attribute! target "data-default-value" (format-size {:type type :value value})) + (st/emit! (dwsl/change-layout-track [(:id shape)] track-type index {:type type :value value}))) + (obj/set! target "value" (dom/get-attribute target "data-default-value")))))) + + handle-keydown-track-input + (mf/use-callback + (fn [event] + (let [enter? (kbd/enter? event) + esc? (kbd/esc? event)] + (when enter? + (dom/blur! (dom/get-target event))) + (when esc? + (dom/blur! (dom/get-target event))))))] (mf/use-effect (fn [] @@ -482,23 +518,19 @@ (gpt/subtract (hv (/ layout-gap-col 2))))) text-p (-> start-p - (gpt/subtract (vv (/ 20 zoom))) - (gpt/add (hv (/ (:size column-data) 2))))] + (gpt/subtract (vv (/ 36 zoom))))] [:* {:key (dm/str "column-" idx)} [:& track-marker {:center marker-p :value (dm/str (inc idx)) :zoom zoom}] - - [:text {:x (:x text-p) - :y (:y text-p) - :font-size (/ 14 zoom) - :font-weight 600 - :font-family "worksans" - :dominant-baseline "central" - :text-anchor "middle" - :style {:fill "var(--color-distance)"}} - (format-size column-data)] - + [:foreignObject {:x (:x text-p) :y (:y text-p) :width (max 0 (- (:size column-data) 4)) :height (/ 32 zoom)} + [:input + {:class (css :grid-editor-label) + :type "text" + :default-value (format-size column-data) + :data-default-value (format-size column-data) + :on-key-down handle-keydown-track-input + :on-blur #(handle-blur-track-input :column idx %)}]] (when (not= idx 0) [:& resize-handler {:shape shape :layout-data layout-data @@ -514,24 +546,25 @@ (gpt/subtract (vv (/ layout-gap-row 2))))) text-p (-> start-p - (gpt/subtract (hv (/ 20 zoom))) - (gpt/add (vv (/ (:size row-data) 2))))] + (gpt/subtract (hv (/ (:size row-data) 2))) + (gpt/subtract (hv (/ 16 zoom))) + (gpt/add (vv (/ (:size row-data) 2))) + (gpt/subtract (vv (/ 18 zoom))))] [:* {:key (dm/str "row-" idx)} [:g {:transform (dm/fmt "rotate(-90 % %)" (:x marker-p) (:y marker-p))} [:& track-marker {:center marker-p :value (dm/str (inc idx)) :zoom zoom}]] - [:g {:transform (dm/fmt "rotate(-90 % %)" (:x text-p) (:y text-p))} - [:text {:x (:x text-p) - :y (:y text-p) - :font-size (/ 14 zoom) - :font-weight 600 - :font-family "worksans" - :dominant-baseline "central" - :text-anchor "middle" - :style {:fill "var(--color-distance)"}} - (format-size row-data)]] + [:g {:transform (dm/fmt "rotate(-90 % %)" (+ (:x text-p) (/ (:size row-data) 2)) (+ (:y text-p) (/ 36 zoom 2)))} + [:foreignObject {:x (:x text-p) :y (:y text-p) :width (:size row-data) :height (/ 36 zoom)} + [:input + {:class (css :grid-editor-label) + :type "text" + :default-value (format-size row-data) + :data-default-value (format-size row-data) + :on-key-down handle-keydown-track-input + :on-blur #(handle-blur-track-input :row idx %)}]]] (when (not= idx 0) [:& resize-handler {:shape shape diff --git a/frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.css.json b/frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.css.json new file mode 100644 index 0000000000..2ab84c5e5e --- /dev/null +++ b/frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.css.json @@ -0,0 +1 @@ +{"grid-track-marker":"viewport_grid_layout_editor_grid-track-marker_HABEp","marker-shape":"viewport_grid_layout_editor_marker-shape_FZTUQ","marker-text":"viewport_grid_layout_editor_marker-text_5xM8J","grid-editor-label":"viewport_grid_layout_editor_grid-editor-label_2NbYe","grid-frame":"viewport_grid_layout_editor_grid-frame_CzMnU","grid-plus-button":"viewport_grid_layout_editor_grid-plus-button_brOge","grid-plus-shape":"viewport_grid_layout_editor_grid-plus-shape_jtOU9","grid-plus-icon":"viewport_grid_layout_editor_grid-plus-icon_Zolso","grid-cell-outline":"viewport_grid_layout_editor_grid-cell-outline_1-cRq","hover":"viewport_grid_layout_editor_hover_Rn-tv","selected":"viewport_grid_layout_editor_selected_nhyhL"} \ No newline at end of file diff --git a/frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.scss b/frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.scss new file mode 100644 index 0000000000..8a8a13659f --- /dev/null +++ b/frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.scss @@ -0,0 +1,64 @@ +.grid-track-marker { + .marker-shape { + fill: var(--color-distance); + fill-opacity: 0.3; + } + .marker-text { + fill: var(--color-distance); + font-size: calc(12px / var(--zoom)); + font-family: worksans; + font-weight: 600; + } +} + +.grid-editor-label { + background: none; + width: 100%; + height: 100%; + text-align: center; + font-family: worksans; + color: var(--color-distance); + font-weight: 600; + margin: 0; + padding: 0; + border: 0; + font-size: calc(12px / var(--zoom)); + + &:focus { + outline: none; + border-bottom: calc(1px / var(--zoom)) solid var(--color-distance); + } +} + +.grid-frame { + fill: transparent; + stroke: var(--color-distance); + stroke-width: calc(1 / var(--zoom)); +} + +.grid-plus-button { + cursor: pointer; + + .grid-plus-shape { + fill: var(--color-distance); + stroke: var(--color-distance); + stroke-width: calc(1 / var(--zoom)); + } + + .grid-plus-icon { + fill: white; + } +} + +.grid-cell-outline { + fill: transparent; + stroke: var(--color-distance); + stroke-linecap: round; + stroke-width: calc(2 / var(--zoom)); + stroke-dasharray: 0 calc(8 / var(--zoom)); + + &.hover, + &.selected { + stroke-dasharray: initial; + } +} From 4bd15b5de1783cad1cf256839469b0c856ff445d Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 4 May 2023 17:07:08 +0200 Subject: [PATCH 06/52] :sparkles: Adds child layout options to grid children --- frontend/src/app/main/refs.cljs | 8 ++++---- .../ui/workspace/sidebar/options/shapes/bool.cljs | 8 ++++---- .../workspace/sidebar/options/shapes/circle.cljs | 8 ++++---- .../ui/workspace/sidebar/options/shapes/frame.cljs | 10 +++++----- .../ui/workspace/sidebar/options/shapes/group.cljs | 8 ++++---- .../ui/workspace/sidebar/options/shapes/image.cljs | 8 ++++---- .../workspace/sidebar/options/shapes/multiple.cljs | 14 +++++++------- .../ui/workspace/sidebar/options/shapes/path.cljs | 8 ++++---- .../ui/workspace/sidebar/options/shapes/rect.cljs | 8 ++++---- .../workspace/sidebar/options/shapes/svg_raw.cljs | 8 ++++---- .../ui/workspace/sidebar/options/shapes/text.cljs | 8 ++++---- 11 files changed, 48 insertions(+), 48 deletions(-) diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index bcb78e7a4a..51a90a6386 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -485,22 +485,22 @@ (defn workspace-text-modifier-by-id [id] (l/derived #(get % id) workspace-text-modifier =)) -(defn is-flex-layout-child? +(defn is-layout-child? [ids] (l/derived (fn [objects] (->> ids (map (d/getf objects)) - (some (partial ctl/flex-layout-immediate-child? objects)))) + (some (partial ctl/any-layout-immediate-child? objects)))) workspace-page-objects)) -(defn all-flex-layout-child? +(defn all-layout-child? [ids] (l/derived (fn [objects] (->> ids (map (d/getf objects)) - (every? (partial ctl/flex-layout-immediate-child? objects)))) + (every? (partial ctl/any-layout-immediate-child? objects)))) workspace-page-objects)) (defn get-flex-child-viewer diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs index 458acf5d91..9803d57c67 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs @@ -30,8 +30,8 @@ layout-item-values (select-keys shape layout-item-attrs) layout-container-values (select-keys shape layout-container-flex-attrs) - is-flex-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-flex-layout-child? ids)) - is-flex-layout-child? (mf/deref is-flex-layout-child-ref) + is-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-layout-child? ids)) + is-layout-child? (mf/deref is-layout-child-ref) is-layout-child-absolute? (ctl/layout-absolute? shape)] [:* @@ -41,7 +41,7 @@ :shape shape}] [:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values :multiple false}] - (when is-flex-layout-child? + (when is-layout-child? [:& layout-item-menu {:ids ids :type type @@ -49,7 +49,7 @@ :is-layout-child? true :shape shape}]) - (when (or (not is-flex-layout-child?) is-layout-child-absolute?) + (when (or (not is-layout-child?) is-layout-child-absolute?) [:& constraints-menu {:ids ids :values constraint-values}]) [:& layer-menu {:ids ids diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs index 78b938ca72..0820c9be81 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs @@ -32,8 +32,8 @@ layout-item-values (select-keys shape layout-item-attrs) layout-container-values (select-keys shape layout-container-flex-attrs) - is-flex-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-flex-layout-child? ids)) - is-flex-layout-child? (mf/deref is-flex-layout-child-ref) + is-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-layout-child? ids)) + is-layout-child? (mf/deref is-layout-child-ref) is-layout-child-absolute? (ctl/layout-absolute? shape)] [:* [:& measures-menu {:ids ids @@ -42,14 +42,14 @@ :shape shape}] [:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values :multiple false}] - (when is-flex-layout-child? + (when is-layout-child? [:& layout-item-menu {:ids ids :type type :values layout-item-values :is-layout-child? true :is-layout-container? false :shape shape}]) - (when (or (not is-flex-layout-child?) is-layout-child-absolute?) + (when (or (not is-layout-child?) is-layout-child-absolute?) [:& constraints-menu {:ids ids :values constraint-values}]) [:& layer-menu {:ids ids diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs index f92c7f542a..21a9ae46cb 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs @@ -36,8 +36,8 @@ layout-item-values (select-keys shape layout-item-attrs) [comp-ids comp-values] [[(:id shape)] (select-keys shape component-attrs)] - is-flex-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-flex-layout-child? ids)) - is-flex-layout-child? (mf/deref is-flex-layout-child-ref) + is-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-layout-child? ids)) + is-layout-child? (mf/deref is-layout-child-ref) is-flex-layout-container? (ctl/flex-layout? shape) is-layout-child-absolute? (ctl/layout-absolute? shape)] [:* @@ -48,17 +48,17 @@ [:& component-menu {:ids comp-ids :values comp-values :shape shape}] - (when (or (not is-flex-layout-child?) is-layout-child-absolute?) + (when (or (not is-layout-child?) is-layout-child-absolute?) [:& constraints-menu {:ids ids :values constraint-values}]) [:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values :multiple false}] - (when (or is-flex-layout-child? is-flex-layout-container?) + (when (or is-layout-child? is-flex-layout-container?) [:& layout-item-menu {:ids ids :type type :values layout-item-values - :is-layout-child? is-flex-layout-child? + :is-layout-child? is-layout-child? :is-layout-container? is-flex-layout-container? :shape shape}]) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs index b840728358..842f554811 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs @@ -36,8 +36,8 @@ file-id (unchecked-get props "file-id") layout-container-values (select-keys shape layout-container-flex-attrs) ids [(:id shape)] - is-flex-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-flex-layout-child? ids)) - is-flex-layout-child? (mf/deref is-flex-layout-child-ref) + is-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-layout-child? ids)) + is-layout-child? (mf/deref is-layout-child-ref) is-layout-child-absolute? (ctl/layout-absolute? shape) type :group @@ -58,7 +58,7 @@ [:& component-menu {:ids comp-ids :values comp-values :shape shape}] ;;remove this in components-v2 [:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values :multiple false}] - (when is-flex-layout-child? + (when is-layout-child? [:& layout-item-menu {:type type :ids layout-item-ids @@ -66,7 +66,7 @@ :is-layout-container? false :values layout-item-values}]) - (when (or (not is-flex-layout-child?) is-layout-child-absolute?) + (when (or (not is-layout-child?) is-layout-child-absolute?) [:& constraints-menu {:ids constraint-ids :values constraint-values}]) [:& layer-menu {:type type :ids layer-ids :values layer-values}] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/image.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/image.cljs index 7d93273044..0f3bf75fb1 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/image.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/image.cljs @@ -32,8 +32,8 @@ layout-item-values (select-keys shape layout-item-attrs) layout-container-values (select-keys shape layout-container-flex-attrs) - is-flex-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-flex-layout-child? ids)) - is-flex-layout-child? (mf/deref is-flex-layout-child-ref) + is-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-layout-child? ids)) + is-layout-child? (mf/deref is-layout-child-ref) is-layout-child-absolute? (ctl/layout-absolute? shape)] [:* [:& measures-menu {:ids ids @@ -42,7 +42,7 @@ :shape shape}] [:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values :multiple false}] - (when is-flex-layout-child? + (when is-layout-child? [:& layout-item-menu {:ids ids :type type @@ -50,7 +50,7 @@ :is-layout-child? true :shape shape}]) - (when (or (not is-flex-layout-child?) is-layout-child-absolute?) + (when (or (not is-layout-child?) is-layout-child-absolute?) [:& constraints-menu {:ids ids :values constraint-values}]) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs index bdef18f32c..179474d1a6 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs @@ -294,15 +294,15 @@ all-types (into #{} (map :type shapes)) ids (->> shapes (map :id)) - is-flex-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-flex-layout-child? ids)) - is-flex-layout-child? (mf/deref is-flex-layout-child-ref) + is-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-layout-child? ids)) + is-layout-child? (mf/deref is-layout-child-ref) has-text? (contains? all-types :text) has-flex-layout-container? (->> shapes (some ctl/flex-layout?)) - all-flex-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/all-flex-layout-child? ids)) - all-flex-layout-child? (mf/deref all-flex-layout-child-ref) + all-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/all-layout-child? ids)) + all-layout-child? (mf/deref all-layout-child-ref) all-flex-layout-container? (->> shapes (every? ctl/flex-layout?)) @@ -342,15 +342,15 @@ [:& layout-container-menu {:type type :ids layout-container-ids :values layout-container-values :multiple true}] - (when (or is-flex-layout-child? has-flex-layout-container?) + (when (or is-layout-child? has-flex-layout-container?) [:& layout-item-menu {:type type :ids layout-item-ids - :is-layout-child? all-flex-layout-child? + :is-layout-child? all-layout-child? :is-layout-container? all-flex-layout-container? :values layout-item-values}]) - (when-not (or (empty? constraint-ids) is-flex-layout-child?) + (when-not (or (empty? constraint-ids) is-layout-child?) [:& constraints-menu {:ids constraint-ids :values constraint-values}]) (when-not (empty? layer-ids) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs index 5a7b1bd953..28494c225e 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs @@ -32,8 +32,8 @@ layout-item-values (select-keys shape layout-item-attrs) layout-container-values (select-keys shape layout-container-flex-attrs) - is-flex-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-flex-layout-child? ids)) - is-flex-layout-child? (mf/deref is-flex-layout-child-ref) + is-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-layout-child? ids)) + is-layout-child? (mf/deref is-layout-child-ref) is-layout-child-absolute? (ctl/layout-absolute? shape)] [:* [:& measures-menu {:ids ids @@ -42,14 +42,14 @@ :shape shape}] [:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values :multiple false}] - (when is-flex-layout-child? + (when is-layout-child? [:& layout-item-menu {:ids ids :type type :values layout-item-values :is-layout-child? true :is-layout-container? false :shape shape}]) - (when (or (not is-flex-layout-child?) is-layout-child-absolute?) + (when (or (not is-layout-child?) is-layout-child-absolute?) [:& constraints-menu {:ids ids :values constraint-values}]) [:& layer-menu {:ids ids diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs index 401ef70ef4..5ee633fcf0 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs @@ -32,8 +32,8 @@ stroke-values (select-keys shape stroke-attrs) layout-item-values (select-keys shape layout-item-attrs) layout-container-values (select-keys shape layout-container-flex-attrs) - is-flex-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-flex-layout-child? ids)) - is-flex-layout-child? (mf/deref is-flex-layout-child-ref) + is-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-layout-child? ids)) + is-layout-child? (mf/deref is-layout-child-ref) is-layout-child-absolute? (ctl/layout-absolute? shape)] [:* [:& measures-menu {:ids ids @@ -41,7 +41,7 @@ :values measure-values :shape shape}] [:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values :multiple false}] - (when is-flex-layout-child? + (when is-layout-child? [:& layout-item-menu {:ids ids :type type @@ -49,7 +49,7 @@ :is-layout-child? true :shape shape}]) - (when (or (not is-flex-layout-child?) is-layout-child-absolute?) + (when (or (not is-layout-child?) is-layout-child-absolute?) [:& constraints-menu {:ids ids :values constraint-values}]) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs index 0560de5494..a8ec66fb9e 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs @@ -106,8 +106,8 @@ layout-item-values (select-keys shape layout-item-attrs) layout-container-values (select-keys shape layout-container-flex-attrs) - is-flex-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-flex-layout-child? ids)) - is-flex-layout-child? (mf/deref is-flex-layout-child-ref) + is-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-layout-child? ids)) + is-layout-child? (mf/deref is-layout-child-ref) is-layout-child-absolute? (ctl/layout-absolute? shape)] (when (contains? svg-elements tag) @@ -118,7 +118,7 @@ :shape shape}] [:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values :multiple false}] - (when is-flex-layout-child? + (when is-layout-child? [:& layout-item-menu {:ids ids :type type @@ -126,7 +126,7 @@ :is-layout-child? true :shape shape}]) - (when (or (not is-flex-layout-child?) is-layout-child-absolute?) + (when (or (not is-layout-child?) is-layout-child-absolute?) [:& constraints-menu {:ids ids :values constraint-values}]) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs index 8c93c8f9e1..6bfcb5dda5 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs @@ -28,8 +28,8 @@ (let [ids [(:id shape)] type (:type shape) - is-flex-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-flex-layout-child? ids)) - is-flex-layout-child? (mf/deref is-flex-layout-child-ref) + is-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-layout-child? ids)) + is-layout-child? (mf/deref is-layout-child-ref) layout-container-values (select-keys shape layout-container-flex-attrs) is-layout-child-absolute? (ctl/layout-absolute? shape) state-map (mf/deref refs/workspace-editor-state) @@ -76,7 +76,7 @@ :shape shape}] [:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values :multiple false}] - (when is-flex-layout-child? + (when is-layout-child? [:& layout-item-menu {:ids ids :type type @@ -84,7 +84,7 @@ :is-layout-child? true :shape shape}]) - (when (or (not is-flex-layout-child?) is-layout-child-absolute?) + (when (or (not is-layout-child?) is-layout-child-absolute?) [:& constraints-menu {:ids ids :values (select-keys shape constraint-attrs)}]) From 0195165de0f2618196615a6d7dd4078640cb34d1 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 4 May 2023 17:07:55 +0200 Subject: [PATCH 07/52] :sparkles: Resize tracks from editor --- .../viewport/grid_layout_editor.cljs | 358 +++++++++++------- 1 file changed, 222 insertions(+), 136 deletions(-) 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 f9a6b1313e..185af68f90 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 @@ -12,6 +12,7 @@ [app.common.geom.point :as gpt] [app.common.geom.shapes.grid-layout :as gsg] [app.common.geom.shapes.points :as gpo] + [app.common.math :as mth] [app.common.types.modifiers :as ctm] [app.common.types.shape.layout :as ctl] [app.main.data.workspace.grid-layout.editor :as dwge] @@ -19,6 +20,7 @@ [app.main.data.workspace.shape-layout :as dwsl] [app.main.refs :as refs] [app.main.store :as st] + [app.main.ui.formats :as fmt] [app.main.ui.workspace.viewport.viewport-ref :as uwvv] [app.util.dom :as dom] [app.util.keyboard :as kbd] @@ -31,9 +33,9 @@ (defn format-size [{:keys [type value]}] (case type - :fixed (str value "PX") - :percent (str value "%") - :flex (str value "FR") + :fixed (dm/str (fmt/format-number value) "PX") + :percent (dm/str (fmt/format-number value) "%") + :flex (dm/str (fmt/format-number value) "FR") :auto "AUTO")) (mf/defc track-marker @@ -255,7 +257,6 @@ {:keys [handle-pointer-down handle-lost-pointer-capture handle-pointer-move]} (use-drag {:on-drag-position handle-drag-position - ;;:on-drag-start handle-drag-start :on-drag-end handle-drag-end})] [:rect {:x x @@ -336,47 +337,81 @@ :direction dir :layout-data layout-data}])]))])) -(mf/defc resize-handler +(mf/defc resize-track-handler {::mf/wrap-props false} [props] (let [shape (unchecked-get props "shape") + index (unchecked-get props "index") + track-before (unchecked-get props "track-before") + track-after (unchecked-get props "track-after") + {:keys [column-total-size column-total-gap row-total-size row-total-gap]} (unchecked-get props "layout-data") start-p (unchecked-get props "start-p") type (unchecked-get props "type") zoom (unchecked-get props "zoom") - dragging-ref (mf/use-ref false) - start-ref (mf/use-ref nil) - [layout-gap-row layout-gap-col] (ctl/gaps shape) - on-pointer-down + start-size-before (mf/use-var nil) + start-size-after (mf/use-var nil) + + snap-pixel? (mf/deref refs/snap-pixel?) + + handle-drag-start (mf/use-callback - (fn [event] - (dom/capture-pointer event) - (mf/set-ref-val! dragging-ref true) - (mf/set-ref-val! start-ref (dom/get-client-position event)))) + (mf/deps shape track-before track-after) + (fn [] + (reset! start-size-before (:size track-before)) + (reset! start-size-after (:size track-after)) + (let [tracks-prop + (if (= :column type) :layout-grid-columns :layout-grid-rows) - on-lost-pointer-capture + shape + (-> shape + (cond-> (some? track-before) + (update-in [tracks-prop (dec index)] merge {:type :fixed :value (:size track-before)})) + (cond-> (some? track-after) + (update-in [tracks-prop index] merge {:type :fixed :value (:size track-after)}))) + + modifiers + (-> (ctm/empty) + (ctm/change-property tracks-prop (get shape tracks-prop)))] + (st/emit! (dwm/set-modifiers (dwm/create-modif-tree [(:id shape)] modifiers)))))) + + handle-drag-position (mf/use-callback - (fn [event] - (dom/release-pointer event) - (mf/set-ref-val! dragging-ref false) - (mf/set-ref-val! start-ref nil))) + (mf/deps shape track-before track-after) + (fn [position] + (let [[tracks-prop axis] + (if (= :column type) [:layout-grid-columns :x] [:layout-grid-rows :y]) - on-pointer-move + precision (if snap-pixel? mth/round identity) + delta (get position axis) + shape + (-> shape + (cond-> (some? track-before) + (update-in [tracks-prop (dec index)] merge {:type :fixed :value (precision (+ @start-size-before delta))})) + (cond-> (some? track-after) + (update-in [tracks-prop index] merge {:type :fixed :value (precision (- @start-size-after delta))}))) + + modifiers + (-> (ctm/empty) + (ctm/change-property tracks-prop (get shape tracks-prop)))] + (st/emit! (dwm/set-modifiers (dwm/create-modif-tree [(:id shape)] modifiers)))))) + + handle-drag-end (mf/use-callback - (fn [event] - (when (mf/ref-val dragging-ref) - (let [start (mf/ref-val start-ref) - pos (dom/get-client-position event) - _delta (-> (gpt/to-vec start pos) - (get (if (= type :column) :x :y)))] - - ;; TODO Implement resize - #_(prn ">Delta" delta))))) + (mf/deps track-before track-after) + (fn [] + (reset! start-size-before nil) + (reset! start-size-after nil) + (st/emit! (dwm/apply-modifiers)))) + {:keys [handle-pointer-down handle-lost-pointer-capture handle-pointer-move]} + (use-drag {:on-drag-start handle-drag-start + :on-drag-delta handle-drag-position + :on-drag-end handle-drag-end}) [x y width height] (if (= type :column) @@ -390,7 +425,7 @@ (+ column-total-size column-total-gap (/ 40 zoom)) (max layout-gap-row (/ 16 zoom))])] - [:rect.resize-handler + [:rect.resize-track-handler {:x x :y y :class (if (= type :column) @@ -398,64 +433,54 @@ "resize-ns-0") :height height :width width - :on-pointer-down on-pointer-down - :on-lost-pointer-capture on-lost-pointer-capture - :on-pointer-move on-pointer-move + :on-pointer-down handle-pointer-down + :on-lost-pointer-capture handle-lost-pointer-capture + :on-pointer-move handle-pointer-move :style {:fill "transparent"}}])) -(mf/defc editor +(mf/defc track {::mf/wrap [mf/memo] ::mf/wrap-props false} [props] + (let [shape (unchecked-get props "shape") + zoom (unchecked-get props "zoom") + type (unchecked-get props "type") + index (unchecked-get props "index") + track-data (unchecked-get props "track-data") + layout-data (unchecked-get props "layout-data") - (let [shape (unchecked-get props "shape") - objects (unchecked-get props "objects") - zoom (unchecked-get props "zoom") - view-only (unchecked-get props "view-only") - bounds (:points shape) - - ;; We need to know the state unmodified so we can create the modifiers - shape-ref (mf/use-memo (mf/deps (:id shape)) #(refs/object-by-id (:id shape))) - base-shape (mf/deref shape-ref) - - grid-edition-id-ref (mf/use-memo #(refs/workspace-grid-edition-id (:id shape))) - grid-edition (mf/deref grid-edition-id-ref) - - hover-cells (:hover grid-edition) - selected-cells (:selected grid-edition) - - children (->> (:shapes shape) - (map (d/getf objects)) - (remove :hidden) - (map #(vector (gpo/parent-coords-bounds (:points %) (:points shape)) %))) - - hv #(gpo/start-hv bounds %) - vv #(gpo/start-vv bounds %) - width (gpo/width-points bounds) - height (gpo/height-points bounds) - origin (gpo/origin bounds) - + track-input-ref (mf/use-ref) [layout-gap-row layout-gap-col] (ctl/gaps shape) + bounds (:points shape) + vv #(gpo/start-vv bounds %) + hv #(gpo/start-hv bounds %) - {:keys [row-tracks column-tracks] :as layout-data} - (gsg/calc-layout-data shape children bounds) + start-p (:start-p track-data) + marker-p + (if (= type :column) + (-> start-p + (gpt/subtract (vv (/ 20 zoom))) + (cond-> (not= index 0) + (gpt/subtract (hv (/ layout-gap-col 2))))) + (-> start-p + (gpt/subtract (hv (/ 20 zoom))) + (cond-> (not= index 0) + (gpt/subtract (vv (/ layout-gap-row 2)))))) - handle-add-column - (mf/use-callback - (mf/deps (:id shape)) - (fn [] - (st/emit! (st/emit! (dwsl/add-layout-track [(:id shape)] :column ctl/default-track-value))))) - - handle-add-row - (mf/use-callback - (mf/deps (:id shape)) - (fn [] - (st/emit! (st/emit! (dwsl/add-layout-track [(:id shape)] :row ctl/default-track-value))))) + text-p + (if (= type :column) + (-> start-p + (gpt/subtract (vv (/ 36 zoom)))) + (-> start-p + (gpt/subtract (hv (/ (:size track-data) 2))) + (gpt/subtract (hv (/ 16 zoom))) + (gpt/add (vv (/ (:size track-data) 2))) + (gpt/subtract (vv (/ 18 zoom))))) handle-blur-track-input (mf/use-callback (mf/deps (:id shape)) - (fn [track-type index event] + (fn [event] (let [target (-> event dom/get-target) value (-> target dom/get-input-value str/upper) value-int (d/parse-integer value) @@ -474,9 +499,9 @@ (or (= value "AUTO") (= "" value)) [:auto nil])] (if (some? type) - (do (obj/set! target "value" (format-size {:type type :value value})) - (dom/set-attribute! target "data-default-value" (format-size {:type type :value value})) - (st/emit! (dwsl/change-layout-track [(:id shape)] track-type index {:type type :value value}))) + (do (dom/set-value! target (format-size {:type type :value value})) + (dom/set-data! target "default-value" (format-size {:type type :value value})) + (st/emit! (dwsl/change-layout-track [(:id shape)] type index {:type type :value value}))) (obj/set! target "value" (dom/get-attribute target "data-default-value")))))) handle-keydown-track-input @@ -487,7 +512,92 @@ (when enter? (dom/blur! (dom/get-target event))) (when esc? - (dom/blur! (dom/get-target event))))))] + (dom/blur! (dom/get-target event)))))) + + track-list-prop (if (= type :column) :column-tracks :row-tracks) + [text-x text-y text-width text-height] + (if (= type :column) + [(:x text-p) (:y text-p) (max 0 (- (:size track-data) 4)) (/ 32 zoom)] + [(:x text-p) (:y text-p) (:size track-data) (/ 36 zoom)])] + + (mf/use-effect + (mf/deps track-data) + (fn [] + (dom/set-value! (mf/ref-val track-input-ref) (format-size track-data)))) + + [:g.track + [:g {:transform (when (= type :row) (dm/fmt "rotate(-90 % %)" (:x marker-p) (:y marker-p)))} + [:& track-marker {:center marker-p + :value (dm/str (inc index)) + :zoom zoom}]] + [:g {:transform (when (= type :row) (dm/fmt "rotate(-90 % %)" (+ (:x text-p) (/ (:size track-data) 2)) (+ (:y text-p) (/ 36 zoom 2))))} + [:foreignObject {:x text-x :y text-y :width text-width :height text-height} + [:input + {:ref track-input-ref + :class (css :grid-editor-label) + :type "text" + :default-value (format-size track-data) + :data-default-value (format-size track-data) + :on-key-down handle-keydown-track-input + :on-blur handle-blur-track-input}]]] + + (let [track-before (get-in layout-data [track-list-prop (dec index)])] + [:& resize-track-handler + {:index index + :shape shape + :layout-data layout-data + :start-p start-p + :type type + :track-before track-before + :track-after track-data + :zoom zoom}])])) + +(mf/defc editor + {::mf/wrap [mf/memo] + ::mf/wrap-props false} + [props] + + (let [shape (unchecked-get props "shape") + objects (unchecked-get props "objects") + zoom (unchecked-get props "zoom") + view-only (unchecked-get props "view-only") + + ;; We need to know the state unmodified so we can create the modifiers + shape-ref (mf/use-memo (mf/deps (:id shape)) #(refs/object-by-id (:id shape))) + base-shape (mf/deref shape-ref) + + grid-edition-id-ref (mf/use-memo #(refs/workspace-grid-edition-id (:id shape))) + grid-edition (mf/deref grid-edition-id-ref) + + hover-cells (:hover grid-edition) + selected-cells (:selected grid-edition) + + children (->> (:shapes shape) + (map (d/getf objects)) + (remove :hidden) + (map #(vector (gpo/parent-coords-bounds (:points %) (:points shape)) %))) + + bounds (:points shape) + hv #(gpo/start-hv bounds %) + vv #(gpo/start-vv bounds %) + width (gpo/width-points bounds) + height (gpo/height-points bounds) + origin (gpo/origin bounds) + + {:keys [row-tracks column-tracks] :as layout-data} + (gsg/calc-layout-data shape children bounds) + + handle-add-column + (mf/use-callback + (mf/deps (:id shape)) + (fn [] + (st/emit! (st/emit! (dwsl/add-layout-track [(:id shape)] :column ctl/default-track-value))))) + + handle-add-row + (mf/use-callback + (mf/deps (:id shape)) + (fn [] + (st/emit! (st/emit! (dwsl/add-layout-track [(:id shape)] :row ctl/default-track-value)))))] (mf/use-effect (fn [] @@ -511,67 +621,43 @@ :on-click handle-add-row}])]) (for [[idx column-data] (d/enumerate column-tracks)] - (let [start-p (:start-p column-data) - marker-p (-> start-p - (gpt/subtract (vv (/ 20 zoom))) - (cond-> (not= idx 0) - (gpt/subtract (hv (/ layout-gap-col 2))))) + [:& track {:key (dm/str "column-track-" idx) + :shape shape + :zoom zoom + :type :column + :index idx + :layout-data layout-data + :track-data column-data}]) - text-p (-> start-p - (gpt/subtract (vv (/ 36 zoom))))] - [:* {:key (dm/str "column-" idx)} - [:& track-marker {:center marker-p - :value (dm/str (inc idx)) - :zoom zoom}] - [:foreignObject {:x (:x text-p) :y (:y text-p) :width (max 0 (- (:size column-data) 4)) :height (/ 32 zoom)} - [:input - {:class (css :grid-editor-label) - :type "text" - :default-value (format-size column-data) - :data-default-value (format-size column-data) - :on-key-down handle-keydown-track-input - :on-blur #(handle-blur-track-input :column idx %)}]] - (when (not= idx 0) - [:& resize-handler {:shape shape - :layout-data layout-data - :start-p start-p - :type :column - :zoom zoom}])])) + ;; Last track resize handler + (let [last-track (last column-tracks) + start-p (:start-p (last column-tracks)) + marker-p (-> start-p + (gpt/subtract (vv (/ 20 zoom))) + (gpt/add (hv (:size last-track))))] + [:g.track + [:& track-marker {:center marker-p + :value (dm/str (inc (count column-tracks))) + :zoom zoom}] + [:& resize-track-handler + {:index (count column-tracks) + :shape shape + :layout-data layout-data + :start-p (-> start-p + (gpt/add (hv (:size last-track))) + (gpt/add (hv (/ 20 zoom)))) + :type :column + :track-before (last column-tracks) + :zoom zoom}]]) (for [[idx row-data] (d/enumerate row-tracks)] - (let [start-p (:start-p row-data) - marker-p (-> start-p - (gpt/subtract (hv (/ 20 zoom))) - (cond-> (not= idx 0) - (gpt/subtract (vv (/ layout-gap-row 2))))) - - text-p (-> start-p - (gpt/subtract (hv (/ (:size row-data) 2))) - (gpt/subtract (hv (/ 16 zoom))) - (gpt/add (vv (/ (:size row-data) 2))) - (gpt/subtract (vv (/ 18 zoom))))] - [:* {:key (dm/str "row-" idx)} - [:g {:transform (dm/fmt "rotate(-90 % %)" (:x marker-p) (:y marker-p))} - [:& track-marker {:center marker-p - :value (dm/str (inc idx)) - :zoom zoom}]] - - [:g {:transform (dm/fmt "rotate(-90 % %)" (+ (:x text-p) (/ (:size row-data) 2)) (+ (:y text-p) (/ 36 zoom 2)))} - [:foreignObject {:x (:x text-p) :y (:y text-p) :width (:size row-data) :height (/ 36 zoom)} - [:input - {:class (css :grid-editor-label) - :type "text" - :default-value (format-size row-data) - :data-default-value (format-size row-data) - :on-key-down handle-keydown-track-input - :on-blur #(handle-blur-track-input :row idx %)}]]] - - (when (not= idx 0) - [:& resize-handler {:shape shape - :layout-data layout-data - :start-p start-p - :type :column - :zoom zoom}])])) + [:& track {:key (dm/str "row-track-" idx) + :shape shape + :zoom zoom + :type :row + :index idx + :layout-data layout-data + :track-data row-data}]) (for [[_ cell] (:layout-grid-cells shape)] [:& grid-cell {:key (dm/str "cell-" (:id cell)) From 3c8934e847efcc0ffa9bb5a355c3c5b805fe2aec Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 5 May 2023 12:25:59 +0200 Subject: [PATCH 08/52] :sparkles: Fill size for grid children --- .../geom/shapes/flex_layout/modifiers.cljc | 23 +++-- .../app/common/geom/shapes/grid_layout.cljc | 1 + .../geom/shapes/grid_layout/positions.cljc | 97 ++++++++++++++++++- .../src/app/common/geom/shapes/modifiers.cljc | 8 +- .../viewport/grid_layout_editor.cljs | 87 +++++++++-------- 5 files changed, 160 insertions(+), 56 deletions(-) diff --git a/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc b/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc index 9247f20bfe..7f0d5ee5b4 100644 --- a/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc @@ -63,8 +63,7 @@ {:height target-height :modifiers (ctm/resize-modifiers (gpt/point 1 fill-scale) child-origin transform transform-inverse)}))) -(defn layout-child-modifiers - "Calculates the modifiers for the layout" +(defn fill-modifiers [parent parent-bounds child child-bounds layout-line] (let [child-origin (gpo/origin child-bounds) child-width (gpo/width-points child-bounds) @@ -83,15 +82,27 @@ (calc-fill-height-data parent transform transform-inverse child child-origin child-height layout-line)) child-width (or (:width fill-width) child-width) - child-height (or (:height fill-height) child-height) + child-height (or (:height fill-height) child-height)] + + [child-width + child-height + (-> (ctm/empty) + (cond-> fill-width (ctm/add-modifiers (:modifiers fill-width))) + (cond-> fill-height (ctm/add-modifiers (:modifiers fill-height))))])) + +(defn layout-child-modifiers + "Calculates the modifiers for the layout" + [parent parent-bounds child child-bounds layout-line] + (let [child-origin (gpo/origin child-bounds) + + [child-width child-height fill-modifiers] + (fill-modifiers parent parent-bounds child child-bounds layout-line) [corner-p layout-line] (fpo/get-child-position parent child child-width child-height layout-line) - move-vec (gpt/to-vec child-origin corner-p) modifiers (-> (ctm/empty) - (cond-> fill-width (ctm/add-modifiers (:modifiers fill-width))) - (cond-> fill-height (ctm/add-modifiers (:modifiers fill-height))) + (ctm/add-modifiers fill-modifiers) (ctm/move move-vec))] [modifiers layout-line])) diff --git a/common/src/app/common/geom/shapes/grid_layout.cljc b/common/src/app/common/geom/shapes/grid_layout.cljc index cb28f3ab7b..8483b8dde9 100644 --- a/common/src/app/common/geom/shapes/grid_layout.cljc +++ b/common/src/app/common/geom/shapes/grid_layout.cljc @@ -15,3 +15,4 @@ (dm/export glp/child-modifiers) (dm/export glp/get-position-grid-coord) (dm/export glp/get-drop-cell) +(dm/export glp/cell-bounds) diff --git a/common/src/app/common/geom/shapes/grid_layout/positions.cljc b/common/src/app/common/geom/shapes/grid_layout/positions.cljc index 3dcdeba332..30d5de604a 100644 --- a/common/src/app/common/geom/shapes/grid_layout/positions.cljc +++ b/common/src/app/common/geom/shapes/grid_layout/positions.cljc @@ -7,19 +7,108 @@ (ns app.common.geom.shapes.grid-layout.positions (:require [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes.common :as gco] [app.common.geom.shapes.grid-layout.layout-data :as ld] [app.common.geom.shapes.points :as gpo] + [app.common.geom.shapes.transforms :as gtr] [app.common.math :as mth] [app.common.pages.helpers :as cph] - [app.common.types.modifiers :as ctm])) + [app.common.types.modifiers :as ctm] + [app.common.types.shape.layout :as ctl])) + +(defn cell-bounds + [{:keys [origin row-tracks column-tracks layout-bounds column-gap row-gap] :as layout-data} {:keys [row column row-span column-span] :as cell}] + + (let [hv #(gpo/start-hv layout-bounds %) + vv #(gpo/start-vv layout-bounds %) + + span-column-tracks (subvec column-tracks (dec column) (+ (dec column) column-span)) + span-row-tracks (subvec row-tracks (dec row) (+ (dec row) row-span)) + + p1 + (gpt/add + origin + (gpt/add + (gpt/to-vec origin (dm/get-in span-column-tracks [0 :start-p])) + (gpt/to-vec origin (dm/get-in span-row-tracks [0 :start-p])))) + + p2 + (as-> p1 $ + (reduce (fn [p track] (gpt/add p (hv (:size track)))) $ span-column-tracks) + (gpt/add $ (hv (* column-gap (dec (count span-column-tracks)))))) + + p3 + (as-> p2 $ + (reduce (fn [p track] (gpt/add p (vv (:size track)))) $ span-row-tracks) + (gpt/add $ (vv (* row-gap (dec (count span-row-tracks)))))) + + p4 + (as-> p1 $ + (reduce (fn [p track] (gpt/add p (vv (:size track)))) $ span-row-tracks) + (gpt/add $ (vv (* row-gap (dec (count span-row-tracks))))))] + [p1 p2 p3 p4])) + +(defn calc-fill-width-data + "Calculates the size and modifiers for the width of an auto-fill child" + [_parent + transform + transform-inverse + _child + child-origin child-width + cell-bounds] + + (let [target-width (max (gpo/width-points cell-bounds) 0.01) + fill-scale (/ target-width child-width)] + {:width target-width + :modifiers (ctm/resize-modifiers (gpt/point fill-scale 1) child-origin transform transform-inverse)})) + +(defn calc-fill-height-data + "Calculates the size and modifiers for the height of an auto-fill child" + [_parent + transform transform-inverse + _child + child-origin child-height + cell-bounds] + (let [target-height (max (gpo/height-points cell-bounds) 0.01) + fill-scale (/ target-height child-height)] + {:height target-height + :modifiers (ctm/resize-modifiers (gpt/point 1 fill-scale) child-origin transform transform-inverse)})) + +(defn fill-modifiers + [parent parent-bounds child child-bounds layout-data cell-data] + (let [child-origin (gpo/origin child-bounds) + child-width (gpo/width-points child-bounds) + child-height (gpo/height-points child-bounds) + + cell-bounds (cell-bounds layout-data cell-data) + + [_ transform transform-inverse] + (when (or (ctl/fill-width? child) (ctl/fill-height? child)) + (gtr/calculate-geometry @parent-bounds)) + + fill-width + (when (ctl/fill-width? child) + (calc-fill-width-data parent transform transform-inverse child child-origin child-width cell-bounds)) + + fill-height + (when (ctl/fill-height? child) + (calc-fill-height-data parent transform transform-inverse child child-origin child-height cell-bounds))] + + (-> (ctm/empty) + (cond-> fill-width (ctm/add-modifiers (:modifiers fill-width))) + (cond-> fill-height (ctm/add-modifiers (:modifiers fill-height)))))) (defn child-modifiers - [_parent _transformed-parent-bounds _child child-bounds cell-data] - (ctm/move-modifiers - (gpt/subtract (:start-p cell-data) (gpo/origin child-bounds)))) + [parent parent-bounds child child-bounds layout-data cell-data] + + (let [fill-modifiers (fill-modifiers parent parent-bounds child child-bounds layout-data cell-data) + position-delta (gpt/subtract (:start-p cell-data) (gpo/origin child-bounds))] + (-> (ctm/empty) + (ctm/add-modifiers fill-modifiers) + (ctm/move position-delta)))) (defn line-value diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index 37dd7019a9..9d3df9ed40 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -205,8 +205,10 @@ (gpo/parent-coords-bounds @transformed-parent-bounds)) child]) - (set-child-modifiers [modif-tree cell-data [child-bounds child]] - (let [modifiers (gcgl/child-modifiers parent transformed-parent-bounds child child-bounds cell-data) + (set-child-modifiers [modif-tree grid-data cell-data [child-bounds child]] + (let [modifiers + (gcgl/child-modifiers parent transformed-parent-bounds child child-bounds grid-data cell-data) + modif-tree (cond-> modif-tree (d/not-empty? modifiers) @@ -224,7 +226,7 @@ (let [cell-data (gcgl/get-cell-data grid-data @transformed-parent-bounds bound+child) modif-tree (cond-> modif-tree (some? cell-data) - (set-child-modifiers cell-data bound+child))] + (set-child-modifiers grid-data cell-data bound+child))] (recur modif-tree (first pending) (rest pending))) modif-tree))))) 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 185af68f90..b446f1b7b2 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 @@ -271,45 +271,26 @@ (mf/defc grid-cell {::mf/wrap-props false} [props] - (let [shape (unchecked-get props "shape") + (let [shape (unchecked-get props "shape") + cell (unchecked-get props "cell") + layout-data (unchecked-get props "layout-data") + zoom (unchecked-get props "zoom") + hover? (unchecked-get props "hover?") + selected? (unchecked-get props "selected?") - {:keys [row column row-span column-span] :as cell} (unchecked-get props "cell") - {:keys [origin row-tracks column-tracks layout-bounds column-gap row-gap] :as layout-data} - (unchecked-get props "layout-data") - zoom (unchecked-get props "zoom") - - hover? (unchecked-get props "hover?") - selected? (unchecked-get props "selected?") - - span-column-tracks (subvec column-tracks (dec column) (+ (dec column) column-span)) - span-row-tracks (subvec row-tracks (dec row) (+ (dec row) row-span)) - - hv #(gpo/start-hv layout-bounds %) - vv #(gpo/start-vv layout-bounds %) - - start-p (gpt/add origin - (gpt/add - (gpt/to-vec origin (dm/get-in span-column-tracks [0 :start-p])) - (gpt/to-vec origin (dm/get-in span-row-tracks [0 :start-p])))) - - end-p - (as-> start-p $ - (reduce (fn [p track] (gpt/add p (hv (:size track)))) $ span-column-tracks) - (reduce (fn [p track] (gpt/add p (vv (:size track)))) $ span-row-tracks) - (gpt/add $ (hv (* column-gap (dec (count span-column-tracks))))) - (gpt/add $ (vv (* row-gap (dec (count span-row-tracks)))))) - - cell-width (- (:x end-p) (:x start-p)) - cell-height (- (:y end-p) (:y start-p))] + cell-bounds (gsg/cell-bounds layout-data cell) + cell-origin (gpo/origin cell-bounds) + cell-width (gpo/width-points cell-bounds) + cell-height (gpo/height-points cell-bounds)] [:g.cell-editor [:rect {:class (dom/classnames (css :grid-cell-outline) true (css :hover) hover? (css :selected) selected?) - :x (:x start-p) - :y (:y start-p) + :x (:x cell-origin) + :y (:y cell-origin) :width cell-width :height cell-height @@ -320,10 +301,10 @@ (when selected? (let [handlers ;; Handlers positions, size and cursor - [[:top (:x start-p) (+ (:y start-p) (/ -10 zoom)) cell-width (/ 20 zoom) :row] - [:right (+ (:x start-p) cell-width (/ -10 zoom)) (:y start-p) (/ 20 zoom) cell-height :column] - [:bottom (:x start-p) (+ (:y start-p) cell-height (/ -10 zoom)) cell-width (/ 20 zoom) :row] - [:left (+ (:x start-p) (/ -10 zoom)) (:y start-p) (/ 20 zoom) cell-height :column]]] + [[:top (:x cell-origin) (+ (:y cell-origin) (/ -10 zoom)) cell-width (/ 20 zoom) :row] + [:right (+ (:x cell-origin) cell-width (/ -10 zoom)) (:y cell-origin) (/ 20 zoom) cell-height :column] + [:bottom (:x cell-origin) (+ (:y cell-origin) cell-height (/ -10 zoom)) cell-width (/ 20 zoom) :row] + [:left (+ (:x cell-origin) (/ -10 zoom)) (:y cell-origin) (/ 20 zoom) cell-height :column]]] [:* (for [[handler x y width height dir] handlers] [:& resize-cell-handler {:key (dm/str "resize-" (d/name handler) "-" (:id cell)) @@ -366,7 +347,6 @@ (reset! start-size-after (:size track-after)) (let [tracks-prop (if (= :column type) :layout-grid-columns :layout-grid-rows) - shape (-> shape (cond-> (some? track-before) @@ -387,7 +367,8 @@ (if (= :column type) [:layout-grid-columns :x] [:layout-grid-rows :y]) precision (if snap-pixel? mth/round identity) - delta (get position axis) + delta (/ (get position axis) zoom) + shape (-> shape (cond-> (some? track-before) @@ -439,7 +420,7 @@ :style {:fill "transparent"}}])) (mf/defc track - {::mf/wrap [mf/memo] + {::mf/wrap [#(mf/memo' % (mf/check-props ["shape" "zoom" "index" "type" "track-data" "layout-data"]))] ::mf/wrap-props false} [props] (let [shape (unchecked-get props "shape") @@ -485,7 +466,7 @@ value (-> target dom/get-input-value str/upper) value-int (d/parse-integer value) - [type value] + [track-type value] (cond (str/ends-with? value "%") [:percent value-int] @@ -498,10 +479,10 @@ (or (= value "AUTO") (= "" value)) [:auto nil])] - (if (some? type) - (do (dom/set-value! target (format-size {:type type :value value})) - (dom/set-data! target "default-value" (format-size {:type type :value value})) - (st/emit! (dwsl/change-layout-track [(:id shape)] type index {:type type :value value}))) + + (if (some? track-type) + (do (st/emit! (dwsl/change-layout-track [(:id shape)] type index {:type track-type :value value})) + (dom/set-data! target "default-value" (format-size {:type type :value value}))) (obj/set! target "value" (dom/get-attribute target "data-default-value")))))) handle-keydown-track-input @@ -659,6 +640,26 @@ :layout-data layout-data :track-data row-data}]) + (let [last-track (last row-tracks) + start-p (:start-p (last row-tracks)) + marker-p (-> start-p + (gpt/subtract (hv (/ 20 zoom))) + (gpt/add (vv (:size last-track))))] + [:g.track + [:g {:transform (dm/fmt "rotate(-90 % %)" (:x marker-p) (:y marker-p))} + [:& track-marker {:center marker-p + :value (dm/str (inc (count row-tracks))) + :zoom zoom}]] + [:& resize-track-handler + {:index (count row-tracks) + :shape shape + :layout-data layout-data + :start-p (-> start-p + (gpt/add (vv (:size last-track)))) + :type :row + :track-before (last row-tracks) + :zoom zoom}]]) + (for [[_ cell] (:layout-grid-cells shape)] [:& grid-cell {:key (dm/str "cell-" (:id cell)) :shape base-shape From 5925d2520ffc1f8fdecbe3d82e8b225ba1a6f033 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 8 May 2023 17:25:22 +0200 Subject: [PATCH 09/52] :sparkles: Changes to the editor UI --- .../geom/shapes/grid_layout/layout_data.cljc | 3 + .../resources/styles/common/framework.scss | 4 + .../styles/main/partials/workspace.scss | 24 +- .../app/main/data/workspace/drawing/box.cljs | 11 +- .../main/data/workspace/drawing/curve.cljs | 11 +- .../data/workspace/grid_layout/editor.cljs | 20 + .../app/main/data/workspace/shape_layout.cljs | 2 +- .../src/app/main/data/workspace/shapes.cljs | 3 + .../shapes/frame/dynamic_modifiers.cljs | 2 +- .../main/ui/workspace/viewport/actions.cljs | 5 +- .../viewport/grid_layout_editor.cljs | 355 +++++++++++------- .../viewport/grid_layout_editor.scss | 2 +- .../app/main/ui/workspace/viewport/utils.cljs | 15 +- .../main/ui/workspace/viewport/widgets.cljs | 55 ++- frontend/translations/en.po | 3 + frontend/translations/es.po | 3 + 16 files changed, 350 insertions(+), 168 deletions(-) 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 b89b372b4b..53663bbef0 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 @@ -349,6 +349,9 @@ column-tracks (add-auto-size column-tracks column-add-auto) row-tracks (add-auto-size row-tracks row-add-auto) + column-total-size (tracks-total-size column-tracks) + row-total-size (tracks-total-size row-tracks) + start-p (cond-> bound-corner (= :end (:layout-align-content parent)) diff --git a/frontend/resources/styles/common/framework.scss b/frontend/resources/styles/common/framework.scss index 257ea2e311..37e877f269 100644 --- a/frontend/resources/styles/common/framework.scss +++ b/frontend/resources/styles/common/framework.scss @@ -123,6 +123,10 @@ padding: $size-1; svg { fill: $color-gray-20; + + &.icon-close { + transform: rotate(45deg); + } } &:hover { background: transparent; diff --git a/frontend/resources/styles/main/partials/workspace.scss b/frontend/resources/styles/main/partials/workspace.scss index b1a04a6d84..595d2d8312 100644 --- a/frontend/resources/styles/main/partials/workspace.scss +++ b/frontend/resources/styles/main/partials/workspace.scss @@ -367,7 +367,8 @@ $height-palette-max: 80px; z-index: 12; pointer-events: none; - .path-actions { + .path-actions, + .grid-actions { pointer-events: initial; display: flex; flex-direction: row; @@ -378,6 +379,27 @@ $height-palette-max: 80px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); } + .grid-actions { + padding-left: 1rem; + gap: 12px; + color: var(--color-gray-60); + align-items: center; + font-size: 12px; + + .btn-primary, + .btn-secondary { + height: 24px; + } + + .grid-edit-title { + margin-right: 2rem; + } + + .grid-edit-board-name { + font-weight: 600; + } + } + .viewport-actions-group { display: flex; flex-direction: row; diff --git a/frontend/src/app/main/data/workspace/drawing/box.cljs b/frontend/src/app/main/data/workspace/drawing/box.cljs index 9517047d79..2f5209e5f0 100644 --- a/frontend/src/app/main/data/workspace/drawing/box.cljs +++ b/frontend/src/app/main/data/workspace/drawing/box.cljs @@ -8,7 +8,8 @@ (:require [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] - [app.common.geom.shapes.flex-layout :as gsl] + [app.common.geom.shapes.flex-layout :as gslf] + [app.common.geom.shapes.grid-layout :as gslg] [app.common.math :as mth] [app.common.pages.helpers :as cph] [app.common.types.modifiers :as ctm] @@ -84,7 +85,10 @@ fid (ctst/top-nested-frame objects initial) flex-layout? (ctl/flex-layout? objects fid) - drop-index (when flex-layout? (gsl/get-drop-index fid objects initial)) + grid-layout? (ctl/grid-layout? objects fid) + + drop-index (when flex-layout? (gslf/get-drop-index fid objects initial)) + drop-cell (when grid-layout? (gslg/get-drop-cell fid objects initial)) shape (get-in state [:workspace-drawing :object]) shape (-> shape @@ -101,6 +105,9 @@ (cond-> (some? drop-index) (with-meta {:index drop-index})) + (cond-> (some? drop-cell) + (with-meta {:cell drop-cell})) + (assoc :initialized? true) (assoc :click-draw? true))] (rx/concat diff --git a/frontend/src/app/main/data/workspace/drawing/curve.cljs b/frontend/src/app/main/data/workspace/drawing/curve.cljs index 307f0973a8..e4c400a047 100644 --- a/frontend/src/app/main/data/workspace/drawing/curve.cljs +++ b/frontend/src/app/main/data/workspace/drawing/curve.cljs @@ -8,7 +8,8 @@ (:require [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] - [app.common.geom.shapes.flex-layout :as gsl] + [app.common.geom.shapes.flex-layout :as gslf] + [app.common.geom.shapes.grid-layout :as gslg] [app.common.geom.shapes.path :as gsp] [app.common.types.shape-tree :as ctst] [app.common.types.shape.layout :as ctl] @@ -53,11 +54,15 @@ position (when start (gpt/point start)) frame-id (ctst/top-nested-frame objects position) flex-layout? (ctl/flex-layout? objects frame-id) - drop-index (when flex-layout? (gsl/get-drop-index frame-id objects position))] + grid-layout? (ctl/grid-layout? objects frame-id) + drop-index (when flex-layout? (gslf/get-drop-index frame-id objects position)) + drop-cell (when grid-layout? (gslg/get-drop-cell frame-id objects position))] (-> state (assoc-in [:workspace-drawing :object :frame-id] frame-id) (cond-> (some? drop-index) - (update-in [:workspace-drawing :object] with-meta {:index drop-index}))))))) + (update-in [:workspace-drawing :object] with-meta {:index drop-index})) + (cond-> (some? drop-cell) + (update-in [:workspace-drawing :object] with-meta {:cell drop-cell}))))))) (defn curve-to-path [{:keys [segments] :as shape}] (let [content (gsp/segments->content segments) diff --git a/frontend/src/app/main/data/workspace/grid_layout/editor.cljs b/frontend/src/app/main/data/workspace/grid_layout/editor.cljs index 2a77103669..045927fdce 100644 --- a/frontend/src/app/main/data/workspace/grid_layout/editor.cljs +++ b/frontend/src/app/main/data/workspace/grid_layout/editor.cljs @@ -6,6 +6,8 @@ (ns app.main.data.workspace.grid-layout.editor (:require + [app.common.geom.shapes :as gsh] + [app.main.data.workspace.state-helpers :as wsh] [potok.core :as ptk])) (defn hover-grid-cell @@ -42,3 +44,21 @@ ptk/UpdateEvent (update [_ state] (update state :workspace-grid-edition dissoc grid-id)))) + +(defn locate-board + [grid-id] + (ptk/reify ::locate-board + ptk/UpdateEvent + (update [_ state] + (let [objects (wsh/lookup-page-objects state) + srect (get-in objects [grid-id :selrect])] + (prn srect) + (-> state + (update :workspace-local + (fn [{:keys [zoom vport] :as local}] + (let [{:keys [x y width height]} srect + x (+ x (/ width 2) (- (/ (:width vport) 2 zoom))) + y (+ y (/ height 2) (- (/ (:height vport) 2 zoom))) + srect (gsh/make-selrect x y width height)] + (-> local + (update :vbox merge (select-keys srect [:x :y :x1 :x2 :y1 :y2]))))))))))) diff --git a/frontend/src/app/main/data/workspace/shape_layout.cljs b/frontend/src/app/main/data/workspace/shape_layout.cljs index fc1ec63956..7deb5385f6 100644 --- a/frontend/src/app/main/data/workspace/shape_layout.cljs +++ b/frontend/src/app/main/data/workspace/shape_layout.cljs @@ -1,4 +1,4 @@ -; This Source Code Form is subject to the terms of the Mozilla Public +;; 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/. ;; diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs index 0ef0f39a28..e8d04ff940 100644 --- a/frontend/src/app/main/data/workspace/shapes.cljs +++ b/frontend/src/app/main/data/workspace/shapes.cljs @@ -82,6 +82,7 @@ selected) index (:index (meta attrs)) + [row column :as cell] (:cell (meta attrs)) changes (-> changes (pcb/with-objects objects) @@ -91,6 +92,8 @@ (pcb/add-object shape)) (cond-> (some? (:parent-id attrs)) (pcb/change-parent (:parent-id attrs) [shape] index)) + (cond-> (some? cell) + (pcb/update-shapes [(:parent-id shape)] #(ctl/push-into-cell % [id] row column))) (cond-> (ctl/grid-layout? objects (:parent-id shape)) (pcb/update-shapes [(:parent-id shape)] ctl/assign-cells)))] diff --git a/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs b/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs index caba728b9c..f0141037c5 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs @@ -151,7 +151,7 @@ (dom/class? node "frame-title") (let [shape (gsh/transform-shape shape modifiers) zoom (get-in @st/state [:workspace-local :zoom] 1) - mtx (vwu/title-transform shape zoom)] + mtx (vwu/title-transform shape zoom false)] (override-transform-att! node "transform" mtx)) (or (= (dom/get-tag-name node) "mask") diff --git a/frontend/src/app/main/ui/workspace/viewport/actions.cljs b/frontend/src/app/main/ui/workspace/viewport/actions.cljs index 628c6c8ff1..7c1f48565d 100644 --- a/frontend/src/app/main/ui/workspace/viewport/actions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/actions.cljs @@ -53,9 +53,9 @@ (.setPointerCapture editor (.-pointerId bevent)) (.setPointerCapture target (.-pointerId bevent)))) - (when (or (dom/class? (dom/get-target bevent) "viewport-controls") - (dom/class? (dom/get-target bevent) "viewport-selrect")) + (dom/child? (dom/get-target bevent) (dom/query ".viewport-controls"))) + (dom/stop-propagation bevent) (when-not @z? @@ -79,7 +79,6 @@ (st/emit! (dw/start-zooming pt))) (st/emit! (dw/start-panning)))) - left-click? (do (st/emit! (ms/->MouseEvent :down ctrl? shift? alt? meta?)) 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 b446f1b7b2..9eb72cb46b 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 @@ -38,48 +38,6 @@ :flex (dm/str (fmt/format-number value) "FR") :auto "AUTO")) -(mf/defc track-marker - {::mf/wrap-props false} - [props] - - (let [center (unchecked-get props "center") - value (unchecked-get props "value") - zoom (unchecked-get props "zoom") - - marker-width (/ 24 zoom) - marker-h1 (/ 22 zoom) - marker-h2 (/ 8 zoom) - - marker-half-width (/ marker-width 2) - marker-half-height (/ (+ marker-h1 marker-h2) 2) - - marker-points - (reduce - apply-to-point - [(gpt/subtract center - (gpt/point marker-half-width marker-half-height))] - [#(gpt/add % (gpt/point marker-width 0)) - #(gpt/add % (gpt/point 0 marker-h1)) - #(gpt/add % (gpt/point (- marker-half-width) marker-h2)) - #(gpt/subtract % (gpt/point marker-half-width marker-h2))]) - - text-x (:x center) - text-y (:y center)] - - [:g {:class (css :grid-track-marker)} - [:polygon {:class (css :marker-shape) - :points (->> marker-points - (map #(dm/fmt "%,%" (:x %) (:y %))) - (str/join " "))}] - [:text {:class (css :marker-text) - :x text-x - :y text-y - :width (/ 26.26 zoom) - :height (/ 32 zoom) - :text-anchor "middle" - :dominant-baseline "middle"} - (dm/str value)]])) - (mf/defc grid-editor-frame {::mf/wrap-props false} [props] @@ -282,7 +240,22 @@ cell-bounds (gsg/cell-bounds layout-data cell) cell-origin (gpo/origin cell-bounds) cell-width (gpo/width-points cell-bounds) - cell-height (gpo/height-points cell-bounds)] + cell-height (gpo/height-points cell-bounds) + + handle-pointer-enter + (mf/use-callback + (fn [] + (st/emit! (dwge/hover-grid-cell (:id shape) (:id cell) true)))) + + handle-pointer-leave + (mf/use-callback + (fn [] + (st/emit! (dwge/hover-grid-cell (:id shape) (:id cell) false)))) + + handle-pointer-down + (mf/use-callback + (fn [] + (st/emit! (dwge/select-grid-cell (:id shape) (:id cell)))))] [:g.cell-editor [:rect @@ -294,9 +267,9 @@ :width cell-width :height cell-height - :on-pointer-enter #(st/emit! (dwge/hover-grid-cell (:id shape) (:id cell) true)) - :on-pointer-leave #(st/emit! (dwge/hover-grid-cell (:id shape) (:id cell) false)) - :on-click #(st/emit! (dwge/select-grid-cell (:id shape) (:id cell)))}] + :on-pointer-enter handle-pointer-enter + :on-pointer-leave handle-pointer-leave + :on-pointer-down handle-pointer-down}] (when selected? (let [handlers @@ -318,31 +291,17 @@ :direction dir :layout-data layout-data}])]))])) -(mf/defc resize-track-handler - {::mf/wrap-props false} - [props] +(defn use-resize-track + [type shape index track-before track-after zoom snap-pixel?] - (let [shape (unchecked-get props "shape") - index (unchecked-get props "index") - track-before (unchecked-get props "track-before") - track-after (unchecked-get props "track-after") - - {:keys [column-total-size column-total-gap row-total-size row-total-gap]} (unchecked-get props "layout-data") - start-p (unchecked-get props "start-p") - type (unchecked-get props "type") - zoom (unchecked-get props "zoom") - - [layout-gap-row layout-gap-col] (ctl/gaps shape) - - start-size-before (mf/use-var nil) + (let [start-size-before (mf/use-var nil) start-size-after (mf/use-var nil) - snap-pixel? (mf/deref refs/snap-pixel?) - handle-drag-start (mf/use-callback (mf/deps shape track-before track-after) (fn [] + (prn "???" type (:name shape) index track-before track-after zoom snap-pixel?) (reset! start-size-before (:size track-before)) (reset! start-size-after (:size track-after)) (let [tracks-prop @@ -387,23 +346,42 @@ (fn [] (reset! start-size-before nil) (reset! start-size-after nil) - (st/emit! (dwm/apply-modifiers)))) + (st/emit! (dwm/apply-modifiers))))] + + (use-drag {:on-drag-start handle-drag-start + :on-drag-delta handle-drag-position + :on-drag-end handle-drag-end}))) + +(mf/defc resize-track-handler + {::mf/wrap-props false} + [props] + + (let [shape (unchecked-get props "shape") + index (unchecked-get props "index") + track-before (unchecked-get props "track-before") + track-after (unchecked-get props "track-after") + snap-pixel? (unchecked-get props "snap-pixel?") + + {:keys [column-total-size column-total-gap row-total-size row-total-gap]} (unchecked-get props "layout-data") + start-p (unchecked-get props "start-p") + type (unchecked-get props "type") + zoom (unchecked-get props "zoom") + + [layout-gap-row layout-gap-col] (ctl/gaps shape) {:keys [handle-pointer-down handle-lost-pointer-capture handle-pointer-move]} - (use-drag {:on-drag-start handle-drag-start - :on-drag-delta handle-drag-position - :on-drag-end handle-drag-end}) + (use-resize-track type shape index track-before track-after zoom snap-pixel?) [x y width height] (if (= type :column) - [(- (:x start-p) (max layout-gap-col (/ 8 zoom))) - (- (:y start-p) (/ 40 zoom)) + [(- (:x start-p) layout-gap-col (/ (- (max layout-gap-col (/ 16 zoom)) layout-gap-col) 2)) + (:y start-p) (max layout-gap-col (/ 16 zoom)) - (+ row-total-size row-total-gap (/ 40 zoom))] + (+ row-total-size row-total-gap)] - [(- (:x start-p) (/ 40 zoom)) - (- (:y start-p) (max layout-gap-row (/ 8 zoom))) - (+ column-total-size column-total-gap (/ 40 zoom)) + [(:x start-p) + (- (:y start-p) layout-gap-row (/ (- (max layout-gap-row (/ 16 zoom)) layout-gap-row) 2)) + (+ column-total-size column-total-gap) (max layout-gap-row (/ 16 zoom))])] [:rect.resize-track-handler @@ -419,6 +397,63 @@ :on-pointer-move handle-pointer-move :style {:fill "transparent"}}])) +(mf/defc track-marker + {::mf/wrap-props false} + [props] + + (let [center (unchecked-get props "center") + value (unchecked-get props "value") + zoom (unchecked-get props "zoom") + shape (unchecked-get props "shape") + index (unchecked-get props "index") + type (unchecked-get props "type") + track-before (unchecked-get props "track-before") + track-after (unchecked-get props "track-after") + snap-pixel? (unchecked-get props "snap-pixel?") + + marker-width (/ 24 zoom) + marker-h1 (/ 22 zoom) + marker-h2 (/ 8 zoom) + + marker-half-width (/ marker-width 2) + marker-half-height (/ (+ marker-h1 marker-h2) 2) + + marker-points + (reduce + apply-to-point + [(gpt/subtract center + (gpt/point marker-half-width marker-half-height))] + [#(gpt/add % (gpt/point marker-width 0)) + #(gpt/add % (gpt/point 0 marker-h1)) + #(gpt/add % (gpt/point (- marker-half-width) marker-h2)) + #(gpt/subtract % (gpt/point marker-half-width marker-h2))]) + + text-x (:x center) + text-y (:y center) + + {:keys [handle-pointer-down handle-lost-pointer-capture handle-pointer-move]} + (use-resize-track type shape index track-before track-after zoom snap-pixel?)] + + [:g {:on-pointer-down handle-pointer-down + :on-lost-pointer-capture handle-lost-pointer-capture + :on-pointer-move handle-pointer-move + :class (css :grid-track-marker) + :style {:cursor (if (= type :column) + (cur/resize-ew 0) + (cur/resize-ns 0))}} + [:polygon {:class (css :marker-shape) + :points (->> marker-points + (map #(dm/fmt "%,%" (:x %) (:y %))) + (str/join " "))}] + [:text {:class (css :marker-text) + :x text-x + :y text-y + :width (/ 26.26 zoom) + :height (/ 32 zoom) + :text-anchor "middle" + :dominant-baseline "middle"} + (dm/str value)]])) + (mf/defc track {::mf/wrap [#(mf/memo' % (mf/check-props ["shape" "zoom" "index" "type" "track-data" "layout-data"]))] ::mf/wrap-props false} @@ -427,32 +462,40 @@ zoom (unchecked-get props "zoom") type (unchecked-get props "type") index (unchecked-get props "index") + snap-pixel? (unchecked-get props "snap-pixel?") track-data (unchecked-get props "track-data") layout-data (unchecked-get props "layout-data") track-input-ref (mf/use-ref) [layout-gap-row layout-gap-col] (ctl/gaps shape) bounds (:points shape) + origin (gpo/origin bounds) vv #(gpo/start-vv bounds %) hv #(gpo/start-hv bounds %) start-p (:start-p track-data) + relative (gpt/to-vec origin start-p) + marker-p (if (= type :column) - (-> start-p + (-> origin + (gpt/add (hv (:x relative))) (gpt/subtract (vv (/ 20 zoom))) (cond-> (not= index 0) (gpt/subtract (hv (/ layout-gap-col 2))))) - (-> start-p + (-> origin + (gpt/add (vv (:y relative))) (gpt/subtract (hv (/ 20 zoom))) (cond-> (not= index 0) (gpt/subtract (vv (/ layout-gap-row 2)))))) text-p (if (= type :column) - (-> start-p + (-> origin + (gpt/add (hv (:x relative))) (gpt/subtract (vv (/ 36 zoom)))) - (-> start-p + (-> origin + (gpt/add (vv (:y relative))) (gpt/subtract (hv (/ (:size track-data) 2))) (gpt/subtract (hv (/ 16 zoom))) (gpt/add (vv (/ (:size track-data) 2))) @@ -499,7 +542,9 @@ [text-x text-y text-width text-height] (if (= type :column) [(:x text-p) (:y text-p) (max 0 (- (:size track-data) 4)) (/ 32 zoom)] - [(:x text-p) (:y text-p) (:size track-data) (/ 36 zoom)])] + [(:x text-p) (:y text-p) (:size track-data) (/ 36 zoom)]) + + track-before (get-in layout-data [track-list-prop (dec index)])] (mf/use-effect (mf/deps track-data) @@ -509,6 +554,12 @@ [:g.track [:g {:transform (when (= type :row) (dm/fmt "rotate(-90 % %)" (:x marker-p) (:y marker-p)))} [:& track-marker {:center marker-p + :index index + :shape shape + :snap-pixel? snap-pixel? + :track-after track-data + :track-before track-before + :type type :value (dm/str (inc index)) :zoom zoom}]] [:g {:transform (when (= type :row) (dm/fmt "rotate(-90 % %)" (+ (:x text-p) (/ (:size track-data) 2)) (+ (:y text-p) (/ 36 zoom 2))))} @@ -522,16 +573,17 @@ :on-key-down handle-keydown-track-input :on-blur handle-blur-track-input}]]] - (let [track-before (get-in layout-data [track-list-prop (dec index)])] - [:& resize-track-handler - {:index index - :shape shape - :layout-data layout-data - :start-p start-p - :type type - :track-before track-before - :track-after track-data - :zoom zoom}])])) + + [:& resize-track-handler + {:index index + :layout-data layout-data + :shape shape + :snap-pixel? snap-pixel? + :start-p start-p + :track-after track-data + :track-before track-before + :type type + :zoom zoom}]])) (mf/defc editor {::mf/wrap [mf/memo] @@ -547,6 +599,8 @@ shape-ref (mf/use-memo (mf/deps (:id shape)) #(refs/object-by-id (:id shape))) base-shape (mf/deref shape-ref) + snap-pixel? (mf/deref refs/snap-pixel?) + grid-edition-id-ref (mf/use-memo #(refs/workspace-grid-edition-id (:id shape))) grid-edition (mf/deref grid-edition-id-ref) @@ -565,9 +619,18 @@ height (gpo/height-points bounds) origin (gpo/origin bounds) + [layout-gap-row layout-gap-col] (ctl/gaps shape) + {:keys [row-tracks column-tracks] :as layout-data} (gsg/calc-layout-data shape children bounds) + handle-pointer-down + (mf/use-callback + (fn [event] + (let [left-click? (= 1 (.-which (.-nativeEvent event)))] + (when left-click? + (dom/stop-propagation event))))) + handle-add-column (mf/use-callback (mf/deps (:id shape)) @@ -581,10 +644,11 @@ (st/emit! (st/emit! (dwsl/add-layout-track [(:id shape)] :row ctl/default-track-value)))))] (mf/use-effect - (fn [] - #(st/emit! (dwge/stop-grid-layout-editing (:id shape))))) + (fn [] + #(st/emit! (dwge/stop-grid-layout-editing (:id shape))))) - [:g.grid-editor {:pointer-events (when view-only "none")} + [:g.grid-editor {:pointer-events (when view-only "none") + :on-pointer-down handle-pointer-down} (when-not view-only [:* [:& grid-editor-frame {:zoom zoom @@ -608,57 +672,76 @@ :type :column :index idx :layout-data layout-data + :snap-pixel? snap-pixel? :track-data column-data}]) ;; Last track resize handler - (let [last-track (last column-tracks) - start-p (:start-p (last column-tracks)) - marker-p (-> start-p - (gpt/subtract (vv (/ 20 zoom))) - (gpt/add (hv (:size last-track))))] - [:g.track - [:& track-marker {:center marker-p - :value (dm/str (inc (count column-tracks))) - :zoom zoom}] - [:& resize-track-handler - {:index (count column-tracks) - :shape shape - :layout-data layout-data - :start-p (-> start-p - (gpt/add (hv (:size last-track))) - (gpt/add (hv (/ 20 zoom)))) - :type :column - :track-before (last column-tracks) - :zoom zoom}]]) + (when-not (empty? column-tracks) + (let [last-track (last column-tracks) + start-p (:start-p (last column-tracks)) + relative (gpt/to-vec origin start-p) + marker-p (-> origin + (gpt/add (hv (:x relative))) + (gpt/subtract (vv (/ 20 zoom))) + (gpt/add (hv (:size last-track))))] + [:g.track + [:& track-marker {:center marker-p + :index (count column-tracks) + :shape shape + :snap-pixel? snap-pixel? + :track-before (last column-tracks) + :type :column + :value (dm/str (inc (count column-tracks))) + :zoom zoom}] + [:& resize-track-handler + {:index (count column-tracks) + :shape shape + :layout-data layout-data + :snap-pixel? snap-pixel? + :start-p (-> start-p (gpt/add (hv (+ layout-gap-col (:size last-track))))) + :type :column + :track-before (last column-tracks) + :zoom zoom}]])) (for [[idx row-data] (d/enumerate row-tracks)] - [:& track {:key (dm/str "row-track-" idx) - :shape shape - :zoom zoom - :type :row - :index idx + [:& track {:index idx + :key (dm/str "row-track-" idx) :layout-data layout-data - :track-data row-data}]) + :shape shape + :snap-pixel? snap-pixel? + :track-data row-data + :type :row + :zoom zoom}]) - (let [last-track (last row-tracks) - start-p (:start-p (last row-tracks)) - marker-p (-> start-p - (gpt/subtract (hv (/ 20 zoom))) - (gpt/add (vv (:size last-track))))] - [:g.track - [:g {:transform (dm/fmt "rotate(-90 % %)" (:x marker-p) (:y marker-p))} - [:& track-marker {:center marker-p - :value (dm/str (inc (count row-tracks))) - :zoom zoom}]] - [:& resize-track-handler - {:index (count row-tracks) - :shape shape - :layout-data layout-data - :start-p (-> start-p - (gpt/add (vv (:size last-track)))) - :type :row - :track-before (last row-tracks) - :zoom zoom}]]) + (when-not (empty? row-tracks) + (let [last-track (last row-tracks) + start-p (:start-p (last row-tracks)) + relative (gpt/to-vec origin start-p) + marker-p + (-> origin + (gpt/add (vv (:y relative))) + (gpt/subtract (hv (/ 20 zoom))) + (gpt/add (vv (:size last-track))))] + [:g.track + [:g {:transform (dm/fmt "rotate(-90 % %)" (:x marker-p) (:y marker-p))} + [:& track-marker {:center marker-p + :index (count row-tracks) + :shape shape + :snap-pixel? snap-pixel? + :track-before (last row-tracks) + :type :row + :value (dm/str (inc (count row-tracks))) + :zoom zoom}]] + [:& resize-track-handler + {:index (count row-tracks) + :shape shape + :layout-data layout-data + :start-p (-> start-p + (gpt/add (vv (+ layout-gap-row (:size last-track))))) + :type :row + :track-before (last row-tracks) + :snap-pixel? snap-pixel? + :zoom zoom}]])) (for [[_ cell] (:layout-grid-cells shape)] [:& grid-cell {:key (dm/str "cell-" (:id cell)) diff --git a/frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.scss b/frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.scss index 8a8a13659f..1526c3aaa6 100644 --- a/frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.scss +++ b/frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.scss @@ -31,7 +31,7 @@ } .grid-frame { - fill: transparent; + fill: #F6F6F6; stroke: var(--color-distance); stroke-width: calc(1 / var(--zoom)); } diff --git a/frontend/src/app/main/ui/workspace/viewport/utils.cljs b/frontend/src/app/main/ui/workspace/viewport/utils.cljs index 5587022d89..5fdc522a63 100644 --- a/frontend/src/app/main/ui/workspace/viewport/utils.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/utils.cljs @@ -69,7 +69,8 @@ (> (:x cand) (:x cur)) cand :else cur))) -(defn title-transform [{:keys [points] :as shape} zoom] +(defn title-transform + [{:keys [points] :as shape} zoom grid-edition?] (let [leftmost (->> points (reduce left?)) topmost (->> points (remove #{leftmost}) (reduce top?)) rightmost (->> points (remove #{leftmost topmost}) (reduce right?)) @@ -81,14 +82,18 @@ top-right-angle (gpt/angle top-right) ;; Choose the position that creates the less angle between left-side and top-side - [label-pos angle v-pos] + [label-pos angle h-pos v-pos] (if (< (mth/abs left-top-angle) (mth/abs top-right-angle)) - [leftmost left-top-angle (gpt/perpendicular left-top)] - [topmost top-right-angle (gpt/perpendicular top-right)]) + [leftmost left-top-angle left-top (gpt/perpendicular left-top)] + [topmost top-right-angle top-right (gpt/perpendicular top-right)]) + delta-x (if grid-edition? 40 0) + delta-y (if grid-edition? 50 10) label-pos - (gpt/subtract label-pos (gpt/scale (gpt/unit v-pos) (/ 10 zoom)))] + (-> label-pos + (gpt/subtract (gpt/scale (gpt/unit v-pos) (/ delta-y zoom))) + (gpt/subtract (gpt/scale (gpt/unit h-pos) (/ delta-x zoom))))] (dm/fmt "rotate(% %,%) scale(%, %) translate(%, %)" ;; rotate diff --git a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs index 7755d080ae..9da52f0b22 100644 --- a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs @@ -12,8 +12,10 @@ [app.common.pages.helpers :as cph] [app.common.types.container :as ctn] [app.common.types.shape-tree :as ctt] + [app.common.types.shape.layout :as ctl] [app.common.uuid :as uuid] [app.main.data.workspace :as dw] + [app.main.data.workspace.grid-layout.editor :as dwge] [app.main.data.workspace.interactions :as dwi] [app.main.refs :as refs] [app.main.store :as st] @@ -24,6 +26,7 @@ [app.main.ui.workspace.viewport.path-actions :refer [path-actions]] [app.main.ui.workspace.viewport.utils :as vwu] [app.util.dom :as dom] + [app.util.i18n :as i18n :refer [tr]] [app.util.timers :as ts] [debug :refer [debug?]] [rumext.v2 :as mf])) @@ -56,16 +59,34 @@ selected (mf/deref refs/selected-objects) drawing (mf/deref refs/workspace-drawing) drawing-obj (:object drawing) - shape (or drawing-obj (-> selected first))] - (when (or (and (= (count selected) 1) - (= (:id shape) edition) - (and (not (cph/text-shape? shape)) - (not (cph/frame-shape? shape)))) - (and (some? drawing-obj) - (cph/path-shape? drawing-obj) - (not= :curve (:tool drawing)))) + shape (or drawing-obj (-> selected first)) + + single? (= (count selected) 1) + editing? (= (:id shape) edition) + draw-path? (and (some? drawing-obj) + (cph/path-shape? drawing-obj) + (not= :curve (:tool drawing))) + + path-edition? (or (and single? editing? + (and (not (cph/text-shape? shape)) + (not (cph/frame-shape? shape)))) + draw-path?) + + grid-edition? (and single? editing? (ctl/grid-layout? shape))] + + (cond + path-edition? [:div.viewport-actions - [:& path-actions {:shape shape}]]))) + [:& path-actions {:shape shape}]] + + grid-edition? + [:div.viewport-actions + [:div.grid-actions + [:div.grid-edit-title + (tr "workspace.layout_grid.editor.title") " " [:span.grid-edit-board-name (:name shape)]] + [:button.btn-secondary {:on-click #(st/emit! (dwge/locate-board (:id shape)))} "Locate"] + [:button.btn-primary {:on-click #(st/emit! dw/clear-edition-mode)} "Done"] + [:button.btn-icon-basic {:on-click #(st/emit! dw/clear-edition-mode)} i/close]]]))) (mf/defc cursor-tooltip [{:keys [zoom tooltip] :as props}] @@ -97,7 +118,7 @@ (mf/defc frame-title {::mf/wrap [mf/memo #(mf/deferred % ts/raf)]} - [{:keys [frame selected? zoom show-artboard-names? show-id? on-frame-enter on-frame-leave on-frame-select]}] + [{:keys [frame selected? zoom show-artboard-names? show-id? on-frame-enter on-frame-leave on-frame-select grid-edition?]}] (let [workspace-read-only? (mf/use-ctx ctx/workspace-read-only?) ;; Note that we don't use mf/deref to avoid a repaint dependency here @@ -114,8 +135,8 @@ (fn [bevent] (let [event (.-nativeEvent bevent)] (when (= 1 (.-which event)) - (dom/prevent-default event) - (dom/stop-propagation event) + (dom/prevent-default bevent) + (dom/stop-propagation bevent) (on-frame-select event (:id frame)))))) on-double-click @@ -150,7 +171,7 @@ (when (not (:hidden frame)) [:g.frame-title {:id (dm/str "frame-title-" (:id frame)) - :transform (vwu/title-transform frame zoom) + :transform (vwu/title-transform frame zoom grid-edition?) :pointer-events (when (:blocked frame) "none")} (when (:use-for-thumbnail? frame) [:svg {:x 0 @@ -195,7 +216,10 @@ (map (d/getf objects)) selected) shapes) - focus (unchecked-get props "focus")] + focus (unchecked-get props "focus") + + edition (mf/deref refs/selected-edition) + grid-edition? (ctl/grid-layout? objects edition)] [:g.frame-titles (for [{:keys [id parent-id] :as shape} shapes] @@ -211,7 +235,8 @@ :show-id? (debug? :shape-titles) :on-frame-enter on-frame-enter :on-frame-leave on-frame-leave - :on-frame-select on-frame-select}]))])) + :on-frame-select on-frame-select + :grid-edition? (and (= id edition) grid-edition?)}]))])) (mf/defc frame-flow [{:keys [flow frame selected? zoom on-frame-enter on-frame-leave on-frame-select]}] diff --git a/frontend/translations/en.po b/frontend/translations/en.po index bfefc32b13..964aceddf6 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -4957,3 +4957,6 @@ msgstr "Marketing" #: src/app/main/ui/onboarding/questions.cljs msgid "questions.student-teacher" msgstr "Student or teacher" + +msgid "workspace.layout_grid.editor.title" +msgstr "Editing grid" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index dcdd6727b9..16a40b9373 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -5075,3 +5075,6 @@ msgstr "Marketing" #: src/app/main/ui/onboarding/questions.cljs msgid "questions.student-teacher" msgstr "Estudiante o profesorado" + +msgid "workspace.layout_grid.editor.title" +msgstr "Editando rejilla" From f5bb6b05f3de8d053279b73ca6a24e4943f0a258 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 9 May 2023 11:41:30 +0200 Subject: [PATCH 10/52] :sparkles: Add grid icons to layers --- .../app/main/data/workspace/grid_layout/editor.cljs | 1 - frontend/src/app/main/ui/components/shape_icon.cljs | 3 ++- frontend/src/app/main/ui/workspace/viewport.cljs | 11 ++++++----- .../ui/workspace/viewport/grid_layout_editor.cljs | 1 - .../src/app/main/ui/workspace/viewport/widgets.cljs | 13 ++++++++++--- 5 files changed, 18 insertions(+), 11 deletions(-) diff --git a/frontend/src/app/main/data/workspace/grid_layout/editor.cljs b/frontend/src/app/main/data/workspace/grid_layout/editor.cljs index 045927fdce..ff5c05253f 100644 --- a/frontend/src/app/main/data/workspace/grid_layout/editor.cljs +++ b/frontend/src/app/main/data/workspace/grid_layout/editor.cljs @@ -52,7 +52,6 @@ (update [_ state] (let [objects (wsh/lookup-page-objects state) srect (get-in objects [grid-id :selrect])] - (prn srect) (-> state (update :workspace-local (fn [{:keys [zoom vport] :as local}] diff --git a/frontend/src/app/main/ui/components/shape_icon.cljs b/frontend/src/app/main/ui/components/shape_icon.cljs index 811cd8ed74..9b4ac2e75c 100644 --- a/frontend/src/app/main/ui/components/shape_icon.cljs +++ b/frontend/src/app/main/ui/components/shape_icon.cljs @@ -26,7 +26,8 @@ (and (ctl/flex-layout? shape) (ctl/row? shape)) i/layout-rows - ;; TODO: GRID ICON + (ctl/grid-layout? shape) + i/grid-layout-mode :else i/artboard) diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 8af5e3702a..cefd87433f 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -204,7 +204,8 @@ (>= zoom 8)) show-text-editor? (and editing-shape (= :text (:type editing-shape))) - hover-grid? (and (some? @hover-top-frame-id) (ctl/grid-layout? objects @hover-top-frame-id)) + ;; Debug utility + ;; hover-grid? (and (some? @hover-top-frame-id) (ctl/grid-layout? objects @hover-top-frame-id)) show-grid-editor? (and editing-shape (ctl/grid-layout? editing-shape)) show-presence? page-id @@ -589,8 +590,9 @@ {:id (first selected) :zoom zoom}]) - ;; TODO: Temporary showing on hover. Remove eventualy - (when (or show-grid-editor? hover-grid?) + + (when show-grid-editor? + #_(or show-grid-editor? hover-grid?) ;; Debug utility [:& grid-layout/editor {:zoom zoom :objects objects-modified @@ -598,6 +600,5 @@ (gsh/transform-shape (get base-objects @hover-top-frame-id) (dm/get-in modifiers [@hover-top-frame-id :modifiers]))) - :view-only (not show-grid-editor?) - }]) + :view-only (not show-grid-editor?)}]) ]]])) 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 9eb72cb46b..af72e959c4 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 @@ -301,7 +301,6 @@ (mf/use-callback (mf/deps shape track-before track-after) (fn [] - (prn "???" type (:name shape) index track-before track-after zoom snap-pixel?) (reset! start-size-before (:size track-before)) (reset! start-size-after (:size track-after)) (let [tracks-prop diff --git a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs index 9da52f0b22..f498a0273a 100644 --- a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs @@ -167,13 +167,14 @@ (mf/deps (:id frame) on-frame-leave) (fn [_] (on-frame-leave (:id frame)))) - text-pos-x (if (:use-for-thumbnail? frame) 15 0)] + text-pos-x (if (or (:use-for-thumbnail? frame) grid-edition?) 15 0)] (when (not (:hidden frame)) [:g.frame-title {:id (dm/str "frame-title-" (:id frame)) :transform (vwu/title-transform frame zoom grid-edition?) :pointer-events (when (:blocked frame) "none")} - (when (:use-for-thumbnail? frame) + (cond + (or (:use-for-thumbnail? frame) grid-edition?) [:svg {:x 0 :y -9 :width 12 @@ -181,7 +182,13 @@ :class "workspace-frame-icon" :style {:fill color} :visibility (if show-artboard-names? "visible" "hidden")} - [:use {:href "#icon-set-thumbnail"}]]) + (cond + (:use-for-thumbnail? frame) + [:use {:href "#icon-set-thumbnail"}] + + grid-edition? + [:use {:href "#icon-grid-layout-mode"}])]) + [:text {:x text-pos-x :y 0 :width (:width frame) From 47e927d571af46e05ecbf38aba1692316fa75b9e Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 10 May 2023 12:42:43 +0200 Subject: [PATCH 11/52] :sparkles: Change column/row from cell options --- .../geom/shapes/grid_layout/positions.cljc | 46 +++--- common/src/app/common/types/shape/layout.cljc | 152 ++++++++++-------- .../data/workspace/grid_layout/editor.cljs | 1 + .../app/main/data/workspace/shape_layout.cljs | 46 +++++- .../shapes/frame/dynamic_modifiers.cljs | 3 +- .../sidebar/options/shapes/grid_cell.cljs | 150 +++++++++-------- .../src/app/main/ui/workspace/viewport.cljs | 6 +- .../viewport/grid_layout_editor.cljs | 151 +++++++++-------- .../main/ui/workspace/viewport/widgets.cljs | 1 + 9 files changed, 316 insertions(+), 240 deletions(-) diff --git a/common/src/app/common/geom/shapes/grid_layout/positions.cljc b/common/src/app/common/geom/shapes/grid_layout/positions.cljc index 30d5de604a..aae5e7e309 100644 --- a/common/src/app/common/geom/shapes/grid_layout/positions.cljc +++ b/common/src/app/common/geom/shapes/grid_layout/positions.cljc @@ -128,13 +128,13 @@ (let [hv #(gpo/start-hv layout-bounds %) vv #(gpo/start-vv layout-bounds %) - ;;make-is-inside-track - ;;(fn [type] - ;; (let [[vfn ofn] (if (= type :column) [vv hv] [hv vv])] - ;; (fn is-inside-track? [{:keys [start-p size] :as track}] - ;; (let [unit-v (vfn 1) - ;; end-p (gpt/add start-p (ofn size))] - ;; (is-inside-lines? [start-p unit-v] [end-p unit-v] position))))) + make-is-inside-track + (fn [type] + (let [[vfn ofn] (if (= type :column) [vv hv] [hv vv])] + (fn is-inside-track? [{:keys [start-p size] :as track}] + (let [unit-v (vfn 1) + end-p (gpt/add start-p (ofn size))] + (is-inside-lines? [start-p unit-v] [end-p unit-v] position))))) make-min-distance-track (fn [type] @@ -149,25 +149,29 @@ [[cur-idx track] (min dist-1 dist-2)] [selected selected-dist]))))) - ;;[col-idx column] - ;;(->> (d/enumerate column-tracks) - ;; (d/seek (comp (make-is-inside-track :column) second))) - ;; - ;;[row-idx row] - ;;(->> (d/enumerate row-tracks) - ;; (d/seek (comp (make-is-inside-track :row) second))) - - + ;; Check if it's inside a track [col-idx column] (->> (d/enumerate column-tracks) - (reduce (make-min-distance-track :column) [[nil nil] ##Inf]) - (first)) + (d/seek (comp (make-is-inside-track :column) second))) [row-idx row] (->> (d/enumerate row-tracks) - (reduce (make-min-distance-track :row) [[nil nil] ##Inf]) - (first)) - ] + (d/seek (comp (make-is-inside-track :row) second))) + + ;; If not inside we find the closest start/end line + [col-idx column] + (if (some? column) + [col-idx column] + (->> (d/enumerate column-tracks) + (reduce (make-min-distance-track :column) [[nil nil] ##Inf]) + (first))) + + [row-idx row] + (if (some? row) + [row-idx row] + (->> (d/enumerate row-tracks) + (reduce (make-min-distance-track :row) [[nil nil] ##Inf]) + (first)))] (when (and (some? column) (some? row)) [(inc row-idx) (inc col-idx)]))) diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index 165f555637..92550c11a0 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -593,8 +593,7 @@ (assoc result id (merge {:id id :row (inc row-idx) - :column new-col-num - :track? true} + :column new-col-num} grid-cell-defaults)))) (:layout-grid-cells parent)))] (-> parent @@ -617,8 +616,7 @@ (assoc result id (merge {:id id :column (inc col-idx) - :row new-row-num - :track? true} + :row new-row-num} grid-cell-defaults)))) (:layout-grid-cells parent)))] (-> parent @@ -852,86 +850,98 @@ (first))) parent))) + +(defn create-cells + "Create cells in an area. One cell per row/column " + [parent [column row column-span row-span]] + + (->> (for [row (range row (+ row row-span)) + column (range column (+ column column-span))] + (merge grid-cell-defaults + {:id (uuid/next) + :row row + :column column + :row-span 1 + :column-span 1})) + (reduce #(assoc-in %1 [:layout-grid-cells (:id %2)] %2) parent))) + (defn resize-cell-area "Increases/decreases the cell size" [parent row column new-row new-column new-row-span new-column-span] - (let [cells (vec (get-cells parent {:sort? true})) + (if (and (>= new-row 0) + (>= new-column 0) + (>= new-row-span 1) + (>= new-column-span 1)) + (let [prev-cell (cell-by-row-column parent row column) + prev-area (sga/make-area prev-cell) - prev-cell (cell-by-row-column parent row column) - prev-area (sga/make-area prev-cell) + target-cell + (-> prev-cell + (assoc + :row new-row + :column new-column + :row-span new-row-span + :column-span new-column-span)) - target-cell - (-> prev-cell - (assoc - :row new-row - :column new-column - :row-span new-row-span - :column-span new-column-span)) + target-area (sga/make-area target-cell) - target-area (sga/make-area target-cell)] + ;; Create columns/rows if necessary + parent + (->> (range (count (:layout-grid-columns parent)) + (+ new-column new-column-span -1)) + (reduce (fn [parent _] (add-grid-column parent default-track-value)) parent)) - (if (sga/contains? prev-area target-area) - ;; The new area is smaller than the previous. We need to create cells in the empty space - (let [parent - (-> parent - (assoc-in [:layout-grid-cells (:id target-cell)] target-cell)) + parent + (->> (range (count (:layout-grid-rows parent)) + (+ new-row new-row-span -1)) + (reduce (fn [parent _] (add-grid-row parent default-track-value)) parent)) - new-cells - (->> (sga/difference prev-area target-area) - (mapcat (fn [[column row column-span row-span]] - (for [new-col (range column (+ column column-span)) - new-row (range row (+ row row-span))] - (merge grid-cell-defaults - {:id (uuid/next) - :row new-row - :column new-col - :row-span 1 - :column-span 1}))))) + parent (create-cells parent prev-area) - parent - (->> new-cells - (reduce #(assoc-in %1 [:layout-grid-cells (:id %2)] %2) parent))] + cells (vec (get-cells parent {:sort? true})) + remove-cells + (->> cells + (filter #(and (not= (:id target-cell) (:id %)) + (sga/contains? target-area (sga/make-area %)))) + (into #{})) - parent) + split-cells + (->> cells + (filter #(and (not= (:id target-cell) (:id %)) + (not (contains? remove-cells %)) + (sga/intersects? target-area (sga/make-area %))))) - ;; The new area is bigger we need to remove the cells filled and split the intersections - (let [remove-cells (->> cells - (filter #(and (not= (:id target-cell) (:id %)) - (sga/contains? target-area (sga/make-area %)))) - (into #{})) + [parent _] + (->> (d/enumerate cells) + (reduce (fn [[parent cells] [index cur-cell]] + (if (contains? remove-cells cur-cell) + (let [[parent cells] (free-cell-push parent cells index)] + [parent (conj cells cur-cell)]) + [parent cells])) + [parent cells])) - split-cells (->> cells (filter #(and (not= (:id target-cell) (:id %)) - (not (contains? remove-cells %)) - (sga/intersects? target-area (sga/make-area %))))) + parent + (-> parent + (assoc-in [:layout-grid-cells (:id target-cell)] target-cell)) - [parent _] - (->> (d/enumerate cells) - (reduce (fn [[parent cells] [index cur-cell]] - (if (contains? remove-cells cur-cell) - (let [[parent cells] (free-cell-push parent cells index)] - [parent (conj cells cur-cell)]) - [parent cells])) - [parent cells])) + parent + (->> remove-cells + (reduce (fn [parent cell] + (update parent :layout-grid-cells dissoc (:id cell))) + parent)) - parent - (-> parent - (assoc-in [:layout-grid-cells (:id target-cell)] target-cell)) + parent + (->> split-cells + (reduce (fn [parent cell] + (let [new-areas (sga/difference (sga/make-area cell) target-area)] + (as-> parent $ + (update-in $ [:layout-grid-cells (:id cell)] merge (sga/area->cell-props (first new-areas))) + (reduce (fn [parent area] + (let [cell (merge (assoc grid-cell-defaults :id (uuid/next)) (sga/area->cell-props area))] + (assoc-in parent [:layout-grid-cells (:id cell)] cell))) $ new-areas)))) + parent))] + parent) - parent - (->> remove-cells - (reduce (fn [parent cell] - (update parent :layout-grid-cells dissoc (:id cell))) - parent)) - - parent - (->> split-cells - (reduce (fn [parent cell] - (let [new-areas (sga/difference (sga/make-area cell) target-area)] - (as-> parent $ - (update-in $ [:layout-grid-cells (:id cell)] merge (sga/area->cell-props (first new-areas))) - (reduce (fn [parent area] - (let [cell (merge (assoc grid-cell-defaults :id (uuid/next)) (sga/area->cell-props area))] - (assoc-in parent [:layout-grid-cells (:id cell)] cell))) $ new-areas)))) - parent))] - parent)))) + ;; Not valid resize: we don't alter the layout + parent)) diff --git a/frontend/src/app/main/data/workspace/grid_layout/editor.cljs b/frontend/src/app/main/data/workspace/grid_layout/editor.cljs index ff5c05253f..62a25035c6 100644 --- a/frontend/src/app/main/data/workspace/grid_layout/editor.cljs +++ b/frontend/src/app/main/data/workspace/grid_layout/editor.cljs @@ -61,3 +61,4 @@ srect (gsh/make-selrect x y width height)] (-> local (update :vbox merge (select-keys srect [:x :y :x1 :x2 :y1 :y2]))))))))))) + diff --git a/frontend/src/app/main/data/workspace/shape_layout.cljs b/frontend/src/app/main/data/workspace/shape_layout.cljs index 7deb5385f6..b6672256b2 100644 --- a/frontend/src/app/main/data/workspace/shape_layout.cljs +++ b/frontend/src/app/main/data/workspace/shape_layout.cljs @@ -8,6 +8,7 @@ (:require [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] @@ -52,13 +53,13 @@ :layout-padding-type :simple :layout-padding {:p1 0 :p2 0 :p3 0 :p4 0}}) -(def initial-grid-layout ;; TODO +(def initial-grid-layout {:layout :grid :layout-grid-dir :row :layout-gap-type :multiple :layout-gap {:row-gap 0 :column-gap 0} :layout-align-items :start - :layout-align-content :stretch + :layout-align-content :start :layout-justify-items :start :layout-justify-content :start :layout-padding-type :simple @@ -549,3 +550,44 @@ (dwc/update-shapes parent-ids (partial fix-parent-sizing objects (set ids) changes)) (ptk/data-event :layout/update ids) (dwu/commit-undo-transaction undo-id)))))) + +(defn update-grid-cell + [layout-id cell-id props] + (ptk/reify ::update-grid-cell + ptk/WatchEvent + (watch [_ _ _] + (let [undo-id (js/Symbol)] + (rx/of + (dwu/start-undo-transaction undo-id) + (dwc/update-shapes + [layout-id] + (fn [shape] + (-> shape + (d/update-in-when [:layout-grid-cells cell-id] + #(d/without-nils (merge % props)))))) + (ptk/data-event :layout/update [layout-id]) + (dwu/commit-undo-transaction undo-id)))))) + +(defn update-grid-cell-position + [layout-id cell-id props] + + (ptk/reify ::update-grid-cell-position + ptk/WatchEvent + (watch [_ _ _] + (let [undo-id (js/Symbol)] + (rx/of + (dwu/start-undo-transaction undo-id) + (dwc/update-shapes + [layout-id] + (fn [shape] + (let [prev-data (-> (dm/get-in shape [:layout-grid-cells cell-id]) + (select-keys [:row :column :row-span :column-span])) + + new-data (merge prev-data props)] + (-> shape + (ctl/resize-cell-area (:row prev-data) (:column prev-data) + (:row new-data) (:column new-data) + (:row-span new-data) (:column-span new-data)) + (ctl/assign-cells))))) + (ptk/data-event :layout/update [layout-id]) + (dwu/commit-undo-transaction undo-id)))))) diff --git a/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs b/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs index f0141037c5..26878c5370 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs @@ -151,7 +151,8 @@ (dom/class? node "frame-title") (let [shape (gsh/transform-shape shape modifiers) zoom (get-in @st/state [:workspace-local :zoom] 1) - mtx (vwu/title-transform shape zoom false)] + edit-grid? (= (dom/get-data node "edit-grid") "true") + mtx (vwu/title-transform shape zoom edit-grid?)] (override-transform-att! node "transform" mtx)) (or (= (dom/get-tag-name node) "mask") diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/grid_cell.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/grid_cell.cljs index 1153005950..752eaaea71 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/grid_cell.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/grid_cell.cljs @@ -8,6 +8,8 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] + [app.main.data.workspace.shape-layout :as dwsl] + [app.main.store :as st] [app.main.ui.components.numeric-input :refer [numeric-input]] [app.main.ui.icons :as i] [app.main.ui.workspace.sidebar.options.menus.layout-container :as lyc] @@ -16,7 +18,8 @@ (mf/defc set-self-alignment [{:keys [is-col? alignment set-alignment] :as props}] - (let [dir-v [:auto :start :center :end :stretch #_:baseline]] + (let [dir-v [:auto :start :center :end :stretch #_:baseline] + alignment (or alignment :auto)] [:div.align-self-style (for [align dir-v] [:button.align-self.tooltip.tooltip-bottom @@ -31,51 +34,69 @@ (mf/defc options {::mf/wrap [mf/memo]} - [{:keys [_shape cell] :as props}] + [{:keys [shape cell] :as props}] - (let [position-mode (mf/use-state :auto) ;; TODO this should come from shape + (let [{:keys [mode area-name align-self justify-self column column-span row row-span]} cell + column-end (+ column column-span) + row-end (+ row row-span) - set-position-mode (fn [mode] - (reset! position-mode mode)) + cell-mode (or mode :auto) + cell-mode (if (and (= :auto cell-mode) + (or (> (:column-span cell) 1) + (> (:row-span cell) 1))) + :manual + cell-mode) + set-alignment + (mf/use-callback + (mf/deps align-self (:id shape) (:id cell)) + (fn [value] + (if (= align-self value) + (st/emit! (dwsl/update-grid-cell (:id shape) (:id cell) {:align-self nil})) + (st/emit! (dwsl/update-grid-cell (:id shape) (:id cell) {:align-self value}))))) - align-self (mf/use-state :auto) ;; TODO this should come from shape - justify-alignment (mf/use-state :auto) ;; TODO this should come from shape - set-alignment (fn [value] - (reset! align-self value) - #_(if (= align-self value) - (st/emit! (dwsl/update-layout-child ids {:layout-item-align-self nil})) - (st/emit! (dwsl/update-layout-child ids {:layout-item-align-self value})))) - set-justify-self (fn [value] - (reset! justify-alignment value) - #_(if (= align-self value) - (st/emit! (dwsl/update-layout-child ids {:layout-item-align-self nil})) - (st/emit! (dwsl/update-layout-child ids {:layout-item-align-self value})))) - column-start (:column cell) - column-end (+ (:column cell) (:column-span cell)) - row-start (:row cell) - row-end (+ (:row cell) (:row-span cell)) + set-justify-self + (mf/use-callback + (mf/deps justify-self (:id shape) (:id cell)) + (fn [value] + (if (= justify-self value) + (st/emit! (dwsl/update-grid-cell (:id shape) (:id cell) {:justify-self nil})) + (st/emit! (dwsl/update-grid-cell (:id shape) (:id cell) {:justify-self value}))))) on-change - (fn [_side _orientation _value] - ;; TODO - #_(if (= orientation :column) - (case side - :all ((reset! column-start value) - (reset! column-end value)) - :start (reset! column-start value) - :end (reset! column-end value)) - (case side - :all ((reset! row-start value) - (reset! row-end value)) - :start (reset! row-start value) - :end (reset! row-end value)))) + (mf/use-callback + (mf/deps column row (:id shape) (:id cell)) + (fn [field type value] + (let [[property value] + (cond + (and (= type :column) (or (= field :all) (= field :start))) + [:column value] - area-name (mf/use-state "header") ;; TODO this should come from shape + (and (= type :column) (= field :end)) + [:column-span (max 1 (- value column))] - on-area-name-change (fn [value] - (reset! area-name value)) - on-key-press (fn [_event])] + (and (= type :row) (or (= field :all) (= field :start))) + [:row value] + + (and (= type :row) (= field :end)) + [:row-span (max 1 (- value row))])] + + (st/emit! (dwsl/update-grid-cell-position (:id shape) (:id cell) {property value}))))) + + on-area-name-change + (mf/use-callback + (mf/deps (:id shape) (:id cell)) + (fn [event] + (let [value (dom/get-value (dom/get-target event))] + (if (= value "") + (st/emit! (dwsl/update-grid-cell (:id shape) (:id cell) {:area-name nil})) + (st/emit! (dwsl/update-grid-cell (:id shape) (:id cell) {:area-name value})))))) + + set-cell-mode + (mf/use-callback + (mf/deps (:id shape) (:id cell)) + (fn [mode] + (st/emit! (dwsl/update-grid-cell (:id shape) (:id cell) {:mode mode}))))] [:div.element-set [:div.element-set-title @@ -86,16 +107,17 @@ [:div.row-title.sizing "Position"] [:div.position-wrapper [:button.position-btn - {:on-click #(set-position-mode :auto) - :class (dom/classnames :active (= :auto @position-mode))} "Auto"] + {:on-click #(set-cell-mode :auto) + :class (dom/classnames :active (= :auto cell-mode))} "Auto"] [:button.position-btn - {:on-click #(set-position-mode :manual) - :class (dom/classnames :active (= :manual @position-mode))} "Manual"] + {:on-click #(set-cell-mode :manual) + :class (dom/classnames :active (= :manual cell-mode))} "Manual"] [:button.position-btn - {:on-click #(set-position-mode :area) - :class (dom/classnames :active (= :area @position-mode))} "Area"]]] + {:on-click #(set-cell-mode :area) + :class (dom/classnames :active (= :area cell-mode))} "Area"]]] + [:div.manage-grid-columns - (when (= :auto @position-mode) + (when (= :auto cell-mode) [:div.grid-auto [:div.grid-columns-auto [:span.icon i/layout-rows] @@ -103,42 +125,42 @@ [:> numeric-input {:placeholder "--" :on-click #(dom/select-target %) - :on-change (partial on-change :all :column) ;; TODO cambiar este on-change y el value - :value column-start}]]] + :on-change (partial on-change :all :column) + :value column}]]] [:div.grid-rows-auto [:span.icon i/layout-columns] [:div.input-wrapper [:> numeric-input {:placeholder "--" :on-click #(dom/select-target %) - :on-change (partial on-change :all :row) ;; TODO cambiar este on-change y el value - :value row-start}]]]]) - (when (= :area @position-mode) + :on-change (partial on-change :all :row) + :value row}]]]]) + + (when (= :area cell-mode) [:div.input-wrapper [:input.input-text - {:key "grid-area-name" + {:key (dm/str "name-" (:id cell)) :id "grid-area-name" :type "text" :aria-label "grid-area-name" :placeholder "--" - :default-value @area-name + :default-value area-name :auto-complete "off" - :on-change on-area-name-change - :on-key-press on-key-press}]]) + :on-change on-area-name-change}]]) - (when (or (= :manual @position-mode) (= :area @position-mode)) + (when (or (= :manual cell-mode) (= :area cell-mode)) [:div.grid-manual [:div.grid-columns-auto [:span.icon i/layout-rows] [:div.input-wrapper [:> numeric-input {:placeholder "--" - :on-click #(dom/select-target %) + :on-pointer-down #(dom/select-target %) :on-change (partial on-change :start :column) - :value column-start}] + :value column}] [:> numeric-input {:placeholder "--" - :on-click #(dom/select-target %) + :on-pointer-down #(dom/select-target %) :on-change (partial on-change :end :column) :value column-end}]]] [:div.grid-rows-auto @@ -146,12 +168,12 @@ [:div.input-wrapper [:> numeric-input {:placeholder "--" - :on-click #(dom/select-target %) + :on-pointer-down #(dom/select-target %) :on-change (partial on-change :start :row) - :value row-start}] + :value row}] [:> numeric-input {:placeholder "--" - :on-click #(dom/select-target %) + :on-pointer-down #(dom/select-target %) :on-change (partial on-change :end :row) :value row-end}]]]])] @@ -159,13 +181,11 @@ [:div.row-title "Align"] [:div.btn-wrapper [:& set-self-alignment {:is-col? false - :alignment @align-self + :alignment align-self :set-alignment set-alignment}]]] - - [:div.layout-row [:div.row-title "Justify"] [:div.btn-wrapper [:& set-self-alignment {:is-col? true - :alignment @justify-alignment + :alignment justify-self :set-alignment set-justify-self}]]]]])) diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index cefd87433f..946bf1df06 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -204,8 +204,7 @@ (>= zoom 8)) show-text-editor? (and editing-shape (= :text (:type editing-shape))) - ;; Debug utility - ;; hover-grid? (and (some? @hover-top-frame-id) (ctl/grid-layout? objects @hover-top-frame-id)) + hover-grid? (and (some? @hover-top-frame-id) (ctl/grid-layout? objects @hover-top-frame-id)) show-grid-editor? (and editing-shape (ctl/grid-layout? editing-shape)) show-presence? page-id @@ -591,8 +590,7 @@ :zoom zoom}]) - (when show-grid-editor? - #_(or show-grid-editor? hover-grid?) ;; Debug utility + (when (or show-grid-editor? hover-grid?) [:& grid-layout/editor {:zoom zoom :objects objects-modified 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 af72e959c4..c8143f11c4 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 @@ -572,7 +572,6 @@ :on-key-down handle-keydown-track-input :on-blur handle-blur-track-input}]]] - [:& resize-track-handler {:index index :layout-data layout-data @@ -662,85 +661,85 @@ [:& plus-btn {:start-p start-p :zoom zoom :type :row - :on-click handle-add-row}])]) + :on-click handle-add-row}]) - (for [[idx column-data] (d/enumerate column-tracks)] - [:& track {:key (dm/str "column-track-" idx) - :shape shape - :zoom zoom - :type :column - :index idx - :layout-data layout-data - :snap-pixel? snap-pixel? - :track-data column-data}]) + (for [[idx column-data] (d/enumerate column-tracks)] + [:& track {:key (dm/str "column-track-" idx) + :shape shape + :zoom zoom + :type :column + :index idx + :layout-data layout-data + :snap-pixel? snap-pixel? + :track-data column-data}]) - ;; Last track resize handler - (when-not (empty? column-tracks) - (let [last-track (last column-tracks) - start-p (:start-p (last column-tracks)) - relative (gpt/to-vec origin start-p) - marker-p (-> origin - (gpt/add (hv (:x relative))) - (gpt/subtract (vv (/ 20 zoom))) - (gpt/add (hv (:size last-track))))] - [:g.track - [:& track-marker {:center marker-p - :index (count column-tracks) - :shape shape - :snap-pixel? snap-pixel? - :track-before (last column-tracks) - :type :column - :value (dm/str (inc (count column-tracks))) - :zoom zoom}] - [:& resize-track-handler - {:index (count column-tracks) - :shape shape - :layout-data layout-data - :snap-pixel? snap-pixel? - :start-p (-> start-p (gpt/add (hv (+ layout-gap-col (:size last-track))))) - :type :column - :track-before (last column-tracks) - :zoom zoom}]])) + ;; Last track resize handler + (when-not (empty? column-tracks) + (let [last-track (last column-tracks) + start-p (:start-p (last column-tracks)) + relative (gpt/to-vec origin start-p) + marker-p (-> origin + (gpt/add (hv (:x relative))) + (gpt/subtract (vv (/ 20 zoom))) + (gpt/add (hv (:size last-track))))] + [:g.track + [:& track-marker {:center marker-p + :index (count column-tracks) + :shape shape + :snap-pixel? snap-pixel? + :track-before (last column-tracks) + :type :column + :value (dm/str (inc (count column-tracks))) + :zoom zoom}] + [:& resize-track-handler + {:index (count column-tracks) + :shape shape + :layout-data layout-data + :snap-pixel? snap-pixel? + :start-p (-> start-p (gpt/add (hv (+ layout-gap-col (:size last-track))))) + :type :column + :track-before (last column-tracks) + :zoom zoom}]])) - (for [[idx row-data] (d/enumerate row-tracks)] - [:& track {:index idx - :key (dm/str "row-track-" idx) - :layout-data layout-data - :shape shape - :snap-pixel? snap-pixel? - :track-data row-data - :type :row - :zoom zoom}]) + (for [[idx row-data] (d/enumerate row-tracks)] + [:& track {:index idx + :key (dm/str "row-track-" idx) + :layout-data layout-data + :shape shape + :snap-pixel? snap-pixel? + :track-data row-data + :type :row + :zoom zoom}]) - (when-not (empty? row-tracks) - (let [last-track (last row-tracks) - start-p (:start-p (last row-tracks)) - relative (gpt/to-vec origin start-p) - marker-p - (-> origin - (gpt/add (vv (:y relative))) - (gpt/subtract (hv (/ 20 zoom))) - (gpt/add (vv (:size last-track))))] - [:g.track - [:g {:transform (dm/fmt "rotate(-90 % %)" (:x marker-p) (:y marker-p))} - [:& track-marker {:center marker-p - :index (count row-tracks) - :shape shape - :snap-pixel? snap-pixel? - :track-before (last row-tracks) - :type :row - :value (dm/str (inc (count row-tracks))) - :zoom zoom}]] - [:& resize-track-handler - {:index (count row-tracks) - :shape shape - :layout-data layout-data - :start-p (-> start-p - (gpt/add (vv (+ layout-gap-row (:size last-track))))) - :type :row - :track-before (last row-tracks) - :snap-pixel? snap-pixel? - :zoom zoom}]])) + (when-not (empty? row-tracks) + (let [last-track (last row-tracks) + start-p (:start-p (last row-tracks)) + relative (gpt/to-vec origin start-p) + marker-p + (-> origin + (gpt/add (vv (:y relative))) + (gpt/subtract (hv (/ 20 zoom))) + (gpt/add (vv (:size last-track))))] + [:g.track + [:g {:transform (dm/fmt "rotate(-90 % %)" (:x marker-p) (:y marker-p))} + [:& track-marker {:center marker-p + :index (count row-tracks) + :shape shape + :snap-pixel? snap-pixel? + :track-before (last row-tracks) + :type :row + :value (dm/str (inc (count row-tracks))) + :zoom zoom}]] + [:& resize-track-handler + {:index (count row-tracks) + :shape shape + :layout-data layout-data + :start-p (-> start-p + (gpt/add (vv (+ layout-gap-row (:size last-track))))) + :type :row + :track-before (last row-tracks) + :snap-pixel? snap-pixel? + :zoom zoom}]]))]) (for [[_ cell] (:layout-grid-cells shape)] [:& grid-cell {:key (dm/str "cell-" (:id cell)) diff --git a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs index f498a0273a..3997788958 100644 --- a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs @@ -171,6 +171,7 @@ (when (not (:hidden frame)) [:g.frame-title {:id (dm/str "frame-title-" (:id frame)) + :data-edit-grid grid-edition? :transform (vwu/title-transform frame zoom grid-edition?) :pointer-events (when (:blocked frame) "none")} (cond From c31dc94496d77640ea25db3b671c20663ea0457f Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 11 May 2023 15:41:58 +0200 Subject: [PATCH 12/52] :sparkles: Align items in grid layout --- .../geom/shapes/grid_layout/positions.cljc | 50 ++++++++++++++++++- common/src/app/common/geom/shapes/points.cljc | 21 +++++++- .../options/menus/layout_container.cljs | 2 +- .../sidebar/options/shapes/grid_cell.cljs | 5 +- .../viewport/grid_layout_editor.cljs | 40 +++++++++++++++ 5 files changed, 113 insertions(+), 5 deletions(-) diff --git a/common/src/app/common/geom/shapes/grid_layout/positions.cljc b/common/src/app/common/geom/shapes/grid_layout/positions.cljc index aae5e7e309..e4f480499f 100644 --- a/common/src/app/common/geom/shapes/grid_layout/positions.cljc +++ b/common/src/app/common/geom/shapes/grid_layout/positions.cljc @@ -104,8 +104,54 @@ (defn child-modifiers [parent parent-bounds child child-bounds layout-data cell-data] - (let [fill-modifiers (fill-modifiers parent parent-bounds child child-bounds layout-data cell-data) - position-delta (gpt/subtract (:start-p cell-data) (gpo/origin child-bounds))] + (let [cell-bounds (cell-bounds layout-data cell-data) + + fill-modifiers (fill-modifiers parent parent-bounds child child-bounds layout-data cell-data) + + align (:layout-align-items parent) + justify (:layout-justify-items parent) + align-self (:align-self cell-data) + justify-self (:justify-self cell-data) + + align-self (when (and align-self (not= align-self :auto)) align-self) + justify-self (when (and justify-self (not= justify-self :auto)) justify-self) + + align (or align-self align) + justify (or justify-self justify) + + position-delta (gpt/point) + + ;; Adjust alignment/justify + [from-h to-h] + (case justify + :end + [(gpo/project-point cell-bounds :h (nth child-bounds 1)) + (nth cell-bounds 1)] + + :center + [(gpo/project-point cell-bounds :h (gpo/center child-bounds)) + (gpo/project-point cell-bounds :h (gpo/center cell-bounds))] + + [(gpo/project-point cell-bounds :h (first child-bounds)) + (first cell-bounds)]) + + [from-v to-v] + (case align + :end + [(gpo/project-point cell-bounds :v (nth child-bounds 3)) + (nth cell-bounds 3)] + + :center + [(gpo/project-point cell-bounds :v (gpo/center child-bounds)) + (gpo/project-point cell-bounds :v (gpo/center cell-bounds))] + + [(gpo/project-point cell-bounds :v (first child-bounds)) + (first cell-bounds)]) + + position-delta + (-> position-delta + (gpt/add (gpt/to-vec from-h to-h)) + (gpt/add (gpt/to-vec from-v to-v)))] (-> (ctm/empty) (ctm/add-modifiers fill-modifiers) (ctm/move position-delta)))) diff --git a/common/src/app/common/geom/shapes/points.cljc b/common/src/app/common/geom/shapes/points.cljc index ff6bd15df8..e2403b76b0 100644 --- a/common/src/app/common/geom/shapes/points.cljc +++ b/common/src/app/common/geom/shapes/points.cljc @@ -80,7 +80,7 @@ "Given a point and a line returns the parametric t the cross point with the line going through the other axis projected" [point [start end] other-axis-vec] - (let [line-vec (gpt/to-vec start end) + (let [line-vec (gpt/to-vec start end) pr-point (gsi/line-line-intersect point (gpt/add point other-axis-vec) start end)] (cond (not (mth/almost-zero? (:x line-vec))) @@ -93,6 +93,15 @@ :else 0))) +(defn project-point + "Project the point into the given axis: `:h` or `:v` means horizontal or vertical axis" + [[p0 p1 _ p3 :as bounds] axis point] + (let [[other-vec start end] + (if (= axis :h) + [(gpt/to-vec p0 p3) p0 p1] + [(gpt/to-vec p0 p1) p0 p3])] + (gsi/line-line-intersect point (gpt/add point other-vec) start end))) + (defn parent-coords-bounds [child-bounds [p1 p2 _ p4 :as parent-bounds]] @@ -154,3 +163,13 @@ [bounds vector] (->> bounds (map #(gpt/add % vector)))) + +(defn center + [bounds] + (let [width (width-points bounds) + height (height-points bounds) + half-h (start-hv bounds (/ width 2)) + half-v (start-vv bounds (/ height 2))] + (-> (origin bounds) + (gpt/add half-h) + (gpt/add half-v)))) 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 a4e9979e45..cc61a2c075 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 @@ -390,7 +390,7 @@ [{:keys [is-col? align-items set-align] :as props}] (let [type (if is-col? :column :row)] [:div.align-items-style - (for [align [:start :center :end :stretch :baseline]] + (for [align [:start :center :end]] [:button.align-start.tooltip {:class (dom/classnames :active (= align-items align) :tooltip-bottom-left (not= align :start) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/grid_cell.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/grid_cell.cljs index 752eaaea71..9250d9a391 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/grid_cell.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/grid_cell.cljs @@ -96,7 +96,10 @@ (mf/use-callback (mf/deps (:id shape) (:id cell)) (fn [mode] - (st/emit! (dwsl/update-grid-cell (:id shape) (:id cell) {:mode mode}))))] + (let [props (cond-> {:mode mode} + (not= mode :area) + (assoc :area-name nil))] + (st/emit! (dwsl/update-grid-cell (:id shape) (:id cell) props)))))] [:div.element-set [:div.element-set-title 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 c8143f11c4..1a10b91b10 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 @@ -226,6 +226,40 @@ :on-lost-pointer-capture handle-lost-pointer-capture :on-pointer-move handle-pointer-move}])) +(mf/defc grid-cell-area-label + {::mf/wrap-props false} + [props] + + (let [cell-origin (unchecked-get props "origin") + cell-width (unchecked-get props "width") + zoom (unchecked-get props "zoom") + text (unchecked-get props "text") + + area-width (/ (* 10 (count text)) zoom) + area-height (/ 25 zoom) + area-x (- (+ (:x cell-origin) cell-width) area-width) + area-y (:y cell-origin) + + area-text-x (+ area-x (/ area-width 2)) + area-text-y (+ area-y (/ area-height 2))] + + [:g {:pointer-events "none"} + [:rect {:x area-x + :y area-y + :width area-width + :height area-height + :style {:fill "var(--color-distance)" + :fill-opacity 0.3}}] + [:text {:x area-text-x + :y area-text-y + :style {:fill "var(--color-distance)" + :font-family "worksans" + :font-weight 600 + :font-size (/ 14 zoom) + :alignment-baseline "central" + :text-anchor "middle"}} + text]])) + (mf/defc grid-cell {::mf/wrap-props false} [props] @@ -271,6 +305,12 @@ :on-pointer-leave handle-pointer-leave :on-pointer-down handle-pointer-down}] + (when (:area-name cell) + [:& grid-cell-area-label {:origin cell-origin + :width cell-width + :zoom zoom + :text (:area-name cell)}]) + (when selected? (let [handlers ;; Handlers positions, size and cursor From 1c4678ad5d50611c6c0c36baa3b09de7d6f87e3b Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 12 May 2023 13:01:06 +0200 Subject: [PATCH 13/52] :sparkles: Update grid on child changes --- common/src/app/common/types/shape/layout.cljc | 10 +++++++ frontend/src/app/main/data/workspace.cljs | 10 ++++++- .../app/main/data/workspace/selection.cljs | 20 +++++++++++-- .../sidebar/options/menus/layout_item.cljs | 29 ++++++++++++++----- 4 files changed, 58 insertions(+), 11 deletions(-) diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index 92550c11a0..f04f534208 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -208,6 +208,11 @@ parent (get objects parent-id)] (flex-layout? parent))) +(defn grid-layout-immediate-child? [objects shape] + (let [parent-id (:parent-id shape) + parent (get objects parent-id)] + (grid-layout? parent))) + (defn any-layout-immediate-child? [objects shape] (let [parent-id (:parent-id shape) parent (get objects parent-id)] @@ -218,6 +223,11 @@ parent (get objects parent-id)] (flex-layout? parent))) +(defn grid-layout-immediate-child-id? [objects id] + (let [parent-id (dm/get-in objects [id :parent-id]) + parent (get objects parent-id)] + (grid-layout? parent))) + (defn any-layout-immediate-child-id? [objects id] (let [parent-id (dm/get-in objects [id :parent-id]) parent (get objects parent-id)] diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 7043327671..05e11c3be6 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -783,6 +783,12 @@ (assoc :layout-item-v-sizing :fix)) parent))) + ;; Update grid layout + (pcb/update-shapes parents + (fn [parent] + (cond-> parent + (ctl/grid-layout? parent) + (ctl/assign-cells)))) ;; Resize parent containers that need to (pcb/resize-parents parents)))) @@ -1800,7 +1806,9 @@ changes (-> (dws/prepare-duplicate-changes all-objects page selected delta it libraries library-data file-id) (pcb/amend-changes (partial process-rchange media-idx)) - (pcb/amend-changes (partial change-add-obj-index paste-objects selected index))) + (pcb/amend-changes (partial change-add-obj-index paste-objects selected index)) + (cond-> (ctl/grid-layout? all-objects parent-id) + (pcb/update-shapes [parent-id] ctl/assign-cells))) ;; Adds a resize-parents operation so the groups are updated. We add all the new objects new-objects-ids (->> changes :redo-changes (filter #(= (:type %) :add-obj)) (mapv :id)) diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index 9bab204bf7..ec88cd88c8 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -18,6 +18,7 @@ [app.common.types.file :as ctf] [app.common.types.page :as ctp] [app.common.types.shape.interactions :as ctsi] + [app.common.types.shape.layout :as ctl] [app.common.uuid :as uuid] [app.main.data.modal :as md] [app.main.data.workspace.changes :as dch] @@ -324,6 +325,16 @@ (when selected (rx/of (select-shape (:id selected)))))))) +(defn remap-grid-cells + "Remaps the shapes inside the cells" + [shape ids-map] + + (let [do-remap-cells + (fn [cell] + (-> cell + (update :shapes #(mapv ids-map %))))] + + (update shape :layout-grid-cells update-vals do-remap-cells))) ;; --- Duplicate Shapes (declare prepare-duplicate-shape-change) @@ -431,10 +442,15 @@ :shape-ref :use-for-thumbnail?) (gsh/move delta) - (d/update-when :interactions #(ctsi/remap-interactions % ids-map objects))) + (d/update-when :interactions #(ctsi/remap-interactions % ids-map objects)) + + (cond-> (ctl/grid-layout? obj) + (remap-grid-cells ids-map))) changes (-> (pcb/add-object changes new-obj) - (pcb/amend-last-change #(assoc % :old-id (:id obj)))) + (pcb/amend-last-change #(assoc % :old-id (:id obj))) + (cond-> (ctl/grid-layout? objects (:parent-id obj)) + (pcb/update-shapes [(:parent-id obj)] ctl/assign-cells))) changes (cond-> changes (and is-component-root? is-component-main?) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs index e4885697ad..574435dd23 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs @@ -178,7 +178,7 @@ (mf/defc layout-item-menu {::mf/wrap [#(mf/memo' % (mf/check-props ["ids" "values" "type" "is-layout-child?"]))]} - [{:keys [ids values is-layout-child? is-layout-container?] :as props}] + [{:keys [ids values is-layout-child? is-layout-container? is-grid-parent? is-flex-parent?] :as props}] (let [selection-parents-ref (mf/use-memo (mf/deps ids) #(refs/parents-by-ids ids)) selection-parents (mf/deref selection-parents-ref) @@ -188,11 +188,15 @@ (st/emit! (dwsl/update-layout-child ids {:layout-item-margin-type type}))) align-self (:layout-item-align-self values) - set-align-self (fn [value] - (if (= align-self value) - (st/emit! (dwsl/update-layout-child ids {:layout-item-align-self nil})) - (st/emit! (dwsl/update-layout-child ids {:layout-item-align-self value})))) - + + set-align-self + (mf/use-callback + (mf/deps ids align-self) + (fn [value] + (if (= align-self value) + (st/emit! (dwsl/update-layout-child ids {:layout-item-align-self nil})) + (st/emit! (dwsl/update-layout-child ids {:layout-item-align-self value}))))) + is-absolute? (:layout-item-absolute values) is-col? (every? ctl/col? selection-parents) @@ -231,9 +235,18 @@ [:div.element-set [:div.element-set-title - [:span (if (and is-layout-container? (not is-layout-child?)) + [:span (cond + (and is-layout-container? (not is-layout-child?)) "Flex board" - "Flex element")]] + + is-flex-parent? + "Flex element" + + is-grid-parent? + "Grid element" + + :else + "Layout element")]] [:div.element-set-content.layout-item-menu (when is-layout-child? From 8671e9cf8a8b0b763545c2c81e7e629286792c8a Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 12 May 2023 13:25:20 +0200 Subject: [PATCH 14/52] :sparkles: Child element options --- frontend/src/app/main/refs.cljs | 20 ++++++++++++++++++- .../sidebar/options/menus/layout_item.cljs | 4 ++-- .../sidebar/options/shapes/bool.cljs | 8 ++++++++ .../sidebar/options/shapes/circle.cljs | 9 +++++++++ .../sidebar/options/shapes/frame.cljs | 9 +++++++++ .../sidebar/options/shapes/group.cljs | 9 +++++++++ .../sidebar/options/shapes/image.cljs | 9 +++++++++ .../sidebar/options/shapes/multiple.cljs | 8 ++++++++ .../sidebar/options/shapes/path.cljs | 9 +++++++++ .../sidebar/options/shapes/rect.cljs | 9 +++++++++ .../sidebar/options/shapes/svg_raw.cljs | 9 +++++++++ .../sidebar/options/shapes/text.cljs | 9 +++++++++ 12 files changed, 109 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 51a90a6386..0a08096a5d 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -501,7 +501,25 @@ (->> ids (map (d/getf objects)) (every? (partial ctl/any-layout-immediate-child? objects)))) - workspace-page-objects)) + workspace-page-objects =)) + +(defn flex-layout-child? + [ids] + (l/derived + (fn [objects] + (->> ids + (map (d/getf objects)) + (every? (partial ctl/flex-layout-immediate-child? objects)))) + workspace-page-objects =)) + +(defn grid-layout-child? + [ids] + (l/derived + (fn [objects] + (->> ids + (map (d/getf objects)) + (every? (partial ctl/grid-layout-immediate-child? objects)))) + workspace-page-objects =)) (defn get-flex-child-viewer [ids page-id] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs index 574435dd23..0b0fa29d70 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs @@ -177,7 +177,7 @@ (get-layout-flex-icon :align-self align is-col?)])])) (mf/defc layout-item-menu - {::mf/wrap [#(mf/memo' % (mf/check-props ["ids" "values" "type" "is-layout-child?"]))]} + {::mf/wrap [#(mf/memo' % (mf/check-props ["ids" "values" "type" "is-layout-child?" "is-grid-parent?" "is-flex-parent?"]))]} [{:keys [ids values is-layout-child? is-layout-container? is-grid-parent? is-flex-parent?] :as props}] (let [selection-parents-ref (mf/use-memo (mf/deps ids) #(refs/parents-by-ids ids)) @@ -285,7 +285,7 @@ :layout-item-h-sizing (or (:layout-item-h-sizing values) :fix) :on-change-behavior on-change-behavior}]] - (when is-layout-child? + (when (and is-layout-child? is-flex-parent?) [:div.layout-row [:div.row-title "Align"] [:div.btn-wrapper diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs index 9803d57c67..7cc2d18287 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs @@ -33,6 +33,12 @@ is-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-layout-child? ids)) is-layout-child? (mf/deref is-layout-child-ref) + is-flex-parent-ref (mf/use-memo (mf/deps ids) #(refs/flex-layout-child? ids)) + is-flex-parent? (mf/deref is-flex-parent-ref) + + is-grid-parent-ref (mf/use-memo (mf/deps ids) #(refs/grid-layout-child? ids)) + is-grid-parent? (mf/deref is-grid-parent-ref) + is-layout-child-absolute? (ctl/layout-absolute? shape)] [:* [:& measures-menu {:ids ids @@ -47,6 +53,8 @@ :type type :values layout-item-values :is-layout-child? true + :is-flex-parent? is-flex-parent? + :is-grid-parent? is-grid-parent? :shape shape}]) (when (or (not is-layout-child?) is-layout-child-absolute?) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs index 0820c9be81..75b809ab21 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs @@ -34,6 +34,13 @@ is-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-layout-child? ids)) is-layout-child? (mf/deref is-layout-child-ref) + + is-flex-parent-ref (mf/use-memo (mf/deps ids) #(refs/flex-layout-child? ids)) + is-flex-parent? (mf/deref is-flex-parent-ref) + + is-grid-parent-ref (mf/use-memo (mf/deps ids) #(refs/grid-layout-child? ids)) + is-grid-parent? (mf/deref is-grid-parent-ref) + is-layout-child-absolute? (ctl/layout-absolute? shape)] [:* [:& measures-menu {:ids ids @@ -48,6 +55,8 @@ :values layout-item-values :is-layout-child? true :is-layout-container? false + :is-flex-parent? is-flex-parent? + :is-grid-parent? is-grid-parent? :shape shape}]) (when (or (not is-layout-child?) is-layout-child-absolute?) [:& constraints-menu {:ids ids diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs index 21a9ae46cb..53e80ca57d 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs @@ -38,6 +38,13 @@ is-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-layout-child? ids)) is-layout-child? (mf/deref is-layout-child-ref) + + is-flex-parent-ref (mf/use-memo (mf/deps ids) #(refs/flex-layout-child? ids)) + is-flex-parent? (mf/deref is-flex-parent-ref) + + is-grid-parent-ref (mf/use-memo (mf/deps ids) #(refs/grid-layout-child? ids)) + is-grid-parent? (mf/deref is-grid-parent-ref) + is-flex-layout-container? (ctl/flex-layout? shape) is-layout-child-absolute? (ctl/layout-absolute? shape)] [:* @@ -58,6 +65,8 @@ {:ids ids :type type :values layout-item-values + :is-flex-parent? is-flex-parent? + :is-grid-parent? is-grid-parent? :is-layout-child? is-layout-child? :is-layout-container? is-flex-layout-container? :shape shape}]) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs index 842f554811..b7ce7775ef 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs @@ -38,6 +38,13 @@ ids [(:id shape)] is-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-layout-child? ids)) is-layout-child? (mf/deref is-layout-child-ref) + + is-flex-parent-ref (mf/use-memo (mf/deps ids) #(refs/flex-layout-child? ids)) + is-flex-parent? (mf/deref is-flex-parent-ref) + + is-grid-parent-ref (mf/use-memo (mf/deps ids) #(refs/grid-layout-child? ids)) + is-grid-parent? (mf/deref is-grid-parent-ref) + is-layout-child-absolute? (ctl/layout-absolute? shape) type :group @@ -64,6 +71,8 @@ :ids layout-item-ids :is-layout-child? true :is-layout-container? false + :is-flex-parent? is-flex-parent? + :is-grid-parent? is-grid-parent? :values layout-item-values}]) (when (or (not is-layout-child?) is-layout-child-absolute?) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/image.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/image.cljs index 0f3bf75fb1..42173c1c98 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/image.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/image.cljs @@ -34,6 +34,13 @@ is-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-layout-child? ids)) is-layout-child? (mf/deref is-layout-child-ref) + + is-flex-parent-ref (mf/use-memo (mf/deps ids) #(refs/flex-layout-child? ids)) + is-flex-parent? (mf/deref is-flex-parent-ref) + + is-grid-parent-ref (mf/use-memo (mf/deps ids) #(refs/grid-layout-child? ids)) + is-grid-parent? (mf/deref is-grid-parent-ref) + is-layout-child-absolute? (ctl/layout-absolute? shape)] [:* [:& measures-menu {:ids ids @@ -48,6 +55,8 @@ :type type :values layout-item-values :is-layout-child? true + :is-flex-parent? is-flex-parent? + :is-grid-parent? is-grid-parent? :shape shape}]) (when (or (not is-layout-child?) is-layout-child-absolute?) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs index 179474d1a6..30943b7111 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs @@ -297,6 +297,12 @@ is-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-layout-child? ids)) is-layout-child? (mf/deref is-layout-child-ref) + is-flex-parent-ref (mf/use-memo (mf/deps ids) #(refs/flex-layout-child? ids)) + is-flex-parent? (mf/deref is-flex-parent-ref) + + is-grid-parent-ref (mf/use-memo (mf/deps ids) #(refs/grid-layout-child? ids)) + is-grid-parent? (mf/deref is-grid-parent-ref) + has-text? (contains? all-types :text) has-flex-layout-container? (->> shapes (some ctl/flex-layout?)) @@ -348,6 +354,8 @@ :ids layout-item-ids :is-layout-child? all-layout-child? :is-layout-container? all-flex-layout-container? + :is-flex-parent? is-flex-parent? + :is-grid-parent? is-grid-parent? :values layout-item-values}]) (when-not (or (empty? constraint-ids) is-layout-child?) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs index 28494c225e..ec97ec6eb8 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs @@ -34,6 +34,13 @@ is-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-layout-child? ids)) is-layout-child? (mf/deref is-layout-child-ref) + + is-flex-parent-ref (mf/use-memo (mf/deps ids) #(refs/flex-layout-child? ids)) + is-flex-parent? (mf/deref is-flex-parent-ref) + + is-grid-parent-ref (mf/use-memo (mf/deps ids) #(refs/grid-layout-child? ids)) + is-grid-parent? (mf/deref is-grid-parent-ref) + is-layout-child-absolute? (ctl/layout-absolute? shape)] [:* [:& measures-menu {:ids ids @@ -48,6 +55,8 @@ :values layout-item-values :is-layout-child? true :is-layout-container? false + :is-flex-parent? is-flex-parent? + :is-grid-parent? is-grid-parent? :shape shape}]) (when (or (not is-layout-child?) is-layout-child-absolute?) [:& constraints-menu {:ids ids diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs index 5ee633fcf0..ee63fe3b8f 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs @@ -32,8 +32,15 @@ stroke-values (select-keys shape stroke-attrs) layout-item-values (select-keys shape layout-item-attrs) layout-container-values (select-keys shape layout-container-flex-attrs) + is-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-layout-child? ids)) is-layout-child? (mf/deref is-layout-child-ref) + + is-flex-parent-ref (mf/use-memo (mf/deps ids) #(refs/flex-layout-child? ids)) + is-flex-parent? (mf/deref is-flex-parent-ref) + + is-grid-parent-ref (mf/use-memo (mf/deps ids) #(refs/grid-layout-child? ids)) + is-grid-parent? (mf/deref is-grid-parent-ref) is-layout-child-absolute? (ctl/layout-absolute? shape)] [:* [:& measures-menu {:ids ids @@ -47,6 +54,8 @@ :type type :values layout-item-values :is-layout-child? true + :is-flex-parent? is-flex-parent? + :is-grid-parent? is-grid-parent? :shape shape}]) (when (or (not is-layout-child?) is-layout-child-absolute?) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs index a8ec66fb9e..2d1aa06fb4 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs @@ -108,6 +108,13 @@ is-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-layout-child? ids)) is-layout-child? (mf/deref is-layout-child-ref) + + is-flex-parent-ref (mf/use-memo (mf/deps ids) #(refs/flex-layout-child? ids)) + is-flex-parent? (mf/deref is-flex-parent-ref) + + is-grid-parent-ref (mf/use-memo (mf/deps ids) #(refs/grid-layout-child? ids)) + is-grid-parent? (mf/deref is-grid-parent-ref) + is-layout-child-absolute? (ctl/layout-absolute? shape)] (when (contains? svg-elements tag) @@ -124,6 +131,8 @@ :type type :values layout-item-values :is-layout-child? true + :is-flex-parent? is-flex-parent? + :is-grid-parent? is-grid-parent? :shape shape}]) (when (or (not is-layout-child?) is-layout-child-absolute?) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs index 6bfcb5dda5..0e8c82c030 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs @@ -30,6 +30,13 @@ is-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-layout-child? ids)) is-layout-child? (mf/deref is-layout-child-ref) + + is-flex-parent-ref (mf/use-memo (mf/deps ids) #(refs/flex-layout-child? ids)) + is-flex-parent? (mf/deref is-flex-parent-ref) + + is-grid-parent-ref (mf/use-memo (mf/deps ids) #(refs/grid-layout-child? ids)) + is-grid-parent? (mf/deref is-grid-parent-ref) + layout-container-values (select-keys shape layout-container-flex-attrs) is-layout-child-absolute? (ctl/layout-absolute? shape) state-map (mf/deref refs/workspace-editor-state) @@ -82,6 +89,8 @@ :type type :values layout-item-values :is-layout-child? true + :is-flex-parent? is-flex-parent? + :is-grid-parent? is-grid-parent? :shape shape}]) (when (or (not is-layout-child?) is-layout-child-absolute?) From 2177b7ae1355d1c698eadcebb97755a66e6e34ed Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 17 May 2023 09:48:22 +0200 Subject: [PATCH 15/52] :sparkles: Improved auto/flex size assignment --- .../geom/shapes/grid_layout/layout_data.cljc | 109 ++++++++++++------ .../geom/shapes/grid_layout/positions.cljc | 2 + common/src/app/common/types/shape/layout.cljc | 12 +- .../src/app/main/data/workspace/shapes.cljs | 5 +- 4 files changed, 86 insertions(+), 42 deletions(-) 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 53663bbef0..2df3982f35 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 @@ -61,6 +61,18 @@ (let [[pad-top pad-right pad-bottom pad-left] (ctl/paddings parent)] (gpo/pad-points shape-bounds pad-top pad-right pad-bottom pad-left))) +(defn child-min-width + [child bounds] + (if (ctl/fill-width? child) + (ctl/child-min-width child) + (gpo/width-points bounds))) + +(defn child-min-height + [child bounds] + (if (ctl/fill-height? child) + (ctl/child-min-height child) + (gpo/height-points bounds))) + (defn calculate-initial-track-size [total-value {:keys [type value] :as track}] @@ -82,8 +94,8 @@ (let [[prop prop-span size-fn] (if (= type :column) - [:column :column-span gpo/width-points] - [:row :row-span gpo/height-points])] + [:column :column-span child-min-width] + [:row :row-span child-min-height])] (reduce (fn [tracks [child-bounds child-shape]] (let [cell (get shape-cells (:id child-shape)) @@ -91,7 +103,7 @@ track (get tracks idx)] (cond-> tracks (and (= (get cell prop-span) 1) (= :auto (:type track))) - (update-in [idx :size] max (size-fn child-bounds))))) + (update-in [idx :size] max (size-fn child-shape child-bounds))))) track-list children))) @@ -135,17 +147,28 @@ (= :auto type) (assoc :size (min (+ size add-size) max-size))))))) +(defn has-flex-track? + [type track-list cell] + (let [[prop prop-span] + (if (= type :column) + [:column :column-span] + [:row :row-span]) + from-idx (dec (get cell prop)) + to-idx (+ (dec (get cell prop)) (get cell prop-span)) + tracks (subvec track-list from-idx to-idx)] + (some? (->> tracks (d/seek #(= :flex (:type %))))))) + (defn size-to-allocate - [type parent [child-bounds _] cell] + [type parent [child-bounds child] cell] (let [[row-gap column-gap] (ctl/gaps parent) [sfn gap prop-span] (if (= type :column) - [gpo/width-points column-gap :column-span ] - [gpo/height-points row-gap :row-span ]) + [child-min-width column-gap :column-span] + [child-min-height row-gap :row-span]) span (get cell prop-span)] - (- (sfn child-bounds) (* gap (dec span))))) + (- (sfn child child-bounds) (* gap (dec span))))) -(defn allocate-size +(defn allocate-auto-tracks [allocations indexed-tracks to-allocate] (if (empty? indexed-tracks) allocations @@ -158,11 +181,13 @@ (/ to-allocate (count indexed-tracks)) (:size track)) (:size track))] - (recur (cond-> allocations auto-track? - (assoc idx allocated)) - (rest indexed-tracks) (- to-allocate allocated))))) + (recur (cond-> allocations + auto-track? + (assoc idx allocated)) + (rest indexed-tracks) + (- to-allocate allocated))))) -(defn allocate-flex +(defn allocate-flex-tracks [allocations indexed-tracks to-allocate fr-value] (if (empty? indexed-tracks) allocations @@ -196,29 +221,40 @@ [:row :row-span]) ;; First calculate allocation without applying so we can modify them on the following tracks - allocate-auto-tracks + allocated (->> shape-cells (vals) (filter #(> (get % prop-span) 1)) + (remove #(has-flex-track? type track-list %)) (sort-by prop-span -) (reduce - (fn [alloc cell] + (fn [allocated cell] (let [shape-id (first (:shapes cell)) + from-idx (dec (get cell prop)) to-idx (+ (dec (get cell prop)) (get cell prop-span)) + indexed-tracks (subvec (d/enumerate track-list) from-idx to-idx) - has-flex? (->> indexed-tracks (d/seek #(= :flex (:type (second %)))) some?) - to-allocate (size-to-allocate type parent (get children-map shape-id) cell)] - (cond-> alloc - ;; skip cells with flex tracks - (and (not has-flex?) (some? to-allocate)) - (allocate-size indexed-tracks to-allocate)))) + to-allocate (size-to-allocate type parent (get children-map shape-id) cell) + + ;; Remove the size and the tracks that are not allocated + [to-allocate indexed-tracks] + (->> indexed-tracks + (reduce (fn find-auto-allocations + [[to-allocate result] [_ track :as idx-track]] + (if (= :auto (:type track)) + ;; If auto, we don't change allocate and add the track + [to-allocate (conj result idx-track)] + ;; If fixed, we remove from allocate and don't add the track + [(- to-allocate (:size track)) result])) + [to-allocate []]))] + (allocate-auto-tracks allocated indexed-tracks (max to-allocate 0)))) {})) ;; Apply the allocations to the tracks track-list (into [] - (map-indexed #(update %2 :size max (get allocate-auto-tracks %1))) + (map-indexed #(update %2 :size max (get allocated %1))) track-list)] track-list)) @@ -235,6 +271,7 @@ (->> shape-cells (vals) (filter #(> (get % prop-span) 1)) + (filter #(has-flex-track? type track-list %)) (sort-by prop-span -) (reduce (fn [alloc cell] @@ -242,20 +279,25 @@ from-idx (dec (get cell prop)) to-idx (+ (dec (get cell prop)) (get cell prop-span)) indexed-tracks (subvec (d/enumerate track-list) from-idx to-idx) - has-flex? (->> indexed-tracks (d/seek #(= :auto (:type (second %)))) some?) - total-frs (->> indexed-tracks (reduce (fn [tot [_ {:keys [type value]}]] - (cond-> tot - (= type :flex) - (+ value))) - 0)) - to-allocate (size-to-allocate type parent (get children-map shape-id) cell) - fr-value (when (some? to-allocate) (/ to-allocate total-frs))] - (cond-> alloc - ;; skip cells with no flex tracks - (and has-flex? (some? to-allocate)) - (allocate-flex indexed-tracks to-allocate fr-value)))) + + ;; Remove the size and the tracks that are not allocated + [to-allocate total-frs indexed-tracks] + (->> indexed-tracks + (reduce (fn find-lex-allocations + [[to-allocate total-fr result] [_ track :as idx-track]] + (if (= :flex (:type track)) + ;; If flex, we don't change allocate and add the track + [to-allocate (+ total-fr (:value track)) (conj result idx-track)] + + ;; If fixed or auto, we remove from allocate and don't add the track + [(- to-allocate (:size track)) total-fr result])) + [to-allocate 0 []])) + + to-allocate (max to-allocate 0) + fr-value (/ to-allocate total-frs)] + (allocate-flex-tracks alloc indexed-tracks to-allocate fr-value))) {})) ;; Apply the allocations to the tracks @@ -305,7 +347,6 @@ column-tracks (set-auto-base-size column-tracks children shape-cells :column) row-tracks (set-auto-base-size row-tracks children shape-cells :row) - ;; Adjust multi-spaned cells with no flex columns column-tracks (set-auto-multi-span parent column-tracks children-map shape-cells :column) row-tracks (set-auto-multi-span parent row-tracks children-map shape-cells :row) diff --git a/common/src/app/common/geom/shapes/grid_layout/positions.cljc b/common/src/app/common/geom/shapes/grid_layout/positions.cljc index e4f480499f..58362a6b5e 100644 --- a/common/src/app/common/geom/shapes/grid_layout/positions.cljc +++ b/common/src/app/common/geom/shapes/grid_layout/positions.cljc @@ -49,6 +49,7 @@ (as-> p1 $ (reduce (fn [p track] (gpt/add p (vv (:size track)))) $ span-row-tracks) (gpt/add $ (vv (* row-gap (dec (count span-row-tracks))))))] + [p1 p2 p3 p4])) (defn calc-fill-width-data @@ -152,6 +153,7 @@ (-> position-delta (gpt/add (gpt/to-vec from-h to-h)) (gpt/add (gpt/to-vec from-v to-v)))] + (-> (ctm/empty) (ctm/add-modifiers fill-modifiers) (ctm/move position-delta)))) diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index f04f534208..3db0ccedad 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -320,28 +320,28 @@ [child] (if (and (fill-width? child) (some? (:layout-item-min-w child))) - (max 0 (:layout-item-min-w child)) - 0)) + (max 0.01 (:layout-item-min-w child)) + 0.01)) (defn child-max-width [child] (if (and (fill-width? child) (some? (:layout-item-max-w child))) - (max 0 (:layout-item-max-w child)) + (max 0.01 (:layout-item-max-w child)) ##Inf)) (defn child-min-height [child] (if (and (fill-height? child) (some? (:layout-item-min-h child))) - (max 0 (:layout-item-min-h child)) - 0)) + (max 0.01 (:layout-item-min-h child)) + 0.01)) (defn child-max-height [child] (if (and (fill-height? child) (some? (:layout-item-max-h child))) - (max 0 (:layout-item-max-h child)) + (max 0.01 (:layout-item-max-h child)) ##Inf)) (defn child-margins diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs index e8d04ff940..719ba9abb7 100644 --- a/frontend/src/app/main/data/workspace/shapes.cljs +++ b/frontend/src/app/main/data/workspace/shapes.cljs @@ -357,8 +357,9 @@ (rx/of (dwu/start-undo-transaction undo-id) (dc/detach-comment-thread ids) (dch/commit-changes changes) - (ptk/data-event :layout/update all-parents) - (dwu/commit-undo-transaction undo-id)))) + (dwu/commit-undo-transaction undo-id) + (ptk/data-event :layout/update all-parents)))) + (defn create-and-add-shape [type frame-x frame-y data] From 117a8d09d3784ed29bdf7e62191b434f1e0b02f9 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 17 May 2023 12:16:13 +0200 Subject: [PATCH 16/52] :sparkles: Add space-between/space-around/space evenly to grids --- .../geom/shapes/grid_layout/layout_data.cljc | 41 ++++++++++++++++++- .../options/menus/layout_container.cljs | 30 +++++++++----- 2 files changed, 60 insertions(+), 11 deletions(-) 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 2df3982f35..a5338211a7 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 @@ -393,6 +393,32 @@ column-total-size (tracks-total-size column-tracks) row-total-size (tracks-total-size row-tracks) + column-gap + (case (:layout-align-content parent) + :space-evenly + (max column-gap (/ (- bound-width column-total-size) (inc (count column-tracks)))) + + :space-around + (max column-gap (/ (- bound-width column-total-size) (count column-tracks))) + + :space-between + (max column-gap (/ (- bound-width column-total-size) (dec (count column-tracks)))) + + column-gap) + + row-gap + (case (:layout-justify-content parent) + :space-evenly + (max row-gap (/ (- bound-height row-total-size) (inc (count row-tracks)))) + + :space-around + (max row-gap (/ (- bound-height row-total-size) (count row-tracks))) + + :space-between + (max row-gap (/ (- bound-height row-total-size) (dec (count row-tracks)))) + + row-gap) + start-p (cond-> bound-corner (= :end (:layout-align-content parent)) @@ -405,7 +431,20 @@ (gpt/add (vv (- bound-height (+ row-total-size row-total-gap)))) (= :center (:layout-justify-content parent)) - (gpt/add (vv (/ (- bound-height (+ row-total-size row-total-gap)) 2)))) + (gpt/add (vv (/ (- bound-height (+ row-total-size row-total-gap)) 2))) + + + (= :space-around (:layout-align-content parent)) + (gpt/add (hv (/ column-gap 2))) + + (= :space-evenly (:layout-align-content parent)) + (gpt/add (hv column-gap)) + + (= :space-around (:layout-justify-content parent)) + (gpt/add (vv (/ row-gap 2))) + + (= :space-evenly (:layout-justify-content parent)) + (gpt/add (vv row-gap))) column-tracks (->> column-tracks 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 cc61a2c075..c4ef62818e 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 @@ -129,13 +129,16 @@ :end i/grid-justify-content-column-end :center i/grid-justify-content-column-center :space-around i/grid-justify-content-column-around - :space-between i/grid-justify-content-column-between) + :space-between i/grid-justify-content-column-between + :space-evenly i/grid-justify-content-column-between) + (case val :start i/grid-justify-content-row-start :end i/grid-justify-content-row-end :center i/grid-justify-content-row-center :space-around i/grid-justify-content-row-around - :space-between i/grid-justify-content-row-between)))) + :space-between i/grid-justify-content-row-between + :space-evenly i/grid-justify-content-row-between)))) (mf/defc direction-btn [{:keys [dir saved-dir set-direction icon?] :as props}] @@ -404,7 +407,7 @@ [{:keys [is-col? justify-items set-justify] :as props}] (let [type (if is-col? :column :row)] [:div.justify-content-style - (for [align [:start :center :end :space-around :space-between]] + (for [align [:start :center :end :space-around :space-between :space-evenly]] [:button.align-start.tooltip {:class (dom/classnames :active (= justify-items align) :tooltip-bottom-left (not= align :start) @@ -457,14 +460,15 @@ [:> numeric-input {:no-validate true :value (:value column) :on-change #(set-column-value type index %) - :placeholder "--"}]] + :placeholder "--" + :disabled (= :auto (:type column))}]] [:div.grid-column-unit [:& select {:class "grid-column-unit-selector" :default-value (:type column) - :options [{:value :flex :label "fr"} - {:value :auto :label "auto"} - {:value :fixed :label "px"} + :options [{:value :flex :label "FR"} + {:value :auto :label "AUTO"} + {:value :fixed :label "PX"} {:value :percent :label "%"}] :on-change #(set-column-type type index %)}]] [:button.remove-grid-column @@ -472,8 +476,8 @@ i/minus]])])])) (mf/defc layout-container-menu - {::mf/wrap [#(mf/memo' % (mf/check-props ["ids" "values" "type" "multiple"]))]} - [{:keys [ids _type values multiple] :as props}] + {::mf/wrap [#(mf/memo' % (mf/check-props ["ids" "values" "multiple"]))]} + [{:keys [ids values multiple] :as props}] (let [open? (mf/use-state false) ;; Display @@ -630,7 +634,13 @@ (mf/use-callback (mf/deps ids) (fn [type index track-type] - (st/emit! (dwsl/change-layout-track ids type index {:type track-type}))))] + (let [value (case track-type + :auto nil + :flex 1 + :percent 20 + :fixed 100)] + (st/emit! (dwsl/change-layout-track ids type index {:value value + :type track-type})))))] [:div.element-set [:div.element-set-title From c9b932f9549f052a3dfb49e138bf3c5695472fbc Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 17 May 2023 13:45:14 +0200 Subject: [PATCH 17/52] :sparkles: Position absolute in grid layout --- .../app/common/geom/shapes/grid_layout/layout_data.cljc | 1 + .../src/app/common/geom/shapes/grid_layout/positions.cljc | 7 ++++--- common/src/app/common/geom/shapes/modifiers.cljc | 4 ++-- frontend/src/app/main/data/workspace/modifiers.cljs | 1 + 4 files changed, 8 insertions(+), 5 deletions(-) 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 a5338211a7..a1fc3072ef 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 @@ -329,6 +329,7 @@ (->> (:shapes cell) (map #(vector % cell))))) (:layout-grid-cells parent)) + children (->> children (remove #(ctl/layout-absolute? (second %)))) children-map (into {} (map #(vector (:id (second %)) %)) diff --git a/common/src/app/common/geom/shapes/grid_layout/positions.cljc b/common/src/app/common/geom/shapes/grid_layout/positions.cljc index 58362a6b5e..5e7c3454c1 100644 --- a/common/src/app/common/geom/shapes/grid_layout/positions.cljc +++ b/common/src/app/common/geom/shapes/grid_layout/positions.cljc @@ -154,9 +154,10 @@ (gpt/add (gpt/to-vec from-h to-h)) (gpt/add (gpt/to-vec from-v to-v)))] - (-> (ctm/empty) - (ctm/add-modifiers fill-modifiers) - (ctm/move position-delta)))) + (cond-> (ctm/empty) + (not (ctl/layout-absolute? child)) + (-> (ctm/add-modifiers fill-modifiers) + (ctm/move position-delta))))) (defn line-value diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index 9d3df9ed40..4bf1974e5d 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -300,13 +300,13 @@ transformed-parent-bounds (delay (gtr/transform-bounds @(get bounds parent-id) modifiers)) children-modifiers - (if flex-layout? + (if (or flex-layout? grid-layout?) (->> (:shapes parent) (filter #(ctl/layout-absolute? objects %))) (:shapes parent)) children-layout - (when flex-layout? + (when (or flex-layout? grid-layout?) (->> (:shapes parent) (remove #(ctl/layout-absolute? objects %))))] diff --git a/frontend/src/app/main/data/workspace/modifiers.cljs b/frontend/src/app/main/data/workspace/modifiers.cljs index bf9d8c7285..4428173b2d 100644 --- a/frontend/src/app/main/data/workspace/modifiers.cljs +++ b/frontend/src/app/main/data/workspace/modifiers.cljs @@ -184,6 +184,7 @@ (update :shapes #(d/removev selected %)) (ctl/assign-cells)) + selected (->> selected (remove #(ctl/layout-absolute? objects %))) frame (-> frame (update :shapes d/concat-vec selected) (cond-> (some? cell) From 7e7b642e20a3536522f39da9a11616992c224ae6 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 18 May 2023 11:01:02 +0200 Subject: [PATCH 18/52] :sparkles: Move objects in grid with keys --- common/src/app/common/types/shape/layout.cljc | 32 +++++- .../app/main/data/workspace/transforms.cljs | 98 +++++++++++-------- 2 files changed, 86 insertions(+), 44 deletions(-) diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index 3db0ccedad..73eb37dc04 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -584,8 +584,6 @@ :justify-self :auto :shapes []}) -;; TODO: GRID ASSIGNMENTS - ;; Adding a track creates the cells. We should check the shapes that are not tracked (with default values) and assign to the correct tracked values (defn add-grid-column [parent value] @@ -634,7 +632,6 @@ (assoc :layout-grid-cells layout-grid-cells)))) -;; TODO: SPAN NOT CHECK!! (defn make-remove-track [attr track-num] (comp #(= % track-num) attr second)) @@ -648,6 +645,7 @@ (update attr dec))] [key new-val]))) +;; TODO: CHECK CELLS MULTI SPAN (defn remove-grid-column [parent index] (let [track-num (inc index) @@ -860,7 +858,6 @@ (first))) parent))) - (defn create-cells "Create cells in an area. One cell per row/column " [parent [column row column-span row-span]] @@ -955,3 +952,30 @@ ;; Not valid resize: we don't alter the layout parent)) + + +(defn get-cell-by-position + [parent target-row target-column] + (->> (:layout-grid-cells parent) + (d/seek + (fn [[_ {:keys [column row column-span row-span]}]] + (and (>= target-row row) + (>= target-column column) + (< target-column (+ column column-span)) + (< target-row (+ row row-span))))) + (second))) + +(defn get-cell-by-shape-id + [parent shape-id] + (->> (:layout-grid-cells parent) + (d/seek + (fn [[_ {:keys [shapes]}]] + (contains? (set shapes) shape-id))) + (second))) + +(defn swap-shapes + [parent id-from id-to] + + (-> parent + (assoc-in [:layout-grid-cells id-from :shapes] (dm/get-in parent [:layout-grid-cells id-to :shapes])) + (assoc-in [:layout-grid-cells id-to :shapes] (dm/get-in parent [:layout-grid-cells id-from :shapes])))) diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 125f12cea2..6cc4b3995f 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -556,57 +556,75 @@ objects (wsh/lookup-page-objects state) page-id (:current-page-id state) - get-new-position + get-move-to-index (fn [parent-id position] (let [parent (get objects parent-id)] - (cond - (ctl/flex-layout? parent) - (if (or - (and (ctl/reverse? parent) - (or (= direction :left) - (= direction :up))) - (and (not (ctl/reverse? parent)) - (or (= direction :right) - (= direction :down)))) - (dec position) - (+ position 2)) + (if (or (and (ctl/reverse? parent) + (or (= direction :left) + (= direction :up))) + (and (not (ctl/reverse? parent)) + (or (= direction :right) + (= direction :down)))) + (dec position) + (+ position 2)))) - ;; TODO: GRID - (ctl/grid-layout? parent) - nil - ))) + move-flex-children + (fn [changes parent-id children] + (->> children + ;; Add the position to move the children + (map (fn [id] + (let [position (cph/get-position-on-parent objects id)] + [id (get-move-to-index parent-id position)]))) + (sort-by second >) + (reduce (fn [changes [child-id index]] + (pcb/change-parent changes parent-id [(get objects child-id)] index)) + changes))) - add-children-position - (fn [[parent-id children]] - (let [children+position + move-grid-children + (fn [changes parent-id children] + (let [parent (get objects parent-id) + + key-prop (case direction + (:up :down) :row + (:right :left) :column) + key-comp (case direction + (:up :left) < + (:down :right) >) + + {:keys [layout-grid-cells]} (->> children - (keep #(let [new-position (get-new-position - parent-id - (cph/get-position-on-parent objects %))] - (when new-position - (vector % new-position)))) - (sort-by second >))] - [parent-id children+position])) - - change-parents-and-position - (->> selected - (group-by #(dm/get-in objects [% :parent-id])) - (map add-children-position) - (into {})) + (keep #(ctl/get-cell-by-shape-id parent %)) + (sort-by key-prop key-comp) + (reduce (fn [parent {:keys [id row column row-span column-span]}] + (let [[next-row next-column] + (case direction + :up [(dec row) column] + :right [row (+ column column-span)] + :down [(+ row row-span) column] + :left [row (dec column)]) + next-cell (ctl/get-cell-by-position parent next-row next-column)] + (cond-> parent + (some? next-cell) + (ctl/swap-shapes id (:id next-cell))))) + parent))] + (-> changes + (pcb/update-shapes [(:id parent)] (fn [shape] (assoc shape :layout-grid-cells layout-grid-cells)))))) changes - (->> change-parents-and-position + (->> selected + (group-by #(dm/get-in objects [% :parent-id])) (reduce (fn [changes [parent-id children]] - (->> children - (reduce - (fn [changes [child-id index]] - (pcb/change-parent changes parent-id - [(get objects child-id)] - index)) - changes))) + (cond-> changes + (ctl/flex-layout? objects parent-id) + (move-flex-children parent-id children) + + (ctl/grid-layout? objects parent-id) + (move-grid-children parent-id children))) + (-> (pcb/empty-changes it page-id) (pcb/with-objects objects)))) + undo-id (js/Symbol)] (rx/of From 664825a2a6118733e2d5f0878da3c6c74f78f0a1 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 18 May 2023 12:06:36 +0200 Subject: [PATCH 19/52] :sparkles: Fix specs for grid layout --- common/src/app/common/types/shape.cljc | 32 +++-- common/src/app/common/types/shape/layout.cljc | 111 +++++++++--------- 2 files changed, 75 insertions(+), 68 deletions(-) diff --git a/common/src/app/common/types/shape.cljc b/common/src/app/common/types/shape.cljc index 4b5703acd2..352a49f9aa 100644 --- a/common/src/app/common/types/shape.cljc +++ b/common/src/app/common/types/shape.cljc @@ -19,8 +19,7 @@ [app.common.types.shape.blur :as ctsb] [app.common.types.shape.export :as ctse] [app.common.types.shape.interactions :as ctsi] - ;; FIXME: missing spec -> schema - #_[app.common.types.shape.layout :as ctsl] + [app.common.types.shape.layout :as ctsl] [app.common.types.shape.shadow :as ctss] [app.common.types.shape.text :as ctsx] [app.common.uuid :as uuid] @@ -232,48 +231,57 @@ [:group [:merge {:title "GroupShape"} ::shape-attrs - ::group-attrs]] + ::group-attrs + ::ctsl/layout-child-attrs]] [:frame [:merge {:title "FrameShape"} ::shape-attrs - ::frame-attrs]] + ::frame-attrs + ::ctsl/layout-attrs + ::ctsl/layout-child-attrs]] [:bool [:merge {:title "BoolShape"} ::shape-attrs - ::bool-attrs]] + ::bool-attrs + ::ctsl/layout-child-attrs]] [:rect [:merge {:title "RectShape"} ::shape-attrs - ::rect-attrs]] + ::rect-attrs + ::ctsl/layout-child-attrs]] [:circle [:merge {:title "CircleShape"} ::shape-attrs - ::circle-attrs]] + ::circle-attrs + ::ctsl/layout-child-attrs]] [:image [:merge {:title "ImageShape"} ::shape-attrs - ::image-attrs]] + ::image-attrs + ::ctsl/layout-child-attrs]] [:svg-raw [:merge {:title "SvgRawShape"} ::shape-attrs - ::svg-raw-attrs]] + ::svg-raw-attrs + ::ctsl/layout-child-attrs]] [:path [:merge {:title "PathShape"} ::shape-attrs - ::path-attrs]] + ::path-attrs + ::ctsl/layout-child-attrs]] [:text [:merge {:title "TextShape"} ::shape-attrs - ::text-attrs]] - ]) + ::text-attrs + ::ctsl/layout-child-attrs]]]) (def shape? (sm/pred-fn ::shape)) diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index 73eb37dc04..810ed0d64e 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -21,15 +21,16 @@ ;; :layout-gap ;; {:row-gap number , :column-gap number} ;; :layout-align-items ;; :start :end :center :stretch -;; :layout-justify-content ;; :start :center :end :space-between :space-around :space-evenly ;; :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-grid-rows -;; layout-grid-columns -;; layout-justify-items +;; 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} @@ -40,8 +41,9 @@ ;; :layout-item-min-h ;; num ;; :layout-item-max-w ;; num ;; :layout-item-min-w ;; num -;; :layout-item-absolute -;; :layout-item-z-index +;; :layout-item-absolute ;; boolean +;; :layout-item-z-index ;; int + (def layout-types #{:flex :grid}) @@ -49,6 +51,9 @@ (def flex-direction-types #{:row :reverse-row :row-reverse :column :reverse-column :column-reverse}) ;;TODO remove reverse-column and reverse-row after script +(def grid-direction-types + #{:row :column}) + (def gap-types #{:simple :multiple}) @@ -59,7 +64,7 @@ #{:simple :multiple}) (def justify-content-types - #{:start :center :end :space-between :space-around :space-evenly}) + #{:start :center :end :space-between :space-around :space-evenly :stretch}) (def align-content-types #{:start :end :center :space-between :space-around :space-evenly :stretch}) @@ -90,48 +95,49 @@ [:layout-justify-content {:optional true} [::sm/one-of justify-content-types]] [:layout-justify-items {:optional true} [::sm/one-of justify-items-types]] [:layout-align-content {:optional true} [::sm/one-of align-content-types]] - [:layout-align-items {:optional true} [::sm/one-of align-items-types]]]) + [:layout-align-items {:optional true} [::sm/one-of align-items-types]] -;; (s/def :grid/type #{:percent :flex :auto :fixed}) -;; (s/def :grid/value (s/nilable ::us/safe-number)) -;; (s/def ::grid-definition (s/keys :req-un [:grid/type] -;; :opt-un [:grid/value])) -;; (s/def ::layout-grid-rows (s/coll-of ::grid-definition :kind vector?)) -;; (s/def ::layout-grid-columns (s/coll-of ::grid-definition :kind vector?)) + [:layout-grid-dir {:optional true} [::sm/one-of grid-direction-types]] + [:layout-grid-rows {:optional true} + [:vector {:gen/max 2} ::grid-track]] + [:layout-grid-columns {:optional true} + [:vector {:gen/max 2} ::grid-track]] + [:layout-grid-cells {:optional true} + [:map-of {:gen/max 5} ::sm/uuid ::grid-cell]]]) -;; (s/def :grid-cell/id uuid?) -;; (s/def :grid-cell/area-name ::us/string) -;; (s/def :grid-cell/row-start ::us/safe-integer) -;; (s/def :grid-cell/row-span ::us/safe-integer) -;; (s/def :grid-cell/column-start ::us/safe-integer) -;; (s/def :grid-cell/column-span ::us/safe-integer) -;; (s/def :grid-cell/position #{:auto :manual :area}) -;; (s/def :grid-cell/align-self #{:auto :start :end :center :stretch}) -;; (s/def :grid-cell/justify-self #{:auto :start :end :center :stretch}) -;; (s/def :grid-cell/shapes (s/coll-of uuid?)) +;; Grid types +(def grid-track-types + #{:percent :flex :auto :fixed}) -;; (s/def ::grid-cell (s/keys :opt-un [:grid-cell/id -;; :grid-cell/area-name -;; :grid-cell/row-start -;; :grid-cell/row-span -;; :grid-cell/column-start -;; :grid-cell/column-span -;; :grid-cell/position ;; auto, manual, area -;; :grid-cell/align-self -;; :grid-cell/justify-self -;; :grid-cell/shapes])) -;; (s/def ::layout-grid-cells (s/map-of uuid? ::grid-cell)) +(def grid-position-types + #{:auto :manual :area}) -;; (s/def ::layout-container-props -;; (s/keys :opt-un [ -;; ;; grid -;; ::layout-grid-dir -;; ::layout-justify-items -;; ::layout-grid-rows -;; ::layout-grid-columns -;; ::layout-grid-cells -;; ])) +(def grid-cell-align-self-types + #{:auto :start :center :end :stretch}) +(def grid-cell-justify-self-types + #{:auto :start :center :end :stretch}) + +(sm/def! ::grid-cell + [:map {:title "GridCell"} + [:id ::sm/uuid] + [:area-name {:optional true} :string] + [:row ::sm/safe-int] + [:row-span ::sm/safe-int] + [:column ::sm/safe-int] + [:column-span ::sm/safe-int] + [:position {:optional true} [::sm/one-of grid-position-types]] + [:align-self {:optional true} [::sm/one-of grid-cell-align-self-types]] + [:justify-self {:optional true} [::sm/one-of grid-cell-justify-self-types]] + [:shapes + [:vector {:gen/max 1} ::sm/uuid]]]) + +(sm/def! ::grid-track + [:map {:title "GridTrack"} + [:type [::sm/one-of grid-track-types]] + [:value {:optional true} [:maybe ::sm/safe-number]]]) + +;; LAYOUT CHILDREN (def item-margin-types #{:simple :multiple}) @@ -164,13 +170,7 @@ [:layout-item-absolute {:optional true} :boolean] [:layout-item-z-index {:optional true} ::sm/safe-number]]) -(def schema:grid-definition - [:map {:title "LayoutGridDefinition"} - [:type [::sm/one-of #{:percent :flex :auto :fixed}]] - [:value {:optional true} [:maybe ::sm/safe-int]]]) - -(def grid-definition? - (sm/pred-fn schema:grid-definition)) +(def grid-track? (sm/pred-fn ::grid-track)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; SCHEMAS @@ -524,11 +524,11 @@ :layout-wrap-type :layout-padding-type :layout-padding + :layout-align-content :layout-justify-content :layout-align-items - :layout-align-content - :layout-grid-dir :layout-justify-items + :layout-grid-dir :layout-grid-columns :layout-grid-rows )) @@ -570,7 +570,6 @@ (d/update-in-when [:layout-item-margin :m3] * scale) (d/update-in-when [:layout-item-margin :m4] * scale))) - (declare assign-cells) (def default-track-value @@ -589,7 +588,7 @@ [parent value] (dm/assert! "expected a valid grid definition for `value`" - (grid-definition? value)) + (grid-track? value)) (let [rows (:layout-grid-rows parent) new-col-num (inc (count (:layout-grid-columns parent))) @@ -612,7 +611,7 @@ [parent value] (dm/assert! "expected a valid grid definition for `value`" - (grid-definition? value)) + (grid-track? value)) (let [cols (:layout-grid-columns parent) new-row-num (inc (count (:layout-grid-rows parent))) From b3b984d3392fb822671abd981e63e8426f93817a Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 18 May 2023 16:08:57 +0200 Subject: [PATCH 20/52] :sparkles: Add import/export svg for grid --- frontend/src/app/main/ui/shapes/export.cljs | 60 +++++++++++++++-- .../viewport/grid_layout_editor.cljs | 2 +- frontend/src/app/util/import/parser.cljs | 67 ++++++++++++++++++- frontend/src/app/worker/import.cljs | 11 ++- 4 files changed, 131 insertions(+), 9 deletions(-) diff --git a/frontend/src/app/main/ui/shapes/export.cljs b/frontend/src/app/main/ui/shapes/export.cljs index 00de59a7a6..9999772822 100644 --- a/frontend/src/app/main/ui/shapes/export.cljs +++ b/frontend/src/app/main/ui/shapes/export.cljs @@ -340,9 +340,14 @@ layout-wrap-type layout-padding-type layout-padding + layout-justify-items layout-justify-content layout-align-items - layout-align-content]}] + layout-align-content + layout-grid-dir + layout-grid-rows + layout-grid-columns + layout-grid-cells]}] (when layout (mf/html @@ -358,9 +363,48 @@ :penpot:layout-padding-p2 (:p2 layout-padding) :penpot:layout-padding-p3 (:p3 layout-padding) :penpot:layout-padding-p4 (:p4 layout-padding) + :penpot:layout-justify-items (d/name layout-justify-items) :penpot:layout-justify-content (d/name layout-justify-content) :penpot:layout-align-items (d/name layout-align-items) - :penpot:layout-align-content (d/name layout-align-content)}]))) + :penpot:layout-align-content (d/name layout-align-content) + :penpot:layout-grid-dir (d/name layout-grid-dir)} + + [:> "penpot:grid-rows" #js {} + (for [[idx {:keys [type value]}] (d/enumerate layout-grid-rows)] + [:> "penpot:grid-track" + #js {:penpot:index idx + :penpot:type (d/name type) + :penpot:value value}])] + + [:> "penpot:grid-columns" #js {} + (for [[idx {:keys [type value]}] (d/enumerate layout-grid-columns)] + [:> "penpot:grid-track" + #js {:penpot:index idx + :penpot:type (d/name type) + :penpot:value value}])] + + [:> "penpot:grid-cells" #js {} + (for [[_ {:keys [id + area-name + row + row-span + column + column-span + position + align-self + justify-self + shapes]}] layout-grid-cells] + [:> "penpot:grid-cell" + #js {:penpot:id id + :penpot:area-name area-name + :penpot:row row + :penpot:row-span row-span + :penpot:column column + :penpot:column-span column-span + :penpot:position (d/name position) + :penpot:align-self (d/name align-self) + :penpot:justify-self (d/name justify-self) + :penpot:shapes (str/join " " shapes)}])]]))) (defn- export-layout-item-data [{:keys [layout-item-margin @@ -371,7 +415,9 @@ layout-item-min-h layout-item-max-w layout-item-min-w - layout-item-align-self]}] + layout-item-align-self + layout-item-absolute + layout-item-z-index]}] (when (or layout-item-margin layout-item-margin-type @@ -381,7 +427,9 @@ layout-item-min-h layout-item-max-w layout-item-min-w - layout-item-align-self) + layout-item-align-self + layout-item-absolute + layout-item-z-index) (mf/html [:> "penpot:layout-item" #js {:penpot:layout-item-margin-m1 (:m1 layout-item-margin) @@ -395,7 +443,9 @@ :penpot:layout-item-min-h layout-item-min-h :penpot:layout-item-max-w layout-item-max-w :penpot:layout-item-min-w layout-item-min-w - :penpot:layout-item-align-self (d/name layout-item-align-self)}]))) + :penpot:layout-item-align-self (d/name layout-item-align-self) + :penpot:layout-item-absolute layout-item-absolute + :penpot:layout-item-z-index layout-item-z-index}]))) (mf/defc export-data 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 1a10b91b10..0ff2557cd2 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 @@ -564,7 +564,7 @@ (if (some? track-type) (do (st/emit! (dwsl/change-layout-track [(:id shape)] type index {:type track-type :value value})) - (dom/set-data! target "default-value" (format-size {:type type :value value}))) + (dom/set-data! target "default-value" (format-size {:type track-type :value value}))) (obj/set! target "value" (dom/get-attribute target "data-default-value")))))) handle-keydown-track-input diff --git a/frontend/src/app/util/import/parser.cljs b/frontend/src/app/util/import/parser.cljs index f692f35438..0da933bea9 100644 --- a/frontend/src/app/util/import/parser.cljs +++ b/frontend/src/app/util/import/parser.cljs @@ -869,12 +869,64 @@ :style parse-style)))) +(defn parse-grid-tracks + [node label] + (let [node (-> (get-data node :penpot:layout) + (find-node label))] + (->> node + :content + (mapv + (fn [track-node] + (let [{:keys [type value]} (-> track-node :attrs remove-penpot-prefix)] + {:type (keyword type) + :value (d/parse-double value)})))))) + +(defn parse-grid-cells + [node] + (let [node (-> (get-data node :penpot:layout) + (find-node :penpot:grid-cells))] + (->> node + :content + (mapv + (fn [cell-node] + (let [{:keys [id + area-name + row + row-span + column + column-span + position + align-self + justify-self + shapes]} (-> cell-node :attrs remove-penpot-prefix) + id (uuid/uuid id)] + [id (d/without-nils + {:id id + :area-name area-name + :row (d/parse-integer row) + :row-span (d/parse-integer row-span) + :column (d/parse-integer column) + :column-span (d/parse-integer column-span) + :position (keyword position) + :align-self (keyword align-self) + :justify-self (keyword justify-self) + :shapes (if (and (some? shapes) (d/not-empty? shapes)) + (->> (str/split shapes " ") + (mapv uuid/uuid)) + [])})]))) + (into {})))) + (defn add-layout-container-data [props node] (if-let [data (get-data node :penpot:layout)] - (merge props + (let [layout-grid-rows (parse-grid-tracks node :penpot:grid-rows) + layout-grid-columns (parse-grid-tracks node :penpot:grid-columns) + layout-grid-cells (parse-grid-cells node)] + (-> props + (merge (d/without-nils {:layout (get-meta data :layout keyword) :layout-flex-dir (get-meta data :layout-flex-dir keyword) + :layout-grid-dir (get-meta data :layout-grid-dir keyword) :layout-wrap-type (get-meta data :layout-wrap-type keyword) :layout-gap-type (get-meta data :layout-gap-type keyword) @@ -891,9 +943,17 @@ :p3 (get-meta data :layout-padding-p3 d/parse-double) :p4 (get-meta data :layout-padding-p4 d/parse-double)}) + :layout-justify-items (get-meta data :layout-justify-items keyword) :layout-justify-content (get-meta data :layout-justify-content keyword) :layout-align-items (get-meta data :layout-align-items keyword) :layout-align-content (get-meta data :layout-align-content keyword)})) + + (cond-> (d/not-empty? layout-grid-rows) + (assoc :layout-grid-rows layout-grid-rows)) + (cond-> (d/not-empty? layout-grid-columns) + (assoc :layout-grid-columns layout-grid-columns)) + (cond-> (d/not-empty? layout-grid-cells) + (assoc :layout-grid-cells layout-grid-cells)))) props)) (defn add-layout-item-data [props node] @@ -914,7 +974,10 @@ :layout-item-min-h (get-meta data :layout-item-min-h d/parse-double) :layout-item-max-w (get-meta data :layout-item-max-w d/parse-double) :layout-item-min-w (get-meta data :layout-item-min-w d/parse-double) - :layout-item-align-self (get-meta data :layout-item-align-self keyword)})) + :layout-item-align-self (get-meta data :layout-item-align-self keyword) + :layout-item-align-absolute (get-meta data :layout-item-align-absolute str->bool) + :layout-item-align-index (get-meta data :layout-item-align-index d/parse-double) + })) props)) (defn parse-data diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs index 2e533d894d..5a561cfac7 100644 --- a/frontend/src/app/worker/import.cljs +++ b/frontend/src/app/worker/import.cljs @@ -229,7 +229,16 @@ (d/update-when :shape-ref resolve) (cond-> (= type :text) - (d/update-when :content resolve-text-content context))))) + (d/update-when :content resolve-text-content context)) + + (cond-> (and (= type :frame) (= :grid (:layout data))) + (update + :layout-grid-cells + (fn [cells] + (->> (vals cells) + (reduce (fn [cells {:keys [id shapes]}] + (assoc-in cells [id :shapes] (mapv resolve shapes))) + cells)))))))) (defn- translate-frame [data type file] From b0136fef296ebaa19c912cff6a586f94ff64ab15 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 19 May 2023 15:33:39 +0200 Subject: [PATCH 21/52] :bug: Fix problem with fill width/height and alignment --- .../geom/shapes/grid_layout/positions.cljc | 52 +++++++++++-------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/common/src/app/common/geom/shapes/grid_layout/positions.cljc b/common/src/app/common/geom/shapes/grid_layout/positions.cljc index 5e7c3454c1..4671ac4d54 100644 --- a/common/src/app/common/geom/shapes/grid_layout/positions.cljc +++ b/common/src/app/common/geom/shapes/grid_layout/positions.cljc @@ -96,18 +96,21 @@ fill-height (when (ctl/fill-height? child) - (calc-fill-height-data parent transform transform-inverse child child-origin child-height cell-bounds))] + (calc-fill-height-data parent transform transform-inverse child child-origin child-height cell-bounds)) - (-> (ctm/empty) + child-width (or (:width fill-width) child-width) + child-height (or (:height fill-height) child-height)] + + [child-width + child-height + (-> (ctm/empty) (cond-> fill-width (ctm/add-modifiers (:modifiers fill-width))) - (cond-> fill-height (ctm/add-modifiers (:modifiers fill-height)))))) - -(defn child-modifiers - [parent parent-bounds child child-bounds layout-data cell-data] + (cond-> fill-height (ctm/add-modifiers (:modifiers fill-height))))])) +(defn child-position-delta + [parent child-bounds child-width child-height layout-data cell-data] (let [cell-bounds (cell-bounds layout-data cell-data) - - fill-modifiers (fill-modifiers parent parent-bounds child child-bounds layout-data cell-data) + child-origin (gpo/origin child-bounds) align (:layout-align-items parent) justify (:layout-justify-items parent) @@ -120,39 +123,46 @@ align (or align-self align) justify (or justify-self justify) - position-delta (gpt/point) + origin-h (gpo/project-point cell-bounds :h child-origin) + origin-v (gpo/project-point cell-bounds :v child-origin) + hv (partial gpo/start-hv cell-bounds) + vv (partial gpo/start-vv cell-bounds) ;; Adjust alignment/justify [from-h to-h] (case justify :end - [(gpo/project-point cell-bounds :h (nth child-bounds 1)) + [(gpt/add origin-h (hv child-width)) (nth cell-bounds 1)] :center - [(gpo/project-point cell-bounds :h (gpo/center child-bounds)) + [(gpt/add origin-h (hv (/ child-width 2))) (gpo/project-point cell-bounds :h (gpo/center cell-bounds))] - [(gpo/project-point cell-bounds :h (first child-bounds)) - (first cell-bounds)]) + [origin-h (first cell-bounds)]) [from-v to-v] (case align :end - [(gpo/project-point cell-bounds :v (nth child-bounds 3)) + [(gpt/add origin-v (vv child-height)) (nth cell-bounds 3)] :center - [(gpo/project-point cell-bounds :v (gpo/center child-bounds)) + [(gpt/add origin-v (vv (/ child-height 2))) (gpo/project-point cell-bounds :v (gpo/center cell-bounds))] - [(gpo/project-point cell-bounds :v (first child-bounds)) - (first cell-bounds)]) + [origin-v (first cell-bounds)])] + (-> (gpt/point) + (gpt/add (gpt/to-vec from-h to-h)) + (gpt/add (gpt/to-vec from-v to-v))))) - position-delta - (-> position-delta - (gpt/add (gpt/to-vec from-h to-h)) - (gpt/add (gpt/to-vec from-v to-v)))] +(defn child-modifiers + [parent parent-bounds child child-bounds layout-data cell-data] + + (let [[child-width child-height fill-modifiers] + (fill-modifiers parent parent-bounds child child-bounds layout-data cell-data) + + position-delta (child-position-delta parent child-bounds child-width child-height layout-data cell-data)] (cond-> (ctm/empty) (not (ctl/layout-absolute? child)) From 714b2c8805c612843b62d7300a4a982c3f65b301 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 22 May 2023 14:36:38 +0200 Subject: [PATCH 22/52] :sparkles: Remove tracks update multispan cells --- common/src/app/common/types/shape/layout.cljc | 66 ++++++++++++------- 1 file changed, 41 insertions(+), 25 deletions(-) diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index 810ed0d64e..01a390ab0a 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -631,52 +631,68 @@ (assoc :layout-grid-cells layout-grid-cells)))) -(defn make-remove-track - [attr track-num] - (comp #(= % track-num) attr second)) +(defn make-remove-cell + [attr span-attr track-num] + (fn [[_ cell]] + ;; Only remove cells with span=1 otherwise the cell will be fixed + (and (= track-num (get cell attr)) + (= (get cell span-attr) 1)))) (defn make-decrease-track-num - [attr track-num] - (fn [[key value]] - (let [new-val - (cond-> value - (> (get value attr) track-num) - (update attr dec))] - [key new-val]))) + [attr span-attr track-num] + (fn [[id cell]] + (let [inner-track? + (or (= track-num (get cell attr)) + (and (< (get cell attr) track-num (+ (get cell attr) (get cell span-attr))))) + + displace-cell? + (and (not inner-track?) (< track-num (get cell attr))) + + cell + (cond-> cell + inner-track? + (update span-attr dec) + + displace-cell? + (update attr dec))] + + [id cell]))) -;; TODO: CHECK CELLS MULTI SPAN (defn remove-grid-column [parent index] + (let [track-num (inc index) - decrease-track-num (make-decrease-track-num :column track-num) - remove-track? (make-remove-track :column track-num) + decrease-track-num (make-decrease-track-num :column :column-span track-num) + remove-track? (make-remove-cell :column :column-span track-num) - remove-cells + update-cells (fn [cells] - (into {} (comp - (remove remove-track?) - (map decrease-track-num)) cells))] + (into {} + (comp (remove remove-track?) + (map decrease-track-num)) + cells))] (-> parent (update :layout-grid-columns d/remove-at-index index) - (update :layout-grid-cells remove-cells) + (update :layout-grid-cells update-cells) (assign-cells)))) (defn remove-grid-row [parent index] (let [track-num (inc index) - decrease-track-num (make-decrease-track-num :row track-num) - remove-track? (make-remove-track :row track-num) + decrease-track-num (make-decrease-track-num :row :row-span track-num) + remove-track? (make-remove-cell :row :row-span track-num) - remove-cells + update-cells (fn [cells] - (into {} (comp - (remove remove-track?) - (map decrease-track-num)) cells))] + (into {} + (comp (remove remove-track?) + (map decrease-track-num)) + cells))] (-> parent (update :layout-grid-rows d/remove-at-index index) - (update :layout-grid-cells remove-cells) + (update :layout-grid-cells update-cells) (assign-cells)))) (defn get-cells From 0e152bb7f9d48d56c4db6af242274abc976e112c Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 22 May 2023 15:34:01 +0200 Subject: [PATCH 23/52] :sparkles: Paste on position in grid --- common/src/app/common/types/shape/layout.cljc | 15 +++++++++++++++ frontend/src/app/main/data/workspace.cljs | 15 +++++++++++---- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index 01a390ab0a..6f85b84990 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -994,3 +994,18 @@ (-> parent (assoc-in [:layout-grid-cells id-from :shapes] (dm/get-in parent [:layout-grid-cells id-to :shapes])) (assoc-in [:layout-grid-cells id-to :shapes] (dm/get-in parent [:layout-grid-cells id-from :shapes])))) + +(defn add-children-to-cell + [frame children objects [row column :as cell]] + (let [;; Temporary remove the children when moving them + frame (-> frame + (update :shapes #(d/removev children %)) + (assign-cells)) + + children (->> children (remove #(layout-absolute? objects %)))] + + (-> frame + (update :shapes d/concat-vec children) + (cond-> (some? cell) + (push-into-cell children row column)) + (assign-cells)))) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 05e11c3be6..acb2bfdd1f 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -14,6 +14,7 @@ [app.common.geom.point :as gpt] [app.common.geom.proportions :as gpp] [app.common.geom.shapes :as gsh] + [app.common.geom.shapes.grid-layout :as gslg] [app.common.logging :as log] [app.common.pages :as cp] [app.common.pages.changes-builder :as pcb] @@ -1806,13 +1807,19 @@ changes (-> (dws/prepare-duplicate-changes all-objects page selected delta it libraries library-data file-id) (pcb/amend-changes (partial process-rchange media-idx)) - (pcb/amend-changes (partial change-add-obj-index paste-objects selected index)) - (cond-> (ctl/grid-layout? all-objects parent-id) - (pcb/update-shapes [parent-id] ctl/assign-cells))) + (pcb/amend-changes (partial change-add-obj-index paste-objects selected index))) ;; Adds a resize-parents operation so the groups are updated. We add all the new objects new-objects-ids (->> changes :redo-changes (filter #(= (:type %) :add-obj)) (mapv :id)) - changes (pcb/resize-parents changes new-objects-ids) + + drop-cell + (when (ctl/grid-layout? all-objects parent-id) + (gslg/get-drop-cell frame-id all-objects mouse-pos)) + + changes + (-> (pcb/resize-parents changes new-objects-ids) + (cond-> (some? drop-cell) + (pcb/update-shapes [parent-id] #(ctl/add-children-to-cell % (into (d/ordered-set) new-objects-ids) all-objects drop-cell)))) selected (->> changes :redo-changes From cf4e2f91d1aa602bbac875c62a75ee8e8cf30eb9 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 23 May 2023 12:33:39 +0200 Subject: [PATCH 24/52] :sparkles: Grid layout polishing --- common/src/app/common/types/shape/layout.cljc | 2 +- .../src/app/main/data/workspace/edition.cljs | 3 +- .../main/ui/workspace/sidebar/options.cljs | 53 ++++++++++++------- .../src/app/main/ui/workspace/viewport.cljs | 40 +++++++------- .../main/ui/workspace/viewport/actions.cljs | 8 ++- .../viewport/grid_layout_editor.cljs | 29 ++++++---- 6 files changed, 82 insertions(+), 53 deletions(-) diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index 6f85b84990..1de4aed70b 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -643,7 +643,7 @@ (fn [[id cell]] (let [inner-track? (or (= track-num (get cell attr)) - (and (< (get cell attr) track-num (+ (get cell attr) (get cell span-attr))))) + (< (get cell attr) track-num (+ (get cell attr) (get cell span-attr)))) displace-cell? (and (not inner-track?) (< track-num (get cell attr))) diff --git a/frontend/src/app/main/data/workspace/edition.cljs b/frontend/src/app/main/data/workspace/edition.cljs index 01be4c72ec..66a33acf5e 100644 --- a/frontend/src/app/main/data/workspace/edition.cljs +++ b/frontend/src/app/main/data/workspace/edition.cljs @@ -26,7 +26,8 @@ (if (contains? objects id) (-> state (assoc-in [:workspace-local :selected] #{id}) - (assoc-in [:workspace-local :edition] id)) + (assoc-in [:workspace-local :edition] id) + (dissoc :workspace-grid-edition)) state))) ptk/WatchEvent diff --git a/frontend/src/app/main/ui/workspace/sidebar/options.cljs b/frontend/src/app/main/ui/workspace/sidebar/options.cljs index 16ce75b7b9..76076a7e0b 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options.cljs @@ -85,6 +85,7 @@ (if (= options-mode :inspect) ;;TODO maybe move this logic to set-options-mode (st/emit! :interrupt (udw/set-workspace-read-only true)) (st/emit! :interrupt (udw/set-workspace-read-only false))))] + [:div.tool-window [:div.tool-window-content [:& tabs-container {:on-change-tab on-change-tab @@ -95,27 +96,41 @@ [:& align-options] [:& bool-options] (cond - grid-cell-selected? [:& grid-cell/options {:shape (get objects grid-id) - :cell (get-in objects [grid-id :layout-grid-cells cell-id])}] + grid-cell-selected? + [:& grid-cell/options + {:shape (get objects grid-id) + :cell (get-in objects [grid-id :layout-grid-cells cell-id])}] - (d/not-empty? drawing) [:& shape-options {:shape (:object drawing) - :page-id page-id - :file-id file-id - :shared-libs shared-libs}] - (= 0 (count selected)) [:& page/options] - (= 1 (count selected)) [:& shape-options {:shape (first selected-shapes) - :page-id page-id - :file-id file-id - :shared-libs shared-libs - :shapes-with-children shapes-with-children}] - :else [:& multiple/options {:shapes-with-children shapes-with-children - :shapes selected-shapes - :page-id page-id - :file-id file-id - :shared-libs shared-libs}])]] + (d/not-empty? drawing) + [:& shape-options + {:shape (:object drawing) + :page-id page-id + :file-id file-id + :shared-libs shared-libs}] + + (= 0 (count selected)) + [:& page/options] + + (= 1 (count selected)) + [:& shape-options + {:shape (first selected-shapes) + :page-id page-id + :file-id file-id + :shared-libs shared-libs + :shapes-with-children shapes-with-children}] + + :else + [:& multiple/options + {:shapes-with-children shapes-with-children + :shapes selected-shapes + :page-id page-id + :file-id file-id + :shared-libs shared-libs}])]] + + [:& tabs-element + {:id :prototype + :title (tr "workspace.options.prototype")} - [:& tabs-element {:id :prototype - :title (tr "workspace.options.prototype")} [:div.element-options [:& interactions-menu {:shape (first shapes)}]]] diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 946bf1df06..177e3b1d58 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -251,7 +251,9 @@ offset-y (if selecting-first-level-frame? (:y (first selected-shapes)) - (:y selected-frame))] + (:y selected-frame)) + + rule-area-size (/ rules/rule-area-size zoom)] (hooks/setup-dom-events zoom disable-paste in-viewport? workspace-read-only?) (hooks/setup-viewport-size vport viewport-ref) @@ -349,6 +351,14 @@ :on-pointer-move on-pointer-move :on-pointer-up on-pointer-up} + [:defs + ;; This clip is so the handlers are not over the rules + [:clipPath {:id "clip-handlers"} + [:rect {:x (+ (:x vbox) rule-area-size) + :y (+ (:y vbox) rule-area-size) + :width (- (:width vbox) (* rule-area-size 2)) + :height (- (:height vbox) (* rule-area-size 2))}]]] + [:g {:style {:pointer-events (if disable-events? "none" "auto")}} (when show-text-editor? [:& editor/text-editor-svg {:shape editing-shape @@ -559,15 +569,6 @@ (when show-selection-handlers? [:g.selection-handlers {:clipPath "url(#clip-handlers)"} - [:defs - (let [rule-area-size (/ rules/rule-area-size zoom)] - ;; This clip is so the handlers are not over the rules - [:clipPath {:id "clip-handlers"} - [:rect {:x (+ (:x vbox) rule-area-size) - :y (+ (:y vbox) rule-area-size) - :width (max 0 (- (:width vbox) (* rule-area-size 2))) - :height (max 0 (- (:height vbox) (* rule-area-size 2)))}]])] - [:& selection/selection-handlers {:selected selected :shapes selected-shapes @@ -589,14 +590,13 @@ {:id (first selected) :zoom zoom}]) - (when (or show-grid-editor? hover-grid?) - [:& grid-layout/editor - {:zoom zoom - :objects objects-modified - :shape (or (get objects-modified edition) - (gsh/transform-shape - (get base-objects @hover-top-frame-id) - (dm/get-in modifiers [@hover-top-frame-id :modifiers]))) - :view-only (not show-grid-editor?)}]) - ]]])) + [:g.grid-layout-editor {:clipPath "url(#clip-handlers)"} + [:& grid-layout/editor + {:zoom zoom + :objects objects-modified + :shape (or (get objects-modified edition) + (gsh/transform-shape + (get base-objects @hover-top-frame-id) + (dm/get-in modifiers [@hover-top-frame-id :modifiers]))) + :view-only (not show-grid-editor?)}]])]]])) diff --git a/frontend/src/app/main/ui/workspace/viewport/actions.cljs b/frontend/src/app/main/ui/workspace/viewport/actions.cljs index 7c1f48565d..f9ec1e96aa 100644 --- a/frontend/src/app/main/ui/workspace/viewport/actions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/actions.cljs @@ -1,4 +1,4 @@ -; This Source Code Form is subject to the terms of the Mozilla Public +;; 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/. ;; @@ -9,6 +9,7 @@ [app.common.geom.point :as gpt] [app.common.math :as mth] [app.common.pages.helpers :as cph] + [app.common.types.shape.layout :as ctl] [app.common.uuid :as uuid] [app.config :as cfg] [app.main.data.workspace :as dw] @@ -204,7 +205,7 @@ (st/emit! (ms/->MouseEvent :double-click ctrl? shift? alt? meta?)) - ;; Emit asynchronously so the double click to exit shapes won't break + ;; Emit asynchronously so the double click to exit shapes won't break (timers/schedule (fn [] (when (and (not drawing-path?) shape) @@ -213,6 +214,9 @@ (st/emit! (dw/select-shape id) (dw/start-editing-selected)) + (ctl/grid-layout? shape) + (st/emit! (dw/start-edition-mode id)) + :else (let [;; We only get inside childrens of the hovering shape hover-ids (->> @hover-ids (filter (partial cph/is-child? objects id))) 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 0ff2557cd2..1f59f0c243 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 @@ -261,7 +261,8 @@ text]])) (mf/defc grid-cell - {::mf/wrap-props false} + {::mf/wrap [#(mf/memo' % (mf/check-props ["shape" "cell" "layout-data" "zoom" "hover?" "selected?"]))] + ::mf/wrap-props false} [props] (let [shape (unchecked-get props "shape") cell (unchecked-get props "cell") @@ -278,16 +279,19 @@ handle-pointer-enter (mf/use-callback + (mf/deps (:id shape) (:id cell)) (fn [] (st/emit! (dwge/hover-grid-cell (:id shape) (:id cell) true)))) handle-pointer-leave (mf/use-callback + (mf/deps (:id shape) (:id cell)) (fn [] (st/emit! (dwge/hover-grid-cell (:id shape) (:id cell) false)))) handle-pointer-down (mf/use-callback + (mf/deps (:id shape) (:id cell)) (fn [] (st/emit! (dwge/select-grid-cell (:id shape) (:id cell)))))] @@ -639,7 +643,11 @@ snap-pixel? (mf/deref refs/snap-pixel?) - grid-edition-id-ref (mf/use-memo #(refs/workspace-grid-edition-id (:id shape))) + grid-edition-id-ref + (mf/use-memo + (mf/deps (:id shape)) + #(refs/workspace-grid-edition-id (:id shape))) + grid-edition (mf/deref grid-edition-id-ref) hover-cells (:hover grid-edition) @@ -781,11 +789,12 @@ :snap-pixel? snap-pixel? :zoom zoom}]]))]) - (for [[_ cell] (:layout-grid-cells shape)] - [:& grid-cell {:key (dm/str "cell-" (:id cell)) - :shape base-shape - :layout-data layout-data - :cell cell - :zoom zoom - :hover? (contains? hover-cells (:id cell)) - :selected? (= selected-cells (:id cell))}])])) + [:g.cells + (for [cell (ctl/get-cells shape {:sort? true})] + [:& grid-cell {:key (dm/str "cell-" (:id cell)) + :shape base-shape + :layout-data layout-data + :cell cell + :zoom zoom + :hover? (contains? hover-cells (:id cell)) + :selected? (= selected-cells (:id cell))}])]])) From 0358eb51e8d15e8b94c3f1aaa8213a0da3e3c92c Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 24 May 2023 09:25:09 +0200 Subject: [PATCH 25/52] :sparkles: Change behavior on empty grid creation --- .../geom/shapes/grid_layout/layout_data.cljc | 2 - .../app/main/data/workspace/shape_layout.cljs | 142 +++++++++--------- .../src/app/main/ui/workspace/viewport.cljs | 4 +- .../viewport/grid_layout_editor.cljs | 7 +- 4 files changed, 82 insertions(+), 73 deletions(-) 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 a1fc3072ef..a377d48d33 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 @@ -10,8 +10,6 @@ ;; - Auto ;; - Flex ;; -;; -;; ;; Min functions: ;; - Fixed: value ;; - Percent: value to pixels diff --git a/frontend/src/app/main/data/workspace/shape_layout.cljs b/frontend/src/app/main/data/workspace/shape_layout.cljs index b6672256b2..4e8c5e75d5 100644 --- a/frontend/src/app/main/data/workspace/shape_layout.cljs +++ b/frontend/src/app/main/data/workspace/shape_layout.cljs @@ -88,84 +88,90 @@ ([objects shapes] (shapes->flex-params objects shapes nil)) ([objects shapes parent] - (let [points - (->> shapes - (map :id) - (ctt/sort-z-index objects) - (map (comp gsh/center-shape (d/getf objects)))) + (when (d/not-empty? shapes) + (let [points + (->> shapes + (map :id) + (ctt/sort-z-index objects) + (map (comp gsh/center-shape (d/getf objects)))) - start (first points) - end (reduce (fn [acc p] (gpt/add acc (gpt/to-vec start p))) points) + 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 (gpt/signed-angle-with-other + (gpt/to-vec start end) + (gpt/point 1 0)) - angle (mod angle 360) + angle (mod angle 360) - t1 (min (abs (- angle 0)) (abs (- angle 360))) - t2 (abs (- angle 90)) - t3 (abs (- angle 180)) - t4 (abs (- angle 270)) + 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) + 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) + 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) + 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) + 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)} + 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))})] + 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)}))))) + (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] - {})) + ([_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?] @@ -176,10 +182,9 @@ children-ids (into [] (mapcat #(get-in objects [% :shapes])) ids) children-shapes (map (d/getf objects) children-ids) parent (get objects (first ids)) - layout-params (when (d/not-empty? children-shapes) - (case type - :flex (shapes->flex-params objects children-shapes parent) - :grid (shapes->grid-params objects children-shapes parent))) + 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?)) @@ -190,7 +195,8 @@ (cond-> (not from-frame?) (assoc :layout-item-h-sizing :auto :layout-item-v-sizing :auto)) - (merge layout-params)))) + (merge layout-params) + (cond-> (= type :grid) (ctl/assign-cells))))) (ptk/data-event :layout/update ids) (dwc/update-shapes children-ids #(dissoc % :constraints-h :constraints-v)) (dwu/commit-undo-transaction undo-id)))))) @@ -280,7 +286,7 @@ (let [new-shape-id (uuid/next) undo-id (js/Symbol) - flex-params (shapes->flex-params objects selected-shapes)] + flex-params (shapes->flex-params objects selected-shapes)] (rx/of (dwu/start-undo-transaction undo-id) (dws/create-artboard-from-selection new-shape-id) diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 177e3b1d58..a45923394c 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -356,8 +356,8 @@ [:clipPath {:id "clip-handlers"} [:rect {:x (+ (:x vbox) rule-area-size) :y (+ (:y vbox) rule-area-size) - :width (- (:width vbox) (* rule-area-size 2)) - :height (- (:height vbox) (* rule-area-size 2))}]]] + :width (max 0 (- (:width vbox) (* rule-area-size 2))) + :height (max 0 (- (:height vbox) (* rule-area-size 2)))}]]] [:g {:style {:pointer-events (if disable-events? "none" "auto")}} (when show-text-editor? 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 1f59f0c243..364bb1da7b 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 @@ -21,6 +21,7 @@ [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.formats :as fmt] + [app.main.ui.hooks :as hooks] [app.main.ui.workspace.viewport.viewport-ref :as uwvv] [app.util.dom :as dom] [app.util.keyboard :as kbd] @@ -658,6 +659,8 @@ (remove :hidden) (map #(vector (gpo/parent-coords-bounds (:points %) (:points shape)) %))) + children (hooks/use-equal-memo children) + bounds (:points shape) hv #(gpo/start-hv bounds %) vv #(gpo/start-vv bounds %) @@ -668,7 +671,9 @@ [layout-gap-row layout-gap-col] (ctl/gaps shape) {:keys [row-tracks column-tracks] :as layout-data} - (gsg/calc-layout-data shape children bounds) + (mf/use-memo + (mf/deps shape children) + #(gsg/calc-layout-data shape children bounds)) handle-pointer-down (mf/use-callback From 0c1e83e4a685ad2c04e79a6e5fb6db711e9926b0 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 24 May 2023 10:12:19 +0200 Subject: [PATCH 26/52] :sparkles: Fix problem with effects --- frontend/src/app/main/data/workspace/shapes.cljs | 4 ++-- .../app/main/ui/workspace/sidebar/options/menus/measures.cljs | 3 +++ .../app/main/ui/workspace/sidebar/options/shapes/frame.cljs | 3 +++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs index 719ba9abb7..d23ad2dc71 100644 --- a/frontend/src/app/main/data/workspace/shapes.cljs +++ b/frontend/src/app/main/data/workspace/shapes.cljs @@ -357,8 +357,8 @@ (rx/of (dwu/start-undo-transaction undo-id) (dc/detach-comment-thread ids) (dch/commit-changes changes) - (dwu/commit-undo-transaction undo-id) - (ptk/data-event :layout/update all-parents)))) + (ptk/data-event :layout/update all-parents) + (dwu/commit-undo-transaction undo-id)))) (defn create-and-add-shape diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs index 7c245ed051..101830e0f8 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs @@ -19,6 +19,7 @@ [app.main.store :as st] [app.main.ui.components.dropdown :refer [dropdown]] [app.main.ui.components.numeric-input :refer [numeric-input]] + [app.main.ui.hooks :as hooks] [app.main.ui.icons :as i] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] @@ -80,6 +81,8 @@ [shape]) frames (map #(deref (refs/object-by-id (:frame-id %))) old-shapes) + ids (hooks/use-equal-memo ids) + selection-parents-ref (mf/use-memo (mf/deps ids) #(refs/parents-by-ids ids)) selection-parents (mf/deref selection-parents-ref) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs index 53e80ca57d..635721a2da 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs @@ -9,6 +9,7 @@ [app.common.data :as d] [app.common.types.shape.layout :as ctl] [app.main.refs :as refs] + [app.main.ui.hooks :as hooks] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [app.main.ui.workspace.sidebar.options.menus.color-selection :refer [color-selection-menu]] [app.main.ui.workspace.sidebar.options.menus.component :refer [component-attrs component-menu]] @@ -36,6 +37,8 @@ layout-item-values (select-keys shape layout-item-attrs) [comp-ids comp-values] [[(:id shape)] (select-keys shape component-attrs)] + ids (hooks/use-equal-memo ids) + is-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-layout-child? ids)) is-layout-child? (mf/deref is-layout-child-ref) From f920d4213ea7be334f54d1559b3a6b05525cff50 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 24 May 2023 11:34:26 +0200 Subject: [PATCH 27/52] :sparkles: Fix problem with zoom --- .../src/app/main/ui/workspace/viewport/grid_layout_editor.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.scss b/frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.scss index 1526c3aaa6..443498da26 100644 --- a/frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.scss +++ b/frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.scss @@ -12,6 +12,7 @@ } .grid-editor-label { + position: absolute; background: none; width: 100%; height: 100%; From c0342a2c753d69ebdfcb6927b0eba162c30eab72 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 24 May 2023 11:46:30 +0200 Subject: [PATCH 28/52] :sparkles: Adds cell to shape options --- .../app/main/ui/workspace/sidebar/options.cljs | 2 +- .../options/{shapes => menus}/grid_cell.cljs | 2 +- .../ui/workspace/sidebar/options/shapes/bool.cljs | 13 ++++++++++++- .../workspace/sidebar/options/shapes/circle.cljs | 15 +++++++++++++-- .../workspace/sidebar/options/shapes/frame.cljs | 12 +++++++++++- .../workspace/sidebar/options/shapes/group.cljs | 11 +++++++++++ .../workspace/sidebar/options/shapes/image.cljs | 13 ++++++++++++- .../ui/workspace/sidebar/options/shapes/path.cljs | 13 ++++++++++++- .../ui/workspace/sidebar/options/shapes/rect.cljs | 14 +++++++++++++- .../workspace/sidebar/options/shapes/svg_raw.cljs | 13 ++++++++++++- .../ui/workspace/sidebar/options/shapes/text.cljs | 12 ++++++++++++ 11 files changed, 110 insertions(+), 10 deletions(-) rename frontend/src/app/main/ui/workspace/sidebar/options/{shapes => menus}/grid_cell.cljs (99%) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options.cljs b/frontend/src/app/main/ui/workspace/sidebar/options.cljs index 76076a7e0b..c2e44baf3f 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options.cljs @@ -18,12 +18,12 @@ [app.main.ui.workspace.sidebar.options.menus.align :refer [align-options]] [app.main.ui.workspace.sidebar.options.menus.bool :refer [bool-options]] [app.main.ui.workspace.sidebar.options.menus.exports :refer [exports-menu]] + [app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell] [app.main.ui.workspace.sidebar.options.menus.interactions :refer [interactions-menu]] [app.main.ui.workspace.sidebar.options.page :as page] [app.main.ui.workspace.sidebar.options.shapes.bool :as bool] [app.main.ui.workspace.sidebar.options.shapes.circle :as circle] [app.main.ui.workspace.sidebar.options.shapes.frame :as frame] - [app.main.ui.workspace.sidebar.options.shapes.grid-cell :as grid-cell] [app.main.ui.workspace.sidebar.options.shapes.group :as group] [app.main.ui.workspace.sidebar.options.shapes.image :as image] [app.main.ui.workspace.sidebar.options.shapes.multiple :as multiple] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/grid_cell.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/grid_cell.cljs similarity index 99% rename from frontend/src/app/main/ui/workspace/sidebar/options/shapes/grid_cell.cljs rename to frontend/src/app/main/ui/workspace/sidebar/options/menus/grid_cell.cljs index 9250d9a391..d99ee185b0 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/grid_cell.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/grid_cell.cljs @@ -4,7 +4,7 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns app.main.ui.workspace.sidebar.options.shapes.grid-cell +(ns app.main.ui.workspace.sidebar.options.menus.grid-cell (:require [app.common.data :as d] [app.common.data.macros :as dm] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs index 7cc2d18287..7e122d1d65 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs @@ -8,9 +8,11 @@ (:require [app.common.types.shape.layout :as ctl] [app.main.refs :as refs] + [app.main.ui.hooks :as hooks] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]] [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]] + [app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell] [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]] [app.main.ui.workspace.sidebar.options.menus.layout-container :refer [layout-container-flex-attrs layout-container-menu]] [app.main.ui.workspace.sidebar.options.menus.layout-item :refer [layout-item-attrs layout-item-menu]] @@ -39,13 +41,22 @@ is-grid-parent-ref (mf/use-memo (mf/deps ids) #(refs/grid-layout-child? ids)) is-grid-parent? (mf/deref is-grid-parent-ref) - is-layout-child-absolute? (ctl/layout-absolute? shape)] + is-layout-child-absolute? (ctl/layout-absolute? shape) + + ids (hooks/use-equal-memo ids) + parents-by-ids-ref (mf/use-memo (mf/deps ids) #(refs/parents-by-ids ids)) + parents (mf/deref parents-by-ids-ref)] [:* [:& measures-menu {:ids ids :type type :values measure-values :shape shape}] [:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values :multiple false}] + + (when (and (= (count ids) 1) is-layout-child? is-grid-parent?) + [:& grid-cell/options + {:shape (first parents) + :cell (ctl/get-cell-by-shape-id (first parents) (first ids))}]) (when is-layout-child? [:& layout-item-menu diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs index 75b809ab21..e985ee1e89 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs @@ -8,9 +8,11 @@ (:require [app.common.types.shape.layout :as ctl] [app.main.refs :as refs] + [app.main.ui.hooks :as hooks] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]] [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]] + [app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell] [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]] [app.main.ui.workspace.sidebar.options.menus.layout-container :refer [layout-container-flex-attrs layout-container-menu]] [app.main.ui.workspace.sidebar.options.menus.layout-item :refer [layout-item-attrs layout-item-menu]] @@ -41,14 +43,23 @@ is-grid-parent-ref (mf/use-memo (mf/deps ids) #(refs/grid-layout-child? ids)) is-grid-parent? (mf/deref is-grid-parent-ref) - is-layout-child-absolute? (ctl/layout-absolute? shape)] + is-layout-child-absolute? (ctl/layout-absolute? shape) + + ids (hooks/use-equal-memo ids) + parents-by-ids-ref (mf/use-memo (mf/deps ids) #(refs/parents-by-ids ids)) + parents (mf/deref parents-by-ids-ref)] [:* [:& measures-menu {:ids ids :type type :values measure-values :shape shape}] [:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values :multiple false}] - + + (when (and (= (count ids) 1) is-layout-child? is-grid-parent?) + [:& grid-cell/options + {:shape (first parents) + :cell (ctl/get-cell-by-shape-id (first parents) (first ids))}]) + (when is-layout-child? [:& layout-item-menu {:ids ids :type type diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs index 635721a2da..cafa9dbfd5 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs @@ -16,6 +16,7 @@ [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]] [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs-shape fill-menu]] [app.main.ui.workspace.sidebar.options.menus.frame-grid :refer [frame-grid]] + [app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell] [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]] [app.main.ui.workspace.sidebar.options.menus.layout-container :refer [layout-container-flex-attrs layout-container-menu]] [app.main.ui.workspace.sidebar.options.menus.layout-item :refer [layout-item-attrs layout-item-menu]] @@ -49,7 +50,11 @@ is-grid-parent? (mf/deref is-grid-parent-ref) is-flex-layout-container? (ctl/flex-layout? shape) - is-layout-child-absolute? (ctl/layout-absolute? shape)] + is-layout-child-absolute? (ctl/layout-absolute? shape) + + ids (hooks/use-equal-memo ids) + parents-by-ids-ref (mf/use-memo (mf/deps ids) #(refs/parents-by-ids ids)) + parents (mf/deref parents-by-ids-ref)] [:* [:& measures-menu {:ids [(:id shape)] :values measure-values @@ -63,6 +68,11 @@ :values constraint-values}]) [:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values :multiple false}] + (when (and (= (count ids) 1) is-layout-child? is-grid-parent?) + [:& grid-cell/options + {:shape (first parents) + :cell (ctl/get-cell-by-shape-id (first parents) (first ids))}]) + (when (or is-layout-child? is-flex-layout-container?) [:& layout-item-menu {:ids ids diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs index b7ce7775ef..3ee8055a3b 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs @@ -9,11 +9,13 @@ [app.common.data :as d] [app.common.types.shape.layout :as ctl] [app.main.refs :as refs] + [app.main.ui.hooks :as hooks] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [app.main.ui.workspace.sidebar.options.menus.color-selection :refer [color-selection-menu]] [app.main.ui.workspace.sidebar.options.menus.component :refer [component-attrs component-menu]] [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraints-menu]] [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-menu]] + [app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell] [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-menu]] [app.main.ui.workspace.sidebar.options.menus.layout-container :refer [layout-container-flex-attrs layout-container-menu]] [app.main.ui.workspace.sidebar.options.menus.layout-item :refer [layout-item-menu]] @@ -47,6 +49,10 @@ is-layout-child-absolute? (ctl/layout-absolute? shape) + ids (hooks/use-equal-memo ids) + parents-by-ids-ref (mf/use-memo (mf/deps ids) #(refs/parents-by-ids ids)) + parents (mf/deref parents-by-ids-ref) + type :group [measure-ids measure-values] (get-attrs [shape] objects :measure) [layer-ids layer-values] (get-attrs [shape] objects :layer) @@ -65,6 +71,11 @@ [:& component-menu {:ids comp-ids :values comp-values :shape shape}] ;;remove this in components-v2 [:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values :multiple false}] + (when (and (= (count ids) 1) is-layout-child? is-grid-parent?) + [:& grid-cell/options + {:shape (first parents) + :cell (ctl/get-cell-by-shape-id (first parents) (first ids))}]) + (when is-layout-child? [:& layout-item-menu {:type type diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/image.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/image.cljs index 42173c1c98..e880821985 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/image.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/image.cljs @@ -8,9 +8,11 @@ (:require [app.common.types.shape.layout :as ctl] [app.main.refs :as refs] + [app.main.ui.hooks :as hooks] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]] [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]] + [app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell] [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]] [app.main.ui.workspace.sidebar.options.menus.layout-container :refer [layout-container-flex-attrs layout-container-menu]] [app.main.ui.workspace.sidebar.options.menus.layout-item :refer [layout-item-attrs layout-item-menu]] @@ -41,13 +43,22 @@ is-grid-parent-ref (mf/use-memo (mf/deps ids) #(refs/grid-layout-child? ids)) is-grid-parent? (mf/deref is-grid-parent-ref) - is-layout-child-absolute? (ctl/layout-absolute? shape)] + is-layout-child-absolute? (ctl/layout-absolute? shape) + + ids (hooks/use-equal-memo ids) + parents-by-ids-ref (mf/use-memo (mf/deps ids) #(refs/parents-by-ids ids)) + parents (mf/deref parents-by-ids-ref)] [:* [:& measures-menu {:ids ids :type type :values measure-values :shape shape}] [:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values :multiple false}] + + (when (and (= (count ids) 1) is-layout-child? is-grid-parent?) + [:& grid-cell/options + {:shape (first parents) + :cell (ctl/get-cell-by-shape-id (first parents) (first ids))}]) (when is-layout-child? [:& layout-item-menu diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs index ec97ec6eb8..0c94e7b333 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs @@ -8,9 +8,11 @@ (:require [app.common.types.shape.layout :as ctl] [app.main.refs :as refs] + [app.main.ui.hooks :as hooks] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]] [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]] + [app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell] [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]] [app.main.ui.workspace.sidebar.options.menus.layout-container :refer [layout-container-flex-attrs layout-container-menu]] [app.main.ui.workspace.sidebar.options.menus.layout-item :refer [layout-item-attrs layout-item-menu]] @@ -41,7 +43,11 @@ is-grid-parent-ref (mf/use-memo (mf/deps ids) #(refs/grid-layout-child? ids)) is-grid-parent? (mf/deref is-grid-parent-ref) - is-layout-child-absolute? (ctl/layout-absolute? shape)] + is-layout-child-absolute? (ctl/layout-absolute? shape) + + ids (hooks/use-equal-memo ids) + parents-by-ids-ref (mf/use-memo (mf/deps ids) #(refs/parents-by-ids ids)) + parents (mf/deref parents-by-ids-ref)] [:* [:& measures-menu {:ids ids :type type @@ -49,6 +55,11 @@ :shape shape}] [:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values :multiple false}] + (when (and (= (count ids) 1) is-layout-child? is-grid-parent?) + [:& grid-cell/options + {:shape (first parents) + :cell (ctl/get-cell-by-shape-id (first parents) (first ids))}]) + (when is-layout-child? [:& layout-item-menu {:ids ids :type type diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs index ee63fe3b8f..545a7e9b7e 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs @@ -8,9 +8,11 @@ (:require [app.common.types.shape.layout :as ctl] [app.main.refs :as refs] + [app.main.ui.hooks :as hooks] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]] [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]] + [app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell] [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]] [app.main.ui.workspace.sidebar.options.menus.layout-container :refer [layout-container-flex-attrs layout-container-menu]] [app.main.ui.workspace.sidebar.options.menus.layout-item :refer [layout-item-attrs layout-item-menu]] @@ -41,13 +43,23 @@ is-grid-parent-ref (mf/use-memo (mf/deps ids) #(refs/grid-layout-child? ids)) is-grid-parent? (mf/deref is-grid-parent-ref) - is-layout-child-absolute? (ctl/layout-absolute? shape)] + is-layout-child-absolute? (ctl/layout-absolute? shape) + + ids (hooks/use-equal-memo ids) + parents-by-ids-ref (mf/use-memo (mf/deps ids) #(refs/parents-by-ids ids)) + parents (mf/deref parents-by-ids-ref)] [:* [:& measures-menu {:ids ids :type type :values measure-values :shape shape}] [:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values :multiple false}] + + (when (and (= (count ids) 1) is-layout-child? is-grid-parent?) + [:& grid-cell/options + {:shape (first parents) + :cell (ctl/get-cell-by-shape-id (first parents) (first ids))}]) + (when is-layout-child? [:& layout-item-menu {:ids ids diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs index 2d1aa06fb4..be577a2cce 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs @@ -10,9 +10,11 @@ [app.common.data :as d] [app.common.types.shape.layout :as ctl] [app.main.refs :as refs] + [app.main.ui.hooks :as hooks] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]] [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]] + [app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell] [app.main.ui.workspace.sidebar.options.menus.layout-container :refer [layout-container-flex-attrs layout-container-menu]] [app.main.ui.workspace.sidebar.options.menus.layout-item :refer [layout-item-attrs layout-item-menu]] [app.main.ui.workspace.sidebar.options.menus.measures :refer [measure-attrs measures-menu]] @@ -115,7 +117,11 @@ is-grid-parent-ref (mf/use-memo (mf/deps ids) #(refs/grid-layout-child? ids)) is-grid-parent? (mf/deref is-grid-parent-ref) - is-layout-child-absolute? (ctl/layout-absolute? shape)] + is-layout-child-absolute? (ctl/layout-absolute? shape) + + ids (hooks/use-equal-memo ids) + parents-by-ids-ref (mf/use-memo (mf/deps ids) #(refs/parents-by-ids ids)) + parents (mf/deref parents-by-ids-ref)] (when (contains? svg-elements tag) [:* @@ -125,6 +131,11 @@ :shape shape}] [:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values :multiple false}] + (when (and (= (count ids) 1) is-layout-child? is-grid-parent?) + [:& grid-cell/options + {:shape (first parents) + :cell (ctl/get-cell-by-shape-id (first parents) (first ids))}]) + (when is-layout-child? [:& layout-item-menu {:ids ids diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs index 0e8c82c030..aa40c8cdec 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs @@ -10,10 +10,12 @@ [app.common.types.shape.layout :as ctl] [app.main.data.workspace.texts :as dwt :refer [text-fill-attrs root-attrs paragraph-attrs text-attrs]] [app.main.refs :as refs] + [app.main.ui.hooks :as hooks] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [app.main.ui.workspace.sidebar.options.menus.color-selection :refer [color-selection-menu]] [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]] [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-menu fill-attrs]] + [app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell] [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]] [app.main.ui.workspace.sidebar.options.menus.layout-container :refer [layout-container-flex-attrs layout-container-menu]] [app.main.ui.workspace.sidebar.options.menus.layout-item :refer [layout-item-attrs layout-item-menu]] @@ -39,6 +41,11 @@ layout-container-values (select-keys shape layout-container-flex-attrs) is-layout-child-absolute? (ctl/layout-absolute? shape) + + ids (hooks/use-equal-memo ids) + parents-by-ids-ref (mf/use-memo (mf/deps ids) #(refs/parents-by-ids ids)) + parents (mf/deref parents-by-ids-ref) + state-map (mf/deref refs/workspace-editor-state) shared-libs (mf/deref refs/workspace-libraries) @@ -83,6 +90,11 @@ :shape shape}] [:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values :multiple false}] + (when (and (= (count ids) 1) is-layout-child? is-grid-parent?) + [:& grid-cell/options + {:shape (first parents) + :cell (ctl/get-cell-by-shape-id (first parents) (first ids))}]) + (when is-layout-child? [:& layout-item-menu {:ids ids From 7b410d46ec20c388c1944c0bac109dcb1421d0bf Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 24 May 2023 12:48:30 +0200 Subject: [PATCH 29/52] :sparkles: Editing on double click --- frontend/src/app/main/ui/workspace/viewport.cljs | 5 +++-- frontend/src/app/main/ui/workspace/viewport/actions.cljs | 8 ++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index a45923394c..a9b04c51d6 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -168,7 +168,7 @@ on-click (actions/on-click hover selected edition drawing-path? drawing-tool space? selrect z?) on-context-menu (actions/on-context-menu hover hover-ids workspace-read-only?) - on-double-click (actions/on-double-click hover hover-ids drawing-path? base-objects edition drawing-tool z? workspace-read-only?) + on-double-click (actions/on-double-click hover hover-ids hover-top-frame-id drawing-path? base-objects edition drawing-tool z? workspace-read-only?) on-drag-enter (actions/on-drag-enter) on-drag-over (actions/on-drag-over) on-drop (actions/on-drop file) @@ -204,7 +204,8 @@ (>= zoom 8)) show-text-editor? (and editing-shape (= :text (:type editing-shape))) - hover-grid? (and (some? @hover-top-frame-id) (ctl/grid-layout? objects @hover-top-frame-id)) + hover-grid? (and (some? @hover-top-frame-id) + (ctl/grid-layout? objects @hover-top-frame-id)) show-grid-editor? (and editing-shape (ctl/grid-layout? editing-shape)) show-presence? page-id diff --git a/frontend/src/app/main/ui/workspace/viewport/actions.cljs b/frontend/src/app/main/ui/workspace/viewport/actions.cljs index f9ec1e96aa..a1b70a0211 100644 --- a/frontend/src/app/main/ui/workspace/viewport/actions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/actions.cljs @@ -187,10 +187,10 @@ (st/emit! (dw/increase-zoom pt))))))))) (defn on-double-click - [hover hover-ids drawing-path? objects edition drawing-tool z? workspace-read-only?] + [hover hover-ids hover-top-frame-id drawing-path? objects edition drawing-tool z? workspace-read-only?] (mf/use-callback - (mf/deps @hover @hover-ids drawing-path? edition drawing-tool @z? workspace-read-only?) + (mf/deps @hover @hover-ids @hover-top-frame-id drawing-path? edition drawing-tool @z? workspace-read-only?) (fn [event] (dom/stop-propagation event) (when-not @z? @@ -214,8 +214,8 @@ (st/emit! (dw/select-shape id) (dw/start-editing-selected)) - (ctl/grid-layout? shape) - (st/emit! (dw/start-edition-mode id)) + (ctl/grid-layout? objects @hover-top-frame-id) + (st/emit! (dw/start-edition-mode @hover-top-frame-id)) :else (let [;; We only get inside childrens of the hovering shape From b83c35b0ddd2702868f53639ceb308621ed49fe0 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 24 May 2023 14:13:18 +0200 Subject: [PATCH 30/52] :sparkles: Refresh grid cells after change static/absolute item --- frontend/src/app/main/data/workspace/shape_layout.cljs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/main/data/workspace/shape_layout.cljs b/frontend/src/app/main/data/workspace/shape_layout.cljs index 4e8c5e75d5..777d9552df 100644 --- a/frontend/src/app/main/data/workspace/shape_layout.cljs +++ b/frontend/src/app/main/data/workspace/shape_layout.cljs @@ -505,7 +505,7 @@ (assoc :layout-item-v-sizing :fix)))) (defn fix-parent-sizing - [objects ids-set changes parent] + [parent objects ids-set changes] (let [auto-width? (ctl/auto-width? parent) auto-height? (ctl/auto-height? parent) @@ -553,7 +553,12 @@ (rx/of (dwu/start-undo-transaction undo-id) (dwc/update-shapes ids #(d/deep-merge (or % {}) changes)) (dwc/update-shapes children-ids (partial fix-child-sizing objects changes)) - (dwc/update-shapes parent-ids (partial fix-parent-sizing objects (set ids) changes)) + (dwc/update-shapes parent-ids + (fn [parent] + (-> parent + (fix-parent-sizing objects (set ids) changes) + (cond-> (ctl/grid-layout? parent) + (ctl/assign-cells))))) (ptk/data-event :layout/update ids) (dwu/commit-undo-transaction undo-id)))))) From 03c64303f5a9c4879cb73adb774144910f930ddd Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 26 May 2023 11:46:38 +0200 Subject: [PATCH 31/52] :sparkles: Support rotated UI --- .../geom/shapes/grid_layout/positions.cljc | 3 +- .../main/ui/workspace/viewport/actions.cljs | 9 +- .../viewport/grid_layout_editor.cljs | 172 ++++++++++-------- 3 files changed, 102 insertions(+), 82 deletions(-) diff --git a/common/src/app/common/geom/shapes/grid_layout/positions.cljc b/common/src/app/common/geom/shapes/grid_layout/positions.cljc index 4671ac4d54..3c0e2a60cb 100644 --- a/common/src/app/common/geom/shapes/grid_layout/positions.cljc +++ b/common/src/app/common/geom/shapes/grid_layout/positions.cljc @@ -242,7 +242,6 @@ children (->> (cph/get-immediate-children objects (:id frame)) (remove :hidden) (map #(vector (gpo/parent-coords-bounds (:points %) (:points frame)) %))) - layout-data (ld/calc-layout-data frame children (:points frame)) - position (gmt/transform-point-center position (gco/center-shape frame) (:transform-inverse frame))] + layout-data (ld/calc-layout-data frame children (:points frame))] (get-position-grid-coord layout-data position))) diff --git a/frontend/src/app/main/ui/workspace/viewport/actions.cljs b/frontend/src/app/main/ui/workspace/viewport/actions.cljs index a1b70a0211..6d9e67d388 100644 --- a/frontend/src/app/main/ui/workspace/viewport/actions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/actions.cljs @@ -160,6 +160,7 @@ (fn [event] (when (and (nil? selrect) (or (dom/class? (dom/get-target event) "viewport-controls") + (dom/child? (dom/get-target event) (dom/query ".viewport-controls")) (dom/class? (dom/get-target event) "viewport-selrect"))) (let [ctrl? (kbd/ctrl? event) shift? (kbd/shift? event) @@ -233,11 +234,11 @@ (if workspace-read-only? (dom/prevent-default event) (when (or (dom/class? (dom/get-target event) "viewport-controls") - (dom/class? (dom/get-target event) "viewport-selrect")) - (dom/prevent-default event) - + (dom/child? (dom/get-target event) (dom/query ".viewport-controls")) + (dom/class? (dom/get-target event) "viewport-selrect") + workspace-read-only?) (let [position (dom/get-client-position event)] - ;; Delayed callback because we need to wait to the previous context menu to be closed + ;; Delayed callback because we need to wait to the previous context menu to be closed (timers/schedule #(st/emit! (if (some? @hover) 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 364bb1da7b..a1e00b5d8d 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 @@ -9,7 +9,9 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] + [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] + [app.common.geom.shapes :as gsh] [app.common.geom.shapes.grid-layout :as gsg] [app.common.geom.shapes.points :as gpo] [app.common.math :as mth] @@ -170,7 +172,10 @@ direction (unchecked-get props "direction") layout-data (unchecked-get props "layout-data") - cursor (if (= direction :row) (cur/scale-ns 0) (cur/scale-ew 0)) + cursor + (if (= direction :row) + (cur/scale-ns (:rotation shape)) + (cur/scale-ew (:rotation shape))) handle-drag-position (mf/use-callback @@ -277,6 +282,8 @@ cell-origin (gpo/origin cell-bounds) cell-width (gpo/width-points cell-bounds) cell-height (gpo/height-points cell-bounds) + cell-center (gsh/center-points cell-bounds) + cell-origin (gpt/transform cell-origin (gmt/transform-in cell-center (:transform-inverse shape))) handle-pointer-enter (mf/use-callback @@ -298,7 +305,8 @@ [:g.cell-editor [:rect - {:class (dom/classnames (css :grid-cell-outline) true + {:transform (dm/str (gmt/transform-in cell-center (:transform shape))) + :class (dom/classnames (css :grid-cell-outline) true (css :hover) hover? (css :selected) selected?) :x (:x cell-origin) @@ -323,7 +331,7 @@ [:right (+ (:x cell-origin) cell-width (/ -10 zoom)) (:y cell-origin) (/ 20 zoom) cell-height :column] [:bottom (:x cell-origin) (+ (:y cell-origin) cell-height (/ -10 zoom)) cell-width (/ 20 zoom) :row] [:left (+ (:x cell-origin) (/ -10 zoom)) (:y cell-origin) (/ 20 zoom) cell-height :column]]] - [:* + [:g {:transform (dm/str (gmt/transform-in cell-center (:transform shape)))} (for [[handler x y width height dir] handlers] [:& resize-cell-handler {:key (dm/str "resize-" (d/name handler) "-" (:id cell)) :shape shape @@ -402,6 +410,7 @@ (let [shape (unchecked-get props "shape") index (unchecked-get props "index") + last? (unchecked-get props "last?") track-before (unchecked-get props "track-before") track-after (unchecked-get props "track-after") snap-pixel? (unchecked-get props "snap-pixel?") @@ -411,35 +420,54 @@ type (unchecked-get props "type") zoom (unchecked-get props "zoom") + bounds (:points shape) + hv #(gpo/start-hv bounds %) + vv #(gpo/start-vv bounds %) + [layout-gap-row layout-gap-col] (ctl/gaps shape) {:keys [handle-pointer-down handle-lost-pointer-capture handle-pointer-move]} (use-resize-track type shape index track-before track-after zoom snap-pixel?) - [x y width height] + [width height] (if (= type :column) - [(- (:x start-p) layout-gap-col (/ (- (max layout-gap-col (/ 16 zoom)) layout-gap-col) 2)) - (:y start-p) - (max layout-gap-col (/ 16 zoom)) + [(max layout-gap-col (/ 16 zoom)) (+ row-total-size row-total-gap)] - [(:x start-p) - (- (:y start-p) layout-gap-row (/ (- (max layout-gap-row (/ 16 zoom)) layout-gap-row) 2)) - (+ column-total-size column-total-gap) - (max layout-gap-row (/ 16 zoom))])] + [(+ column-total-size column-total-gap) + (max layout-gap-row (/ 16 zoom))]) + + start-p + (cond-> start-p + (and (= type :column) (= index 0)) + (gpt/subtract (hv width)) + + (and (= type :row) (= index 0)) + (gpt/subtract (vv height)) + + (and (= type :column) (not= index 0) (not last?)) + (-> (gpt/subtract (hv (/ layout-gap-col 2))) + (gpt/subtract (hv (/ width 2)))) + + (and (= type :row) (not= index 0) (not last?)) + (-> (gpt/subtract (vv (/ layout-gap-row 2))) + (gpt/subtract (vv (/ height 2)))))] [:rect.resize-track-handler - {:x x - :y y - :class (if (= type :column) - "resize-ew-0" - "resize-ns-0") + {:x (:x start-p) + :y (:y start-p) :height height :width width :on-pointer-down handle-pointer-down :on-lost-pointer-capture handle-lost-pointer-capture :on-pointer-move handle-pointer-move - :style {:fill "transparent"}}])) + :transform (dm/str (gmt/transform-in start-p (:transform shape))) + :style {:fill "transparent" + :opacity 0.5 + :stroke-width 0 + :cursor (if (= type :column) + (cur/resize-ew (:rotation shape)) + (cur/resize-ns (:rotation shape)))}}])) (mf/defc track-marker {::mf/wrap-props false} @@ -482,9 +510,11 @@ :on-lost-pointer-capture handle-lost-pointer-capture :on-pointer-move handle-pointer-move :class (css :grid-track-marker) + :transform (dm/str (gmt/transform-in center (:transform shape))) :style {:cursor (if (= type :column) - (cur/resize-ew 0) - (cur/resize-ns 0))}} + (cur/resize-ew (:rotation shape)) + (cur/resize-ns (:rotation shape)))}} + [:polygon {:class (css :marker-shape) :points (->> marker-points (map #(dm/fmt "%,%" (:x %) (:y %))) @@ -493,7 +523,7 @@ :x text-x :y text-y :width (/ 26.26 zoom) - :height (/ 32 zoom) + :height (/ 36 zoom) :text-anchor "middle" :dominant-baseline "middle"} (dm/str value)]])) @@ -512,38 +542,28 @@ track-input-ref (mf/use-ref) [layout-gap-row layout-gap-col] (ctl/gaps shape) + bounds (:points shape) - origin (gpo/origin bounds) vv #(gpo/start-vv bounds %) hv #(gpo/start-hv bounds %) start-p (:start-p track-data) - relative (gpt/to-vec origin start-p) + + hpt (gpo/project-point bounds :h start-p) + vpt (gpo/project-point bounds :v start-p) marker-p (if (= type :column) - (-> origin - (gpt/add (hv (:x relative))) + (-> hpt (gpt/subtract (vv (/ 20 zoom))) (cond-> (not= index 0) (gpt/subtract (hv (/ layout-gap-col 2))))) - (-> origin - (gpt/add (vv (:y relative))) + (-> vpt (gpt/subtract (hv (/ 20 zoom))) (cond-> (not= index 0) (gpt/subtract (vv (/ layout-gap-row 2)))))) - text-p - (if (= type :column) - (-> origin - (gpt/add (hv (:x relative))) - (gpt/subtract (vv (/ 36 zoom)))) - (-> origin - (gpt/add (vv (:y relative))) - (gpt/subtract (hv (/ (:size track-data) 2))) - (gpt/subtract (hv (/ 16 zoom))) - (gpt/add (vv (/ (:size track-data) 2))) - (gpt/subtract (vv (/ 18 zoom))))) + text-p (if (= type :column) hpt vpt) handle-blur-track-input (mf/use-callback @@ -585,8 +605,8 @@ track-list-prop (if (= type :column) :column-tracks :row-tracks) [text-x text-y text-width text-height] (if (= type :column) - [(:x text-p) (:y text-p) (max 0 (- (:size track-data) 4)) (/ 32 zoom)] - [(:x text-p) (:y text-p) (:size track-data) (/ 36 zoom)]) + [(:x text-p) (- (:y text-p) (/ 36 zoom)) (max 0 (:size track-data)) (/ 36 zoom)] + [(- (:x text-p) (max 0 (:size track-data))) (- (:y text-p) (/ 36 zoom)) (max 0 (:size track-data)) (/ 36 zoom)]) track-before (get-in layout-data [track-list-prop (dec index)])] @@ -596,17 +616,9 @@ (dom/set-value! (mf/ref-val track-input-ref) (format-size track-data)))) [:g.track - [:g {:transform (when (= type :row) (dm/fmt "rotate(-90 % %)" (:x marker-p) (:y marker-p)))} - [:& track-marker {:center marker-p - :index index - :shape shape - :snap-pixel? snap-pixel? - :track-after track-data - :track-before track-before - :type type - :value (dm/str (inc index)) - :zoom zoom}]] - [:g {:transform (when (= type :row) (dm/fmt "rotate(-90 % %)" (+ (:x text-p) (/ (:size track-data) 2)) (+ (:y text-p) (/ 36 zoom 2))))} + [:g {:transform (if (= type :column) + (dm/str (gmt/transform-in text-p (:transform shape))) + (dm/str (gmt/transform-in text-p (gmt/rotate (:transform shape) -90))))} [:foreignObject {:x text-x :y text-y :width text-width :height text-height} [:input {:ref track-input-ref @@ -617,6 +629,18 @@ :on-key-down handle-keydown-track-input :on-blur handle-blur-track-input}]]] + [:g {:transform (when (= type :row) (dm/fmt "rotate(-90 % %)" (:x marker-p) (:y marker-p)))} + [:& track-marker + {:center marker-p + :index index + :shape shape + :snap-pixel? snap-pixel? + :track-after track-data + :track-before track-before + :type type + :value (dm/str (inc index)) + :zoom zoom}]] + [:& resize-track-handler {:index index :layout-data layout-data @@ -668,8 +692,6 @@ height (gpo/height-points bounds) origin (gpo/origin bounds) - [layout-gap-row layout-gap-col] (ctl/gaps shape) - {:keys [row-tracks column-tracks] :as layout-data} (mf/use-memo (mf/deps shape children) @@ -705,16 +727,18 @@ [:& grid-editor-frame {:zoom zoom :bounds bounds}] (let [start-p (-> origin (gpt/add (hv width)))] - [:& plus-btn {:start-p start-p - :zoom zoom - :type :column - :on-click handle-add-column}]) + [:g {:transform (dm/str (gmt/transform-in start-p (:transform shape)))} + [:& plus-btn {:start-p start-p + :zoom zoom + :type :column + :on-click handle-add-column}]]) (let [start-p (-> origin (gpt/add (vv height)))] - [:& plus-btn {:start-p start-p - :zoom zoom - :type :row - :on-click handle-add-row}]) + [:g {:transform (dm/str (gmt/transform-in start-p (:transform shape)))} + [:& plus-btn {:start-p start-p + :zoom zoom + :type :row + :on-click handle-add-row}]]) (for [[idx column-data] (d/enumerate column-tracks)] [:& track {:key (dm/str "column-track-" idx) @@ -729,12 +753,10 @@ ;; Last track resize handler (when-not (empty? column-tracks) (let [last-track (last column-tracks) - start-p (:start-p (last column-tracks)) - relative (gpt/to-vec origin start-p) - marker-p (-> origin - (gpt/add (hv (:x relative))) - (gpt/subtract (vv (/ 20 zoom))) - (gpt/add (hv (:size last-track))))] + start-p (:start-p last-track) + end-p (gpt/add start-p (hv (:size last-track))) + marker-p (-> (gpo/project-point bounds :h end-p) + (gpt/subtract (vv (/ 20 zoom))))] [:g.track [:& track-marker {:center marker-p :index (count column-tracks) @@ -746,10 +768,11 @@ :zoom zoom}] [:& resize-track-handler {:index (count column-tracks) + :last? true :shape shape :layout-data layout-data :snap-pixel? snap-pixel? - :start-p (-> start-p (gpt/add (hv (+ layout-gap-col (:size last-track))))) + :start-p end-p :type :column :track-before (last column-tracks) :zoom zoom}]])) @@ -766,13 +789,10 @@ (when-not (empty? row-tracks) (let [last-track (last row-tracks) - start-p (:start-p (last row-tracks)) - relative (gpt/to-vec origin start-p) - marker-p - (-> origin - (gpt/add (vv (:y relative))) - (gpt/subtract (hv (/ 20 zoom))) - (gpt/add (vv (:size last-track))))] + start-p (:start-p last-track) + end-p (gpt/add start-p (vv (:size last-track))) + marker-p (-> (gpo/project-point bounds :v end-p) + (gpt/subtract (hv (/ 20 zoom))))] [:g.track [:g {:transform (dm/fmt "rotate(-90 % %)" (:x marker-p) (:y marker-p))} [:& track-marker {:center marker-p @@ -785,10 +805,10 @@ :zoom zoom}]] [:& resize-track-handler {:index (count row-tracks) + :last? true :shape shape :layout-data layout-data - :start-p (-> start-p - (gpt/add (vv (+ layout-gap-row (:size last-track))))) + :start-p end-p :type :row :track-before (last row-tracks) :snap-pixel? snap-pixel? From b13db69cf90d49d042bd1f72a35a87faf8928f2f Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 30 May 2023 17:52:11 +0200 Subject: [PATCH 32/52] :sparkles: Grid layout polishing --- .../geom/shapes/grid_layout/positions.cljc | 2 - .../src/app/main/data/workspace/common.cljs | 33 ++-- .../src/app/main/data/workspace/edition.cljs | 1 + .../app/main/data/workspace/shape_layout.cljs | 13 +- .../app/main/data/workspace/shortcuts.cljs | 7 +- .../app/main/ui/workspace/context_menu.cljs | 12 +- .../main/ui/workspace/sidebar/options.cljs | 22 ++- .../sidebar/options/menus/frame_grid.cljs | 3 +- .../options/menus/layout_container.cljs | 186 ++++++++++-------- .../src/app/main/ui/workspace/viewport.cljs | 2 +- .../main/ui/workspace/viewport/actions.cljs | 4 +- .../app/main/ui/workspace/viewport/hooks.cljs | 4 +- frontend/translations/en.po | 3 + frontend/translations/es.po | 3 + 14 files changed, 179 insertions(+), 116 deletions(-) diff --git a/common/src/app/common/geom/shapes/grid_layout/positions.cljc b/common/src/app/common/geom/shapes/grid_layout/positions.cljc index 3c0e2a60cb..cea3162d42 100644 --- a/common/src/app/common/geom/shapes/grid_layout/positions.cljc +++ b/common/src/app/common/geom/shapes/grid_layout/positions.cljc @@ -8,9 +8,7 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] - [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] - [app.common.geom.shapes.common :as gco] [app.common.geom.shapes.grid-layout.layout-data :as ld] [app.common.geom.shapes.points :as gpo] [app.common.geom.shapes.transforms :as gtr] diff --git a/frontend/src/app/main/data/workspace/common.cljs b/frontend/src/app/main/data/workspace/common.cljs index 11fef1fd55..5aecdc0b91 100644 --- a/frontend/src/app/main/data/workspace/common.cljs +++ b/frontend/src/app/main/data/workspace/common.cljs @@ -6,8 +6,11 @@ (ns app.main.data.workspace.common (:require + [app.common.data.macros :as dm] [app.common.logging :as log] + [app.common.types.shape.layout :as ctl] [app.main.data.workspace.changes :as dch] + [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.undo :as dwu] [app.util.router :as rt] [beicon.core :as rx] @@ -53,10 +56,13 @@ (ptk/reify ::undo ptk/WatchEvent (watch [it state _] - (let [edition (get-in state [:workspace-local :edition]) + (let [objects (wsh/lookup-page-objects state) + edition (get-in state [:workspace-local :edition]) drawing (get state :workspace-drawing)] + ;; Editors handle their own undo's - (when (and (nil? edition) (nil? (:object drawing))) + (when (or (and (nil? edition) (nil? (:object drawing))) + (ctl/grid-layout? objects edition)) (let [undo (:workspace-undo state) items (:items undo) index (or (:index undo) (dec (count items)))] @@ -64,14 +70,17 @@ (let [item (get items index) changes (:undo-changes item) undo-group (:undo-group item) - find-first-group-idx (fn ffgidx[index] - (let [item (get items index)] - (if (= (:undo-group item) undo-group) - (ffgidx (dec index)) - (inc index)))) - undo-group-index (when undo-group - (find-first-group-idx index))] + find-first-group-idx + (fn [index] + (if (= (dm/get-in items [index :undo-group]) undo-group) + (recur (dec index)) + (inc index))) + + undo-group-index + (when undo-group + (find-first-group-idx index))] + (if undo-group (rx/of (undo-to-index (dec undo-group-index))) (rx/of (dwu/materialize-undo changes (dec index)) @@ -117,9 +126,11 @@ (ptk/reify ::undo-to-index ptk/WatchEvent (watch [it state _] - (let [edition (get-in state [:workspace-local :edition]) + (let [objects (wsh/lookup-page-objects state) + edition (get-in state [:workspace-local :edition]) drawing (get state :workspace-drawing)] - (when-not (or (some? edition) (not-empty drawing)) + (when-not (and (or (some? edition) (not-empty drawing)) + (not (ctl/grid-layout? objects edition))) (let [undo (:workspace-undo state) items (:items undo) index (or (:index undo) (dec (count items)))] diff --git a/frontend/src/app/main/data/workspace/edition.cljs b/frontend/src/app/main/data/workspace/edition.cljs index 66a33acf5e..5318424131 100644 --- a/frontend/src/app/main/data/workspace/edition.cljs +++ b/frontend/src/app/main/data/workspace/edition.cljs @@ -45,4 +45,5 @@ (let [id (get-in state [:workspace-local :edition])] (-> state (update :workspace-local dissoc :edition) + (dissoc :workspace-grid-edition) (cond-> (some? id) (update-in [:workspace-local :edit-path] dissoc id))))))) diff --git a/frontend/src/app/main/data/workspace/shape_layout.cljs b/frontend/src/app/main/data/workspace/shape_layout.cljs index 777d9552df..2642bb3b77 100644 --- a/frontend/src/app/main/data/workspace/shape_layout.cljs +++ b/frontend/src/app/main/data/workspace/shape_layout.cljs @@ -343,22 +343,21 @@ (create-layout-from-selection type)) (dwu/commit-undo-transaction undo-id)))))) -(defn toggle-layout-flex - [] +(defn toggle-layout + [type] (ptk/reify ::toggle-layout-flex ptk/WatchEvent (watch [_ state _] - (let [page-id (:current-page-id state) - objects (wsh/lookup-page-objects state page-id) + (let [objects (wsh/lookup-page-objects state) selected (wsh/lookup-selected state) selected-shapes (map (d/getf objects) selected) single? (= (count selected-shapes) 1) - has-flex-layout? (and single? (ctl/flex-layout? objects (:id (first selected-shapes))))] + has-layout? (and single? (ctl/any-layout? objects (:id (first selected-shapes))))] (when (not= 0 (count selected)) - (if has-flex-layout? + (if has-layout? (rx/of (remove-layout selected)) - (rx/of (create-layout :flex)))))))) + (rx/of (create-layout type)))))))) (defn update-layout [ids changes] diff --git a/frontend/src/app/main/data/workspace/shortcuts.cljs b/frontend/src/app/main/data/workspace/shortcuts.cljs index 4cf059c95a..6f2bb6c502 100644 --- a/frontend/src/app/main/data/workspace/shortcuts.cljs +++ b/frontend/src/app/main/data/workspace/shortcuts.cljs @@ -219,7 +219,12 @@ :toggle-layout-flex {:tooltip (ds/shift "A") :command "shift+a" :subsections [:modify-layers] - :fn #(emit-when-no-readonly (dwsl/toggle-layout-flex))} + :fn #(emit-when-no-readonly (dwsl/toggle-layout :flex))} + + :toggle-layout-grid {:tooltip (ds/meta-shift "A") + :command (ds/c-mod "shift+a") + :subsections [:modify-layers] + :fn #(emit-when-no-readonly (dwsl/toggle-layout :grid))} ;; TOOLS diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs index c2d8995453..59c5d7ed66 100644 --- a/frontend/src/app/main/ui/workspace/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/context_menu.cljs @@ -413,9 +413,10 @@ is-frame? (and single? has-frame?) is-flex-container? (and is-frame? (= :flex (:layout (first shapes)))) ids (->> shapes (map :id)) - add-flex #(st/emit! (if is-frame? - (dwsl/create-layout-from-id ids :flex true) - (dwsl/create-layout-from-selection :flex))) + 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))] [:* @@ -424,7 +425,10 @@ [:& menu-separator] [:& menu-entry {:title (tr "workspace.shape.menu.add-flex") :shortcut (sc/get-tooltip :toggle-layout-flex) - :on-click add-flex}]]) + :on-click #(add-layout :flex)}] + [:& menu-entry {:title (tr "workspace.shape.menu.add-grid") + :shortcut (sc/get-tooltip :toggle-layout-grid) + :on-click #(add-layout :grid)}]]) (when is-flex-container? [:div [:& menu-separator] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options.cljs b/frontend/src/app/main/ui/workspace/sidebar/options.cljs index c2e44baf3f..6151dade2c 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options.cljs @@ -7,8 +7,10 @@ (ns app.main.ui.workspace.sidebar.options (:require [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.geom.shapes :as gsh] [app.common.pages.helpers :as cph] + [app.common.types.shape.layout :as ctl] [app.main.data.workspace :as udw] [app.main.refs :as refs] [app.main.store :as st] @@ -20,6 +22,7 @@ [app.main.ui.workspace.sidebar.options.menus.exports :refer [exports-menu]] [app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell] [app.main.ui.workspace.sidebar.options.menus.interactions :refer [interactions-menu]] + [app.main.ui.workspace.sidebar.options.menus.layout-container :as layout-container] [app.main.ui.workspace.sidebar.options.page :as page] [app.main.ui.workspace.sidebar.options.shapes.bool :as bool] [app.main.ui.workspace.sidebar.options.shapes.circle :as circle] @@ -68,15 +71,15 @@ (let [drawing (mf/deref refs/workspace-drawing) objects (mf/deref refs/workspace-page-objects) shared-libs (mf/deref refs/workspace-libraries) + edition (mf/deref refs/selected-edition) grid-edition (mf/deref refs/workspace-grid-edition) + selected-shapes (into [] (keep (d/getf objects)) selected) first-selected-shape (first selected-shapes) shape-parent-frame (cph/get-frame objects (:frame-id first-selected-shape)) - [grid-id {cell-id :selected}] - (d/seek (fn [[_ {:keys [selected]}]] (some? selected)) grid-edition) - - grid-cell-selected? (and (some? grid-id) (some? cell-id)) + edit-grid? (ctl/grid-layout? objects edition) + selected-cell (dm/get-in grid-edition [edition :selected]) on-change-tab (fn [options-mode] @@ -96,10 +99,15 @@ [:& align-options] [:& bool-options] (cond - grid-cell-selected? + (some? selected-cell) [:& grid-cell/options - {:shape (get objects grid-id) - :cell (get-in objects [grid-id :layout-grid-cells cell-id])}] + {:shape (get objects edition) + :cell (dm/get-in objects [edition :layout-grid-cells selected-cell])}] + + edit-grid? + [:& layout-container/grid-layout-edition + {:ids [edition] + :values (get objects edition)}] (d/not-empty? drawing) [:& shape-options diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs index 0573166a3d..aba9e6c672 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs @@ -238,7 +238,8 @@ [:button.btn-options {:disabled is-default :on-click handle-set-as-default} (tr "workspace.options.grid.params.set-default")]]]])) -(mf/defc frame-grid [{:keys [shape]}] +(mf/defc frame-grid + [{:keys [shape]}] (let [id (:id shape) saved-grids (mf/deref workspace-saved-grids) default-grid-params (mf/use-memo (mf/deps saved-grids) #(merge dw/default-grid-params saved-grids)) 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 c4ef62818e..ce38aede0e 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 @@ -477,7 +477,7 @@ (mf/defc layout-container-menu {::mf/wrap [#(mf/memo' % (mf/check-props ["ids" "values" "multiple"]))]} - [{:keys [ids values multiple] :as props}] + [{:keys [ids values multiple] :as props}] (let [open? (mf/use-state false) ;; Display @@ -594,53 +594,7 @@ (fn [value type] (if (= type :row) (st/emit! (dwsl/update-layout ids {:layout-align-content value})) - (st/emit! (dwsl/update-layout ids {:layout-justify-content value}))))) - - - ;;Grid columns - column-grid-values (:layout-grid-columns values) - grid-columns-open? (mf/use-state false) - toggle-columns-info (mf/use-callback - (fn [_] - (swap! grid-columns-open? not))) - - ; Grid rows / columns - rows-grid-values (:layout-grid-rows values) - grid-rows-open? (mf/use-state false) - toggle-rows-info - (mf/use-callback - (fn [_] - (swap! grid-rows-open? not))) - - add-new-element - (mf/use-callback - (mf/deps ids) - (fn [type value] - (st/emit! (dwsl/add-layout-track ids type value)))) - - remove-element - (mf/use-callback - (mf/deps ids) - (fn [type index] - (st/emit! (dwsl/remove-layout-track ids type index)))) - - set-column-value - (mf/use-callback - (mf/deps ids) - (fn [type index value] - (st/emit! (dwsl/change-layout-track ids type index {:value value})))) - - set-column-type - (mf/use-callback - (mf/deps ids) - (fn [type index track-type] - (let [value (case track-type - :auto nil - :flex 1 - :percent 20 - :fixed 100)] - (st/emit! (dwsl/change-layout-track ids type index {:value value - :type track-type})))))] + (st/emit! (dwsl/update-layout ids {:layout-justify-content value})))))] [:div.element-set [:div.element-set-title @@ -751,36 +705,112 @@ :set-justify set-justify-grid}] [:& justify-grid-row {:is-col? false :justify-items grid-justify-content-row - :set-justify set-justify-grid}]]] - - [:& grid-columns-row {:is-col? true - :expanded? @grid-columns-open? - :toggle toggle-columns-info - :column-values column-grid-values - :add-new-element add-new-element - :set-column-value set-column-value - :set-column-type set-column-type - :remove-element remove-element}] - - [:& grid-columns-row {:is-col? false - :expanded? @grid-rows-open? - :toggle toggle-rows-info - :column-values rows-grid-values - :add-new-element add-new-element - :set-column-value set-column-value - :set-column-type set-column-type - :remove-element remove-element}] - - [:& gap-section {:is-col? is-col? - :wrap-type wrap-type - :gap-selected? gap-selected? - :set-gap set-gap - :gap-value (:layout-gap values)}] - - [:& padding-section {:values values - :on-change-style change-padding-type - :on-change on-padding-change}]] - + :set-justify set-justify-grid}]]]] ;; Default if not grid or flex nil)))])) + +(mf/defc grid-layout-edition + {::mf/wrap [#(mf/memo' % (mf/check-props ["ids" "values"]))]} + [{:keys [ids values] :as props}] + (let [;; Gap + gap-selected? (mf/use-state :none) + + set-gap + (fn [gap-multiple? type val] + (if gap-multiple? + (st/emit! (dwsl/update-layout ids {:layout-gap {:row-gap val :column-gap val}})) + (st/emit! (dwsl/update-layout ids {:layout-gap {type val}})))) + + ;; Padding + change-padding-type + (fn [type] + (st/emit! (dwsl/update-layout ids {:layout-padding-type type}))) + + on-padding-change + (fn [type prop val] + (cond + (and (= type :simple) (= prop :p1)) + (st/emit! (dwsl/update-layout ids {:layout-padding {:p1 val :p3 val}})) + + (and (= type :simple) (= prop :p2)) + (st/emit! (dwsl/update-layout ids {:layout-padding {:p2 val :p4 val}})) + + :else + (st/emit! (dwsl/update-layout ids {:layout-padding {prop val}})))) + + ;;Grid columns + column-grid-values (:layout-grid-columns values) + grid-columns-open? (mf/use-state false) + toggle-columns-info (mf/use-callback + (fn [_] + (swap! grid-columns-open? not))) + + ; Grid rows / columns + rows-grid-values (:layout-grid-rows values) + grid-rows-open? (mf/use-state false) + toggle-rows-info + (mf/use-callback + (fn [_] + (swap! grid-rows-open? not))) + + add-new-element + (mf/use-callback + (mf/deps ids) + (fn [type value] + (st/emit! (dwsl/add-layout-track ids type value)))) + + remove-element + (mf/use-callback + (mf/deps ids) + (fn [type index] + (st/emit! (dwsl/remove-layout-track ids type index)))) + + set-column-value + (mf/use-callback + (mf/deps ids) + (fn [type index value] + (st/emit! (dwsl/change-layout-track ids type index {:value value})))) + + set-column-type + (mf/use-callback + (mf/deps ids) + (fn [type index track-type] + (let [value (case track-type + :auto nil + :flex 1 + :percent 20 + :fixed 100)] + (st/emit! (dwsl/change-layout-track ids type index {:value value + :type track-type})))))] + + [:div.element-set + [:div.element-set-title + [:span "Grid Layout"]] + + [:div.element-set-content.layout-menu + [:& grid-columns-row {:is-col? true + :expanded? @grid-columns-open? + :toggle toggle-columns-info + :column-values column-grid-values + :add-new-element add-new-element + :set-column-value set-column-value + :set-column-type set-column-type + :remove-element remove-element}] + + [:& grid-columns-row {:is-col? false + :expanded? @grid-rows-open? + :toggle toggle-rows-info + :column-values rows-grid-values + :add-new-element add-new-element + :set-column-value set-column-value + :set-column-type set-column-type + :remove-element remove-element}] + + [:& gap-section {:gap-selected? gap-selected? + :set-gap set-gap + :gap-value (:layout-gap values)}] + + [:& padding-section {:values values + :on-change-style change-padding-type + :on-change on-padding-change}]]])) diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index a9b04c51d6..c355a684a2 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -159,7 +159,7 @@ create-comment? (= :comments drawing-tool) drawing-path? (or (and edition (= :draw (get-in edit-path [edition :edit-mode]))) (and (some? drawing-obj) (= :path (:type drawing-obj)))) - node-editing? (and edition (not= :text (get-in base-objects [edition :type]))) + node-editing? (and edition (= :path (get-in base-objects [edition :type]))) text-editing? (and edition (= :text (get-in base-objects [edition :type]))) grid-editing? (and edition (ctl/grid-layout? base-objects edition)) diff --git a/frontend/src/app/main/ui/workspace/viewport/actions.cljs b/frontend/src/app/main/ui/workspace/viewport/actions.cljs index 6d9e67d388..1d04c715b5 100644 --- a/frontend/src/app/main/ui/workspace/viewport/actions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/actions.cljs @@ -231,8 +231,8 @@ (mf/use-fn (mf/deps @hover @hover-ids workspace-read-only?) (fn [event] - (if workspace-read-only? - (dom/prevent-default event) + (dom/prevent-default event) + (when-not workspace-read-only? (when (or (dom/class? (dom/get-target event) "viewport-controls") (dom/child? (dom/get-target event) (dom/query ".viewport-controls")) (dom/class? (dom/get-target event) "viewport-selrect") diff --git a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs index 0a12452391..bb46c1efa5 100644 --- a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs @@ -354,5 +354,5 @@ (do (st/emit! (dsc/push-shortcuts ::path psc/shortcuts)) #(st/emit! (dsc/pop-shortcuts ::path))) text-editing? - (do (st/emit! (dsc/push-shortcuts ::text tsc/shortcuts)) - #(st/emit! (dsc/pop-shortcuts ::text))))))) + (do (st/emit! (dsc/push-shortcuts ::text tsc/shortcuts)) + #(st/emit! (dsc/pop-shortcuts ::text))))))) diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 964aceddf6..da1727af87 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -4379,6 +4379,9 @@ msgstr "Updating %s..." msgid "workspace.shape.menu.add-flex" msgstr "Add flex layout" +msgid "workspace.shape.menu.add-grid" +msgstr "Add grid layout" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.back" msgstr "Send to back" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 16a40b9373..6bb5d60126 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -4481,6 +4481,9 @@ msgstr "Actualizando %s..." msgid "workspace.shape.menu.add-flex" msgstr "Añadir flex layout" +msgid "workspace.shape.menu.add-grid" +msgstr "Añadir grid layout" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.back" msgstr "Enviar al fondo" From 06ab577e41063590b9c275523e967b6807adc1f8 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 31 May 2023 16:55:44 +0200 Subject: [PATCH 33/52] :sparkles: More improvements to layout grid UI --- .../src/app/common/geom/shapes/modifiers.cljc | 2 +- common/src/app/common/pages/helpers.cljc | 6 +- .../options/menus/layout_container.cljs | 65 ++++++++++++++++++- .../sidebar/options/shapes/frame.cljs | 6 +- .../src/app/main/ui/workspace/viewport.cljs | 7 +- .../main/ui/workspace/viewport/actions.cljs | 22 ++++--- .../viewport/grid_layout_editor.cljs | 24 ++++--- .../app/main/ui/workspace/viewport/hooks.cljs | 4 +- 8 files changed, 105 insertions(+), 31 deletions(-) diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index 4bf1974e5d..3fdb5aec00 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -424,7 +424,7 @@ to-reflow (cond-> to-reflow - (and (ctl/flex-layout-descent? objects parent-base) + (and (ctl/any-layout-descent? objects parent-base) (not= uuid/zero (:frame-id parent-base))) (conj (:frame-id parent-base)))] (recur modif-tree diff --git a/common/src/app/common/pages/helpers.cljc b/common/src/app/common/pages/helpers.cljc index 529d3bf2f6..0def65c046 100644 --- a/common/src/app/common/pages/helpers.cljc +++ b/common/src/app/common/pages/helpers.cljc @@ -45,8 +45,10 @@ (= type :group))) (defn mask-shape? - [{:keys [type masked-group?]}] - (and (= type :group) masked-group?)) + ([objects id] + (mask-shape? (get objects id))) + ([{:keys [type masked-group?]}] + (and (= type :group) masked-group?))) (defn bool-shape? [{:keys [type]}] 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 ce38aede0e..9cb7565464 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 @@ -715,6 +715,11 @@ [{:keys [ids values] :as props}] (let [;; Gap gap-selected? (mf/use-state :none) + saved-grid-dir (:layout-grid-dir values) + + set-direction + (fn [dir] + (st/emit! (dwsl/update-layout ids {:layout-grid-dir dir}))) set-gap (fn [gap-multiple? type val] @@ -739,6 +744,28 @@ :else (st/emit! (dwsl/update-layout ids {:layout-padding {prop val}})))) + ;; Align grid + align-items-row (:layout-align-items values) + align-items-column (:layout-justify-items values) + + set-align-grid + (fn [value type] + (if (= type :row) + (st/emit! (dwsl/update-layout ids {:layout-align-items value})) + (st/emit! (dwsl/update-layout ids {:layout-justify-items value})))) + + ;; Justify grid + grid-justify-content-row (:layout-align-content values) + grid-justify-content-column (:layout-justify-content values) + + set-justify-grid + (mf/use-callback + (mf/deps ids) + (fn [value type] + (if (= type :row) + (st/emit! (dwsl/update-layout ids {:layout-align-content value})) + (st/emit! (dwsl/update-layout ids {:layout-justify-content value}))))) + ;;Grid columns column-grid-values (:layout-grid-columns values) grid-columns-open? (mf/use-state false) @@ -746,7 +773,7 @@ (fn [_] (swap! grid-columns-open? not))) - ; Grid rows / columns + ;; Grid rows / columns rows-grid-values (:layout-grid-rows values) grid-rows-open? (mf/use-state false) toggle-rows-info @@ -789,6 +816,42 @@ [:span "Grid Layout"]] [:div.element-set-content.layout-menu + [:div.layout-row + [:div.direction-wrap.row-title "Direction"] + [:div.btn-wrapper + [:div.direction + [:* + (for [dir [:row :column]] + [:& direction-btn {:key (d/name dir) + :dir dir + :saved-dir saved-grid-dir + :set-direction #(set-direction dir) + :icon? false}])]] + + (when (= 1 (count ids)) + [:div.edit-mode + [:& grid-edit-mode {:id (first ids)}]])]] + + [:div.layout-row + [:div.align-items-grid.row-title "Align"] + [:div.btn-wrapper.align-grid + [:& align-grid-row {:is-col? false + :align-items align-items-row + :set-align set-align-grid}] + + [:& align-grid-row {:is-col? true + :align-items align-items-column + :set-align set-align-grid}]]] + + [:div.layout-row + [:div.jusfiy-content-grid.row-title "Justify"] + [:div.btn-wrapper.align-grid + [:& justify-grid-row {:is-col? true + :justify-items grid-justify-content-column + :set-justify set-justify-grid}] + [:& justify-grid-row {:is-col? false + :justify-items grid-justify-content-row + :set-justify set-justify-grid}]]] [:& grid-columns-row {:is-col? true :expanded? @grid-columns-open? :toggle toggle-columns-info diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs index cafa9dbfd5..1c8585f1bb 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs @@ -49,7 +49,7 @@ is-grid-parent-ref (mf/use-memo (mf/deps ids) #(refs/grid-layout-child? ids)) is-grid-parent? (mf/deref is-grid-parent-ref) - is-flex-layout-container? (ctl/flex-layout? shape) + is-layout-container? (ctl/any-layout? shape) is-layout-child-absolute? (ctl/layout-absolute? shape) ids (hooks/use-equal-memo ids) @@ -73,7 +73,7 @@ {:shape (first parents) :cell (ctl/get-cell-by-shape-id (first parents) (first ids))}]) - (when (or is-layout-child? is-flex-layout-container?) + (when (or is-layout-child? is-layout-container?) [:& layout-item-menu {:ids ids :type type @@ -81,7 +81,7 @@ :is-flex-parent? is-flex-parent? :is-grid-parent? is-grid-parent? :is-layout-child? is-layout-child? - :is-layout-container? is-flex-layout-container? + :is-layout-container? is-layout-container? :shape shape}]) [:& layer-menu {:ids ids diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index c355a684a2..2213e8f94a 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -595,9 +595,8 @@ [:g.grid-layout-editor {:clipPath "url(#clip-handlers)"} [:& grid-layout/editor {:zoom zoom - :objects objects-modified + :objects base-objects + :modifiers modifiers :shape (or (get objects-modified edition) - (gsh/transform-shape - (get base-objects @hover-top-frame-id) - (dm/get-in modifiers [@hover-top-frame-id :modifiers]))) + (get base-objects @hover-top-frame-id)) :view-only (not show-grid-editor?)}]])]]])) diff --git a/frontend/src/app/main/ui/workspace/viewport/actions.cljs b/frontend/src/app/main/ui/workspace/viewport/actions.cljs index 1d04c715b5..5b173616a7 100644 --- a/frontend/src/app/main/ui/workspace/viewport/actions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/actions.cljs @@ -6,6 +6,7 @@ (ns app.main.ui.workspace.viewport.actions (:require + [app.common.data :as d] [app.common.geom.point :as gpt] [app.common.math :as mth] [app.common.pages.helpers :as cph] @@ -202,7 +203,12 @@ {:keys [id type] :as shape} (or @hover (get objects (first @hover-ids))) - editable? (contains? #{:text :rect :path :image :circle} type)] + editable? (contains? #{:text :rect :path :image :circle} type) + + hover-shape (->> @hover-ids (filter (partial cph/is-child? objects id)) first) + selected-shape (get objects hover-shape) + + grid-layout-id (->> @hover-ids reverse (d/seek (partial ctl/grid-layout? objects)))] (st/emit! (ms/->MouseEvent :double-click ctrl? shift? alt? meta?)) @@ -215,16 +221,12 @@ (st/emit! (dw/select-shape id) (dw/start-editing-selected)) - (ctl/grid-layout? objects @hover-top-frame-id) - (st/emit! (dw/start-edition-mode @hover-top-frame-id)) + (some? selected-shape) + (do (reset! hover selected-shape) + (st/emit! (dw/select-shape (:id selected-shape)))) - :else - (let [;; We only get inside childrens of the hovering shape - hover-ids (->> @hover-ids (filter (partial cph/is-child? objects id))) - selected (get objects (first hover-ids))] - (when (some? selected) - (reset! hover selected) - (st/emit! (dw/select-shape (:id selected)))))))))))))) + (and (not selected-shape) (some? grid-layout-id)) + (st/emit! (dw/start-edition-mode grid-layout-id))))))))))) (defn on-context-menu [hover hover-ids workspace-read-only?] 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 a1e00b5d8d..9afcae0e52 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 @@ -299,9 +299,11 @@ handle-pointer-down (mf/use-callback - (mf/deps (:id shape) (:id cell)) + (mf/deps (:id shape) (:id cell) selected?) (fn [] - (st/emit! (dwge/select-grid-cell (:id shape) (:id cell)))))] + (if selected? + (st/emit! (dwge/remove-selection (:id shape))) + (st/emit! (dwge/select-grid-cell (:id shape) (:id cell))))))] [:g.cell-editor [:rect @@ -657,14 +659,17 @@ ::mf/wrap-props false} [props] - (let [shape (unchecked-get props "shape") - objects (unchecked-get props "objects") - zoom (unchecked-get props "zoom") - view-only (unchecked-get props "view-only") + (let [base-shape (unchecked-get props "shape") + objects (unchecked-get props "objects") + modifiers (unchecked-get props "modifiers") + zoom (unchecked-get props "zoom") + view-only (unchecked-get props "view-only") - ;; We need to know the state unmodified so we can create the modifiers - shape-ref (mf/use-memo (mf/deps (:id shape)) #(refs/object-by-id (:id shape))) - base-shape (mf/deref shape-ref) + shape (mf/use-memo + (mf/deps modifiers base-shape) + #(gsh/transform-shape + base-shape + (dm/get-in modifiers [(:id base-shape) :modifiers]))) snap-pixel? (mf/deref refs/snap-pixel?) @@ -680,6 +685,7 @@ children (->> (:shapes shape) (map (d/getf objects)) + (map #(gsh/transform-shape % (dm/get-in modifiers [(:id %) :modifiers]))) (remove :hidden) (map #(vector (gpo/parent-coords-bounds (:points %) (:points shape)) %))) diff --git a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs index bb46c1efa5..4c4cdfdaf2 100644 --- a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs @@ -206,7 +206,9 @@ (remove #(dm/get-in objects [% :blocked])) (ctt/sort-z-index objects ids {:bottom-frames? mod?})) - grouped? (fn [id] (contains? #{:group :bool} (get-in objects [id :type]))) + grouped? (fn [id] + (and (cph/group-shape? objects id) + (not (cph/mask-shape? objects id)))) selected-with-parents (into #{} (mapcat #(cph/get-parent-ids objects %)) selected) From e86939b8ee351675ad208ad7b82635056c6f7c79 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 5 Jun 2023 15:33:00 +0200 Subject: [PATCH 34/52] :sparkles: Improved flex tracks behavior and auto sizing --- .../app/common/geom/shapes/grid_layout.cljc | 3 + .../geom/shapes/grid_layout/bounds.cljc | 53 +++++++ .../geom/shapes/grid_layout/layout_data.cljc | 147 ++++++++++++++---- .../src/app/common/geom/shapes/modifiers.cljc | 7 +- .../src/app/main/ui/workspace/viewport.cljs | 2 +- .../app/main/ui/workspace/viewport/debug.cljs | 15 +- .../viewport/grid_layout_editor.cljs | 11 +- 7 files changed, 197 insertions(+), 41 deletions(-) create mode 100644 common/src/app/common/geom/shapes/grid_layout/bounds.cljc diff --git a/common/src/app/common/geom/shapes/grid_layout.cljc b/common/src/app/common/geom/shapes/grid_layout.cljc index 8483b8dde9..5de5a5ce32 100644 --- a/common/src/app/common/geom/shapes/grid_layout.cljc +++ b/common/src/app/common/geom/shapes/grid_layout.cljc @@ -7,6 +7,7 @@ (ns app.common.geom.shapes.grid-layout (:require [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.positions :as glp])) @@ -16,3 +17,5 @@ (dm/export glp/get-position-grid-coord) (dm/export glp/get-drop-cell) (dm/export glp/cell-bounds) +(dm/export glpb/layout-content-points) +(dm/export glpb/layout-content-bounds) diff --git a/common/src/app/common/geom/shapes/grid_layout/bounds.cljc b/common/src/app/common/geom/shapes/grid_layout/bounds.cljc new file mode 100644 index 0000000000..57ee718682 --- /dev/null +++ b/common/src/app/common/geom/shapes/grid_layout/bounds.cljc @@ -0,0 +1,53 @@ +;; 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.bounds + (:require + [app.common.data :as d] + [app.common.geom.point :as gpt] + [app.common.geom.shapes.grid-layout.layout-data :as ld] + [app.common.geom.shapes.points :as gpo])) + +(defn layout-content-points + [bounds parent children] + (let [parent-id (:id parent) + parent-bounds @(get bounds parent-id) + + hv #(gpo/start-hv parent-bounds %) + vv #(gpo/start-vv parent-bounds %) + + children (->> children + (map #(vector @(get bounds (:id %)) %))) + + {:keys [row-tracks column-tracks]} (ld/calc-layout-data parent children parent-bounds)] + (d/concat-vec + (->> row-tracks + (mapcat #(vector (:start-p %) + (gpt/add (:start-p %) (vv (:size %)))))) + (->> column-tracks + (mapcat #(vector (:start-p %) + (gpt/add (:start-p %) (hv (:size %))))))))) + +(defn layout-content-bounds + [bounds {:keys [layout-padding] :as parent} children] + + (let [parent-id (:id parent) + parent-bounds @(get bounds parent-id) + + {pad-top :p1 pad-right :p2 pad-bottom :p3 pad-left :p4} layout-padding + pad-top (or pad-top 0) + pad-right (or pad-right 0) + pad-bottom (or pad-bottom 0) + pad-left (or pad-left 0) + + layout-points (layout-content-points bounds parent children)] + + (if (d/not-empty? layout-points) + (-> layout-points + (gpo/merge-parent-coords-bounds parent-bounds) + (gpo/pad-points (- pad-top) (- pad-right) (- pad-bottom) (- pad-left))) + ;; Cannot create some bounds from the children so we return the parent's + parent-bounds))) 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 a377d48d33..1d035998c5 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 @@ -52,6 +52,7 @@ [app.common.data :as d] [app.common.geom.point :as gpt] [app.common.geom.shapes.points :as gpo] + [app.common.math :as mth] [app.common.types.shape.layout :as ctl])) (defn layout-bounds @@ -100,7 +101,8 @@ idx (dec (get cell prop)) track (get tracks idx)] (cond-> tracks - (and (= (get cell prop-span) 1) (= :auto (:type track))) + (and (= (get cell prop-span) 1) + (contains? #{:flex :auto} (:type track))) (update-in [idx :size] max (size-fn child-shape child-bounds))))) track-list children))) @@ -129,13 +131,68 @@ (cond-> acc (= type :auto) (inc)))] (->> track-list (reduce calc-tracks-total-autos 0)))) + (defn set-fr-value - [track-list fr-value] - (->> track-list - (mapv (fn [{:keys [type value max-size] :as track}] - (cond-> track - (= :flex type) - (assoc :size (min (* value fr-value) max-size))))))) + "Tries to assign the fr value distributing the excess between the free spaces" + [track-list fr-value auto?] + + (let [flex? #(= :flex (:type (second %))) + + ;; Fixes the assignments so they respect the min size constraint + ;; returns pending with the necessary space to allocate and free-frs + ;; are the addition of the fr tracks with free space + assign-fn + (fn [[assign-fr pending free-frs] [idx t]] + (let [fr (:value t) + current (get assign-fr idx (* fr-value fr)) + full? (<= current (:size t)) + cur-pending (if full? (- (:size t) current) 0)] + [(assoc assign-fr idx (if full? (:size t) current)) + (+ pending cur-pending) + (cond-> free-frs (not full?) (+ fr))])) + + ;; Sets the assigned-fr map removing the pending/free-frs + change-fn + (fn [delta] + (fn [assign-fr [idx t]] + (let [fr (:value t) + current (get assign-fr idx) + full? (<= current (:size t))] + (cond-> assign-fr + (not full?) + (update idx - (* delta fr)))))) + + assign-fr + (loop [assign-fr {}] + (let [[assign-fr pending free-frs] + (->> (d/enumerate track-list) + (filter flex?) + (reduce assign-fn [assign-fr 0 0]))] + + ;; When auto, we don't need to remove the excess + (if (or auto? + (= free-frs 0) + (mth/almost-zero? pending)) + assign-fr + + (let [delta (/ pending free-frs) + assign-fr + (->> (d/enumerate track-list) + (filter flex?) + (reduce (change-fn delta) assign-fr))] + + (recur assign-fr))))) + + ;; Apply assign-fr to the track-list + track-list + (reduce + (fn [track-list [idx assignment] ] + (-> track-list + (update-in [idx :size] max assignment))) + track-list + assign-fr)] + + track-list)) (defn add-auto-size [track-list add-size] @@ -305,6 +362,15 @@ track-list)] track-list)) +(defn min-fr-value + [tracks] + (loop [tracks (seq tracks) + min-fr 0.01] + (if (empty? tracks) + min-fr + (let [{:keys [size type value]} (first tracks) + min-fr (if (= type :flex) (max min-fr (/ size value)) min-fr)] + (recur (rest tracks) min-fr))))) (defn calc-layout-data [parent children transformed-parent-bounds] @@ -319,13 +385,22 @@ bound-corner (gpo/origin layout-bounds) [row-gap column-gap] (ctl/gaps parent) + auto-height? (ctl/auto-height? parent) + auto-width? (ctl/auto-width? parent) + + {:keys [layout-grid-columns layout-grid-rows layout-grid-cells]} parent + num-columns (count layout-grid-columns) + num-rows (count layout-grid-rows) + + column-total-gap (* column-gap (dec num-columns)) + row-total-gap (* row-gap (dec num-rows)) ;; Map shape->cell shape-cells (into {} (mapcat (fn [[_ cell]] (->> (:shapes cell) (map #(vector % cell))))) - (:layout-grid-cells parent)) + layout-grid-cells) children (->> children (remove #(ctl/layout-absolute? (second %)))) children-map @@ -335,11 +410,11 @@ ;; Initialize tracks column-tracks - (->> (:layout-grid-columns parent) + (->> layout-grid-columns (mapv (partial calculate-initial-track-size bound-width))) row-tracks - (->> (:layout-grid-rows parent) + (->> layout-grid-rows (mapv (partial calculate-initial-track-size bound-height))) ;; Go through cells to adjust auto sizes for span=1. Base is the max of its children @@ -351,11 +426,8 @@ row-tracks (set-auto-multi-span parent row-tracks children-map shape-cells :row) ;; Calculate the `fr` unit and adjust the size - column-total-size (tracks-total-size column-tracks) - row-total-size (tracks-total-size row-tracks) - - column-total-gap (* column-gap (dec (count column-tracks))) - row-total-gap (* row-gap (dec (count row-tracks))) + column-total-size-nofr (tracks-total-size (->> column-tracks (remove #(= :flex (:type %))))) + row-total-size-nofr (tracks-total-size (->> row-tracks (remove #(= :flex (:type %))))) column-frs (tracks-total-frs column-tracks) row-frs (tracks-total-frs row-tracks) @@ -367,22 +439,28 @@ row-tracks (set-flex-multi-span parent row-tracks children-map shape-cells :row) ;; Once auto sizes have been calculated we get calculate the `fr` unit with the remainining size and adjust the size - free-column-space (- bound-width (+ column-total-size column-total-gap)) - free-row-space (- bound-height (+ row-total-size row-total-gap)) - column-fr (/ free-column-space column-frs) - row-fr (/ free-row-space row-frs) + free-column-space (max 0 (- bound-width (+ column-total-size-nofr column-total-gap))) + free-row-space (max 0 (- bound-height (+ row-total-size-nofr row-total-gap))) - column-tracks (set-fr-value column-tracks column-fr) - row-tracks (set-fr-value row-tracks row-fr) + ;; Get the minimum values for fr's + min-column-fr (min-fr-value column-tracks) + min-row-fr (min-fr-value row-tracks) + + column-fr (if auto-width? min-column-fr (mth/finite (/ free-column-space column-frs) 0)) + row-fr (if auto-height? min-row-fr (mth/finite (/ free-row-space row-frs) 0)) + + column-tracks (set-fr-value column-tracks column-fr auto-width?) + row-tracks (set-fr-value row-tracks row-fr auto-height?) ;; Distribute free space between `auto` tracks column-total-size (tracks-total-size column-tracks) row-total-size (tracks-total-size row-tracks) - free-column-space (- bound-width (+ column-total-size column-total-gap)) - free-row-space (- bound-height (+ row-total-size row-total-gap)) + free-column-space (max 0 (if auto-width? 0 (- bound-width (+ column-total-size column-total-gap)))) + free-row-space (max 0 (if auto-height? 0 (- bound-height (+ row-total-size row-total-gap)))) column-autos (tracks-total-autos column-tracks) row-autos (tracks-total-autos row-tracks) + column-add-auto (/ free-column-space column-autos) row-add-auto (/ free-row-space row-autos) @@ -394,6 +472,9 @@ column-gap (case (:layout-align-content parent) + auto-width? + column-gap + :space-evenly (max column-gap (/ (- bound-width column-total-size) (inc (count column-tracks)))) @@ -407,6 +488,9 @@ row-gap (case (:layout-justify-content parent) + auto-height? + row-gap + :space-evenly (max row-gap (/ (- bound-height row-total-size) (inc (count row-tracks)))) @@ -420,29 +504,28 @@ start-p (cond-> bound-corner - (= :end (:layout-align-content parent)) + (and (not auto-width?) (= :end (:layout-align-content parent))) (gpt/add (hv (- bound-width (+ column-total-size column-total-gap)))) - (= :center (:layout-align-content parent)) + (and (not auto-width?) (= :center (:layout-align-content parent))) (gpt/add (hv (/ (- bound-width (+ column-total-size column-total-gap)) 2))) - (= :end (:layout-justify-content parent)) + (and (not auto-height?) (= :end (:layout-justify-content parent))) (gpt/add (vv (- bound-height (+ row-total-size row-total-gap)))) - (= :center (:layout-justify-content parent)) + (and (not auto-height?) (= :center (:layout-justify-content parent))) (gpt/add (vv (/ (- bound-height (+ row-total-size row-total-gap)) 2))) - - (= :space-around (:layout-align-content parent)) + (and (not auto-width?) (= :space-around (:layout-align-content parent))) (gpt/add (hv (/ column-gap 2))) - (= :space-evenly (:layout-align-content parent)) + (and (not auto-width?) (= :space-evenly (:layout-align-content parent))) (gpt/add (hv column-gap)) - (= :space-around (:layout-justify-content parent)) + (and (not auto-height?) (= :space-around (:layout-justify-content parent))) (gpt/add (vv (/ row-gap 2))) - (= :space-evenly (:layout-justify-content parent)) + (and (not auto-height?) (= :space-evenly (:layout-justify-content parent))) (gpt/add (vv row-gap))) column-tracks diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index 3fdb5aec00..f0eb8f320c 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -256,7 +256,12 @@ content-bounds (when (and (d/not-empty? children) (or (ctl/auto-height? parent) (ctl/auto-width? parent))) - (gcfl/layout-content-bounds bounds parent children)) + (cond + (ctl/flex-layout? parent) + (gcfl/layout-content-bounds bounds parent children) + + (ctl/grid-layout? parent) + (gcgl/layout-content-bounds bounds parent children))) auto-width (when content-bounds (gpo/width-points content-bounds)) auto-height (when content-bounds (gpo/height-points content-bounds))] diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 2213e8f94a..3c65902aab 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -597,6 +597,6 @@ {:zoom zoom :objects base-objects :modifiers modifiers - :shape (or (get objects-modified edition) + :shape (or (get base-objects edition) (get base-objects @hover-top-frame-id)) :view-only (not show-grid-editor?)}]])]]])) diff --git a/frontend/src/app/main/ui/workspace/viewport/debug.cljs b/frontend/src/app/main/ui/workspace/viewport/debug.cljs index 7f686435a8..67116f6244 100644 --- a/frontend/src/app/main/ui/workspace/viewport/debug.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/debug.cljs @@ -40,8 +40,19 @@ (let [children (->> (cph/get-immediate-children objects (:id shape)) (remove :hidden)) bounds (d/lazy-map (keys objects) #(dm/get-in objects [% :points])) - layout-bounds (gsl/layout-content-bounds bounds shape children) - layout-points (flatten (gsl/layout-content-points bounds shape children))] + layout-bounds + (cond (ctl/flex-layout? shape) + (gsl/layout-content-bounds bounds shape children) + + (ctl/grid-layout? shape) + (gsg/layout-content-bounds bounds shape children)) + layout-points + (cond (ctl/flex-layout? shape) + (flatten (gsl/layout-content-points bounds shape children)) + + (ctl/grid-layout? shape) + (flatten (gsg/layout-content-points bounds shape children)))] + [:g.debug-layout {:pointer-events "none"} [:polygon {:points (->> layout-bounds (map #(dm/fmt "%, %" (:x %) (:y %))) (str/join " ")) :style {:stroke "red" :fill "none"}}] 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 9afcae0e52..72dbd278de 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 @@ -665,11 +665,12 @@ zoom (unchecked-get props "zoom") view-only (unchecked-get props "view-only") - shape (mf/use-memo - (mf/deps modifiers base-shape) - #(gsh/transform-shape - base-shape - (dm/get-in modifiers [(:id base-shape) :modifiers]))) + shape + (mf/use-memo + (mf/deps modifiers base-shape) + #(gsh/transform-shape + base-shape + (dm/get-in modifiers [(:id base-shape) :modifiers]))) snap-pixel? (mf/deref refs/snap-pixel?) From 9b8ef356037cb3ff39c51fa791d50327d6afcda1 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 5 Jun 2023 18:07:41 +0200 Subject: [PATCH 35/52] :sparkles: Grid layers order --- .../src/app/common/pages/changes_builder.cljc | 40 +++++++++++ common/src/app/common/types/shape/layout.cljc | 70 +++++++++++++++++-- frontend/src/app/main/data/workspace.cljs | 6 ++ .../src/app/main/data/workspace/changes.cljs | 1 + .../app/main/data/workspace/selection.cljs | 3 +- .../app/main/data/workspace/shape_layout.cljs | 46 +----------- .../src/app/main/data/workspace/shapes.cljs | 7 +- .../app/main/data/workspace/transforms.cljs | 6 +- 8 files changed, 125 insertions(+), 54 deletions(-) diff --git a/common/src/app/common/pages/changes_builder.cljc b/common/src/app/common/pages/changes_builder.cljc index be93f9cc72..08c2db3d83 100644 --- a/common/src/app/common/pages/changes_builder.cljc +++ b/common/src/app/common/pages/changes_builder.cljc @@ -17,6 +17,7 @@ [app.common.pages.helpers :as cph] [app.common.types.component :as ctk] [app.common.types.file :as ctf] + [app.common.types.shape.layout :as ctl] [app.common.uuid :as uuid])) ;; Auxiliary functions to help create a set of changes (undo + redo) @@ -712,3 +713,42 @@ (-> changes (update :redo-changes add-ignore-remote) (update :undo-changes add-ignore-remote)))) + +(defn reorder-grid-children + [changes ids] + (assert-page-id changes) + (assert-objects changes) + + (let [page-id (::page-id (meta changes)) + objects (lookup-objects changes) + + reorder-grid + (fn [changes grid] + (let [old-shapes (:shapes grid) + grid (ctl/reorder-grid-children grid) + + redo-change + {:type :mov-objects + :parent-id (:id grid) + :page-id page-id + :shapes (:shapes grid) + :index 0} + + undo-change + {:type :mov-objects + :parent-id (:id grid) + :page-id page-id + :shapes old-shapes + :index 0}] + (-> changes + (update :redo-changes conj redo-change) + (update :undo-changes d/preconj undo-change) + (apply-changes-local)))) + + changes + (->> ids + (map (d/getf objects)) + (filter ctl/grid-layout?) + (reduce reorder-grid changes))] + + changes)) diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index 1de4aed70b..bb4017a431 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -595,7 +595,7 @@ layout-grid-cells (->> (d/enumerate rows) - (reduce (fn [result [row-idx _row]] + (reduce (fn [result [row-idx _]] (let [id (uuid/next)] (assoc result id (merge {:id id @@ -618,7 +618,7 @@ layout-grid-cells (->> (d/enumerate cols) - (reduce (fn [result [col-idx _col]] + (reduce (fn [result [col-idx _]] (let [id (uuid/next)] (assoc result id (merge {:id id @@ -699,16 +699,20 @@ ([parent] (get-cells parent nil)) - ([{:keys [layout-grid-cells layout-grid-dir]} {:keys [sort?] :or {sort? false}}] + ([{:keys [layout-grid-cells layout-grid-dir]} {:keys [sort? remove-empty?] :or {sort? false remove-empty? false}}] (let [comp-fn (if (= layout-grid-dir :row) (juxt :row :column) (juxt :column :row)) maybe-sort? - (if sort? (partial sort-by (comp comp-fn second)) identity)] + (if sort? (partial sort-by (comp comp-fn second)) identity) + + maybe-remove? + (if remove-empty? (partial remove #(empty? (:shapes (second %)))) identity)] (->> layout-grid-cells (maybe-sort?) + (maybe-remove?) (map (fn [[id cell]] (assoc cell :id id))))))) (defn get-free-cells @@ -739,7 +743,35 @@ (assoc parent :layout-grid-cells cells))) -;; TODO +(defn overlapping-cells + "Find overlapping cells" + [parent] + (let [cells (->> parent + :layout-grid-cells + (map (fn [[id cell]] + [id (sga/make-area cell)]))) + find-overlaps + (fn [result [id area]] + (let [[fid _] + (d/seek #(and (not= (first %) id) + (sga/intersects? (second %) area)) + cells)] + (cond-> result + (some? fid) + (conj #{id fid}))))] + (reduce find-overlaps #{} cells))) + +;; FIXME: This is only for development +#_(defn fix-overlaps + [parent overlaps] + (reduce (fn [parent ids] + (let [id (if (empty? (get-in parent [:layout-grid-cells (first ids)])) + (first ids) + (second ids))] + (update parent :layout-grid-cells dissoc id))) + parent + overlaps)) + ;; Assign cells takes the children and move them into the allotted cells. If there are not enough cells it creates ;; not-tracked rows/columns and put the shapes there ;; Non-tracked tracks need to be deleted when they are empty and there are no more shapes unallocated @@ -798,6 +830,8 @@ cells (update-in cells [next-free :shapes] conj current)] (recur cells (rest free-cells) (rest pending)))))] + ;; TODO: Remove after testing + (assert (empty? (overlapping-cells parent)) (dm/str (overlapping-cells parent))) (assoc parent :layout-grid-cells cells))))) (defn free-cell-push @@ -1009,3 +1043,29 @@ (cond-> (some? cell) (push-into-cell children row column)) (assign-cells)))) + +(defn add-children-to-index + [parent ids objects to-index] + (let [ids (into (d/ordered-set) ids) + cells (get-cells parent {:sort? true :remove-empty? true}) + to-index (- (count cells) to-index) + target-cell (nth cells to-index nil)] + + (cond-> parent + (some? target-cell) + (add-children-to-cell ids objects [(:row target-cell) (:column target-cell)])))) + +(defn reorder-grid-children + [parent] + (let [cells (get-cells parent {:sort? true}) + child? (set (:shapes parent)) + new-shapes + (into (d/ordered-set) + (comp (keep (comp first :shapes)) + (filter child?)) + cells) + + ;; Add the children that are not in cells (absolute positioned for example) + new-shapes (into new-shapes (:shapes parent))] + + (assoc parent :shapes (into [] (reverse new-shapes))))) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index acb2bfdd1f..f74ae2c763 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -785,11 +785,17 @@ parent))) ;; Update grid layout + (cond-> (ctl/grid-layout? objects parent-id) + (pcb/update-shapes [parent-id] #(ctl/add-children-to-index % ids objects to-index))) + (pcb/update-shapes parents (fn [parent] (cond-> parent (ctl/grid-layout? parent) (ctl/assign-cells)))) + + (pcb/reorder-grid-children parents) + ;; Resize parent containers that need to (pcb/resize-parents parents)))) diff --git a/frontend/src/app/main/data/workspace/changes.cljs b/frontend/src/app/main/data/workspace/changes.cljs index a8461f4155..2868ce4a8b 100644 --- a/frontend/src/app/main/data/workspace/changes.cljs +++ b/frontend/src/app/main/data/workspace/changes.cljs @@ -80,6 +80,7 @@ (pcb/set-stack-undo? stack-undo?) (pcb/with-objects objects)) ids) + changes (pcb/reorder-grid-children changes ids) changes (add-undo-group changes state)] (rx/concat (if (seq (:redo-changes changes)) diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index ec88cd88c8..3219d56bec 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -450,7 +450,8 @@ changes (-> (pcb/add-object changes new-obj) (pcb/amend-last-change #(assoc % :old-id (:id obj))) (cond-> (ctl/grid-layout? objects (:parent-id obj)) - (pcb/update-shapes [(:parent-id obj)] ctl/assign-cells))) + (-> (pcb/update-shapes [(:parent-id obj)] ctl/assign-cells) + (pcb/reorder-grid-children [(:parent-id obj)])))) changes (cond-> changes (and is-component-root? is-component-main?) diff --git a/frontend/src/app/main/data/workspace/shape_layout.cljs b/frontend/src/app/main/data/workspace/shape_layout.cljs index 2642bb3b77..bba3d9bda6 100644 --- a/frontend/src/app/main/data/workspace/shape_layout.cljs +++ b/frontend/src/app/main/data/workspace/shape_layout.cljs @@ -196,7 +196,9 @@ (assoc :layout-item-h-sizing :auto :layout-item-v-sizing :auto)) (merge layout-params) - (cond-> (= type :grid) (ctl/assign-cells))))) + (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)))))) @@ -370,48 +372,6 @@ (ptk/data-event :layout/update ids) (dwu/commit-undo-transaction undo-id)))))) -#_(defn update-grid-cells - [parent objects] - (let [children (cph/get-immediate-children objects (:id parent)) - layout-grid-rows (:layout-grid-rows parent) - layout-grid-columns (:layout-grid-columns parent) - num-rows (count layout-grid-columns) - num-columns (count layout-grid-columns) - layout-grid-cells (:layout-grid-cells parent) - - allocated-shapes - (into #{} (mapcat :shapes) (:layout-grid-cells parent)) - - no-cell-shapes - (->> children (:shapes parent) (remove allocated-shapes)) - - layout-grid-cells - (for [[row-idx row] (d/enumerate layout-grid-rows) - [col-idx col] (d/enumerate layout-grid-columns)] - - (let [shape (nth children (+ (* row-idx num-columns) col-idx) nil) - cell-data {:id (uuid/next) - :row (inc row-idx) - :column (inc col-idx) - :row-span 1 - :col-span 1 - :shapes (when shape [(:id shape)])}] - [(:id cell-data) cell-data]))] - (assoc parent :layout-grid-cells (into {} layout-grid-cells)))) - -#_(defn check-grid-cells-update - [ids] - (ptk/reify ::check-grid-cells-update - ptk/WatchEvent - (watch [_ state _] - (let [objects (wsh/lookup-page-objects state) - undo-id (js/Symbol)] - (rx/of (dwc/update-shapes - ids - (fn [shape] - (-> shape - (update-grid-cells objects))))))))) - (defn add-layout-track [ids type value] (assert (#{:row :column} type)) diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs index d23ad2dc71..9b9833ac0f 100644 --- a/frontend/src/app/main/data/workspace/shapes.cljs +++ b/frontend/src/app/main/data/workspace/shapes.cljs @@ -95,8 +95,8 @@ (cond-> (some? cell) (pcb/update-shapes [(:parent-id shape)] #(ctl/push-into-cell % [id] row column))) (cond-> (ctl/grid-layout? objects (:parent-id shape)) - (pcb/update-shapes [(:parent-id shape)] ctl/assign-cells)))] - + (-> (pcb/update-shapes [(:parent-id shape)] ctl/assign-cells) + (pcb/reorder-grid-children [(:parent-id shape)]))))] [shape changes])) (defn add-shape @@ -144,7 +144,8 @@ (pcb/update-shapes ordered-indexes #(cond-> % (cph/frame-shape? %) (assoc :hide-in-viewer true))) (pcb/change-parent frame-id to-move-shapes 0) (cond-> (ctl/grid-layout? objects frame-id) - (pcb/update-shapes [frame-id] ctl/assign-cells)))))) + (pcb/update-shapes [frame-id] ctl/assign-cells)) + (pcb/reorder-grid-children [frame-id]))))) (defn move-shapes-into-frame [frame-id shapes] diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 6cc4b3995f..2e94d95160 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -539,8 +539,8 @@ (fn [[_ target-frame drop-index]] (let [undo-id (js/Symbol)] (rx/of (dwu/start-undo-transaction undo-id) - (move-shapes-to-frame ids target-frame drop-index) (dwm/apply-modifiers {:undo-transation? false}) + (move-shapes-to-frame ids target-frame drop-index) (finish-transform) (dwu/commit-undo-transaction undo-id)))))))))))))) @@ -608,7 +608,8 @@ (ctl/swap-shapes id (:id next-cell))))) parent))] (-> changes - (pcb/update-shapes [(:id parent)] (fn [shape] (assoc shape :layout-grid-cells layout-grid-cells)))))) + (pcb/update-shapes [(:id parent)] (fn [shape] (assoc shape :layout-grid-cells layout-grid-cells))) + (pcb/reorder-grid-children [(:id parent)])))) changes (->> selected @@ -812,6 +813,7 @@ (pcb/update-shapes moving-shapes-ids #(cond-> % (cph/frame-shape? %) (assoc :hide-in-viewer true))) (pcb/update-shapes shape-ids-to-detach ctk/detach-shape) (pcb/change-parent frame-id moving-shapes drop-index) + (pcb/reorder-grid-children [frame-id]) (pcb/remove-objects empty-parents))] (when (and (some? frame-id) (d/not-empty? changes)) From 4b8783c104789f5a45f24d8dbc8ecf85d9206b8a Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 9 Jun 2023 13:22:38 +0200 Subject: [PATCH 36/52] :bug: Fix problem with paste objects --- frontend/src/app/main/data/workspace.cljs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index f74ae2c763..b2e9436e75 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -1822,10 +1822,7 @@ (when (ctl/grid-layout? all-objects parent-id) (gslg/get-drop-cell frame-id all-objects mouse-pos)) - changes - (-> (pcb/resize-parents changes new-objects-ids) - (cond-> (some? drop-cell) - (pcb/update-shapes [parent-id] #(ctl/add-children-to-cell % (into (d/ordered-set) new-objects-ids) all-objects drop-cell)))) + changes (pcb/resize-parents changes new-objects-ids) selected (->> changes :redo-changes @@ -1833,6 +1830,13 @@ (filter #(selected (:old-id %))) (map #(get-in % [:obj :id])) (into (d/ordered-set))) + + changes + (cond-> changes + (some? drop-cell) + (pcb/update-shapes [parent-id] + #(ctl/add-children-to-cell % selected all-objects drop-cell))) + undo-id (js/Symbol)] (rx/of (dwu/start-undo-transaction undo-id) From 600b1a6d8d7e3291aae9dd7d28bff9b63ddab728 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 12 Jun 2023 18:14:24 +0200 Subject: [PATCH 37/52] :sparkles: Improved code generation --- .../geom/shapes/grid_layout/layout_data.cljc | 12 +- .../styles/main/layouts/inspect.scss | 3 - .../styles/main/partials/inspect.scss | 83 +++- .../styles/main/partials/tab-container.scss | 4 + .../styles/main/partials/workspace.scss | 9 +- frontend/src/app/main.cljs | 6 +- frontend/src/app/main/data/workspace.cljs | 14 - .../app/main/data/workspace/shape_layout.cljs | 4 +- frontend/src/app/main/ui/hooks/resize.cljs | 11 +- .../app/main/ui/shapes/text/fontfaces.cljs | 13 +- .../app/main/ui/shapes/text/html_text.cljs | 19 +- .../src/app/main/ui/shapes/text/styles.cljs | 3 +- .../src/app/main/ui/viewer/inspect/code.cljs | 168 ++++++-- .../main/ui/viewer/inspect/right_sidebar.cljs | 44 ++- .../src/app/main/ui/workspace/sidebar.cljs | 42 +- .../main/ui/workspace/sidebar/options.cljs | 32 +- .../options/menus/layout_container.cljs | 20 +- frontend/src/app/util/code_gen.cljs | 363 ++++++++++++++---- 18 files changed, 651 insertions(+), 199 deletions(-) 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 1d035998c5..8871045f6f 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 @@ -464,8 +464,13 @@ column-add-auto (/ free-column-space column-autos) row-add-auto (/ free-row-space row-autos) - column-tracks (add-auto-size column-tracks column-add-auto) - row-tracks (add-auto-size row-tracks row-add-auto) + column-tracks (cond-> column-tracks + (= :stretch (:layout-align-content parent)) + (add-auto-size column-add-auto)) + + row-tracks (cond-> row-tracks + (= :stretch (:layout-justify-content parent)) + (add-auto-size row-add-auto)) column-total-size (tracks-total-size column-tracks) row-total-size (tracks-total-size row-tracks) @@ -556,8 +561,7 @@ :column-total-size column-total-size :column-total-gap column-total-gap :row-total-size row-total-size - :row-total-gap row-total-gap - })) + :row-total-gap row-total-gap})) (defn get-cell-data [{:keys [origin row-tracks column-tracks shape-cells]} _transformed-parent-bounds [_ child]] diff --git a/frontend/resources/styles/main/layouts/inspect.scss b/frontend/resources/styles/main/layouts/inspect.scss index 8b13d09f79..e26df3ece3 100644 --- a/frontend/resources/styles/main/layouts/inspect.scss +++ b/frontend/resources/styles/main/layouts/inspect.scss @@ -106,9 +106,6 @@ $width-settings-bar: 256px; .settings-bar { transition: width 0.2s; width: $width-settings-bar; - &.expanded { - width: $width-settings-bar * 3; - } &.settings-bar-right, &.settings-bar-left { diff --git a/frontend/resources/styles/main/partials/inspect.scss b/frontend/resources/styles/main/partials/inspect.scss index 32ce5a9e7e..8693da4e5d 100644 --- a/frontend/resources/styles/main/partials/inspect.scss +++ b/frontend/resources/styles/main/partials/inspect.scss @@ -328,6 +328,7 @@ } .code-block { + position: relative; margin-top: 0.5rem; border-top: 1px solid $color-gray-60; @@ -353,17 +354,86 @@ .copy-button { margin-top: 8px; } + + .custom-select { + border: 1px solid $color-gray-40; + border-radius: 3px; + cursor: pointer; + padding: 0.25rem 1.5rem 0.25rem 0.25rem; + position: relative; + display: flex; + flex-direction: column; + justify-content: center; + + .dropdown-button { + position: absolute; + right: 0.25rem; + top: 7px; + + svg { + fill: $color-gray-40; + height: 10px; + width: 10px; + } + } + } + + .custom-select-dropdown { + background-color: $color-white; + border-radius: 3px; + box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.25); + left: 0; + max-height: 30rem; + min-width: 7rem; + position: absolute; + overflow-y: auto; + top: 30px; + z-index: 12; + + li { + color: $color-gray-60; + cursor: pointer; + font-size: 0.875rem; + display: flex; + gap: 0 10px; + justify-content: flex-start; + padding: 0.5rem; + + .checked-element { + padding-left: 0; + } + } + + svg { + visibility: hidden; + width: 8px; + height: 8px; + background: none; + margin: 0.25rem; + fill: $color-black; + } + + .is-selected svg { + visibility: visible; + } + } + } .code-row-display { + line-height: 1; margin: 0.5rem; font-size: $fs14; + max-height: var(--code-height, 400px); + overflow: auto; .code-display { + font-family: monospace; border-radius: $br4; - padding: 1rem; + padding: 0.5rem 1rem; overflow: hidden; - white-space: pre-wrap; + white-space: pre; + min-width: fit-content; background: $color-gray-60; user-select: text; @@ -378,6 +448,15 @@ } } } + .resize-area { + width: 100%; + position: absolute; + bottom: -15px; + left: 0; + height: 18px; + z-index: 1; + cursor: ns-resize; + } } .element-options > :first-child { diff --git a/frontend/resources/styles/main/partials/tab-container.scss b/frontend/resources/styles/main/partials/tab-container.scss index 41490f896c..33c759e0a3 100644 --- a/frontend/resources/styles/main/partials/tab-container.scss +++ b/frontend/resources/styles/main/partials/tab-container.scss @@ -35,6 +35,10 @@ overflow-x: hidden; } +.inspect .tab-container-content { + overflow: hidden; +} + .tab-element, .tab-element-content { height: 100%; diff --git a/frontend/resources/styles/main/partials/workspace.scss b/frontend/resources/styles/main/partials/workspace.scss index 595d2d8312..9f5b8104fc 100644 --- a/frontend/resources/styles/main/partials/workspace.scss +++ b/frontend/resources/styles/main/partials/workspace.scss @@ -48,14 +48,11 @@ $height-palette-max: 80px; } .settings-bar.settings-bar-right { - transition: width 0.2s; - min-width: $width-settings-bar; - max-width: $width-settings-bar * 3; - width: $width-settings-bar; + width: var(--width, $width-settings-bar); grid-area: right-sidebar; - &.expanded { - width: $width-settings-bar * 3; + &.not-expand { + max-width: $width-settings-bar; } } diff --git a/frontend/src/app/main.cljs b/frontend/src/app/main.cljs index 33e019b993..0de1233fb5 100644 --- a/frontend/src/app/main.cljs +++ b/frontend/src/app/main.cljs @@ -88,9 +88,9 @@ (defn ^:export reinit [] - (mf/unmount (dom/get-element "app")) - (mf/unmount (dom/get-element "modal")) - (st/emit! (ev/initialize)) + #_(mf/unmount (dom/get-element "app")) + #_(mf/unmount (dom/get-element "modal")) + #_(st/emit! (ev/initialize)) (init-ui)) (defn ^:dev/after-load after-load diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index b2e9436e75..ce0cf18a77 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -2160,20 +2160,6 @@ (let [orphans (set (into [] (keys (wsh/find-orphan-shapes state))))] (rx/of (relocate-shapes orphans uuid/zero 0 true)))))) - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Inspect -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - - -(defn set-inspect-expanded - [expanded?] - (ptk/reify ::set-inspect-expanded - ptk/UpdateEvent - (update [_ state] - (assoc-in state [:workspace-local :inspect-expanded] expanded?)))) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Sitemap ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/frontend/src/app/main/data/workspace/shape_layout.cljs b/frontend/src/app/main/data/workspace/shape_layout.cljs index bba3d9bda6..15873ee1cb 100644 --- a/frontend/src/app/main/data/workspace/shape_layout.cljs +++ b/frontend/src/app/main/data/workspace/shape_layout.cljs @@ -59,9 +59,9 @@ :layout-gap-type :multiple :layout-gap {:row-gap 0 :column-gap 0} :layout-align-items :start - :layout-align-content :start :layout-justify-items :start - :layout-justify-content :start + :layout-align-content :stretch + :layout-justify-content :stretch :layout-padding-type :simple :layout-padding {:p1 0 :p2 0 :p3 0 :p4 0} :layout-grid-cells {} diff --git a/frontend/src/app/main/ui/hooks/resize.cljs b/frontend/src/app/main/ui/hooks/resize.cljs index 1782048cae..6dd334678e 100644 --- a/frontend/src/app/main/ui/hooks/resize.cljs +++ b/frontend/src/app/main/ui/hooks/resize.cljs @@ -9,6 +9,7 @@ [app.common.data.macros :as dm] [app.common.geom.point :as gpt] [app.common.logging :as log] + [app.common.math :as mth] [app.main.ui.context :as ctx] [app.main.ui.hooks :as hooks] [app.util.dom :as dom] @@ -65,11 +66,19 @@ start-size (mf/ref-val start-size-ref) new-size (-> (+ start-size delta) (max min-val) (min max-val))] (reset! size-state new-size) - (swap! storage assoc-in [::saved-resize current-file-id key] new-size)))))] + (swap! storage assoc-in [::saved-resize current-file-id key] new-size))))) + + set-size + (mf/use-callback + (fn [new-size] + (let [new-size (mth/clamp new-size min-val max-val)] + (reset! size-state new-size) + (swap! storage assoc-in [::saved-resize current-file-id key] new-size))))] {:on-pointer-down on-pointer-down :on-lost-pointer-capture on-lost-pointer-capture :on-pointer-move on-pointer-move :parent-ref parent-ref + :set-size set-size :size @size-state})) (defn use-resize-observer diff --git a/frontend/src/app/main/ui/shapes/text/fontfaces.cljs b/frontend/src/app/main/ui/shapes/text/fontfaces.cljs index 6bfcdf675c..56fa884885 100644 --- a/frontend/src/app/main/ui/shapes/text/fontfaces.cljs +++ b/frontend/src/app/main/ui/shapes/text/fontfaces.cljs @@ -48,6 +48,18 @@ (mf/ref-val fonts-css-ref))) +(mf/defc fontfaces-style-html + {::mf/wrap-props false + ::mf/wrap [#(mf/memo' % (mf/check-props ["fonts"]))]} + [props] + + (let [fonts (obj/get props "fonts") + + ;; Fetch its CSS fontfaces + fonts-css (use-fonts-css fonts)] + + [:style fonts-css])) + (mf/defc fontfaces-style-render {::mf/wrap-props false ::mf/wrap [#(mf/memo' % (mf/check-props ["fonts"]))]} @@ -63,7 +75,6 @@ (mf/deps fonts-css) #(fonts/extract-fontface-urls fonts-css)) - ;; Calculate the data-uris for these fonts fonts-embed (embed/use-data-uris fonts-urls) diff --git a/frontend/src/app/main/ui/shapes/text/html_text.cljs b/frontend/src/app/main/ui/shapes/text/html_text.cljs index d60810e166..399d57d3e5 100644 --- a/frontend/src/app/main/ui/shapes/text/html_text.cljs +++ b/frontend/src/app/main/ui/shapes/text/html_text.cljs @@ -83,19 +83,24 @@ [props ref] (let [shape (obj/get props "shape") grow-type (obj/get props "grow-type") - {:keys [id x y width height content]} shape] + code? (obj/get props "code?") + {:keys [id x y width height content]} shape + + style + (when-not code? + #js {:position "fixed" + :left 0 + :top 0 + :background "white" + :width (if (#{:auto-width} grow-type) 100000 width) + :height (if (#{:auto-height :auto-width} grow-type) 100000 height)})] [:div.text-node-html {:id (dm/str "html-text-node-" id) :ref ref :data-x x :data-y y - :style {:position "fixed" - :left 0 - :top 0 - :background "white" - :width (if (#{:auto-width} grow-type) 100000 width) - :height (if (#{:auto-height :auto-width} grow-type) 100000 height)}} + :style style} ;; We use a class here because react has a bug that won't use the appropriate selector for ;; `background-clip` [:style ".text-node { background-clip: text; diff --git a/frontend/src/app/main/ui/shapes/text/styles.cljs b/frontend/src/app/main/ui/shapes/text/styles.cljs index 5ad25a7dfd..891cca1ee2 100644 --- a/frontend/src/app/main/ui/shapes/text/styles.cljs +++ b/frontend/src/app/main/ui/shapes/text/styles.cljs @@ -48,7 +48,8 @@ [_shape data] (let [line-height (:line-height data 1.2) text-align (:text-align data "start") - base #js {:fontSize (str (:font-size data (:font-size txt/default-text-attrs)) "px") + base #js {;; Fix a problem when exporting HTML + :fontSize 0 ;;(str (:font-size data (:font-size txt/default-text-attrs)) "px") :lineHeight (:line-height data (:line-height txt/default-text-attrs)) :margin 0}] (cond-> base diff --git a/frontend/src/app/main/ui/viewer/inspect/code.cljs b/frontend/src/app/main/ui/viewer/inspect/code.cljs index 2d97af2ed3..fc3f4dd6de 100644 --- a/frontend/src/app/main/ui/viewer/inspect/code.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/code.cljs @@ -7,41 +7,36 @@ (ns app.main.ui.viewer.inspect.code (:require ["js-beautify" :as beautify] - ["react-dom/server" :as rds] + [app.common.data :as d] [app.common.data.macros :as dm] [app.common.geom.shapes :as gsh] + [app.common.pages.helpers :as cph] + [app.common.types.shape-tree :as ctst] + [app.config :as cfg] [app.main.data.events :as ev] + [app.main.fonts :as fonts] [app.main.refs :as refs] - [app.main.render :as render] [app.main.store :as st] [app.main.ui.components.code-block :refer [code-block]] [app.main.ui.components.copy-button :refer [copy-button]] + [app.main.ui.components.select :refer [select]] [app.main.ui.hooks :as hooks] + [app.main.ui.hooks.resize :refer [use-resize-hook]] [app.main.ui.icons :as i] + [app.main.ui.shapes.text.fontfaces :refer [shapes->fonts]] [app.util.code-gen :as cg] + [app.util.http :as http] + [beicon.core :as rx] [cuerdas.core :as str] [potok.core :as ptk] [rumext.v2 :as mf])) -(defn generate-markup-code [objects shapes] - ;; Here we can render specific HTML code - (->> shapes - (map (fn [shape] - (dm/str - "" - (rds/renderToStaticMarkup - (mf/element - render/object-svg - #js {:objects objects - :object-id (-> shape :id)}))))) - (str/join "\n\n"))) - (defn format-code [code type] (let [code (-> code (str/replace "" "") (str/replace "><" ">\n<"))] (cond-> code - (= type "svg") (beautify/html #js {"indent_size" 2})))) + (or (= type "svg") (= type "html")) (beautify/html #js {"indent_size" 2})))) (defn get-flex-elements [page-id shapes from] (let [ids (mapv :id shapes) @@ -62,46 +57,139 @@ (refs/get-viewer-objects))))] (mf/deref page-objects-ref))) +(defn shapes->images + [shapes] + (->> shapes + (keep + (fn [shape] + (when-let [data (or (:metadata shape) (:fill-image shape))] + [(:id shape) (cfg/resolve-file-media data)]))))) + +(defn replace-map + [value map] + (reduce + (fn [value [old new]] + (str/replace value old new)) + value map)) + (mf/defc code [{:keys [shapes frame on-expand from]}] - (let [style-type (mf/use-state "css") - markup-type (mf/use-state "svg") + (let [style-type* (mf/use-state "css") + markup-type* (mf/use-state "html") + fontfaces-css* (mf/use-state nil) + images-data* (mf/use-state nil) + + style-type (deref style-type*) + markup-type (deref markup-type*) + fontfaces-css (deref fontfaces-css*) + images-data (deref images-data*) + shapes (->> shapes (map #(gsh/translate-to-frame % frame))) + route (mf/deref refs/route) page-id (:page-id (:query-params route)) flex-items (get-flex-elements page-id shapes from) objects (get-objects from) + + ;; TODO REMOVE THIS shapes (->> shapes (map #(assoc % :parent (get objects (:parent-id %)))) (map #(assoc % :flex-items flex-items))) - style-code (-> (cg/generate-style-code @style-type shapes) - (format-code "css")) + + all-children (->> shapes + (map :id) + (cph/selected-with-children objects) + (ctst/sort-z-index objects) + (map (d/getf objects))) + + + shapes (hooks/use-equal-memo shapes) + all-children (hooks/use-equal-memo all-children) + + fonts (-> (shapes->fonts all-children) + (hooks/use-equal-memo)) + + images-urls (-> (shapes->images all-children) + (hooks/use-equal-memo)) + + style-code + (mf/use-memo + (mf/deps fontfaces-css style-type all-children) + (fn [] + (dm/str + fontfaces-css "\n" + (-> (cg/generate-style-code objects style-type all-children) + (format-code style-type))))) markup-code - (-> (mf/use-memo (mf/deps shapes) #(generate-markup-code objects shapes)) - (format-code "svg")) + (mf/use-memo + (mf/deps markup-type shapes images-data) + (fn [] + (-> (cg/generate-markup-code objects markup-type (map :id shapes)) + (format-code markup-type)))) on-markup-copied (mf/use-callback - (mf/deps @markup-type) + (mf/deps markup-type) (fn [] (st/emit! (ptk/event ::ev/event {::ev/name "copy-inspect-code" - :type @markup-type})))) + :type markup-type})))) on-style-copied (mf/use-callback - (mf/deps @style-type) + (mf/deps style-type) (fn [] (st/emit! (ptk/event ::ev/event {::ev/name "copy-inspect-style" - :type @style-type}))))] + :type style-type})))) + + {on-code-pointer-down :on-pointer-down + on-code-lost-pointer-capture :on-lost-pointer-capture + on-code-pointer-move :on-pointer-move + code-size :size} + (use-resize-hook :code 400 100 800 :y false :bottom) + + set-style + (mf/use-callback + (fn [value] + (reset! style-type* value))) + + set-markup + (mf/use-callback + (fn [value] + (reset! markup-type* value)))] + + (mf/use-effect + (mf/deps fonts) + #(->> (rx/from fonts) + (rx/merge-map fonts/fetch-font-css) + (rx/reduce conj []) + (rx/subs + (fn [result] + (let [css (str/join "\n" result)] + (reset! fontfaces-css* css)))))) + + (mf/use-effect + (mf/deps images-urls) + #(->> (rx/from images-urls) + (rx/merge-map + (fn [[_ uri]] + (->> (http/fetch-data-uri uri true) + (rx/catch (fn [_] (rx/of (hash-map uri uri))))))) + (rx/reduce conj {}) + (rx/subs + (fn [result] + (reset! images-data* result))))) [:div.element-options [:div.code-block - [:div.code-row-lang "CSS" - + [:div.code-row-lang + [:& select {:default-value style-type + :class "custom-select" + :options [{:label "CSS" :value "css"}] + :on-change set-style}] [:button.expand-button {:on-click on-expand} i/full-screen] @@ -109,19 +197,31 @@ [:& copy-button {:data style-code :on-copied on-style-copied}]] - [:div.code-row-display - [:& code-block {:type @style-type - :code style-code}]]] + [:div.code-row-display {:style #js {"--code-height" (str (or code-size 400) "px")}} + [:& code-block {:type style-type + :code style-code}]] + + [:div.resize-area {:on-pointer-down on-code-pointer-down + :on-lost-pointer-capture on-code-lost-pointer-capture + :on-pointer-move on-code-pointer-move}]] + [:div.code-block - [:div.code-row-lang "SVG" + [:div.code-row-lang + [:& select {:default-value markup-type + :class "input-option" + :options [{:label "HTML" :value "html"} + {:label "SVG" :value "svg"}] + :on-change set-markup}] [:button.expand-button {:on-click on-expand} i/full-screen] - [:& copy-button {:data markup-code + [:& copy-button {:data (replace-map markup-code images-data) :on-copied on-markup-copied}]] + + [:div.code-row-display - [:& code-block {:type @markup-type + [:& code-block {:type markup-type :code markup-code}]]]])) diff --git a/frontend/src/app/main/ui/viewer/inspect/right_sidebar.cljs b/frontend/src/app/main/ui/viewer/inspect/right_sidebar.cljs index e3d03bc3f4..2cceadd341 100644 --- a/frontend/src/app/main/ui/viewer/inspect/right_sidebar.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/right_sidebar.cljs @@ -6,9 +6,7 @@ (ns app.main.ui.viewer.inspect.right-sidebar (:require - [app.main.data.workspace :as dw] [app.main.refs :as refs] - [app.main.store :as st] [app.main.ui.components.shape-icon :as si] [app.main.ui.components.tabs-container :refer [tabs-container tabs-element]] [app.main.ui.icons :as i] @@ -38,10 +36,9 @@ :data local}))))) (mf/defc right-sidebar - [{:keys [frame page file selected shapes page-id file-id share-id from] + [{:keys [frame page file selected shapes page-id file-id share-id from on-change-section on-expand] :or {from :inspect}}] - (let [expanded (mf/use-state false) - section (mf/use-state :info #_:code) + (let [section (mf/use-state :info #_:code) shapes (or shapes (resolve-shapes (:objects page) selected)) @@ -49,9 +46,29 @@ page-id (or page-id (:id page)) file-id (or file-id (:id file)) - libraries (get-libraries from)] + libraries (get-libraries from) - [:aside.settings-bar.settings-bar-right {:class (when @expanded "expanded")} + handle-change-tab + (mf/use-callback + (mf/deps from on-change-section) + (fn [new-section] + (reset! section new-section) + (when on-change-section + (on-change-section new-section)))) + + handle-expand + (mf/use-callback + (mf/deps on-expand) + (fn [] + (when on-expand (on-expand))))] + + (mf/use-effect + (mf/deps shapes handle-change-tab) + (fn [] + (when-not (seq shapes) + (handle-change-tab :info)))) + + [:aside.settings-bar.settings-bar-right [:div.settings-bar-inside (if (seq shapes) [:div.tool-window @@ -77,12 +94,8 @@ ;; inspect.tabs.code.selected.text [:span.tool-window-bar-title (:name first-shape)]])] [:div.tool-window-content.inspect - [:& tabs-container {:on-change-tab #(do - (reset! expanded false) - (reset! section %) - (when (= from :workspace) - (st/emit! (dw/set-inspect-expanded false)))) - :selected @section} + [:& tabs-container {:on-change-tab handle-change-tab + :selected @section} [:& tabs-element {:id :info :title (tr "inspect.tabs.info")} [:& attributes {:page-id page-id :file-id file-id @@ -95,10 +108,7 @@ [:& tabs-element {:id :code :title (tr "inspect.tabs.code")} [:& code {:frame frame :shapes shapes - :on-expand (fn [] - (when (= from :workspace) - (st/emit! (dw/set-inspect-expanded (not @expanded)))) - (swap! expanded not)) + :on-expand handle-expand :from from}]]]]] [:div.empty [:span.tool-window-bar-icon i/code] diff --git a/frontend/src/app/main/ui/workspace/sidebar.cljs b/frontend/src/app/main/ui/workspace/sidebar.cljs index 870487b719..f4ed014a75 100644 --- a/frontend/src/app/main/ui/workspace/sidebar.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar.cljs @@ -26,6 +26,7 @@ [app.main.ui.workspace.sidebar.sitemap :refer [sitemap]] [app.util.dom :as dom] [app.util.i18n :refer [tr]] + [app.util.object :as obj] [rumext.v2 :as mf])) ;; --- Left Sidebar (Component) @@ -134,16 +135,45 @@ is-history? (contains? layout :document-history) is-inspect? (= section :inspect) - expanded? (mf/deref refs/inspect-expanded) + ;;expanded? (mf/deref refs/inspect-expanded) + ;;prev-expanded? (hooks/use-previous expanded?) + + current-section* (mf/use-state :info) + current-section (deref current-section*) + can-be-expanded? (and (not is-comments?) (not is-history?) - is-inspect?)] + is-inspect? + (= current-section :code)) - (mf/with-effect [can-be-expanded?] - (when (not can-be-expanded?) - (st/emit! (dw/set-inspect-expanded false)))) + {:keys [on-pointer-down on-lost-pointer-capture on-pointer-move set-size size]} + (use-resize-hook :code 256 256 768 :x true :right) - [:aside.settings-bar.settings-bar-right {:class (when (and can-be-expanded? expanded?) "expanded")} + handle-change-section + (mf/use-callback + (fn [section] + (reset! current-section* section))) + + handle-expand + (mf/use-callback + (mf/deps size) + (fn [] + (set-size (if (> size 256) 256 768)))) + + props + (-> props + (obj/clone) + (obj/set! "on-change-section" handle-change-section) + (obj/set! "on-expand" handle-expand))] + + [:aside.settings-bar.settings-bar-right + {:class (when (not can-be-expanded?) "not-expand") + :style #js {"--width" (when can-be-expanded? (dm/str size "px"))}} + (when can-be-expanded? + [:div.resize-area + {:on-pointer-down on-pointer-down + :on-lost-pointer-capture on-lost-pointer-capture + :on-pointer-move on-pointer-move}]) [:div.settings-bar-inside (cond (true? is-comments?) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options.cljs b/frontend/src/app/main/ui/workspace/sidebar/options.cljs index 6151dade2c..19d98b8ac8 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options.cljs @@ -67,7 +67,7 @@ (mf/defc options-content {::mf/wrap [mf/memo]} - [{:keys [selected section shapes shapes-with-children page-id file-id]}] + [{:keys [selected section shapes shapes-with-children page-id file-id on-change-section on-expand]}] (let [drawing (mf/deref refs/workspace-drawing) objects (mf/deref refs/workspace-page-objects) shared-libs (mf/deref refs/workspace-libraries) @@ -83,9 +83,8 @@ on-change-tab (fn [options-mode] - (st/emit! (udw/set-options-mode options-mode) - (udw/set-inspect-expanded false)) - (if (= options-mode :inspect) ;;TODO maybe move this logic to set-options-mode + (st/emit! (udw/set-options-mode options-mode)) + (if (= options-mode :inspect) (st/emit! :interrupt (udw/set-workspace-read-only true)) (st/emit! :interrupt (udw/set-workspace-read-only false))))] @@ -94,7 +93,7 @@ [:& tabs-container {:on-change-tab on-change-tab :selected section} [:& tabs-element {:id :design - :title (tr "workspace.options.design")} + :title (tr "workspace.options.design")} [:div.element-options [:& align-options] [:& bool-options] @@ -143,13 +142,16 @@ [:& interactions-menu {:shape (first shapes)}]]] [:& tabs-element {:id :inspect - :title (tr "workspace.options.inspect")} - [:div.element-options - [:& hrs/right-sidebar {:page-id page-id - :file-id file-id - :frame shape-parent-frame - :shapes selected-shapes - :from :workspace}]]]]]])) + :title (tr "workspace.options.inspect")} + + [:div.element-options.element-options-inspect + [:& hrs/right-sidebar {:page-id page-id + :file-id file-id + :frame shape-parent-frame + :shapes selected-shapes + :on-change-section on-change-section + :on-expand on-expand + :from :workspace}]]]]]])) ;; TODO: this need optimizations, selected-objects and ;; selected-objects-with-children are derefed always but they only @@ -161,6 +163,8 @@ [props] (let [section (obj/get props "section") selected (obj/get props "selected") + on-change-section (obj/get props "on-change-section") + on-expand (obj/get props "on-expand") page-id (mf/use-ctx ctx/current-page-id) file-id (mf/use-ctx ctx/current-file-id) shapes (mf/deref refs/selected-objects) @@ -171,4 +175,6 @@ :shapes-with-children shapes-with-children :file-id file-id :page-id page-id - :section section}])) + :section section + :on-change-section on-change-section + :on-expand on-expand}])) 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 9cb7565464..16eaaa871c 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 @@ -125,6 +125,7 @@ :justify-items (if is-col? (case val + :stretch i/grid-justify-content-column-around :start i/grid-justify-content-column-start :end i/grid-justify-content-column-end :center i/grid-justify-content-column-center @@ -133,6 +134,7 @@ :space-evenly i/grid-justify-content-column-between) (case val + :stretch i/grid-justify-content-column-around :start i/grid-justify-content-row-start :end i/grid-justify-content-row-end :center i/grid-justify-content-row-center @@ -407,7 +409,7 @@ [{:keys [is-col? justify-items set-justify] :as props}] (let [type (if is-col? :column :row)] [:div.justify-content-style - (for [align [:start :center :end :space-around :space-between :space-evenly]] + (for [align [:stretch :start :center :end :space-around :space-between]] [:button.align-start.tooltip {:class (dom/classnames :active (= justify-items align) :tooltip-bottom-left (not= align :start) @@ -748,7 +750,7 @@ align-items-row (:layout-align-items values) align-items-column (:layout-justify-items values) - set-align-grid + set-items-grid (fn [value type] (if (= type :row) (st/emit! (dwsl/update-layout ids {:layout-align-items value})) @@ -758,7 +760,7 @@ grid-justify-content-row (:layout-align-content values) grid-justify-content-column (:layout-justify-content values) - set-justify-grid + set-content-grid (mf/use-callback (mf/deps ids) (fn [value type] @@ -833,25 +835,25 @@ [:& grid-edit-mode {:id (first ids)}]])]] [:div.layout-row - [:div.align-items-grid.row-title "Align"] + [:div.align-items-grid.row-title "Items"] [:div.btn-wrapper.align-grid [:& align-grid-row {:is-col? false :align-items align-items-row - :set-align set-align-grid}] + :set-align set-items-grid}] [:& align-grid-row {:is-col? true :align-items align-items-column - :set-align set-align-grid}]]] + :set-align set-items-grid}]]] [:div.layout-row - [:div.jusfiy-content-grid.row-title "Justify"] + [:div.jusfiy-content-grid.row-title "Content"] [:div.btn-wrapper.align-grid [:& justify-grid-row {:is-col? true :justify-items grid-justify-content-column - :set-justify set-justify-grid}] + :set-justify set-content-grid}] [:& justify-grid-row {:is-col? false :justify-items grid-justify-content-row - :set-justify set-justify-grid}]]] + :set-justify set-content-grid}]]] [:& grid-columns-row {:is-col? true :expanded? @grid-columns-open? :toggle toggle-columns-info diff --git a/frontend/src/app/util/code_gen.cljs b/frontend/src/app/util/code_gen.cljs index f3cf5eea40..24d680320a 100644 --- a/frontend/src/app/util/code_gen.cljs +++ b/frontend/src/app/util/code_gen.cljs @@ -6,14 +6,19 @@ (ns app.util.code-gen (:require + ["react-dom/server" :as rds] [app.common.data :as d] [app.common.data.macros :as dm] [app.common.pages.helpers :as cph] [app.common.text :as txt] [app.common.types.shape.layout :as ctl] + [app.config :as cfg] + [app.main.render :as render] [app.main.ui.formats :as fmt] + [app.main.ui.shapes.text.html-text :as text] [app.util.color :as uc] - [cuerdas.core :as str])) + [cuerdas.core :as str] + [rumext.v2 :as mf])) (defn shadow->css [shadow] (let [{:keys [style offset-x offset-y blur spread]} shadow @@ -24,9 +29,14 @@ (defn fill-color->background [fill] - (uc/color->background {:color (:fill-color fill) - :opacity (:fill-opacity fill) - :gradient (:fill-color-gradient fill)})) + (cond + (not= (:fill-opacity fill) 1) + (uc/color->background {:color (:fill-color fill) + :opacity (:fill-opacity fill) + :gradient (:fill-color-gradient fill)}) + + :else + (str/upper (:fill-color fill)))) (defn format-fill-color [_ shape] (let [fills (:fills shape) @@ -41,7 +51,7 @@ [(fill-color->background first-fill)])] (str/join ", " colors))) -(defn format-stroke [_ shape] +(defn format-stroke [shape] (let [first-stroke (first (:strokes shape)) width (:stroke-width first-stroke) style (let [style (:stroke-style first-stroke)] @@ -52,16 +62,32 @@ (when-not (= :none (:stroke-style first-stroke)) (str/format "%spx %s %s" width style (uc/color->background color))))) -(defn format-position [_ shape] - (let [relative? (cph/frame-shape? shape) - absolute? (or (empty? (:flex-items shape)) - (and (ctl/any-layout? (:parent shape)) (ctl/layout-absolute? shape)))] +(defn format-position [objects] + (fn [_ shape] (cond - absolute? "absolute" - relative? "relative" + (and (ctl/any-layout-immediate-child? objects shape) + (not (ctl/layout-absolute? shape)) + (or (cph/group-shape? shape) + (cph/frame-shape? shape))) + "relative" - ;; static is default value in css - :else nil))) + (and (ctl/any-layout-immediate-child? objects shape) + (not (ctl/layout-absolute? shape))) + nil + + :else + "absolute"))) + +(defn mk-grid-coord + [objects prop span-prop] + + (fn [_ shape] + (when (ctl/grid-layout-immediate-child? objects shape) + (let [parent (get objects (:parent-id shape)) + cell (ctl/get-cell-by-shape-id parent (:id shape))] + (if (> (get cell span-prop) 1) + (dm/str (get cell prop) " / " (+ (get cell prop) (get cell span-prop))) + (get cell prop)))))) (defn get-size [type values] @@ -75,14 +101,35 @@ (fmt/format-size :width value values) (fmt/format-size :heigth value values)))) +(defn make-format-absolute-pos + [objects shape coord] + (fn [value] + (let [parent-id (dm/get-in objects [(:id shape) :parent-id]) + parent-value (dm/get-in objects [parent-id :selrect coord])] + (when-not (or (cph/root-frame? shape) + (ctl/any-layout-immediate-child? objects shape) + (ctl/layout-absolute? shape)) + (fmt/format-pixels (- value parent-value)))))) + +(defn format-tracks + [tracks] + (str/join + " " + (->> tracks (map (fn [{:keys [type value]}] + (case type + :flex (dm/str (fmt/format-number value) "fr") + :percent (fmt/format-percent (/ value 100)) + :auto "auto" + (fmt/format-pixels value))))))) + (defn styles-data - [shape] + [objects shape] {:position {:props [:type] :to-prop {:type "position"} - :format {:type format-position}} + :format {:type (format-position objects)}} :layout {:props (if (or (empty? (:flex-items shape)) (ctl/layout-absolute? shape)) - [:width :height :x :y :radius :rx :r1] + [:x :y :width :height :radius :rx :r1] [:width :height :radius :rx :r1]) :to-prop {:x "left" :y "top" @@ -92,30 +139,60 @@ :format {:rotation #(str/fmt "rotate(%sdeg)" %) :r1 #(apply str/fmt "%spx %spx %spx %spx" %) :width #(get-size :width %) - :height #(get-size :height %)} + :height #(get-size :height %) + :x (make-format-absolute-pos objects shape :x) + :y (make-format-absolute-pos objects shape :y)} :multi {:r1 [:r1 :r2 :r3 :r4]}} :fill {:props [:fills] - :to-prop {:fills (if (> (count (:fills shape)) 1) "background-image" "background-color")} + :to-prop {:fills (cond + (or (cph/path-shape? shape) + (cph/mask-shape? shape) + (cph/bool-shape? shape) + (cph/svg-raw-shape? shape) + (some? (:svg-attrs shape))) + nil + + (> (count (:fills shape)) 1) + "background-image" + + (and (= (count (:fills shape)) 1) + (some? (:fill-color-gradient (first (:fills shape))))) + "background" + + :else + "background-color")} :format {:fills format-fill-color}} :stroke {:props [:strokes] :to-prop {:strokes "border"} - :format {:strokes format-stroke}} + :format {:strokes (fn [_ shape] + (when-not (or (cph/path-shape? shape) + (cph/mask-shape? shape) + (cph/bool-shape? shape) + (cph/svg-raw-shape? shape) + (some? (:svg-attrs shape))) + (format-stroke shape)))}} :shadow {:props [:shadow] :to-prop {:shadow :box-shadow} :format {:shadow #(str/join ", " (map shadow->css %1))}} :blur {:props [:blur] :to-prop {:blur "filter"} :format {:blur #(str/fmt "blur(%spx)" (:value %))}} + :layout-flex {:props [:layout :layout-flex-dir :layout-align-items + :layout-justify-items + :layout-align-content :layout-justify-content :layout-gap :layout-padding :layout-wrap-type] + :gen-props [:flex-shrink] :to-prop {:layout "display" :layout-flex-dir "flex-direction" :layout-align-items "align-items" + :layout-align-content "align-content" + :layout-justify-items "justify-items" :layout-justify-content "justify-content" :layout-wrap-type "flex-wrap" :layout-gap "gap" @@ -123,10 +200,24 @@ :format {:layout d/name :layout-flex-dir d/name :layout-align-items d/name + :layout-align-content d/name + :layout-justify-items d/name :layout-justify-content d/name :layout-wrap-type d/name :layout-gap fmt/format-gap - :layout-padding fmt/format-padding}}}) + :layout-padding fmt/format-padding + :flex-shrink (fn [_ shape] (when (ctl/flex-layout-immediate-child? objects shape) 0))}} + + :layout-grid {:props [:layout-grid-rows + :layout-grid-columns] + :gen-props [:grid-column + :grid-row] + :to-prop {:layout-grid-rows "grid-template-rows" + :layout-grid-columns "grid-template-columns"} + :format {:layout-grid-rows format-tracks + :layout-grid-columns format-tracks + :grid-column (mk-grid-coord objects :column :column-span) + :grid-row (mk-grid-coord objects :row :row-span)}}}) (def style-text {:props [:fills @@ -190,9 +281,12 @@ (defn generate-css-props ([values properties] - (generate-css-props values properties nil)) + (generate-css-props values properties [] nil)) - ([values properties params] + ([values properties gen-properties] + (generate-css-props values properties gen-properties nil)) + + ([values properties gen-properties params] (let [{:keys [to-prop format tab-size multi] :or {to-prop {} tab-size 0 multi {}}} params @@ -210,7 +304,7 @@ to-prop) get-value (fn [prop] - (if-let [props (prop multi)] + (if-let [props (get multi prop)] (map #(get values %) props) (get-specific-value values prop))) @@ -220,30 +314,35 @@ (or (nil? value) (= value 0)))) default-format (fn [value] (dm/str (fmt/format-pixels value))) - format-property (fn [prop] - (let [css-prop (or (prop to-prop) (d/name prop)) - format-fn (or (prop format) default-format) - css-val (format-fn (get-value prop) values)] - (when css-val - (dm/str - (str/repeat " " tab-size) - (str/fmt "%s: %s;" css-prop css-val)))))] - (->> properties - (remove #(null? (get-value %))) - (map format-property) - (filter (comp not nil?)) + format-property + (fn [prop] + (let [css-prop (or (get to-prop prop) (d/name prop)) + format-fn (or (get format prop) default-format) + css-val (format-fn (get-value prop) values)] + (when (and css-val (not= css-val "")) + (dm/str + (str/repeat " " tab-size) + (dm/fmt "%: %;" css-prop css-val)))))] + + (->> (concat + (->> properties + (remove #(null? (get-value %)))) + gen-properties) + (keep format-property) (str/join "\n"))))) -(defn shape->properties [shape] - (let [;; This property is added in an earlier step (code.cljs), +(defn shape->properties [objects shape] + (let [;; This property is added in an earlier step (code.cljs), ;; it will come with a vector of flex-items if any. - ;; If there are none it will continue as usual. + ;; If there are none it will continue as usual. flex-items (:flex-items shape) - props (->> (styles-data shape) vals (mapcat :props)) - to-prop (->> (styles-data shape) vals (map :to-prop) (reduce merge)) - format (->> (styles-data shape) vals (map :format) (reduce merge)) - multi (->> (styles-data shape) vals (map :multi) (reduce merge)) + props (->> (styles-data objects shape) vals (mapcat :props)) + to-prop (->> (styles-data objects shape) vals (map :to-prop) (reduce merge)) + format (->> (styles-data objects shape) vals (map :format) (reduce merge)) + multi (->> (styles-data objects shape) vals (map :multi) (reduce merge)) + gen-props (->> (styles-data objects shape) vals (mapcat :gen-props)) + props (cond-> props (seq flex-items) (concat (:props layout-flex-item-params)) (= :wrap (:layout-wrap-type shape)) (concat (:props layout-align-content))) @@ -253,10 +352,14 @@ format (cond-> format (seq flex-items) (merge (:format layout-flex-item-params)) (= :wrap (:layout-wrap-type shape)) (merge (:format layout-align-content)))] - (generate-css-props shape props {:to-prop to-prop - :format format - :multi multi - :tab-size 2}))) + (generate-css-props + shape + props + gen-props + {:to-prop to-prop + :format format + :multi multi + :tab-size 2}))) (defn search-text-attrs [node attrs] @@ -269,36 +372,36 @@ (defn parse-style-text-blocks [node attrs] (letfn - [(rec-style-text-map [acc node style] - (let [node-style (merge style (select-keys node attrs)) - head (or (-> acc first) [{} ""]) - [head-style head-text] head + [(rec-style-text-map [acc node style] + (let [node-style (merge style (select-keys node attrs)) + head (or (-> acc first) [{} ""]) + [head-style head-text] head - new-acc - (cond - (:children node) - (reduce #(rec-style-text-map %1 %2 node-style) acc (:children node)) + new-acc + (cond + (:children node) + (reduce #(rec-style-text-map %1 %2 node-style) acc (:children node)) - (not= head-style node-style) - (cons [node-style (:text node "")] acc) + (not= head-style node-style) + (cons [node-style (:text node "")] acc) - :else - (cons [node-style (dm/str head-text "" (:text node))] (rest acc))) + :else + (cons [node-style (dm/str head-text "" (:text node))] (rest acc))) ;; We add an end-of-line when finish a paragraph - new-acc - (if (= (:type node) "paragraph") - (let [[hs ht] (first new-acc)] - (cons [hs (dm/str ht "\n")] (rest new-acc))) - new-acc)] - new-acc))] + new-acc + (if (= (:type node) "paragraph") + (let [[hs ht] (first new-acc)] + (cons [hs (dm/str ht "\n")] (rest new-acc))) + new-acc)] + new-acc))] (-> (rec-style-text-map [] node {}) reverse))) -(defn text->properties [shape] +(defn text->properties [objects shape] (let [flex-items (:flex-items shape) - text-shape-style (select-keys (styles-data shape) [:layout :shadow :blur]) + text-shape-style (d/without-keys (styles-data objects shape) [:fill :stroke]) shape-props (->> text-shape-style vals (mapcat :props)) shape-to-prop (->> text-shape-style vals (map :to-prop) (reduce merge)) @@ -315,6 +418,7 @@ (:content shape) (conj (:props style-text) :fill-color-gradient :fill-opacity)) (d/merge txt/default-text-attrs))] + (str/join "\n" [(generate-css-props shape @@ -328,21 +432,128 @@ :format (:format style-text) :tab-size 2})]))) -(defn generate-css [shape] - (let [name (:name shape) - properties (if (= :text (:type shape)) - (text->properties shape) - (shape->properties shape)) - selector (str/css-selector name) +(defn selector-name [shape] + (let [ + name (-> (:name shape) + #_(subs 0 (min 10 (count (:name shape))))) + ;; selectors cannot start with numbers + name (if (re-matches #"^\d.*" name) (dm/str "c-" name) name) + id (-> (dm/str (:id shape)) + #_(subs 24 36)) + selector (str/css-selector (dm/str name " " id)) selector (if (str/starts-with? selector "-") (subs selector 1) selector)] + selector)) + +(defn generate-css [objects shape] + (let [name (:name shape) + properties (shape->properties objects shape) + selector (selector-name shape)] (str/join "\n" [(str/fmt "/* %s */" name) (str/fmt ".%s {" selector) properties "}"]))) -(defn generate-style-code [type shapes] +(defn generate-svg + [objects shape-id] + (let [shape (get objects shape-id)] + (rds/renderToStaticMarkup + (mf/element + render/object-svg + #js {:objects objects + :object-id (-> shape :id)})))) + +(defn generate-html + ([objects shape-id] + (generate-html objects shape-id 0)) + + ([objects shape-id level] + (let [shape (get objects shape-id) + indent (str/repeat " " level) + maybe-reverse (if (ctl/any-layout? shape) reverse identity)] + (cond + (cph/text-shape? shape) + (let [text-shape-html (rds/renderToStaticMarkup (mf/element text/text-shape #js {:shape shape :code? true}))] + (dm/fmt "%
\n%\n%
" + indent + (selector-name shape) + text-shape-html + indent)) + + (cph/image-shape? shape) + (let [data (or (:metadata shape) (:fill-image shape)) + image-url (cfg/resolve-file-media data)] + (dm/fmt "%\n%" + indent + image-url + (selector-name shape) + indent)) + + (or (cph/path-shape? shape) + (cph/mask-shape? shape) + (cph/bool-shape? shape) + (cph/svg-raw-shape? shape) + (some? (:svg-attrs shape))) + (let [svg-markup (rds/renderToStaticMarkup (mf/element render/object-svg #js {:objects objects :object-id (:id shape) :render-embed? false}))] + (dm/fmt "%
\n%\n%
" + indent + (selector-name shape) + svg-markup + indent)) + + (empty? (:shapes shape)) + (dm/fmt "%
\n%
" + indent + (selector-name shape) + indent) + + :else + (dm/fmt "%
\n%\n%
" + indent + (selector-name shape) + (->> (:shapes shape) + (maybe-reverse) + (map #(generate-html objects % (inc level))) + (str/join "\n")) + indent))))) + +(defn generate-markup-code [objects type shapes] + (let [generate-markup-fn (case type + "html" generate-html + "svg" generate-svg)] + (->> shapes + (map #(generate-markup-fn objects % 0)) + (str/join "\n")))) + +(defn generate-style-code [objects type shapes] (let [generate-style-fn (case type "css" generate-css)] - (->> shapes - (map generate-style-fn) - (str/join "\n\n")))) + (dm/str + "html, body { + background-color: #E8E9EA; + height: 100%; + margin: 0; + padding: 0; + width: 100%; +} + +body { + display: flex; + flex-direction: column; + align-items: center; + padding: 2rem; +} + +svg { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); +} + +* { + box-sizing: border-box; +} +\n" + (->> shapes + (map (partial generate-style-fn objects)) + (str/join "\n\n"))))) From e01af790f374372018cfdcb173591138e9f2ad43 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 14 Jun 2023 12:24:40 +0200 Subject: [PATCH 38/52] :sparkles: Add copy all code button --- .../styles/main/partials/inspect.scss | 4 ++ .../app/main/ui/components/copy_button.cljs | 2 +- .../src/app/main/ui/shapes/text/fo_text.cljs | 2 +- .../app/main/ui/shapes/text/html_text.cljs | 2 +- .../src/app/main/ui/viewer/inspect/code.cljs | 72 ++++++++++++++----- frontend/src/app/util/code_gen.cljs | 5 +- 6 files changed, 64 insertions(+), 23 deletions(-) diff --git a/frontend/resources/styles/main/partials/inspect.scss b/frontend/resources/styles/main/partials/inspect.scss index 8693da4e5d..9b2a29ea72 100644 --- a/frontend/resources/styles/main/partials/inspect.scss +++ b/frontend/resources/styles/main/partials/inspect.scss @@ -332,6 +332,10 @@ margin-top: 0.5rem; border-top: 1px solid $color-gray-60; + &:last-child { + margin-bottom: 18px; + } + &:hover { .code-row-lang { .expand-button, diff --git a/frontend/src/app/main/ui/components/copy_button.cljs b/frontend/src/app/main/ui/components/copy_button.cljs index 571c37551e..55cbee2f77 100644 --- a/frontend/src/app/main/ui/components/copy_button.cljs +++ b/frontend/src/app/main/ui/components/copy_button.cljs @@ -27,7 +27,7 @@ [:button.copy-button {:on-click #(when-not @just-copied (reset! just-copied true) - (wapi/write-to-clipboard data))} + (wapi/write-to-clipboard (if (fn? data) (data) data)))} (if @just-copied i/tick i/copy)])) diff --git a/frontend/src/app/main/ui/shapes/text/fo_text.cljs b/frontend/src/app/main/ui/shapes/text/fo_text.cljs index 994b989808..6a8d21eb26 100644 --- a/frontend/src/app/main/ui/shapes/text/fo_text.cljs +++ b/frontend/src/app/main/ui/shapes/text/fo_text.cljs @@ -197,7 +197,7 @@ ;; We use a class here because react has a bug that won't use the appropriate selector for ;; `background-clip` [:style ".text-node { background-clip: text; - -webkit-background-clip: text;" ] + -webkit-background-clip: text; }" ] [:& render-node {:index 0 :shape shape :node content}]])) diff --git a/frontend/src/app/main/ui/shapes/text/html_text.cljs b/frontend/src/app/main/ui/shapes/text/html_text.cljs index 399d57d3e5..7a41869f75 100644 --- a/frontend/src/app/main/ui/shapes/text/html_text.cljs +++ b/frontend/src/app/main/ui/shapes/text/html_text.cljs @@ -104,7 +104,7 @@ ;; We use a class here because react has a bug that won't use the appropriate selector for ;; `background-clip` [:style ".text-node { background-clip: text; - -webkit-background-clip: text;" ] + -webkit-background-clip: text; }" ] [:& render-node {:index 0 :shape shape :node content}]])) diff --git a/frontend/src/app/main/ui/viewer/inspect/code.cljs b/frontend/src/app/main/ui/viewer/inspect/code.cljs index fc3f4dd6de..99e881d778 100644 --- a/frontend/src/app/main/ui/viewer/inspect/code.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/code.cljs @@ -26,11 +26,25 @@ [app.main.ui.shapes.text.fontfaces :refer [shapes->fonts]] [app.util.code-gen :as cg] [app.util.http :as http] + [app.util.webapi :as wapi] [beicon.core :as rx] [cuerdas.core :as str] [potok.core :as ptk] [rumext.v2 :as mf])) +(def page-template + " + + + + + + %s + +") + (defn format-code [code type] (let [code (-> code (str/replace "" "") @@ -41,9 +55,13 @@ (defn get-flex-elements [page-id shapes from] (let [ids (mapv :id shapes) ids (hooks/use-equal-memo ids) - get-layout-children-refs (mf/use-memo (mf/deps ids page-id from) #(if (= from :workspace) - (refs/workspace-get-flex-child ids) - (refs/get-flex-child-viewer ids page-id)))] + + get-layout-children-refs + (mf/use-memo + (mf/deps ids page-id from) + #(if (= from :workspace) + (refs/workspace-get-flex-child ids) + (refs/get-flex-child-viewer ids page-id)))] (mf/deref get-layout-children-refs))) @@ -145,10 +163,16 @@ {::ev/name "copy-inspect-style" :type style-type})))) - {on-code-pointer-down :on-pointer-down - on-code-lost-pointer-capture :on-lost-pointer-capture - on-code-pointer-move :on-pointer-move - code-size :size} + {on-markup-pointer-down :on-pointer-down + on-markup-lost-pointer-capture :on-lost-pointer-capture + on-markup-pointer-move :on-pointer-move + markup-size :size} + (use-resize-hook :code 400 100 800 :y false :bottom) + + {on-style-pointer-down :on-pointer-down + on-style-lost-pointer-capture :on-lost-pointer-capture + on-style-pointer-move :on-pointer-move + style-size :size} (use-resize-hook :code 400 100 800 :y false :bottom) set-style @@ -159,7 +183,15 @@ set-markup (mf/use-callback (fn [value] - (reset! markup-type* value)))] + (reset! markup-type* value))) + + handle-copy-all-code + (mf/use-callback + (mf/deps style-code markup-code images-data) + (fn [] + (let [markup-code (replace-map markup-code images-data) + data (str/format page-template style-code markup-code)] + (wapi/write-to-clipboard data))))] (mf/use-effect (mf/deps fonts) @@ -184,6 +216,10 @@ (reset! images-data* result))))) [:div.element-options + [:div.attributes-block + [:button.download-button {:on-click handle-copy-all-code} + "Copy all code"]] + [:div.code-block [:div.code-row-lang [:& select {:default-value style-type @@ -197,14 +233,13 @@ [:& copy-button {:data style-code :on-copied on-style-copied}]] - [:div.code-row-display {:style #js {"--code-height" (str (or code-size 400) "px")}} + [:div.code-row-display {:style #js {"--code-height" (str (or style-size 400) "px")}} [:& code-block {:type style-type :code style-code}]] - [:div.resize-area {:on-pointer-down on-code-pointer-down - :on-lost-pointer-capture on-code-lost-pointer-capture - :on-pointer-move on-code-pointer-move}]] - + [:div.resize-area {:on-pointer-down on-style-pointer-down + :on-lost-pointer-capture on-style-lost-pointer-capture + :on-pointer-move on-style-pointer-move}]] [:div.code-block [:div.code-row-lang @@ -218,10 +253,13 @@ {:on-click on-expand} i/full-screen] - [:& copy-button {:data (replace-map markup-code images-data) + [:& copy-button {:data #(replace-map markup-code images-data) :on-copied on-markup-copied}]] - - [:div.code-row-display + [:div.code-row-display {:style #js {"--code-height" (str (or markup-size 400) "px")}} [:& code-block {:type markup-type - :code markup-code}]]]])) + :code markup-code}]] + + [:div.resize-area {:on-pointer-down on-markup-pointer-down + :on-lost-pointer-capture on-markup-lost-pointer-capture + :on-pointer-move on-markup-pointer-move}]]])) diff --git a/frontend/src/app/util/code_gen.cljs b/frontend/src/app/util/code_gen.cljs index 24d680320a..8dfb5d364f 100644 --- a/frontend/src/app/util/code_gen.cljs +++ b/frontend/src/app/util/code_gen.cljs @@ -433,9 +433,8 @@ :tab-size 2})]))) (defn selector-name [shape] - (let [ - name (-> (:name shape) - #_(subs 0 (min 10 (count (:name shape))))) + (let [name (-> (:name shape) + (subs 0 (min 10 (count (:name shape))))) ;; selectors cannot start with numbers name (if (re-matches #"^\d.*" name) (dm/str "c-" name) name) id (-> (dm/str (:id shape)) From c3a8c3826d1a4736359d001dcabf51f7676fe7d1 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 14 Jun 2023 13:00:50 +0200 Subject: [PATCH 39/52] :sparkles: Changes to edit UI --- .../partials/sidebar-element-options.scss | 27 +++++++++++-- .../options/menus/layout_container.cljs | 38 ++++++++++--------- 2 files changed, 45 insertions(+), 20 deletions(-) diff --git a/frontend/resources/styles/main/partials/sidebar-element-options.scss b/frontend/resources/styles/main/partials/sidebar-element-options.scss index 7febf9415a..192e82c9da 100644 --- a/frontend/resources/styles/main/partials/sidebar-element-options.scss +++ b/frontend/resources/styles/main/partials/sidebar-element-options.scss @@ -1781,20 +1781,31 @@ } .edit-mode { - display: flex; - justify-content: center; align-items: center; + border-radius: 4px; + border: 1px solid $color-gray-60; + display: flex; + flex-direction: row; + gap: 10px; + justify-content: center; margin-left: 5px; + padding: 0 8px; + text-align: left; + width: 120px; button { + color: $color-gray-30; display: flex; justify-content: center; align-items: center; background: transparent; border: none; cursor: pointer; + gap: 16px; + &.active, &:hover { + color: $color-primary; svg { fill: $color-primary; } @@ -1802,7 +1813,17 @@ } } - &.align-grid { + &.align-grid-items { + flex-direction: row; + gap: 0px; + margin: 7px 0; + + .align-items-style { + margin-right: 2px; + } + } + + &.align-grid-content { flex-direction: column; gap: 7px; margin: 7px 0; 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 16eaaa871c..f75e9c2c5c 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 @@ -125,7 +125,7 @@ :justify-items (if is-col? (case val - :stretch i/grid-justify-content-column-around + :stretch i/align-items-row-strech :start i/grid-justify-content-column-start :end i/grid-justify-content-column-end :center i/grid-justify-content-column-center @@ -134,7 +134,7 @@ :space-evenly i/grid-justify-content-column-between) (case val - :stretch i/grid-justify-content-column-around + :stretch i/align-items-column-strech :start i/grid-justify-content-row-start :end i/grid-justify-content-row-end :center i/grid-justify-content-row-center @@ -389,6 +389,7 @@ :alt "Grid edit mode" :on-click #(toggle-edit-mode) :style {:padding 0}} + "Edit grid" i/grid-layout-mode])) (mf/defc align-grid-row @@ -400,7 +401,9 @@ {:class (dom/classnames :active (= align-items align) :tooltip-bottom-left (not= align :start) :tooltip-bottom (= align :start)) - :alt (dm/str "Align items " (d/name align)) + :alt (if is-col? + (dm/str "justify-items: " (d/name align)) + (dm/str "align-items: " (d/name align))) :on-click #(set-align align type) :key (dm/str "align-items" (d/name align))} (get-layout-flex-icon :align-items align is-col?)])])) @@ -409,12 +412,14 @@ [{:keys [is-col? justify-items set-justify] :as props}] (let [type (if is-col? :column :row)] [:div.justify-content-style - (for [align [:stretch :start :center :end :space-around :space-between]] + (for [align [:start :center :end :space-around :space-between :stretch]] [:button.align-start.tooltip {:class (dom/classnames :active (= justify-items align) :tooltip-bottom-left (not= align :start) :tooltip-bottom (= align :start)) - :alt (dm/str "Justify content " (d/name align)) + :alt (if is-col? + (dm/str "justify-content: " (d/name align)) + (dm/str "align-content: " (d/name align))) :on-click #(set-justify align type) :key (dm/str "justify-content" (d/name align))} (get-layout-grid-icon :justify-items align is-col?)])])) @@ -682,15 +687,15 @@ :dir dir :saved-dir saved-grid-dir :set-direction #(set-direction dir :grid) - :icon? false}])]] + :icon? true}])]] (when (= 1 (count ids)) [:div.edit-mode [:& grid-edit-mode {:id (first ids)}]])]] [:div.layout-row - [:div.align-items-grid.row-title "Align"] - [:div.btn-wrapper.align-grid + [:div.align-items-grid.row-title "Items"] + [:div.btn-wrapper.align-grid-items [:& align-grid-row {:is-col? false :align-items align-items-row :set-align set-align-grid}] @@ -700,8 +705,8 @@ :set-align set-align-grid}]]] [:div.layout-row - [:div.jusfiy-content-grid.row-title "Justify"] - [:div.btn-wrapper.align-grid + [:div.jusfiy-content-grid.row-title "Content"] + [:div.btn-wrapper.align-grid-content [:& justify-grid-row {:is-col? true :justify-items grid-justify-content-column :set-justify set-justify-grid}] @@ -822,13 +827,12 @@ [:div.direction-wrap.row-title "Direction"] [:div.btn-wrapper [:div.direction - [:* - (for [dir [:row :column]] - [:& direction-btn {:key (d/name dir) - :dir dir - :saved-dir saved-grid-dir - :set-direction #(set-direction dir) - :icon? false}])]] + (for [dir [:row :column]] + [:& direction-btn {:key (d/name dir) + :dir dir + :saved-dir saved-grid-dir + :set-direction #(set-direction dir) + :icon? true}])] (when (= 1 (count ids)) [:div.edit-mode From a2c59acfa9243cb166be6dd9ba6c6d1de43d1f48 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 19 Jun 2023 11:06:24 +0200 Subject: [PATCH 40/52] :sparkles: Update info panel --- common/src/app/common/text.cljc | 34 ++ frontend/src/app/main/ui/formats.cljs | 28 +- .../app/main/ui/shapes/text/html_text.cljs | 14 +- .../main/ui/viewer/inspect/attributes.cljs | 30 +- .../ui/viewer/inspect/attributes/blur.cljs | 19 +- .../ui/viewer/inspect/attributes/common.cljs | 1 - .../ui/viewer/inspect/attributes/fill.cljs | 21 +- .../viewer/inspect/attributes/geometry.cljs | 38 ++ .../ui/viewer/inspect/attributes/image.cljs | 12 +- .../ui/viewer/inspect/attributes/layout.cljs | 114 ++-- .../inspect/attributes/layout_element.cljs | 60 ++ .../inspect/attributes/layout_flex.cljs | 139 ----- .../attributes/layout_flex_element.cljs | 155 ----- .../ui/viewer/inspect/attributes/shadow.cljs | 19 +- .../ui/viewer/inspect/attributes/stroke.cljs | 61 +- .../ui/viewer/inspect/attributes/text.cljs | 106 ++-- .../src/app/main/ui/viewer/inspect/code.cljs | 4 +- .../main/ui/viewer/inspect/right_sidebar.cljs | 7 +- .../main/ui/workspace/sidebar/options.cljs | 1 + .../viewport/grid_layout_editor.cljs | 28 +- frontend/src/app/util/code_gen.cljs | 568 +----------------- frontend/src/app/util/code_gen/common.cljs | 22 + .../src/app/util/code_gen/markup_html.cljs | 78 +++ .../src/app/util/code_gen/markup_svg.cljs | 26 + frontend/src/app/util/code_gen/style_css.cljs | 194 ++++++ .../app/util/code_gen/style_css_formats.cljs | 123 ++++ .../app/util/code_gen/style_css_values.cljs | 282 +++++++++ 27 files changed, 1040 insertions(+), 1144 deletions(-) create mode 100644 frontend/src/app/main/ui/viewer/inspect/attributes/geometry.cljs create mode 100644 frontend/src/app/main/ui/viewer/inspect/attributes/layout_element.cljs delete mode 100644 frontend/src/app/main/ui/viewer/inspect/attributes/layout_flex.cljs delete mode 100644 frontend/src/app/main/ui/viewer/inspect/attributes/layout_flex_element.cljs create mode 100644 frontend/src/app/util/code_gen/common.cljs create mode 100644 frontend/src/app/util/code_gen/markup_html.cljs create mode 100644 frontend/src/app/util/code_gen/markup_svg.cljs create mode 100644 frontend/src/app/util/code_gen/style_css.cljs create mode 100644 frontend/src/app/util/code_gen/style_css_formats.cljs create mode 100644 frontend/src/app/util/code_gen/style_css_values.cljs diff --git a/common/src/app/common/text.cljc b/common/src/app/common/text.cljc index da941a60ec..abcb366315 100644 --- a/common/src/app/common/text.cljc +++ b/common/src/app/common/text.cljc @@ -8,6 +8,7 @@ (:require [app.common.colors :as clr] [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.transit :as t] [clojure.walk :as walk] [cuerdas.core :as str])) @@ -29,6 +30,8 @@ :fills [{:fill-color clr/black :fill-opacity 1}]}) +(def text-attrs (keys default-text-attrs)) + (def typography-fields [:font-id :font-family @@ -252,3 +255,34 @@ {:blocks (reduce #(conj %1 (build-block %2)) [] (node-seq #(= (:type %) "paragraph") root)) :entityMap {}})) + +(defn content->text+styles + "Given a root node of a text content extracts the texts with its associated styles" + [node] + (letfn + [(rec-style-text-map [acc node style] + (let [node-style (merge style (select-keys node text-attrs)) + head (or (-> acc first) [{} ""]) + [head-style head-text] head + + new-acc + (cond + (:children node) + (reduce #(rec-style-text-map %1 %2 node-style) acc (:children node)) + + (not= head-style node-style) + (cons [node-style (:text node "")] acc) + + :else + (cons [node-style (dm/str head-text "" (:text node))] (rest acc))) + + ;; We add an end-of-line when finish a paragraph + new-acc + (if (= (:type node) "paragraph") + (let [[hs ht] (first new-acc)] + (cons [hs (dm/str ht "\n")] (rest new-acc))) + new-acc)] + new-acc))] + + (-> (rec-style-text-map [] node {}) + reverse))) diff --git a/frontend/src/app/main/ui/formats.cljs b/frontend/src/app/main/ui/formats.cljs index 76fac5eda0..28296b183a 100644 --- a/frontend/src/app/main/ui/formats.cljs +++ b/frontend/src/app/main/ui/formats.cljs @@ -16,32 +16,36 @@ (format-percent value nil)) ([value {:keys [precision] :or {precision 2}}] - (when (d/num? value) - (let [percent-val (mth/precision (* value 100) precision)] - (dm/str percent-val "%"))))) + (let [value (if (string? value) (d/parse-double value) value)] + (when (d/num? value) + (let [percent-val (mth/precision (* value 100) precision)] + (dm/str percent-val "%")))))) (defn format-number ([value] (format-number value nil)) ([value {:keys [precision] :or {precision 2}}] - (when (d/num? value) - (let [value (mth/precision value precision)] - (dm/str value))))) + (let [value (if (string? value) (d/parse-double value) value)] + (when (d/num? value) + (let [value (mth/precision value precision)] + (dm/str value)))))) (defn format-pixels ([value] (format-pixels value nil)) ([value {:keys [precision] :or {precision 2}}] - (when (d/num? value) - (let [value (mth/precision value precision)] - (dm/str value "px"))))) + (let [value (if (string? value) (d/parse-double value) value)] + (when (d/num? value) + (let [value (mth/precision value precision)] + (dm/str value "px")))))) (defn format-int [value] - (when (d/num? value) - (let [value (mth/precision value 0)] - (dm/str value)))) + (let [value (if (string? value) (d/parse-double value) value)] + (when (d/num? value) + (let [value (mth/precision value 0)] + (dm/str value))))) (defn format-padding-margin-shorthand [values] diff --git a/frontend/src/app/main/ui/shapes/text/html_text.cljs b/frontend/src/app/main/ui/shapes/text/html_text.cljs index 7a41869f75..301b56fb66 100644 --- a/frontend/src/app/main/ui/shapes/text/html_text.cljs +++ b/frontend/src/app/main/ui/shapes/text/html_text.cljs @@ -59,7 +59,8 @@ (mf/defc render-node {::mf/wrap-props false} [props] - (let [{:keys [type text children] :as parent} (obj/get props "node")] + (let [{:keys [type text children] :as parent} (obj/get props "node") + code? (obj/get props "code?")] (if (string? text) [:> render-text props] (let [component (case type @@ -74,7 +75,8 @@ (obj/set! "node" node) (obj/set! "parent" parent) (obj/set! "index" index) - (obj/set! "key" index))] + (obj/set! "key" index) + (obj/set! "code?" code?))] [:> render-node props]))]))))) (mf/defc text-shape @@ -103,8 +105,10 @@ :style style} ;; We use a class here because react has a bug that won't use the appropriate selector for ;; `background-clip` - [:style ".text-node { background-clip: text; - -webkit-background-clip: text; }" ] + (when (not code?) + [:style ".text-node { background-clip: text; + -webkit-background-clip: text; }" ]) [:& render-node {:index 0 :shape shape - :node content}]])) + :node content + :code? code?}]])) diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes.cljs index 19fc153112..d1196a7180 100644 --- a/frontend/src/app/main/ui/viewer/inspect/attributes.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/attributes.cljs @@ -6,16 +6,15 @@ (ns app.main.ui.viewer.inspect.attributes (:require - [app.common.geom.shapes :as gsh] [app.common.types.components-list :as ctkl] [app.main.ui.hooks :as hooks] [app.main.ui.viewer.inspect.annotation :refer [annotation]] [app.main.ui.viewer.inspect.attributes.blur :refer [blur-panel]] [app.main.ui.viewer.inspect.attributes.fill :refer [fill-panel]] + [app.main.ui.viewer.inspect.attributes.geometry :refer [geometry-panel]] [app.main.ui.viewer.inspect.attributes.image :refer [image-panel]] [app.main.ui.viewer.inspect.attributes.layout :refer [layout-panel]] - [app.main.ui.viewer.inspect.attributes.layout-flex :refer [layout-flex-panel]] - [app.main.ui.viewer.inspect.attributes.layout-flex-element :refer [layout-flex-element-panel]] + [app.main.ui.viewer.inspect.attributes.layout-element :refer [layout-element-panel]] [app.main.ui.viewer.inspect.attributes.shadow :refer [shadow-panel]] [app.main.ui.viewer.inspect.attributes.stroke :refer [stroke-panel]] [app.main.ui.viewer.inspect.attributes.svg :refer [svg-panel]] @@ -24,20 +23,18 @@ [rumext.v2 :as mf])) (def type->options - {:multiple [:fill :stroke :image :text :shadow :blur :layout-flex-item] - :frame [:layout :fill :stroke :shadow :blur :layout-flex :layout-flex-item] - :group [:layout :svg :layout-flex-item] - :rect [:layout :fill :stroke :shadow :blur :svg :layout-flex-item] - :circle [:layout :fill :stroke :shadow :blur :svg :layout-flex-item] - :path [:layout :fill :stroke :shadow :blur :svg :layout-flex-item] - :image [:image :layout :fill :stroke :shadow :blur :svg :layout-flex-item] - :text [:layout :text :shadow :blur :stroke :layout-flex-item]}) + {:multiple [:fill :stroke :image :text :shadow :blur :layout-element] + :frame [:geometry :fill :stroke :shadow :blur :layout :layout-element] + :group [:geometry :svg :layout-element] + :rect [:geometry :fill :stroke :shadow :blur :svg :layout-element] + :circle [:geometry :fill :stroke :shadow :blur :svg :layout-element] + :path [:geometry :fill :stroke :shadow :blur :svg :layout-element] + :image [:image :geometry :fill :stroke :shadow :blur :svg :layout-element] + :text [:geometry :text :shadow :blur :stroke :layout-element]}) (mf/defc attributes - [{:keys [page-id file-id shapes frame from libraries share-id]}] + [{:keys [page-id file-id shapes frame from libraries share-id objects]}] (let [shapes (hooks/use-equal-memo shapes) - shapes (mf/with-memo [shapes] - (mapv #(gsh/translate-to-frame % frame) shapes)) type (if (= (count shapes) 1) (-> shapes first :type) :multiple) options (type->options type) content (when (= (count shapes) 1) @@ -46,9 +43,9 @@ [:div.element-options (for [[idx option] (map-indexed vector options)] [:> (case option + :geometry geometry-panel :layout layout-panel - :layout-flex layout-flex-panel - :layout-flex-item layout-flex-element-panel + :layout-element layout-element-panel :fill fill-panel :stroke stroke-panel :shadow shadow-panel @@ -58,6 +55,7 @@ :svg svg-panel) {:key idx :shapes shapes + :objects objects :frame frame :from from}]) (when content diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/blur.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/blur.cljs index 86d97d3606..f8c3446da4 100644 --- a/frontend/src/app/main/ui/viewer/inspect/attributes/blur.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/attributes/blur.cljs @@ -7,32 +7,25 @@ (ns app.main.ui.viewer.inspect.attributes.blur (:require [app.main.ui.components.copy-button :refer [copy-button]] - [app.util.code-gen :as cg] + [app.util.code-gen.style-css :as css] [app.util.i18n :refer [tr]] - [cuerdas.core :as str] [rumext.v2 :as mf])) (defn has-blur? [shape] (:blur shape)) -(defn copy-data [shape] - (cg/generate-css-props - shape - :blur - {:to-prop "filter" - :format #(str/fmt "blur(%spx)" (:value %))})) - -(mf/defc blur-panel [{:keys [shapes]}] +(mf/defc blur-panel + [{:keys [objects shapes]}] (let [shapes (->> shapes (filter has-blur?))] (when (seq shapes) [:div.attributes-block [:div.attributes-block-title [:div.attributes-block-title-text (tr "inspect.attributes.blur")] (when (= (count shapes) 1) - [:& copy-button {:data (copy-data (first shapes))}])] + [:& copy-button {:data (css/get-css-property objects (first shapes) :filter)}])] (for [shape shapes] [:div.attributes-unit-row [:div.attributes-label (tr "inspect.attributes.blur.value")] - [:div.attributes-value (-> shape :blur :value) "px"] - [:& copy-button {:data (copy-data shape)}]])]))) + [:div.attributes-value (css/get-css-value objects shape :filter)] + [:& copy-button {:data (css/get-css-property objects shape :filter)}]])]))) diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/common.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/common.cljs index 76e593f4c8..468132b7e4 100644 --- a/frontend/src/app/main/ui/viewer/inspect/attributes/common.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/attributes/common.cljs @@ -17,7 +17,6 @@ [okulary.core :as l] [rumext.v2 :as mf])) - (def file-colors-ref (l/derived (l/in [:viewer :file :data :colors]) st/state)) diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/fill.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/fill.cljs index 500ddd5007..0292569ed7 100644 --- a/frontend/src/app/main/ui/viewer/inspect/attributes/fill.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/attributes/fill.cljs @@ -7,12 +7,11 @@ (ns app.main.ui.viewer.inspect.attributes.fill (:require [app.main.ui.viewer.inspect.attributes.common :refer [color-row]] - [app.util.code-gen :as cg] - [app.util.color :as uc] + [app.util.code-gen.style-css :as css] [app.util.i18n :refer [tr]] [rumext.v2 :as mf])) -(def fill-attributes [:fill-color :fill-color-gradient]) +(def properties [:background :background-color :background-image]) (defn shape->color [shape] {:color (:fill-color shape) @@ -21,21 +20,15 @@ :id (:fill-color-ref-id shape) :file-id (:fill-color-ref-file shape)}) -(defn has-color? [shape] +(defn has-fill? [shape] (and (not (contains? #{:text :group} (:type shape))) (or (:fill-color shape) (:fill-color-gradient shape) (seq (:fills shape))))) -(defn copy-data [shape] - (cg/generate-css-props - shape - fill-attributes - {:to-prop "background" - :format #(uc/color->background (shape->color shape))})) - -(mf/defc fill-block [{:keys [shape]}] +(mf/defc fill-block + [{:keys [objects shape]}] (let [color-format (mf/use-state :hex) color (shape->color shape)] @@ -43,11 +36,11 @@ [:& color-row {:color color :format @color-format :on-change-format #(reset! color-format %) - :copy-data (copy-data shape)}]])) + :copy-data (css/get-shape-properties-css objects {:fills [shape]} properties)}]])) (mf/defc fill-panel [{:keys [shapes]}] - (let [shapes (->> shapes (filter has-color?))] + (let [shapes (->> shapes (filter has-fill?))] (when (seq shapes) [:div.attributes-block [:div.attributes-block-title diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/geometry.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/geometry.cljs new file mode 100644 index 0000000000..d24ea3e6a7 --- /dev/null +++ b/frontend/src/app/main/ui/viewer/inspect/attributes/geometry.cljs @@ -0,0 +1,38 @@ +;; 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.main.ui.viewer.inspect.attributes.geometry + (:require + [app.common.data :as d] + [app.main.ui.components.copy-button :refer [copy-button]] + [app.util.code-gen.style-css :as css] + [app.util.i18n :refer [tr]] + [rumext.v2 :as mf])) + +(def properties [:width :height :left :top :border-radius :transform]) + +(mf/defc geometry-block + [{:keys [objects shape]}] + [:* + (for [property properties] + (when-let [value (css/get-css-value objects shape property)] + [:div.attributes-unit-row + [:div.attributes-label (d/name property)] + [:div.attributes-value value] + [:& copy-button {:data (css/get-css-property objects shape property)}]]))]) + +(mf/defc geometry-panel + [{:keys [objects shapes]}] + [:div.attributes-block + [:div.attributes-block-title + [:div.attributes-block-title-text (tr "inspect.attributes.size")] + (when (= (count shapes) 1) + [:& copy-button {:data (css/get-shape-properties-css objects (first shapes) properties)}])] + + (for [shape shapes] + [:& geometry-block {:shape shape + :objects objects + :key (:id shape)}])]) diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/image.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/image.cljs index 5d25e5e9db..878661a3f8 100644 --- a/frontend/src/app/main/ui/viewer/inspect/attributes/image.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/attributes/image.cljs @@ -10,7 +10,7 @@ [app.common.pages.helpers :as cph] [app.config :as cf] [app.main.ui.components.copy-button :refer [copy-button]] - [app.util.code-gen :as cg] + [app.util.code-gen.style-css :as css] [app.util.i18n :refer [tr]] [cuerdas.core :as str] [rumext.v2 :as mf])) @@ -19,7 +19,7 @@ (= (:type shape) :image)) (mf/defc image-panel - [{:keys [shapes]}] + [{:keys [objects shapes]}] (for [shape (filter cph/image-shape? shapes)] [:div.attributes-block {:key (str "image-" (:id shape))} [:div.attributes-image-row @@ -28,13 +28,13 @@ [:div.attributes-unit-row [:div.attributes-label (tr "inspect.attributes.image.width")] - [:div.attributes-value (-> shape :metadata :width) "px"] - [:& copy-button {:data (cg/generate-css-props shape :width)}]] + [:div.attributes-value (css/get-css-value objects (:metadata shape) :width)] + [:& copy-button {:data (css/get-css-property objects (:metadata shape) :width)}]] [:div.attributes-unit-row [:div.attributes-label (tr "inspect.attributes.image.height")] - [:div.attributes-value (-> shape :metadata :height) "px"] - [:& copy-button {:data (cg/generate-css-props shape :height)}]] + [:div.attributes-value (css/get-css-value objects (:metadata shape) :height)] + [:& copy-button {:data (css/get-css-property objects (:metadata shape) :height)}]] (let [mtype (-> shape :metadata :mtype) name (:name shape) diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/layout.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/layout.cljs index f1d4490a2c..29dd385823 100644 --- a/frontend/src/app/main/ui/viewer/inspect/attributes/layout.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/attributes/layout.cljs @@ -6,92 +6,46 @@ (ns app.main.ui.viewer.inspect.attributes.layout (:require - [app.common.types.shape.radius :as ctsr] + [app.common.data :as d] + [app.common.types.shape.layout :as ctl] [app.main.ui.components.copy-button :refer [copy-button]] - [app.main.ui.formats :as fmt] - [app.util.code-gen :as cg] - [app.util.i18n :refer [tr]] - [cuerdas.core :as str] + [app.util.code-gen.style-css :as css] [rumext.v2 :as mf])) -(def properties [:width :height :x :y :radius :rx :r1]) - -(def params - {:to-prop {:x "left" - :y "top" - :rotation "transform" - :rx "border-radius" - :r1 "border-radius"} - :format {:rotation #(str/fmt "rotate(%sdeg)" %) - :r1 #(apply str/fmt "%spx, %spx, %spx, %spx" %) - :width #(cg/get-size :width %) - :height #(cg/get-size :height %)} - :multi {:r1 [:r1 :r2 :r3 :r4]}}) - -(defn copy-data - ([shape] - (apply copy-data shape properties)) - ([shape & properties] - (cg/generate-css-props shape properties params))) +(def properties + [:display + :flex-direction + :flex-wrap + :grid-template-rows + :grid-template-columns + :align-items + :align-content + :justify-items + :justify-content + :gap + :padding]) (mf/defc layout-block - [{:keys [shape]}] - (let [selrect (:selrect shape) - {:keys [x y width height]} selrect] - [:* - [:div.attributes-unit-row - [:div.attributes-label (tr "inspect.attributes.layout.width")] - [:div.attributes-value (fmt/format-size :width width shape)] - [:& copy-button {:data (copy-data selrect :width)}]] - - [:div.attributes-unit-row - [:div.attributes-label (tr "inspect.attributes.layout.height")] - [:div.attributes-value (fmt/format-size :height height shape)] - [:& copy-button {:data (copy-data selrect :height)}]] - - (when (not= (:x shape) 0) + [{:keys [objects shape]}] + [:* + (for [property properties] + (when-let [value (css/get-css-value objects shape property)] [:div.attributes-unit-row - [:div.attributes-label (tr "inspect.attributes.layout.left")] - [:div.attributes-value (fmt/format-pixels x)] - [:& copy-button {:data (copy-data selrect :x)}]]) - - (when (not= (:y shape) 0) - [:div.attributes-unit-row - [:div.attributes-label (tr "inspect.attributes.layout.top")] - [:div.attributes-value (fmt/format-pixels y)] - [:& copy-button {:data (copy-data selrect :y)}]]) - - (when (ctsr/radius-1? shape) - [:div.attributes-unit-row - [:div.attributes-label (tr "inspect.attributes.layout.radius")] - [:div.attributes-value (fmt/format-pixels (:rx shape 0))] - [:& copy-button {:data (copy-data shape :rx)}]]) - - (when (ctsr/radius-4? shape) - [:div.attributes-unit-row - [:div.attributes-label (tr "inspect.attributes.layout.radius")] - [:div.attributes-value - (fmt/format-number (:r1 shape)) ", " - (fmt/format-number (:r2 shape)) ", " - (fmt/format-number (:r3 shape)) ", " - (fmt/format-pixels (:r4 shape))] - [:& copy-button {:data (copy-data shape :r1)}]]) - - (when (not= (:rotation shape 0) 0) - [:div.attributes-unit-row - [:div.attributes-label (tr "inspect.attributes.layout.rotation")] - [:div.attributes-value (fmt/format-number (:rotation shape)) "deg"] - [:& copy-button {:data (copy-data shape :rotation)}]])])) - + [:div.attributes-label (d/name property)] + [:div.attributes-value value] + [:& copy-button {:data (css/get-css-property objects shape property)}]]))]) (mf/defc layout-panel - [{:keys [shapes]}] - [:div.attributes-block - [:div.attributes-block-title - [:div.attributes-block-title-text (tr "inspect.attributes.size")] - (when (= (count shapes) 1) - [:& copy-button {:data (copy-data (first shapes))}])] + [{:keys [objects shapes]}] + (let [shapes (->> shapes (filter ctl/any-layout?))] + (when (seq shapes) + [:div.attributes-block + [:div.attributes-block-title + [:div.attributes-block-title-text "Layout"] + (when (= (count shapes) 1) + [:& copy-button {:data (css/get-shape-properties-css objects (first shapes) properties)}])] - (for [shape shapes] - [:& layout-block {:shape shape - :key (:id shape)}])]) + (for [shape shapes] + [:& layout-block {:shape shape + :objects objects + :key (:id shape)}])]))) diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/layout_element.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/layout_element.cljs new file mode 100644 index 0000000000..f59e856561 --- /dev/null +++ b/frontend/src/app/main/ui/viewer/inspect/attributes/layout_element.cljs @@ -0,0 +1,60 @@ +;; 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.main.ui.viewer.inspect.attributes.layout-element + (:require + [app.common.data :as d] + [app.common.types.shape.layout :as ctl] + [app.main.ui.components.copy-button :refer [copy-button]] + [app.util.code-gen.style-css :as css] + [rumext.v2 :as mf])) + +(def properties + [:margin + :max-height + :min-height + :max-width + :min-width + :align-self + :justify-self + + ;; Grid cell properties + :grid-column + :grid-row]) + +(mf/defc layout-element-block + [{:keys [objects shape]}] + [:* + (for [property properties] + (when-let [value (css/get-css-value objects shape property)] + [:div.attributes-unit-row + [:div.attributes-label (d/name property)] + [:div.attributes-value value] + [:& copy-button {:data (css/get-css-property objects shape property)}]]))]) + +(mf/defc layout-element-panel + [{:keys [objects shapes]}] + (let [shapes (->> shapes (filter #(ctl/any-layout-immediate-child? objects %))) + only-flex? (every? #(ctl/flex-layout-immediate-child? objects %) shapes) + only-grid? (every? #(ctl/grid-layout-immediate-child? objects %) shapes)] + (when (seq shapes) + [:div.attributes-block + [:div.attributes-block-title + [:div.attributes-block-title-text (cond + only-flex? + "Flex element" + only-grid? + "Flex element" + :else + "Layout element" + )] + (when (= (count shapes) 1) + [:& copy-button {:data (css/get-shape-properties-css objects (first shapes) properties)}])] + + (for [shape shapes] + [:& layout-element-block {:shape shape + :objects objects + :key (:id shape)}])]))) diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/layout_flex.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/layout_flex.cljs deleted file mode 100644 index 4e4430f3cd..0000000000 --- a/frontend/src/app/main/ui/viewer/inspect/attributes/layout_flex.cljs +++ /dev/null @@ -1,139 +0,0 @@ -;; 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.main.ui.viewer.inspect.attributes.layout-flex - (:require - [app.common.data :as d] - [app.main.ui.components.copy-button :refer [copy-button]] - [app.main.ui.formats :as fm] - [app.util.code-gen :as cg] - [cuerdas.core :as str] - [rumext.v2 :as mf])) - -(def properties [:layout - :layout-flex-dir - :layout-align-items - :layout-justify-content - :layout-gap - :layout-padding - :layout-wrap-type]) - -(def align-contet-prop [:layout-align-content]) - -(def layout-flex-params - {:props [:layout - :layout-align-items - :layout-flex-dir - :layout-justify-content - :layout-gap - :layout-padding - :layout-wrap-type] - :to-prop {:layout "display" - :layout-flex-dir "flex-direction" - :layout-align-items "align-items" - :layout-justify-content "justify-content" - :layout-wrap-type "flex-wrap" - :layout-gap "gap" - :layout-padding "padding"} - :format {:layout d/name - :layout-flex-dir d/name - :layout-align-items d/name - :layout-justify-content d/name - :layout-wrap-type d/name - :layout-gap fm/format-gap - :layout-padding fm/format-padding}}) - -(def layout-align-content-params - {:props [:layout-align-content] - :to-prop {:layout-align-content "align-content"} - :format {:layout-align-content d/name}}) - -(defn copy-data - ([shape] - (let [properties-for-copy (if (:layout-align-content shape) - (into [] (concat properties align-contet-prop)) - properties)] - (apply copy-data shape properties-for-copy))) - - ([shape & properties] - (let [params (if (:layout-align-content shape) - (d/deep-merge layout-align-content-params layout-flex-params ) - layout-flex-params)] - (cg/generate-css-props shape properties params)))) - -(mf/defc manage-padding - [{:keys [padding type]}] - (let [values (fm/format-padding-margin-shorthand (vals padding))] - [:div.attributes-value - {:title (str (str/join "px " (vals values)) "px")} - (for [[k v] values] - [:span.items {:key (str type "-" k "-" v)} v "px"])])) - -(mf/defc layout-flex-block - [{:keys [shape]}] - [:* - [:div.attributes-unit-row - [:div.attributes-label "Display"] - [:div.attributes-value "Flex"] - [:& copy-button {:data (copy-data shape)}]] - - [:div.attributes-unit-row - [:div.attributes-label "Direction"] - [:div.attributes-value (str/capital (d/name (:layout-flex-dir shape)))] - [:& copy-button {:data (copy-data shape :layout-flex-dir)}]] - - [:div.attributes-unit-row - [:div.attributes-label "Align-items"] - [:div.attributes-value (str/capital (d/name (:layout-align-items shape)))] - [:& copy-button {:data (copy-data shape :layout-align-items)}]] - - [:div.attributes-unit-row - [:div.attributes-label "Justify-content"] - [:div.attributes-value (str/capital (d/name (:layout-justify-content shape)))] - [:& copy-button {:data (copy-data shape :layout-justify-content)}]] - - [:div.attributes-unit-row - [:div.attributes-label "Flex wrap"] - [:div.attributes-value (str/capital (d/name (:layout-wrap-type shape)))] - [:& copy-button {:data (copy-data shape :layout-wrap-type)}]] - - (when (= :wrap (:layout-wrap-type shape)) - [:div.attributes-unit-row - [:div.attributes-label "Align-content"] - [:div.attributes-value (str/capital (d/name (:layout-align-content shape)))] - [:& copy-button {:data (copy-data shape :layout-align-content)}]]) - - [:div.attributes-unit-row - [:div.attributes-label "Gaps"] - (if (= (:row-gap (:layout-gap shape)) (:column-gap (:layout-gap shape))) - [:div.attributes-value - [:span (-> shape :layout-gap :row-gap fm/format-pixels)]] - [:div.attributes-value - [:span.items (-> shape :layout-gap :row-gap fm/format-pixels)] - [:span (-> shape :layout-gap :column-gap fm/format-pixels)]]) - [:& copy-button {:data (copy-data shape :layout-gap)}]] - - [:div.attributes-unit-row - [:div.attributes-label "Padding"] - [:& manage-padding {:padding (:layout-padding shape) :type "padding"}] - [:& copy-button {:data (copy-data shape :layout-padding)}]]]) - -(defn has-flex? [shape] - (= :flex (:layout shape))) - -(mf/defc layout-flex-panel - [{:keys [shapes]}] - (let [shapes (->> shapes (filter has-flex?))] - (when (seq shapes) - [:div.attributes-block - [:div.attributes-block-title - [:div.attributes-block-title-text "Layout"] - (when (= (count shapes) 1) - [:& copy-button {:data (copy-data (first shapes))}])] - - (for [shape shapes] - [:& layout-flex-block {:shape shape - :key (:id shape)}])]))) diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/layout_flex_element.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/layout_flex_element.cljs deleted file mode 100644 index 9e23c6a4c1..0000000000 --- a/frontend/src/app/main/ui/viewer/inspect/attributes/layout_flex_element.cljs +++ /dev/null @@ -1,155 +0,0 @@ -;; 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.main.ui.viewer.inspect.attributes.layout-flex-element - (:require - [app.common.data :as d] - [app.main.refs :as refs] - [app.main.ui.components.copy-button :refer [copy-button]] - [app.main.ui.formats :as fmt] - [app.main.ui.viewer.inspect.code :as cd] - [app.util.code-gen :as cg] - [cuerdas.core :as str] - [rumext.v2 :as mf])) - - -(defn format-margin - [margin-values] - (let [short-hand (fmt/format-padding-margin-shorthand (vals margin-values)) - parsed-values (map #(str/fmt "%spx" %) (vals short-hand))] - (str/join " " parsed-values))) - -(def properties [:layout-item-margin ;; {:m1 0 :m2 0 :m3 0 :m4 0} - :layout-item-max-h ;; num - :layout-item-min-h ;; num - :layout-item-max-w ;; num - :layout-item-min-w ;; num - :layout-item-align-self]) ;; :start :end :center - -(def layout-flex-item-params - {:props [:layout-item-margin - :layout-item-max-h - :layout-item-min-h - :layout-item-max-w - :layout-item-min-w - :layout-item-align-self] - :to-prop {:layout-item-margin "margin" - :layout-item-align-self "align-self" - :layout-item-max-h "max-height" - :layout-item-min-h "min-height" - :layout-item-max-w "max-width" - :layout-item-min-w "min-width"} - :format {:layout-item-margin format-margin - :layout-item-align-self d/name}}) - -(defn copy-data - ([shape] - (apply copy-data shape properties)) - - ([shape & properties] - (cg/generate-css-props shape properties layout-flex-item-params))) - -(mf/defc manage-margin - [{:keys [margin type]}] - (let [values (fmt/format-padding-margin-shorthand (vals margin))] - [:div.attributes-value - (for [[k v] values] - [:span.items {:key (str type "-" k "-" v)} v "px"])])) - -(defn manage-sizing - [value type] - (let [ref-value-h {:fill "Width 100%" - :fix "Fixed width" - :auto "Fit content"} - ref-value-v {:fill "Height 100%" - :fix "Fixed height" - :auto "Fit content"}] - (if (= :h type) - (ref-value-h value) - (ref-value-v value)))) - -(mf/defc layout-element-block - [{:keys [shape]}] - (let [old-margin (:layout-item-margin shape) - new-margin {:m1 0 :m2 0 :m3 0 :m4 0} - merged-margin (merge new-margin old-margin) - shape (assoc shape :layout-item-margin merged-margin)] - - [:* - (when (:layout-item-align-self shape) - [:div.attributes-unit-row - [:div.attributes-label "Align self"] - [:div.attributes-value (str/capital (d/name (:layout-item-align-self shape)))] - [:& copy-button {:data (copy-data shape :layout-item-align-self)}]]) - - (when (:layout-item-margin shape) - [:div.attributes-unit-row - [:div.attributes-label "Margin"] - [:& manage-margin {:margin merged-margin :type "margin"}] - [:& copy-button {:data (copy-data shape :layout-item-margin)}]]) - - (when (:layout-item-h-sizing shape) - [:div.attributes-unit-row - [:div.attributes-label "Horizontal sizing"] - [:div.attributes-value (manage-sizing (:layout-item-h-sizing shape) :h)] - [:& copy-button {:data (copy-data shape :layout-item-h-sizing)}]]) - - (when (:layout-item-v-sizing shape) - [:div.attributes-unit-row - [:div.attributes-label "Vertical sizing"] - [:div.attributes-value (manage-sizing (:layout-item-v-sizing shape) :v)] - [:& copy-button {:data (copy-data shape :layout-item-v-sizing)}]]) - - (when (= :fill (:layout-item-h-sizing shape)) - [:* - (when (some? (:layout-item-max-w shape)) - [:div.attributes-unit-row - [:div.attributes-label "Max. width"] - [:div.attributes-value (fmt/format-pixels (:layout-item-max-w shape))] - [:& copy-button {:data (copy-data shape :layout-item-max-w)}]]) - - (when (some? (:layout-item-min-w shape)) - [:div.attributes-unit-row - [:div.attributes-label "Min. width"] - [:div.attributes-value (fmt/format-pixels (:layout-item-min-w shape))] - [:& copy-button {:data (copy-data shape :layout-item-min-w)}]])]) - - (when (= :fill (:layout-item-v-sizing shape)) - [:* - (when (:layout-item-max-h shape) - [:div.attributes-unit-row - [:div.attributes-label "Max. height"] - [:div.attributes-value (fmt/format-pixels (:layout-item-max-h shape))] - [:& copy-button {:data (copy-data shape :layout-item-max-h)}]]) - - (when (:layout-item-min-h shape) - [:div.attributes-unit-row - [:div.attributes-label "Min. height"] - [:div.attributes-value (fmt/format-pixels (:layout-item-min-h shape))] - [:& copy-button {:data (copy-data shape :layout-item-min-h)}]])])])) - -(mf/defc layout-flex-element-panel - [{:keys [shapes from]}] - (let [route (mf/deref refs/route) - page-id (:page-id (:query-params route)) - mod-shapes (cd/get-flex-elements page-id shapes from) - shape (first mod-shapes) - has-margin? (some? (:layout-item-margin shape)) - has-values? (or (some? (:layout-item-max-w shape)) - (some? (:layout-item-max-h shape)) - (some? (:layout-item-min-w shape)) - (some? (:layout-item-min-h shape))) - has-align? (some? (:layout-item-align-self shape)) - has-sizing? (or (some? (:layout-item-h-sizing shape)) - (some? (:layout-item-w-sizing shape))) - must-show (or has-margin? has-values? has-align? has-sizing?)] - (when (and (= (count mod-shapes) 1) must-show) - [:div.attributes-block - [:div.attributes-block-title - [:div.attributes-block-title-text "Flex element"] - [:& copy-button {:data (copy-data shape)}]] - - [:& layout-element-block {:shape shape}]]))) diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/shadow.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/shadow.cljs index 25f04df952..ff8cf5a808 100644 --- a/frontend/src/app/main/ui/viewer/inspect/attributes/shadow.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/attributes/shadow.cljs @@ -7,30 +7,13 @@ (ns app.main.ui.viewer.inspect.attributes.shadow (:require [app.common.data :as d] - [app.main.ui.components.copy-button :refer [copy-button]] [app.main.ui.viewer.inspect.attributes.common :refer [color-row]] - [app.util.code-gen :as cg] [app.util.i18n :refer [tr]] - [cuerdas.core :as str] [rumext.v2 :as mf])) (defn has-shadow? [shape] (:shadow shape)) -(defn shape-copy-data [shape] - (cg/generate-css-props - shape - :shadow - {:to-prop "box-shadow" - :format #(str/join ", " (map cg/shadow->css (:shadow shape)))})) - -(defn shadow-copy-data [shadow] - (cg/generate-css-props - shadow - :style - {:to-prop "box-shadow" - :format #(cg/shadow->css shadow)})) - (mf/defc shadow-block [{:keys [shadow]}] (let [color-format (mf/use-state :hex)] [:div.attributes-shadow-block @@ -48,7 +31,7 @@ [:div.attributes-shadow {:title (tr "workspace.options.shadow-options.spread")} [:div.attributes-value (str (:spread shadow) "px")]] - [:& copy-button {:data (shadow-copy-data shadow)}]] + #_[:& copy-button {:data (shadow-copy-data shadow)}]] [:& color-row {:color (:color shadow) :format @color-format diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/stroke.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/stroke.cljs index 34e1f70bc5..59c08fa9c5 100644 --- a/frontend/src/app/main/ui/viewer/inspect/attributes/stroke.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/attributes/stroke.cljs @@ -7,76 +7,52 @@ (ns app.main.ui.viewer.inspect.attributes.stroke (:require [app.common.data :as d] + [app.common.data.macros :as dm] [app.main.ui.components.copy-button :refer [copy-button]] + [app.main.ui.formats :as fmt] [app.main.ui.viewer.inspect.attributes.common :refer [color-row]] - [app.util.code-gen :as cg] + [app.util.code-gen.style-css-formats :as cssf] + [app.util.code-gen.style-css-values :as cssv] [app.util.color :as uc] [app.util.i18n :refer [tr]] - [cuerdas.core :as str] [rumext.v2 :as mf])) -(defn shape->color [shape] +(defn stroke->color [shape] {:color (:stroke-color shape) :opacity (:stroke-opacity shape) :gradient (:stroke-color-gradient shape) :id (:stroke-color-ref-id shape) :file-id (:stroke-color-ref-file shape)}) -(defn format-stroke [shape] - (let [width (:stroke-width shape) - style (d/name (:stroke-style shape)) - style (if (= style "svg") "solid" style) - color (-> shape shape->color uc/color->background)] - (str/format "%spx %s %s" width style color))) - (defn has-stroke? [shape] - (let [stroke-style (:stroke-style shape)] - (or - (and stroke-style - (and (not= stroke-style :none) - (not= stroke-style :svg))) - (seq (:strokes shape))))) - -(defn copy-stroke-data [shape] - (cg/generate-css-props - shape - :stroke-style - {:to-prop "border" - :format #(format-stroke shape)})) - -(defn copy-color-data [shape] - (cg/generate-css-props - shape - :stroke-color - {:to-prop "border-color" - :format #(uc/color->background (shape->color shape))})) + (seq (:strokes shape))) (mf/defc stroke-block - [{:keys [shape]}] + [{:keys [stroke]}] (let [color-format (mf/use-state :hex) - color (shape->color shape)] + color (stroke->color stroke)] [:div.attributes-stroke-block - (let [{:keys [stroke-style stroke-alignment]} shape + (let [{:keys [stroke-style stroke-alignment]} stroke stroke-style (if (= stroke-style :svg) :solid stroke-style) stroke-alignment (or stroke-alignment :center)] [:div.attributes-stroke-row [:div.attributes-label (tr "inspect.attributes.stroke.width")] - [:div.attributes-value (:stroke-width shape) "px"] + [:div.attributes-value (fmt/format-pixels (:stroke-width stroke))] ;; Execution time translation strings: ;; inspect.attributes.stroke.style.dotted ;; inspect.attributes.stroke.style.mixed ;; inspect.attributes.stroke.style.none ;; inspect.attributes.stroke.style.solid - [:div.attributes-value (->> stroke-style d/name (str "inspect.attributes.stroke.style.") (tr))] + [:div.attributes-value (tr (dm/str "inspect.attributes.stroke.style." (d/name stroke-style)))] ;; Execution time translation strings: ;; inspect.attributes.stroke.alignment.center ;; inspect.attributes.stroke.alignment.inner ;; inspect.attributes.stroke.alignment.outer - [:div.attributes-label (->> stroke-alignment d/name (str "inspect.attributes.stroke.alignment.") (tr))] - [:& copy-button {:data (copy-stroke-data shape)}]]) + [:div.attributes-label (tr (dm/str "inspect.attributes.stroke.alignment." (d/name stroke-alignment)))] + [:& copy-button {:data (cssf/format-value :border (cssv/get-stroke-data stroke))}]]) [:& color-row {:color color :format @color-format - :copy-data (copy-color-data shape) + :copy-data (uc/color->background color) :on-change-format #(reset! color-format %)}]])) (mf/defc stroke-panel @@ -89,9 +65,6 @@ [:div.attributes-stroke-blocks (for [shape shapes] - (if (seq (:strokes shape)) - (for [value (:strokes shape [])] - [:& stroke-block {:key (str "stroke-color-" (:id shape) value) - :shape value}]) - [:& stroke-block {:key (str "stroke-color-only" (:id shape)) - :shape shape}]))]]))) + (for [value (:strokes shape)] + [:& stroke-block {:key (str "stroke-color-" (:id shape) value) + :stroke value}]))]]))) diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/text.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/text.cljs index d5c1c87b45..2721f2358a 100644 --- a/frontend/src/app/main/ui/viewer/inspect/attributes/text.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/attributes/text.cljs @@ -6,17 +6,14 @@ (ns app.main.ui.viewer.inspect.attributes.text (:require - [app.common.data :as d] [app.common.data.macros :as dm] [app.common.text :as txt] [app.main.fonts :as fonts] [app.main.store :as st] [app.main.ui.components.copy-button :refer [copy-button]] + [app.main.ui.formats :as fmt] [app.main.ui.viewer.inspect.attributes.common :refer [color-row]] - [app.util.code-gen :as cg] - [app.util.color :as uc] [app.util.i18n :refer [tr]] - [app.util.strings :as ust] [cuerdas.core :as str] [okulary.core :as l] [rumext.v2 :as mf])) @@ -33,58 +30,23 @@ (get-in state [:viewer-libraries file-id :data :typographies]))] #(l/derived get-library st/state))) -(defn format-number [number] - (-> number - d/parse-double - (ust/format-precision 2))) +(defn fill->color [{:keys [fill-color fill-opacity fill-color-gradient fill-color-ref-id fill-color-ref-file]}] + {:color fill-color + :opacity fill-opacity + :gradient fill-color-gradient + :id fill-color-ref-id + :file-id fill-color-ref-file}) -(def properties [:fill-color - :fill-color-gradient - :font-family - :font-style - :font-size - :font-weight - :line-height - :letter-spacing - :text-decoration - :text-transform]) +(mf/defc typography-block + [{:keys [text style]}] + (let [typography-library-ref + (mf/use-memo + (mf/deps (:typography-ref-file style)) + (make-typographies-library-ref (:typography-ref-file style))) -(defn shape->color [shape] - {:color (:fill-color shape) - :opacity (:fill-opacity shape) - :gradient (:fill-color-gradient shape) - :id (:fill-color-ref-id shape) - :file-id (:fill-color-ref-file shape)}) - -(def params - {:to-prop {:fill-color "color" - :fill-color-gradient "color"} - :format {:font-family #(dm/str "'" % "'") - :font-style #(dm/str % ) - :font-size #(dm/str (format-number %) "px") - :font-weight d/name - :line-height #(format-number %) - :letter-spacing #(dm/str (format-number %) "px") - :text-decoration d/name - :text-transform d/name - :fill-color #(-> %2 shape->color uc/color->background) - :fill-color-gradient #(-> %2 shape->color uc/color->background)}}) - -(defn copy-style-data - ([style] - (cg/generate-css-props style properties params)) - ([style & properties] - (cg/generate-css-props style properties params))) - -(mf/defc typography-block [{:keys [text style]}] - (let [typography-library-ref (mf/use-memo - (mf/deps (:typography-ref-file style)) - (make-typographies-library-ref (:typography-ref-file style))) typography-library (mf/deref typography-library-ref) - - file-typographies (mf/deref file-typographies-ref) - - color-format (mf/use-state :hex) + file-typographies (mf/deref file-typographies-ref) + color-format (mf/use-state :hex) typography (get (or typography-library file-typographies) (:typography-ref-id style))] @@ -98,7 +60,7 @@ :font-style (:font-style typography)}} (tr "workspace.assets.typography.text-styles")]] [:div.typography-entry-name (:name typography)] - [:& copy-button {:data (copy-style-data typography)}]] + #_[:& copy-button {:data (copy-style-data typography)}]] [:div.attributes-typography-row [:div.typography-sample @@ -106,51 +68,51 @@ :font-weight (:font-weight style) :font-style (:font-style style)}} (tr "workspace.assets.typography.text-styles")] - [:& copy-button {:data (copy-style-data style)}]]) + #_[:& copy-button {:data (copy-style-data style)}]]) (when (:fills style) (for [[idx fill] (map-indexed vector (:fills style))] [:& color-row {:key idx :format @color-format - :color (shape->color fill) - :copy-data (copy-style-data fill :fill-color :fill-color-gradient) + :color (fill->color fill) + ;;:copy-data (copy-style-data fill :fill-color :fill-color-gradient) :on-change-format #(reset! color-format %)}])) (when (:font-id style) [:div.attributes-unit-row [:div.attributes-label (tr "inspect.attributes.typography.font-family")] [:div.attributes-value (-> style :font-id fonts/get-font-data :name)] - [:& copy-button {:data (copy-style-data style :font-family)}]]) + #_[:& copy-button {:data (copy-style-data style :font-family)}]]) (when (:font-style style) [:div.attributes-unit-row [:div.attributes-label (tr "inspect.attributes.typography.font-style")] [:div.attributes-value (str (:font-style style))] - [:& copy-button {:data (copy-style-data style :font-style)}]]) + #_[:& copy-button {:data (copy-style-data style :font-style)}]]) (when (:font-size style) [:div.attributes-unit-row [:div.attributes-label (tr "inspect.attributes.typography.font-size")] - [:div.attributes-value (str (format-number (:font-size style))) "px"] - [:& copy-button {:data (copy-style-data style :font-size)}]]) + [:div.attributes-value (fmt/format-pixels (:font-size style))] + #_[:& copy-button {:data (copy-style-data style :font-size)}]]) (when (:font-weight style) [:div.attributes-unit-row [:div.attributes-label (tr "inspect.attributes.typography.font-weight")] [:div.attributes-value (str (:font-weight style))] - [:& copy-button {:data (copy-style-data style :font-weight)}]]) + #_[:& copy-button {:data (copy-style-data style :font-weight)}]]) (when (:line-height style) [:div.attributes-unit-row [:div.attributes-label (tr "inspect.attributes.typography.line-height")] - [:div.attributes-value (format-number (:line-height style))] - [:& copy-button {:data (copy-style-data style :line-height)}]]) + [:div.attributes-value (fmt/format-number (:line-height style))] + #_[:& copy-button {:data (copy-style-data style :line-height)}]]) (when (:letter-spacing style) [:div.attributes-unit-row [:div.attributes-label (tr "inspect.attributes.typography.letter-spacing")] - [:div.attributes-value (str (format-number (:letter-spacing style))) "px"] - [:& copy-button {:data (copy-style-data style :letter-spacing)}]]) + [:div.attributes-value (fmt/format-pixels (:letter-spacing style))] + #_[:& copy-button {:data (copy-style-data style :letter-spacing)}]]) (when (:text-decoration style) [:div.attributes-unit-row @@ -159,8 +121,8 @@ ;; inspect.attributes.typography.text-decoration.none ;; inspect.attributes.typography.text-decoration.strikethrough ;; inspect.attributes.typography.text-decoration.underline - [:div.attributes-value (->> style :text-decoration (str "inspect.attributes.typography.text-decoration.") (tr))] - [:& copy-button {:data (copy-style-data style :text-decoration)}]]) + [:div.attributes-value (tr (dm/str "inspect.attributes.typography.text-decoration." (:text-decoration style)))] + #_[:& copy-button {:data (copy-style-data style :text-decoration)}]]) (when (:text-transform style) [:div.attributes-unit-row @@ -170,8 +132,8 @@ ;; inspect.attributes.typography.text-transform.none ;; inspect.attributes.typography.text-transform.titlecase ;; inspect.attributes.typography.text-transform.uppercase - [:div.attributes-value (->> style :text-transform (str "inspect.attributes.typography.text-transform.") (tr))] - [:& copy-button {:data (copy-style-data style :text-transform)}]]) + [:div.attributes-value (tr (dm/str "inspect.attributes.typography.text-transform." (:text-transform style)))] + #_[:& copy-button {:data (copy-style-data style :text-transform)}]]) [:div.attributes-content-row [:pre.attributes-content (str/trim text)] @@ -179,8 +141,8 @@ (mf/defc text-block [{:keys [shape]}] - (let [style-text-blocks (->> (keys txt/default-text-attrs) - (cg/parse-style-text-blocks (:content shape)) + (let [style-text-blocks (->> (:content shape) + (txt/content->text+styles) (remove (fn [[_ text]] (str/empty? (str/trim text)))) (mapv (fn [[style text]] (vector (merge txt/default-text-attrs style) text))))] diff --git a/frontend/src/app/main/ui/viewer/inspect/code.cljs b/frontend/src/app/main/ui/viewer/inspect/code.cljs index 99e881d778..daa7fd7ad0 100644 --- a/frontend/src/app/main/ui/viewer/inspect/code.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/code.cljs @@ -133,7 +133,7 @@ style-code (mf/use-memo - (mf/deps fontfaces-css style-type all-children) + (mf/deps fontfaces-css style-type all-children cg/generate-style-code) (fn [] (dm/str fontfaces-css "\n" @@ -144,7 +144,7 @@ (mf/use-memo (mf/deps markup-type shapes images-data) (fn [] - (-> (cg/generate-markup-code objects markup-type (map :id shapes)) + (-> (cg/generate-markup-code objects markup-type shapes) (format-code markup-type)))) on-markup-copied diff --git a/frontend/src/app/main/ui/viewer/inspect/right_sidebar.cljs b/frontend/src/app/main/ui/viewer/inspect/right_sidebar.cljs index 2cceadd341..ad9509372d 100644 --- a/frontend/src/app/main/ui/viewer/inspect/right_sidebar.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/right_sidebar.cljs @@ -36,12 +36,12 @@ :data local}))))) (mf/defc right-sidebar - [{:keys [frame page file selected shapes page-id file-id share-id from on-change-section on-expand] + [{:keys [frame page objects file selected shapes page-id file-id share-id from on-change-section on-expand] :or {from :inspect}}] (let [section (mf/use-state :info #_:code) + objects (or objects (:objects page)) shapes (or shapes - (resolve-shapes (:objects page) selected)) - + (resolve-shapes objects selected)) first-shape (first shapes) page-id (or page-id (:id page)) file-id (or file-id (:id file)) @@ -98,6 +98,7 @@ :selected @section} [:& tabs-element {:id :info :title (tr "inspect.tabs.info")} [:& attributes {:page-id page-id + :objects objects :file-id file-id :frame frame :shapes shapes diff --git a/frontend/src/app/main/ui/workspace/sidebar/options.cljs b/frontend/src/app/main/ui/workspace/sidebar/options.cljs index 19d98b8ac8..37dc1c7796 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options.cljs @@ -146,6 +146,7 @@ [:div.element-options.element-options-inspect [:& hrs/right-sidebar {:page-id page-id + :objects objects :file-id file-id :frame shape-parent-frame :shapes selected-shapes 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 72dbd278de..f34634a0c1 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 @@ -22,6 +22,7 @@ [app.main.data.workspace.shape-layout :as dwsl] [app.main.refs :as refs] [app.main.store :as st] + [app.main.ui.cursors :as cur] [app.main.ui.formats :as fmt] [app.main.ui.hooks :as hooks] [app.main.ui.workspace.viewport.viewport-ref :as uwvv] @@ -172,10 +173,6 @@ direction (unchecked-get props "direction") layout-data (unchecked-get props "layout-data") - cursor - (if (= direction :row) - (cur/scale-ns (:rotation shape)) - (cur/scale-ew (:rotation shape))) handle-drag-position (mf/use-callback @@ -227,7 +224,11 @@ :y y :height height :width width - :style {:fill "transparent" :stroke-width 0 :cursor cursor} + :class (if (= direction :row) + (cur/get-dynamic "scale-ns" (:rotation shape)) + (cur/get-dynamic "scale-ew" (:rotation shape))) + :style {:fill "transparent" + :stroke-width 0} :on-pointer-down handle-pointer-down :on-lost-pointer-capture handle-lost-pointer-capture :on-pointer-move handle-pointer-move}])) @@ -464,12 +465,12 @@ :on-lost-pointer-capture handle-lost-pointer-capture :on-pointer-move handle-pointer-move :transform (dm/str (gmt/transform-in start-p (:transform shape))) + :class (if (= type :column) + (cur/get-dynamic "resize-ew" (:rotation shape)) + (cur/get-dynamic "resize-ns" (:rotation shape))) :style {:fill "transparent" :opacity 0.5 - :stroke-width 0 - :cursor (if (= type :column) - (cur/resize-ew (:rotation shape)) - (cur/resize-ns (:rotation shape)))}}])) + :stroke-width 0}}])) (mf/defc track-marker {::mf/wrap-props false} @@ -511,11 +512,10 @@ [:g {:on-pointer-down handle-pointer-down :on-lost-pointer-capture handle-lost-pointer-capture :on-pointer-move handle-pointer-move - :class (css :grid-track-marker) - :transform (dm/str (gmt/transform-in center (:transform shape))) - :style {:cursor (if (= type :column) - (cur/resize-ew (:rotation shape)) - (cur/resize-ns (:rotation shape)))}} + :class (dom/classnames (css :grid-track-marker) true + (cur/get-dynamic "resize-ew" (:rotation shape)) (= type :column) + (cur/get-dynamic "resize-ns" (:rotation shape)) (= type :row)) + :transform (dm/str (gmt/transform-in center (:transform shape)))} [:polygon {:class (css :marker-shape) :points (->> marker-points diff --git a/frontend/src/app/util/code_gen.cljs b/frontend/src/app/util/code_gen.cljs index 8dfb5d364f..e1c35d1699 100644 --- a/frontend/src/app/util/code_gen.cljs +++ b/frontend/src/app/util/code_gen.cljs @@ -6,553 +6,21 @@ (ns app.util.code-gen (:require - ["react-dom/server" :as rds] - [app.common.data :as d] - [app.common.data.macros :as dm] - [app.common.pages.helpers :as cph] - [app.common.text :as txt] - [app.common.types.shape.layout :as ctl] - [app.config :as cfg] - [app.main.render :as render] - [app.main.ui.formats :as fmt] - [app.main.ui.shapes.text.html-text :as text] - [app.util.color :as uc] - [cuerdas.core :as str] - [rumext.v2 :as mf])) - -(defn shadow->css [shadow] - (let [{:keys [style offset-x offset-y blur spread]} shadow - css-color (uc/color->background (:color shadow))] - (dm/str - (if (= style :inner-shadow) "inset " "") - (str/fmt "%spx %spx %spx %spx %s" offset-x offset-y blur spread css-color)))) - -(defn fill-color->background - [fill] - (cond - (not= (:fill-opacity fill) 1) - (uc/color->background {:color (:fill-color fill) - :opacity (:fill-opacity fill) - :gradient (:fill-color-gradient fill)}) - - :else - (str/upper (:fill-color fill)))) - -(defn format-fill-color [_ shape] - (let [fills (:fills shape) - first-fill (first fills) - colors (if (> (count fills) 1) - (map (fn [fill] - (let [color (fill-color->background fill)] - (if (some? (:fill-color-gradient fill)) - color - (str/format "linear-gradient(%s,%s)" color color)))) - (:fills shape)) - [(fill-color->background first-fill)])] - (str/join ", " colors))) - -(defn format-stroke [shape] - (let [first-stroke (first (:strokes shape)) - width (:stroke-width first-stroke) - style (let [style (:stroke-style first-stroke)] - (when (keyword? style) (d/name style))) - color {:color (:stroke-color first-stroke) - :opacity (:stroke-opacity first-stroke) - :gradient (:stroke-color-gradient first-stroke)}] - (when-not (= :none (:stroke-style first-stroke)) - (str/format "%spx %s %s" width style (uc/color->background color))))) - -(defn format-position [objects] - (fn [_ shape] - (cond - (and (ctl/any-layout-immediate-child? objects shape) - (not (ctl/layout-absolute? shape)) - (or (cph/group-shape? shape) - (cph/frame-shape? shape))) - "relative" - - (and (ctl/any-layout-immediate-child? objects shape) - (not (ctl/layout-absolute? shape))) - nil - - :else - "absolute"))) - -(defn mk-grid-coord - [objects prop span-prop] - - (fn [_ shape] - (when (ctl/grid-layout-immediate-child? objects shape) - (let [parent (get objects (:parent-id shape)) - cell (ctl/get-cell-by-shape-id parent (:id shape))] - (if (> (get cell span-prop) 1) - (dm/str (get cell prop) " / " (+ (get cell prop) (get cell span-prop))) - (get cell prop)))))) - -(defn get-size - [type values] - (let [value (cond - (number? values) values - (string? values) values - (type values) (type values) - :else (type (:selrect values)))] - - (if (= :width type) - (fmt/format-size :width value values) - (fmt/format-size :heigth value values)))) - -(defn make-format-absolute-pos - [objects shape coord] - (fn [value] - (let [parent-id (dm/get-in objects [(:id shape) :parent-id]) - parent-value (dm/get-in objects [parent-id :selrect coord])] - (when-not (or (cph/root-frame? shape) - (ctl/any-layout-immediate-child? objects shape) - (ctl/layout-absolute? shape)) - (fmt/format-pixels (- value parent-value)))))) - -(defn format-tracks - [tracks] - (str/join - " " - (->> tracks (map (fn [{:keys [type value]}] - (case type - :flex (dm/str (fmt/format-number value) "fr") - :percent (fmt/format-percent (/ value 100)) - :auto "auto" - (fmt/format-pixels value))))))) - -(defn styles-data - [objects shape] - {:position {:props [:type] - :to-prop {:type "position"} - :format {:type (format-position objects)}} - :layout {:props (if (or (empty? (:flex-items shape)) - (ctl/layout-absolute? shape)) - [:x :y :width :height :radius :rx :r1] - [:width :height :radius :rx :r1]) - :to-prop {:x "left" - :y "top" - :rotation "transform" - :rx "border-radius" - :r1 "border-radius"} - :format {:rotation #(str/fmt "rotate(%sdeg)" %) - :r1 #(apply str/fmt "%spx %spx %spx %spx" %) - :width #(get-size :width %) - :height #(get-size :height %) - :x (make-format-absolute-pos objects shape :x) - :y (make-format-absolute-pos objects shape :y)} - :multi {:r1 [:r1 :r2 :r3 :r4]}} - :fill {:props [:fills] - :to-prop {:fills (cond - (or (cph/path-shape? shape) - (cph/mask-shape? shape) - (cph/bool-shape? shape) - (cph/svg-raw-shape? shape) - (some? (:svg-attrs shape))) - nil - - (> (count (:fills shape)) 1) - "background-image" - - (and (= (count (:fills shape)) 1) - (some? (:fill-color-gradient (first (:fills shape))))) - "background" - - :else - "background-color")} - :format {:fills format-fill-color}} - :stroke {:props [:strokes] - :to-prop {:strokes "border"} - :format {:strokes (fn [_ shape] - (when-not (or (cph/path-shape? shape) - (cph/mask-shape? shape) - (cph/bool-shape? shape) - (cph/svg-raw-shape? shape) - (some? (:svg-attrs shape))) - (format-stroke shape)))}} - :shadow {:props [:shadow] - :to-prop {:shadow :box-shadow} - :format {:shadow #(str/join ", " (map shadow->css %1))}} - :blur {:props [:blur] - :to-prop {:blur "filter"} - :format {:blur #(str/fmt "blur(%spx)" (:value %))}} - - :layout-flex {:props [:layout - :layout-flex-dir - :layout-align-items - :layout-justify-items - :layout-align-content - :layout-justify-content - :layout-gap - :layout-padding - :layout-wrap-type] - :gen-props [:flex-shrink] - :to-prop {:layout "display" - :layout-flex-dir "flex-direction" - :layout-align-items "align-items" - :layout-align-content "align-content" - :layout-justify-items "justify-items" - :layout-justify-content "justify-content" - :layout-wrap-type "flex-wrap" - :layout-gap "gap" - :layout-padding "padding"} - :format {:layout d/name - :layout-flex-dir d/name - :layout-align-items d/name - :layout-align-content d/name - :layout-justify-items d/name - :layout-justify-content d/name - :layout-wrap-type d/name - :layout-gap fmt/format-gap - :layout-padding fmt/format-padding - :flex-shrink (fn [_ shape] (when (ctl/flex-layout-immediate-child? objects shape) 0))}} - - :layout-grid {:props [:layout-grid-rows - :layout-grid-columns] - :gen-props [:grid-column - :grid-row] - :to-prop {:layout-grid-rows "grid-template-rows" - :layout-grid-columns "grid-template-columns"} - :format {:layout-grid-rows format-tracks - :layout-grid-columns format-tracks - :grid-column (mk-grid-coord objects :column :column-span) - :grid-row (mk-grid-coord objects :row :row-span)}}}) - -(def style-text - {:props [:fills - :font-family - :font-style - :font-size - :font-weight - :line-height - :letter-spacing - :text-decoration - :text-transform] - :to-prop {:fills "color"} - :format {:font-family #(dm/str "'" % "'") - :font-style #(dm/str %) - :font-size #(dm/str % "px") - :font-weight #(dm/str %) - :line-height #(dm/str %) - :letter-spacing #(dm/str % "px") - :text-decoration d/name - :text-transform d/name - :fills format-fill-color}}) - -(def layout-flex-item-params - {:props [:layout-item-margin - :layout-item-max-h - :layout-item-min-h - :layout-item-max-w - :layout-item-min-w - :layout-item-align-self] - :to-prop {:layout-item-margin "margin" - :layout-item-max-h "max-height" - :layout-item-min-h "min-height" - :layout-item-max-w "max-width" - :layout-item-min-w "min-width" - :layout-item-align-self "align-self"} - :format {:layout-item-margin fmt/format-margin - :layout-item-max-h #(dm/str % "px") - :layout-item-min-h #(dm/str % "px") - :layout-item-max-w #(dm/str % "px") - :layout-item-min-w #(dm/str % "px") - :layout-item-align-self d/name}}) - -(def layout-align-content - {:props [:layout-align-content] - :to-prop {:layout-align-content "align-content"} - :format {:layout-align-content d/name}}) - -(defn get-specific-value - [values prop] - (let [result (if (get values prop) - (get values prop) - (get (:selrect values) prop)) - result (if (= :width prop) - (get-size :width values) - result) - result (if (= :height prop) - (get-size :height values) - result)] - - result)) - -(defn generate-css-props - ([values properties] - (generate-css-props values properties [] nil)) - - ([values properties gen-properties] - (generate-css-props values properties gen-properties nil)) - - ([values properties gen-properties params] - (let [{:keys [to-prop format tab-size multi] - :or {to-prop {} tab-size 0 multi {}}} params - - ;; We allow the :format and :to-prop to be a map for different properties - ;; or just a value for a single property. This code transform a single - ;; property to a uniform one - properties (if-not (coll? properties) [properties] properties) - - format (if (not (map? format)) - (into {} (map #(vector % format) properties)) - format) - - to-prop (if (not (map? to-prop)) - (into {} (map #(vector % to-prop) properties)) - to-prop) - - get-value (fn [prop] - (if-let [props (get multi prop)] - (map #(get values %) props) - (get-specific-value values prop))) - - null? (fn [value] - (if (coll? value) - (every? #(or (nil? %) (= % 0)) value) - (or (nil? value) (= value 0)))) - - default-format (fn [value] (dm/str (fmt/format-pixels value))) - - format-property - (fn [prop] - (let [css-prop (or (get to-prop prop) (d/name prop)) - format-fn (or (get format prop) default-format) - css-val (format-fn (get-value prop) values)] - (when (and css-val (not= css-val "")) - (dm/str - (str/repeat " " tab-size) - (dm/fmt "%: %;" css-prop css-val)))))] - - (->> (concat - (->> properties - (remove #(null? (get-value %)))) - gen-properties) - (keep format-property) - (str/join "\n"))))) - -(defn shape->properties [objects shape] - (let [;; This property is added in an earlier step (code.cljs), - ;; it will come with a vector of flex-items if any. - ;; If there are none it will continue as usual. - flex-items (:flex-items shape) - props (->> (styles-data objects shape) vals (mapcat :props)) - to-prop (->> (styles-data objects shape) vals (map :to-prop) (reduce merge)) - format (->> (styles-data objects shape) vals (map :format) (reduce merge)) - multi (->> (styles-data objects shape) vals (map :multi) (reduce merge)) - gen-props (->> (styles-data objects shape) vals (mapcat :gen-props)) - - props (cond-> props - (seq flex-items) (concat (:props layout-flex-item-params)) - (= :wrap (:layout-wrap-type shape)) (concat (:props layout-align-content))) - to-prop (cond-> to-prop - (seq flex-items) (merge (:to-prop layout-flex-item-params)) - (= :wrap (:layout-wrap-type shape)) (merge (:to-prop layout-align-content))) - format (cond-> format - (seq flex-items) (merge (:format layout-flex-item-params)) - (= :wrap (:layout-wrap-type shape)) (merge (:format layout-align-content)))] - (generate-css-props - shape - props - gen-props - {:to-prop to-prop - :format format - :multi multi - :tab-size 2}))) - -(defn search-text-attrs - [node attrs] - (->> (txt/node-seq node) - (map #(select-keys % attrs)) - (reduce d/merge))) - - -;; TODO: used on inspect -(defn parse-style-text-blocks - [node attrs] - (letfn - [(rec-style-text-map [acc node style] - (let [node-style (merge style (select-keys node attrs)) - head (or (-> acc first) [{} ""]) - [head-style head-text] head - - new-acc - (cond - (:children node) - (reduce #(rec-style-text-map %1 %2 node-style) acc (:children node)) - - (not= head-style node-style) - (cons [node-style (:text node "")] acc) - - :else - (cons [node-style (dm/str head-text "" (:text node))] (rest acc))) - - ;; We add an end-of-line when finish a paragraph - new-acc - (if (= (:type node) "paragraph") - (let [[hs ht] (first new-acc)] - (cons [hs (dm/str ht "\n")] (rest new-acc))) - new-acc)] - new-acc))] - - (-> (rec-style-text-map [] node {}) - reverse))) - -(defn text->properties [objects shape] - (let [flex-items (:flex-items shape) - text-shape-style (d/without-keys (styles-data objects shape) [:fill :stroke]) - - shape-props (->> text-shape-style vals (mapcat :props)) - shape-to-prop (->> text-shape-style vals (map :to-prop) (reduce merge)) - shape-format (->> text-shape-style vals (map :format) (reduce merge)) - - shape-props (cond-> shape-props - (seq flex-items) (concat (:props layout-flex-item-params))) - shape-to-prop (cond-> shape-to-prop - (seq flex-items) (merge (:to-prop layout-flex-item-params))) - shape-format (cond-> shape-format - (seq flex-items) (merge (:format layout-flex-item-params))) - - text-values (->> (search-text-attrs - (:content shape) - (conj (:props style-text) :fill-color-gradient :fill-opacity)) - (d/merge txt/default-text-attrs))] - - (str/join - "\n" - [(generate-css-props shape - shape-props - {:to-prop shape-to-prop - :format shape-format - :tab-size 2}) - (generate-css-props text-values - (:props style-text) - {:to-prop (:to-prop style-text) - :format (:format style-text) - :tab-size 2})]))) - -(defn selector-name [shape] - (let [name (-> (:name shape) - (subs 0 (min 10 (count (:name shape))))) - ;; selectors cannot start with numbers - name (if (re-matches #"^\d.*" name) (dm/str "c-" name) name) - id (-> (dm/str (:id shape)) - #_(subs 24 36)) - selector (str/css-selector (dm/str name " " id)) - selector (if (str/starts-with? selector "-") (subs selector 1) selector)] - selector)) - -(defn generate-css [objects shape] - (let [name (:name shape) - properties (shape->properties objects shape) - selector (selector-name shape)] - (str/join "\n" [(str/fmt "/* %s */" name) - (str/fmt ".%s {" selector) - properties - "}"]))) - -(defn generate-svg - [objects shape-id] - (let [shape (get objects shape-id)] - (rds/renderToStaticMarkup - (mf/element - render/object-svg - #js {:objects objects - :object-id (-> shape :id)})))) - -(defn generate-html - ([objects shape-id] - (generate-html objects shape-id 0)) - - ([objects shape-id level] - (let [shape (get objects shape-id) - indent (str/repeat " " level) - maybe-reverse (if (ctl/any-layout? shape) reverse identity)] - (cond - (cph/text-shape? shape) - (let [text-shape-html (rds/renderToStaticMarkup (mf/element text/text-shape #js {:shape shape :code? true}))] - (dm/fmt "%
\n%\n%
" - indent - (selector-name shape) - text-shape-html - indent)) - - (cph/image-shape? shape) - (let [data (or (:metadata shape) (:fill-image shape)) - image-url (cfg/resolve-file-media data)] - (dm/fmt "%\n%" - indent - image-url - (selector-name shape) - indent)) - - (or (cph/path-shape? shape) - (cph/mask-shape? shape) - (cph/bool-shape? shape) - (cph/svg-raw-shape? shape) - (some? (:svg-attrs shape))) - (let [svg-markup (rds/renderToStaticMarkup (mf/element render/object-svg #js {:objects objects :object-id (:id shape) :render-embed? false}))] - (dm/fmt "%
\n%\n%
" - indent - (selector-name shape) - svg-markup - indent)) - - (empty? (:shapes shape)) - (dm/fmt "%
\n%
" - indent - (selector-name shape) - indent) - - :else - (dm/fmt "%
\n%\n%
" - indent - (selector-name shape) - (->> (:shapes shape) - (maybe-reverse) - (map #(generate-html objects % (inc level))) - (str/join "\n")) - indent))))) - -(defn generate-markup-code [objects type shapes] - (let [generate-markup-fn (case type - "html" generate-html - "svg" generate-svg)] - (->> shapes - (map #(generate-markup-fn objects % 0)) - (str/join "\n")))) - -(defn generate-style-code [objects type shapes] - (let [generate-style-fn (case type - "css" generate-css)] - (dm/str - "html, body { - background-color: #E8E9EA; - height: 100%; - margin: 0; - padding: 0; - width: 100%; -} - -body { - display: flex; - flex-direction: column; - align-items: center; - padding: 2rem; -} - -svg { - position: absolute; - left: 50%; - top: 50%; - transform: translate(-50%, -50%); -} - -* { - box-sizing: border-box; -} -\n" - (->> shapes - (map (partial generate-style-fn objects)) - (str/join "\n\n"))))) + [app.util.code-gen.markup-html :as html] + [app.util.code-gen.markup-svg :as svg] + [app.util.code-gen.style-css :as css])) + +(defn generate-markup-code + [objects type shapes] + (let [generate-markup + (case type + "html" html/generate-markup + "svg" svg/generate-markup)] + (generate-markup objects shapes))) + +(defn generate-style-code + [objects type shapes] + (let [generate-style + (case type + "css" css/generate-style)] + (generate-style objects shapes))) diff --git a/frontend/src/app/util/code_gen/common.cljs b/frontend/src/app/util/code_gen/common.cljs new file mode 100644 index 0000000000..07eb37ce21 --- /dev/null +++ b/frontend/src/app/util/code_gen/common.cljs @@ -0,0 +1,22 @@ +;; 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.util.code-gen.common + (:require + [app.common.data.macros :as dm] + [cuerdas.core :as str])) + +(defn shape->selector + [shape] + (let [name (-> (:name shape) + (subs 0 (min 10 (count (:name shape))))) + ;; selectors cannot start with numbers + name (if (re-matches #"^\d.*" name) (dm/str "c-" name) name) + id (-> (dm/str (:id shape)) + #_(subs 24 36)) + selector (str/css-selector (dm/str name " " id)) + selector (if (str/starts-with? selector "-") (subs selector 1) selector)] + selector)) diff --git a/frontend/src/app/util/code_gen/markup_html.cljs b/frontend/src/app/util/code_gen/markup_html.cljs new file mode 100644 index 0000000000..bdbdb5f218 --- /dev/null +++ b/frontend/src/app/util/code_gen/markup_html.cljs @@ -0,0 +1,78 @@ +;; 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.util.code-gen.markup-html + (:require + ["react-dom/server" :as rds] + [app.common.data.macros :as dm] + [app.common.pages.helpers :as cph] + [app.common.types.shape.layout :as ctl] + [app.config :as cfg] + [app.main.ui.shapes.text.html-text :as text] + [app.util.code-gen.common :as cgc] + [app.util.code-gen.markup-svg :refer [generate-svg]] + [cuerdas.core :as str] + [rumext.v2 :as mf])) + +(defn generate-html + ([objects shape] + (generate-html objects shape 0)) + + ([objects shape level] + (let [indent (str/repeat " " level) + maybe-reverse (if (ctl/any-layout? shape) reverse identity)] + + (cond + (cph/text-shape? shape) + (let [text-shape-html (rds/renderToStaticMarkup (mf/element text/text-shape #js {:shape shape :code? true}))] + (dm/fmt "%
\n%\n%
" + indent + (cgc/shape->selector shape) + text-shape-html + indent)) + + (cph/image-shape? shape) + (let [data (or (:metadata shape) (:fill-image shape)) + image-url (cfg/resolve-file-media data)] + (dm/fmt "%\n%" + indent + image-url + (cgc/shape->selector shape) + indent)) + + (or (cph/path-shape? shape) + (cph/mask-shape? shape) + (cph/bool-shape? shape) + (cph/svg-raw-shape? shape) + (some? (:svg-attrs shape))) + (let [svg-markup (generate-svg objects shape)] + (dm/fmt "%
\n%\n%
" + indent + (cgc/shape->selector shape) + svg-markup + indent)) + + (empty? (:shapes shape)) + (dm/fmt "%
\n%
" + indent + (cgc/shape->selector shape) + indent) + + :else + (dm/fmt "%
\n%\n%
" + indent + (cgc/shape->selector shape) + (->> (:shapes shape) + (maybe-reverse) + (map #(generate-html objects (get objects %) (inc level))) + (str/join "\n")) + indent))))) + +(defn generate-markup + [objects shapes] + (->> shapes + (map #(generate-html objects %)) + (str/join "\n"))) diff --git a/frontend/src/app/util/code_gen/markup_svg.cljs b/frontend/src/app/util/code_gen/markup_svg.cljs new file mode 100644 index 0000000000..a25177bee2 --- /dev/null +++ b/frontend/src/app/util/code_gen/markup_svg.cljs @@ -0,0 +1,26 @@ +;; 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.util.code-gen.markup-svg + (:require + ["react-dom/server" :as rds] + [app.main.render :as render] + [cuerdas.core :as str] + [rumext.v2 :as mf])) + +(defn generate-svg + [objects shape] + (rds/renderToStaticMarkup + (mf/element + render/object-svg + #js {:objects objects + :object-id (-> shape :id)}))) + +(defn generate-markup + [objects shapes] + (->> shapes + (map #(generate-svg objects %)) + (str/join "\n"))) diff --git a/frontend/src/app/util/code_gen/style_css.cljs b/frontend/src/app/util/code_gen/style_css.cljs new file mode 100644 index 0000000000..de2c26e906 --- /dev/null +++ b/frontend/src/app/util/code_gen/style_css.cljs @@ -0,0 +1,194 @@ +;; 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.util.code-gen.style-css + (:require + [app.common.data :as d] + [app.common.data.macros :as dm] + [app.util.code-gen.common :as cgc] + [app.util.code-gen.style-css-formats :refer [format-value]] + [app.util.code-gen.style-css-values :refer [get-value]] + [cuerdas.core :as str])) + +;; +;; Common styles to display always. Will be attached as a prelude to the generated CSS +;; +(def prelude " +html, body { + background-color: #E8E9EA; + height: 100%; + margin: 0; + padding: 0; + width: 100%; +} + +body { + display: flex; + flex-direction: column; + align-items: center; + padding: 2rem; +} + +svg { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); +} + +* { + box-sizing: border-box; +} + +") + +(def shape-css-properties + [:position + :left + :top + :width + :height + :transform + :background + :background-color + :background-image + :border + :border-radius + :box-shadow + :filter + + ;; Flex/grid related properties + :display + :align-items + :align-content + :justify-items + :justify-content + :gap + :padding + + ;; Flex related properties + :flex-direction + :flex-wrap + + ;; Grid related properties + :grid-template-rows + :grid-template-columns + + ;; Flex/grid self properties + :flex-shrink + :margin + :max-height + :min-height + :max-width + :min-width + :align-self + :justify-self + + ;; Grid cell properties + :grid-column + :grid-row + ]) + +(def text-node-css-properties + [:font-family + :font-style + :font-size + :font-weight + :line-height + :letter-spacing + :text-decoration + :text-transform + :color]) + +(defn shape->css-property + [shape objects property] + (when-let [value (get-value property shape objects)] + [property value])) + +(defn shape->css-properties + "Given a shape extract the CSS properties in the format of list [property value]" + [shape objects properties] + (->> properties + (keep (fn [property] + (when-let [value (get-value property shape objects)] + [property value]))))) + + + +(defn format-css-value + ([[property value] options] + (format-css-value property value options)) + + ([property value options] + (when (some? value) + (format-value property value options)))) + +(defn format-css-property + [[property value] options] + (when (some? value) + (let [formatted-value (format-css-value property value options)] + (dm/fmt "%: %;" (d/name property) formatted-value)))) + +(defn format-css-properties + "Format a list of [property value] into a list of css properties in the format 'property: value;'" + [properties options] + (->> properties + (map #(dm/str " " (format-css-property % options))) + (str/join "\n"))) + + +(defn get-shape-properties-css + ([objects shape properties] + (get-shape-properties-css objects shape properties nil)) + + ([objects shape properties options] + (-> shape + (shape->css-properties objects properties) + (format-css-properties options)))) + +(defn get-shape-css-selector + ([objects shape] + (get-shape-css-selector shape objects nil)) + + ([shape objects options] + (let [properties (-> shape + (shape->css-properties objects shape-css-properties) + (format-css-properties options)) + selector (cgc/shape->selector shape)] + (str/join "\n" [(str/fmt "/* %s */" (:name shape)) + (str/fmt ".%s {\n%s\n}" selector properties)])))) + +(defn get-css-property + ([objects shape property] + (get-css-property objects shape property nil)) + + ([objects shape property options] + (-> shape + (shape->css-property objects property) + (format-css-property options)))) + +(defn get-css-value + ([objects shape property] + (get-css-value objects shape property nil)) + + ([objects shape property options] + (when-let [prop (shape->css-property shape objects property)] + (format-css-value prop options)))) + +(defn generate-style + ([objects shapes] + (generate-style objects shapes nil)) + ([objects shapes options] + (dm/str + prelude + (->> shapes + (map #(get-shape-css-selector % objects options)) + (str/join "\n\n"))))) + + +#_(defn extract-text-css + [node] + (cg/parse-style-text-blocks (:content shape) (keys txt/default-text-attrs))) diff --git a/frontend/src/app/util/code_gen/style_css_formats.cljs b/frontend/src/app/util/code_gen/style_css_formats.cljs new file mode 100644 index 0000000000..cee5004742 --- /dev/null +++ b/frontend/src/app/util/code_gen/style_css_formats.cljs @@ -0,0 +1,123 @@ +;; 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.util.code-gen.style-css-formats + (:require + [app.common.data :as d] + [app.common.data.macros :as dm] + [app.main.ui.formats :as fmt] + [app.util.color :as uc] + [cuerdas.core :as str])) + +(def css-formatters + {:left :position + :top :position + :width :size + :height :size + :background :color + :background-color :color + :background-image :color-array + :border :border + :border-radius :size-array + :box-shadow :shadows + :filter :blur + :gap :size-array + :padding :size-array + :grid-template-rows :tracks + :grid-template-columns :tracks + }) + +(defmulti format-value + (fn [property _value _options] (css-formatters property))) + +(defmethod format-value :position + [_ value _options] + (cond + (number? value) (fmt/format-pixels value) + :else value)) + +(defmethod format-value :size + [_ value _options] + (cond + (= value :fill) "100%" + (= value :auto) "auto" + (number? value) (fmt/format-pixels value) + :else value)) + +(defn format-color + [value _options] + (cond + (not= (:opacity value) 1) + (uc/color->background value) + + :else + (str/upper (:color value)))) + +(defmethod format-value :color + [_ value options] + (format-color value options)) + +(defmethod format-value :color-array + [_ value options] + (->> value + (map #(format-color % options)) + (str/join ", "))) + +(defmethod format-value :border + [_ {:keys [color style width]} options] + (dm/fmt "% % %" + (fmt/format-pixels width) + (d/name style) + (format-color color options))) + +(defmethod format-value :size-array + [_ value _options] + (cond + (and (coll? value) (d/not-empty? value)) + (->> value + (map fmt/format-pixels) + (str/join " ")) + + (some? value) + value)) + +(defmethod format-value :keyword + [_ value _options] + (d/name value)) + +(defmethod format-value :tracks + [_ value _options] + (->> value + (map (fn [{:keys [type value]}] + (case type + :flex (dm/str (fmt/format-number value) "fr") + :percent (fmt/format-percent (/ value 100)) + :auto "auto" + (fmt/format-pixels value)))) + (str/join " "))) + +(defn format-shadow + [{:keys [style offset-x offset-y blur spread color]} options] + (let [css-color (format-color color options)] + (dm/str + (if (= style :inner-shadow) "inset " "") + (str/fmt "%spx %spx %spx %spx %s" offset-x offset-y blur spread css-color)))) + +(defmethod format-value :shadows + [_ value options] + (->> value + (map #(format-shadow % options)) + (str/join ", " ))) + +(defmethod format-value :blur + [_ value _options] + (dm/fmt "blur(%)" (fmt/format-pixels value))) + +(defmethod format-value :default + [_ value _options] + (if (keyword? value) + (d/name value) + value)) diff --git a/frontend/src/app/util/code_gen/style_css_values.cljs b/frontend/src/app/util/code_gen/style_css_values.cljs new file mode 100644 index 0000000000..67674ca38d --- /dev/null +++ b/frontend/src/app/util/code_gen/style_css_values.cljs @@ -0,0 +1,282 @@ +;; 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.util.code-gen.style-css-values + (:require + [app.common.data.macros :as dm] + [app.common.geom.matrix :as gmt] + [app.common.pages.helpers :as cph] + [app.common.types.shape.layout :as ctl])) + +(defn fill->color + [{:keys [fill-color fill-opacity fill-color-gradient]}] + {:color fill-color + :opacity fill-opacity + :gradient fill-color-gradient}) + +(defmulti get-value + (fn [property _shape _objects] property)) + +(defmethod get-value :position + [_ shape objects] + (cond + (and (ctl/any-layout-immediate-child? objects shape) + (not (ctl/layout-absolute? shape)) + (or (cph/group-shape? shape) + (cph/frame-shape? shape))) + :relative + + (and (ctl/any-layout-immediate-child? objects shape) + (not (ctl/layout-absolute? shape))) + nil + + :else + :absolute)) + +(defn get-shape-position + [shape objects coord] + (let [shape-value (-> shape :selrect coord) + parent-value (dm/get-in objects [(:parent-id shape) :selrect coord])] + (when-not (or (cph/root-frame? shape) + (ctl/any-layout-immediate-child? objects shape) + (ctl/layout-absolute? shape)) + (- shape-value parent-value)))) + +(defmethod get-value :left + [_ shape objects] + (get-shape-position shape objects :x)) + +(defmethod get-value :top + [_ shape objects] + (get-shape-position shape objects :y)) + +(defn get-shape-size + [shape type] + (let [sizing (if (= type :width) + (:layout-item-h-sizing shape) + (:layout-item-v-sizing shape))] + (cond + (or (= sizing :fill) (= sizing :auto)) + sizing + + (some? (:selrect shape)) + (-> shape :selrect type) + + (some? (get shape type)) + (get shape type)))) + +(defmethod get-value :width + [_ shape _] + (get-shape-size shape :width)) + +(defmethod get-value :height + [_ shape _] + (get-shape-size shape :height)) + +(defmethod get-value :transform + [_ {:keys [transform] :as shape} _] + (when (and (some? transform) (not (gmt/unit? transform))) + (dm/str transform))) + +(defn background? + [shape] + (and (not (cph/path-shape? shape)) + (not (cph/mask-shape? shape)) + (not (cph/bool-shape? shape)) + (not (cph/svg-raw-shape? shape)) + (nil? (:svg-attrs shape)))) + +(defmethod get-value :background + [_ {:keys [fills] :as shape} _] + (let [single-fill? (= (count fills) 1) + ffill (first fills) + gradient? (some? (:fill-color-gradient ffill))] + (when (and (background? shape) single-fill? gradient?) + (fill->color ffill)))) + +(defmethod get-value :background-color + [_ {:keys [fills] :as shape} _] + (let [single-fill? (= (count fills) 1) + ffill (first fills) + gradient? (some? (:fill-color-gradient ffill))] + (when (and (background? shape) single-fill? (not gradient?)) + (fill->color ffill)))) + +(defmethod get-value :background-image + [_ {:keys [fills] :as shape} _] + (when (and (background? shape) (> (count fills) 1)) + (->> fills + (map fill->color)))) + +(defn get-stroke-data + [stroke] + (let [width (:stroke-width stroke) + style (:stroke-style stroke) + color {:color (:stroke-color stroke) + :opacity (:stroke-opacity stroke) + :gradient (:stroke-color-gradient stroke)}] + + (when (and (some? stroke) (not= :none (:stroke-style stroke))) + {:color color + :style style + :width width}))) + +(defmethod get-value :border + [_ shape _] + (get-stroke-data (first (:strokes shape)))) + +(defmethod get-value :border-radius + [_ {:keys [rx r1 r2 r3 r4]} _] + (cond + (some? rx) + [rx] + + (every? some? [r1 r2 r3 r4]) + [r1 r2 r3 r4])) + +(defmethod get-value :box-shadow + [_ shape _] + (:shadow shape)) + +(defmethod get-value :filter + [_ shape _] + (get-in shape [:blur :value])) + +(defmethod get-value :display + [_ shape _] + (cond + (ctl/flex-layout? shape) "flex" + (ctl/grid-layout? shape) "grid")) + +(defmethod get-value :flex-direction + [_ shape _] + (:layout-flex-dir shape)) + +(defmethod get-value :align-items + [_ shape _] + (:layout-align-items shape)) + +(defmethod get-value :align-content + [_ shape _] + (:layout-align-content shape)) + +(defmethod get-value :justify-items + [_ shape _] + (:layout-justify-items shape)) + +(defmethod get-value :justify-content + [_ shape _] + (:layout-justify-content shape)) + +(defmethod get-value :flex-wrap + [_ shape _] + (:layout-wrap-type shape)) + +(defmethod get-value :gap + [_ shape _] + (let [[g1 g2] (ctl/gaps shape)] + (when (or (not= g1 0) (not= g2 0)) + [g1 g2]))) + +(defmethod get-value :padding + [_ {:keys [layout-padding]} _] + (when (some? layout-padding) + (let [default-padding {:p1 0 :p2 0 :p3 0 :p4 0} + {:keys [p1 p2 p3 p4]} (merge default-padding layout-padding)] + (when (or (not= p1 0) (not= p2 0) (not= p3 0) (not= p4 0)) + [p1 p2 p3 p4])))) + +(defmethod get-value :grid-template-rows + [_ shape _] + (:layout-grid-rows shape)) + +(defmethod get-value :grid-template-columns + [_ shape _] + (:layout-grid-columns shape)) + +(defn get-grid-coord + [shape objects prop span-prop] + (when (ctl/grid-layout-immediate-child? objects shape) + (let [parent (get objects (:parent-id shape)) + cell (ctl/get-cell-by-shape-id parent (:id shape))] + (if (> (get cell span-prop) 1) + (dm/str (get cell prop) " / " (+ (get cell prop) (get cell span-prop))) + (get cell prop))))) + +(defmethod get-value :grid-column + [_ shape objects] + (get-grid-coord shape objects :column :column-span)) + +(defmethod get-value :grid-row + [_ shape objects] + (get-grid-coord shape objects :row :row-span)) + +(defmethod get-value :flex-shrink + [_ shape objects] + (when (and (ctl/flex-layout-immediate-child? objects shape) + (not= :fill (:layout-item-h-sizing shape)) + (not= :fill (:layout-item-v-sizing shape)) + (not= :auto (:layout-item-h-sizing shape)) + (not= :auto (:layout-item-v-sizing shape))) + 0)) + +(defmethod get-value :margin + [_ shape objects] + (cond + (ctl/flex-layout-immediate-child? objects shape) + (:layout-item-margin shape))) + +(defmethod get-value :max-height + [_ shape objects] + (cond + (ctl/flex-layout-immediate-child? objects shape) + (:layout-item-max-h shape))) + +(defmethod get-value :min-height + [_ shape objects] + (cond + (ctl/flex-layout-immediate-child? objects shape) + (:layout-item-min-h shape))) + +(defmethod get-value :max-width + [_ shape objects] + (cond + (ctl/flex-layout-immediate-child? objects shape) + (:layout-item-max-w shape))) + +(defmethod get-value :min-width + [_ shape objects] + (cond + (ctl/flex-layout-immediate-child? objects shape) + (:layout-item-min-w shape))) + +(defmethod get-value :align-self + [_ shape objects] + (cond + (ctl/flex-layout-immediate-child? objects shape) + (:layout-item-align-self shape) + + (ctl/grid-layout-immediate-child? objects shape) + (let [parent (get objects (:parent-id shape)) + cell (ctl/get-cell-by-shape-id parent (:id shape)) + align-self (:align-self cell)] + (when (not= align-self :auto) align-self)))) + +(defmethod get-value :justify-self + [_ shape objects] + (cond + (ctl/grid-layout-immediate-child? objects shape) + (let [parent (get objects (:parent-id shape)) + cell (ctl/get-cell-by-shape-id parent (:id shape)) + justify-self (:justify-self cell)] + (when (not= justify-self :auto) justify-self)))) + +(defmethod get-value :default + [property shape _] + (get shape property)) + + From 3741a6527647fad75c22ac09fe2d9b429867d2fc Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 19 Jun 2023 14:51:05 +0200 Subject: [PATCH 41/52] :sparkles: Moved text styles to css when generating code --- common/src/app/common/text.cljc | 16 ++++++ .../app/main/ui/shapes/text/html_text.cljs | 36 ++++++++---- .../src/app/main/ui/shapes/text/styles.cljs | 5 +- frontend/src/app/util/code_gen/style_css.cljs | 55 ++++++++++++++++--- 4 files changed, 90 insertions(+), 22 deletions(-) diff --git a/common/src/app/common/text.cljc b/common/src/app/common/text.cljc index abcb366315..554a7d7c04 100644 --- a/common/src/app/common/text.cljc +++ b/common/src/app/common/text.cljc @@ -286,3 +286,19 @@ (-> (rec-style-text-map [] node {}) reverse))) + +(defn index-content + "Adds a property `$id` that identifies the current node inside" + ([content] + (index-content content nil 0)) + ([node path index] + (let [cur-path (if path (dm/str path "-") (dm/str "")) + cur-path (dm/str cur-path (d/name (:type node :text)) "-" index)] + (-> node + (assoc :$id cur-path) + (update :children + (fn [children] + (->> children + (d/enumerate) + (mapv (fn [[idx node]] + (index-content node cur-path idx)))))))))) diff --git a/frontend/src/app/main/ui/shapes/text/html_text.cljs b/frontend/src/app/main/ui/shapes/text/html_text.cljs index 301b56fb66..859228157b 100644 --- a/frontend/src/app/main/ui/shapes/text/html_text.cljs +++ b/frontend/src/app/main/ui/shapes/text/html_text.cljs @@ -8,6 +8,7 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] + [app.common.text :as txt] [app.main.ui.shapes.text.styles :as sts] [app.util.object :as obj] [rumext.v2 :as mf])) @@ -18,11 +19,14 @@ (let [node (obj/get props "node") parent (obj/get props "parent") shape (obj/get props "shape") + code? (obj/get props "code?") text (:text node) - style (if (= text "") - (sts/generate-text-styles shape parent) - (sts/generate-text-styles shape node))] - [:span.text-node {:style style} + style (when-not code? + (if (= text "") + (sts/generate-text-styles shape parent) + (sts/generate-text-styles shape node))) + class (when code? (:$id node))] + [:span.text-node {:style style :class class} (if (= text "") "\u00A0" text)])) (mf/defc render-root @@ -31,19 +35,25 @@ (let [node (obj/get props "node") children (obj/get props "children") shape (obj/get props "shape") - style (sts/generate-root-styles shape node)] + code? (obj/get props "code?") + style (when-not code? (sts/generate-root-styles shape node)) + class (when code? (:$id node))] [:div.root.rich-text {:style style + :class class :xmlns "http://www.w3.org/1999/xhtml"} children])) (mf/defc render-paragraph-set {::mf/wrap-props false} [props] - (let [children (obj/get props "children") + (let [node (obj/get props "node") + children (obj/get props "children") shape (obj/get props "shape") - style (sts/generate-paragraph-set-styles shape)] - [:div.paragraph-set {:style style} children])) + code? (obj/get props "code?") + style (when-not code? (sts/generate-paragraph-set-styles shape)) + class (when code? (:$id node))] + [:div.paragraph-set {:style style :class class} children])) (mf/defc render-paragraph {::mf/wrap-props false} @@ -51,9 +61,11 @@ (let [node (obj/get props "node") shape (obj/get props "shape") children (obj/get props "children") - style (sts/generate-paragraph-styles shape node) + code? (obj/get props "code?") + style (when-not code? (sts/generate-paragraph-styles shape node)) + class (when code? (:$id node)) dir (:text-direction node "auto")] - [:p.paragraph {:style style :dir dir} children])) + [:p.paragraph {:style style :dir dir :class class} children])) ;; -- Text nodes (mf/defc render-node @@ -88,6 +100,8 @@ code? (obj/get props "code?") {:keys [id x y width height content]} shape + content (if code? (txt/index-content content) content) + style (when-not code? #js {:position "fixed" @@ -107,7 +121,7 @@ ;; `background-clip` (when (not code?) [:style ".text-node { background-clip: text; - -webkit-background-clip: text; }" ]) + -webkit-background-clip: text; }" ]) [:& render-node {:index 0 :shape shape :node content diff --git a/frontend/src/app/main/ui/shapes/text/styles.cljs b/frontend/src/app/main/ui/shapes/text/styles.cljs index 891cca1ee2..26db0e44b2 100644 --- a/frontend/src/app/main/ui/shapes/text/styles.cljs +++ b/frontend/src/app/main/ui/shapes/text/styles.cljs @@ -7,6 +7,7 @@ (ns app.main.ui.shapes.text.styles (:require [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.text :as txt] [app.common.transit :as transit] [app.main.fonts :as fonts] @@ -17,8 +18,8 @@ (defn generate-root-styles [{:keys [width height]} node] (let [valign (:vertical-align node "top") - base #js {:height height - :width width + base #js {:height (dm/str height "px") + :width (dm/str width "px") :fontFamily "sourcesanspro" :display "flex" :whiteSpace "break-spaces"}] diff --git a/frontend/src/app/util/code_gen/style_css.cljs b/frontend/src/app/util/code_gen/style_css.cljs index de2c26e906..e228500d45 100644 --- a/frontend/src/app/util/code_gen/style_css.cljs +++ b/frontend/src/app/util/code_gen/style_css.cljs @@ -8,6 +8,9 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] + [app.common.pages.helpers :as cph] + [app.common.text :as txt] + [app.main.ui.shapes.text.styles :as sts] [app.util.code-gen.common :as cgc] [app.util.code-gen.style-css-formats :refer [format-value]] [app.util.code-gen.style-css-values :refer [get-value]] @@ -43,6 +46,9 @@ svg { box-sizing: border-box; } +.text-node { background-clip: text; + -webkit-background-clip: text; } + ") (def shape-css-properties @@ -116,8 +122,6 @@ svg { (when-let [value (get-value property shape objects)] [property value]))))) - - (defn format-css-value ([[property value] options] (format-css-value property value options)) @@ -139,7 +143,6 @@ svg { (map #(dm/str " " (format-css-property % options))) (str/join "\n"))) - (defn get-shape-properties-css ([objects shape properties] (get-shape-properties-css objects shape properties nil)) @@ -149,6 +152,44 @@ svg { (shape->css-properties objects properties) (format-css-properties options)))) +(defn format-js-styles + [properties _options] + (format-css-properties + (->> (.keys js/Object properties) + (remove #(str/starts-with? % "--")) + (mapv (fn [key] + [(str/kebab key) (unchecked-get properties key)]))) + nil)) + +(defn node->css + [shape shape-selector node] + (let [properties + (case (:type node) + (:root "root") + (sts/generate-root-styles shape node) + + (:paragraph-set "paragraph-set") + (sts/generate-paragraph-set-styles shape) + + (:paragraph "paragraph") + (sts/generate-paragraph-styles shape node) + + (sts/generate-text-styles shape node))] + (dm/fmt + ".% {\n%\n}" + (dm/str shape-selector " ." (:$id node)) + (format-js-styles properties nil)))) + +(defn generate-text-css + [shape] + (let [selector (cgc/shape->selector shape)] + (->> shape + :content + (txt/index-content) + (txt/node-seq) + (map #(node->css shape selector %)) + (str/join "\n")))) + (defn get-shape-css-selector ([objects shape] (get-shape-css-selector shape objects nil)) @@ -159,7 +200,8 @@ svg { (format-css-properties options)) selector (cgc/shape->selector shape)] (str/join "\n" [(str/fmt "/* %s */" (:name shape)) - (str/fmt ".%s {\n%s\n}" selector properties)])))) + (str/fmt ".%s {\n%s\n}" selector properties) + (when (cph/text-shape? shape) (generate-text-css shape))])))) (defn get-css-property ([objects shape property] @@ -187,8 +229,3 @@ svg { (->> shapes (map #(get-shape-css-selector % objects options)) (str/join "\n\n"))))) - - -#_(defn extract-text-css - [node] - (cg/parse-style-text-blocks (:content shape) (keys txt/default-text-attrs))) From 34575b94138a69bd91812679d3cc9e89eb6ff126 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 19 Jun 2023 14:51:25 +0200 Subject: [PATCH 42/52] :sparkles: Resize inspect on viewer --- .../styles/main/layouts/inspect.scss | 21 ++++++++- .../styles/main/partials/tab-container.scss | 9 ---- .../styles/main/partials/workspace.scss | 1 + frontend/src/app/main/ui/viewer/inspect.cljs | 44 ++++++++++++++++--- .../viewport/grid_layout_editor.cljs | 2 +- 5 files changed, 60 insertions(+), 17 deletions(-) diff --git a/frontend/resources/styles/main/layouts/inspect.scss b/frontend/resources/styles/main/layouts/inspect.scss index e26df3ece3..41f261ad53 100644 --- a/frontend/resources/styles/main/layouts/inspect.scss +++ b/frontend/resources/styles/main/layouts/inspect.scss @@ -104,7 +104,6 @@ $width-settings-bar: 256px; } .settings-bar { - transition: width 0.2s; width: $width-settings-bar; &.settings-bar-right, @@ -119,6 +118,10 @@ $width-settings-bar: 256px; overflow-y: auto; } } + + &.settings-bar-right, { + width: 100%; + } } .inspect-svg-wrapper { @@ -139,3 +142,19 @@ $width-settings-bar: 256px; margin: 0 auto; } } + +.sidebar-container { + display: flex; + flex-direction: column; + width: var(--width, $width-settings-bar); + height: 100%; + overflow: hidden; + + & > .resize-area { + position: absolute; + width: 8px; + height: 100%; + z-index: 10; + cursor: ew-resize; + } +} diff --git a/frontend/resources/styles/main/partials/tab-container.scss b/frontend/resources/styles/main/partials/tab-container.scss index 33c759e0a3..5406e69f4d 100644 --- a/frontend/resources/styles/main/partials/tab-container.scss +++ b/frontend/resources/styles/main/partials/tab-container.scss @@ -30,15 +30,6 @@ } } -.tab-container-content { - overflow-y: auto; - overflow-x: hidden; -} - -.inspect .tab-container-content { - overflow: hidden; -} - .tab-element, .tab-element-content { height: 100%; diff --git a/frontend/resources/styles/main/partials/workspace.scss b/frontend/resources/styles/main/partials/workspace.scss index 9f5b8104fc..f7011e1789 100644 --- a/frontend/resources/styles/main/partials/workspace.scss +++ b/frontend/resources/styles/main/partials/workspace.scss @@ -48,6 +48,7 @@ $height-palette-max: 80px; } .settings-bar.settings-bar-right { + height: 100%; width: var(--width, $width-settings-bar); grid-area: right-sidebar; diff --git a/frontend/src/app/main/ui/viewer/inspect.cljs b/frontend/src/app/main/ui/viewer/inspect.cljs index 4faa901099..aa19349c94 100644 --- a/frontend/src/app/main/ui/viewer/inspect.cljs +++ b/frontend/src/app/main/ui/viewer/inspect.cljs @@ -6,8 +6,10 @@ (ns app.main.ui.viewer.inspect (:require + [app.common.data.macros :as dm] [app.main.data.viewer :as dv] [app.main.store :as st] + [app.main.ui.hooks.resize :refer [use-resize-hook]] [app.main.ui.viewer.inspect.left-sidebar :refer [left-sidebar]] [app.main.ui.viewer.inspect.render :refer [render-frame-svg]] [app.main.ui.viewer.inspect.right-sidebar :refer [right-sidebar]] @@ -37,6 +39,11 @@ (mf/defc viewport [{:keys [local file page frame index viewer-pagination size share-id]}] (let [inspect-svg-container-ref (mf/use-ref nil) + current-section* (mf/use-state :info) + current-section (deref current-section*) + + can-be-expanded? (= current-section :code) + on-mouse-wheel (fn [event] (when (kbd/mod? event) @@ -55,7 +62,22 @@ (let [key1 (events/listen goog/global EventType.WHEEL on-mouse-wheel #js {"passive" false})] (fn [] - (events/unlistenByKey key1))))] + (events/unlistenByKey key1)))) + + {:keys [on-pointer-down on-lost-pointer-capture on-pointer-move] + set-right-size :set-size + right-size :size} + (use-resize-hook :code 256 256 768 :x true :right) + + handle-change-section + (mf/use-callback + (fn [section] + (reset! current-section* section))) + + handle-expand + (mf/use-callback + (mf/deps right-size) + #(set-right-size (if (> right-size 256) 256 768)))] (mf/use-effect on-mount) @@ -73,8 +95,18 @@ [:div.inspect-svg-container {:ref inspect-svg-container-ref} [:& render-frame-svg {:frame frame :page page :local local :size size}]]] - [:& right-sidebar {:frame frame - :selected (:selected local) - :page page - :file file - :share-id share-id}]])) + [:div.sidebar-container + {:class (when (not can-be-expanded?) "not-expand") + :style #js {"--width" (when can-be-expanded? (dm/str right-size "px"))}} + [:div.resize-area + {:on-pointer-down on-pointer-down + :on-lost-pointer-capture on-lost-pointer-capture + :on-pointer-move on-pointer-move}] + [:& right-sidebar {:frame frame + :selected (:selected local) + :page page + :file file + :on-change-section handle-change-section + :on-expand handle-expand + :share-id share-id + }]]])) 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 f34634a0c1..109a17a3fa 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 @@ -22,7 +22,7 @@ [app.main.data.workspace.shape-layout :as dwsl] [app.main.refs :as refs] [app.main.store :as st] - [app.main.ui.cursors :as cur] + [app.main.ui.css-cursors :as cur] [app.main.ui.formats :as fmt] [app.main.ui.hooks :as hooks] [app.main.ui.workspace.viewport.viewport-ref :as uwvv] From b3482c1d6aee28732a37191b9862bf9c18fe00a4 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 20 Jun 2023 15:48:08 +0200 Subject: [PATCH 43/52] :bug: Fix problem with space-between and only one track --- .../geom/shapes/grid_layout/layout_data.cljc | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) 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 8871045f6f..29335b86a8 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 @@ -475,35 +475,37 @@ column-total-size (tracks-total-size column-tracks) row-total-size (tracks-total-size row-tracks) + num-columns (count column-tracks) column-gap (case (:layout-align-content parent) auto-width? column-gap :space-evenly - (max column-gap (/ (- bound-width column-total-size) (inc (count column-tracks)))) + (max column-gap (/ (- bound-width column-total-size) (inc num-columns))) :space-around - (max column-gap (/ (- bound-width column-total-size) (count column-tracks))) + (max column-gap (/ (- bound-width column-total-size) num-columns)) :space-between - (max column-gap (/ (- bound-width column-total-size) (dec (count column-tracks)))) + (max column-gap (if (= num-columns 1) column-gap (/ (- bound-width column-total-size) (dec num-columns)))) column-gap) + num-rows (count row-tracks) row-gap (case (:layout-justify-content parent) auto-height? row-gap :space-evenly - (max row-gap (/ (- bound-height row-total-size) (inc (count row-tracks)))) + (max row-gap (/ (- bound-height row-total-size) (inc num-rows))) :space-around - (max row-gap (/ (- bound-height row-total-size) (count row-tracks))) + (max row-gap (/ (- bound-height row-total-size) num-rows)) :space-between - (max row-gap (/ (- bound-height row-total-size) (dec (count row-tracks)))) + (max row-gap (if (= num-rows 1) row-gap (/ (- bound-height row-total-size) (dec num-rows)))) row-gap) From 704421fa1f67c34a40ba251873dae29807ab3ac7 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 21 Jun 2023 09:26:41 +0200 Subject: [PATCH 44/52] :bug: Fix scroll problem --- .../resources/styles/main/partials/tab-container.scss | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/frontend/resources/styles/main/partials/tab-container.scss b/frontend/resources/styles/main/partials/tab-container.scss index 5406e69f4d..33c759e0a3 100644 --- a/frontend/resources/styles/main/partials/tab-container.scss +++ b/frontend/resources/styles/main/partials/tab-container.scss @@ -30,6 +30,15 @@ } } +.tab-container-content { + overflow-y: auto; + overflow-x: hidden; +} + +.inspect .tab-container-content { + overflow: hidden; +} + .tab-element, .tab-element-content { height: 100%; From 61573dcef533dda4e76b67a871d27f257452d8bc Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 21 Jun 2023 10:12:23 +0200 Subject: [PATCH 45/52] :bug: Fix problem with validation --- common/src/app/common/pages/changes.cljc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/app/common/pages/changes.cljc b/common/src/app/common/pages/changes.cljc index 97be5e91b1..739cf6ac09 100644 --- a/common/src/app/common/pages/changes.cljc +++ b/common/src/app/common/pages/changes.cljc @@ -101,7 +101,7 @@ [:ignore-touched {:optional true} :boolean] [:parent-id ::sm/uuid] [:shapes :any] - [:index {:optional true} :int] + [:index {:optional true} [:maybe :int]] [:after-shape {:optional true} :any]]] [:add-page From 68c85c8fa578e06761075f227f098097adc86165 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 21 Jun 2023 11:29:55 +0200 Subject: [PATCH 46/52] :sparkles: Changes to transform --- common/src/app/common/pages/helpers.cljc | 13 +++ frontend/src/app/util/code_gen/style_css.cljs | 4 + .../app/util/code_gen/style_css_formats.cljs | 16 +++- .../app/util/code_gen/style_css_values.cljs | 79 ++++++++++++++----- 4 files changed, 93 insertions(+), 19 deletions(-) diff --git a/common/src/app/common/pages/helpers.cljc b/common/src/app/common/pages/helpers.cljc index 0def65c046..2f7fdcdd2e 100644 --- a/common/src/app/common/pages/helpers.cljc +++ b/common/src/app/common/pages/helpers.cljc @@ -66,6 +66,10 @@ [{:keys [type]}] (= type :rect)) +(defn circle-shape? + [{:keys [type]}] + (= type :circle)) + (defn image-shape? [{:keys [type]}] (= type :image)) @@ -133,6 +137,15 @@ (recur (conj result parent-id) parent-id) result)))) +(defn get-parents + "Returns a vector of parents of the specified shape." + [objects shape-id] + (loop [result [] id shape-id] + (let [parent-id (dm/get-in objects [id :parent-id])] + (if (and (some? parent-id) (not= parent-id id)) + (recur (conj result (get objects parent-id)) parent-id) + result)))) + (defn get-parents-with-self [objects id] (let [lookup (d/getf objects)] diff --git a/frontend/src/app/util/code_gen/style_css.cljs b/frontend/src/app/util/code_gen/style_css.cljs index e228500d45..7ab0968687 100644 --- a/frontend/src/app/util/code_gen/style_css.cljs +++ b/frontend/src/app/util/code_gen/style_css.cljs @@ -65,6 +65,8 @@ svg { :border-radius :box-shadow :filter + :opacity + :overflow ;; Flex/grid related properties :display @@ -73,6 +75,8 @@ svg { :justify-items :justify-content :gap + :column-gap + :row-gap :padding ;; Flex related properties diff --git a/frontend/src/app/util/code_gen/style_css_formats.cljs b/frontend/src/app/util/code_gen/style_css_formats.cljs index cee5004742..e5e6c9d6b6 100644 --- a/frontend/src/app/util/code_gen/style_css_formats.cljs +++ b/frontend/src/app/util/code_gen/style_css_formats.cljs @@ -21,7 +21,7 @@ :background-color :color :background-image :color-array :border :border - :border-radius :size-array + :border-radius :string-or-size-array :box-shadow :shadows :filter :blur :gap :size-array @@ -84,6 +84,20 @@ (some? value) value)) +(defmethod format-value :string-or-size-array + [_ value _] + (cond + (string? value) + value + + (and (coll? value) (d/not-empty? value)) + (->> value + (map fmt/format-pixels) + (str/join " ")) + + (some? value) + value)) + (defmethod format-value :keyword [_ value _options] (d/name value)) 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 67674ca38d..88a274348a 100644 --- a/frontend/src/app/util/code_gen/style_css_values.cljs +++ b/frontend/src/app/util/code_gen/style_css_values.cljs @@ -9,9 +9,19 @@ (:require [app.common.data.macros :as dm] [app.common.geom.matrix :as gmt] + ;;[app.common.geom.point :as gpt] + ;;[app.common.geom.shapes.points :as gpo] [app.common.pages.helpers :as cph] [app.common.types.shape.layout :as ctl])) +(defn svg-render? + [shape] + (or (cph/path-shape? shape) + (cph/mask-shape? shape) + (cph/bool-shape? shape) + (cph/svg-raw-shape? shape) + (some? (:svg-attrs shape)))) + (defn fill->color [{:keys [fill-color fill-opacity fill-color-gradient]}] {:color fill-color @@ -46,6 +56,16 @@ (ctl/layout-absolute? shape)) (- shape-value parent-value)))) +#_(defn get-shape-position + [shape objects coord] + (when-not (or (cph/root-frame? shape) + (and (ctl/any-layout-immediate-child? objects shape) + (not (ctl/layout-absolute? shape)))) + (let [parent (get objects (:parent-id shape)) + bounds (gpo/parent-coords-bounds (:points shape) (:points parent)) + vv (gpt/to-vec (first (:points parent)) (first bounds))] + (get vv coord)))) + (defmethod get-value :left [_ shape objects] (get-shape-position shape objects :x)) @@ -78,24 +98,21 @@ (get-shape-size shape :height)) (defmethod get-value :transform - [_ {:keys [transform] :as shape} _] - (when (and (some? transform) (not (gmt/unit? transform))) - (dm/str transform))) - -(defn background? - [shape] - (and (not (cph/path-shape? shape)) - (not (cph/mask-shape? shape)) - (not (cph/bool-shape? shape)) - (not (cph/svg-raw-shape? shape)) - (nil? (:svg-attrs shape)))) + [_ {:keys [transform] :as shape} objects] + (let [transform + (->> (cph/get-parents objects (:id shape)) + (reduce (fn [mtx {:keys [transform-inverse]}] + (gmt/multiply transform-inverse mtx)) + transform))] + (when (and (some? transform) (not (gmt/unit? transform))) + (dm/str transform)))) (defmethod get-value :background [_ {:keys [fills] :as shape} _] (let [single-fill? (= (count fills) 1) ffill (first fills) gradient? (some? (:fill-color-gradient ffill))] - (when (and (background? shape) single-fill? gradient?) + (when (and (not (svg-render? shape)) single-fill? gradient?) (fill->color ffill)))) (defmethod get-value :background-color @@ -103,12 +120,12 @@ (let [single-fill? (= (count fills) 1) ffill (first fills) gradient? (some? (:fill-color-gradient ffill))] - (when (and (background? shape) single-fill? (not gradient?)) + (when (and (not (svg-render? shape)) single-fill? (not gradient?)) (fill->color ffill)))) (defmethod get-value :background-image [_ {:keys [fills] :as shape} _] - (when (and (background? shape) (> (count fills) 1)) + (when (and (not (svg-render? shape)) (> (count fills) 1)) (->> fills (map fill->color)))) @@ -127,11 +144,15 @@ (defmethod get-value :border [_ shape _] - (get-stroke-data (first (:strokes shape)))) + (when-not (svg-render? shape) + (get-stroke-data (first (:strokes shape))))) (defmethod get-value :border-radius - [_ {:keys [rx r1 r2 r3 r4]} _] + [_ {:keys [rx r1 r2 r3 r4] :as shape} _] (cond + (cph/circle-shape? shape) + "50%" + (some? rx) [rx] @@ -149,9 +170,21 @@ (defmethod get-value :display [_ shape _] (cond + (:hidden shape) "none" (ctl/flex-layout? shape) "flex" (ctl/grid-layout? shape) "grid")) +(defmethod get-value :opacity + [_ shape _] + (when (< (:opacity shape) 1) + (:opacity shape))) + +(defmethod get-value :overflow + [_ shape _] + (when (and (cph/frame-shape? shape) + (not (:show-content shape))) + "hidden")) + (defmethod get-value :flex-direction [_ shape _] (:layout-flex-dir shape)) @@ -179,8 +212,18 @@ (defmethod get-value :gap [_ shape _] (let [[g1 g2] (ctl/gaps shape)] - (when (or (not= g1 0) (not= g2 0)) - [g1 g2]))) + (when (and (= g1 g2) (or (not= g1 0) (not= g2 0))) + [g1]))) + +(defmethod get-value :row-gap + [_ shape _] + (let [[g1 _] (ctl/gaps shape)] + (when (not= g1 0) [g1]))) + +(defmethod get-value :column-gap + [_ shape _] + (let [[_ g2] (ctl/gaps shape)] + (when (not= g2 0) [g2]))) (defmethod get-value :padding [_ {:keys [layout-padding]} _] From a70d909a25af4dd91fdd0160d2fa097409837486 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 21 Jun 2023 13:17:59 +0200 Subject: [PATCH 47/52] :sparkles: Show grid layout on component thumbnails and empty grids --- common/src/app/common/pages/common.cljc | 5 + frontend/src/app/main/ui/shapes/frame.cljs | 8 +- .../main/ui/shapes/grid_layout_viewer.cljs | 99 +++++++++++++++++++ .../src/app/main/ui/workspace/viewport.cljs | 19 +++- .../viewport/grid_layout_editor.cljs | 14 ++- 5 files changed, 135 insertions(+), 10 deletions(-) create mode 100644 frontend/src/app/main/ui/shapes/grid_layout_viewer.cljs diff --git a/common/src/app/common/pages/common.cljc b/common/src/app/common/pages/common.cljc index 49e922632c..bb9fd07f43 100644 --- a/common/src/app/common/pages/common.cljc +++ b/common/src/app/common/pages/common.cljc @@ -89,11 +89,16 @@ :layout-gap :layout-container :layout-gap-type :layout-container :layout-justify-content :layout-container + :layout-justify-items :layout-container :layout-wrap-type :layout-container :layout-padding-type :layout-container :layout-padding :layout-container :layout-h-orientation :layout-container :layout-v-orientation :layout-container + :layout-grid-dir :layout-container + :layout-grid-rows :layout-container + :layout-grid-columns :layout-container + :layout-grid-cells :layout-container :layout-item-margin :layout-item :layout-item-margin-type :layout-item diff --git a/frontend/src/app/main/ui/shapes/frame.cljs b/frontend/src/app/main/ui/shapes/frame.cljs index 8a71a26ac8..8bf5aacce1 100644 --- a/frontend/src/app/main/ui/shapes/frame.cljs +++ b/frontend/src/app/main/ui/shapes/frame.cljs @@ -14,6 +14,7 @@ [app.main.ui.context :as muc] [app.main.ui.shapes.attrs :as attrs] [app.main.ui.shapes.custom-stroke :refer [shape-fills shape-strokes]] + [app.main.ui.shapes.grid-layout-viewer :refer [grid-layout-viewer]] [app.util.object :as obj] [debug :refer [debug?]] [rumext.v2 :as mf])) @@ -138,9 +139,12 @@ childs (unchecked-get props "childs") childs (cond-> childs (ctl/any-layout? shape) - (cph/sort-layout-children-z-index))] + (cph/sort-layout-children-z-index)) + is-component? (mf/use-ctx muc/is-component?)] [:> frame-container props [:g.frame-children {:opacity (:opacity shape)} (for [item childs] - [:& shape-wrapper {:key (dm/str (:id item)) :shape item}])]]))) + [:& shape-wrapper {:key (dm/str (:id item)) :shape item}])] + (when (and is-component? (empty? childs)) + [:& grid-layout-viewer {:shape shape :childs childs}])]))) diff --git a/frontend/src/app/main/ui/shapes/grid_layout_viewer.cljs b/frontend/src/app/main/ui/shapes/grid_layout_viewer.cljs new file mode 100644 index 0000000000..337a8a6096 --- /dev/null +++ b/frontend/src/app/main/ui/shapes/grid_layout_viewer.cljs @@ -0,0 +1,99 @@ +;; 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.main.ui.shapes.grid-layout-viewer + (:require + [app.common.data.macros :as dm] + [app.common.geom.matrix :as gmt] + [app.common.geom.point :as gpt] + [app.common.geom.shapes :as gsh] + [app.common.geom.shapes.grid-layout :as gsg] + [app.common.geom.shapes.points :as gpo] + [app.common.types.shape.layout :as ctl] + [rumext.v2 :as mf])) + +(mf/defc grid-cell-area-label + {::mf/wrap-props false} + [props] + + (let [cell-origin (unchecked-get props "origin") + cell-width (unchecked-get props "width") + text (unchecked-get props "text") + + area-width (* 10 (count text)) + area-height 25 + area-x (- (+ (:x cell-origin) cell-width) area-width) + area-y (:y cell-origin) + + area-text-x (+ area-x (/ area-width 2)) + area-text-y (+ area-y (/ area-height 2))] + + [:g {:pointer-events "none"} + [:rect {:x area-x + :y area-y + :width area-width + :height area-height + :style {:fill "var(--color-distance)" + :fill-opacity 0.3}}] + [:text {:x area-text-x + :y area-text-y + :style {:fill "var(--color-distance)" + :font-family "worksans" + :font-weight 600 + :font-size 14 + :alignment-baseline "central" + :text-anchor "middle"}} + text]])) + +(mf/defc grid-cell + {::mf/wrap-props false} + [props] + (let [shape (unchecked-get props "shape") + cell (unchecked-get props "cell") + layout-data (unchecked-get props "layout-data") + + cell-bounds (gsg/cell-bounds layout-data cell) + cell-origin (gpo/origin cell-bounds) + cell-width (gpo/width-points cell-bounds) + cell-height (gpo/height-points cell-bounds) + cell-center (gsh/center-points cell-bounds) + cell-origin (gpt/transform cell-origin (gmt/transform-in cell-center (:transform-inverse shape)))] + + [:g.cell + [:rect + {:transform (dm/str (gmt/transform-in cell-center (:transform shape))) + :x (:x cell-origin) + :y (:y cell-origin) + :width cell-width + :height cell-height + :style {:stroke "var(--color-distance)" + :stroke-width 1.5 + :fill "none"}}] + + (when (:area-name cell) + [:& grid-cell-area-label {:origin cell-origin + :width cell-width + :text (:area-name cell)}])])) + +(mf/defc grid-layout-viewer + {::mf/wrap-props false} + [props] + (let [shape (unchecked-get props "shape") + childs (unchecked-get props "childs") + + children + (->> childs + (remove :hidden) + (map #(vector (gpo/parent-coords-bounds (:points %) (:points shape)) %))) + + layout-data (gsg/calc-layout-data shape children (:points shape))] + + [:g.cells + (for [cell (ctl/get-cells shape {:sort? true})] + [:& grid-cell {:key (dm/str "cell-" (:id cell)) + :shape shape + :layout-data layout-data + :cell cell}])])) diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 3c65902aab..8919d25c33 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -11,6 +11,7 @@ [app.common.data.macros :as dm] [app.common.geom.shapes :as gsh] [app.common.pages.helpers :as cph] + [app.common.types.shape-tree :as ctt] [app.common.types.shape.layout :as ctl] [app.main.data.workspace.modifiers :as dwm] [app.main.refs :as refs] @@ -591,12 +592,24 @@ {:id (first selected) :zoom zoom}]) - (when (or show-grid-editor? hover-grid?) - [:g.grid-layout-editor {:clipPath "url(#clip-handlers)"} + [:g.grid-layout-editor {:clipPath "url(#clip-handlers)"} + (when (or show-grid-editor? hover-grid?) [:& grid-layout/editor {:zoom zoom :objects base-objects :modifiers modifiers :shape (or (get base-objects edition) (get base-objects @hover-top-frame-id)) - :view-only (not show-grid-editor?)}]])]]])) + :view-only (not show-grid-editor?)}]) + + (for [frame (ctt/get-frames objects)] + (when (and (ctl/grid-layout? frame) + (empty? (:shapes frame)) + (not= edition (:id frame)) + (not= @hover-top-frame-id (:id frame))) + [:& grid-layout/editor + {:zoom zoom + :objects base-objects + :modifiers modifiers + :shape frame + :view-only true}]))]]]])) 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 109a17a3fa..948e6d9282 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 @@ -684,11 +684,15 @@ hover-cells (:hover grid-edition) selected-cells (:selected grid-edition) - children (->> (:shapes shape) - (map (d/getf objects)) - (map #(gsh/transform-shape % (dm/get-in modifiers [(:id %) :modifiers]))) - (remove :hidden) - (map #(vector (gpo/parent-coords-bounds (:points %) (:points shape)) %))) + children + (mf/use-memo + (mf/deps shape modifiers) + (fn [] + (->> (:shapes shape) + (map (d/getf objects)) + (map #(gsh/transform-shape % (dm/get-in modifiers [(:id %) :modifiers]))) + (remove :hidden) + (map #(vector (gpo/parent-coords-bounds (:points %) (:points shape)) %))))) children (hooks/use-equal-memo children) From ecc3b29996bc236d39e066e8f2e9316d6658a664 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 21 Jun 2023 15:24:49 +0200 Subject: [PATCH 48/52] :sparkles: Fix problem with rotated layers --- .../app/util/code_gen/style_css_formats.cljs | 2 ++ .../app/util/code_gen/style_css_values.cljs | 23 +++++++++++++------ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/frontend/src/app/util/code_gen/style_css_formats.cljs b/frontend/src/app/util/code_gen/style_css_formats.cljs index e5e6c9d6b6..ff26757f74 100644 --- a/frontend/src/app/util/code_gen/style_css_formats.cljs +++ b/frontend/src/app/util/code_gen/style_css_formats.cljs @@ -25,6 +25,8 @@ :box-shadow :shadows :filter :blur :gap :size-array + :row-gap :size-array + :column-gap :size-array :padding :size-array :grid-template-rows :tracks :grid-template-columns :tracks 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 88a274348a..2b35577ad8 100644 --- a/frontend/src/app/util/code_gen/style_css_values.cljs +++ b/frontend/src/app/util/code_gen/style_css_values.cljs @@ -9,8 +9,7 @@ (:require [app.common.data.macros :as dm] [app.common.geom.matrix :as gmt] - ;;[app.common.geom.point :as gpt] - ;;[app.common.geom.shapes.points :as gpo] + [app.common.geom.shapes :as gsh] [app.common.pages.helpers :as cph] [app.common.types.shape.layout :as ctl])) @@ -49,11 +48,21 @@ (defn get-shape-position [shape objects coord] - (let [shape-value (-> shape :selrect coord) - parent-value (dm/get-in objects [(:parent-id shape) :selrect coord])] - (when-not (or (cph/root-frame? shape) - (ctl/any-layout-immediate-child? objects shape) - (ctl/layout-absolute? shape)) + (let [ + parent (get objects (:parent-id shape)) + parent-value (dm/get-in parent [:selrect coord]) + + [selrect _ _] + (-> (:points shape) + (gsh/transform-points (gsh/center-shape parent) (:transform-inverse parent)) + (gsh/calculate-geometry)) + + ;;shape (gsh/transform-shape) + shape-value (get selrect coord) + ] + (when (and (not (cph/root-frame? shape)) + (or (not (ctl/any-layout-immediate-child? objects shape)) + (ctl/layout-absolute? shape))) (- shape-value parent-value)))) #_(defn get-shape-position From cb502fc70de9e1852cf2431f54958e7bfd429bac Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 23 Jun 2023 12:36:36 +0200 Subject: [PATCH 49/52] :sparkles: Improved code gen --- .../src/app/main/ui/shapes/text/styles.cljs | 10 +++- .../src/app/main/ui/viewer/inspect/code.cljs | 23 ++++++--- .../src/app/util/code_gen/markup_html.cljs | 47 ++++++++++++++----- frontend/src/app/util/code_gen/style_css.cljs | 3 +- .../app/util/code_gen/style_css_values.cljs | 45 +++++++----------- 5 files changed, 80 insertions(+), 48 deletions(-) diff --git a/frontend/src/app/main/ui/shapes/text/styles.cljs b/frontend/src/app/main/ui/shapes/text/styles.cljs index 26db0e44b2..35f0126a95 100644 --- a/frontend/src/app/main/ui/shapes/text/styles.cljs +++ b/frontend/src/app/main/ui/shapes/text/styles.cljs @@ -74,16 +74,24 @@ font-size (:font-size data) fill-color (or (-> data :fills first :fill-color) (:fill-color data)) fill-opacity (or (-> data :fills first :fill-opacity) (:fill-opacity data)) + fill-gradient (or (-> data :fills first :fill-color-gradient) (:fill-color-gradient data)) [r g b a] (uc/hex->rgba fill-color fill-opacity) text-color (when (and (some? fill-color) (some? fill-opacity)) (str/format "rgba(%s, %s, %s, %s)" r g b a)) + gradient? (some? fill-gradient) + + text-color (if gradient? + (uc/color->background {:gradient fill-gradient}) + text-color) + fontsdb (deref fonts/fontsdb) base #js {:textDecoration text-decoration :textTransform text-transform - :color (if show-text? text-color "transparent") + :color (if (and show-text? (not gradient?)) text-color "transparent") + :background (when (and show-text? gradient?) text-color) :caretColor (or text-color "black") :overflowWrap "initial" :lineBreak "auto" diff --git a/frontend/src/app/main/ui/viewer/inspect/code.cljs b/frontend/src/app/main/ui/viewer/inspect/code.cljs index daa7fd7ad0..7738480630 100644 --- a/frontend/src/app/main/ui/viewer/inspect/code.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/code.cljs @@ -32,6 +32,9 @@ [potok.core :as ptk] [rumext.v2 :as mf])) +(def embed-images? false) +(def remove-localhost? true) + (def page-template " @@ -46,11 +49,13 @@ ") (defn format-code [code type] - (let [code (-> code - (str/replace "" "") - (str/replace "><" ">\n<"))] - (cond-> code - (or (= type "svg") (= type "html")) (beautify/html #js {"indent_size" 2})))) + (cond-> code + (= type "svg") + (-> (str/replace "" "") + (str/replace "><" ">\n<")) + + (or (= type "svg") (= type "html")) + (beautify/html #js {"indent_size" 2}))) (defn get-flex-elements [page-id shapes from] (let [ids (mapv :id shapes) @@ -189,7 +194,13 @@ (mf/use-callback (mf/deps style-code markup-code images-data) (fn [] - (let [markup-code (replace-map markup-code images-data) + (let [markup-code (cond-> markup-code + embed-images? (replace-map images-data)) + + style-code (cond-> style-code + remove-localhost? + (str/replace "http://localhost:3449" "")) + data (str/format page-template style-code markup-code)] (wapi/write-to-clipboard data))))] diff --git a/frontend/src/app/util/code_gen/markup_html.cljs b/frontend/src/app/util/code_gen/markup_html.cljs index bdbdb5f218..2605d31d39 100644 --- a/frontend/src/app/util/code_gen/markup_html.cljs +++ b/frontend/src/app/util/code_gen/markup_html.cljs @@ -7,6 +7,7 @@ (ns app.util.code-gen.markup-html (:require ["react-dom/server" :as rds] + [app.common.data :as d] [app.common.data.macros :as dm] [app.common.pages.helpers :as cph] [app.common.types.shape.layout :as ctl] @@ -17,6 +18,32 @@ [cuerdas.core :as str] [rumext.v2 :as mf])) +(defn svg-markup? + "Function to determine whether a shape is rendered in HTML+CSS or is rendered + through a SVG" + [shape] + (or + ;; path and path-like shapes + (cph/path-shape? shape) + (cph/bool-shape? shape) + + ;; imported SVG images + (cph/svg-raw-shape? shape) + (some? (:svg-attrs shape)) + + ;; CSS masks are not enough we need to delegate to SVG + (cph/mask-shape? shape) + + ;; Texts with shadows or strokes we render in SVG + (and (cph/text-shape? shape) + (or (d/not-empty? (:shadow shape)) + (d/not-empty? (:strokes shape)))) + + ;; When a shape has several strokes or the stroke is not a "border" + (or (> (count (:strokes shape)) 1) + (and (= (count (:strokes shape)) 1) + (not= (-> shape :strokes first :stroke-alignment) :center))))) + (defn generate-html ([objects shape] (generate-html objects shape 0)) @@ -26,6 +53,14 @@ maybe-reverse (if (ctl/any-layout? shape) reverse identity)] (cond + (svg-markup? shape) + (let [svg-markup (generate-svg objects shape)] + (dm/fmt "%
\n%\n%
" + indent + (cgc/shape->selector shape) + svg-markup + indent)) + (cph/text-shape? shape) (let [text-shape-html (rds/renderToStaticMarkup (mf/element text/text-shape #js {:shape shape :code? true}))] (dm/fmt "%
\n%\n%
" @@ -43,18 +78,6 @@ (cgc/shape->selector shape) indent)) - (or (cph/path-shape? shape) - (cph/mask-shape? shape) - (cph/bool-shape? shape) - (cph/svg-raw-shape? shape) - (some? (:svg-attrs shape))) - (let [svg-markup (generate-svg objects shape)] - (dm/fmt "%
\n%\n%
" - indent - (cgc/shape->selector shape) - svg-markup - indent)) - (empty? (:shapes shape)) (dm/fmt "%
\n%
" indent diff --git a/frontend/src/app/util/code_gen/style_css.cljs b/frontend/src/app/util/code_gen/style_css.cljs index 7ab0968687..1b9fa1cf12 100644 --- a/frontend/src/app/util/code_gen/style_css.cljs +++ b/frontend/src/app/util/code_gen/style_css.cljs @@ -46,8 +46,7 @@ svg { box-sizing: border-box; } -.text-node { background-clip: text; - -webkit-background-clip: text; } +.text-node { background-clip: text !important; -webkit-background-clip: text !important; } ") 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 2b35577ad8..1dfc6d8dbc 100644 --- a/frontend/src/app/util/code_gen/style_css_values.cljs +++ b/frontend/src/app/util/code_gen/style_css_values.cljs @@ -11,15 +11,8 @@ [app.common.geom.matrix :as gmt] [app.common.geom.shapes :as gsh] [app.common.pages.helpers :as cph] - [app.common.types.shape.layout :as ctl])) - -(defn svg-render? - [shape] - (or (cph/path-shape? shape) - (cph/mask-shape? shape) - (cph/bool-shape? shape) - (cph/svg-raw-shape? shape) - (some? (:svg-attrs shape)))) + [app.common.types.shape.layout :as ctl] + [app.util.code-gen.markup-html :refer [svg-markup?]])) (defn fill->color [{:keys [fill-color fill-opacity fill-color-gradient]}] @@ -107,21 +100,17 @@ (get-shape-size shape :height)) (defmethod get-value :transform - [_ {:keys [transform] :as shape} objects] - (let [transform - (->> (cph/get-parents objects (:id shape)) - (reduce (fn [mtx {:keys [transform-inverse]}] - (gmt/multiply transform-inverse mtx)) - transform))] - (when (and (some? transform) (not (gmt/unit? transform))) - (dm/str transform)))) + [_ shape objects] + (let [parent (get objects (:parent-id shape))] + (dm/str (gmt/multiply (:transform shape (gmt/matrix)) + (:transform-inverse parent (gmt/matrix)))))) (defmethod get-value :background [_ {:keys [fills] :as shape} _] (let [single-fill? (= (count fills) 1) ffill (first fills) gradient? (some? (:fill-color-gradient ffill))] - (when (and (not (svg-render? shape)) single-fill? gradient?) + (when (and (not (svg-markup? shape)) single-fill? gradient?) (fill->color ffill)))) (defmethod get-value :background-color @@ -129,12 +118,12 @@ (let [single-fill? (= (count fills) 1) ffill (first fills) gradient? (some? (:fill-color-gradient ffill))] - (when (and (not (svg-render? shape)) single-fill? (not gradient?)) + (when (and (not (svg-markup? shape)) single-fill? (not gradient?)) (fill->color ffill)))) (defmethod get-value :background-image [_ {:keys [fills] :as shape} _] - (when (and (not (svg-render? shape)) (> (count fills) 1)) + (when (and (not (svg-markup? shape)) (> (count fills) 1)) (->> fills (map fill->color)))) @@ -153,7 +142,7 @@ (defmethod get-value :border [_ shape _] - (when-not (svg-render? shape) + (when-not (svg-markup? shape) (get-stroke-data (first (:strokes shape))))) (defmethod get-value :border-radius @@ -170,11 +159,13 @@ (defmethod get-value :box-shadow [_ shape _] - (:shadow shape)) + (when-not (svg-markup? shape) + (:shadow shape))) (defmethod get-value :filter [_ shape _] - (get-in shape [:blur :value])) + (when-not (svg-markup? shape) + (get-in shape [:blur :value]))) (defmethod get-value :display [_ shape _] @@ -226,13 +217,13 @@ (defmethod get-value :row-gap [_ shape _] - (let [[g1 _] (ctl/gaps shape)] - (when (not= g1 0) [g1]))) + (let [[g1 g2] (ctl/gaps shape)] + (when (and (not= g1 g2) (not= g1 0)) [g1]))) (defmethod get-value :column-gap [_ shape _] - (let [[_ g2] (ctl/gaps shape)] - (when (not= g2 0) [g2]))) + (let [[g1 g2] (ctl/gaps shape)] + (when (and (not= g1 g2) (not= g2 0)) [g2]))) (defmethod get-value :padding [_ {:keys [layout-padding]} _] From 30d78554c22c013ce67826828ed5bcf18111d5b6 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 26 Jun 2023 12:51:02 +0200 Subject: [PATCH 50/52] :sparkles: Improved code generation --- .../geom/shapes/grid_layout/layout_data.cljc | 24 ++++----- common/src/app/common/pages/changes.cljc | 54 +++++++++++++++++++ .../src/app/common/pages/changes_builder.cljc | 14 ++--- frontend/src/app/main/ui/formats.cljs | 12 +++++ .../src/app/main/ui/shapes/text/styles.cljs | 6 +-- .../src/app/main/ui/viewer/inspect/code.cljs | 2 +- .../options/menus/layout_container.cljs | 12 ++--- frontend/src/app/util/code_gen/common.cljs | 5 +- .../app/util/code_gen/style_css_formats.cljs | 5 ++ .../app/util/code_gen/style_css_values.cljs | 23 ++++---- 10 files changed, 117 insertions(+), 40 deletions(-) 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 29335b86a8..4a92e0b17b 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 @@ -465,11 +465,11 @@ row-add-auto (/ free-row-space row-autos) column-tracks (cond-> column-tracks - (= :stretch (:layout-align-content parent)) + (= :stretch (:layout-justify-content parent)) (add-auto-size column-add-auto)) row-tracks (cond-> row-tracks - (= :stretch (:layout-justify-content parent)) + (= :stretch (:layout-align-content parent)) (add-auto-size row-add-auto)) column-total-size (tracks-total-size column-tracks) @@ -477,7 +477,7 @@ num-columns (count column-tracks) column-gap - (case (:layout-align-content parent) + (case (:layout-justify-content parent) auto-width? column-gap @@ -494,7 +494,7 @@ num-rows (count row-tracks) row-gap - (case (:layout-justify-content parent) + (case (:layout-align-content parent) auto-height? row-gap @@ -511,28 +511,28 @@ start-p (cond-> bound-corner - (and (not auto-width?) (= :end (:layout-align-content parent))) + (and (not auto-width?) (= :end (:layout-justify-content parent))) (gpt/add (hv (- bound-width (+ column-total-size column-total-gap)))) - (and (not auto-width?) (= :center (:layout-align-content parent))) + (and (not auto-width?) (= :center (:layout-justify-content parent))) (gpt/add (hv (/ (- bound-width (+ column-total-size column-total-gap)) 2))) - (and (not auto-height?) (= :end (:layout-justify-content parent))) + (and (not auto-height?) (= :end (:layout-align-content parent))) (gpt/add (vv (- bound-height (+ row-total-size row-total-gap)))) - (and (not auto-height?) (= :center (:layout-justify-content parent))) + (and (not auto-height?) (= :center (:layout-align-content parent))) (gpt/add (vv (/ (- bound-height (+ row-total-size row-total-gap)) 2))) - (and (not auto-width?) (= :space-around (:layout-align-content parent))) + (and (not auto-width?) (= :space-around (:layout-justify-content parent))) (gpt/add (hv (/ column-gap 2))) - (and (not auto-width?) (= :space-evenly (:layout-align-content parent))) + (and (not auto-width?) (= :space-evenly (:layout-justify-content parent))) (gpt/add (hv column-gap)) - (and (not auto-height?) (= :space-around (:layout-justify-content parent))) + (and (not auto-height?) (= :space-around (:layout-align-content parent))) (gpt/add (vv (/ row-gap 2))) - (and (not auto-height?) (= :space-evenly (:layout-justify-content parent))) + (and (not auto-height?) (= :space-evenly (:layout-align-content parent))) (gpt/add (vv row-gap))) column-tracks diff --git a/common/src/app/common/pages/changes.cljc b/common/src/app/common/pages/changes.cljc index 739cf6ac09..45854b9375 100644 --- a/common/src/app/common/pages/changes.cljc +++ b/common/src/app/common/pages/changes.cljc @@ -104,6 +104,15 @@ [:index {:optional true} [:maybe :int]] [:after-shape {:optional true} :any]]] + [:reorder-children + [:map {:title "ReorderChildrenChange"} + [:type [:= :reorder-children]] + [:page-id {:optional true} ::sm/uuid] + [:component-id {:optional true} ::sm/uuid] + [:ignore-touched {:optional true} :boolean] + [:parent-id ::sm/uuid] + [:shapes :any]]] + [:add-page [:map {:title "AddPageChange"} [:type [:= :add-page]] @@ -331,6 +340,51 @@ (d/update-in-when $ [:components component-id :objects] update-fn)) (check-modify-component $)))) +(defmethod process-change :reorder-children + [data {:keys [parent-id shapes page-id component-id]}] + (let [changed? (atom false) + + update-fn + (fn [objects] + (let [old-shapes (dm/get-in objects [parent-id :shapes]) + + id->idx + (update-vals + (->> shapes + d/enumerate + (group-by second)) + (comp first first)) + + new-shapes + (into [] (sort-by id->idx < old-shapes))] + + (reset! changed? (not= old-shapes new-shapes)) + + (cond-> objects + @changed? + (assoc-in [parent-id :shapes] new-shapes)))) + + check-modify-component + (fn [data] + (if @changed? + ;; When a shape is modified, if it belongs to a main component instance, + ;; the component needs to be marked as modified. + (let [objects (if page-id + (-> data :pages-index (get page-id) :objects) + (-> data :components (get component-id) :objects)) + shape (get objects parent-id) + component-root (ctn/get-component-shape objects shape {:allow-main? true})] + (if (and (some? component-root) (ctk/main-instance? component-root)) + (ctkl/set-component-modified data (:component-id component-root)) + data)) + data))] + + (as-> data $ + (if page-id + (d/update-in-when $ [:pages-index page-id :objects] update-fn) + (d/update-in-when $ [:components component-id :objects] update-fn)) + (check-modify-component $)))) + (defmethod process-change :del-obj [data {:keys [page-id component-id id ignore-touched]}] (if page-id diff --git a/common/src/app/common/pages/changes_builder.cljc b/common/src/app/common/pages/changes_builder.cljc index 08c2db3d83..8e2588d4e5 100644 --- a/common/src/app/common/pages/changes_builder.cljc +++ b/common/src/app/common/pages/changes_builder.cljc @@ -725,21 +725,21 @@ reorder-grid (fn [changes grid] (let [old-shapes (:shapes grid) - grid (ctl/reorder-grid-children grid) + grid (ctl/reorder-grid-children grid) + new-shapes (->> (:shapes grid) + (filterv #(contains? objects %))) redo-change - {:type :mov-objects + {:type :reorder-children :parent-id (:id grid) :page-id page-id - :shapes (:shapes grid) - :index 0} + :shapes new-shapes} undo-change - {:type :mov-objects + {:type :reorder-children :parent-id (:id grid) :page-id page-id - :shapes old-shapes - :index 0}] + :shapes old-shapes}] (-> changes (update :redo-changes conj redo-change) (update :undo-changes d/preconj undo-change) diff --git a/frontend/src/app/main/ui/formats.cljs b/frontend/src/app/main/ui/formats.cljs index 28296b183a..a11c12f84a 100644 --- a/frontend/src/app/main/ui/formats.cljs +++ b/frontend/src/app/main/ui/formats.cljs @@ -101,3 +101,15 @@ (if (= row-gap column-gap) (str/fmt "%spx" (format-number row-gap)) (str/fmt "%spx %spx" (format-number row-gap) (format-number column-gap))))) + +(defn format-matrix + ([mtx] + (format-matrix mtx 2)) + ([{:keys [a b c d e f]} precision] + (dm/fmt "matrix(%, %, %, %, %, %)" + (mth/to-fixed a precision) + (mth/to-fixed b precision) + (mth/to-fixed c precision) + (mth/to-fixed d precision) + (mth/to-fixed e precision) + (mth/to-fixed f precision)))) diff --git a/frontend/src/app/main/ui/shapes/text/styles.cljs b/frontend/src/app/main/ui/shapes/text/styles.cljs index 35f0126a95..38625b2c72 100644 --- a/frontend/src/app/main/ui/shapes/text/styles.cljs +++ b/frontend/src/app/main/ui/shapes/text/styles.cljs @@ -7,10 +7,10 @@ (ns app.main.ui.shapes.text.styles (:require [app.common.data :as d] - [app.common.data.macros :as dm] [app.common.text :as txt] [app.common.transit :as transit] [app.main.fonts :as fonts] + [app.main.ui.formats :as fmt] [app.util.color :as uc] [app.util.object :as obj] [cuerdas.core :as str])) @@ -18,8 +18,8 @@ (defn generate-root-styles [{:keys [width height]} node] (let [valign (:vertical-align node "top") - base #js {:height (dm/str height "px") - :width (dm/str width "px") + base #js {:height (fmt/format-pixels height) + :width (fmt/format-pixels width) :fontFamily "sourcesanspro" :display "flex" :whiteSpace "break-spaces"}] diff --git a/frontend/src/app/main/ui/viewer/inspect/code.cljs b/frontend/src/app/main/ui/viewer/inspect/code.cljs index 7738480630..91fdd23db8 100644 --- a/frontend/src/app/main/ui/viewer/inspect/code.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/code.cljs @@ -32,7 +32,7 @@ [potok.core :as ptk] [rumext.v2 :as mf])) -(def embed-images? false) +(def embed-images? true) (def remove-localhost? true) (def page-template 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 f75e9c2c5c..ab490b749e 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 @@ -418,8 +418,8 @@ :tooltip-bottom-left (not= align :start) :tooltip-bottom (= align :start)) :alt (if is-col? - (dm/str "justify-content: " (d/name align)) - (dm/str "align-content: " (d/name align))) + (dm/str "align-content: " (d/name align)) + (dm/str "justify-content: " (d/name align))) :on-click #(set-justify align type) :key (dm/str "justify-content" (d/name align))} (get-layout-grid-icon :justify-items align is-col?)])])) @@ -592,16 +592,16 @@ (st/emit! (dwsl/update-layout ids {:layout-justify-items value})))) ;; Justify grid - grid-justify-content-row (:layout-align-content values) - grid-justify-content-column (:layout-justify-content values) + grid-justify-content-row (:layout-justify-content values) + grid-justify-content-column (:layout-align-content values) set-justify-grid (mf/use-callback (mf/deps ids) (fn [value type] (if (= type :row) - (st/emit! (dwsl/update-layout ids {:layout-align-content value})) - (st/emit! (dwsl/update-layout ids {:layout-justify-content value})))))] + (st/emit! (dwsl/update-layout ids {:layout-justify-content value})) + (st/emit! (dwsl/update-layout ids {:layout-align-content value})))))] [:div.element-set [:div.element-set-title diff --git a/frontend/src/app/util/code_gen/common.cljs b/frontend/src/app/util/code_gen/common.cljs index 07eb37ce21..1cddbf506c 100644 --- a/frontend/src/app/util/code_gen/common.cljs +++ b/frontend/src/app/util/code_gen/common.cljs @@ -12,11 +12,12 @@ (defn shape->selector [shape] (let [name (-> (:name shape) - (subs 0 (min 10 (count (:name shape))))) + (subs 0 (min 10 (count (:name shape)))) + (str/replace #"[^a-zA-Z0-9\s\:]+" "")) ;; selectors cannot start with numbers name (if (re-matches #"^\d.*" name) (dm/str "c-" name) name) id (-> (dm/str (:id shape)) - #_(subs 24 36)) + (subs 24 36)) selector (str/css-selector (dm/str name " " id)) selector (if (str/starts-with? selector "-") (subs selector 1) selector)] selector)) diff --git a/frontend/src/app/util/code_gen/style_css_formats.cljs b/frontend/src/app/util/code_gen/style_css_formats.cljs index ff26757f74..440868be75 100644 --- a/frontend/src/app/util/code_gen/style_css_formats.cljs +++ b/frontend/src/app/util/code_gen/style_css_formats.cljs @@ -30,6 +30,7 @@ :padding :size-array :grid-template-rows :tracks :grid-template-columns :tracks + :transform :matrix }) (defmulti format-value @@ -132,6 +133,10 @@ [_ value _options] (dm/fmt "blur(%)" (fmt/format-pixels value))) +(defmethod format-value :matrix + [_ value _options] + (fmt/format-matrix value)) + (defmethod format-value :default [_ value _options] (if (keyword? value) 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 1dfc6d8dbc..0037414928 100644 --- a/frontend/src/app/util/code_gen/style_css_values.cljs +++ b/frontend/src/app/util/code_gen/style_css_values.cljs @@ -77,12 +77,13 @@ (get-shape-position shape objects :y)) (defn get-shape-size - [shape type] + [shape objects type] (let [sizing (if (= type :width) (:layout-item-h-sizing shape) (:layout-item-v-sizing shape))] (cond - (or (= sizing :fill) (= sizing :auto)) + (or (and (ctl/any-layout? shape) (= sizing :auto)) + (and (ctl/any-layout-immediate-child? shape objects) (= sizing :fill))) sizing (some? (:selrect shape)) @@ -92,18 +93,22 @@ (get shape type)))) (defmethod get-value :width - [_ shape _] - (get-shape-size shape :width)) + [_ shape objects] + (get-shape-size shape objects :width)) (defmethod get-value :height - [_ shape _] - (get-shape-size shape :height)) + [_ shape objects] + (get-shape-size shape objects :height)) (defmethod get-value :transform [_ shape objects] - (let [parent (get objects (:parent-id shape))] - (dm/str (gmt/multiply (:transform shape (gmt/matrix)) - (:transform-inverse parent (gmt/matrix)))))) + (let [parent (get objects (:parent-id shape)) + + transform + (gmt/multiply (:transform shape (gmt/matrix)) + (:transform-inverse parent (gmt/matrix)))] + (when-not (gmt/unit? transform) + transform))) (defmethod get-value :background [_ {:keys [fills] :as shape} _] From ac184a7c8fca9852266355e8f9402e4f5c101a98 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 27 Jun 2023 10:59:14 +0200 Subject: [PATCH 51/52] :sparkles: Improved codegen --- common/src/app/common/types/shape.cljc | 1 - .../src/app/util/code_gen/markup_html.cljs | 78 ++++++++++--------- frontend/src/app/util/code_gen/style_css.cljs | 5 +- .../app/util/code_gen/style_css_values.cljs | 30 +++---- 4 files changed, 59 insertions(+), 55 deletions(-) diff --git a/common/src/app/common/types/shape.cljc b/common/src/app/common/types/shape.cljc index 352a49f9aa..9c5202a779 100644 --- a/common/src/app/common/types/shape.cljc +++ b/common/src/app/common/types/shape.cljc @@ -437,4 +437,3 @@ (make-minimal-group uuid/zero geom-props (:name attrs))) (setup-shape geom-props) (merge attrs))) - diff --git a/frontend/src/app/util/code_gen/markup_html.cljs b/frontend/src/app/util/code_gen/markup_html.cljs index 2605d31d39..db7f27d4b6 100644 --- a/frontend/src/app/util/code_gen/markup_html.cljs +++ b/frontend/src/app/util/code_gen/markup_html.cljs @@ -50,49 +50,51 @@ ([objects shape level] (let [indent (str/repeat " " level) - maybe-reverse (if (ctl/any-layout? shape) reverse identity)] + maybe-reverse (if (ctl/any-layout? shape) reverse identity) - (cond - (svg-markup? shape) - (let [svg-markup (generate-svg objects shape)] - (dm/fmt "%
\n%\n%
" - indent - (cgc/shape->selector shape) - svg-markup - indent)) + shape-html + (cond + (svg-markup? shape) + (let [svg-markup (generate-svg objects shape)] + (dm/fmt "%
\n%\n%
" + indent + (cgc/shape->selector shape) + svg-markup + indent)) - (cph/text-shape? shape) - (let [text-shape-html (rds/renderToStaticMarkup (mf/element text/text-shape #js {:shape shape :code? true}))] - (dm/fmt "%
\n%\n%
" - indent - (cgc/shape->selector shape) - text-shape-html - indent)) + (cph/text-shape? shape) + (let [text-shape-html (rds/renderToStaticMarkup (mf/element text/text-shape #js {:shape shape :code? true}))] + (dm/fmt "%
\n%\n%
" + indent + (cgc/shape->selector shape) + text-shape-html + indent)) - (cph/image-shape? shape) - (let [data (or (:metadata shape) (:fill-image shape)) - image-url (cfg/resolve-file-media data)] - (dm/fmt "%\n%" - indent - image-url - (cgc/shape->selector shape) - indent)) + (cph/image-shape? shape) + (let [data (or (:metadata shape) (:fill-image shape)) + image-url (cfg/resolve-file-media data)] + (dm/fmt "%\n%" + indent + image-url + (cgc/shape->selector shape) + indent)) - (empty? (:shapes shape)) - (dm/fmt "%
\n%
" - indent - (cgc/shape->selector shape) - indent) + (empty? (:shapes shape)) + (dm/fmt "%
\n%
" + indent + (cgc/shape->selector shape) + indent) - :else - (dm/fmt "%
\n%\n%
" - indent - (cgc/shape->selector shape) - (->> (:shapes shape) - (maybe-reverse) - (map #(generate-html objects (get objects %) (inc level))) - (str/join "\n")) - indent))))) + :else + (dm/fmt "%
\n%\n%
" + indent + (cgc/shape->selector shape) + (->> (:shapes shape) + (maybe-reverse) + (map #(generate-html objects (get objects %) (inc level))) + (str/join "\n")) + indent))] + (dm/fmt "%\n%" indent (dm/str (d/name (:type shape)) ": " (:name shape)) shape-html)))) (defn generate-markup [objects shapes] diff --git a/frontend/src/app/util/code_gen/style_css.cljs b/frontend/src/app/util/code_gen/style_css.cljs index 1b9fa1cf12..18dab1a592 100644 --- a/frontend/src/app/util/code_gen/style_css.cljs +++ b/frontend/src/app/util/code_gen/style_css.cljs @@ -22,10 +22,10 @@ (def prelude " html, body { background-color: #E8E9EA; - height: 100%; margin: 0; + min-height: 100%; + min-width: 100%; padding: 0; - width: 100%; } body { @@ -33,6 +33,7 @@ body { flex-direction: column; align-items: center; padding: 2rem; + gap: 2rem; } svg { 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 0037414928..edfc9b1c4f 100644 --- a/frontend/src/app/util/code_gen/style_css_values.cljs +++ b/frontend/src/app/util/code_gen/style_css_values.cljs @@ -26,10 +26,11 @@ (defmethod get-value :position [_ shape objects] (cond - (and (ctl/any-layout-immediate-child? objects shape) - (not (ctl/layout-absolute? shape)) - (or (cph/group-shape? shape) - (cph/frame-shape? shape))) + (or (and (ctl/any-layout-immediate-child? objects shape) + (not (ctl/layout-absolute? shape)) + (or (cph/group-shape? shape) + (cph/frame-shape? shape))) + (cph/root-frame? shape)) :relative (and (ctl/any-layout-immediate-child? objects shape) @@ -83,7 +84,7 @@ (:layout-item-v-sizing shape))] (cond (or (and (ctl/any-layout? shape) (= sizing :auto)) - (and (ctl/any-layout-immediate-child? shape objects) (= sizing :fill))) + (and (ctl/any-layout-immediate-child? objects shape) (= sizing :fill))) sizing (some? (:selrect shape)) @@ -102,20 +103,21 @@ (defmethod get-value :transform [_ shape objects] - (let [parent (get objects (:parent-id shape)) + (when-not (svg-markup? shape) + (let [parent (get objects (:parent-id shape)) - transform - (gmt/multiply (:transform shape (gmt/matrix)) - (:transform-inverse parent (gmt/matrix)))] - (when-not (gmt/unit? transform) - transform))) + transform + (gmt/multiply (:transform shape (gmt/matrix)) + (:transform-inverse parent (gmt/matrix)))] + (when-not (gmt/unit? transform) + transform)))) (defmethod get-value :background [_ {:keys [fills] :as shape} _] (let [single-fill? (= (count fills) 1) ffill (first fills) gradient? (some? (:fill-color-gradient ffill))] - (when (and (not (svg-markup? shape)) single-fill? gradient?) + (when (and (not (svg-markup? shape)) (not (cph/group-shape? shape)) single-fill? gradient?) (fill->color ffill)))) (defmethod get-value :background-color @@ -123,12 +125,12 @@ (let [single-fill? (= (count fills) 1) ffill (first fills) gradient? (some? (:fill-color-gradient ffill))] - (when (and (not (svg-markup? shape)) single-fill? (not gradient?)) + (when (and (not (svg-markup? shape)) (not (cph/group-shape? shape)) single-fill? (not gradient?)) (fill->color ffill)))) (defmethod get-value :background-image [_ {:keys [fills] :as shape} _] - (when (and (not (svg-markup? shape)) (> (count fills) 1)) + (when (and (not (svg-markup? shape)) (not (cph/group-shape? shape)) (> (count fills) 1)) (->> fills (map fill->color)))) From da9fa31c27494ecf9351cb0cb000a2465b55c8cb Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 10 Jul 2023 11:27:43 +0200 Subject: [PATCH 52/52] :sparkles: Adds grid to the actibable features --- frontend/resources/styles/main/layouts/inspect.scss | 2 +- frontend/resources/styles/main/partials/inspect.scss | 1 - frontend/src/app/main/features.cljs | 2 +- .../workspace/sidebar/options/menus/layout_container.cljs | 7 ++++--- .../app/main/ui/workspace/viewport/grid_layout_editor.scss | 2 +- frontend/src/features.cljs | 3 +++ 6 files changed, 10 insertions(+), 7 deletions(-) diff --git a/frontend/resources/styles/main/layouts/inspect.scss b/frontend/resources/styles/main/layouts/inspect.scss index 41f261ad53..b0ad47bd20 100644 --- a/frontend/resources/styles/main/layouts/inspect.scss +++ b/frontend/resources/styles/main/layouts/inspect.scss @@ -119,7 +119,7 @@ $width-settings-bar: 256px; } } - &.settings-bar-right, { + &.settings-bar-right { width: 100%; } } diff --git a/frontend/resources/styles/main/partials/inspect.scss b/frontend/resources/styles/main/partials/inspect.scss index 9b2a29ea72..df0e22357a 100644 --- a/frontend/resources/styles/main/partials/inspect.scss +++ b/frontend/resources/styles/main/partials/inspect.scss @@ -421,7 +421,6 @@ visibility: visible; } } - } .code-row-display { diff --git a/frontend/src/app/main/features.cljs b/frontend/src/app/main/features.cljs index 4b0735c79e..a922dac971 100644 --- a/frontend/src/app/main/features.cljs +++ b/frontend/src/app/main/features.cljs @@ -20,7 +20,7 @@ (log/set-level! :warn) (def available-features - #{:auto-layout :components-v2 :new-css-system}) + #{:components-v2 :new-css-system :grid-layout}) (defn- toggle-feature [feature] 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 ab490b749e..2d030cdfff 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 @@ -12,6 +12,7 @@ [app.common.types.shape.layout :as ctl] [app.main.data.workspace :as udw] [app.main.data.workspace.shape-layout :as dwsl] + [app.main.features :as features] [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.numeric-input :refer [numeric-input]] @@ -602,20 +603,20 @@ (if (= type :row) (st/emit! (dwsl/update-layout ids {:layout-justify-content value})) (st/emit! (dwsl/update-layout ids {:layout-align-content value})))))] - [:div.element-set [:div.element-set-title [:* [:span "Layout"] (if (and (not multiple) (:layout values)) [:div.title-actions - [:div.layout-btns + (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"]] + :active (= :grid layout-type))} "Grid"]]) [:button.remove-layout {:on-click on-remove-layout} i/minus]] [:button.add-page {:on-click #(on-add-layout :flex)} i/close])]] diff --git a/frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.scss b/frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.scss index 443498da26..97269139b9 100644 --- a/frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.scss +++ b/frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.scss @@ -32,7 +32,7 @@ } .grid-frame { - fill: #F6F6F6; + fill: #f6f6f6; stroke: var(--color-distance); stroke-width: calc(1 / var(--zoom)); } diff --git a/frontend/src/features.cljs b/frontend/src/features.cljs index c129ae84e4..ebce21ba73 100644 --- a/frontend/src/features.cljs +++ b/frontend/src/features.cljs @@ -19,3 +19,6 @@ (defn ^:export new-css-system [] (features/toggle-feature! :new-css-system)) + +(defn ^:export grid [] + (features/toggle-feature! :grid-layout))