Update info panel

This commit is contained in:
alonso.torres 2023-06-19 11:06:24 +02:00
parent c3a8c3826d
commit a2c59acfa9
27 changed files with 1040 additions and 1144 deletions

View file

@ -8,6 +8,7 @@
(:require
[app.common.colors :as clr]
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.transit :as t]
[clojure.walk :as walk]
[cuerdas.core :as str]))
@ -29,6 +30,8 @@
:fills [{:fill-color clr/black
:fill-opacity 1}]})
(def text-attrs (keys default-text-attrs))
(def typography-fields
[:font-id
:font-family
@ -252,3 +255,34 @@
{:blocks (reduce #(conj %1 (build-block %2)) [] (node-seq #(= (:type %) "paragraph") root))
:entityMap {}}))
(defn content->text+styles
"Given a root node of a text content extracts the texts with its associated styles"
[node]
(letfn
[(rec-style-text-map [acc node style]
(let [node-style (merge style (select-keys node text-attrs))
head (or (-> acc first) [{} ""])
[head-style head-text] head
new-acc
(cond
(:children node)
(reduce #(rec-style-text-map %1 %2 node-style) acc (:children node))
(not= head-style node-style)
(cons [node-style (:text node "")] acc)
:else
(cons [node-style (dm/str head-text "" (:text node))] (rest acc)))
;; We add an end-of-line when finish a paragraph
new-acc
(if (= (:type node) "paragraph")
(let [[hs ht] (first new-acc)]
(cons [hs (dm/str ht "\n")] (rest new-acc)))
new-acc)]
new-acc))]
(-> (rec-style-text-map [] node {})
reverse)))

View file

@ -16,32 +16,36 @@
(format-percent value nil))
([value {:keys [precision] :or {precision 2}}]
(when (d/num? value)
(let [percent-val (mth/precision (* value 100) precision)]
(dm/str percent-val "%")))))
(let [value (if (string? value) (d/parse-double value) value)]
(when (d/num? value)
(let [percent-val (mth/precision (* value 100) precision)]
(dm/str percent-val "%"))))))
(defn format-number
([value]
(format-number value nil))
([value {:keys [precision] :or {precision 2}}]
(when (d/num? value)
(let [value (mth/precision value precision)]
(dm/str value)))))
(let [value (if (string? value) (d/parse-double value) value)]
(when (d/num? value)
(let [value (mth/precision value precision)]
(dm/str value))))))
(defn format-pixels
([value]
(format-pixels value nil))
([value {:keys [precision] :or {precision 2}}]
(when (d/num? value)
(let [value (mth/precision value precision)]
(dm/str value "px")))))
(let [value (if (string? value) (d/parse-double value) value)]
(when (d/num? value)
(let [value (mth/precision value precision)]
(dm/str value "px"))))))
(defn format-int
[value]
(when (d/num? value)
(let [value (mth/precision value 0)]
(dm/str value))))
(let [value (if (string? value) (d/parse-double value) value)]
(when (d/num? value)
(let [value (mth/precision value 0)]
(dm/str value)))))
(defn format-padding-margin-shorthand
[values]

View file

@ -59,7 +59,8 @@
(mf/defc render-node
{::mf/wrap-props false}
[props]
(let [{:keys [type text children] :as parent} (obj/get props "node")]
(let [{:keys [type text children] :as parent} (obj/get props "node")
code? (obj/get props "code?")]
(if (string? text)
[:> render-text props]
(let [component (case type
@ -74,7 +75,8 @@
(obj/set! "node" node)
(obj/set! "parent" parent)
(obj/set! "index" index)
(obj/set! "key" index))]
(obj/set! "key" index)
(obj/set! "code?" code?))]
[:> render-node props]))])))))
(mf/defc text-shape
@ -103,8 +105,10 @@
:style style}
;; We use a class here because react has a bug that won't use the appropriate selector for
;; `background-clip`
[:style ".text-node { background-clip: text;
-webkit-background-clip: text; }" ]
(when (not code?)
[:style ".text-node { background-clip: text;
-webkit-background-clip: text; }" ])
[:& render-node {:index 0
:shape shape
:node content}]]))
:node content
:code? code?}]]))

View file

@ -6,16 +6,15 @@
(ns app.main.ui.viewer.inspect.attributes
(:require
[app.common.geom.shapes :as gsh]
[app.common.types.components-list :as ctkl]
[app.main.ui.hooks :as hooks]
[app.main.ui.viewer.inspect.annotation :refer [annotation]]
[app.main.ui.viewer.inspect.attributes.blur :refer [blur-panel]]
[app.main.ui.viewer.inspect.attributes.fill :refer [fill-panel]]
[app.main.ui.viewer.inspect.attributes.geometry :refer [geometry-panel]]
[app.main.ui.viewer.inspect.attributes.image :refer [image-panel]]
[app.main.ui.viewer.inspect.attributes.layout :refer [layout-panel]]
[app.main.ui.viewer.inspect.attributes.layout-flex :refer [layout-flex-panel]]
[app.main.ui.viewer.inspect.attributes.layout-flex-element :refer [layout-flex-element-panel]]
[app.main.ui.viewer.inspect.attributes.layout-element :refer [layout-element-panel]]
[app.main.ui.viewer.inspect.attributes.shadow :refer [shadow-panel]]
[app.main.ui.viewer.inspect.attributes.stroke :refer [stroke-panel]]
[app.main.ui.viewer.inspect.attributes.svg :refer [svg-panel]]
@ -24,20 +23,18 @@
[rumext.v2 :as mf]))
(def type->options
{:multiple [:fill :stroke :image :text :shadow :blur :layout-flex-item]
:frame [:layout :fill :stroke :shadow :blur :layout-flex :layout-flex-item]
:group [:layout :svg :layout-flex-item]
:rect [:layout :fill :stroke :shadow :blur :svg :layout-flex-item]
:circle [:layout :fill :stroke :shadow :blur :svg :layout-flex-item]
:path [:layout :fill :stroke :shadow :blur :svg :layout-flex-item]
:image [:image :layout :fill :stroke :shadow :blur :svg :layout-flex-item]
:text [:layout :text :shadow :blur :stroke :layout-flex-item]})
{:multiple [:fill :stroke :image :text :shadow :blur :layout-element]
:frame [:geometry :fill :stroke :shadow :blur :layout :layout-element]
:group [:geometry :svg :layout-element]
:rect [:geometry :fill :stroke :shadow :blur :svg :layout-element]
:circle [:geometry :fill :stroke :shadow :blur :svg :layout-element]
:path [:geometry :fill :stroke :shadow :blur :svg :layout-element]
:image [:image :geometry :fill :stroke :shadow :blur :svg :layout-element]
:text [:geometry :text :shadow :blur :stroke :layout-element]})
(mf/defc attributes
[{:keys [page-id file-id shapes frame from libraries share-id]}]
[{:keys [page-id file-id shapes frame from libraries share-id objects]}]
(let [shapes (hooks/use-equal-memo shapes)
shapes (mf/with-memo [shapes]
(mapv #(gsh/translate-to-frame % frame) shapes))
type (if (= (count shapes) 1) (-> shapes first :type) :multiple)
options (type->options type)
content (when (= (count shapes) 1)
@ -46,9 +43,9 @@
[:div.element-options
(for [[idx option] (map-indexed vector options)]
[:> (case option
:geometry geometry-panel
:layout layout-panel
:layout-flex layout-flex-panel
:layout-flex-item layout-flex-element-panel
:layout-element layout-element-panel
:fill fill-panel
:stroke stroke-panel
:shadow shadow-panel
@ -58,6 +55,7 @@
:svg svg-panel)
{:key idx
:shapes shapes
:objects objects
:frame frame
:from from}])
(when content

View file

@ -7,32 +7,25 @@
(ns app.main.ui.viewer.inspect.attributes.blur
(:require
[app.main.ui.components.copy-button :refer [copy-button]]
[app.util.code-gen :as cg]
[app.util.code-gen.style-css :as css]
[app.util.i18n :refer [tr]]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(defn has-blur? [shape]
(:blur shape))
(defn copy-data [shape]
(cg/generate-css-props
shape
:blur
{:to-prop "filter"
:format #(str/fmt "blur(%spx)" (:value %))}))
(mf/defc blur-panel [{:keys [shapes]}]
(mf/defc blur-panel
[{:keys [objects shapes]}]
(let [shapes (->> shapes (filter has-blur?))]
(when (seq shapes)
[:div.attributes-block
[:div.attributes-block-title
[:div.attributes-block-title-text (tr "inspect.attributes.blur")]
(when (= (count shapes) 1)
[:& copy-button {:data (copy-data (first shapes))}])]
[:& copy-button {:data (css/get-css-property objects (first shapes) :filter)}])]
(for [shape shapes]
[:div.attributes-unit-row
[:div.attributes-label (tr "inspect.attributes.blur.value")]
[:div.attributes-value (-> shape :blur :value) "px"]
[:& copy-button {:data (copy-data shape)}]])])))
[:div.attributes-value (css/get-css-value objects shape :filter)]
[:& copy-button {:data (css/get-css-property objects shape :filter)}]])])))

View file

@ -17,7 +17,6 @@
[okulary.core :as l]
[rumext.v2 :as mf]))
(def file-colors-ref
(l/derived (l/in [:viewer :file :data :colors]) st/state))

View file

@ -7,12 +7,11 @@
(ns app.main.ui.viewer.inspect.attributes.fill
(:require
[app.main.ui.viewer.inspect.attributes.common :refer [color-row]]
[app.util.code-gen :as cg]
[app.util.color :as uc]
[app.util.code-gen.style-css :as css]
[app.util.i18n :refer [tr]]
[rumext.v2 :as mf]))
(def fill-attributes [:fill-color :fill-color-gradient])
(def properties [:background :background-color :background-image])
(defn shape->color [shape]
{:color (:fill-color shape)
@ -21,21 +20,15 @@
:id (:fill-color-ref-id shape)
:file-id (:fill-color-ref-file shape)})
(defn has-color? [shape]
(defn has-fill? [shape]
(and
(not (contains? #{:text :group} (:type shape)))
(or (:fill-color shape)
(:fill-color-gradient shape)
(seq (:fills shape)))))
(defn copy-data [shape]
(cg/generate-css-props
shape
fill-attributes
{:to-prop "background"
:format #(uc/color->background (shape->color shape))}))
(mf/defc fill-block [{:keys [shape]}]
(mf/defc fill-block
[{:keys [objects shape]}]
(let [color-format (mf/use-state :hex)
color (shape->color shape)]
@ -43,11 +36,11 @@
[:& color-row {:color color
:format @color-format
:on-change-format #(reset! color-format %)
:copy-data (copy-data shape)}]]))
:copy-data (css/get-shape-properties-css objects {:fills [shape]} properties)}]]))
(mf/defc fill-panel
[{:keys [shapes]}]
(let [shapes (->> shapes (filter has-color?))]
(let [shapes (->> shapes (filter has-fill?))]
(when (seq shapes)
[:div.attributes-block
[:div.attributes-block-title

View file

@ -0,0 +1,38 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.viewer.inspect.attributes.geometry
(:require
[app.common.data :as d]
[app.main.ui.components.copy-button :refer [copy-button]]
[app.util.code-gen.style-css :as css]
[app.util.i18n :refer [tr]]
[rumext.v2 :as mf]))
(def properties [:width :height :left :top :border-radius :transform])
(mf/defc geometry-block
[{:keys [objects shape]}]
[:*
(for [property properties]
(when-let [value (css/get-css-value objects shape property)]
[:div.attributes-unit-row
[:div.attributes-label (d/name property)]
[:div.attributes-value value]
[:& copy-button {:data (css/get-css-property objects shape property)}]]))])
(mf/defc geometry-panel
[{:keys [objects shapes]}]
[:div.attributes-block
[:div.attributes-block-title
[:div.attributes-block-title-text (tr "inspect.attributes.size")]
(when (= (count shapes) 1)
[:& copy-button {:data (css/get-shape-properties-css objects (first shapes) properties)}])]
(for [shape shapes]
[:& geometry-block {:shape shape
:objects objects
:key (:id shape)}])])

View file

@ -10,7 +10,7 @@
[app.common.pages.helpers :as cph]
[app.config :as cf]
[app.main.ui.components.copy-button :refer [copy-button]]
[app.util.code-gen :as cg]
[app.util.code-gen.style-css :as css]
[app.util.i18n :refer [tr]]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
@ -19,7 +19,7 @@
(= (:type shape) :image))
(mf/defc image-panel
[{:keys [shapes]}]
[{:keys [objects shapes]}]
(for [shape (filter cph/image-shape? shapes)]
[:div.attributes-block {:key (str "image-" (:id shape))}
[:div.attributes-image-row
@ -28,13 +28,13 @@
[:div.attributes-unit-row
[:div.attributes-label (tr "inspect.attributes.image.width")]
[:div.attributes-value (-> shape :metadata :width) "px"]
[:& copy-button {:data (cg/generate-css-props shape :width)}]]
[:div.attributes-value (css/get-css-value objects (:metadata shape) :width)]
[:& copy-button {:data (css/get-css-property objects (:metadata shape) :width)}]]
[:div.attributes-unit-row
[:div.attributes-label (tr "inspect.attributes.image.height")]
[:div.attributes-value (-> shape :metadata :height) "px"]
[:& copy-button {:data (cg/generate-css-props shape :height)}]]
[:div.attributes-value (css/get-css-value objects (:metadata shape) :height)]
[:& copy-button {:data (css/get-css-property objects (:metadata shape) :height)}]]
(let [mtype (-> shape :metadata :mtype)
name (:name shape)

View file

@ -6,92 +6,46 @@
(ns app.main.ui.viewer.inspect.attributes.layout
(:require
[app.common.types.shape.radius :as ctsr]
[app.common.data :as d]
[app.common.types.shape.layout :as ctl]
[app.main.ui.components.copy-button :refer [copy-button]]
[app.main.ui.formats :as fmt]
[app.util.code-gen :as cg]
[app.util.i18n :refer [tr]]
[cuerdas.core :as str]
[app.util.code-gen.style-css :as css]
[rumext.v2 :as mf]))
(def properties [:width :height :x :y :radius :rx :r1])
(def params
{:to-prop {:x "left"
:y "top"
:rotation "transform"
:rx "border-radius"
:r1 "border-radius"}
:format {:rotation #(str/fmt "rotate(%sdeg)" %)
:r1 #(apply str/fmt "%spx, %spx, %spx, %spx" %)
:width #(cg/get-size :width %)
:height #(cg/get-size :height %)}
:multi {:r1 [:r1 :r2 :r3 :r4]}})
(defn copy-data
([shape]
(apply copy-data shape properties))
([shape & properties]
(cg/generate-css-props shape properties params)))
(def properties
[:display
:flex-direction
:flex-wrap
:grid-template-rows
:grid-template-columns
:align-items
:align-content
:justify-items
:justify-content
:gap
:padding])
(mf/defc layout-block
[{:keys [shape]}]
(let [selrect (:selrect shape)
{:keys [x y width height]} selrect]
[:*
[:div.attributes-unit-row
[:div.attributes-label (tr "inspect.attributes.layout.width")]
[:div.attributes-value (fmt/format-size :width width shape)]
[:& copy-button {:data (copy-data selrect :width)}]]
[:div.attributes-unit-row
[:div.attributes-label (tr "inspect.attributes.layout.height")]
[:div.attributes-value (fmt/format-size :height height shape)]
[:& copy-button {:data (copy-data selrect :height)}]]
(when (not= (:x shape) 0)
[{:keys [objects shape]}]
[:*
(for [property properties]
(when-let [value (css/get-css-value objects shape property)]
[:div.attributes-unit-row
[:div.attributes-label (tr "inspect.attributes.layout.left")]
[:div.attributes-value (fmt/format-pixels x)]
[:& copy-button {:data (copy-data selrect :x)}]])
(when (not= (:y shape) 0)
[:div.attributes-unit-row
[:div.attributes-label (tr "inspect.attributes.layout.top")]
[:div.attributes-value (fmt/format-pixels y)]
[:& copy-button {:data (copy-data selrect :y)}]])
(when (ctsr/radius-1? shape)
[:div.attributes-unit-row
[:div.attributes-label (tr "inspect.attributes.layout.radius")]
[:div.attributes-value (fmt/format-pixels (:rx shape 0))]
[:& copy-button {:data (copy-data shape :rx)}]])
(when (ctsr/radius-4? shape)
[:div.attributes-unit-row
[:div.attributes-label (tr "inspect.attributes.layout.radius")]
[:div.attributes-value
(fmt/format-number (:r1 shape)) ", "
(fmt/format-number (:r2 shape)) ", "
(fmt/format-number (:r3 shape)) ", "
(fmt/format-pixels (:r4 shape))]
[:& copy-button {:data (copy-data shape :r1)}]])
(when (not= (:rotation shape 0) 0)
[:div.attributes-unit-row
[:div.attributes-label (tr "inspect.attributes.layout.rotation")]
[:div.attributes-value (fmt/format-number (:rotation shape)) "deg"]
[:& copy-button {:data (copy-data shape :rotation)}]])]))
[:div.attributes-label (d/name property)]
[:div.attributes-value value]
[:& copy-button {:data (css/get-css-property objects shape property)}]]))])
(mf/defc layout-panel
[{:keys [shapes]}]
[:div.attributes-block
[:div.attributes-block-title
[:div.attributes-block-title-text (tr "inspect.attributes.size")]
(when (= (count shapes) 1)
[:& copy-button {:data (copy-data (first shapes))}])]
[{:keys [objects shapes]}]
(let [shapes (->> shapes (filter ctl/any-layout?))]
(when (seq shapes)
[:div.attributes-block
[:div.attributes-block-title
[:div.attributes-block-title-text "Layout"]
(when (= (count shapes) 1)
[:& copy-button {:data (css/get-shape-properties-css objects (first shapes) properties)}])]
(for [shape shapes]
[:& layout-block {:shape shape
:key (:id shape)}])])
(for [shape shapes]
[:& layout-block {:shape shape
:objects objects
:key (:id shape)}])])))

View file

@ -0,0 +1,60 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.viewer.inspect.attributes.layout-element
(:require
[app.common.data :as d]
[app.common.types.shape.layout :as ctl]
[app.main.ui.components.copy-button :refer [copy-button]]
[app.util.code-gen.style-css :as css]
[rumext.v2 :as mf]))
(def properties
[:margin
:max-height
:min-height
:max-width
:min-width
:align-self
:justify-self
;; Grid cell properties
:grid-column
:grid-row])
(mf/defc layout-element-block
[{:keys [objects shape]}]
[:*
(for [property properties]
(when-let [value (css/get-css-value objects shape property)]
[:div.attributes-unit-row
[:div.attributes-label (d/name property)]
[:div.attributes-value value]
[:& copy-button {:data (css/get-css-property objects shape property)}]]))])
(mf/defc layout-element-panel
[{:keys [objects shapes]}]
(let [shapes (->> shapes (filter #(ctl/any-layout-immediate-child? objects %)))
only-flex? (every? #(ctl/flex-layout-immediate-child? objects %) shapes)
only-grid? (every? #(ctl/grid-layout-immediate-child? objects %) shapes)]
(when (seq shapes)
[:div.attributes-block
[:div.attributes-block-title
[:div.attributes-block-title-text (cond
only-flex?
"Flex element"
only-grid?
"Flex element"
:else
"Layout element"
)]
(when (= (count shapes) 1)
[:& copy-button {:data (css/get-shape-properties-css objects (first shapes) properties)}])]
(for [shape shapes]
[:& layout-element-block {:shape shape
:objects objects
:key (:id shape)}])])))

View file

@ -1,139 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.viewer.inspect.attributes.layout-flex
(:require
[app.common.data :as d]
[app.main.ui.components.copy-button :refer [copy-button]]
[app.main.ui.formats :as fm]
[app.util.code-gen :as cg]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(def properties [:layout
:layout-flex-dir
:layout-align-items
:layout-justify-content
:layout-gap
:layout-padding
:layout-wrap-type])
(def align-contet-prop [:layout-align-content])
(def layout-flex-params
{:props [:layout
:layout-align-items
:layout-flex-dir
:layout-justify-content
:layout-gap
:layout-padding
:layout-wrap-type]
:to-prop {:layout "display"
:layout-flex-dir "flex-direction"
:layout-align-items "align-items"
:layout-justify-content "justify-content"
:layout-wrap-type "flex-wrap"
:layout-gap "gap"
:layout-padding "padding"}
:format {:layout d/name
:layout-flex-dir d/name
:layout-align-items d/name
:layout-justify-content d/name
:layout-wrap-type d/name
:layout-gap fm/format-gap
:layout-padding fm/format-padding}})
(def layout-align-content-params
{:props [:layout-align-content]
:to-prop {:layout-align-content "align-content"}
:format {:layout-align-content d/name}})
(defn copy-data
([shape]
(let [properties-for-copy (if (:layout-align-content shape)
(into [] (concat properties align-contet-prop))
properties)]
(apply copy-data shape properties-for-copy)))
([shape & properties]
(let [params (if (:layout-align-content shape)
(d/deep-merge layout-align-content-params layout-flex-params )
layout-flex-params)]
(cg/generate-css-props shape properties params))))
(mf/defc manage-padding
[{:keys [padding type]}]
(let [values (fm/format-padding-margin-shorthand (vals padding))]
[:div.attributes-value
{:title (str (str/join "px " (vals values)) "px")}
(for [[k v] values]
[:span.items {:key (str type "-" k "-" v)} v "px"])]))
(mf/defc layout-flex-block
[{:keys [shape]}]
[:*
[:div.attributes-unit-row
[:div.attributes-label "Display"]
[:div.attributes-value "Flex"]
[:& copy-button {:data (copy-data shape)}]]
[:div.attributes-unit-row
[:div.attributes-label "Direction"]
[:div.attributes-value (str/capital (d/name (:layout-flex-dir shape)))]
[:& copy-button {:data (copy-data shape :layout-flex-dir)}]]
[:div.attributes-unit-row
[:div.attributes-label "Align-items"]
[:div.attributes-value (str/capital (d/name (:layout-align-items shape)))]
[:& copy-button {:data (copy-data shape :layout-align-items)}]]
[:div.attributes-unit-row
[:div.attributes-label "Justify-content"]
[:div.attributes-value (str/capital (d/name (:layout-justify-content shape)))]
[:& copy-button {:data (copy-data shape :layout-justify-content)}]]
[:div.attributes-unit-row
[:div.attributes-label "Flex wrap"]
[:div.attributes-value (str/capital (d/name (:layout-wrap-type shape)))]
[:& copy-button {:data (copy-data shape :layout-wrap-type)}]]
(when (= :wrap (:layout-wrap-type shape))
[:div.attributes-unit-row
[:div.attributes-label "Align-content"]
[:div.attributes-value (str/capital (d/name (:layout-align-content shape)))]
[:& copy-button {:data (copy-data shape :layout-align-content)}]])
[:div.attributes-unit-row
[:div.attributes-label "Gaps"]
(if (= (:row-gap (:layout-gap shape)) (:column-gap (:layout-gap shape)))
[:div.attributes-value
[:span (-> shape :layout-gap :row-gap fm/format-pixels)]]
[:div.attributes-value
[:span.items (-> shape :layout-gap :row-gap fm/format-pixels)]
[:span (-> shape :layout-gap :column-gap fm/format-pixels)]])
[:& copy-button {:data (copy-data shape :layout-gap)}]]
[:div.attributes-unit-row
[:div.attributes-label "Padding"]
[:& manage-padding {:padding (:layout-padding shape) :type "padding"}]
[:& copy-button {:data (copy-data shape :layout-padding)}]]])
(defn has-flex? [shape]
(= :flex (:layout shape)))
(mf/defc layout-flex-panel
[{:keys [shapes]}]
(let [shapes (->> shapes (filter has-flex?))]
(when (seq shapes)
[:div.attributes-block
[:div.attributes-block-title
[:div.attributes-block-title-text "Layout"]
(when (= (count shapes) 1)
[:& copy-button {:data (copy-data (first shapes))}])]
(for [shape shapes]
[:& layout-flex-block {:shape shape
:key (:id shape)}])])))

View file

@ -1,155 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.viewer.inspect.attributes.layout-flex-element
(:require
[app.common.data :as d]
[app.main.refs :as refs]
[app.main.ui.components.copy-button :refer [copy-button]]
[app.main.ui.formats :as fmt]
[app.main.ui.viewer.inspect.code :as cd]
[app.util.code-gen :as cg]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(defn format-margin
[margin-values]
(let [short-hand (fmt/format-padding-margin-shorthand (vals margin-values))
parsed-values (map #(str/fmt "%spx" %) (vals short-hand))]
(str/join " " parsed-values)))
(def properties [:layout-item-margin ;; {:m1 0 :m2 0 :m3 0 :m4 0}
:layout-item-max-h ;; num
:layout-item-min-h ;; num
:layout-item-max-w ;; num
:layout-item-min-w ;; num
:layout-item-align-self]) ;; :start :end :center
(def layout-flex-item-params
{:props [:layout-item-margin
:layout-item-max-h
:layout-item-min-h
:layout-item-max-w
:layout-item-min-w
:layout-item-align-self]
:to-prop {:layout-item-margin "margin"
:layout-item-align-self "align-self"
:layout-item-max-h "max-height"
:layout-item-min-h "min-height"
:layout-item-max-w "max-width"
:layout-item-min-w "min-width"}
:format {:layout-item-margin format-margin
:layout-item-align-self d/name}})
(defn copy-data
([shape]
(apply copy-data shape properties))
([shape & properties]
(cg/generate-css-props shape properties layout-flex-item-params)))
(mf/defc manage-margin
[{:keys [margin type]}]
(let [values (fmt/format-padding-margin-shorthand (vals margin))]
[:div.attributes-value
(for [[k v] values]
[:span.items {:key (str type "-" k "-" v)} v "px"])]))
(defn manage-sizing
[value type]
(let [ref-value-h {:fill "Width 100%"
:fix "Fixed width"
:auto "Fit content"}
ref-value-v {:fill "Height 100%"
:fix "Fixed height"
:auto "Fit content"}]
(if (= :h type)
(ref-value-h value)
(ref-value-v value))))
(mf/defc layout-element-block
[{:keys [shape]}]
(let [old-margin (:layout-item-margin shape)
new-margin {:m1 0 :m2 0 :m3 0 :m4 0}
merged-margin (merge new-margin old-margin)
shape (assoc shape :layout-item-margin merged-margin)]
[:*
(when (:layout-item-align-self shape)
[:div.attributes-unit-row
[:div.attributes-label "Align self"]
[:div.attributes-value (str/capital (d/name (:layout-item-align-self shape)))]
[:& copy-button {:data (copy-data shape :layout-item-align-self)}]])
(when (:layout-item-margin shape)
[:div.attributes-unit-row
[:div.attributes-label "Margin"]
[:& manage-margin {:margin merged-margin :type "margin"}]
[:& copy-button {:data (copy-data shape :layout-item-margin)}]])
(when (:layout-item-h-sizing shape)
[:div.attributes-unit-row
[:div.attributes-label "Horizontal sizing"]
[:div.attributes-value (manage-sizing (:layout-item-h-sizing shape) :h)]
[:& copy-button {:data (copy-data shape :layout-item-h-sizing)}]])
(when (:layout-item-v-sizing shape)
[:div.attributes-unit-row
[:div.attributes-label "Vertical sizing"]
[:div.attributes-value (manage-sizing (:layout-item-v-sizing shape) :v)]
[:& copy-button {:data (copy-data shape :layout-item-v-sizing)}]])
(when (= :fill (:layout-item-h-sizing shape))
[:*
(when (some? (:layout-item-max-w shape))
[:div.attributes-unit-row
[:div.attributes-label "Max. width"]
[:div.attributes-value (fmt/format-pixels (:layout-item-max-w shape))]
[:& copy-button {:data (copy-data shape :layout-item-max-w)}]])
(when (some? (:layout-item-min-w shape))
[:div.attributes-unit-row
[:div.attributes-label "Min. width"]
[:div.attributes-value (fmt/format-pixels (:layout-item-min-w shape))]
[:& copy-button {:data (copy-data shape :layout-item-min-w)}]])])
(when (= :fill (:layout-item-v-sizing shape))
[:*
(when (:layout-item-max-h shape)
[:div.attributes-unit-row
[:div.attributes-label "Max. height"]
[:div.attributes-value (fmt/format-pixels (:layout-item-max-h shape))]
[:& copy-button {:data (copy-data shape :layout-item-max-h)}]])
(when (:layout-item-min-h shape)
[:div.attributes-unit-row
[:div.attributes-label "Min. height"]
[:div.attributes-value (fmt/format-pixels (:layout-item-min-h shape))]
[:& copy-button {:data (copy-data shape :layout-item-min-h)}]])])]))
(mf/defc layout-flex-element-panel
[{:keys [shapes from]}]
(let [route (mf/deref refs/route)
page-id (:page-id (:query-params route))
mod-shapes (cd/get-flex-elements page-id shapes from)
shape (first mod-shapes)
has-margin? (some? (:layout-item-margin shape))
has-values? (or (some? (:layout-item-max-w shape))
(some? (:layout-item-max-h shape))
(some? (:layout-item-min-w shape))
(some? (:layout-item-min-h shape)))
has-align? (some? (:layout-item-align-self shape))
has-sizing? (or (some? (:layout-item-h-sizing shape))
(some? (:layout-item-w-sizing shape)))
must-show (or has-margin? has-values? has-align? has-sizing?)]
(when (and (= (count mod-shapes) 1) must-show)
[:div.attributes-block
[:div.attributes-block-title
[:div.attributes-block-title-text "Flex element"]
[:& copy-button {:data (copy-data shape)}]]
[:& layout-element-block {:shape shape}]])))

View file

@ -7,30 +7,13 @@
(ns app.main.ui.viewer.inspect.attributes.shadow
(:require
[app.common.data :as d]
[app.main.ui.components.copy-button :refer [copy-button]]
[app.main.ui.viewer.inspect.attributes.common :refer [color-row]]
[app.util.code-gen :as cg]
[app.util.i18n :refer [tr]]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(defn has-shadow? [shape]
(:shadow shape))
(defn shape-copy-data [shape]
(cg/generate-css-props
shape
:shadow
{:to-prop "box-shadow"
:format #(str/join ", " (map cg/shadow->css (:shadow shape)))}))
(defn shadow-copy-data [shadow]
(cg/generate-css-props
shadow
:style
{:to-prop "box-shadow"
:format #(cg/shadow->css shadow)}))
(mf/defc shadow-block [{:keys [shadow]}]
(let [color-format (mf/use-state :hex)]
[:div.attributes-shadow-block
@ -48,7 +31,7 @@
[:div.attributes-shadow {:title (tr "workspace.options.shadow-options.spread")}
[:div.attributes-value (str (:spread shadow) "px")]]
[:& copy-button {:data (shadow-copy-data shadow)}]]
#_[:& copy-button {:data (shadow-copy-data shadow)}]]
[:& color-row {:color (:color shadow)
:format @color-format

View file

@ -7,76 +7,52 @@
(ns app.main.ui.viewer.inspect.attributes.stroke
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.main.ui.components.copy-button :refer [copy-button]]
[app.main.ui.formats :as fmt]
[app.main.ui.viewer.inspect.attributes.common :refer [color-row]]
[app.util.code-gen :as cg]
[app.util.code-gen.style-css-formats :as cssf]
[app.util.code-gen.style-css-values :as cssv]
[app.util.color :as uc]
[app.util.i18n :refer [tr]]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(defn shape->color [shape]
(defn stroke->color [shape]
{:color (:stroke-color shape)
:opacity (:stroke-opacity shape)
:gradient (:stroke-color-gradient shape)
:id (:stroke-color-ref-id shape)
:file-id (:stroke-color-ref-file shape)})
(defn format-stroke [shape]
(let [width (:stroke-width shape)
style (d/name (:stroke-style shape))
style (if (= style "svg") "solid" style)
color (-> shape shape->color uc/color->background)]
(str/format "%spx %s %s" width style color)))
(defn has-stroke? [shape]
(let [stroke-style (:stroke-style shape)]
(or
(and stroke-style
(and (not= stroke-style :none)
(not= stroke-style :svg)))
(seq (:strokes shape)))))
(defn copy-stroke-data [shape]
(cg/generate-css-props
shape
:stroke-style
{:to-prop "border"
:format #(format-stroke shape)}))
(defn copy-color-data [shape]
(cg/generate-css-props
shape
:stroke-color
{:to-prop "border-color"
:format #(uc/color->background (shape->color shape))}))
(seq (:strokes shape)))
(mf/defc stroke-block
[{:keys [shape]}]
[{:keys [stroke]}]
(let [color-format (mf/use-state :hex)
color (shape->color shape)]
color (stroke->color stroke)]
[:div.attributes-stroke-block
(let [{:keys [stroke-style stroke-alignment]} shape
(let [{:keys [stroke-style stroke-alignment]} stroke
stroke-style (if (= stroke-style :svg) :solid stroke-style)
stroke-alignment (or stroke-alignment :center)]
[:div.attributes-stroke-row
[:div.attributes-label (tr "inspect.attributes.stroke.width")]
[:div.attributes-value (:stroke-width shape) "px"]
[:div.attributes-value (fmt/format-pixels (:stroke-width stroke))]
;; Execution time translation strings:
;; inspect.attributes.stroke.style.dotted
;; inspect.attributes.stroke.style.mixed
;; inspect.attributes.stroke.style.none
;; inspect.attributes.stroke.style.solid
[:div.attributes-value (->> stroke-style d/name (str "inspect.attributes.stroke.style.") (tr))]
[:div.attributes-value (tr (dm/str "inspect.attributes.stroke.style." (d/name stroke-style)))]
;; Execution time translation strings:
;; inspect.attributes.stroke.alignment.center
;; inspect.attributes.stroke.alignment.inner
;; inspect.attributes.stroke.alignment.outer
[:div.attributes-label (->> stroke-alignment d/name (str "inspect.attributes.stroke.alignment.") (tr))]
[:& copy-button {:data (copy-stroke-data shape)}]])
[:div.attributes-label (tr (dm/str "inspect.attributes.stroke.alignment." (d/name stroke-alignment)))]
[:& copy-button {:data (cssf/format-value :border (cssv/get-stroke-data stroke))}]])
[:& color-row {:color color
:format @color-format
:copy-data (copy-color-data shape)
:copy-data (uc/color->background color)
:on-change-format #(reset! color-format %)}]]))
(mf/defc stroke-panel
@ -89,9 +65,6 @@
[:div.attributes-stroke-blocks
(for [shape shapes]
(if (seq (:strokes shape))
(for [value (:strokes shape [])]
[:& stroke-block {:key (str "stroke-color-" (:id shape) value)
:shape value}])
[:& stroke-block {:key (str "stroke-color-only" (:id shape))
:shape shape}]))]])))
(for [value (:strokes shape)]
[:& stroke-block {:key (str "stroke-color-" (:id shape) value)
:stroke value}]))]])))

View file

@ -6,17 +6,14 @@
(ns app.main.ui.viewer.inspect.attributes.text
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.text :as txt]
[app.main.fonts :as fonts]
[app.main.store :as st]
[app.main.ui.components.copy-button :refer [copy-button]]
[app.main.ui.formats :as fmt]
[app.main.ui.viewer.inspect.attributes.common :refer [color-row]]
[app.util.code-gen :as cg]
[app.util.color :as uc]
[app.util.i18n :refer [tr]]
[app.util.strings :as ust]
[cuerdas.core :as str]
[okulary.core :as l]
[rumext.v2 :as mf]))
@ -33,58 +30,23 @@
(get-in state [:viewer-libraries file-id :data :typographies]))]
#(l/derived get-library st/state)))
(defn format-number [number]
(-> number
d/parse-double
(ust/format-precision 2)))
(defn fill->color [{:keys [fill-color fill-opacity fill-color-gradient fill-color-ref-id fill-color-ref-file]}]
{:color fill-color
:opacity fill-opacity
:gradient fill-color-gradient
:id fill-color-ref-id
:file-id fill-color-ref-file})
(def properties [:fill-color
:fill-color-gradient
:font-family
:font-style
:font-size
:font-weight
:line-height
:letter-spacing
:text-decoration
:text-transform])
(mf/defc typography-block
[{:keys [text style]}]
(let [typography-library-ref
(mf/use-memo
(mf/deps (:typography-ref-file style))
(make-typographies-library-ref (:typography-ref-file style)))
(defn shape->color [shape]
{:color (:fill-color shape)
:opacity (:fill-opacity shape)
:gradient (:fill-color-gradient shape)
:id (:fill-color-ref-id shape)
:file-id (:fill-color-ref-file shape)})
(def params
{:to-prop {:fill-color "color"
:fill-color-gradient "color"}
:format {:font-family #(dm/str "'" % "'")
:font-style #(dm/str % )
:font-size #(dm/str (format-number %) "px")
:font-weight d/name
:line-height #(format-number %)
:letter-spacing #(dm/str (format-number %) "px")
:text-decoration d/name
:text-transform d/name
:fill-color #(-> %2 shape->color uc/color->background)
:fill-color-gradient #(-> %2 shape->color uc/color->background)}})
(defn copy-style-data
([style]
(cg/generate-css-props style properties params))
([style & properties]
(cg/generate-css-props style properties params)))
(mf/defc typography-block [{:keys [text style]}]
(let [typography-library-ref (mf/use-memo
(mf/deps (:typography-ref-file style))
(make-typographies-library-ref (:typography-ref-file style)))
typography-library (mf/deref typography-library-ref)
file-typographies (mf/deref file-typographies-ref)
color-format (mf/use-state :hex)
file-typographies (mf/deref file-typographies-ref)
color-format (mf/use-state :hex)
typography (get (or typography-library file-typographies) (:typography-ref-id style))]
@ -98,7 +60,7 @@
:font-style (:font-style typography)}}
(tr "workspace.assets.typography.text-styles")]]
[:div.typography-entry-name (:name typography)]
[:& copy-button {:data (copy-style-data typography)}]]
#_[:& copy-button {:data (copy-style-data typography)}]]
[:div.attributes-typography-row
[:div.typography-sample
@ -106,51 +68,51 @@
:font-weight (:font-weight style)
:font-style (:font-style style)}}
(tr "workspace.assets.typography.text-styles")]
[:& copy-button {:data (copy-style-data style)}]])
#_[:& copy-button {:data (copy-style-data style)}]])
(when (:fills style)
(for [[idx fill] (map-indexed vector (:fills style))]
[:& color-row {:key idx
:format @color-format
:color (shape->color fill)
:copy-data (copy-style-data fill :fill-color :fill-color-gradient)
:color (fill->color fill)
;;:copy-data (copy-style-data fill :fill-color :fill-color-gradient)
:on-change-format #(reset! color-format %)}]))
(when (:font-id style)
[:div.attributes-unit-row
[:div.attributes-label (tr "inspect.attributes.typography.font-family")]
[:div.attributes-value (-> style :font-id fonts/get-font-data :name)]
[:& copy-button {:data (copy-style-data style :font-family)}]])
#_[:& copy-button {:data (copy-style-data style :font-family)}]])
(when (:font-style style)
[:div.attributes-unit-row
[:div.attributes-label (tr "inspect.attributes.typography.font-style")]
[:div.attributes-value (str (:font-style style))]
[:& copy-button {:data (copy-style-data style :font-style)}]])
#_[:& copy-button {:data (copy-style-data style :font-style)}]])
(when (:font-size style)
[:div.attributes-unit-row
[:div.attributes-label (tr "inspect.attributes.typography.font-size")]
[:div.attributes-value (str (format-number (:font-size style))) "px"]
[:& copy-button {:data (copy-style-data style :font-size)}]])
[:div.attributes-value (fmt/format-pixels (:font-size style))]
#_[:& copy-button {:data (copy-style-data style :font-size)}]])
(when (:font-weight style)
[:div.attributes-unit-row
[:div.attributes-label (tr "inspect.attributes.typography.font-weight")]
[:div.attributes-value (str (:font-weight style))]
[:& copy-button {:data (copy-style-data style :font-weight)}]])
#_[:& copy-button {:data (copy-style-data style :font-weight)}]])
(when (:line-height style)
[:div.attributes-unit-row
[:div.attributes-label (tr "inspect.attributes.typography.line-height")]
[:div.attributes-value (format-number (:line-height style))]
[:& copy-button {:data (copy-style-data style :line-height)}]])
[:div.attributes-value (fmt/format-number (:line-height style))]
#_[:& copy-button {:data (copy-style-data style :line-height)}]])
(when (:letter-spacing style)
[:div.attributes-unit-row
[:div.attributes-label (tr "inspect.attributes.typography.letter-spacing")]
[:div.attributes-value (str (format-number (:letter-spacing style))) "px"]
[:& copy-button {:data (copy-style-data style :letter-spacing)}]])
[:div.attributes-value (fmt/format-pixels (:letter-spacing style))]
#_[:& copy-button {:data (copy-style-data style :letter-spacing)}]])
(when (:text-decoration style)
[:div.attributes-unit-row
@ -159,8 +121,8 @@
;; inspect.attributes.typography.text-decoration.none
;; inspect.attributes.typography.text-decoration.strikethrough
;; inspect.attributes.typography.text-decoration.underline
[:div.attributes-value (->> style :text-decoration (str "inspect.attributes.typography.text-decoration.") (tr))]
[:& copy-button {:data (copy-style-data style :text-decoration)}]])
[:div.attributes-value (tr (dm/str "inspect.attributes.typography.text-decoration." (:text-decoration style)))]
#_[:& copy-button {:data (copy-style-data style :text-decoration)}]])
(when (:text-transform style)
[:div.attributes-unit-row
@ -170,8 +132,8 @@
;; inspect.attributes.typography.text-transform.none
;; inspect.attributes.typography.text-transform.titlecase
;; inspect.attributes.typography.text-transform.uppercase
[:div.attributes-value (->> style :text-transform (str "inspect.attributes.typography.text-transform.") (tr))]
[:& copy-button {:data (copy-style-data style :text-transform)}]])
[:div.attributes-value (tr (dm/str "inspect.attributes.typography.text-transform." (:text-transform style)))]
#_[:& copy-button {:data (copy-style-data style :text-transform)}]])
[:div.attributes-content-row
[:pre.attributes-content (str/trim text)]
@ -179,8 +141,8 @@
(mf/defc text-block [{:keys [shape]}]
(let [style-text-blocks (->> (keys txt/default-text-attrs)
(cg/parse-style-text-blocks (:content shape))
(let [style-text-blocks (->> (:content shape)
(txt/content->text+styles)
(remove (fn [[_ text]] (str/empty? (str/trim text))))
(mapv (fn [[style text]] (vector (merge txt/default-text-attrs style) text))))]

View file

@ -133,7 +133,7 @@
style-code
(mf/use-memo
(mf/deps fontfaces-css style-type all-children)
(mf/deps fontfaces-css style-type all-children cg/generate-style-code)
(fn []
(dm/str
fontfaces-css "\n"
@ -144,7 +144,7 @@
(mf/use-memo
(mf/deps markup-type shapes images-data)
(fn []
(-> (cg/generate-markup-code objects markup-type (map :id shapes))
(-> (cg/generate-markup-code objects markup-type shapes)
(format-code markup-type))))
on-markup-copied

View file

@ -36,12 +36,12 @@
:data local})))))
(mf/defc right-sidebar
[{:keys [frame page file selected shapes page-id file-id share-id from on-change-section on-expand]
[{:keys [frame page objects file selected shapes page-id file-id share-id from on-change-section on-expand]
:or {from :inspect}}]
(let [section (mf/use-state :info #_:code)
objects (or objects (:objects page))
shapes (or shapes
(resolve-shapes (:objects page) selected))
(resolve-shapes objects selected))
first-shape (first shapes)
page-id (or page-id (:id page))
file-id (or file-id (:id file))
@ -98,6 +98,7 @@
:selected @section}
[:& tabs-element {:id :info :title (tr "inspect.tabs.info")}
[:& attributes {:page-id page-id
:objects objects
:file-id file-id
:frame frame
:shapes shapes

View file

@ -146,6 +146,7 @@
[:div.element-options.element-options-inspect
[:& hrs/right-sidebar {:page-id page-id
:objects objects
:file-id file-id
:frame shape-parent-frame
:shapes selected-shapes

View file

@ -22,6 +22,7 @@
[app.main.data.workspace.shape-layout :as dwsl]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.cursors :as cur]
[app.main.ui.formats :as fmt]
[app.main.ui.hooks :as hooks]
[app.main.ui.workspace.viewport.viewport-ref :as uwvv]
@ -172,10 +173,6 @@
direction (unchecked-get props "direction")
layout-data (unchecked-get props "layout-data")
cursor
(if (= direction :row)
(cur/scale-ns (:rotation shape))
(cur/scale-ew (:rotation shape)))
handle-drag-position
(mf/use-callback
@ -227,7 +224,11 @@
:y y
:height height
:width width
:style {:fill "transparent" :stroke-width 0 :cursor cursor}
:class (if (= direction :row)
(cur/get-dynamic "scale-ns" (:rotation shape))
(cur/get-dynamic "scale-ew" (:rotation shape)))
:style {:fill "transparent"
:stroke-width 0}
:on-pointer-down handle-pointer-down
:on-lost-pointer-capture handle-lost-pointer-capture
:on-pointer-move handle-pointer-move}]))
@ -464,12 +465,12 @@
:on-lost-pointer-capture handle-lost-pointer-capture
:on-pointer-move handle-pointer-move
:transform (dm/str (gmt/transform-in start-p (:transform shape)))
:class (if (= type :column)
(cur/get-dynamic "resize-ew" (:rotation shape))
(cur/get-dynamic "resize-ns" (:rotation shape)))
:style {:fill "transparent"
:opacity 0.5
:stroke-width 0
:cursor (if (= type :column)
(cur/resize-ew (:rotation shape))
(cur/resize-ns (:rotation shape)))}}]))
:stroke-width 0}}]))
(mf/defc track-marker
{::mf/wrap-props false}
@ -511,11 +512,10 @@
[:g {:on-pointer-down handle-pointer-down
:on-lost-pointer-capture handle-lost-pointer-capture
:on-pointer-move handle-pointer-move
:class (css :grid-track-marker)
:transform (dm/str (gmt/transform-in center (:transform shape)))
:style {:cursor (if (= type :column)
(cur/resize-ew (:rotation shape))
(cur/resize-ns (:rotation shape)))}}
:class (dom/classnames (css :grid-track-marker) true
(cur/get-dynamic "resize-ew" (:rotation shape)) (= type :column)
(cur/get-dynamic "resize-ns" (:rotation shape)) (= type :row))
:transform (dm/str (gmt/transform-in center (:transform shape)))}
[:polygon {:class (css :marker-shape)
:points (->> marker-points

View file

@ -6,553 +6,21 @@
(ns app.util.code-gen
(:require
["react-dom/server" :as rds]
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.pages.helpers :as cph]
[app.common.text :as txt]
[app.common.types.shape.layout :as ctl]
[app.config :as cfg]
[app.main.render :as render]
[app.main.ui.formats :as fmt]
[app.main.ui.shapes.text.html-text :as text]
[app.util.color :as uc]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(defn shadow->css [shadow]
(let [{:keys [style offset-x offset-y blur spread]} shadow
css-color (uc/color->background (:color shadow))]
(dm/str
(if (= style :inner-shadow) "inset " "")
(str/fmt "%spx %spx %spx %spx %s" offset-x offset-y blur spread css-color))))
(defn fill-color->background
[fill]
(cond
(not= (:fill-opacity fill) 1)
(uc/color->background {:color (:fill-color fill)
:opacity (:fill-opacity fill)
:gradient (:fill-color-gradient fill)})
:else
(str/upper (:fill-color fill))))
(defn format-fill-color [_ shape]
(let [fills (:fills shape)
first-fill (first fills)
colors (if (> (count fills) 1)
(map (fn [fill]
(let [color (fill-color->background fill)]
(if (some? (:fill-color-gradient fill))
color
(str/format "linear-gradient(%s,%s)" color color))))
(:fills shape))
[(fill-color->background first-fill)])]
(str/join ", " colors)))
(defn format-stroke [shape]
(let [first-stroke (first (:strokes shape))
width (:stroke-width first-stroke)
style (let [style (:stroke-style first-stroke)]
(when (keyword? style) (d/name style)))
color {:color (:stroke-color first-stroke)
:opacity (:stroke-opacity first-stroke)
:gradient (:stroke-color-gradient first-stroke)}]
(when-not (= :none (:stroke-style first-stroke))
(str/format "%spx %s %s" width style (uc/color->background color)))))
(defn format-position [objects]
(fn [_ shape]
(cond
(and (ctl/any-layout-immediate-child? objects shape)
(not (ctl/layout-absolute? shape))
(or (cph/group-shape? shape)
(cph/frame-shape? shape)))
"relative"
(and (ctl/any-layout-immediate-child? objects shape)
(not (ctl/layout-absolute? shape)))
nil
:else
"absolute")))
(defn mk-grid-coord
[objects prop span-prop]
(fn [_ shape]
(when (ctl/grid-layout-immediate-child? objects shape)
(let [parent (get objects (:parent-id shape))
cell (ctl/get-cell-by-shape-id parent (:id shape))]
(if (> (get cell span-prop) 1)
(dm/str (get cell prop) " / " (+ (get cell prop) (get cell span-prop)))
(get cell prop))))))
(defn get-size
[type values]
(let [value (cond
(number? values) values
(string? values) values
(type values) (type values)
:else (type (:selrect values)))]
(if (= :width type)
(fmt/format-size :width value values)
(fmt/format-size :heigth value values))))
(defn make-format-absolute-pos
[objects shape coord]
(fn [value]
(let [parent-id (dm/get-in objects [(:id shape) :parent-id])
parent-value (dm/get-in objects [parent-id :selrect coord])]
(when-not (or (cph/root-frame? shape)
(ctl/any-layout-immediate-child? objects shape)
(ctl/layout-absolute? shape))
(fmt/format-pixels (- value parent-value))))))
(defn format-tracks
[tracks]
(str/join
" "
(->> tracks (map (fn [{:keys [type value]}]
(case type
:flex (dm/str (fmt/format-number value) "fr")
:percent (fmt/format-percent (/ value 100))
:auto "auto"
(fmt/format-pixels value)))))))
(defn styles-data
[objects shape]
{:position {:props [:type]
:to-prop {:type "position"}
:format {:type (format-position objects)}}
:layout {:props (if (or (empty? (:flex-items shape))
(ctl/layout-absolute? shape))
[:x :y :width :height :radius :rx :r1]
[:width :height :radius :rx :r1])
:to-prop {:x "left"
:y "top"
:rotation "transform"
:rx "border-radius"
:r1 "border-radius"}
:format {:rotation #(str/fmt "rotate(%sdeg)" %)
:r1 #(apply str/fmt "%spx %spx %spx %spx" %)
:width #(get-size :width %)
:height #(get-size :height %)
:x (make-format-absolute-pos objects shape :x)
:y (make-format-absolute-pos objects shape :y)}
:multi {:r1 [:r1 :r2 :r3 :r4]}}
:fill {:props [:fills]
:to-prop {:fills (cond
(or (cph/path-shape? shape)
(cph/mask-shape? shape)
(cph/bool-shape? shape)
(cph/svg-raw-shape? shape)
(some? (:svg-attrs shape)))
nil
(> (count (:fills shape)) 1)
"background-image"
(and (= (count (:fills shape)) 1)
(some? (:fill-color-gradient (first (:fills shape)))))
"background"
:else
"background-color")}
:format {:fills format-fill-color}}
:stroke {:props [:strokes]
:to-prop {:strokes "border"}
:format {:strokes (fn [_ shape]
(when-not (or (cph/path-shape? shape)
(cph/mask-shape? shape)
(cph/bool-shape? shape)
(cph/svg-raw-shape? shape)
(some? (:svg-attrs shape)))
(format-stroke shape)))}}
:shadow {:props [:shadow]
:to-prop {:shadow :box-shadow}
:format {:shadow #(str/join ", " (map shadow->css %1))}}
:blur {:props [:blur]
:to-prop {:blur "filter"}
:format {:blur #(str/fmt "blur(%spx)" (:value %))}}
:layout-flex {:props [:layout
:layout-flex-dir
:layout-align-items
:layout-justify-items
:layout-align-content
:layout-justify-content
:layout-gap
:layout-padding
:layout-wrap-type]
:gen-props [:flex-shrink]
:to-prop {:layout "display"
:layout-flex-dir "flex-direction"
:layout-align-items "align-items"
:layout-align-content "align-content"
:layout-justify-items "justify-items"
:layout-justify-content "justify-content"
:layout-wrap-type "flex-wrap"
:layout-gap "gap"
:layout-padding "padding"}
:format {:layout d/name
:layout-flex-dir d/name
:layout-align-items d/name
:layout-align-content d/name
:layout-justify-items d/name
:layout-justify-content d/name
:layout-wrap-type d/name
:layout-gap fmt/format-gap
:layout-padding fmt/format-padding
:flex-shrink (fn [_ shape] (when (ctl/flex-layout-immediate-child? objects shape) 0))}}
:layout-grid {:props [:layout-grid-rows
:layout-grid-columns]
:gen-props [:grid-column
:grid-row]
:to-prop {:layout-grid-rows "grid-template-rows"
:layout-grid-columns "grid-template-columns"}
:format {:layout-grid-rows format-tracks
:layout-grid-columns format-tracks
:grid-column (mk-grid-coord objects :column :column-span)
:grid-row (mk-grid-coord objects :row :row-span)}}})
(def style-text
{:props [:fills
:font-family
:font-style
:font-size
:font-weight
:line-height
:letter-spacing
:text-decoration
:text-transform]
:to-prop {:fills "color"}
:format {:font-family #(dm/str "'" % "'")
:font-style #(dm/str %)
:font-size #(dm/str % "px")
:font-weight #(dm/str %)
:line-height #(dm/str %)
:letter-spacing #(dm/str % "px")
:text-decoration d/name
:text-transform d/name
:fills format-fill-color}})
(def layout-flex-item-params
{:props [:layout-item-margin
:layout-item-max-h
:layout-item-min-h
:layout-item-max-w
:layout-item-min-w
:layout-item-align-self]
:to-prop {:layout-item-margin "margin"
:layout-item-max-h "max-height"
:layout-item-min-h "min-height"
:layout-item-max-w "max-width"
:layout-item-min-w "min-width"
:layout-item-align-self "align-self"}
:format {:layout-item-margin fmt/format-margin
:layout-item-max-h #(dm/str % "px")
:layout-item-min-h #(dm/str % "px")
:layout-item-max-w #(dm/str % "px")
:layout-item-min-w #(dm/str % "px")
:layout-item-align-self d/name}})
(def layout-align-content
{:props [:layout-align-content]
:to-prop {:layout-align-content "align-content"}
:format {:layout-align-content d/name}})
(defn get-specific-value
[values prop]
(let [result (if (get values prop)
(get values prop)
(get (:selrect values) prop))
result (if (= :width prop)
(get-size :width values)
result)
result (if (= :height prop)
(get-size :height values)
result)]
result))
(defn generate-css-props
([values properties]
(generate-css-props values properties [] nil))
([values properties gen-properties]
(generate-css-props values properties gen-properties nil))
([values properties gen-properties params]
(let [{:keys [to-prop format tab-size multi]
:or {to-prop {} tab-size 0 multi {}}} params
;; We allow the :format and :to-prop to be a map for different properties
;; or just a value for a single property. This code transform a single
;; property to a uniform one
properties (if-not (coll? properties) [properties] properties)
format (if (not (map? format))
(into {} (map #(vector % format) properties))
format)
to-prop (if (not (map? to-prop))
(into {} (map #(vector % to-prop) properties))
to-prop)
get-value (fn [prop]
(if-let [props (get multi prop)]
(map #(get values %) props)
(get-specific-value values prop)))
null? (fn [value]
(if (coll? value)
(every? #(or (nil? %) (= % 0)) value)
(or (nil? value) (= value 0))))
default-format (fn [value] (dm/str (fmt/format-pixels value)))
format-property
(fn [prop]
(let [css-prop (or (get to-prop prop) (d/name prop))
format-fn (or (get format prop) default-format)
css-val (format-fn (get-value prop) values)]
(when (and css-val (not= css-val ""))
(dm/str
(str/repeat " " tab-size)
(dm/fmt "%: %;" css-prop css-val)))))]
(->> (concat
(->> properties
(remove #(null? (get-value %))))
gen-properties)
(keep format-property)
(str/join "\n")))))
(defn shape->properties [objects shape]
(let [;; This property is added in an earlier step (code.cljs),
;; it will come with a vector of flex-items if any.
;; If there are none it will continue as usual.
flex-items (:flex-items shape)
props (->> (styles-data objects shape) vals (mapcat :props))
to-prop (->> (styles-data objects shape) vals (map :to-prop) (reduce merge))
format (->> (styles-data objects shape) vals (map :format) (reduce merge))
multi (->> (styles-data objects shape) vals (map :multi) (reduce merge))
gen-props (->> (styles-data objects shape) vals (mapcat :gen-props))
props (cond-> props
(seq flex-items) (concat (:props layout-flex-item-params))
(= :wrap (:layout-wrap-type shape)) (concat (:props layout-align-content)))
to-prop (cond-> to-prop
(seq flex-items) (merge (:to-prop layout-flex-item-params))
(= :wrap (:layout-wrap-type shape)) (merge (:to-prop layout-align-content)))
format (cond-> format
(seq flex-items) (merge (:format layout-flex-item-params))
(= :wrap (:layout-wrap-type shape)) (merge (:format layout-align-content)))]
(generate-css-props
shape
props
gen-props
{:to-prop to-prop
:format format
:multi multi
:tab-size 2})))
(defn search-text-attrs
[node attrs]
(->> (txt/node-seq node)
(map #(select-keys % attrs))
(reduce d/merge)))
;; TODO: used on inspect
(defn parse-style-text-blocks
[node attrs]
(letfn
[(rec-style-text-map [acc node style]
(let [node-style (merge style (select-keys node attrs))
head (or (-> acc first) [{} ""])
[head-style head-text] head
new-acc
(cond
(:children node)
(reduce #(rec-style-text-map %1 %2 node-style) acc (:children node))
(not= head-style node-style)
(cons [node-style (:text node "")] acc)
:else
(cons [node-style (dm/str head-text "" (:text node))] (rest acc)))
;; We add an end-of-line when finish a paragraph
new-acc
(if (= (:type node) "paragraph")
(let [[hs ht] (first new-acc)]
(cons [hs (dm/str ht "\n")] (rest new-acc)))
new-acc)]
new-acc))]
(-> (rec-style-text-map [] node {})
reverse)))
(defn text->properties [objects shape]
(let [flex-items (:flex-items shape)
text-shape-style (d/without-keys (styles-data objects shape) [:fill :stroke])
shape-props (->> text-shape-style vals (mapcat :props))
shape-to-prop (->> text-shape-style vals (map :to-prop) (reduce merge))
shape-format (->> text-shape-style vals (map :format) (reduce merge))
shape-props (cond-> shape-props
(seq flex-items) (concat (:props layout-flex-item-params)))
shape-to-prop (cond-> shape-to-prop
(seq flex-items) (merge (:to-prop layout-flex-item-params)))
shape-format (cond-> shape-format
(seq flex-items) (merge (:format layout-flex-item-params)))
text-values (->> (search-text-attrs
(:content shape)
(conj (:props style-text) :fill-color-gradient :fill-opacity))
(d/merge txt/default-text-attrs))]
(str/join
"\n"
[(generate-css-props shape
shape-props
{:to-prop shape-to-prop
:format shape-format
:tab-size 2})
(generate-css-props text-values
(:props style-text)
{:to-prop (:to-prop style-text)
:format (:format style-text)
:tab-size 2})])))
(defn selector-name [shape]
(let [name (-> (:name shape)
(subs 0 (min 10 (count (:name shape)))))
;; selectors cannot start with numbers
name (if (re-matches #"^\d.*" name) (dm/str "c-" name) name)
id (-> (dm/str (:id shape))
#_(subs 24 36))
selector (str/css-selector (dm/str name " " id))
selector (if (str/starts-with? selector "-") (subs selector 1) selector)]
selector))
(defn generate-css [objects shape]
(let [name (:name shape)
properties (shape->properties objects shape)
selector (selector-name shape)]
(str/join "\n" [(str/fmt "/* %s */" name)
(str/fmt ".%s {" selector)
properties
"}"])))
(defn generate-svg
[objects shape-id]
(let [shape (get objects shape-id)]
(rds/renderToStaticMarkup
(mf/element
render/object-svg
#js {:objects objects
:object-id (-> shape :id)}))))
(defn generate-html
([objects shape-id]
(generate-html objects shape-id 0))
([objects shape-id level]
(let [shape (get objects shape-id)
indent (str/repeat " " level)
maybe-reverse (if (ctl/any-layout? shape) reverse identity)]
(cond
(cph/text-shape? shape)
(let [text-shape-html (rds/renderToStaticMarkup (mf/element text/text-shape #js {:shape shape :code? true}))]
(dm/fmt "%<div class=\"%\">\n%\n%</div>"
indent
(selector-name shape)
text-shape-html
indent))
(cph/image-shape? shape)
(let [data (or (:metadata shape) (:fill-image shape))
image-url (cfg/resolve-file-media data)]
(dm/fmt "%<img src=\"%\" class=\"%\">\n%</img>"
indent
image-url
(selector-name shape)
indent))
(or (cph/path-shape? shape)
(cph/mask-shape? shape)
(cph/bool-shape? shape)
(cph/svg-raw-shape? shape)
(some? (:svg-attrs shape)))
(let [svg-markup (rds/renderToStaticMarkup (mf/element render/object-svg #js {:objects objects :object-id (:id shape) :render-embed? false}))]
(dm/fmt "%<div class=\"%\">\n%\n%</div>"
indent
(selector-name shape)
svg-markup
indent))
(empty? (:shapes shape))
(dm/fmt "%<div class=\"%\">\n%</div>"
indent
(selector-name shape)
indent)
:else
(dm/fmt "%<div class=\"%\">\n%\n%</div>"
indent
(selector-name shape)
(->> (:shapes shape)
(maybe-reverse)
(map #(generate-html objects % (inc level)))
(str/join "\n"))
indent)))))
(defn generate-markup-code [objects type shapes]
(let [generate-markup-fn (case type
"html" generate-html
"svg" generate-svg)]
(->> shapes
(map #(generate-markup-fn objects % 0))
(str/join "\n"))))
(defn generate-style-code [objects type shapes]
(let [generate-style-fn (case type
"css" generate-css)]
(dm/str
"html, body {
background-color: #E8E9EA;
height: 100%;
margin: 0;
padding: 0;
width: 100%;
}
body {
display: flex;
flex-direction: column;
align-items: center;
padding: 2rem;
}
svg {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
* {
box-sizing: border-box;
}
\n"
(->> shapes
(map (partial generate-style-fn objects))
(str/join "\n\n")))))
[app.util.code-gen.markup-html :as html]
[app.util.code-gen.markup-svg :as svg]
[app.util.code-gen.style-css :as css]))
(defn generate-markup-code
[objects type shapes]
(let [generate-markup
(case type
"html" html/generate-markup
"svg" svg/generate-markup)]
(generate-markup objects shapes)))
(defn generate-style-code
[objects type shapes]
(let [generate-style
(case type
"css" css/generate-style)]
(generate-style objects shapes)))

View file

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

View file

@ -0,0 +1,78 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.util.code-gen.markup-html
(:require
["react-dom/server" :as rds]
[app.common.data.macros :as dm]
[app.common.pages.helpers :as cph]
[app.common.types.shape.layout :as ctl]
[app.config :as cfg]
[app.main.ui.shapes.text.html-text :as text]
[app.util.code-gen.common :as cgc]
[app.util.code-gen.markup-svg :refer [generate-svg]]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(defn generate-html
([objects shape]
(generate-html objects shape 0))
([objects shape level]
(let [indent (str/repeat " " level)
maybe-reverse (if (ctl/any-layout? shape) reverse identity)]
(cond
(cph/text-shape? shape)
(let [text-shape-html (rds/renderToStaticMarkup (mf/element text/text-shape #js {:shape shape :code? true}))]
(dm/fmt "%<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))
(or (cph/path-shape? shape)
(cph/mask-shape? shape)
(cph/bool-shape? shape)
(cph/svg-raw-shape? shape)
(some? (:svg-attrs shape)))
(let [svg-markup (generate-svg objects shape)]
(dm/fmt "%<div class=\"%\">\n%\n%</div>"
indent
(cgc/shape->selector shape)
svg-markup
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)))))
(defn generate-markup
[objects shapes]
(->> shapes
(map #(generate-html objects %))
(str/join "\n")))

View file

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

View file

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

View file

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

View file

@ -0,0 +1,282 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.util.code-gen.style-css-values
(:require
[app.common.data.macros :as dm]
[app.common.geom.matrix :as gmt]
[app.common.pages.helpers :as cph]
[app.common.types.shape.layout :as ctl]))
(defn fill->color
[{:keys [fill-color fill-opacity fill-color-gradient]}]
{:color fill-color
:opacity fill-opacity
:gradient fill-color-gradient})
(defmulti get-value
(fn [property _shape _objects] property))
(defmethod get-value :position
[_ shape objects]
(cond
(and (ctl/any-layout-immediate-child? objects shape)
(not (ctl/layout-absolute? shape))
(or (cph/group-shape? shape)
(cph/frame-shape? shape)))
:relative
(and (ctl/any-layout-immediate-child? objects shape)
(not (ctl/layout-absolute? shape)))
nil
:else
:absolute))
(defn get-shape-position
[shape objects coord]
(let [shape-value (-> shape :selrect coord)
parent-value (dm/get-in objects [(:parent-id shape) :selrect coord])]
(when-not (or (cph/root-frame? shape)
(ctl/any-layout-immediate-child? objects shape)
(ctl/layout-absolute? shape))
(- shape-value parent-value))))
(defmethod get-value :left
[_ shape objects]
(get-shape-position shape objects :x))
(defmethod get-value :top
[_ shape objects]
(get-shape-position shape objects :y))
(defn get-shape-size
[shape type]
(let [sizing (if (= type :width)
(:layout-item-h-sizing shape)
(:layout-item-v-sizing shape))]
(cond
(or (= sizing :fill) (= sizing :auto))
sizing
(some? (:selrect shape))
(-> shape :selrect type)
(some? (get shape type))
(get shape type))))
(defmethod get-value :width
[_ shape _]
(get-shape-size shape :width))
(defmethod get-value :height
[_ shape _]
(get-shape-size shape :height))
(defmethod get-value :transform
[_ {:keys [transform] :as shape} _]
(when (and (some? transform) (not (gmt/unit? transform)))
(dm/str transform)))
(defn background?
[shape]
(and (not (cph/path-shape? shape))
(not (cph/mask-shape? shape))
(not (cph/bool-shape? shape))
(not (cph/svg-raw-shape? shape))
(nil? (:svg-attrs shape))))
(defmethod get-value :background
[_ {:keys [fills] :as shape} _]
(let [single-fill? (= (count fills) 1)
ffill (first fills)
gradient? (some? (:fill-color-gradient ffill))]
(when (and (background? shape) single-fill? gradient?)
(fill->color ffill))))
(defmethod get-value :background-color
[_ {:keys [fills] :as shape} _]
(let [single-fill? (= (count fills) 1)
ffill (first fills)
gradient? (some? (:fill-color-gradient ffill))]
(when (and (background? shape) single-fill? (not gradient?))
(fill->color ffill))))
(defmethod get-value :background-image
[_ {:keys [fills] :as shape} _]
(when (and (background? shape) (> (count fills) 1))
(->> fills
(map fill->color))))
(defn get-stroke-data
[stroke]
(let [width (:stroke-width stroke)
style (:stroke-style stroke)
color {:color (:stroke-color stroke)
:opacity (:stroke-opacity stroke)
:gradient (:stroke-color-gradient stroke)}]
(when (and (some? stroke) (not= :none (:stroke-style stroke)))
{:color color
:style style
:width width})))
(defmethod get-value :border
[_ shape _]
(get-stroke-data (first (:strokes shape))))
(defmethod get-value :border-radius
[_ {:keys [rx r1 r2 r3 r4]} _]
(cond
(some? rx)
[rx]
(every? some? [r1 r2 r3 r4])
[r1 r2 r3 r4]))
(defmethod get-value :box-shadow
[_ shape _]
(:shadow shape))
(defmethod get-value :filter
[_ shape _]
(get-in shape [:blur :value]))
(defmethod get-value :display
[_ shape _]
(cond
(ctl/flex-layout? shape) "flex"
(ctl/grid-layout? shape) "grid"))
(defmethod get-value :flex-direction
[_ shape _]
(:layout-flex-dir shape))
(defmethod get-value :align-items
[_ shape _]
(:layout-align-items shape))
(defmethod get-value :align-content
[_ shape _]
(:layout-align-content shape))
(defmethod get-value :justify-items
[_ shape _]
(:layout-justify-items shape))
(defmethod get-value :justify-content
[_ shape _]
(:layout-justify-content shape))
(defmethod get-value :flex-wrap
[_ shape _]
(:layout-wrap-type shape))
(defmethod get-value :gap
[_ shape _]
(let [[g1 g2] (ctl/gaps shape)]
(when (or (not= g1 0) (not= g2 0))
[g1 g2])))
(defmethod get-value :padding
[_ {:keys [layout-padding]} _]
(when (some? layout-padding)
(let [default-padding {:p1 0 :p2 0 :p3 0 :p4 0}
{:keys [p1 p2 p3 p4]} (merge default-padding layout-padding)]
(when (or (not= p1 0) (not= p2 0) (not= p3 0) (not= p4 0))
[p1 p2 p3 p4]))))
(defmethod get-value :grid-template-rows
[_ shape _]
(:layout-grid-rows shape))
(defmethod get-value :grid-template-columns
[_ shape _]
(:layout-grid-columns shape))
(defn get-grid-coord
[shape objects prop span-prop]
(when (ctl/grid-layout-immediate-child? objects shape)
(let [parent (get objects (:parent-id shape))
cell (ctl/get-cell-by-shape-id parent (:id shape))]
(if (> (get cell span-prop) 1)
(dm/str (get cell prop) " / " (+ (get cell prop) (get cell span-prop)))
(get cell prop)))))
(defmethod get-value :grid-column
[_ shape objects]
(get-grid-coord shape objects :column :column-span))
(defmethod get-value :grid-row
[_ shape objects]
(get-grid-coord shape objects :row :row-span))
(defmethod get-value :flex-shrink
[_ shape objects]
(when (and (ctl/flex-layout-immediate-child? objects shape)
(not= :fill (:layout-item-h-sizing shape))
(not= :fill (:layout-item-v-sizing shape))
(not= :auto (:layout-item-h-sizing shape))
(not= :auto (:layout-item-v-sizing shape)))
0))
(defmethod get-value :margin
[_ shape objects]
(cond
(ctl/flex-layout-immediate-child? objects shape)
(:layout-item-margin shape)))
(defmethod get-value :max-height
[_ shape objects]
(cond
(ctl/flex-layout-immediate-child? objects shape)
(:layout-item-max-h shape)))
(defmethod get-value :min-height
[_ shape objects]
(cond
(ctl/flex-layout-immediate-child? objects shape)
(:layout-item-min-h shape)))
(defmethod get-value :max-width
[_ shape objects]
(cond
(ctl/flex-layout-immediate-child? objects shape)
(:layout-item-max-w shape)))
(defmethod get-value :min-width
[_ shape objects]
(cond
(ctl/flex-layout-immediate-child? objects shape)
(:layout-item-min-w shape)))
(defmethod get-value :align-self
[_ shape objects]
(cond
(ctl/flex-layout-immediate-child? objects shape)
(:layout-item-align-self shape)
(ctl/grid-layout-immediate-child? objects shape)
(let [parent (get objects (:parent-id shape))
cell (ctl/get-cell-by-shape-id parent (:id shape))
align-self (:align-self cell)]
(when (not= align-self :auto) align-self))))
(defmethod get-value :justify-self
[_ shape objects]
(cond
(ctl/grid-layout-immediate-child? objects shape)
(let [parent (get objects (:parent-id shape))
cell (ctl/get-cell-by-shape-id parent (:id shape))
justify-self (:justify-self cell)]
(when (not= justify-self :auto) justify-self))))
(defmethod get-value :default
[property shape _]
(get shape property))