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:
commit
6539b7da5b
106 changed files with 5691 additions and 2154 deletions
|
@ -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
|
||||
|
|
|
@ -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]))
|
||||
|
|
|
@ -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)
|
||||
|
|
95
common/src/app/common/geom/shapes/grid_layout/areas.cljc
Normal file
95
common/src/app/common/geom/shapes/grid_layout/areas.cljc
Normal 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])))
|
||||
|
53
common/src/app/common/geom/shapes/grid_layout/bounds.cljc
Normal file
53
common/src/app/common/geom/shapes/grid_layout/bounds.cljc
Normal 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)))
|
|
@ -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)))))
|
||||
|
|
|
@ -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)))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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))))))))))
|
||||
|
|
|
@ -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)))
|
||||
|
||||
|
|
|
@ -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)))))
|
||||
|
|
|
@ -123,6 +123,10 @@
|
|||
padding: $size-1;
|
||||
svg {
|
||||
fill: $color-gray-20;
|
||||
|
||||
&.icon-close {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
background: transparent;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -35,6 +35,10 @@
|
|||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.inspect .tab-container-content {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tab-element,
|
||||
.tab-element-content {
|
||||
height: 100%;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)))]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)))))))
|
||||
|
|
|
@ -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])))))))))))
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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?)
|
||||
|
|
|
@ -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))))))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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)]))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}])])))
|
||||
|
||||
|
|
99
frontend/src/app/main/ui/shapes/grid_layout_viewer.cljs
Normal file
99
frontend/src/app/main/ui/shapes/grid_layout_viewer.cljs
Normal 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}])]))
|
|
@ -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}]]))
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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?}]]))
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
}]]]))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)}]])])))
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)}])])
|
|
@ -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)
|
||||
|
|
|
@ -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)}])])))
|
||||
|
|
|
@ -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)}])])))
|
|
@ -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)}])])))
|
|
@ -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}]])))
|
|
@ -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
|
||||
|
|
|
@ -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}]))]])))
|
||||
|
|
|
@ -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))))]
|
||||
|
||||
|
|
|
@ -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}]]]))
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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?)
|
||||
|
|
|
@ -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}]))
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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}]]]]]))
|
|
@ -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}]]]))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}]]]]]))
|
|
@ -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}]
|
||||
|
|
|
@ -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}])
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}])
|
||||
|
||||
|
|
|
@ -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}])
|
||||
|
||||
|
|
|
@ -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)}])
|
||||
|
|
|
@ -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}]))]]]]))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"}}]
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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"}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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)))))))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]}]
|
||||
|
|
|
@ -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)))
|
||||
|
|
23
frontend/src/app/util/code_gen/common.cljs
Normal file
23
frontend/src/app/util/code_gen/common.cljs
Normal 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))
|
103
frontend/src/app/util/code_gen/markup_html.cljs
Normal file
103
frontend/src/app/util/code_gen/markup_html.cljs
Normal 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")))
|
26
frontend/src/app/util/code_gen/markup_svg.cljs
Normal file
26
frontend/src/app/util/code_gen/markup_svg.cljs
Normal 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")))
|
235
frontend/src/app/util/code_gen/style_css.cljs
Normal file
235
frontend/src/app/util/code_gen/style_css.cljs
Normal 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")))))
|
144
frontend/src/app/util/code_gen/style_css_formats.cljs
Normal file
144
frontend/src/app/util/code_gen/style_css_formats.cljs
Normal 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
Loading…
Add table
Reference in a new issue