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 4a92e0b17b..72c1ea18f9 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 @@ -62,15 +62,17 @@ (defn child-min-width [child bounds] - (if (ctl/fill-width? child) - (ctl/child-min-width child) - (gpo/width-points bounds))) + (+ (if (ctl/fill-width? child) + (ctl/child-min-width child) + (gpo/width-points bounds)) + (ctl/child-width-margin child))) (defn child-min-height [child bounds] - (if (ctl/fill-height? child) - (ctl/child-min-height child) - (gpo/height-points bounds))) + (+ (if (ctl/fill-height? child) + (ctl/child-min-height child) + (gpo/height-points bounds)) + (ctl/child-height-margin child))) (defn calculate-initial-track-size [total-value {:keys [type value] :as track}] 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 cea3162d42..6f73edb886 100644 --- a/common/src/app/common/geom/shapes/grid_layout/positions.cljc +++ b/common/src/app/common/geom/shapes/grid_layout/positions.cljc @@ -18,6 +18,7 @@ [app.common.types.shape.layout :as ctl])) (defn cell-bounds + "Retrieves the points that define the bounds for given cell" [{: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 %) @@ -55,11 +56,13 @@ [_parent transform transform-inverse - _child + child child-origin child-width cell-bounds] - (let [target-width (max (gpo/width-points cell-bounds) 0.01) + (let [target-width (max (- (gpo/width-points cell-bounds) (ctl/child-width-margin child)) 0.01) + max-width (max (ctl/child-max-width child) 0.01) + target-width (mth/clamp target-width (ctl/child-min-width child) max-width) fill-scale (/ target-width child-width)] {:width target-width :modifiers (ctm/resize-modifiers (gpt/point fill-scale 1) child-origin transform transform-inverse)})) @@ -68,10 +71,12 @@ "Calculates the size and modifiers for the height of an auto-fill child" [_parent transform transform-inverse - _child + child child-origin child-height cell-bounds] - (let [target-height (max (gpo/height-points cell-bounds) 0.01) + (let [target-height (max (- (gpo/height-points cell-bounds) (ctl/child-height-margin child)) 0.01) + max-height (max (ctl/child-max-height child) 0.01) + target-height (mth/clamp target-height (ctl/child-min-height child) max-height) fill-scale (/ target-height child-height)] {:height target-height :modifiers (ctm/resize-modifiers (gpt/point 1 fill-scale) child-origin transform transform-inverse)})) @@ -106,7 +111,7 @@ (cond-> fill-height (ctm/add-modifiers (:modifiers fill-height))))])) (defn child-position-delta - [parent child-bounds child-width child-height layout-data cell-data] + [parent child child-bounds child-width child-height layout-data cell-data] (let [cell-bounds (cell-bounds layout-data cell-data) child-origin (gpo/origin child-bounds) @@ -126,30 +131,35 @@ hv (partial gpo/start-hv cell-bounds) vv (partial gpo/start-vv cell-bounds) + [top-m right-m bottom-m left-m] (ctl/child-margins child) + ;; Adjust alignment/justify [from-h to-h] (case justify :end [(gpt/add origin-h (hv child-width)) - (nth cell-bounds 1)] + (gpt/subtract (nth cell-bounds 1) (hv right-m))] :center [(gpt/add origin-h (hv (/ child-width 2))) (gpo/project-point cell-bounds :h (gpo/center cell-bounds))] - [origin-h (first cell-bounds)]) + [origin-h + (gpt/add (first cell-bounds) (hv left-m))]) [from-v to-v] (case align :end [(gpt/add origin-v (vv child-height)) - (nth cell-bounds 3)] + (gpt/subtract (nth cell-bounds 3) (vv bottom-m))] :center [(gpt/add origin-v (vv (/ child-height 2))) (gpo/project-point cell-bounds :v (gpo/center cell-bounds))] - [origin-v (first cell-bounds)])] + [origin-v + (gpt/add (first cell-bounds) (vv top-m))])] + (-> (gpt/point) (gpt/add (gpt/to-vec from-h to-h)) (gpt/add (gpt/to-vec from-v to-v))))) @@ -160,7 +170,7 @@ (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)] + position-delta (child-position-delta parent child child-bounds child-width child-height layout-data cell-data)] (cond-> (ctm/empty) (not (ctl/layout-absolute? child)) diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index c5503d0402..ade9d8a7ef 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -104,14 +104,15 @@ (defn absolute-move "Move the shape to the exactly specified position." [shape pos] - (let [x (dm/get-prop pos :x) - y (dm/get-prop pos :y) - sr (dm/get-prop shape :selrect) - px (dm/get-prop sr :x) - py (dm/get-prop sr :y) - dx (- (d/check-num x) px) - dy (- (d/check-num y) py)] - (move shape (gpt/point dx dy)))) + (when shape + (let [x (dm/get-prop pos :x) + y (dm/get-prop pos :y) + sr (dm/get-prop shape :selrect) + px (dm/get-prop sr :x) + py (dm/get-prop sr :y) + dx (- (d/check-num x) px) + dy (- (d/check-num y) py)] + (move shape (gpt/point dx dy))))) ;; --- Transformation matrix operations diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index bb4017a431..b5b5d9fdae 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -13,8 +13,6 @@ [app.common.schema :as sm] [app.common.uuid :as uuid])) -;; FIXME: need proper schemas - ;; :layout ;; :flex, :grid in the future ;; :layout-flex-dir ;; :row, :row-reverse, :column, :column-reverse ;; :layout-gap-type ;; :simple, :multiple @@ -695,6 +693,29 @@ (update :layout-grid-cells update-cells) (assign-cells)))) +(defn- reorder-grid-track + [prop parent from-index to-index] + (-> parent + (update + prop + (fn [tracks] + (let [tr (nth tracks from-index)] + (mapv + second + (-> tracks + (d/enumerate) ;; make unique so the insert-at-index won't remove the value + (assoc from-index nil) + (d/insert-at-index (inc to-index) [[nil tr]]) + (d/vec-without-nils)))))))) + +(defn reorder-grid-column + [parent from-index to-index] + (reorder-grid-track :layout-grid-columns parent from-index to-index)) + +(defn reorder-grid-row + [parent from-index to-index] + (reorder-grid-track :layout-grid-rows parent from-index to-index)) + (defn get-cells ([parent] (get-cells parent nil)) @@ -827,7 +848,9 @@ cells (let [next-free (first free-cells) current (first pending) - cells (update-in cells [next-free :shapes] conj current)] + cells (-> cells + (update-in [next-free :shapes] conj current) + (assoc-in [next-free :position] :auto))] (recur cells (rest free-cells) (rest pending)))))] ;; TODO: Remove after testing @@ -851,7 +874,7 @@ (let [cell-from (get cells idx) cell-to (get cells (inc idx)) - cell (assoc cell-to :shapes (:shapes cell-from)) + cell (assoc cell-to :shapes (:shapes cell-from) :position (:position cell-from)) parent (assoc-in parent [:layout-grid-cells (:id cell)] cell) result-cells (assoc result-cells (inc idx) cell)] @@ -863,7 +886,7 @@ (recur parent result-cells (inc idx))))))] [(assoc-in parent [:layout-grid-cells (get-in cells [index :id]) :shapes] []) - (assoc-in result-cells [index :shapes] [])])))) + (update result-cells index assoc :shapes [] :position :auto)])))) (defn in-cell? @@ -893,7 +916,7 @@ [start-index start-cell] (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 + (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)))] ;; Move shift the `shapes` attribute between cells @@ -901,7 +924,8 @@ (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]) + [(update-in parent [:layout-grid-cells (get-in cells [idx :id])] + assoc :position :manual :shapes [shape-id]) cells])) [parent cells]) (first))) @@ -1025,9 +1049,17 @@ (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])))) + (let [cell-to (dm/get-in parent [:layout-grid-cells id-to]) + cell-from (dm/get-in parent [:layout-grid-cells id-from])] + (-> parent + (update-in [:layout-grid-cells id-from] + assoc + :shapes (:shapes cell-to) + :podition (:position cell-to)) + (update-in [:layout-grid-cells id-to] + assoc + :shapes (:shapes cell-from) + :position (:position cell-from))))) (defn add-children-to-cell [frame children objects [row column :as cell]] @@ -1069,3 +1101,80 @@ new-shapes (into new-shapes (:shapes parent))] (assoc parent :shapes (into [] (reverse new-shapes))))) + +(defn shapes-by-row + [parent index] + (->> (:layout-grid-cells parent) + (filter (fn [[_ {:keys [row row-span]}]] + (and (>= (inc index) row) + (< (inc index) (+ row row-span))))) + (map second) + (mapcat :shapes))) + +(defn shapes-by-column + [parent index] + (->> (:layout-grid-cells parent) + (filter (fn [[_ {:keys [column column-span]}]] + (and (>= (inc index) column) + (< (inc index) (+ column column-span))))) + (map second) + (mapcat :shapes))) + +(defn cells-coordinates + "Given a group of cells returns the coordinates that define" + [cells] + (loop [cells (seq cells) + result + {:first-row ##Inf + :first-column ##Inf + :last-row ##-Inf + :last-column ##-Inf + :cell-coords #{}}] + + (if (empty? cells) + result + (let [{:keys [first-row last-row first-column last-column cell-coords]} result + current (first cells) + + first-row + (if (< (:row current) first-row) + (:row current) + first-row) + + last-row + (if (> (+ (:row current) (:row-span current) -1) last-row) + (+ (:row current) (:row-span current) -1) + last-row) + + first-column + (if (< (:column current) first-column) + (:column current) + first-column) + + last-column + (if (> (+ (:column current) (:column-span current) -1) last-column) + (+ (:column current) (:column-span current) -1) + last-column) + + cell-coords + (into cell-coords + (for [r (range (:row current) (+ (:row current) (:row-span current))) + c (range (:column current) (+ (:column current) (:column-span current)))] + [r c]))] + (recur (rest cells) + (assoc result + :first-row first-row + :last-row last-row + :first-column first-column + :last-column last-column + :cell-coords cell-coords)))))) + +(defn valid-area-cells? + [cells] + + (let [{:keys [first-row last-row first-column last-column cell-coords]} (cells-coordinates cells)] + (every? + #(contains? cell-coords %) + (for [r (range first-row (inc last-row)) + c (range first-column (inc last-column))] + [r c])))) diff --git a/frontend/resources/styles/main/partials/sidebar-element-options.scss b/frontend/resources/styles/main/partials/sidebar-element-options.scss index 961adc2d5f..9fa16aa288 100644 --- a/frontend/resources/styles/main/partials/sidebar-element-options.scss +++ b/frontend/resources/styles/main/partials/sidebar-element-options.scss @@ -1855,13 +1855,13 @@ } } .position-wrapper { - display: grid; - grid-template-columns: 1fr 1fr 1fr; + display: flex; width: 100%; max-width: 185px; height: 26px; border-radius: 4px; border: 1px solid $color-gray-60; + .position-btn { display: flex; justify-content: center; @@ -1871,6 +1871,8 @@ cursor: pointer; color: $color-gray-20; border-right: 1px solid $color-gray-60; + flex: 1; + &:last-child { border-right: none; } @@ -1878,6 +1880,23 @@ &:hover { color: $color-primary; } + + &[disabled] { + opacity: 0.5; + &:hover { + color: $color-gray-20; + } + } + } + } + + &.single-button { + display: flex; + justify-content: end; + height: 1.5rem; + + .btn-wrapper { + width: initial; } } } @@ -2262,6 +2281,13 @@ grid-template-columns: 35px 1fr 1fr auto; background-color: $color-gray-60; padding: 3px; + border-radius: 3px; + border: 1px solid transparent; + + &:hover { + border: 1px solid $color-primary; + } + &:not(:first-child) { margin-top: 3px; } diff --git a/frontend/resources/styles/main/partials/sidebar-layers.scss b/frontend/resources/styles/main/partials/sidebar-layers.scss index 1ac8f0fb19..461ddaea87 100644 --- a/frontend/resources/styles/main/partials/sidebar-layers.scss +++ b/frontend/resources/styles/main/partials/sidebar-layers.scss @@ -71,7 +71,8 @@ } } - &:hover { + &:hover, + &.hover { background-color: $color-primary; svg { diff --git a/frontend/src/app/main/data/workspace/drawing/box.cljs b/frontend/src/app/main/data/workspace/drawing/box.cljs index 4ac0afb2d4..d6d28dd8b3 100644 --- a/frontend/src/app/main/data/workspace/drawing/box.cljs +++ b/frontend/src/app/main/data/workspace/drawing/box.cljs @@ -41,7 +41,7 @@ (> dy dx) (assoc :x (- (:x point) (* sx (- dy dx))))))) -(defn resize-shape [{:keys [x y width height] :as shape} initial point lock?] +(defn resize-shape [{:keys [x y width height] :as shape} initial point lock? mod?] (if (and (some? x) (some? y) (some? width) (some? height)) (let [draw-rect (grc/make-rect initial (cond-> point lock? (adjust-ratio initial))) shape-rect (grc/make-rect x y width height) @@ -56,13 +56,14 @@ (-> shape (assoc :click-draw? false) + (vary-meta merge {:mod? mod?}) (gsh/transform-shape (-> (ctm/empty) (ctm/resize scalev (gpt/point x y)) (ctm/move movev))))) shape)) -(defn update-drawing [state initial point lock?] - (update-in state [:workspace-drawing :object] resize-shape initial point lock?)) +(defn update-drawing [state initial point lock? mod?] + (update-in state [:workspace-drawing :object] resize-shape initial point lock? mod?)) (defn move-drawing [{:keys [x y]}] @@ -105,9 +106,7 @@ (cond-> (some? drop-index) (with-meta {:index drop-index})) (cond-> (some? drop-cell) - (with-meta {:cell drop-cell}))) - - ] + (with-meta {:cell drop-cell})))] (rx/concat ;; Add shape to drawing state @@ -120,14 +119,14 @@ (->> ms/mouse-position (rx/filter #(> (gpt/distance % initial) (/ 2 zoom))) (rx/with-latest vector ms/mouse-position-shift) + (rx/with-latest conj ms/mouse-position-mod) (rx/switch-map (fn [[point :as current]] (->> (snap/closest-snap-point page-id [shape] objects layout zoom focus point) (rx/map #(conj current %))))) (rx/map - (fn [[_ shift? point]] - #(update-drawing % initial (cond-> point snap-pixel? (gpt/round-step snap-prec)) shift?))))) - + (fn [[_ shift? mod? point]] + #(update-drawing % initial (cond-> point snap-pixel? (gpt/round-step snap-prec)) shift? mod?))))) (rx/take-until stoper)) (->> (rx/of (common/handle-finish-drawing)) 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 f4dce997f4..82621a7c4a 100644 --- a/frontend/src/app/main/data/workspace/grid_layout/editor.cljs +++ b/frontend/src/app/main/data/workspace/grid_layout/editor.cljs @@ -25,19 +25,41 @@ (disj hover-set cell-id)))))))) (defn select-grid-cell - [grid-id cell-id] + [grid-id cell-id add?] (ptk/reify ::select-grid-cell ptk/UpdateEvent (update [_ state] - (assoc-in state [:workspace-grid-edition grid-id :selected] cell-id)))) + (if add? + (update-in state [:workspace-grid-edition grid-id :selected] (fnil conj #{}) cell-id) + (assoc-in state [:workspace-grid-edition grid-id :selected] #{cell-id}))))) (defn remove-selection - [grid-id] + [grid-id cell-id] (ptk/reify ::remove-selection + ptk/UpdateEvent + (update [_ state] + (update-in state [:workspace-grid-edition grid-id :selected] disj cell-id)))) + +(defn clear-selection + [grid-id] + (ptk/reify ::clear-selection ptk/UpdateEvent (update [_ state] (update-in state [:workspace-grid-edition grid-id] dissoc :selected)))) +(defn clean-selection + [grid-id] + (ptk/reify ::clean-selection + ptk/UpdateEvent + (update [_ state] + (let [objects (wsh/lookup-page-objects state) + shape (get objects grid-id)] + (update-in state [:workspace-grid-edition grid-id :selected] + (fn [selected] + (into #{} + (filter #(contains? (:layout-grid-cells shape) %)) + selected))))))) + (defn stop-grid-layout-editing [grid-id] (ptk/reify ::stop-grid-layout-editing diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index a48c242055..06777eab70 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -490,11 +490,13 @@ (let [page (wsh/lookup-page state) libraries (wsh/get-libraries state) + objects (:objects page) changes (-> (pcb/empty-changes it (:id page)) - (pcb/with-objects (:objects page))) + (pcb/with-objects objects)) [new-shape changes] (dwlh/generate-instantiate-component changes + objects file-id component-id position diff --git a/frontend/src/app/main/data/workspace/libraries_helpers.cljs b/frontend/src/app/main/data/workspace/libraries_helpers.cljs index d208f58b1a..bedb73d290 100644 --- a/frontend/src/app/main/data/workspace/libraries_helpers.cljs +++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs @@ -10,6 +10,7 @@ [app.common.data.macros :as dm] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] + [app.common.geom.shapes.grid-layout :as gslg] [app.common.logging :as log] [app.common.pages.changes-builder :as pcb] [app.common.pages.helpers :as cph] @@ -21,6 +22,7 @@ [app.common.types.container :as ctn] [app.common.types.file :as ctf] [app.common.types.shape-tree :as ctst] + [app.common.types.shape.layout :as ctl] [app.common.types.typography :as cty] [app.common.uuid :as uuid] [app.main.data.workspace.state-helpers :as wsh] @@ -158,10 +160,10 @@ (defn generate-instantiate-component "Generate changes to create a new instance from a component." - ([changes file-id component-id position page libraries] - (generate-instantiate-component changes file-id component-id position page libraries nil nil)) + ([changes objects file-id component-id position page libraries] + (generate-instantiate-component changes objects file-id component-id position page libraries nil nil)) - ([changes file-id component-id position page libraries old-id parent-id] + ([changes objects file-id component-id position page libraries old-id parent-id] (let [component (ctf/get-component libraries file-id component-id) library (get libraries file-id) @@ -182,6 +184,19 @@ changes (cond-> (pcb/add-object changes first-shape {:ignore-touched true}) (some? old-id) (pcb/amend-last-change #(assoc % :old-id old-id))) + changes + (if (ctl/grid-layout? objects (:parent-id first-shape)) + (let [[row column] (gslg/get-drop-cell (:parent-id first-shape) objects position)] + (-> changes + (pcb/update-shapes + [(:parent-id first-shape)] + (fn [shape] + (-> shape + (ctl/push-into-cell [(:id first-shape)] row column) + (ctl/assign-cells)))) + (pcb/reorder-grid-children [(:parent-id first-shape)]))) + changes) + changes (reduce #(pcb/add-object %1 %2 {:ignore-touched true}) changes (rest new-shapes))] diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index 50d8e65259..d1ed714fe2 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -384,7 +384,7 @@ (prepare-duplicate-guides shapes page ids-map delta))))) (defn- prepare-duplicate-component-change - [changes page component-root parent-id delta libraries library-data it] + [changes objects page component-root parent-id delta libraries library-data it] (let [component-id (:component-id component-root) file-id (:component-file component-root) main-component (ctf/get-component libraries file-id component-id) @@ -393,6 +393,7 @@ instantiate-component #(dwlh/generate-instantiate-component changes + objects file-id (:component-id component-root) pos @@ -421,7 +422,7 @@ changes (ctf/is-known-component? obj libraries) - (prepare-duplicate-component-change changes page obj parent-id delta libraries library-data it) + (prepare-duplicate-component-change changes objects page obj parent-id delta libraries library-data it) :else (let [frame? (cph/frame-shape? obj) @@ -469,7 +470,10 @@ ; We want the first added object to touch it's parent, but not subsequent children changes (-> (pcb/add-object changes new-obj {:ignore-touched (and duplicating-component? child?)}) - (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) + (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 2cf6841a03..bc645e92d2 100644 --- a/frontend/src/app/main/data/workspace/shape_layout.cljs +++ b/frontend/src/app/main/data/workspace/shape_layout.cljs @@ -20,6 +20,7 @@ [app.common.uuid :as uuid] [app.main.data.workspace.changes :as dwc] [app.main.data.workspace.colors :as cl] + [app.main.data.workspace.grid-layout.editor :as dwge] [app.main.data.workspace.modifiers :as dwm] [app.main.data.workspace.selection :as dwse] [app.main.data.workspace.shapes :as dws] @@ -407,6 +408,48 @@ (ptk/data-event :layout/update ids) (dwu/commit-undo-transaction undo-id)))))) +(defn reorder-layout-track + [ids type from-index to-index] + (assert (#{:row :column} type)) + + (ptk/reify ::reorder-layout-track + ptk/WatchEvent + (watch [_ _ _] + (let [undo-id (js/Symbol)] + (rx/of (dwu/start-undo-transaction undo-id) + (dwc/update-shapes + ids + (fn [shape] + (case type + :row (ctl/reorder-grid-row shape from-index to-index) + :column (ctl/reorder-grid-column shape from-index to-index)))) + (ptk/data-event :layout/update ids) + (dwu/commit-undo-transaction undo-id)))))) + +(defn hover-layout-track + [ids type index hover?] + (assert (#{:row :column} type)) + + (ptk/reify ::hover-layout-track + ptk/UpdateEvent + (update [_ state] + (let [objects (wsh/lookup-page-objects state) + shape (get objects (first ids)) + highlighted (when hover? + (->> (if (= type :row) + (ctl/shapes-by-row shape index) + (ctl/shapes-by-column shape index)) + (set)))] + (cond-> state + hover? + (update-in [:workspace-grid-edition (first ids) :hover-track] (fnil conj #{}) [type index]) + + (not hover?) + (update-in [:workspace-grid-edition (first ids) :hover-track] (fnil disj #{}) [type index]) + + :always + (assoc-in [:workspace-local :highlighted] highlighted)))))) + (defn change-layout-track [ids type index props] (assert (#{:row :column} type)) @@ -521,20 +564,73 @@ (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 +(defn update-grid-cells + [layout-id ids props] + (ptk/reify ::update-grid-cells 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)))))) + (->> ids + (reduce (fn [shape cell-id] + (-> shape + (d/update-in-when [:layout-grid-cells cell-id] + #(d/without-nils (merge % props))))) + shape)))) + (ptk/data-event :layout/update [layout-id]) + (dwu/commit-undo-transaction undo-id)))))) + +(defn change-cells-mode + [layout-id ids mode] + + (ptk/reify ::change-cells-mode + ptk/WatchEvent + (watch [_ _ _] + (let [undo-id (js/Symbol)] + (rx/of + (dwu/start-undo-transaction undo-id) + + (dwc/update-shapes + [layout-id] + (fn [shape] + (cond + (= mode :area) + ;; Create area with the selected cells + (let [{:keys [first-row first-column last-row last-column]} + (ctl/cells-coordinates (->> ids (map #(get-in shape [:layout-grid-cells %])))) + + target-cell + (ctl/get-cell-by-position shape first-row first-column) + + shape + (-> shape + (ctl/resize-cell-area + (:row target-cell) (:column target-cell) + first-row + first-column + (inc (- last-row first-row)) + (inc (- last-column first-column))) + (ctl/assign-cells))] + + (-> shape + (d/update-in-when [:layout-grid-cells (:id target-cell)] assoc :position :area))) + + (= mode :auto) + ;; change the manual cells and move to auto + (->> ids + (reduce + (fn [shape cell-id] + (cond-> shape + (contains? #{:area :manual} (get-in shape [:layout-grid-cells cell-id :position])) + (-> (d/update-in-when [:layout-grid-cells cell-id] assoc :shapes [] :position :auto) + (ctl/assign-cells)))) + shape))))) + (dwge/clean-selection layout-id) (ptk/data-event :layout/update [layout-id]) (dwu/commit-undo-transaction undo-id)))))) diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs index 7c59da35ba..4c65609dd8 100644 --- a/frontend/src/app/main/data/workspace/shapes.cljs +++ b/frontend/src/app/main/data/workspace/shapes.cljs @@ -36,10 +36,10 @@ (defn prepare-add-shape [changes shape objects _selected] (let [index (:index (meta shape)) - ;; FIXME: revisit id (:id shape) - [row column :as cell] (:cell (meta shape)) + mod? (:mod? (meta shape)) + [row column :as cell] (when-not mod? (:cell (meta shape))) changes (-> changes (pcb/with-objects objects) diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 71a62d8612..593dcb6949 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -490,7 +490,7 @@ flex-layout? (ctl/flex-layout? objects target-frame) grid-layout? (ctl/grid-layout? objects target-frame) 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))] + cell-data (when (and grid-layout? (not mod?)) (gslg/get-drop-cell target-frame objects position))] [move-vector target-frame drop-index cell-data]))) (rx/take-until stopper))] diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 2cefb77a8c..867a225c03 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -126,6 +126,9 @@ [id] (l/derived #(contains? % id) selected-shapes)) +(def highlighted-shapes + (l/derived :highlighted workspace-local)) + (def export-in-progress? (l/derived :export-in-progress? export)) diff --git a/frontend/src/app/main/ui/workspace/sidebar/layer_item.cljs b/frontend/src/app/main/ui/workspace/sidebar/layer_item.cljs index 964de3776d..61bb1dbc34 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/layer_item.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/layer_item.cljs @@ -32,7 +32,7 @@ (mf/defc layer-item {::mf/wrap-props false} - [{:keys [index item selected objects sortable? filtered? depth parent-size component-child?]}] + [{:keys [index item selected objects sortable? filtered? depth parent-size component-child? highlighted]}] (let [id (:id item) name (:name item) blocked? (:blocked item) @@ -50,6 +50,7 @@ expanded? (mf/deref expanded-iref) selected? (contains? selected id) + highlighted? (contains? highlighted id) container? (or (cph/frame-shape? item) (cph/group-shape? item)) absolute? (ctl/layout-absolute? item) @@ -341,6 +342,7 @@ [:div.element-list-body {:class (stl/css-case* :selected selected? + :hover highlighted? :icon-layer (= (:type item) :icon)) :on-click select-shape :on-pointer-enter on-pointer-enter @@ -395,6 +397,7 @@ [:& layer-item {:item item :selected selected + :highlighted highlighted :index index :objects objects :key (dm/str id) diff --git a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs index 430dad2767..fee063fabc 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs @@ -43,6 +43,8 @@ [{:keys [objects filtered? parent-size] :as props}] (let [selected (mf/deref refs/selected-shapes) selected (hooks/use-equal-memo selected) + highlighted (mf/deref refs/highlighted-shapes) + highlighted (hooks/use-equal-memo highlighted) root (get objects uuid/zero) new-css-system (mf/use-ctx ctx/new-css-system)] [:ul @@ -54,6 +56,7 @@ [:& frame-wrapper {:item obj :selected selected + :highlighted highlighted :index index :objects objects :key id @@ -64,6 +67,7 @@ [:& layer-item {:item obj :selected selected + :highlighted highlighted :index index :objects objects :key id diff --git a/frontend/src/app/main/ui/workspace/sidebar/options.cljs b/frontend/src/app/main/ui/workspace/sidebar/options.cljs index a7f41befb3..3dda831366 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options.cljs @@ -82,7 +82,8 @@ shape-parent-frame (cph/get-frame objects (:frame-id first-selected-shape)) edit-grid? (ctl/grid-layout? objects edition) - selected-cell (dm/get-in grid-edition [edition :selected]) + selected-cells (->> (dm/get-in grid-edition [edition :selected]) + (map #(dm/get-in objects [edition :layout-grid-cells %]))) on-change-tab (fn [options-mode] @@ -105,10 +106,10 @@ [:& bool-options] (cond - (some? selected-cell) + (d/not-empty? selected-cells) [:& grid-cell/options {:shape (get objects edition) - :cell (dm/get-in objects [edition :layout-grid-cells selected-cell])}] + :cells selected-cells}] edit-grid? [:& layout-container/grid-layout-edition @@ -166,10 +167,10 @@ [:& align-options] [:& bool-options] (cond - (some? selected-cell) + (d/not-empty? selected-cells) [:& grid-cell/options {:shape (get objects edition) - :cell (dm/get-in objects [edition :layout-grid-cells selected-cell])}] + :cells selected-cells}] edit-grid? [:& layout-container/grid-layout-edition diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/grid_cell.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/grid_cell.cljs index c2f03f1d7a..d1fb4e255f 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/grid_cell.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/grid_cell.cljs @@ -6,16 +6,31 @@ (ns app.main.ui.workspace.sidebar.options.menus.grid-cell (:require + [app.common.attrs :as attrs] [app.common.data :as d] [app.common.data.macros :as dm] + [app.common.types.shape.layout :as ctl] + [app.main.data.workspace :as dw] + [app.main.data.workspace.grid-layout.editor :as dwge] [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.hooks :as hooks] [app.main.ui.icons :as i] [app.main.ui.workspace.sidebar.options.menus.layout-container :as lyc] [app.util.dom :as dom] [rumext.v2 :as mf])) +(def cell-props [:id + :position + :row + :row-span + :column + :column-span + :align-self + :justify-self + :area-name]) + (mf/defc set-self-alignment [{:keys [is-col? alignment set-alignment] :as props}] (let [dir-v [:auto :start :center :end :stretch #_:baseline] @@ -34,72 +49,91 @@ (mf/defc options {::mf/wrap [mf/memo]} - [{:keys [shape cell] :as props}] + [{:keys [shape cell cells] :as props}] - (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) + (let [cells (hooks/use-equal-memo cells) + cell (or cell (attrs/get-attrs-multi cells cell-props)) - cell-mode (or mode :auto) + multiple? (= :multiple (:id cell)) + cell-ids (if (some? cell) [(:id cell)] (->> cells (map :id))) + cell-ids (hooks/use-equal-memo cell-ids) + + {:keys [position area-name align-self justify-self column column-span row row-span]} cell + + column-end (when (and (d/num? column) (d/num? column-span)) + (+ column column-span)) + row-end (when (and (d/num? row) (d/num? row-span)) + (+ row row-span)) + + cell-mode (or position :auto) cell-mode (if (and (= :auto cell-mode) (or (> (:column-span cell) 1) (> (:row-span cell) 1))) :manual cell-mode) + valid-area-cells? (mf/use-memo + (mf/deps cells) + #(ctl/valid-area-cells? cells)) + set-alignment (mf/use-callback - (mf/deps align-self (:id shape) (:id cell)) + (mf/deps align-self (:id shape) cell-ids) (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}))))) + (st/emit! (dwsl/update-grid-cells (:id shape) cell-ids {:align-self nil})) + (st/emit! (dwsl/update-grid-cells (:id shape) cell-ids {:align-self value}))))) set-justify-self (mf/use-callback - (mf/deps justify-self (:id shape) (:id cell)) + (mf/deps justify-self (:id shape) cell-ids) (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}))))) + (st/emit! (dwsl/update-grid-cells (:id shape) cell-ids {:justify-self nil})) + (st/emit! (dwsl/update-grid-cells (:id shape) cell-ids {:justify-self value}))))) - on-change + on-grid-coordinates (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] + (when-not multiple? + (let [[property value] + (cond + (and (= type :column) (or (= field :all) (= field :start))) + [:column value] - (and (= type :column) (= field :end)) - [:column-span (max 1 (- value column))] + (and (= type :column) (= field :end)) + [:column-span (max 1 (- value column))] - (and (= type :row) (or (= field :all) (= field :start))) - [:row value] + (and (= type :row) (or (= field :all) (= field :start))) + [:row value] - (and (= type :row) (= field :end)) - [:row-span (max 1 (- value row))])] + (and (= type :row) (= field :end)) + [:row-span (max 1 (- value row))])] - (st/emit! (dwsl/update-grid-cell-position (:id shape) (:id cell) {property value}))))) + (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)) + (mf/deps (:id shape) cell-ids) (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})))))) + (st/emit! (dwsl/update-grid-cells (:id shape) cell-ids {:area-name nil})) + (st/emit! (dwsl/update-grid-cells (:id shape) cell-ids {:area-name value})))))) set-cell-mode (mf/use-callback - (mf/deps (:id shape) (:id cell)) + (mf/deps (:id shape) cell-ids) (fn [mode] - (let [props (cond-> {:mode mode} - (not= mode :area) - (assoc :area-name nil))] - (st/emit! (dwsl/update-grid-cell (:id shape) (:id cell) props)))))] + (st/emit! (dwsl/change-cells-mode (:id shape) cell-ids mode)))) + + toggle-edit-mode + (mf/use-fn + (mf/deps (:id shape)) + (fn [] + (st/emit! (dw/start-edition-mode (:id shape)) + (dwge/clear-selection (:id shape)))))] [:div.element-set [:div.element-set-title @@ -112,15 +146,17 @@ [:button.position-btn {:on-click #(set-cell-mode :auto) :class (dom/classnames :active (= :auto cell-mode))} "Auto"] - [:button.position-btn - {:on-click #(set-cell-mode :manual) - :class (dom/classnames :active (= :manual cell-mode))} "Manual"] + (when-not multiple? + [:button.position-btn + {:on-click #(set-cell-mode :manual) + :class (dom/classnames :active (= :manual cell-mode))} "Manual"]) [:button.position-btn {:on-click #(set-cell-mode :area) + :disabled (not valid-area-cells?) :class (dom/classnames :active (= :area cell-mode))} "Area"]]] [:div.manage-grid-columns - (when (= :auto cell-mode) + (when (and (not multiple?) (= :auto cell-mode)) [:div.grid-auto [:div.grid-columns-auto [:span.icon i/layout-rows] @@ -128,7 +164,7 @@ [:> numeric-input* {:placeholder "--" :on-click #(dom/select-target %) - :on-change (partial on-change :all :column) + :on-change (partial on-grid-coordinates :all :column) :value column}]]] [:div.grid-rows-auto [:span.icon i/layout-columns] @@ -136,7 +172,7 @@ [:> numeric-input* {:placeholder "--" :on-click #(dom/select-target %) - :on-change (partial on-change :all :row) + :on-change (partial on-grid-coordinates :all :row) :value row}]]]]) (when (= :area cell-mode) @@ -151,7 +187,7 @@ :auto-complete "off" :on-change on-area-name-change}]]) - (when (or (= :manual cell-mode) (= :area cell-mode)) + (when (and (not multiple?) (or (= :manual cell-mode) (= :area cell-mode))) [:div.grid-manual [:div.grid-columns-auto [:span.icon i/layout-rows] @@ -159,12 +195,12 @@ [:> numeric-input* {:placeholder "--" :on-pointer-down #(dom/select-target %) - :on-change (partial on-change :start :column) + :on-change (partial on-grid-coordinates :start :column) :value column}] [:> numeric-input* {:placeholder "--" :on-pointer-down #(dom/select-target %) - :on-change (partial on-change :end :column) + :on-change (partial on-grid-coordinates :end :column) :value column-end}]]] [:div.grid-rows-auto [:span.icon i/layout-columns] @@ -172,12 +208,12 @@ [:> numeric-input* {:placeholder "--" :on-pointer-down #(dom/select-target %) - :on-change (partial on-change :start :row) + :on-change (partial on-grid-coordinates :start :row) :value row}] [:> numeric-input* {:placeholder "--" :on-pointer-down #(dom/select-target %) - :on-change (partial on-change :end :row) + :on-change (partial on-grid-coordinates :end :row) :value row-end}]]]])] [:div.layout-row @@ -191,4 +227,14 @@ [:div.btn-wrapper [:& set-self-alignment {:is-col? true :alignment justify-self - :set-alignment set-justify-self}]]]]])) + :set-alignment set-justify-self}]]] + + [:div.layout-row.single-button + [:div.btn-wrapper + [:div.edit-mode + [:button.tooltip.tooltip-bottom-left + {:alt "Grid edit mode" + :on-click toggle-edit-mode + :style {:padding 0}} + "Edit grid" + i/grid-layout-mode]]]]]])) 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 7dbdc17dad..0207f372be 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 @@ -21,6 +21,7 @@ [app.main.ui.components.select :refer [select]] [app.main.ui.components.title-bar :refer [title-bar]] [app.main.ui.context :as ctx] + [app.main.ui.hooks :as h] [app.main.ui.icons :as i] [app.util.dom :as dom] [cuerdas.core :as str] @@ -871,8 +872,68 @@ :fixed (dm/str value "px") value)) +(mf/defc grid-track-info + [{:keys [is-col? type index column set-column-value set-column-type remove-element reorder-track hover-track]}] + (let [drop-track + (mf/use-callback + (mf/deps type reorder-track index) + (fn [drop-position data] + (reorder-track type (:index data) (if (= :top drop-position) (dec index) index)))) + + pointer-enter + (mf/use-callback + (mf/deps type hover-track index) + (fn [] + (hover-track type index true))) + + pointer-leave + (mf/use-callback + (mf/deps type hover-track index) + (fn [] + (hover-track type index false))) + + [dprops dref] + (h/use-sortable + :data-type "penpot/grid-track" + :on-drop drop-track + :data {:is-col? is-col? + :index index + :column column} + :draggable? true)] + [:div.column-info + {:ref dref + :class (dom/classnames + :dnd-over-top (or (= (:over dprops) :top) + (= (:over dprops) :center)) + :dnd-over-bot (= (:over dprops) :bot)) + :on-pointer-enter pointer-enter + :on-pointer-leave pointer-leave} + [:div.direction-grid-icon + (if is-col? + i/layout-rows + i/layout-columns)] + + [:div.grid-column-value + [:> numeric-input* {:no-validate true + :value (:value column) + :on-change #(set-column-value type index %) + :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"} + {:value :percent :label "%"}] + :on-change #(set-column-type type index %)}]] + [:button.remove-grid-column + {:on-click #(remove-element type index)} + i/minus]])) + (mf/defc grid-columns-row - [{:keys [is-col? expanded? column-values toggle add-new-element set-column-value set-column-type remove-element] :as props}] + [{:keys [is-col? expanded? column-values toggle add-new-element set-column-value set-column-type remove-element reorder-track hover-track] :as props}] (let [column-num (count column-values) direction (if (> column-num 1) (if is-col? "Columns " "Rows ") @@ -894,32 +955,19 @@ (add-new-element type ctl/default-track-value))} i/plus]] (when expanded? - [:div.columns-info-wrapper - (for [[index column] (d/enumerate column-values)] - [:div.column-info {:key (dm/str index "-" (name type) "-" column)} - [:div.direction-grid-icon - (if is-col? - i/layout-rows - i/layout-columns)] - - [:div.grid-column-value - [:> numeric-input* {:no-validate true - :value (:value column) - :on-change #(set-column-value type index %) - :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"} - {:value :percent :label "%"}] - :on-change #(set-column-type type index %)}]] - [:button.remove-grid-column - {:on-click #(remove-element type index)} - i/minus]])])])) + [:& h/sortable-container {} + [:div.columns-info-wrapper + (for [[index column] (d/enumerate column-values)] + [:& grid-track-info {:key (dm/str index "-" (name type) "-" column) + :type type + :is-col? is-col? + :index index + :column column + :set-column-value set-column-value + :set-column-type set-column-type + :remove-element remove-element + :reorder-track reorder-track + :hover-track hover-track}])]])])) ;; LAYOUT COMPONENT @@ -1352,7 +1400,7 @@ {::mf/wrap [#(mf/memo' % (mf/check-props ["ids" "values"]))]} [{:keys [ids values] :as props}] (let [;; Gap - gap-selected? (mf/use-state :none) + gap-selected? (mf/use-state :none) saved-grid-dir (:layout-grid-dir values) set-direction @@ -1401,8 +1449,8 @@ (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}))))) ;;Grid columns column-grid-values (:layout-grid-columns values) @@ -1431,6 +1479,18 @@ (fn [type index] (st/emit! (dwsl/remove-layout-track ids type index)))) + reorder-track + (mf/use-fn + (mf/deps ids) + (fn [type from-index to-index] + (st/emit! (dwsl/reorder-layout-track ids type from-index to-index)))) + + hover-track + (mf/use-fn + (mf/deps ids) + (fn [type index hover?] + (st/emit! (dwsl/hover-layout-track ids type index hover?)))) + set-column-value (mf/use-fn (mf/deps ids) @@ -1482,12 +1542,12 @@ [:div.layout-row [:div.jusfiy-content-grid.row-title "Content"] - [:div.btn-wrapper.align-grid + [:div.btn-wrapper.align-grid-content [:& justify-grid-row {:is-col? true - :justify-items grid-justify-content-column + :justify-items grid-justify-content-row :set-justify set-content-grid}] [:& justify-grid-row {:is-col? false - :justify-items grid-justify-content-row + :justify-items grid-justify-content-column :set-justify set-content-grid}]]] [:& grid-columns-row {:is-col? true :expanded? @grid-columns-open? @@ -1496,7 +1556,9 @@ :add-new-element add-new-element :set-column-value set-column-value :set-column-type set-column-type - :remove-element remove-element}] + :remove-element remove-element + :reorder-track reorder-track + :hover-track hover-track}] [:& grid-columns-row {:is-col? false :expanded? @grid-rows-open? @@ -1505,7 +1567,9 @@ :add-new-element add-new-element :set-column-value set-column-value :set-column-type set-column-type - :remove-element remove-element}] + :remove-element remove-element + :reorder-track reorder-track + :hover-track hover-track}] [:& gap-section {:gap-selected? gap-selected? :on-change set-gap 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 85ab7b921a..7f88b351f7 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 @@ -533,7 +533,7 @@ :nillable true :value (:layout-item-z-index values)}]])] - (when is-layout-child? + (when (and is-layout-child? is-flex-parent?) [:div {:class (stl/css :second-row)} [:& align-self-row {:is-col? is-col? :align-self align-self @@ -667,7 +667,7 @@ :on-change-behaviour-v-refactor on-change-behaviour-v :on-change on-change-behaviour}]] - (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/sitemap.cljs b/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs index 333782b2cd..d630eea1da 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs @@ -27,7 +27,8 @@ ;; --- Page Item -(mf/defc page-item [{:keys [page index deletable? selected? editing?] :as props}] +(mf/defc page-item + [{:keys [page index deletable? selected? editing? hovering?] :as props}] (let [input-ref (mf/use-ref) id (:id page) new-css-system (mf/use-ctx ctx/new-css-system) @@ -135,6 +136,7 @@ (css :selected) selected?) (dom/classnames :element-list-body true + :hover hovering? :selected selected?)) :data-test (dm/str "page-" id) :tab-index "0" diff --git a/frontend/src/app/main/ui/workspace/sidebar/sitemap.scss b/frontend/src/app/main/ui/workspace/sidebar/sitemap.scss index e2595c96fe..8ba8c7d6d8 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/sitemap.scss +++ b/frontend/src/app/main/ui/workspace/sidebar/sitemap.scss @@ -176,7 +176,8 @@ } } } - &:hover { + &:hover, + &.hover { .element-list-body { color: var(--layer-row-foreground-color-hover); background-color: var(--layer-row-background-color-hover); diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index d2982a10cb..cb5bf62332 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -173,7 +173,7 @@ on-context-menu (actions/on-context-menu hover hover-ids 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-drag-over (actions/on-drag-over move-stream) on-drop (actions/on-drop file) on-pointer-down (actions/on-pointer-down @hover selected edition drawing-tool text-editing? node-editing? grid-editing? drawing-path? create-comment? space? panning z? workspace-read-only?) diff --git a/frontend/src/app/main/ui/workspace/viewport/actions.cljs b/frontend/src/app/main/ui/workspace/viewport/actions.cljs index 4535a030da..b65e6c279d 100644 --- a/frontend/src/app/main/ui/workspace/viewport/actions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/actions.cljs @@ -404,15 +404,17 @@ (dnd/has-type? e "text/asset-id")) (dom/prevent-default e))))) -(defn on-drag-over [] - (mf/use-callback - (fn [e] - (when (or (dnd/has-type? e "penpot/shape") - (dnd/has-type? e "penpot/component") - (dnd/has-type? e "Files") - (dnd/has-type? e "text/uri-list") - (dnd/has-type? e "text/asset-id")) - (dom/prevent-default e))))) +(defn on-drag-over [move-stream] + (let [on-pointer-move (on-pointer-move move-stream)] + (mf/use-callback + (fn [e] + (when (or (dnd/has-type? e "penpot/shape") + (dnd/has-type? e "penpot/component") + (dnd/has-type? e "Files") + (dnd/has-type? e "text/uri-list") + (dnd/has-type? e "text/asset-id")) + (on-pointer-move e) + (dom/prevent-default e)))))) (defn on-drop [file] 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 194a838dc8..99ef08e8d5 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 @@ -25,6 +25,7 @@ [app.main.ui.css-cursors :as cur] [app.main.ui.formats :as fmt] [app.main.ui.hooks :as hooks] + [app.main.ui.icons :as i] [app.main.ui.workspace.viewport.viewport-ref :as uwvv] [app.util.dom :as dom] [app.util.keyboard :as kbd] @@ -301,10 +302,10 @@ handle-pointer-down (mf/use-callback (mf/deps (:id shape) (:id cell) selected?) - (fn [] - (if selected? - (st/emit! (dwge/remove-selection (:id shape))) - (st/emit! (dwge/select-grid-cell (:id shape) (:id cell))))))] + (fn [event] + (if (and (kbd/shift? event) selected?) + (st/emit! (dwge/remove-selection (:id shape) (:id cell))) + (st/emit! (dwge/select-grid-cell (:id shape) (:id cell) (kbd/shift? event)) ))))] [:g.cell-editor [:rect @@ -434,19 +435,19 @@ [width height] (if (= type :column) - [(max layout-gap-col (/ 16 zoom)) + [(max 0 (- layout-gap-col (/ 10 zoom)) (/ 16 zoom)) (+ row-total-size row-total-gap)] [(+ column-total-size column-total-gap) - (max layout-gap-row (/ 16 zoom))]) + (max 0 (- layout-gap-row (/ 10 zoom)) (/ 16 zoom))]) start-p (cond-> start-p (and (= type :column) (= index 0)) - (gpt/subtract (hv width)) + (gpt/subtract (hv (/ width 2))) (and (= type :row) (= index 0)) - (gpt/subtract (vv height)) + (gpt/subtract (vv (/ height 2))) (and (= type :column) (not= index 0) (not last?)) (-> (gpt/subtract (hv (/ layout-gap-col 2))) @@ -531,16 +532,17 @@ (dm/str value)]])) (mf/defc track - {::mf/wrap [#(mf/memo' % (mf/check-props ["shape" "zoom" "index" "type" "track-data" "layout-data"]))] + {::mf/wrap [#(mf/memo' % (mf/check-props ["shape" "zoom" "index" "type" "track-data" "layout-data" "hovering?"]))] ::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") + (let [shape (unchecked-get props "shape") + 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") + track-data (unchecked-get props "track-data") layout-data (unchecked-get props "layout-data") + hovering? (unchecked-get props "hovering?") track-input-ref (mf/use-ref) [layout-gap-row layout-gap-col] (ctl/gaps shape) @@ -604,6 +606,24 @@ (when esc? (dom/blur! (dom/get-target event)))))) + handle-pointer-enter + (mf/use-callback + (mf/deps (:id shape) type index) + (fn [] + (st/emit! (dwsl/hover-layout-track [(:id shape)] type index true)))) + + handle-pointer-leave + (mf/use-callback + (mf/deps (:id shape) type index) + (fn [] + (st/emit! (dwsl/hover-layout-track [(:id shape)] type index false)))) + + handle-remove-track + (mf/use-callback + (mf/deps (:id shape) type index) + (fn [] + (st/emit! (dwsl/remove-layout-track [(:id shape)] type index)))) + track-list-prop (if (= type :column) :column-tracks :row-tracks) [text-x text-y text-width text-height] (if (= type :column) @@ -618,18 +638,34 @@ (dom/set-value! (mf/ref-val track-input-ref) (format-size track-data)))) [:g.track - [:g {:transform (if (= type :column) + [:g {:on-pointer-enter handle-pointer-enter + :on-pointer-leave handle-pointer-leave + :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))))} + + (when hovering? + [:rect {:x (+ text-x (/ 5 zoom)) + :y text-y + :width (- text-width (/ 10 zoom)) + :height (- text-height (/ 5 zoom)) + :rx (/ 3 zoom) + :fill "var(--color-distance)" + :opacity 0.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}]]] + [:div {:class (css :grid-editor-wrapper)} + [:input + {:ref track-input-ref + :style {} + :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}] + (when hovering? + [:button {:class (css :grid-editor-button) + :on-click handle-remove-track} i/trash])]]] [:g {:transform (when (= type :row) (dm/fmt "rotate(-90 % %)" (:x marker-p) (:y marker-p)))} [:& track-marker @@ -684,6 +720,18 @@ hover-cells (:hover grid-edition) selected-cells (:selected grid-edition) + hover-columns + (->> (:hover-track grid-edition) + (filter (fn [[t _]] (= t :column))) + (map (fn [[_ idx]] idx)) + (into #{})) + + hover-rows + (->> (:hover-track grid-edition) + (filter (fn [[t _]] (= t :row))) + (map (fn [[_ idx]] idx)) + (into #{})) + children (mf/use-memo (mf/deps shape modifiers) @@ -733,6 +781,15 @@ [:g.grid-editor {:pointer-events (when view-only "none") :on-pointer-down handle-pointer-down} + [: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? (contains? selected-cells (:id cell))}])] (when-not view-only [:* [:& grid-editor-frame {:zoom zoom @@ -759,7 +816,8 @@ :index idx :layout-data layout-data :snap-pixel? snap-pixel? - :track-data column-data}]) + :track-data column-data + :hovering? (contains? hover-columns idx)}]) ;; Last track resize handler (when-not (empty? column-tracks) @@ -796,8 +854,8 @@ :snap-pixel? snap-pixel? :track-data row-data :type :row - :zoom zoom}]) - + :zoom zoom + :hovering? (contains? hover-rows idx)}]) (when-not (empty? row-tracks) (let [last-track (last row-tracks) start-p (:start-p last-track) @@ -823,14 +881,4 @@ :type :row :track-before (last row-tracks) :snap-pixel? snap-pixel? - :zoom zoom}]]))]) - - [: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))}])]])) + :zoom zoom}]]))])])) 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 97269139b9..a8043abb3a 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 @@ -11,19 +11,27 @@ } } -.grid-editor-label { - position: absolute; - background: none; +.grid-editor-wrapper { width: 100%; - height: 100%; - text-align: center; - font-family: worksans; + height: 80%; + display: flex; + justify-content: center; + align-items: center; +} + +.grid-editor-label { + background: none; + border-bottom: calc(1px / var(--zoom)) solid transparent; + border: 0; color: var(--color-distance); + font-family: worksans; + font-size: calc(12px / var(--zoom)); font-weight: 600; margin: 0; + max-width: calc(80px / var(--zoom)); padding: 0; - border: 0; - font-size: calc(12px / var(--zoom)); + padding: 4px; + text-align: center; &:focus { outline: none; @@ -31,6 +39,24 @@ } } +.grid-editor-button { + background: none; + border: none; + cursor: pointer; + margin: 0; + padding: 0; + position: absolute; + right: calc(10px / var(--zoom)); + width: calc(20px / var(--zoom)); + height: calc(20px / var(--zoom)); + + svg { + width: calc(16px / var(--zoom)); + height: auto; + fill: var(--color-distance); + } +} + .grid-frame { fill: #f6f6f6; stroke: var(--color-distance); diff --git a/frontend/test/frontend_tests/helpers/pages.cljs b/frontend/test/frontend_tests/helpers/pages.cljs index 837037699d..7ef367f8dd 100644 --- a/frontend/test/frontend_tests/helpers/pages.cljs +++ b/frontend/test/frontend_tests/helpers/pages.cljs @@ -153,12 +153,14 @@ ([state label component-id file-id] (let [page (current-page state) libraries (wsh/get-libraries state) + objects (:objects page) changes (-> (pcb/empty-changes nil (:id page)) - (pcb/with-objects (:objects page))) + (pcb/with-objects objects)) [new-shape changes] (dwlh/generate-instantiate-component changes + objects file-id component-id (gpt/point 100 100)