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/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 eb45960f89..5de5a5ce32 100644 --- a/common/src/app/common/geom/shapes/grid_layout.cljc +++ b/common/src/app/common/geom/shapes/grid_layout.cljc @@ -7,13 +7,15 @@ (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])) (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) +(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/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/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 e797b1c643..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 @@ -4,137 +4,581 @@ ;; ;; 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.data :as d] [app.common.geom.point :as gpt] - [app.common.geom.shapes.points :as gpo])) + [app.common.geom.shapes.points :as gpo] + [app.common.math :as mth] + [app.common.types.shape.layout :as ctl])) -#_(defn set-sample-data - [parent children] +(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))) - (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} - ] +(defn child-min-width + [child bounds] + (if (ctl/fill-width? child) + (ctl/child-min-width child) + (gpo/width-points bounds))) - :layout-grid-rows - [{:type :percent :value 50} - {:type :percent :value 50} - ;;{:type :fixed :value 100} - ;;{:type :auto} - ;;{:type :flex :value 1} - ]) +(defn child-min-height + [child bounds] + (if (ctl/fill-height? child) + (ctl/child-min-height child) + (gpo/height-points bounds))) - num-rows (count (:layout-grid-rows parent)) - num-columns (count (:layout-grid-columns parent)) +(defn calculate-initial-track-size + [total-value {:keys [type value] :as track}] - 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]))) + (let [[size max-size] + (case type + :percent + (let [value (/ (* total-value value) 100) ] + [value value]) - parent (assoc parent :layout-grid-cells layout-grid-cells)] + :fixed + [value value] - [parent children])) + ;; flex, auto + [0.01 ##Inf])] + (assoc track :size size :max-size max-size))) -(defn calculate-initial-track-values - [{:keys [type value]} total-value] +(defn set-auto-base-size + [track-list children shape-cells type] - (case type - :percent - (let [value (/ (* total-value value) 100) ] - value) + (let [[prop prop-span size-fn] + (if (= type :column) + [:column :column-span child-min-width] + [:row :row-span child-min-height])] - :fixed - value + (reduce (fn [tracks [child-bounds child-shape]] + (let [cell (get shape-cells (:id child-shape)) + idx (dec (get cell prop)) + track (get tracks idx)] + (cond-> tracks + (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))) - :auto - 0 - )) +(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 + "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] + (->> track-list + (mapv (fn [{:keys [type size max-size] :as track}] + (cond-> track + (= :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 child] cell] + (let [[row-gap column-gap] (ctl/gaps parent) + [sfn gap prop-span] + (if (= type :column) + [child-min-width column-gap :column-span] + [child-min-height row-gap :row-span]) + span (get cell prop-span)] + (- (sfn child child-bounds) (* gap (dec span))))) + +(defn allocate-auto-tracks + [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-tracks + [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 + allocated + (->> shape-cells + (vals) + (filter #(> (get % prop-span) 1)) + (remove #(has-flex-track? type track-list %)) + (sort-by prop-span -) + (reduce + (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) + 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 allocated %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)) + (filter #(has-flex-track? type track-list %)) + (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) + + 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 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 + track-list + (into [] + (map-indexed #(update %2 :size max (get allocate-fr-tracks %1))) + 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] + [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 + [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) - ;; 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) + 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)) - ] + (->> (:shapes cell) (map #(vector % cell))))) + layout-grid-cells) - {:row-tracks row-tracks + children (->> children (remove #(ctl/layout-absolute? (second %)))) + children-map + (into {} + (map #(vector (:id (second %)) %)) + children) + + ;; Initialize tracks + column-tracks + (->> layout-grid-columns + (mapv (partial calculate-initial-track-size bound-width))) + + row-tracks + (->> 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 + 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) + + ;; Calculate the `fr` unit and adjust the size + 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) + + ;; 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 (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))) + + ;; 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 (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) + + column-tracks (cond-> column-tracks + (= :stretch (:layout-justify-content parent)) + (add-auto-size column-add-auto)) + + row-tracks (cond-> row-tracks + (= :stretch (:layout-align-content parent)) + (add-auto-size row-add-auto)) + + 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-justify-content parent) + auto-width? + column-gap + + :space-evenly + (max column-gap (/ (- bound-width column-total-size) (inc num-columns))) + + :space-around + (max column-gap (/ (- bound-width column-total-size) num-columns)) + + :space-between + (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-align-content parent) + auto-height? + row-gap + + :space-evenly + (max row-gap (/ (- bound-height row-total-size) (inc num-rows))) + + :space-around + (max row-gap (/ (- bound-height row-total-size) num-rows)) + + :space-between + (max row-gap (if (= num-rows 1) row-gap (/ (- bound-height row-total-size) (dec num-rows)))) + + row-gap) + + start-p + (cond-> bound-corner + (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-justify-content parent))) + (gpt/add (hv (/ (- bound-width (+ column-total-size column-total-gap)) 2))) + + (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-align-content parent))) + (gpt/add (vv (/ (- bound-height (+ row-total-size row-total-gap)) 2))) + + (and (not auto-width?) (= :space-around (:layout-justify-content parent))) + (gpt/add (hv (/ column-gap 2))) + + (and (not auto-width?) (= :space-evenly (:layout-justify-content parent))) + (gpt/add (hv column-gap)) + + (and (not auto-height?) (= :space-around (:layout-align-content parent))) + (gpt/add (vv (/ row-gap 2))) + + (and (not auto-height?) (= :space-evenly (:layout-align-content parent))) + (gpt/add (vv row-gap))) + + 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))] + + {:origin start-p + :layout-bounds layout-bounds + :row-tracks row-tracks :column-tracks column-tracks - :shape-cells shape-cells})) + :shape-cells shape-cells + :column-gap column-gap + :row-gap row-gap + + ;; 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-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))] + [{: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) - 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/grid_layout/positions.cljc b/common/src/app/common/geom/shapes/grid_layout/positions.cljc index 3f81928b48..cea3162d42 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,240 @@ (ns app.common.geom.shapes.grid-layout.positions (:require + [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.geom.point :as gpt] + [app.common.geom.shapes.grid-layout.layout-data :as ld] [app.common.geom.shapes.points :as gpo] - [app.common.types.modifiers :as ctm])) + [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.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)) + + 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-position-delta + [parent 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) + + 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) + + 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 + [(gpt/add origin-h (hv child-width)) + (nth cell-bounds 1)] + + :center + [(gpt/add origin-h (hv (/ child-width 2))) + (gpo/project-point cell-bounds :h (gpo/center cell-bounds))] + + [origin-h (first cell-bounds)]) + + [from-v to-v] + (case align + :end + [(gpt/add origin-v (vv child-height)) + (nth cell-bounds 3)] + + :center + [(gpt/add origin-v (vv (/ child-height 2))) + (gpo/project-point cell-bounds :v (gpo/center 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))))) (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 [[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)) + (-> (ctm/add-modifiers fill-modifiers) + (ctm/move position-delta))))) + + +(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]))))) + + ;; Check if it's inside a track + [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))) + + ;; 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)]))) + +(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))] + + (get-position-grid-coord layout-data position))) diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index 14ffa6ddf5..f0eb8f320c 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -204,8 +204,11 @@ [(-> (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) + + (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) @@ -217,13 +220,13 @@ (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 [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 grid-data cell-data bound+child))] (recur modif-tree (first pending) (rest pending))) modif-tree))))) @@ -253,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))] @@ -297,13 +305,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 %))))] @@ -421,7 +429,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/geom/shapes/points.cljc b/common/src/app/common/geom/shapes/points.cljc index 3722beb9e8..e2403b76b0 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] @@ -78,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))) @@ -91,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]] @@ -152,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/common/src/app/common/pages/changes.cljc b/common/src/app/common/pages/changes.cljc index 97be5e91b1..45854b9375 100644 --- a/common/src/app/common/pages/changes.cljc +++ b/common/src/app/common/pages/changes.cljc @@ -101,9 +101,18 @@ [: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]]] + [: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 be93f9cc72..8e2588d4e5 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) + new-shapes (->> (:shapes grid) + (filterv #(contains? objects %))) + + redo-change + {:type :reorder-children + :parent-id (:id grid) + :page-id page-id + :shapes new-shapes} + + undo-change + {:type :reorder-children + :parent-id (:id grid) + :page-id page-id + :shapes old-shapes}] + (-> 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/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/common/src/app/common/pages/helpers.cljc b/common/src/app/common/pages/helpers.cljc index 529d3bf2f6..2f7fdcdd2e 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]}] @@ -64,6 +66,10 @@ [{:keys [type]}] (= type :rect)) +(defn circle-shape? + [{:keys [type]}] + (= type :circle)) + (defn image-shape? [{:keys [type]}] (= type :image)) @@ -131,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/common/src/app/common/text.cljc b/common/src/app/common/text.cljc index da941a60ec..554a7d7c04 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,50 @@ {: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))) + +(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/common/src/app/common/types/shape.cljc b/common/src/app/common/types/shape.cljc index 4b5703acd2..9c5202a779 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)) @@ -429,4 +437,3 @@ (make-minimal-group uuid/zero geom-props (:name attrs))) (setup-shape geom-props) (merge attrs))) - diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index 7f7814ef66..bb4017a431 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])) @@ -20,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} @@ -39,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}) @@ -48,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}) @@ -58,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}) @@ -89,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}) @@ -163,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 @@ -207,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)] @@ -217,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)] @@ -298,32 +309,39 @@ 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) (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 @@ -506,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 )) @@ -552,9 +570,11 @@ (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 + {:type :auto}) + (def grid-cell-defaults {:row-span 1 :column-span 1 @@ -563,27 +583,24 @@ :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] (dm/assert! "expected a valid grid definition for `value`" - (grid-definition? value)) + (grid-track? 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) - (reduce (fn [result [row-idx _row]] + (reduce (fn [result [row-idx _]] (let [id (uuid/next)] (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 @@ -594,45 +611,170 @@ [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))) 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 :column (inc col-idx) - :row new-row-num - :track? true} + :row new-row-num} grid-cell-defaults)))) (:layout-grid-cells parent)))] (-> parent (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 + +(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 span-attr track-num] + (fn [[id cell]] + (let [inner-track? + (or (= track-num (get cell attr)) + (< (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]))) + (defn remove-grid-column - [parent _index] - parent) + [parent index] + + (let [track-num (inc index) + + decrease-track-num (make-decrease-track-num :column :column-span track-num) + remove-track? (make-remove-cell :column :column-span track-num) + + update-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 update-cells) + (assign-cells)))) (defn remove-grid-row - [parent _index] - parent) + [parent index] + (let [track-num (inc index) -;; 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) + decrease-track-num (make-decrease-track-num :row :row-span track-num) + remove-track? (make-remove-cell :row :row-span track-num) + update-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 update-cells) + (assign-cells)))) + +(defn get-cells + ([parent] + (get-cells parent nil)) + + ([{: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) + + 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 + ([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? (set (:shapes parent)) + cells (update-vals + (:layout-grid-cells parent) + (fn [cell] (update cell :shapes #(filterv child? %))))] + + (assoc parent :layout-grid-cells cells))) + +(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)) -;; 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 +783,289 @@ ;; - (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-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-column add-grid-row) + + parent + (->> (range to-add-tracks) + (reduce (fn [parent _] (add-track parent default-track-value)) parent)) + + 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)) + 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)))))] + + ;; TODO: Remove after testing + (assert (empty? (overlapping-cells parent)) (dm/str (overlapping-cells parent))) + (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 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 #(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})) + [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 + 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))) + +(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] + + (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) + + 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) + + ;; 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)) + + parent + (->> (range (count (:layout-grid-rows parent)) + (+ new-row new-row-span -1)) + (reduce (fn [parent _] (add-grid-row parent default-track-value)) parent)) + + parent (create-cells parent prev-area) + + 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 #{})) + + 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) + + ;; 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])))) + +(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)))) + +(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/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/layouts/inspect.scss b/frontend/resources/styles/main/layouts/inspect.scss index 8b13d09f79..b0ad47bd20 100644 --- a/frontend/resources/styles/main/layouts/inspect.scss +++ b/frontend/resources/styles/main/layouts/inspect.scss @@ -104,11 +104,7 @@ $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 { @@ -122,6 +118,10 @@ $width-settings-bar: 256px; overflow-y: auto; } } + + &.settings-bar-right { + width: 100%; + } } .inspect-svg-wrapper { @@ -142,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/inspect.scss b/frontend/resources/styles/main/partials/inspect.scss index 32ce5a9e7e..df0e22357a 100644 --- a/frontend/resources/styles/main/partials/inspect.scss +++ b/frontend/resources/styles/main/partials/inspect.scss @@ -328,9 +328,14 @@ } .code-block { + position: relative; margin-top: 0.5rem; border-top: 1px solid $color-gray-60; + &:last-child { + margin-bottom: 18px; + } + &:hover { .code-row-lang { .expand-button, @@ -353,17 +358,85 @@ .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 +451,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/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/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 b1a04a6d84..f7011e1789 100644 --- a/frontend/resources/styles/main/partials/workspace.scss +++ b/frontend/resources/styles/main/partials/workspace.scss @@ -48,14 +48,12 @@ $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; + height: 100%; + width: var(--width, $width-settings-bar); grid-area: right-sidebar; - &.expanded { - width: $width-settings-bar * 3; + &.not-expand { + max-width: $width-settings-bar; } } @@ -367,7 +365,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 +377,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.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 7043327671..ce0cf18a77 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] @@ -783,6 +784,18 @@ (assoc :layout-item-v-sizing :fix)) 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)))) @@ -1804,6 +1817,11 @@ ;; 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)) + + 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) selected (->> changes @@ -1812,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) @@ -2135,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/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/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/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/edition.cljs b/frontend/src/app/main/data/workspace/edition.cljs index 01be4c72ec..5318424131 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 @@ -44,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/grid_layout/editor.cljs b/frontend/src/app/main/data/workspace/grid_layout/editor.cljs index 6f9bf9a8eb..62a25035c6 100644 --- a/frontend/src/app/main/data/workspace/grid_layout/editor.cljs +++ b/frontend/src/app/main/data/workspace/grid_layout/editor.cljs @@ -6,10 +6,12 @@ (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 - [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 +21,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] @@ -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])] + (-> 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/modifiers.cljs b/frontend/src/app/main/data/workspace/modifiers.cljs index a27f9c4d1b..4428173b2d 100644 --- a/frontend/src/app/main/data/workspace/modifiers.cljs +++ b/frontend/src/app/main/data/workspace/modifiers.cljs @@ -175,8 +175,29 @@ (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)) + + selected (->> selected (remove #(ctl/layout-absolute? objects %))) + 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 +230,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 +250,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 +485,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/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index 9bab204bf7..3219d56bec 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,16 @@ :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) + (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 e235137a8e..15873ee1cb 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/. ;; @@ -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,17 +53,18 @@ :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-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 {} :layout-grid-rows [] :layout-grid-columns []}) @@ -86,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?] @@ -174,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?)) @@ -188,7 +195,10 @@ (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) + (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)))))) @@ -278,7 +288,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) @@ -335,22 +345,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] @@ -363,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)) @@ -443,12 +410,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 @@ -496,7 +464,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) @@ -544,6 +512,52 @@ (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)))))) + +(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/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs index 0ef0f39a28..9b9833ac0f 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,9 +92,11 @@ (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)))] - + (-> (pcb/update-shapes [(:parent-id shape)] ctl/assign-cells) + (pcb/reorder-grid-children [(:parent-id shape)]))))] [shape changes])) (defn add-shape @@ -141,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] @@ -357,6 +361,7 @@ (ptk/data-event :layout/update all-parents) (dwu/commit-undo-transaction undo-id)))) + (defn create-and-add-shape [type frame-x frame-y data] (ptk/reify ::create-and-add-shape 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/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 862cb4599c..2e94d95160 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 @@ -540,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)))))))))))))) @@ -557,57 +556,76 @@ 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))) + (pcb/reorder-grid-children [(:id parent)])))) 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 @@ -795,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)) 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/refs.cljs b/frontend/src/app/main/refs.cljs index bcb78e7a4a..0a08096a5d 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -485,23 +485,41 @@ (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/any-layout-immediate-child? 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)) + 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/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/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/formats.cljs b/frontend/src/app/main/ui/formats.cljs index 76fac5eda0..a11c12f84a 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] @@ -97,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/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/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/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/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/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..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,15 +61,18 @@ (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 {::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 +87,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 @@ -83,23 +97,32 @@ [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 + + content (if code? (txt/index-content content) content) + + 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; - -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/shapes/text/styles.cljs b/frontend/src/app/main/ui/shapes/text/styles.cljs index 5ad25a7dfd..38625b2c72 100644 --- a/frontend/src/app/main/ui/shapes/text/styles.cljs +++ b/frontend/src/app/main/ui/shapes/text/styles.cljs @@ -10,6 +10,7 @@ [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])) @@ -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 (fmt/format-pixels height) + :width (fmt/format-pixels width) :fontFamily "sourcesanspro" :display "flex" :whiteSpace "break-spaces"}] @@ -48,7 +49,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 @@ -72,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.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/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 2d97af2ed3..91fdd23db8 100644 --- a/frontend/src/app/main/ui/viewer/inspect/code.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/code.cljs @@ -7,48 +7,66 @@ (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] + [app.util.webapi :as wapi] + [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"))) +(def embed-images? true) +(def remove-localhost? true) + +(def page-template + " + +
+ + + + %s + +") (defn format-code [code type] - (let [code (-> code - (str/replace "