0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-03-13 00:01:51 -05:00
penpot/frontend/src/app/util/code_gen/style_css.cljs
2023-10-20 13:04:33 +02:00

312 lines
8.3 KiB
Clojure

;; 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.geom.matrix :as gmt]
[app.common.geom.shapes.bounds :as gsb]
[app.common.geom.shapes.points :as gpo]
[app.common.pages.helpers :as cph]
[app.common.text :as txt]
[app.common.types.shape.layout :as ctl]
[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 format-shadow]]
[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 {
margin: 0;
min-height: 100%;
min-width: 100%;
padding: 0;
}
body {
display: flex;
flex-direction: column;
align-items: center;
padding: 2rem;
gap: 2rem;
}
* {
box-sizing: border-box;
}
.text-node { background-clip: text !important; -webkit-background-clip: text !important; }
")
(def shape-wrapper-css-properties
#{:flex-shrink
:margin
:max-height
:min-height
:max-width
:min-width
:align-self
:justify-self
:grid-column
:grid-row
:z-index
:top
:left
:position})
(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
:z-index
;; Flex related properties
:flex-direction
:flex-wrap
:flex
;; Grid related properties
:grid-template-rows
:grid-template-columns
:grid-template-areas
;; 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
:grid-area])
(defn shape->css-property
[shape objects property]
(when-let [value (get-value property shape objects)]
[property value]))
(defn shape->wrapper-css-properties
[shape objects]
(when (and (ctl/any-layout-immediate-child? objects shape)
(not (gmt/unit? (:transform shape))))
(let [parent (get objects (:parent-id shape))
bounds (gpo/parent-coords-bounds (:points shape) (:points parent))
width (gpo/width-points bounds)
height (gpo/height-points bounds)]
(cond-> [[:width width]
[:height height]]
(or (not (ctl/any-layout-immediate-child? objects shape))
(not (ctl/layout-absolute? shape)))
(conj [:position "relative"])))))
(defn shape->wrapper-child-css-properties
[shape objects]
(when (and (ctl/any-layout-immediate-child? objects shape) (not (gmt/unit? (:transform shape))))
[[:position "absolute"]
[:left "50%"]
[:top "50%"]]))
(defn shape->svg-props
[shape objects]
(let [bounds (gsb/get-object-bounds objects shape)]
[[:position "absolute"]
[:top 0]
[:left 0]
[:transform (dm/fmt "translate(%,%)"
(dm/str (- (:x bounds) (-> shape :selrect :x)) "px")
(dm/str (- (:y bounds) (-> shape :selrect :y)) "px"))]]))
(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]
(when properties
(->> 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]
(when (and (some? shape) (some? (:selrect shape)))
(let [selector (cgc/shape->selector shape)
wrapper? (cgc/has-wrapper? objects shape)
svg? (cgc/svg-markup? shape)
css-properties
(if wrapper?
(filter (complement shape-wrapper-css-properties) shape-css-properties)
shape-css-properties)
properties
(-> shape
(shape->css-properties objects css-properties)
(format-css-properties options))
wrapper-properties
(when wrapper?
(-> (d/concat-vec
(shape->css-properties shape objects shape-wrapper-css-properties)
(shape->wrapper-css-properties shape objects))
(format-css-properties options)))
wrapper-child-properties
(when wrapper?
(-> shape
(shape->wrapper-child-css-properties objects)
(format-css-properties options)))
svg-child-props
(when svg?
(-> shape
(shape->svg-props objects)
(format-css-properties options)))]
(str/join
"\n"
(filter some? [(str/fmt "/* %s */" (:name shape))
(when wrapper? (str/fmt ".%s-wrapper {\n%s\n}" selector wrapper-properties))
(when wrapper? (str/fmt ".%s-wrapper > * {\n%s\n}" selector wrapper-child-properties))
(when svg? (str/fmt ".%s > svg {\n%s\n}" selector svg-child-props))
(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
(keep #(get-shape-css-selector % objects options))
(str/join "\n\n")))))
(defn shadow->css
[shadow]
(dm/str "box-shadow: " (format-shadow shadow {})))