0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-04-16 17:01:33 -05:00

Merge pull request #3396 from penpot/alotor-grid-layout

First grid layout version
This commit is contained in:
Alejandro 2023-07-10 15:08:14 +02:00 committed by GitHub
commit 6539b7da5b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
106 changed files with 5691 additions and 2154 deletions

View file

@ -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

View file

@ -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]))

View file

@ -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)

View file

@ -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])))

View file

@ -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)))

View file

@ -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)))))

View file

@ -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)))

View file

@ -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

View file

@ -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))))

View file

@ -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

View file

@ -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))

View file

@ -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

View file

@ -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)]

View file

@ -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))))))))))

View file

@ -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)))

View file

@ -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)))))

View file

@ -123,6 +123,10 @@
padding: $size-1;
svg {
fill: $color-gray-20;
&.icon-close {
transform: rotate(45deg);
}
}
&:hover {
background: transparent;

View file

@ -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;
}
}

View file

@ -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 {

View file

@ -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;

View file

@ -35,6 +35,10 @@
overflow-x: hidden;
}
.inspect .tab-container-content {
overflow: hidden;
}
.tab-element,
.tab-element-content {
height: 100%;

View file

@ -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;

View file

@ -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

View file

@ -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
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View file

@ -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))

View file

@ -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)))]

View file

@ -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

View file

@ -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)

View file

@ -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)))))))

View file

@ -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])))))))))))

View file

@ -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]

View file

@ -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?)

View file

@ -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))))))

View file

@ -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

View file

@ -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

View file

@ -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))

View file

@ -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]

View file

@ -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]

View file

@ -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)]))

View file

@ -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)

View file

@ -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))))

View file

@ -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

View file

@ -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

View file

@ -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}])])))

View file

@ -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}])]))

View file

@ -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}]]))

View file

@ -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)

View file

@ -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?}]]))

View file

@ -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"

View file

@ -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
}]]]))

View file

@ -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

View file

@ -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)}]])])))

View file

@ -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))

View file

@ -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

View file

@ -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)}])])

View file

@ -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)

View file

@ -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)}])])))

View file

@ -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)}])])))

View file

@ -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)}])])))

View file

@ -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}]])))

View file

@ -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

View file

@ -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}]))]])))

View file

@ -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))))]

View file

@ -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
"<!-- Shape: " (:name shape) " -->"
(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
"<!DOCTYPE html>
<html>
<head>
<style>
%s
</style>
</head>
<body>
%s
</body>
</html>")
(defn format-code [code type]
(let [code (-> code
(str/replace "<defs></defs>" "")
(str/replace "><" ">\n<"))]
(cond-> code
(= type "svg") (beautify/html #js {"indent_size" 2}))))
(cond-> code
(= type "svg")
(-> (str/replace "<defs></defs>" "")
(str/replace "><" ">\n<"))
(or (= type "svg") (= type "html"))
(beautify/html #js {"indent_size" 2})))
(defn get-flex-elements [page-id shapes from]
(let [ids (mapv :id shapes)
ids (hooks/use-equal-memo ids)
get-layout-children-refs (mf/use-memo (mf/deps ids page-id from) #(if (= from :workspace)
(refs/workspace-get-flex-child ids)
(refs/get-flex-child-viewer ids page-id)))]
get-layout-children-refs
(mf/use-memo
(mf/deps ids page-id from)
#(if (= from :workspace)
(refs/workspace-get-flex-child ids)
(refs/get-flex-child-viewer ids page-id)))]
(mf/deref get-layout-children-refs)))
@ -62,46 +80,163 @@
(refs/get-viewer-objects))))]
(mf/deref page-objects-ref)))
(defn shapes->images
[shapes]
(->> shapes
(keep
(fn [shape]
(when-let [data (or (:metadata shape) (:fill-image shape))]
[(:id shape) (cfg/resolve-file-media data)])))))
(defn replace-map
[value map]
(reduce
(fn [value [old new]]
(str/replace value old new))
value map))
(mf/defc code
[{:keys [shapes frame on-expand from]}]
(let [style-type (mf/use-state "css")
markup-type (mf/use-state "svg")
(let [style-type* (mf/use-state "css")
markup-type* (mf/use-state "html")
fontfaces-css* (mf/use-state nil)
images-data* (mf/use-state nil)
style-type (deref style-type*)
markup-type (deref markup-type*)
fontfaces-css (deref fontfaces-css*)
images-data (deref images-data*)
shapes (->> shapes
(map #(gsh/translate-to-frame % frame)))
route (mf/deref refs/route)
page-id (:page-id (:query-params route))
flex-items (get-flex-elements page-id shapes from)
objects (get-objects from)
;; TODO REMOVE THIS
shapes (->> shapes
(map #(assoc % :parent (get objects (:parent-id %))))
(map #(assoc % :flex-items flex-items)))
style-code (-> (cg/generate-style-code @style-type shapes)
(format-code "css"))
all-children (->> shapes
(map :id)
(cph/selected-with-children objects)
(ctst/sort-z-index objects)
(map (d/getf objects)))
shapes (hooks/use-equal-memo shapes)
all-children (hooks/use-equal-memo all-children)
fonts (-> (shapes->fonts all-children)
(hooks/use-equal-memo))
images-urls (-> (shapes->images all-children)
(hooks/use-equal-memo))
style-code
(mf/use-memo
(mf/deps fontfaces-css style-type all-children cg/generate-style-code)
(fn []
(dm/str
fontfaces-css "\n"
(-> (cg/generate-style-code objects style-type all-children)
(format-code style-type)))))
markup-code
(-> (mf/use-memo (mf/deps shapes) #(generate-markup-code objects shapes))
(format-code "svg"))
(mf/use-memo
(mf/deps markup-type shapes images-data)
(fn []
(-> (cg/generate-markup-code objects markup-type shapes)
(format-code markup-type))))
on-markup-copied
(mf/use-callback
(mf/deps @markup-type)
(mf/deps markup-type)
(fn []
(st/emit! (ptk/event ::ev/event
{::ev/name "copy-inspect-code"
:type @markup-type}))))
:type markup-type}))))
on-style-copied
(mf/use-callback
(mf/deps @style-type)
(mf/deps style-type)
(fn []
(st/emit! (ptk/event ::ev/event
{::ev/name "copy-inspect-style"
:type @style-type}))))]
:type style-type}))))
{on-markup-pointer-down :on-pointer-down
on-markup-lost-pointer-capture :on-lost-pointer-capture
on-markup-pointer-move :on-pointer-move
markup-size :size}
(use-resize-hook :code 400 100 800 :y false :bottom)
{on-style-pointer-down :on-pointer-down
on-style-lost-pointer-capture :on-lost-pointer-capture
on-style-pointer-move :on-pointer-move
style-size :size}
(use-resize-hook :code 400 100 800 :y false :bottom)
set-style
(mf/use-callback
(fn [value]
(reset! style-type* value)))
set-markup
(mf/use-callback
(fn [value]
(reset! markup-type* value)))
handle-copy-all-code
(mf/use-callback
(mf/deps style-code markup-code images-data)
(fn []
(let [markup-code (cond-> markup-code
embed-images? (replace-map images-data))
style-code (cond-> style-code
remove-localhost?
(str/replace "http://localhost:3449" ""))
data (str/format page-template style-code markup-code)]
(wapi/write-to-clipboard data))))]
(mf/use-effect
(mf/deps fonts)
#(->> (rx/from fonts)
(rx/merge-map fonts/fetch-font-css)
(rx/reduce conj [])
(rx/subs
(fn [result]
(let [css (str/join "\n" result)]
(reset! fontfaces-css* css))))))
(mf/use-effect
(mf/deps images-urls)
#(->> (rx/from images-urls)
(rx/merge-map
(fn [[_ uri]]
(->> (http/fetch-data-uri uri true)
(rx/catch (fn [_] (rx/of (hash-map uri uri)))))))
(rx/reduce conj {})
(rx/subs
(fn [result]
(reset! images-data* result)))))
[:div.element-options
[:div.code-block
[:div.code-row-lang "CSS"
[:div.attributes-block
[:button.download-button {:on-click handle-copy-all-code}
"Copy all code"]]
[:div.code-block
[:div.code-row-lang
[:& select {:default-value style-type
:class "custom-select"
:options [{:label "CSS" :value "css"}]
:on-change set-style}]
[:button.expand-button
{:on-click on-expand}
i/full-screen]
@ -109,19 +244,33 @@
[:& copy-button {:data style-code
:on-copied on-style-copied}]]
[:div.code-row-display
[:& code-block {:type @style-type
:code style-code}]]]
[:div.code-row-display {:style #js {"--code-height" (str (or style-size 400) "px")}}
[:& code-block {:type style-type
:code style-code}]]
[:div.resize-area {:on-pointer-down on-style-pointer-down
:on-lost-pointer-capture on-style-lost-pointer-capture
:on-pointer-move on-style-pointer-move}]]
[:div.code-block
[:div.code-row-lang "SVG"
[:div.code-row-lang
[:& select {:default-value markup-type
:class "input-option"
:options [{:label "HTML" :value "html"}
{:label "SVG" :value "svg"}]
:on-change set-markup}]
[:button.expand-button
{:on-click on-expand}
i/full-screen]
[:& copy-button {:data markup-code
[:& copy-button {:data #(replace-map markup-code images-data)
:on-copied on-markup-copied}]]
[:div.code-row-display
[:& code-block {:type @markup-type
:code markup-code}]]]]))
[:div.code-row-display {:style #js {"--code-height" (str (or markup-size 400) "px")}}
[:& code-block {:type markup-type
:code markup-code}]]
[:div.resize-area {:on-pointer-down on-markup-pointer-down
:on-lost-pointer-capture on-markup-lost-pointer-capture
:on-pointer-move on-markup-pointer-move}]]]))

View file

@ -6,9 +6,7 @@
(ns app.main.ui.viewer.inspect.right-sidebar
(:require
[app.main.data.workspace :as dw]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.components.shape-icon :as si]
[app.main.ui.components.tabs-container :refer [tabs-container tabs-element]]
[app.main.ui.icons :as i]
@ -38,20 +36,39 @@
:data local})))))
(mf/defc right-sidebar
[{:keys [frame page file selected shapes page-id file-id share-id from]
[{:keys [frame page objects file selected shapes page-id file-id share-id from on-change-section on-expand]
:or {from :inspect}}]
(let [expanded (mf/use-state false)
section (mf/use-state :info #_:code)
(let [section (mf/use-state :info #_:code)
objects (or objects (:objects page))
shapes (or shapes
(resolve-shapes (:objects page) selected))
(resolve-shapes objects selected))
first-shape (first shapes)
page-id (or page-id (:id page))
file-id (or file-id (:id file))
libraries (get-libraries from)]
libraries (get-libraries from)
[:aside.settings-bar.settings-bar-right {:class (when @expanded "expanded")}
handle-change-tab
(mf/use-callback
(mf/deps from on-change-section)
(fn [new-section]
(reset! section new-section)
(when on-change-section
(on-change-section new-section))))
handle-expand
(mf/use-callback
(mf/deps on-expand)
(fn []
(when on-expand (on-expand))))]
(mf/use-effect
(mf/deps shapes handle-change-tab)
(fn []
(when-not (seq shapes)
(handle-change-tab :info))))
[:aside.settings-bar.settings-bar-right
[:div.settings-bar-inside
(if (seq shapes)
[:div.tool-window
@ -77,14 +94,11 @@
;; inspect.tabs.code.selected.text
[:span.tool-window-bar-title (:name first-shape)]])]
[:div.tool-window-content.inspect
[:& tabs-container {:on-change-tab #(do
(reset! expanded false)
(reset! section %)
(when (= from :workspace)
(st/emit! (dw/set-inspect-expanded false))))
:selected @section}
[:& tabs-container {:on-change-tab handle-change-tab
:selected @section}
[:& tabs-element {:id :info :title (tr "inspect.tabs.info")}
[:& attributes {:page-id page-id
:objects objects
:file-id file-id
:frame frame
:shapes shapes
@ -95,10 +109,7 @@
[:& tabs-element {:id :code :title (tr "inspect.tabs.code")}
[:& code {:frame frame
:shapes shapes
:on-expand (fn []
(when (= from :workspace)
(st/emit! (dw/set-inspect-expanded (not @expanded))))
(swap! expanded not))
:on-expand handle-expand
:from from}]]]]]
[:div.empty
[:span.tool-window-bar-icon i/code]

View file

@ -413,9 +413,10 @@
is-frame? (and single? has-frame?)
is-flex-container? (and is-frame? (= :flex (:layout (first shapes))))
ids (->> shapes (map :id))
add-flex #(st/emit! (if is-frame?
(dwsl/create-layout-from-id ids :flex true)
(dwsl/create-layout-from-selection :flex)))
add-layout (fn [type]
(st/emit! (if is-frame?
(dwsl/create-layout-from-id ids type true)
(dwsl/create-layout-from-selection type))))
remove-flex #(st/emit! (dwsl/remove-layout ids))]
[:*
@ -424,7 +425,10 @@
[:& menu-separator]
[:& menu-entry {:title (tr "workspace.shape.menu.add-flex")
:shortcut (sc/get-tooltip :toggle-layout-flex)
:on-click add-flex}]])
:on-click #(add-layout :flex)}]
[:& menu-entry {:title (tr "workspace.shape.menu.add-grid")
:shortcut (sc/get-tooltip :toggle-layout-grid)
:on-click #(add-layout :grid)}]])
(when is-flex-container?
[:div
[:& menu-separator]

View file

@ -151,7 +151,8 @@
(dom/class? node "frame-title")
(let [shape (gsh/transform-shape shape modifiers)
zoom (get-in @st/state [:workspace-local :zoom] 1)
mtx (vwu/title-transform shape zoom)]
edit-grid? (= (dom/get-data node "edit-grid") "true")
mtx (vwu/title-transform shape zoom edit-grid?)]
(override-transform-att! node "transform" mtx))
(or (= (dom/get-tag-name node) "mask")

View file

@ -26,6 +26,7 @@
[app.main.ui.workspace.sidebar.sitemap :refer [sitemap]]
[app.util.dom :as dom]
[app.util.i18n :refer [tr]]
[app.util.object :as obj]
[rumext.v2 :as mf]))
;; --- Left Sidebar (Component)
@ -134,16 +135,45 @@
is-history? (contains? layout :document-history)
is-inspect? (= section :inspect)
expanded? (mf/deref refs/inspect-expanded)
;;expanded? (mf/deref refs/inspect-expanded)
;;prev-expanded? (hooks/use-previous expanded?)
current-section* (mf/use-state :info)
current-section (deref current-section*)
can-be-expanded? (and (not is-comments?)
(not is-history?)
is-inspect?)]
is-inspect?
(= current-section :code))
(mf/with-effect [can-be-expanded?]
(when (not can-be-expanded?)
(st/emit! (dw/set-inspect-expanded false))))
{:keys [on-pointer-down on-lost-pointer-capture on-pointer-move set-size size]}
(use-resize-hook :code 256 256 768 :x true :right)
[:aside.settings-bar.settings-bar-right {:class (when (and can-be-expanded? expanded?) "expanded")}
handle-change-section
(mf/use-callback
(fn [section]
(reset! current-section* section)))
handle-expand
(mf/use-callback
(mf/deps size)
(fn []
(set-size (if (> size 256) 256 768))))
props
(-> props
(obj/clone)
(obj/set! "on-change-section" handle-change-section)
(obj/set! "on-expand" handle-expand))]
[:aside.settings-bar.settings-bar-right
{:class (when (not can-be-expanded?) "not-expand")
:style #js {"--width" (when can-be-expanded? (dm/str size "px"))}}
(when can-be-expanded?
[:div.resize-area
{:on-pointer-down on-pointer-down
:on-lost-pointer-capture on-lost-pointer-capture
:on-pointer-move on-pointer-move}])
[:div.settings-bar-inside
(cond
(true? is-comments?)

View file

@ -7,8 +7,10 @@
(ns app.main.ui.workspace.sidebar.options
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.geom.shapes :as gsh]
[app.common.pages.helpers :as cph]
[app.common.types.shape.layout :as ctl]
[app.main.data.workspace :as udw]
[app.main.refs :as refs]
[app.main.store :as st]
@ -18,12 +20,13 @@
[app.main.ui.workspace.sidebar.options.menus.align :refer [align-options]]
[app.main.ui.workspace.sidebar.options.menus.bool :refer [bool-options]]
[app.main.ui.workspace.sidebar.options.menus.exports :refer [exports-menu]]
[app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell]
[app.main.ui.workspace.sidebar.options.menus.interactions :refer [interactions-menu]]
[app.main.ui.workspace.sidebar.options.menus.layout-container :as layout-container]
[app.main.ui.workspace.sidebar.options.page :as page]
[app.main.ui.workspace.sidebar.options.shapes.bool :as bool]
[app.main.ui.workspace.sidebar.options.shapes.circle :as circle]
[app.main.ui.workspace.sidebar.options.shapes.frame :as frame]
[app.main.ui.workspace.sidebar.options.shapes.grid-cell :as grid-cell]
[app.main.ui.workspace.sidebar.options.shapes.group :as group]
[app.main.ui.workspace.sidebar.options.shapes.image :as image]
[app.main.ui.workspace.sidebar.options.shapes.multiple :as multiple]
@ -64,70 +67,92 @@
(mf/defc options-content
{::mf/wrap [mf/memo]}
[{:keys [selected section shapes shapes-with-children page-id file-id]}]
[{:keys [selected section shapes shapes-with-children page-id file-id on-change-section on-expand]}]
(let [drawing (mf/deref refs/workspace-drawing)
objects (mf/deref refs/workspace-page-objects)
shared-libs (mf/deref refs/workspace-libraries)
edition (mf/deref refs/selected-edition)
grid-edition (mf/deref refs/workspace-grid-edition)
selected-shapes (into [] (keep (d/getf objects)) selected)
first-selected-shape (first selected-shapes)
shape-parent-frame (cph/get-frame objects (:frame-id first-selected-shape))
[grid-id {[row-selected col-selected] :selected}]
(d/seek (fn [[_ {:keys [selected]}]] (some? selected)) grid-edition)
grid-cell-selected? (and (some? grid-id) (some? row-selected) (some? col-selected))
edit-grid? (ctl/grid-layout? objects edition)
selected-cell (dm/get-in grid-edition [edition :selected])
on-change-tab
(fn [options-mode]
(st/emit! (udw/set-options-mode options-mode)
(udw/set-inspect-expanded false))
(if (= options-mode :inspect) ;;TODO maybe move this logic to set-options-mode
(st/emit! (udw/set-options-mode options-mode))
(if (= options-mode :inspect)
(st/emit! :interrupt (udw/set-workspace-read-only true))
(st/emit! :interrupt (udw/set-workspace-read-only false))))]
[:div.tool-window
[:div.tool-window-content
[:& tabs-container {:on-change-tab on-change-tab
:selected section}
[:& tabs-element {:id :design
:title (tr "workspace.options.design")}
:title (tr "workspace.options.design")}
[:div.element-options
[:& align-options]
[:& bool-options]
(cond
grid-cell-selected? [:& grid-cell/options {:shape (get objects grid-id)
:row row-selected
:column col-selected}]
(some? selected-cell)
[:& grid-cell/options
{:shape (get objects edition)
:cell (dm/get-in objects [edition :layout-grid-cells selected-cell])}]
(d/not-empty? drawing) [:& shape-options {:shape (:object drawing)
:page-id page-id
:file-id file-id
:shared-libs shared-libs}]
(= 0 (count selected)) [:& page/options]
(= 1 (count selected)) [:& shape-options {:shape (first selected-shapes)
:page-id page-id
:file-id file-id
:shared-libs shared-libs
:shapes-with-children shapes-with-children}]
:else [:& multiple/options {:shapes-with-children shapes-with-children
:shapes selected-shapes
:page-id page-id
:file-id file-id
:shared-libs shared-libs}])]]
edit-grid?
[:& layout-container/grid-layout-edition
{:ids [edition]
:values (get objects edition)}]
(d/not-empty? drawing)
[:& shape-options
{:shape (:object drawing)
:page-id page-id
:file-id file-id
:shared-libs shared-libs}]
(= 0 (count selected))
[:& page/options]
(= 1 (count selected))
[:& shape-options
{:shape (first selected-shapes)
:page-id page-id
:file-id file-id
:shared-libs shared-libs
:shapes-with-children shapes-with-children}]
:else
[:& multiple/options
{:shapes-with-children shapes-with-children
:shapes selected-shapes
:page-id page-id
:file-id file-id
:shared-libs shared-libs}])]]
[:& tabs-element
{:id :prototype
:title (tr "workspace.options.prototype")}
[:& tabs-element {:id :prototype
:title (tr "workspace.options.prototype")}
[:div.element-options
[:& interactions-menu {:shape (first shapes)}]]]
[:& tabs-element {:id :inspect
:title (tr "workspace.options.inspect")}
[:div.element-options
[:& hrs/right-sidebar {:page-id page-id
:file-id file-id
:frame shape-parent-frame
:shapes selected-shapes
:from :workspace}]]]]]]))
:title (tr "workspace.options.inspect")}
[:div.element-options.element-options-inspect
[:& hrs/right-sidebar {:page-id page-id
:objects objects
:file-id file-id
:frame shape-parent-frame
:shapes selected-shapes
:on-change-section on-change-section
:on-expand on-expand
:from :workspace}]]]]]]))
;; TODO: this need optimizations, selected-objects and
;; selected-objects-with-children are derefed always but they only
@ -139,6 +164,8 @@
[props]
(let [section (obj/get props "section")
selected (obj/get props "selected")
on-change-section (obj/get props "on-change-section")
on-expand (obj/get props "on-expand")
page-id (mf/use-ctx ctx/current-page-id)
file-id (mf/use-ctx ctx/current-file-id)
shapes (mf/deref refs/selected-objects)
@ -149,4 +176,6 @@
:shapes-with-children shapes-with-children
:file-id file-id
:page-id page-id
:section section}]))
:section section
:on-change-section on-change-section
:on-expand on-expand}]))

View file

@ -238,7 +238,8 @@
[:button.btn-options {:disabled is-default
:on-click handle-set-as-default} (tr "workspace.options.grid.params.set-default")]]]]))
(mf/defc frame-grid [{:keys [shape]}]
(mf/defc frame-grid
[{:keys [shape]}]
(let [id (:id shape)
saved-grids (mf/deref workspace-saved-grids)
default-grid-params (mf/use-memo (mf/deps saved-grids) #(merge dw/default-grid-params saved-grids))

View file

@ -0,0 +1,194 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.workspace.sidebar.options.menus.grid-cell
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.main.data.workspace.shape-layout :as dwsl]
[app.main.store :as st]
[app.main.ui.components.numeric-input :refer [numeric-input]]
[app.main.ui.icons :as i]
[app.main.ui.workspace.sidebar.options.menus.layout-container :as lyc]
[app.util.dom :as dom]
[rumext.v2 :as mf]))
(mf/defc set-self-alignment
[{:keys [is-col? alignment set-alignment] :as props}]
(let [dir-v [:auto :start :center :end :stretch #_:baseline]
alignment (or alignment :auto)]
[:div.align-self-style
(for [align dir-v]
[:button.align-self.tooltip.tooltip-bottom
{:class (dom/classnames :active (= alignment align)
:tooltip-bottom-left (not= align :start)
:tooltip-bottom (= align :start))
:alt (dm/str "Align self " (d/name align)) ;; TODO fix this tooltip
:on-click #(set-alignment align)
:key (str "align-self" align)}
(lyc/get-layout-flex-icon :align-self align is-col?)])]))
(mf/defc options
{::mf/wrap [mf/memo]}
[{:keys [shape cell] :as props}]
(let [{:keys [mode area-name align-self justify-self column column-span row row-span]} cell
column-end (+ column column-span)
row-end (+ row row-span)
cell-mode (or mode :auto)
cell-mode (if (and (= :auto cell-mode)
(or (> (:column-span cell) 1)
(> (:row-span cell) 1)))
:manual
cell-mode)
set-alignment
(mf/use-callback
(mf/deps align-self (:id shape) (:id cell))
(fn [value]
(if (= align-self value)
(st/emit! (dwsl/update-grid-cell (:id shape) (:id cell) {:align-self nil}))
(st/emit! (dwsl/update-grid-cell (:id shape) (:id cell) {:align-self value})))))
set-justify-self
(mf/use-callback
(mf/deps justify-self (:id shape) (:id cell))
(fn [value]
(if (= justify-self value)
(st/emit! (dwsl/update-grid-cell (:id shape) (:id cell) {:justify-self nil}))
(st/emit! (dwsl/update-grid-cell (:id shape) (:id cell) {:justify-self value})))))
on-change
(mf/use-callback
(mf/deps column row (:id shape) (:id cell))
(fn [field type value]
(let [[property value]
(cond
(and (= type :column) (or (= field :all) (= field :start)))
[:column value]
(and (= type :column) (= field :end))
[:column-span (max 1 (- value column))]
(and (= type :row) (or (= field :all) (= field :start)))
[:row value]
(and (= type :row) (= field :end))
[:row-span (max 1 (- value row))])]
(st/emit! (dwsl/update-grid-cell-position (:id shape) (:id cell) {property value})))))
on-area-name-change
(mf/use-callback
(mf/deps (:id shape) (:id cell))
(fn [event]
(let [value (dom/get-value (dom/get-target event))]
(if (= value "")
(st/emit! (dwsl/update-grid-cell (:id shape) (:id cell) {:area-name nil}))
(st/emit! (dwsl/update-grid-cell (:id shape) (:id cell) {:area-name value}))))))
set-cell-mode
(mf/use-callback
(mf/deps (:id shape) (:id cell))
(fn [mode]
(let [props (cond-> {:mode mode}
(not= mode :area)
(assoc :area-name nil))]
(st/emit! (dwsl/update-grid-cell (:id shape) (:id cell) props)))))]
[:div.element-set
[:div.element-set-title
[:span "Grid Cell"]]
[:div.element-set-content.layout-grid-item-menu
[:div.layout-row
[:div.row-title.sizing "Position"]
[:div.position-wrapper
[:button.position-btn
{:on-click #(set-cell-mode :auto)
:class (dom/classnames :active (= :auto cell-mode))} "Auto"]
[:button.position-btn
{:on-click #(set-cell-mode :manual)
:class (dom/classnames :active (= :manual cell-mode))} "Manual"]
[:button.position-btn
{:on-click #(set-cell-mode :area)
:class (dom/classnames :active (= :area cell-mode))} "Area"]]]
[:div.manage-grid-columns
(when (= :auto cell-mode)
[:div.grid-auto
[:div.grid-columns-auto
[:span.icon i/layout-rows]
[:div.input-wrapper
[:> numeric-input
{:placeholder "--"
:on-click #(dom/select-target %)
:on-change (partial on-change :all :column)
:value column}]]]
[:div.grid-rows-auto
[:span.icon i/layout-columns]
[:div.input-wrapper
[:> numeric-input
{:placeholder "--"
:on-click #(dom/select-target %)
:on-change (partial on-change :all :row)
:value row}]]]])
(when (= :area cell-mode)
[:div.input-wrapper
[:input.input-text
{:key (dm/str "name-" (:id cell))
:id "grid-area-name"
:type "text"
:aria-label "grid-area-name"
:placeholder "--"
:default-value area-name
:auto-complete "off"
:on-change on-area-name-change}]])
(when (or (= :manual cell-mode) (= :area cell-mode))
[:div.grid-manual
[:div.grid-columns-auto
[:span.icon i/layout-rows]
[:div.input-wrapper
[:> numeric-input
{:placeholder "--"
:on-pointer-down #(dom/select-target %)
:on-change (partial on-change :start :column)
:value column}]
[:> numeric-input
{:placeholder "--"
:on-pointer-down #(dom/select-target %)
:on-change (partial on-change :end :column)
:value column-end}]]]
[:div.grid-rows-auto
[:span.icon i/layout-columns]
[:div.input-wrapper
[:> numeric-input
{:placeholder "--"
:on-pointer-down #(dom/select-target %)
:on-change (partial on-change :start :row)
:value row}]
[:> numeric-input
{:placeholder "--"
:on-pointer-down #(dom/select-target %)
:on-change (partial on-change :end :row)
:value row-end}]]]])]
[:div.layout-row
[:div.row-title "Align"]
[:div.btn-wrapper
[:& set-self-alignment {:is-col? false
:alignment align-self
:set-alignment set-alignment}]]]
[:div.layout-row
[:div.row-title "Justify"]
[:div.btn-wrapper
[:& set-self-alignment {:is-col? true
:alignment justify-self
:set-alignment set-justify-self}]]]]]))

View file

@ -9,8 +9,10 @@
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.math :as mth]
[app.common.types.shape.layout :as ctl]
[app.main.data.workspace :as udw]
[app.main.data.workspace.shape-layout :as dwsl]
[app.main.features :as features]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.components.numeric-input :refer [numeric-input]]
@ -124,17 +126,22 @@
:justify-items
(if is-col?
(case val
:stretch i/align-items-row-strech
:start i/grid-justify-content-column-start
:end i/grid-justify-content-column-end
:center i/grid-justify-content-column-center
:space-around i/grid-justify-content-column-around
:space-between i/grid-justify-content-column-between)
:space-between i/grid-justify-content-column-between
:space-evenly i/grid-justify-content-column-between)
(case val
:stretch i/align-items-column-strech
:start i/grid-justify-content-row-start
:end i/grid-justify-content-row-end
:center i/grid-justify-content-row-center
:space-around i/grid-justify-content-row-around
:space-between i/grid-justify-content-row-between))))
:space-between i/grid-justify-content-row-between
:space-evenly i/grid-justify-content-row-between))))
(mf/defc direction-btn
[{:keys [dir saved-dir set-direction icon?] :as props}]
@ -383,18 +390,21 @@
:alt "Grid edit mode"
:on-click #(toggle-edit-mode)
:style {:padding 0}}
"Edit grid"
i/grid-layout-mode]))
(mf/defc align-grid-row
[{:keys [is-col? align-items set-align] :as props}]
(let [type (if is-col? :column :row)]
[:div.align-items-style
(for [align [:start :center :end :stretch :baseline]]
(for [align [:start :center :end]]
[:button.align-start.tooltip
{:class (dom/classnames :active (= align-items align)
:tooltip-bottom-left (not= align :start)
:tooltip-bottom (= align :start))
:alt (dm/str "Align items " (d/name align))
:alt (if is-col?
(dm/str "justify-items: " (d/name align))
(dm/str "align-items: " (d/name align)))
:on-click #(set-align align type)
:key (dm/str "align-items" (d/name align))}
(get-layout-flex-icon :align-items align is-col?)])]))
@ -403,12 +413,14 @@
[{:keys [is-col? justify-items set-justify] :as props}]
(let [type (if is-col? :column :row)]
[:div.justify-content-style
(for [align [:start :center :end :space-around :space-between]]
(for [align [:start :center :end :space-around :space-between :stretch]]
[:button.align-start.tooltip
{:class (dom/classnames :active (= justify-items align)
:tooltip-bottom-left (not= align :start)
:tooltip-bottom (= align :start))
:alt (dm/str "Justify content " (d/name align))
:alt (if is-col?
(dm/str "align-content: " (d/name align))
(dm/str "justify-content: " (d/name align)))
:on-click #(set-justify align type)
:key (dm/str "justify-content" (d/name align))}
(get-layout-grid-icon :justify-items align is-col?)])]))
@ -441,12 +453,12 @@
:on-click toggle} generated-name]
[:button.add-column {:on-click #(do
(when-not expanded? (toggle))
(add-new-element type {:type :fixed :value 100}))} i/plus]]
(add-new-element type ctl/default-track-value))} i/plus]]
(when expanded?
[:div.columns-info-wrapper
(for [[index column] (d/enumerate column-values)]
[:div.column-info
[:div.column-info {:key (dm/str index "-" (name type) "-" column)}
[:div.direction-grid-icon
(if is-col?
i/layout-rows
@ -456,14 +468,15 @@
[:> numeric-input {:no-validate true
:value (:value column)
:on-change #(set-column-value type index %)
:placeholder "--"}]]
:placeholder "--"
:disabled (= :auto (:type column))}]]
[:div.grid-column-unit
[:& select
{:class "grid-column-unit-selector"
:default-value (:type column)
:options [{:value :flex :label "fr"}
{:value :auto :label "auto"}
{:value :fixed :label "px"}
:options [{:value :flex :label "FR"}
{:value :auto :label "AUTO"}
{:value :fixed :label "PX"}
{:value :percent :label "%"}]
:on-change #(set-column-type type index %)}]]
[:button.remove-grid-column
@ -471,8 +484,8 @@
i/minus]])])]))
(mf/defc layout-container-menu
{::mf/wrap [#(mf/memo' % (mf/check-props ["ids" "values" "type" "multiple"]))]}
[{:keys [ids _type values multiple] :as props}]
{::mf/wrap [#(mf/memo' % (mf/check-props ["ids" "values" "multiple"]))]}
[{:keys [ids values multiple] :as props}]
(let [open? (mf/use-state false)
;; Display
@ -489,12 +502,12 @@
(st/emit! (dwsl/remove-layout ids))
(reset! open? false))
_set-flex
set-flex
(fn []
(st/emit! (dwsl/remove-layout ids))
(on-add-layout :flex))
_set-grid
set-grid
(fn []
(st/emit! (dwsl/remove-layout ids))
(on-add-layout :grid))
@ -580,70 +593,30 @@
(st/emit! (dwsl/update-layout ids {:layout-justify-items value}))))
;; Justify grid
grid-justify-content-row (:layout-align-content values)
grid-justify-content-column (:layout-justify-content values)
grid-justify-content-row (:layout-justify-content values)
grid-justify-content-column (:layout-align-content values)
set-justify-grid
(mf/use-callback
(mf/deps ids)
(fn [value type]
(if (= type :row)
(st/emit! (dwsl/update-layout ids {:layout-align-content value}))
(st/emit! (dwsl/update-layout ids {:layout-justify-content value})))))
;;Grid columns
column-grid-values (:layout-grid-columns values)
grid-columns-open? (mf/use-state false)
toggle-columns-info (mf/use-callback
(fn [_]
(swap! grid-columns-open? not)))
; Grid rows / columns
rows-grid-values (:layout-grid-rows values)
grid-rows-open? (mf/use-state false)
toggle-rows-info
(mf/use-callback
(fn [_]
(swap! grid-rows-open? not)))
add-new-element
(mf/use-callback
(mf/deps ids)
(fn [type value]
(st/emit! (dwsl/add-layout-track ids type value))))
remove-element
(mf/use-callback
(mf/deps ids)
(fn [type index]
(st/emit! (dwsl/remove-layout-track ids type index))))
set-column-value
(mf/use-callback
(mf/deps ids)
(fn [type index value]
(st/emit! (dwsl/change-layout-track ids type index {:value value}))))
set-column-type
(mf/use-callback
(mf/deps ids)
(fn [type index track-type]
(st/emit! (dwsl/change-layout-track ids type index {:type track-type}))))]
(st/emit! (dwsl/update-layout ids {:layout-justify-content value}))
(st/emit! (dwsl/update-layout ids {:layout-align-content value})))))]
[:div.element-set
[:div.element-set-title
[:*
[:span "Layout"]
(if (and (not multiple) (:layout values))
[:div.title-actions
#_[:div.layout-btns
(when (features/active-feature? :grid-layout)
[:div.layout-btns
[:button {:on-click set-flex
:class (dom/classnames
:active (= :flex layout-type))} "Flex"]
[:button {:on-click set-grid
:class (dom/classnames
:active (= :grid layout-type))} "Grid"]]
:active (= :grid layout-type))} "Grid"]])
[:button.remove-layout {:on-click on-remove-layout} i/minus]]
[:button.add-page {:on-click #(on-add-layout :flex)} i/close])]]
@ -715,15 +688,15 @@
:dir dir
:saved-dir saved-grid-dir
:set-direction #(set-direction dir :grid)
:icon? false}])]]
:icon? true}])]]
(when (= 1 (count ids))
[:div.edit-mode
[:& grid-edit-mode {:id (first ids)}]])]]
[:div.layout-row
[:div.align-items-grid.row-title "Align"]
[:div.btn-wrapper.align-grid
[:div.align-items-grid.row-title "Items"]
[:div.btn-wrapper.align-grid-items
[:& align-grid-row {:is-col? false
:align-items align-items-row
:set-align set-align-grid}]
@ -733,43 +706,181 @@
:set-align set-align-grid}]]]
[:div.layout-row
[:div.jusfiy-content-grid.row-title "Justify"]
[:div.btn-wrapper.align-grid
[:div.jusfiy-content-grid.row-title "Content"]
[:div.btn-wrapper.align-grid-content
[:& justify-grid-row {:is-col? true
:justify-items grid-justify-content-column
:set-justify set-justify-grid}]
[:& justify-grid-row {:is-col? false
:justify-items grid-justify-content-row
:set-justify set-justify-grid}]]]
[:& grid-columns-row {:is-col? true
:expanded? @grid-columns-open?
:toggle toggle-columns-info
:column-values column-grid-values
:add-new-element add-new-element
:set-column-value set-column-value
:set-column-type set-column-type
:remove-element remove-element}]
[:& grid-columns-row {:is-col? false
:expanded? @grid-rows-open?
:toggle toggle-rows-info
:column-values rows-grid-values
:add-new-element add-new-element
:set-column-value set-column-value
:set-column-type set-column-type
:remove-element remove-element}]
[:& gap-section {:is-col? is-col?
:wrap-type wrap-type
:gap-selected? gap-selected?
:set-gap set-gap
:gap-value (:layout-gap values)}]
[:& padding-section {:values values
:on-change-style change-padding-type
:on-change on-padding-change}]]
:set-justify set-justify-grid}]]]]
;; Default if not grid or flex
nil)))]))
(mf/defc grid-layout-edition
{::mf/wrap [#(mf/memo' % (mf/check-props ["ids" "values"]))]}
[{:keys [ids values] :as props}]
(let [;; Gap
gap-selected? (mf/use-state :none)
saved-grid-dir (:layout-grid-dir values)
set-direction
(fn [dir]
(st/emit! (dwsl/update-layout ids {:layout-grid-dir dir})))
set-gap
(fn [gap-multiple? type val]
(if gap-multiple?
(st/emit! (dwsl/update-layout ids {:layout-gap {:row-gap val :column-gap val}}))
(st/emit! (dwsl/update-layout ids {:layout-gap {type val}}))))
;; Padding
change-padding-type
(fn [type]
(st/emit! (dwsl/update-layout ids {:layout-padding-type type})))
on-padding-change
(fn [type prop val]
(cond
(and (= type :simple) (= prop :p1))
(st/emit! (dwsl/update-layout ids {:layout-padding {:p1 val :p3 val}}))
(and (= type :simple) (= prop :p2))
(st/emit! (dwsl/update-layout ids {:layout-padding {:p2 val :p4 val}}))
:else
(st/emit! (dwsl/update-layout ids {:layout-padding {prop val}}))))
;; Align grid
align-items-row (:layout-align-items values)
align-items-column (:layout-justify-items values)
set-items-grid
(fn [value type]
(if (= type :row)
(st/emit! (dwsl/update-layout ids {:layout-align-items value}))
(st/emit! (dwsl/update-layout ids {:layout-justify-items value}))))
;; Justify grid
grid-justify-content-row (:layout-align-content values)
grid-justify-content-column (:layout-justify-content values)
set-content-grid
(mf/use-callback
(mf/deps ids)
(fn [value type]
(if (= type :row)
(st/emit! (dwsl/update-layout ids {:layout-align-content value}))
(st/emit! (dwsl/update-layout ids {:layout-justify-content value})))))
;;Grid columns
column-grid-values (:layout-grid-columns values)
grid-columns-open? (mf/use-state false)
toggle-columns-info (mf/use-callback
(fn [_]
(swap! grid-columns-open? not)))
;; Grid rows / columns
rows-grid-values (:layout-grid-rows values)
grid-rows-open? (mf/use-state false)
toggle-rows-info
(mf/use-callback
(fn [_]
(swap! grid-rows-open? not)))
add-new-element
(mf/use-callback
(mf/deps ids)
(fn [type value]
(st/emit! (dwsl/add-layout-track ids type value))))
remove-element
(mf/use-callback
(mf/deps ids)
(fn [type index]
(st/emit! (dwsl/remove-layout-track ids type index))))
set-column-value
(mf/use-callback
(mf/deps ids)
(fn [type index value]
(st/emit! (dwsl/change-layout-track ids type index {:value value}))))
set-column-type
(mf/use-callback
(mf/deps ids)
(fn [type index track-type]
(let [value (case track-type
:auto nil
:flex 1
:percent 20
:fixed 100)]
(st/emit! (dwsl/change-layout-track ids type index {:value value
:type track-type})))))]
[:div.element-set
[:div.element-set-title
[:span "Grid Layout"]]
[:div.element-set-content.layout-menu
[:div.layout-row
[:div.direction-wrap.row-title "Direction"]
[:div.btn-wrapper
[:div.direction
(for [dir [:row :column]]
[:& direction-btn {:key (d/name dir)
:dir dir
:saved-dir saved-grid-dir
:set-direction #(set-direction dir)
:icon? true}])]
(when (= 1 (count ids))
[:div.edit-mode
[:& grid-edit-mode {:id (first ids)}]])]]
[:div.layout-row
[:div.align-items-grid.row-title "Items"]
[:div.btn-wrapper.align-grid
[:& align-grid-row {:is-col? false
:align-items align-items-row
:set-align set-items-grid}]
[:& align-grid-row {:is-col? true
:align-items align-items-column
:set-align set-items-grid}]]]
[:div.layout-row
[:div.jusfiy-content-grid.row-title "Content"]
[:div.btn-wrapper.align-grid
[:& justify-grid-row {:is-col? true
:justify-items grid-justify-content-column
:set-justify set-content-grid}]
[:& justify-grid-row {:is-col? false
:justify-items grid-justify-content-row
:set-justify set-content-grid}]]]
[:& grid-columns-row {:is-col? true
:expanded? @grid-columns-open?
:toggle toggle-columns-info
:column-values column-grid-values
:add-new-element add-new-element
:set-column-value set-column-value
:set-column-type set-column-type
:remove-element remove-element}]
[:& grid-columns-row {:is-col? false
:expanded? @grid-rows-open?
:toggle toggle-rows-info
:column-values rows-grid-values
:add-new-element add-new-element
:set-column-value set-column-value
:set-column-type set-column-type
:remove-element remove-element}]
[:& gap-section {:gap-selected? gap-selected?
:set-gap set-gap
:gap-value (:layout-gap values)}]
[:& padding-section {:values values
:on-change-style change-padding-type
:on-change on-padding-change}]]]))

View file

@ -177,8 +177,8 @@
(get-layout-flex-icon :align-self align is-col?)])]))
(mf/defc layout-item-menu
{::mf/wrap [#(mf/memo' % (mf/check-props ["ids" "values" "type" "is-layout-child?"]))]}
[{:keys [ids values is-layout-child? is-layout-container?] :as props}]
{::mf/wrap [#(mf/memo' % (mf/check-props ["ids" "values" "type" "is-layout-child?" "is-grid-parent?" "is-flex-parent?"]))]}
[{:keys [ids values is-layout-child? is-layout-container? is-grid-parent? is-flex-parent?] :as props}]
(let [selection-parents-ref (mf/use-memo (mf/deps ids) #(refs/parents-by-ids ids))
selection-parents (mf/deref selection-parents-ref)
@ -188,11 +188,15 @@
(st/emit! (dwsl/update-layout-child ids {:layout-item-margin-type type})))
align-self (:layout-item-align-self values)
set-align-self (fn [value]
(if (= align-self value)
(st/emit! (dwsl/update-layout-child ids {:layout-item-align-self nil}))
(st/emit! (dwsl/update-layout-child ids {:layout-item-align-self value}))))
set-align-self
(mf/use-callback
(mf/deps ids align-self)
(fn [value]
(if (= align-self value)
(st/emit! (dwsl/update-layout-child ids {:layout-item-align-self nil}))
(st/emit! (dwsl/update-layout-child ids {:layout-item-align-self value})))))
is-absolute? (:layout-item-absolute values)
is-col? (every? ctl/col? selection-parents)
@ -231,9 +235,18 @@
[:div.element-set
[:div.element-set-title
[:span (if (and is-layout-container? (not is-layout-child?))
[:span (cond
(and is-layout-container? (not is-layout-child?))
"Flex board"
"Flex element")]]
is-flex-parent?
"Flex element"
is-grid-parent?
"Grid element"
:else
"Layout element")]]
[:div.element-set-content.layout-item-menu
(when is-layout-child?
@ -272,7 +285,7 @@
:layout-item-h-sizing (or (:layout-item-h-sizing values) :fix)
:on-change-behavior on-change-behavior}]]
(when is-layout-child?
(when (and is-layout-child? is-flex-parent?)
[:div.layout-row
[:div.row-title "Align"]
[:div.btn-wrapper

View file

@ -19,6 +19,7 @@
[app.main.store :as st]
[app.main.ui.components.dropdown :refer [dropdown]]
[app.main.ui.components.numeric-input :refer [numeric-input]]
[app.main.ui.hooks :as hooks]
[app.main.ui.icons :as i]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
@ -80,6 +81,8 @@
[shape])
frames (map #(deref (refs/object-by-id (:frame-id %))) old-shapes)
ids (hooks/use-equal-memo ids)
selection-parents-ref (mf/use-memo (mf/deps ids) #(refs/parents-by-ids ids))
selection-parents (mf/deref selection-parents-ref)

View file

@ -8,9 +8,11 @@
(:require
[app.common.types.shape.layout :as ctl]
[app.main.refs :as refs]
[app.main.ui.hooks :as hooks]
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]]
[app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]]
[app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]]
[app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell]
[app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]]
[app.main.ui.workspace.sidebar.options.menus.layout-container :refer [layout-container-flex-attrs layout-container-menu]]
[app.main.ui.workspace.sidebar.options.menus.layout-item :refer [layout-item-attrs layout-item-menu]]
@ -30,26 +32,43 @@
layout-item-values (select-keys shape layout-item-attrs)
layout-container-values (select-keys shape layout-container-flex-attrs)
is-flex-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-flex-layout-child? ids))
is-flex-layout-child? (mf/deref is-flex-layout-child-ref)
is-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-layout-child? ids))
is-layout-child? (mf/deref is-layout-child-ref)
is-layout-child-absolute? (ctl/layout-absolute? shape)]
is-flex-parent-ref (mf/use-memo (mf/deps ids) #(refs/flex-layout-child? ids))
is-flex-parent? (mf/deref is-flex-parent-ref)
is-grid-parent-ref (mf/use-memo (mf/deps ids) #(refs/grid-layout-child? ids))
is-grid-parent? (mf/deref is-grid-parent-ref)
is-layout-child-absolute? (ctl/layout-absolute? shape)
ids (hooks/use-equal-memo ids)
parents-by-ids-ref (mf/use-memo (mf/deps ids) #(refs/parents-by-ids ids))
parents (mf/deref parents-by-ids-ref)]
[:*
[:& measures-menu {:ids ids
:type type
:values measure-values
:shape shape}]
[:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values :multiple false}]
(when (and (= (count ids) 1) is-layout-child? is-grid-parent?)
[:& grid-cell/options
{:shape (first parents)
:cell (ctl/get-cell-by-shape-id (first parents) (first ids))}])
(when is-flex-layout-child?
(when is-layout-child?
[:& layout-item-menu
{:ids ids
:type type
:values layout-item-values
:is-layout-child? true
:is-flex-parent? is-flex-parent?
:is-grid-parent? is-grid-parent?
:shape shape}])
(when (or (not is-flex-layout-child?) is-layout-child-absolute?)
(when (or (not is-layout-child?) is-layout-child-absolute?)
[:& constraints-menu {:ids ids
:values constraint-values}])
[:& layer-menu {:ids ids

View file

@ -8,9 +8,11 @@
(:require
[app.common.types.shape.layout :as ctl]
[app.main.refs :as refs]
[app.main.ui.hooks :as hooks]
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]]
[app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]]
[app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]]
[app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell]
[app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]]
[app.main.ui.workspace.sidebar.options.menus.layout-container :refer [layout-container-flex-attrs layout-container-menu]]
[app.main.ui.workspace.sidebar.options.menus.layout-item :refer [layout-item-attrs layout-item-menu]]
@ -32,24 +34,42 @@
layout-item-values (select-keys shape layout-item-attrs)
layout-container-values (select-keys shape layout-container-flex-attrs)
is-flex-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-flex-layout-child? ids))
is-flex-layout-child? (mf/deref is-flex-layout-child-ref)
is-layout-child-absolute? (ctl/layout-absolute? shape)]
is-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-layout-child? ids))
is-layout-child? (mf/deref is-layout-child-ref)
is-flex-parent-ref (mf/use-memo (mf/deps ids) #(refs/flex-layout-child? ids))
is-flex-parent? (mf/deref is-flex-parent-ref)
is-grid-parent-ref (mf/use-memo (mf/deps ids) #(refs/grid-layout-child? ids))
is-grid-parent? (mf/deref is-grid-parent-ref)
is-layout-child-absolute? (ctl/layout-absolute? shape)
ids (hooks/use-equal-memo ids)
parents-by-ids-ref (mf/use-memo (mf/deps ids) #(refs/parents-by-ids ids))
parents (mf/deref parents-by-ids-ref)]
[:*
[:& measures-menu {:ids ids
:type type
:values measure-values
:shape shape}]
[:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values :multiple false}]
(when is-flex-layout-child?
(when (and (= (count ids) 1) is-layout-child? is-grid-parent?)
[:& grid-cell/options
{:shape (first parents)
:cell (ctl/get-cell-by-shape-id (first parents) (first ids))}])
(when is-layout-child?
[:& layout-item-menu {:ids ids
:type type
:values layout-item-values
:is-layout-child? true
:is-layout-container? false
:is-flex-parent? is-flex-parent?
:is-grid-parent? is-grid-parent?
:shape shape}])
(when (or (not is-flex-layout-child?) is-layout-child-absolute?)
(when (or (not is-layout-child?) is-layout-child-absolute?)
[:& constraints-menu {:ids ids
:values constraint-values}])
[:& layer-menu {:ids ids

View file

@ -9,12 +9,14 @@
[app.common.data :as d]
[app.common.types.shape.layout :as ctl]
[app.main.refs :as refs]
[app.main.ui.hooks :as hooks]
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]]
[app.main.ui.workspace.sidebar.options.menus.color-selection :refer [color-selection-menu]]
[app.main.ui.workspace.sidebar.options.menus.component :refer [component-attrs component-menu]]
[app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]]
[app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs-shape fill-menu]]
[app.main.ui.workspace.sidebar.options.menus.frame-grid :refer [frame-grid]]
[app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell]
[app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]]
[app.main.ui.workspace.sidebar.options.menus.layout-container :refer [layout-container-flex-attrs layout-container-menu]]
[app.main.ui.workspace.sidebar.options.menus.layout-item :refer [layout-item-attrs layout-item-menu]]
@ -36,10 +38,23 @@
layout-item-values (select-keys shape layout-item-attrs)
[comp-ids comp-values] [[(:id shape)] (select-keys shape component-attrs)]
is-flex-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-flex-layout-child? ids))
is-flex-layout-child? (mf/deref is-flex-layout-child-ref)
is-flex-layout-container? (ctl/flex-layout? shape)
is-layout-child-absolute? (ctl/layout-absolute? shape)]
ids (hooks/use-equal-memo ids)
is-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-layout-child? ids))
is-layout-child? (mf/deref is-layout-child-ref)
is-flex-parent-ref (mf/use-memo (mf/deps ids) #(refs/flex-layout-child? ids))
is-flex-parent? (mf/deref is-flex-parent-ref)
is-grid-parent-ref (mf/use-memo (mf/deps ids) #(refs/grid-layout-child? ids))
is-grid-parent? (mf/deref is-grid-parent-ref)
is-layout-container? (ctl/any-layout? shape)
is-layout-child-absolute? (ctl/layout-absolute? shape)
ids (hooks/use-equal-memo ids)
parents-by-ids-ref (mf/use-memo (mf/deps ids) #(refs/parents-by-ids ids))
parents (mf/deref parents-by-ids-ref)]
[:*
[:& measures-menu {:ids [(:id shape)]
:values measure-values
@ -48,18 +63,25 @@
[:& component-menu {:ids comp-ids
:values comp-values
:shape shape}]
(when (or (not is-flex-layout-child?) is-layout-child-absolute?)
(when (or (not is-layout-child?) is-layout-child-absolute?)
[:& constraints-menu {:ids ids
:values constraint-values}])
[:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values :multiple false}]
(when (or is-flex-layout-child? is-flex-layout-container?)
(when (and (= (count ids) 1) is-layout-child? is-grid-parent?)
[:& grid-cell/options
{:shape (first parents)
:cell (ctl/get-cell-by-shape-id (first parents) (first ids))}])
(when (or is-layout-child? is-layout-container?)
[:& layout-item-menu
{:ids ids
:type type
:values layout-item-values
:is-layout-child? is-flex-layout-child?
:is-layout-container? is-flex-layout-container?
:is-flex-parent? is-flex-parent?
:is-grid-parent? is-grid-parent?
:is-layout-child? is-layout-child?
:is-layout-container? is-layout-container?
:shape shape}])
[:& layer-menu {:ids ids

View file

@ -1,171 +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.workspace.sidebar.options.shapes.grid-cell
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.main.ui.components.numeric-input :refer [numeric-input]]
[app.main.ui.icons :as i]
[app.main.ui.workspace.sidebar.options.menus.layout-container :as lyc]
[app.util.dom :as dom]
[rumext.v2 :as mf]))
(mf/defc set-self-alignment
[{:keys [is-col? alignment set-alignment] :as props}]
(let [dir-v [:auto :start :center :end :stretch #_:baseline]]
[:div.align-self-style
(for [align dir-v]
[:button.align-self.tooltip.tooltip-bottom
{:class (dom/classnames :active (= alignment align)
:tooltip-bottom-left (not= align :start)
:tooltip-bottom (= align :start))
:alt (dm/str "Align self " (d/name align)) ;; TODO fix this tooltip
:on-click #(set-alignment align)
:key (str "align-self" align)}
(lyc/get-layout-flex-icon :align-self align is-col?)])]))
(mf/defc options
{::mf/wrap [mf/memo]}
[{:keys [_shape row column] :as props}]
(let [position-mode (mf/use-state :auto) ;; TODO this should come from shape
set-position-mode (fn [mode]
(reset! position-mode mode))
align-self (mf/use-state :auto) ;; TODO this should come from shape
justify-alignment (mf/use-state :auto) ;; TODO this should come from shape
set-alignment (fn [value]
(reset! align-self value)
#_(if (= align-self value)
(st/emit! (dwsl/update-layout-child ids {:layout-item-align-self nil}))
(st/emit! (dwsl/update-layout-child ids {:layout-item-align-self value}))))
set-justify-self (fn [value]
(reset! justify-alignment value)
#_(if (= align-self value)
(st/emit! (dwsl/update-layout-child ids {:layout-item-align-self nil}))
(st/emit! (dwsl/update-layout-child ids {:layout-item-align-self value}))))
column-start column
column-end (inc column)
row-start row
row-end (inc row)
on-change
(fn [_side _orientation _value]
;; TODO
#_(if (= orientation :column)
(case side
:all ((reset! column-start value)
(reset! column-end value))
:start (reset! column-start value)
:end (reset! column-end value))
(case side
:all ((reset! row-start value)
(reset! row-end value))
:start (reset! row-start value)
:end (reset! row-end value))))
area-name (mf/use-state "header") ;; TODO this should come from shape
on-area-name-change (fn [value]
(reset! area-name value))
on-key-press (fn [_event])]
[:div.element-set
[:div.element-set-title
[:span "Grid Cell"]]
[:div.element-set-content.layout-grid-item-menu
[:div.layout-row
[:div.row-title.sizing "Position"]
[:div.position-wrapper
[:button.position-btn
{:on-click #(set-position-mode :auto)
:class (dom/classnames :active (= :auto @position-mode))} "Auto"]
[:button.position-btn
{:on-click #(set-position-mode :manual)
:class (dom/classnames :active (= :manual @position-mode))} "Manual"]
[:button.position-btn
{:on-click #(set-position-mode :area)
:class (dom/classnames :active (= :area @position-mode))} "Area"]]]
[:div.manage-grid-columns
(when (= :auto @position-mode)
[:div.grid-auto
[:div.grid-columns-auto
[:spam.icon i/layout-rows]
[:div.input-wrapper
[:> numeric-input
{:placeholder "--"
:on-click #(dom/select-target %)
:on-change (partial on-change :all :column) ;; TODO cambiar este on-change y el value
:value column-start}]]]
[:div.grid-rows-auto
[:spam.icon i/layout-columns]
[:div.input-wrapper
[:> numeric-input
{:placeholder "--"
:on-click #(dom/select-target %)
:on-change (partial on-change :all :row) ;; TODO cambiar este on-change y el value
:value row-start}]]]])
(when (= :area @position-mode)
[:div.input-wrapper
[:input.input-text
{:key "grid-area-name"
:id "grid-area-name"
:type "text"
:aria-label "grid-area-name"
:placeholder "--"
:default-value @area-name
:auto-complete "off"
:on-change on-area-name-change
:on-key-press on-key-press}]])
(when (or (= :manual @position-mode) (= :area @position-mode))
[:div.grid-manual
[:div.grid-columns-auto
[:spam.icon i/layout-rows]
[:div.input-wrapper
[:> numeric-input
{:placeholder "--"
:on-click #(dom/select-target %)
:on-change (partial on-change :start :column)
:value column-start}]
[:> numeric-input
{:placeholder "--"
:on-click #(dom/select-target %)
:on-change (partial on-change :end :column)
:value column-end}]]]
[:div.grid-rows-auto
[:spam.icon i/layout-columns]
[:div.input-wrapper
[:> numeric-input
{:placeholder "--"
:on-click #(dom/select-target %)
:on-change (partial on-change :start :row)
:value row-start}]
[:> numeric-input
{:placeholder "--"
:on-click #(dom/select-target %)
:on-change (partial on-change :end :row)
:value row-end}]]]])]
[:div.layout-row
[:div.row-title "Align"]
[:div.btn-wrapper
[:& set-self-alignment {:is-col? false
:alignment @align-self
:set-alignment set-alignment}]]]
[:div.layout-row
[:div.row-title "Justify"]
[:div.btn-wrapper
[:& set-self-alignment {:is-col? true
:alignment @justify-alignment
:set-alignment set-justify-self}]]]]]))

View file

@ -9,11 +9,13 @@
[app.common.data :as d]
[app.common.types.shape.layout :as ctl]
[app.main.refs :as refs]
[app.main.ui.hooks :as hooks]
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]]
[app.main.ui.workspace.sidebar.options.menus.color-selection :refer [color-selection-menu]]
[app.main.ui.workspace.sidebar.options.menus.component :refer [component-attrs component-menu]]
[app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraints-menu]]
[app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-menu]]
[app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell]
[app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-menu]]
[app.main.ui.workspace.sidebar.options.menus.layout-container :refer [layout-container-flex-attrs layout-container-menu]]
[app.main.ui.workspace.sidebar.options.menus.layout-item :refer [layout-item-menu]]
@ -36,10 +38,21 @@
file-id (unchecked-get props "file-id")
layout-container-values (select-keys shape layout-container-flex-attrs)
ids [(:id shape)]
is-flex-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-flex-layout-child? ids))
is-flex-layout-child? (mf/deref is-flex-layout-child-ref)
is-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-layout-child? ids))
is-layout-child? (mf/deref is-layout-child-ref)
is-flex-parent-ref (mf/use-memo (mf/deps ids) #(refs/flex-layout-child? ids))
is-flex-parent? (mf/deref is-flex-parent-ref)
is-grid-parent-ref (mf/use-memo (mf/deps ids) #(refs/grid-layout-child? ids))
is-grid-parent? (mf/deref is-grid-parent-ref)
is-layout-child-absolute? (ctl/layout-absolute? shape)
ids (hooks/use-equal-memo ids)
parents-by-ids-ref (mf/use-memo (mf/deps ids) #(refs/parents-by-ids ids))
parents (mf/deref parents-by-ids-ref)
type :group
[measure-ids measure-values] (get-attrs [shape] objects :measure)
[layer-ids layer-values] (get-attrs [shape] objects :layer)
@ -58,15 +71,22 @@
[:& component-menu {:ids comp-ids :values comp-values :shape shape}] ;;remove this in components-v2
[:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values :multiple false}]
(when is-flex-layout-child?
(when (and (= (count ids) 1) is-layout-child? is-grid-parent?)
[:& grid-cell/options
{:shape (first parents)
:cell (ctl/get-cell-by-shape-id (first parents) (first ids))}])
(when is-layout-child?
[:& layout-item-menu
{:type type
:ids layout-item-ids
:is-layout-child? true
:is-layout-container? false
:is-flex-parent? is-flex-parent?
:is-grid-parent? is-grid-parent?
:values layout-item-values}])
(when (or (not is-flex-layout-child?) is-layout-child-absolute?)
(when (or (not is-layout-child?) is-layout-child-absolute?)
[:& constraints-menu {:ids constraint-ids :values constraint-values}])
[:& layer-menu {:type type :ids layer-ids :values layer-values}]

View file

@ -8,9 +8,11 @@
(:require
[app.common.types.shape.layout :as ctl]
[app.main.refs :as refs]
[app.main.ui.hooks :as hooks]
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]]
[app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]]
[app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]]
[app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell]
[app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]]
[app.main.ui.workspace.sidebar.options.menus.layout-container :refer [layout-container-flex-attrs layout-container-menu]]
[app.main.ui.workspace.sidebar.options.menus.layout-item :refer [layout-item-attrs layout-item-menu]]
@ -32,25 +34,43 @@
layout-item-values (select-keys shape layout-item-attrs)
layout-container-values (select-keys shape layout-container-flex-attrs)
is-flex-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-flex-layout-child? ids))
is-flex-layout-child? (mf/deref is-flex-layout-child-ref)
is-layout-child-absolute? (ctl/layout-absolute? shape)]
is-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-layout-child? ids))
is-layout-child? (mf/deref is-layout-child-ref)
is-flex-parent-ref (mf/use-memo (mf/deps ids) #(refs/flex-layout-child? ids))
is-flex-parent? (mf/deref is-flex-parent-ref)
is-grid-parent-ref (mf/use-memo (mf/deps ids) #(refs/grid-layout-child? ids))
is-grid-parent? (mf/deref is-grid-parent-ref)
is-layout-child-absolute? (ctl/layout-absolute? shape)
ids (hooks/use-equal-memo ids)
parents-by-ids-ref (mf/use-memo (mf/deps ids) #(refs/parents-by-ids ids))
parents (mf/deref parents-by-ids-ref)]
[:*
[:& measures-menu {:ids ids
:type type
:values measure-values
:shape shape}]
[:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values :multiple false}]
(when (and (= (count ids) 1) is-layout-child? is-grid-parent?)
[:& grid-cell/options
{:shape (first parents)
:cell (ctl/get-cell-by-shape-id (first parents) (first ids))}])
(when is-flex-layout-child?
(when is-layout-child?
[:& layout-item-menu
{:ids ids
:type type
:values layout-item-values
:is-layout-child? true
:is-flex-parent? is-flex-parent?
:is-grid-parent? is-grid-parent?
:shape shape}])
(when (or (not is-flex-layout-child?) is-layout-child-absolute?)
(when (or (not is-layout-child?) is-layout-child-absolute?)
[:& constraints-menu {:ids ids
:values constraint-values}])

View file

@ -294,15 +294,21 @@
all-types (into #{} (map :type shapes))
ids (->> shapes (map :id))
is-flex-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-flex-layout-child? ids))
is-flex-layout-child? (mf/deref is-flex-layout-child-ref)
is-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-layout-child? ids))
is-layout-child? (mf/deref is-layout-child-ref)
is-flex-parent-ref (mf/use-memo (mf/deps ids) #(refs/flex-layout-child? ids))
is-flex-parent? (mf/deref is-flex-parent-ref)
is-grid-parent-ref (mf/use-memo (mf/deps ids) #(refs/grid-layout-child? ids))
is-grid-parent? (mf/deref is-grid-parent-ref)
has-text? (contains? all-types :text)
has-flex-layout-container? (->> shapes (some ctl/flex-layout?))
all-flex-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/all-flex-layout-child? ids))
all-flex-layout-child? (mf/deref all-flex-layout-child-ref)
all-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/all-layout-child? ids))
all-layout-child? (mf/deref all-layout-child-ref)
all-flex-layout-container? (->> shapes (every? ctl/flex-layout?))
@ -342,15 +348,17 @@
[:& layout-container-menu {:type type :ids layout-container-ids :values layout-container-values :multiple true}]
(when (or is-flex-layout-child? has-flex-layout-container?)
(when (or is-layout-child? has-flex-layout-container?)
[:& layout-item-menu
{:type type
:ids layout-item-ids
:is-layout-child? all-flex-layout-child?
:is-layout-child? all-layout-child?
:is-layout-container? all-flex-layout-container?
:is-flex-parent? is-flex-parent?
:is-grid-parent? is-grid-parent?
:values layout-item-values}])
(when-not (or (empty? constraint-ids) is-flex-layout-child?)
(when-not (or (empty? constraint-ids) is-layout-child?)
[:& constraints-menu {:ids constraint-ids :values constraint-values}])
(when-not (empty? layer-ids)

View file

@ -8,9 +8,11 @@
(:require
[app.common.types.shape.layout :as ctl]
[app.main.refs :as refs]
[app.main.ui.hooks :as hooks]
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]]
[app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]]
[app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]]
[app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell]
[app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]]
[app.main.ui.workspace.sidebar.options.menus.layout-container :refer [layout-container-flex-attrs layout-container-menu]]
[app.main.ui.workspace.sidebar.options.menus.layout-item :refer [layout-item-attrs layout-item-menu]]
@ -32,9 +34,20 @@
layout-item-values (select-keys shape layout-item-attrs)
layout-container-values (select-keys shape layout-container-flex-attrs)
is-flex-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-flex-layout-child? ids))
is-flex-layout-child? (mf/deref is-flex-layout-child-ref)
is-layout-child-absolute? (ctl/layout-absolute? shape)]
is-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-layout-child? ids))
is-layout-child? (mf/deref is-layout-child-ref)
is-flex-parent-ref (mf/use-memo (mf/deps ids) #(refs/flex-layout-child? ids))
is-flex-parent? (mf/deref is-flex-parent-ref)
is-grid-parent-ref (mf/use-memo (mf/deps ids) #(refs/grid-layout-child? ids))
is-grid-parent? (mf/deref is-grid-parent-ref)
is-layout-child-absolute? (ctl/layout-absolute? shape)
ids (hooks/use-equal-memo ids)
parents-by-ids-ref (mf/use-memo (mf/deps ids) #(refs/parents-by-ids ids))
parents (mf/deref parents-by-ids-ref)]
[:*
[:& measures-menu {:ids ids
:type type
@ -42,14 +55,21 @@
:shape shape}]
[:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values :multiple false}]
(when is-flex-layout-child?
(when (and (= (count ids) 1) is-layout-child? is-grid-parent?)
[:& grid-cell/options
{:shape (first parents)
:cell (ctl/get-cell-by-shape-id (first parents) (first ids))}])
(when is-layout-child?
[:& layout-item-menu {:ids ids
:type type
:values layout-item-values
:is-layout-child? true
:is-layout-container? false
:is-flex-parent? is-flex-parent?
:is-grid-parent? is-grid-parent?
:shape shape}])
(when (or (not is-flex-layout-child?) is-layout-child-absolute?)
(when (or (not is-layout-child?) is-layout-child-absolute?)
[:& constraints-menu {:ids ids
:values constraint-values}])
[:& layer-menu {:ids ids

View file

@ -8,9 +8,11 @@
(:require
[app.common.types.shape.layout :as ctl]
[app.main.refs :as refs]
[app.main.ui.hooks :as hooks]
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]]
[app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]]
[app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]]
[app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell]
[app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]]
[app.main.ui.workspace.sidebar.options.menus.layout-container :refer [layout-container-flex-attrs layout-container-menu]]
[app.main.ui.workspace.sidebar.options.menus.layout-item :refer [layout-item-attrs layout-item-menu]]
@ -32,24 +34,43 @@
stroke-values (select-keys shape stroke-attrs)
layout-item-values (select-keys shape layout-item-attrs)
layout-container-values (select-keys shape layout-container-flex-attrs)
is-flex-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-flex-layout-child? ids))
is-flex-layout-child? (mf/deref is-flex-layout-child-ref)
is-layout-child-absolute? (ctl/layout-absolute? shape)]
is-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-layout-child? ids))
is-layout-child? (mf/deref is-layout-child-ref)
is-flex-parent-ref (mf/use-memo (mf/deps ids) #(refs/flex-layout-child? ids))
is-flex-parent? (mf/deref is-flex-parent-ref)
is-grid-parent-ref (mf/use-memo (mf/deps ids) #(refs/grid-layout-child? ids))
is-grid-parent? (mf/deref is-grid-parent-ref)
is-layout-child-absolute? (ctl/layout-absolute? shape)
ids (hooks/use-equal-memo ids)
parents-by-ids-ref (mf/use-memo (mf/deps ids) #(refs/parents-by-ids ids))
parents (mf/deref parents-by-ids-ref)]
[:*
[:& measures-menu {:ids ids
:type type
:values measure-values
:shape shape}]
[:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values :multiple false}]
(when is-flex-layout-child?
(when (and (= (count ids) 1) is-layout-child? is-grid-parent?)
[:& grid-cell/options
{:shape (first parents)
:cell (ctl/get-cell-by-shape-id (first parents) (first ids))}])
(when is-layout-child?
[:& layout-item-menu
{:ids ids
:type type
:values layout-item-values
:is-layout-child? true
:is-flex-parent? is-flex-parent?
:is-grid-parent? is-grid-parent?
:shape shape}])
(when (or (not is-flex-layout-child?) is-layout-child-absolute?)
(when (or (not is-layout-child?) is-layout-child-absolute?)
[:& constraints-menu {:ids ids
:values constraint-values}])

View file

@ -10,9 +10,11 @@
[app.common.data :as d]
[app.common.types.shape.layout :as ctl]
[app.main.refs :as refs]
[app.main.ui.hooks :as hooks]
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]]
[app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]]
[app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]]
[app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell]
[app.main.ui.workspace.sidebar.options.menus.layout-container :refer [layout-container-flex-attrs layout-container-menu]]
[app.main.ui.workspace.sidebar.options.menus.layout-item :refer [layout-item-attrs layout-item-menu]]
[app.main.ui.workspace.sidebar.options.menus.measures :refer [measure-attrs measures-menu]]
@ -106,9 +108,20 @@
layout-item-values (select-keys shape layout-item-attrs)
layout-container-values (select-keys shape layout-container-flex-attrs)
is-flex-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-flex-layout-child? ids))
is-flex-layout-child? (mf/deref is-flex-layout-child-ref)
is-layout-child-absolute? (ctl/layout-absolute? shape)]
is-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-layout-child? ids))
is-layout-child? (mf/deref is-layout-child-ref)
is-flex-parent-ref (mf/use-memo (mf/deps ids) #(refs/flex-layout-child? ids))
is-flex-parent? (mf/deref is-flex-parent-ref)
is-grid-parent-ref (mf/use-memo (mf/deps ids) #(refs/grid-layout-child? ids))
is-grid-parent? (mf/deref is-grid-parent-ref)
is-layout-child-absolute? (ctl/layout-absolute? shape)
ids (hooks/use-equal-memo ids)
parents-by-ids-ref (mf/use-memo (mf/deps ids) #(refs/parents-by-ids ids))
parents (mf/deref parents-by-ids-ref)]
(when (contains? svg-elements tag)
[:*
@ -118,15 +131,22 @@
:shape shape}]
[:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values :multiple false}]
(when is-flex-layout-child?
(when (and (= (count ids) 1) is-layout-child? is-grid-parent?)
[:& grid-cell/options
{:shape (first parents)
:cell (ctl/get-cell-by-shape-id (first parents) (first ids))}])
(when is-layout-child?
[:& layout-item-menu
{:ids ids
:type type
:values layout-item-values
:is-layout-child? true
:is-flex-parent? is-flex-parent?
:is-grid-parent? is-grid-parent?
:shape shape}])
(when (or (not is-flex-layout-child?) is-layout-child-absolute?)
(when (or (not is-layout-child?) is-layout-child-absolute?)
[:& constraints-menu {:ids ids
:values constraint-values}])

View file

@ -10,10 +10,12 @@
[app.common.types.shape.layout :as ctl]
[app.main.data.workspace.texts :as dwt :refer [text-fill-attrs root-attrs paragraph-attrs text-attrs]]
[app.main.refs :as refs]
[app.main.ui.hooks :as hooks]
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]]
[app.main.ui.workspace.sidebar.options.menus.color-selection :refer [color-selection-menu]]
[app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]]
[app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-menu fill-attrs]]
[app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell]
[app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]]
[app.main.ui.workspace.sidebar.options.menus.layout-container :refer [layout-container-flex-attrs layout-container-menu]]
[app.main.ui.workspace.sidebar.options.menus.layout-item :refer [layout-item-attrs layout-item-menu]]
@ -28,10 +30,22 @@
(let [ids [(:id shape)]
type (:type shape)
is-flex-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-flex-layout-child? ids))
is-flex-layout-child? (mf/deref is-flex-layout-child-ref)
is-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-layout-child? ids))
is-layout-child? (mf/deref is-layout-child-ref)
is-flex-parent-ref (mf/use-memo (mf/deps ids) #(refs/flex-layout-child? ids))
is-flex-parent? (mf/deref is-flex-parent-ref)
is-grid-parent-ref (mf/use-memo (mf/deps ids) #(refs/grid-layout-child? ids))
is-grid-parent? (mf/deref is-grid-parent-ref)
layout-container-values (select-keys shape layout-container-flex-attrs)
is-layout-child-absolute? (ctl/layout-absolute? shape)
ids (hooks/use-equal-memo ids)
parents-by-ids-ref (mf/use-memo (mf/deps ids) #(refs/parents-by-ids ids))
parents (mf/deref parents-by-ids-ref)
state-map (mf/deref refs/workspace-editor-state)
shared-libs (mf/deref refs/workspace-libraries)
@ -76,15 +90,22 @@
:shape shape}]
[:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values :multiple false}]
(when is-flex-layout-child?
(when (and (= (count ids) 1) is-layout-child? is-grid-parent?)
[:& grid-cell/options
{:shape (first parents)
:cell (ctl/get-cell-by-shape-id (first parents) (first ids))}])
(when is-layout-child?
[:& layout-item-menu
{:ids ids
:type type
:values layout-item-values
:is-layout-child? true
:is-flex-parent? is-flex-parent?
:is-grid-parent? is-grid-parent?
:shape shape}])
(when (or (not is-flex-layout-child?) is-layout-child-absolute?)
(when (or (not is-layout-child?) is-layout-child-absolute?)
[:& constraints-menu
{:ids ids
:values (select-keys shape constraint-attrs)}])

View file

@ -11,6 +11,7 @@
[app.common.data.macros :as dm]
[app.common.geom.shapes :as gsh]
[app.common.pages.helpers :as cph]
[app.common.types.shape-tree :as ctt]
[app.common.types.shape.layout :as ctl]
[app.main.data.workspace.modifiers :as dwm]
[app.main.refs :as refs]
@ -159,7 +160,7 @@
create-comment? (= :comments drawing-tool)
drawing-path? (or (and edition (= :draw (get-in edit-path [edition :edit-mode])))
(and (some? drawing-obj) (= :path (:type drawing-obj))))
node-editing? (and edition (not= :text (get-in base-objects [edition :type])))
node-editing? (and edition (= :path (get-in base-objects [edition :type])))
text-editing? (and edition (= :text (get-in base-objects [edition :type])))
grid-editing? (and edition (ctl/grid-layout? base-objects edition))
@ -168,7 +169,7 @@
on-click (actions/on-click hover selected edition drawing-path? drawing-tool space? selrect z?)
on-context-menu (actions/on-context-menu hover hover-ids workspace-read-only?)
on-double-click (actions/on-double-click hover hover-ids drawing-path? base-objects edition drawing-tool z? workspace-read-only?)
on-double-click (actions/on-double-click hover hover-ids hover-top-frame-id drawing-path? base-objects edition drawing-tool z? workspace-read-only?)
on-drag-enter (actions/on-drag-enter)
on-drag-over (actions/on-drag-over)
on-drop (actions/on-drop file)
@ -203,6 +204,10 @@
show-pixel-grid? (and (contains? layout :show-pixel-grid)
(>= zoom 8))
show-text-editor? (and editing-shape (= :text (:type editing-shape)))
hover-grid? (and (some? @hover-top-frame-id)
(ctl/grid-layout? objects @hover-top-frame-id))
show-grid-editor? (and editing-shape (ctl/grid-layout? editing-shape))
show-presence? page-id
show-prototypes? (= options-mode :prototype)
@ -248,7 +253,9 @@
offset-y (if selecting-first-level-frame?
(:y (first selected-shapes))
(:y selected-frame))]
(:y selected-frame))
rule-area-size (/ rules/rule-area-size zoom)]
(hooks/setup-dom-events zoom disable-paste in-viewport? workspace-read-only?)
(hooks/setup-viewport-size vport viewport-ref)
@ -259,7 +266,7 @@
(hooks/setup-shortcuts node-editing? drawing-path? text-editing?)
(hooks/setup-active-frames base-objects hover-ids selected active-frames zoom transform vbox)
[:div.viewport
[:div.viewport {:style #js {"--zoom" zoom}}
[:div.viewport-overlays
;; The behaviour inside a foreign object is a bit different that in plain HTML so we wrap
;; inside a foreign object "dummy" so this awkward behaviour is take into account
@ -346,6 +353,14 @@
:on-pointer-move on-pointer-move
:on-pointer-up on-pointer-up}
[:defs
;; This clip is so the handlers are not over the rules
[:clipPath {:id "clip-handlers"}
[:rect {:x (+ (:x vbox) rule-area-size)
:y (+ (:y vbox) rule-area-size)
:width (max 0 (- (:width vbox) (* rule-area-size 2)))
:height (max 0 (- (:height vbox) (* rule-area-size 2)))}]]]
[:g {:style {:pointer-events (if disable-events? "none" "auto")}}
(when show-text-editor?
[:& editor/text-editor-svg {:shape editing-shape
@ -556,15 +571,6 @@
(when show-selection-handlers?
[:g.selection-handlers {:clipPath "url(#clip-handlers)"}
[:defs
(let [rule-area-size (/ rules/rule-area-size zoom)]
;; This clip is so the handlers are not over the rules
[:clipPath {:id "clip-handlers"}
[:rect {:x (+ (:x vbox) rule-area-size)
:y (+ (:y vbox) rule-area-size)
:width (max 0 (- (:width vbox) (* rule-area-size 2)))
:height (max 0 (- (:height vbox) (* rule-area-size 2)))}]])]
[:& selection/selection-handlers
{:selected selected
:shapes selected-shapes
@ -586,8 +592,24 @@
{:id (first selected)
:zoom zoom}])
(when show-grid-editor?
[:& grid-layout/editor
{:zoom zoom
:objects base-objects
:shape (get base-objects edition)}])]]]))
[:g.grid-layout-editor {:clipPath "url(#clip-handlers)"}
(when (or show-grid-editor? hover-grid?)
[:& grid-layout/editor
{:zoom zoom
:objects base-objects
:modifiers modifiers
:shape (or (get base-objects edition)
(get base-objects @hover-top-frame-id))
:view-only (not show-grid-editor?)}])
(for [frame (ctt/get-frames objects)]
(when (and (ctl/grid-layout? frame)
(empty? (:shapes frame))
(not= edition (:id frame))
(not= @hover-top-frame-id (:id frame)))
[:& grid-layout/editor
{:zoom zoom
:objects base-objects
:modifiers modifiers
:shape frame
:view-only true}]))]]]]))

View file

@ -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/.
;;
@ -6,9 +6,11 @@
(ns app.main.ui.workspace.viewport.actions
(:require
[app.common.data :as d]
[app.common.geom.point :as gpt]
[app.common.math :as mth]
[app.common.pages.helpers :as cph]
[app.common.types.shape.layout :as ctl]
[app.common.uuid :as uuid]
[app.config :as cfg]
[app.main.data.workspace :as dw]
@ -53,9 +55,9 @@
(.setPointerCapture editor (.-pointerId bevent))
(.setPointerCapture target (.-pointerId bevent))))
(when (or (dom/class? (dom/get-target bevent) "viewport-controls")
(dom/class? (dom/get-target bevent) "viewport-selrect"))
(dom/child? (dom/get-target bevent) (dom/query ".viewport-controls")))
(dom/stop-propagation bevent)
(when-not @z?
@ -79,7 +81,6 @@
(st/emit! (dw/start-zooming pt)))
(st/emit! (dw/start-panning))))
left-click?
(do
(st/emit! (ms/->MouseEvent :down ctrl? shift? alt? meta?))
@ -160,6 +161,7 @@
(fn [event]
(when (and (nil? selrect)
(or (dom/class? (dom/get-target event) "viewport-controls")
(dom/child? (dom/get-target event) (dom/query ".viewport-controls"))
(dom/class? (dom/get-target event) "viewport-selrect")))
(let [ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
@ -187,10 +189,10 @@
(st/emit! (dw/increase-zoom pt)))))))))
(defn on-double-click
[hover hover-ids drawing-path? objects edition drawing-tool z? workspace-read-only?]
[hover hover-ids hover-top-frame-id drawing-path? objects edition drawing-tool z? workspace-read-only?]
(mf/use-callback
(mf/deps @hover @hover-ids drawing-path? edition drawing-tool @z? workspace-read-only?)
(mf/deps @hover @hover-ids @hover-top-frame-id drawing-path? edition drawing-tool @z? workspace-read-only?)
(fn [event]
(dom/stop-propagation event)
(when-not @z?
@ -201,11 +203,16 @@
{:keys [id type] :as shape} (or @hover (get objects (first @hover-ids)))
editable? (contains? #{:text :rect :path :image :circle} type)]
editable? (contains? #{:text :rect :path :image :circle} type)
hover-shape (->> @hover-ids (filter (partial cph/is-child? objects id)) first)
selected-shape (get objects hover-shape)
grid-layout-id (->> @hover-ids reverse (d/seek (partial ctl/grid-layout? objects)))]
(st/emit! (ms/->MouseEvent :double-click ctrl? shift? alt? meta?))
;; Emit asynchronously so the double click to exit shapes won't break
;; Emit asynchronously so the double click to exit shapes won't break
(timers/schedule
(fn []
(when (and (not drawing-path?) shape)
@ -214,27 +221,26 @@
(st/emit! (dw/select-shape id)
(dw/start-editing-selected))
:else
(let [;; We only get inside childrens of the hovering shape
hover-ids (->> @hover-ids (filter (partial cph/is-child? objects id)))
selected (get objects (first hover-ids))]
(when (some? selected)
(reset! hover selected)
(st/emit! (dw/select-shape (:id selected))))))))))))))
(some? selected-shape)
(do (reset! hover selected-shape)
(st/emit! (dw/select-shape (:id selected-shape))))
(and (not selected-shape) (some? grid-layout-id))
(st/emit! (dw/start-edition-mode grid-layout-id)))))))))))
(defn on-context-menu
[hover hover-ids workspace-read-only?]
(mf/use-fn
(mf/deps @hover @hover-ids workspace-read-only?)
(fn [event]
(if workspace-read-only?
(dom/prevent-default event)
(dom/prevent-default event)
(when-not workspace-read-only?
(when (or (dom/class? (dom/get-target event) "viewport-controls")
(dom/class? (dom/get-target event) "viewport-selrect"))
(dom/prevent-default event)
(dom/child? (dom/get-target event) (dom/query ".viewport-controls"))
(dom/class? (dom/get-target event) "viewport-selrect")
workspace-read-only?)
(let [position (dom/get-client-position event)]
;; Delayed callback because we need to wait to the previous context menu to be closed
;; Delayed callback because we need to wait to the previous context menu to be closed
(timers/schedule
#(st/emit!
(if (some? @hover)

View file

@ -40,8 +40,19 @@
(let [children (->> (cph/get-immediate-children objects (:id shape))
(remove :hidden))
bounds (d/lazy-map (keys objects) #(dm/get-in objects [% :points]))
layout-bounds (gsl/layout-content-bounds bounds shape children)
layout-points (flatten (gsl/layout-content-points bounds shape children))]
layout-bounds
(cond (ctl/flex-layout? shape)
(gsl/layout-content-bounds bounds shape children)
(ctl/grid-layout? shape)
(gsg/layout-content-bounds bounds shape children))
layout-points
(cond (ctl/flex-layout? shape)
(flatten (gsl/layout-content-points bounds shape children))
(ctl/grid-layout? shape)
(flatten (gsg/layout-content-points bounds shape children)))]
[:g.debug-layout {:pointer-events "none"}
[:polygon {:points (->> layout-bounds (map #(dm/fmt "%, %" (:x %) (:y %))) (str/join " "))
:style {:stroke "red" :fill "none"}}]

View file

@ -0,0 +1 @@
{"grid-track-marker":"viewport_grid_layout_editor_grid-track-marker_HABEp","marker-shape":"viewport_grid_layout_editor_marker-shape_FZTUQ","marker-text":"viewport_grid_layout_editor_marker-text_5xM8J","grid-editor-label":"viewport_grid_layout_editor_grid-editor-label_2NbYe","grid-frame":"viewport_grid_layout_editor_grid-frame_CzMnU","grid-plus-button":"viewport_grid_layout_editor_grid-plus-button_brOge","grid-plus-shape":"viewport_grid_layout_editor_grid-plus-shape_jtOU9","grid-plus-icon":"viewport_grid_layout_editor_grid-plus-icon_Zolso","grid-cell-outline":"viewport_grid_layout_editor_grid-cell-outline_1-cRq","hover":"viewport_grid_layout_editor_hover_Rn-tv","selected":"viewport_grid_layout_editor_selected_nhyhL"}

View file

@ -0,0 +1,65 @@
.grid-track-marker {
.marker-shape {
fill: var(--color-distance);
fill-opacity: 0.3;
}
.marker-text {
fill: var(--color-distance);
font-size: calc(12px / var(--zoom));
font-family: worksans;
font-weight: 600;
}
}
.grid-editor-label {
position: absolute;
background: none;
width: 100%;
height: 100%;
text-align: center;
font-family: worksans;
color: var(--color-distance);
font-weight: 600;
margin: 0;
padding: 0;
border: 0;
font-size: calc(12px / var(--zoom));
&:focus {
outline: none;
border-bottom: calc(1px / var(--zoom)) solid var(--color-distance);
}
}
.grid-frame {
fill: #f6f6f6;
stroke: var(--color-distance);
stroke-width: calc(1 / var(--zoom));
}
.grid-plus-button {
cursor: pointer;
.grid-plus-shape {
fill: var(--color-distance);
stroke: var(--color-distance);
stroke-width: calc(1 / var(--zoom));
}
.grid-plus-icon {
fill: white;
}
}
.grid-cell-outline {
fill: transparent;
stroke: var(--color-distance);
stroke-linecap: round;
stroke-width: calc(2 / var(--zoom));
stroke-dasharray: 0 calc(8 / var(--zoom));
&.hover,
&.selected {
stroke-dasharray: initial;
}
}

View file

@ -206,7 +206,9 @@
(remove #(dm/get-in objects [% :blocked]))
(ctt/sort-z-index objects ids {:bottom-frames? mod?}))
grouped? (fn [id] (contains? #{:group :bool} (get-in objects [id :type])))
grouped? (fn [id]
(and (cph/group-shape? objects id)
(not (cph/mask-shape? objects id))))
selected-with-parents
(into #{} (mapcat #(cph/get-parent-ids objects %)) selected)
@ -354,5 +356,5 @@
(do (st/emit! (dsc/push-shortcuts ::path psc/shortcuts))
#(st/emit! (dsc/pop-shortcuts ::path)))
text-editing?
(do (st/emit! (dsc/push-shortcuts ::text tsc/shortcuts))
#(st/emit! (dsc/pop-shortcuts ::text)))))))
(do (st/emit! (dsc/push-shortcuts ::text tsc/shortcuts))
#(st/emit! (dsc/pop-shortcuts ::text)))))))

View file

@ -69,7 +69,8 @@
(> (:x cand) (:x cur)) cand
:else cur)))
(defn title-transform [{:keys [points] :as shape} zoom]
(defn title-transform
[{:keys [points] :as shape} zoom grid-edition?]
(let [leftmost (->> points (reduce left?))
topmost (->> points (remove #{leftmost}) (reduce top?))
rightmost (->> points (remove #{leftmost topmost}) (reduce right?))
@ -81,14 +82,18 @@
top-right-angle (gpt/angle top-right)
;; Choose the position that creates the less angle between left-side and top-side
[label-pos angle v-pos]
[label-pos angle h-pos v-pos]
(if (< (mth/abs left-top-angle) (mth/abs top-right-angle))
[leftmost left-top-angle (gpt/perpendicular left-top)]
[topmost top-right-angle (gpt/perpendicular top-right)])
[leftmost left-top-angle left-top (gpt/perpendicular left-top)]
[topmost top-right-angle top-right (gpt/perpendicular top-right)])
delta-x (if grid-edition? 40 0)
delta-y (if grid-edition? 50 10)
label-pos
(gpt/subtract label-pos (gpt/scale (gpt/unit v-pos) (/ 10 zoom)))]
(-> label-pos
(gpt/subtract (gpt/scale (gpt/unit v-pos) (/ delta-y zoom)))
(gpt/subtract (gpt/scale (gpt/unit h-pos) (/ delta-x zoom))))]
(dm/fmt "rotate(% %,%) scale(%, %) translate(%, %)"
;; rotate

View file

@ -12,8 +12,10 @@
[app.common.pages.helpers :as cph]
[app.common.types.container :as ctn]
[app.common.types.shape-tree :as ctt]
[app.common.types.shape.layout :as ctl]
[app.common.uuid :as uuid]
[app.main.data.workspace :as dw]
[app.main.data.workspace.grid-layout.editor :as dwge]
[app.main.data.workspace.interactions :as dwi]
[app.main.refs :as refs]
[app.main.store :as st]
@ -24,6 +26,7 @@
[app.main.ui.workspace.viewport.path-actions :refer [path-actions]]
[app.main.ui.workspace.viewport.utils :as vwu]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[app.util.timers :as ts]
[debug :refer [debug?]]
[rumext.v2 :as mf]))
@ -56,16 +59,34 @@
selected (mf/deref refs/selected-objects)
drawing (mf/deref refs/workspace-drawing)
drawing-obj (:object drawing)
shape (or drawing-obj (-> selected first))]
(when (or (and (= (count selected) 1)
(= (:id shape) edition)
(and (not (cph/text-shape? shape))
(not (cph/frame-shape? shape))))
(and (some? drawing-obj)
(cph/path-shape? drawing-obj)
(not= :curve (:tool drawing))))
shape (or drawing-obj (-> selected first))
single? (= (count selected) 1)
editing? (= (:id shape) edition)
draw-path? (and (some? drawing-obj)
(cph/path-shape? drawing-obj)
(not= :curve (:tool drawing)))
path-edition? (or (and single? editing?
(and (not (cph/text-shape? shape))
(not (cph/frame-shape? shape))))
draw-path?)
grid-edition? (and single? editing? (ctl/grid-layout? shape))]
(cond
path-edition?
[:div.viewport-actions
[:& path-actions {:shape shape}]])))
[:& path-actions {:shape shape}]]
grid-edition?
[:div.viewport-actions
[:div.grid-actions
[:div.grid-edit-title
(tr "workspace.layout_grid.editor.title") " " [:span.grid-edit-board-name (:name shape)]]
[:button.btn-secondary {:on-click #(st/emit! (dwge/locate-board (:id shape)))} "Locate"]
[:button.btn-primary {:on-click #(st/emit! dw/clear-edition-mode)} "Done"]
[:button.btn-icon-basic {:on-click #(st/emit! dw/clear-edition-mode)} i/close]]])))
(mf/defc cursor-tooltip
[{:keys [zoom tooltip] :as props}]
@ -97,7 +118,7 @@
(mf/defc frame-title
{::mf/wrap [mf/memo
#(mf/deferred % ts/raf)]}
[{:keys [frame selected? zoom show-artboard-names? show-id? on-frame-enter on-frame-leave on-frame-select]}]
[{:keys [frame selected? zoom show-artboard-names? show-id? on-frame-enter on-frame-leave on-frame-select grid-edition?]}]
(let [workspace-read-only? (mf/use-ctx ctx/workspace-read-only?)
;; Note that we don't use mf/deref to avoid a repaint dependency here
@ -114,8 +135,8 @@
(fn [bevent]
(let [event (.-nativeEvent bevent)]
(when (= 1 (.-which event))
(dom/prevent-default event)
(dom/stop-propagation event)
(dom/prevent-default bevent)
(dom/stop-propagation bevent)
(on-frame-select event (:id frame))))))
on-double-click
@ -146,13 +167,15 @@
(mf/deps (:id frame) on-frame-leave)
(fn [_]
(on-frame-leave (:id frame))))
text-pos-x (if (:use-for-thumbnail? frame) 15 0)]
text-pos-x (if (or (:use-for-thumbnail? frame) grid-edition?) 15 0)]
(when (not (:hidden frame))
[:g.frame-title {:id (dm/str "frame-title-" (:id frame))
:transform (vwu/title-transform frame zoom)
:data-edit-grid grid-edition?
:transform (vwu/title-transform frame zoom grid-edition?)
:pointer-events (when (:blocked frame) "none")}
(when (:use-for-thumbnail? frame)
(cond
(or (:use-for-thumbnail? frame) grid-edition?)
[:svg {:x 0
:y -9
:width 12
@ -160,7 +183,13 @@
:class "workspace-frame-icon"
:style {:fill color}
:visibility (if show-artboard-names? "visible" "hidden")}
[:use {:href "#icon-set-thumbnail"}]])
(cond
(:use-for-thumbnail? frame)
[:use {:href "#icon-set-thumbnail"}]
grid-edition?
[:use {:href "#icon-grid-layout-mode"}])])
[:text {:x text-pos-x
:y 0
:width (:width frame)
@ -195,7 +224,10 @@
(map (d/getf objects))
selected)
shapes)
focus (unchecked-get props "focus")]
focus (unchecked-get props "focus")
edition (mf/deref refs/selected-edition)
grid-edition? (ctl/grid-layout? objects edition)]
[:g.frame-titles
(for [{:keys [id parent-id] :as shape} shapes]
@ -211,7 +243,8 @@
:show-id? (debug? :shape-titles)
:on-frame-enter on-frame-enter
:on-frame-leave on-frame-leave
:on-frame-select on-frame-select}]))]))
:on-frame-select on-frame-select
:grid-edition? (and (= id edition) grid-edition?)}]))]))
(mf/defc frame-flow
[{:keys [flow frame selected? zoom on-frame-enter on-frame-leave on-frame-select]}]

View file

@ -6,343 +6,21 @@
(ns app.util.code-gen
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.pages.helpers :as cph]
[app.common.text :as txt]
[app.common.types.shape.layout :as ctl]
[app.main.ui.formats :as fmt]
[app.util.color :as uc]
[cuerdas.core :as str]))
[app.util.code-gen.markup-html :as html]
[app.util.code-gen.markup-svg :as svg]
[app.util.code-gen.style-css :as css]))
(defn shadow->css [shadow]
(let [{:keys [style offset-x offset-y blur spread]} shadow
css-color (uc/color->background (:color shadow))]
(dm/str
(if (= style :inner-shadow) "inset " "")
(str/fmt "%spx %spx %spx %spx %s" offset-x offset-y blur spread css-color))))
(defn generate-markup-code
[objects type shapes]
(let [generate-markup
(case type
"html" html/generate-markup
"svg" svg/generate-markup)]
(generate-markup objects shapes)))
(defn fill-color->background
[fill]
(uc/color->background {:color (:fill-color fill)
:opacity (:fill-opacity fill)
:gradient (:fill-color-gradient fill)}))
(defn format-fill-color [_ shape]
(let [fills (:fills shape)
first-fill (first fills)
colors (if (> (count fills) 1)
(map (fn [fill]
(let [color (fill-color->background fill)]
(if (some? (:fill-color-gradient fill))
color
(str/format "linear-gradient(%s,%s)" color color))))
(:fills shape))
[(fill-color->background first-fill)])]
(str/join ", " colors)))
(defn format-stroke [_ shape]
(let [first-stroke (first (:strokes shape))
width (:stroke-width first-stroke)
style (let [style (:stroke-style first-stroke)]
(when (keyword? style) (d/name style)))
color {:color (:stroke-color first-stroke)
:opacity (:stroke-opacity first-stroke)
:gradient (:stroke-color-gradient first-stroke)}]
(when-not (= :none (:stroke-style first-stroke))
(str/format "%spx %s %s" width style (uc/color->background color)))))
(defn format-position [_ shape]
(let [relative? (cph/frame-shape? shape)
absolute? (or (empty? (:flex-items shape))
(and (ctl/any-layout? (:parent shape)) (ctl/layout-absolute? shape)))]
(cond
absolute? "absolute"
relative? "relative"
;; static is default value in css
:else nil)))
(defn get-size
[type values]
(let [value (cond
(number? values) values
(string? values) values
(type values) (type values)
:else (type (:selrect values)))]
(if (= :width type)
(fmt/format-size :width value values)
(fmt/format-size :heigth value values))))
(defn styles-data
[shape]
{:position {:props [:type]
:to-prop {:type "position"}
:format {:type format-position}}
:layout {:props (if (or (empty? (:flex-items shape))
(ctl/layout-absolute? shape))
[:width :height :x :y :radius :rx :r1]
[:width :height :radius :rx :r1])
:to-prop {:x "left"
:y "top"
:rotation "transform"
:rx "border-radius"
:r1 "border-radius"}
:format {:rotation #(str/fmt "rotate(%sdeg)" %)
:r1 #(apply str/fmt "%spx %spx %spx %spx" %)
:width #(get-size :width %)
:height #(get-size :height %)}
:multi {:r1 [:r1 :r2 :r3 :r4]}}
:fill {:props [:fills]
:to-prop {:fills (if (> (count (:fills shape)) 1) "background-image" "background-color")}
:format {:fills format-fill-color}}
:stroke {:props [:strokes]
:to-prop {:strokes "border"}
:format {:strokes format-stroke}}
:shadow {:props [:shadow]
:to-prop {:shadow :box-shadow}
:format {:shadow #(str/join ", " (map shadow->css %1))}}
:blur {:props [:blur]
:to-prop {:blur "filter"}
:format {:blur #(str/fmt "blur(%spx)" (:value %))}}
:layout-flex {:props [:layout
:layout-flex-dir
:layout-align-items
:layout-justify-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 fmt/format-gap
:layout-padding fmt/format-padding}}})
(def style-text
{:props [:fills
:font-family
:font-style
:font-size
:font-weight
:line-height
:letter-spacing
:text-decoration
:text-transform]
:to-prop {:fills "color"}
:format {:font-family #(dm/str "'" % "'")
:font-style #(dm/str %)
:font-size #(dm/str % "px")
:font-weight #(dm/str %)
:line-height #(dm/str %)
:letter-spacing #(dm/str % "px")
:text-decoration d/name
:text-transform d/name
:fills format-fill-color}})
(def layout-flex-item-params
{:props [:layout-item-margin
:layout-item-max-h
:layout-item-min-h
:layout-item-max-w
:layout-item-min-w
:layout-item-align-self]
:to-prop {:layout-item-margin "margin"
:layout-item-max-h "max-height"
:layout-item-min-h "min-height"
:layout-item-max-w "max-width"
:layout-item-min-w "min-width"
:layout-item-align-self "align-self"}
:format {:layout-item-margin fmt/format-margin
:layout-item-max-h #(dm/str % "px")
:layout-item-min-h #(dm/str % "px")
:layout-item-max-w #(dm/str % "px")
:layout-item-min-w #(dm/str % "px")
:layout-item-align-self d/name}})
(def layout-align-content
{:props [:layout-align-content]
:to-prop {:layout-align-content "align-content"}
:format {:layout-align-content d/name}})
(defn get-specific-value
[values prop]
(let [result (if (get values prop)
(get values prop)
(get (:selrect values) prop))
result (if (= :width prop)
(get-size :width values)
result)
result (if (= :height prop)
(get-size :height values)
result)]
result))
(defn generate-css-props
([values properties]
(generate-css-props values properties nil))
([values properties params]
(let [{:keys [to-prop format tab-size multi]
:or {to-prop {} tab-size 0 multi {}}} params
;; We allow the :format and :to-prop to be a map for different properties
;; or just a value for a single property. This code transform a single
;; property to a uniform one
properties (if-not (coll? properties) [properties] properties)
format (if (not (map? format))
(into {} (map #(vector % format) properties))
format)
to-prop (if (not (map? to-prop))
(into {} (map #(vector % to-prop) properties))
to-prop)
get-value (fn [prop]
(if-let [props (prop multi)]
(map #(get values %) props)
(get-specific-value values prop)))
null? (fn [value]
(if (coll? value)
(every? #(or (nil? %) (= % 0)) value)
(or (nil? value) (= value 0))))
default-format (fn [value] (dm/str (fmt/format-pixels value)))
format-property (fn [prop]
(let [css-prop (or (prop to-prop) (d/name prop))
format-fn (or (prop format) default-format)
css-val (format-fn (get-value prop) values)]
(when css-val
(dm/str
(str/repeat " " tab-size)
(str/fmt "%s: %s;" css-prop css-val)))))]
(->> properties
(remove #(null? (get-value %)))
(map format-property)
(filter (comp not nil?))
(str/join "\n")))))
(defn shape->properties [shape]
(let [;; This property is added in an earlier step (code.cljs),
;; it will come with a vector of flex-items if any.
;; If there are none it will continue as usual.
flex-items (:flex-items shape)
props (->> (styles-data shape) vals (mapcat :props))
to-prop (->> (styles-data shape) vals (map :to-prop) (reduce merge))
format (->> (styles-data shape) vals (map :format) (reduce merge))
multi (->> (styles-data shape) vals (map :multi) (reduce merge))
props (cond-> props
(seq flex-items) (concat (:props layout-flex-item-params))
(= :wrap (:layout-wrap-type shape)) (concat (:props layout-align-content)))
to-prop (cond-> to-prop
(seq flex-items) (merge (:to-prop layout-flex-item-params))
(= :wrap (:layout-wrap-type shape)) (merge (:to-prop layout-align-content)))
format (cond-> format
(seq flex-items) (merge (:format layout-flex-item-params))
(= :wrap (:layout-wrap-type shape)) (merge (:format layout-align-content)))]
(generate-css-props shape props {:to-prop to-prop
:format format
:multi multi
:tab-size 2})))
(defn search-text-attrs
[node attrs]
(->> (txt/node-seq node)
(map #(select-keys % attrs))
(reduce d/merge)))
;; TODO: used on inspect
(defn parse-style-text-blocks
[node attrs]
(letfn
[(rec-style-text-map [acc node style]
(let [node-style (merge style (select-keys node attrs))
head (or (-> acc first) [{} ""])
[head-style head-text] head
new-acc
(cond
(:children node)
(reduce #(rec-style-text-map %1 %2 node-style) acc (:children node))
(not= head-style node-style)
(cons [node-style (:text node "")] acc)
:else
(cons [node-style (dm/str head-text "" (:text node))] (rest acc)))
;; We add an end-of-line when finish a paragraph
new-acc
(if (= (:type node) "paragraph")
(let [[hs ht] (first new-acc)]
(cons [hs (dm/str ht "\n")] (rest new-acc)))
new-acc)]
new-acc))]
(-> (rec-style-text-map [] node {})
reverse)))
(defn text->properties [shape]
(let [flex-items (:flex-items shape)
text-shape-style (select-keys (styles-data shape) [:layout :shadow :blur])
shape-props (->> text-shape-style vals (mapcat :props))
shape-to-prop (->> text-shape-style vals (map :to-prop) (reduce merge))
shape-format (->> text-shape-style vals (map :format) (reduce merge))
shape-props (cond-> shape-props
(seq flex-items) (concat (:props layout-flex-item-params)))
shape-to-prop (cond-> shape-to-prop
(seq flex-items) (merge (:to-prop layout-flex-item-params)))
shape-format (cond-> shape-format
(seq flex-items) (merge (:format layout-flex-item-params)))
text-values (->> (search-text-attrs
(:content shape)
(conj (:props style-text) :fill-color-gradient :fill-opacity))
(d/merge txt/default-text-attrs))]
(str/join
"\n"
[(generate-css-props shape
shape-props
{:to-prop shape-to-prop
:format shape-format
:tab-size 2})
(generate-css-props text-values
(:props style-text)
{:to-prop (:to-prop style-text)
:format (:format style-text)
:tab-size 2})])))
(defn generate-css [shape]
(let [name (:name shape)
properties (if (= :text (:type shape))
(text->properties shape)
(shape->properties shape))
selector (str/css-selector name)
selector (if (str/starts-with? selector "-") (subs selector 1) selector)]
(str/join "\n" [(str/fmt "/* %s */" name)
(str/fmt ".%s {" selector)
properties
"}"])))
(defn generate-style-code [type shapes]
(let [generate-style-fn (case type
"css" generate-css)]
(->> shapes
(map generate-style-fn)
(str/join "\n\n"))))
(defn generate-style-code
[objects type shapes]
(let [generate-style
(case type
"css" css/generate-style)]
(generate-style objects shapes)))

View file

@ -0,0 +1,23 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.util.code-gen.common
(:require
[app.common.data.macros :as dm]
[cuerdas.core :as str]))
(defn shape->selector
[shape]
(let [name (-> (:name shape)
(subs 0 (min 10 (count (:name shape))))
(str/replace #"[^a-zA-Z0-9\s\:]+" ""))
;; selectors cannot start with numbers
name (if (re-matches #"^\d.*" name) (dm/str "c-" name) name)
id (-> (dm/str (:id shape))
(subs 24 36))
selector (str/css-selector (dm/str name " " id))
selector (if (str/starts-with? selector "-") (subs selector 1) selector)]
selector))

View file

@ -0,0 +1,103 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.util.code-gen.markup-html
(:require
["react-dom/server" :as rds]
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.pages.helpers :as cph]
[app.common.types.shape.layout :as ctl]
[app.config :as cfg]
[app.main.ui.shapes.text.html-text :as text]
[app.util.code-gen.common :as cgc]
[app.util.code-gen.markup-svg :refer [generate-svg]]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(defn svg-markup?
"Function to determine whether a shape is rendered in HTML+CSS or is rendered
through a SVG"
[shape]
(or
;; path and path-like shapes
(cph/path-shape? shape)
(cph/bool-shape? shape)
;; imported SVG images
(cph/svg-raw-shape? shape)
(some? (:svg-attrs shape))
;; CSS masks are not enough we need to delegate to SVG
(cph/mask-shape? shape)
;; Texts with shadows or strokes we render in SVG
(and (cph/text-shape? shape)
(or (d/not-empty? (:shadow shape))
(d/not-empty? (:strokes shape))))
;; When a shape has several strokes or the stroke is not a "border"
(or (> (count (:strokes shape)) 1)
(and (= (count (:strokes shape)) 1)
(not= (-> shape :strokes first :stroke-alignment) :center)))))
(defn generate-html
([objects shape]
(generate-html objects shape 0))
([objects shape level]
(let [indent (str/repeat " " level)
maybe-reverse (if (ctl/any-layout? shape) reverse identity)
shape-html
(cond
(svg-markup? shape)
(let [svg-markup (generate-svg objects shape)]
(dm/fmt "%<div class=\"%\">\n%\n%</div>"
indent
(cgc/shape->selector shape)
svg-markup
indent))
(cph/text-shape? shape)
(let [text-shape-html (rds/renderToStaticMarkup (mf/element text/text-shape #js {:shape shape :code? true}))]
(dm/fmt "%<div class=\"%\">\n%\n%</div>"
indent
(cgc/shape->selector shape)
text-shape-html
indent))
(cph/image-shape? shape)
(let [data (or (:metadata shape) (:fill-image shape))
image-url (cfg/resolve-file-media data)]
(dm/fmt "%<img src=\"%\" class=\"%\">\n%</img>"
indent
image-url
(cgc/shape->selector shape)
indent))
(empty? (:shapes shape))
(dm/fmt "%<div class=\"%\">\n%</div>"
indent
(cgc/shape->selector shape)
indent)
:else
(dm/fmt "%<div class=\"%\">\n%\n%</div>"
indent
(cgc/shape->selector shape)
(->> (:shapes shape)
(maybe-reverse)
(map #(generate-html objects (get objects %) (inc level)))
(str/join "\n"))
indent))]
(dm/fmt "%<!-- % -->\n%" indent (dm/str (d/name (:type shape)) ": " (:name shape)) shape-html))))
(defn generate-markup
[objects shapes]
(->> shapes
(map #(generate-html objects %))
(str/join "\n")))

View file

@ -0,0 +1,26 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.util.code-gen.markup-svg
(:require
["react-dom/server" :as rds]
[app.main.render :as render]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(defn generate-svg
[objects shape]
(rds/renderToStaticMarkup
(mf/element
render/object-svg
#js {:objects objects
:object-id (-> shape :id)})))
(defn generate-markup
[objects shapes]
(->> shapes
(map #(generate-svg objects %))
(str/join "\n")))

View file

@ -0,0 +1,235 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.util.code-gen.style-css
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.pages.helpers :as cph]
[app.common.text :as txt]
[app.main.ui.shapes.text.styles :as sts]
[app.util.code-gen.common :as cgc]
[app.util.code-gen.style-css-formats :refer [format-value]]
[app.util.code-gen.style-css-values :refer [get-value]]
[cuerdas.core :as str]))
;;
;; Common styles to display always. Will be attached as a prelude to the generated CSS
;;
(def prelude "
html, body {
background-color: #E8E9EA;
margin: 0;
min-height: 100%;
min-width: 100%;
padding: 0;
}
body {
display: flex;
flex-direction: column;
align-items: center;
padding: 2rem;
gap: 2rem;
}
svg {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
* {
box-sizing: border-box;
}
.text-node { background-clip: text !important; -webkit-background-clip: text !important; }
")
(def shape-css-properties
[:position
:left
:top
:width
:height
:transform
:background
:background-color
:background-image
:border
:border-radius
:box-shadow
:filter
:opacity
:overflow
;; Flex/grid related properties
:display
:align-items
:align-content
:justify-items
:justify-content
:gap
:column-gap
:row-gap
:padding
;; Flex related properties
:flex-direction
:flex-wrap
;; Grid related properties
:grid-template-rows
:grid-template-columns
;; Flex/grid self properties
:flex-shrink
:margin
:max-height
:min-height
:max-width
:min-width
:align-self
:justify-self
;; Grid cell properties
:grid-column
:grid-row
])
(def text-node-css-properties
[:font-family
:font-style
:font-size
:font-weight
:line-height
:letter-spacing
:text-decoration
:text-transform
:color])
(defn shape->css-property
[shape objects property]
(when-let [value (get-value property shape objects)]
[property value]))
(defn shape->css-properties
"Given a shape extract the CSS properties in the format of list [property value]"
[shape objects properties]
(->> properties
(keep (fn [property]
(when-let [value (get-value property shape objects)]
[property value])))))
(defn format-css-value
([[property value] options]
(format-css-value property value options))
([property value options]
(when (some? value)
(format-value property value options))))
(defn format-css-property
[[property value] options]
(when (some? value)
(let [formatted-value (format-css-value property value options)]
(dm/fmt "%: %;" (d/name property) formatted-value))))
(defn format-css-properties
"Format a list of [property value] into a list of css properties in the format 'property: value;'"
[properties options]
(->> properties
(map #(dm/str " " (format-css-property % options)))
(str/join "\n")))
(defn get-shape-properties-css
([objects shape properties]
(get-shape-properties-css objects shape properties nil))
([objects shape properties options]
(-> shape
(shape->css-properties objects properties)
(format-css-properties options))))
(defn format-js-styles
[properties _options]
(format-css-properties
(->> (.keys js/Object properties)
(remove #(str/starts-with? % "--"))
(mapv (fn [key]
[(str/kebab key) (unchecked-get properties key)])))
nil))
(defn node->css
[shape shape-selector node]
(let [properties
(case (:type node)
(:root "root")
(sts/generate-root-styles shape node)
(:paragraph-set "paragraph-set")
(sts/generate-paragraph-set-styles shape)
(:paragraph "paragraph")
(sts/generate-paragraph-styles shape node)
(sts/generate-text-styles shape node))]
(dm/fmt
".% {\n%\n}"
(dm/str shape-selector " ." (:$id node))
(format-js-styles properties nil))))
(defn generate-text-css
[shape]
(let [selector (cgc/shape->selector shape)]
(->> shape
:content
(txt/index-content)
(txt/node-seq)
(map #(node->css shape selector %))
(str/join "\n"))))
(defn get-shape-css-selector
([objects shape]
(get-shape-css-selector shape objects nil))
([shape objects options]
(let [properties (-> shape
(shape->css-properties objects shape-css-properties)
(format-css-properties options))
selector (cgc/shape->selector shape)]
(str/join "\n" [(str/fmt "/* %s */" (:name shape))
(str/fmt ".%s {\n%s\n}" selector properties)
(when (cph/text-shape? shape) (generate-text-css shape))]))))
(defn get-css-property
([objects shape property]
(get-css-property objects shape property nil))
([objects shape property options]
(-> shape
(shape->css-property objects property)
(format-css-property options))))
(defn get-css-value
([objects shape property]
(get-css-value objects shape property nil))
([objects shape property options]
(when-let [prop (shape->css-property shape objects property)]
(format-css-value prop options))))
(defn generate-style
([objects shapes]
(generate-style objects shapes nil))
([objects shapes options]
(dm/str
prelude
(->> shapes
(map #(get-shape-css-selector % objects options))
(str/join "\n\n")))))

View file

@ -0,0 +1,144 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.util.code-gen.style-css-formats
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.main.ui.formats :as fmt]
[app.util.color :as uc]
[cuerdas.core :as str]))
(def css-formatters
{:left :position
:top :position
:width :size
:height :size
:background :color
:background-color :color
:background-image :color-array
:border :border
:border-radius :string-or-size-array
:box-shadow :shadows
:filter :blur
:gap :size-array
:row-gap :size-array
:column-gap :size-array
:padding :size-array
:grid-template-rows :tracks
:grid-template-columns :tracks
:transform :matrix
})
(defmulti format-value
(fn [property _value _options] (css-formatters property)))
(defmethod format-value :position
[_ value _options]
(cond
(number? value) (fmt/format-pixels value)
:else value))
(defmethod format-value :size
[_ value _options]
(cond
(= value :fill) "100%"
(= value :auto) "auto"
(number? value) (fmt/format-pixels value)
:else value))
(defn format-color
[value _options]
(cond
(not= (:opacity value) 1)
(uc/color->background value)
:else
(str/upper (:color value))))
(defmethod format-value :color
[_ value options]
(format-color value options))
(defmethod format-value :color-array
[_ value options]
(->> value
(map #(format-color % options))
(str/join ", ")))
(defmethod format-value :border
[_ {:keys [color style width]} options]
(dm/fmt "% % %"
(fmt/format-pixels width)
(d/name style)
(format-color color options)))
(defmethod format-value :size-array
[_ value _options]
(cond
(and (coll? value) (d/not-empty? value))
(->> value
(map fmt/format-pixels)
(str/join " "))
(some? value)
value))
(defmethod format-value :string-or-size-array
[_ value _]
(cond
(string? value)
value
(and (coll? value) (d/not-empty? value))
(->> value
(map fmt/format-pixels)
(str/join " "))
(some? value)
value))
(defmethod format-value :keyword
[_ value _options]
(d/name value))
(defmethod format-value :tracks
[_ value _options]
(->> value
(map (fn [{:keys [type value]}]
(case type
:flex (dm/str (fmt/format-number value) "fr")
:percent (fmt/format-percent (/ value 100))
:auto "auto"
(fmt/format-pixels value))))
(str/join " ")))
(defn format-shadow
[{:keys [style offset-x offset-y blur spread color]} options]
(let [css-color (format-color color options)]
(dm/str
(if (= style :inner-shadow) "inset " "")
(str/fmt "%spx %spx %spx %spx %s" offset-x offset-y blur spread css-color))))
(defmethod format-value :shadows
[_ value options]
(->> value
(map #(format-shadow % options))
(str/join ", " )))
(defmethod format-value :blur
[_ value _options]
(dm/fmt "blur(%)" (fmt/format-pixels value)))
(defmethod format-value :matrix
[_ value _options]
(fmt/format-matrix value))
(defmethod format-value :default
[_ value _options]
(if (keyword? value)
(d/name value)
value))

Some files were not shown because too many files have changed in this diff Show more