0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-10 09:08:31 -05:00

CSS code generation first draft

This commit is contained in:
alonso.torres 2020-10-29 17:04:16 +01:00 committed by Hirunatan
parent 5d6b07f2a7
commit 28f90da70e
10 changed files with 237 additions and 108 deletions

View file

@ -978,6 +978,12 @@
"en" : "Left"
}
},
"handoff.attributes.layout.radius" : {
"used-in" : [ "src/app/main/ui/viewer/handoff/attributes/layout.cljs:60" ],
"translations" : {
"en" : "Radius"
}
},
"handoff.attributes.layout.rotation" : {
"used-in" : [ "src/app/main/ui/viewer/handoff/attributes/layout.cljs:60" ],
"translations" : {

View file

@ -264,8 +264,10 @@
}
}
.code-block {
margin-top: 0.5rem;
border-top: 1px solid $color-gray-60;
.code-row-lang {
position: relative;
display: flex;
@ -307,6 +309,7 @@
overflow: hidden;
white-space: pre-wrap;
background: $color-gray-60;
user-select: text;
.hljs-attr {
color: #a6e22e;
@ -319,5 +322,8 @@
}
}
}
}
.element-options :first-child {
border-top: none;
}

View file

@ -97,7 +97,7 @@
(mf/defc layer-blur-filter
[{:keys [filter-id params]}]
[:feGaussianBlur {:stdDeviation (/ (:value params) 2)
[:feGaussianBlur {:stdDeviation (:value params)
:result filter-id}])
(mf/defc image-fix-filter [{:keys [filter-id]}]

View file

@ -18,27 +18,6 @@
[app.main.ui.shapes.gradients :as grad]
[app.main.ui.context :as muc]))
(mf/defc background-blur [{:keys [shape]}]
(when-let [background-blur-filters (->> shape :blur (remove #(= (:type %) :layer-blur)) (remove :hidden))]
(for [filter background-blur-filters]
[:*
[:foreignObject {:key (str "blur_" (:id filter))
:pointerEvents "none"
:x (:x shape)
:y (:y shape)
:width (:width shape)
:height (:height shape)
:transform (geom/transform-matrix shape)}
[:style ""]
[:div.backround-blur
{:style {:width "100%"
:height "100%"
;; :backdrop-filter (str/format "blur(%spx)" (:value filter))
:filter (str/format "blur(4px")
}}]]])))
(mf/defc shape-container
{::mf/wrap-props false}
[props]
@ -51,23 +30,12 @@
(obj/clone)
(obj/without ["shape" "children"])
(obj/set! "className" "shape")
(obj/set! "data-type" (:type shape))
(obj/set! "filter" (filters/filter-str filter-id shape)))
;;group-props (if (seq (:blur shape))
;; (obj/set! group-props "clip-path" (str/fmt "url(#%s)" (str "blur_" render-id)))
;; group-props)
]
(obj/set! "filter" (filters/filter-str filter-id shape)))]
[:& (mf/provider muc/render-ctx) {:value render-id}
[:> :g group-props
[:defs
[:& filters/filters {:shape shape :filter-id filter-id}]
[:& grad/gradient {:shape shape :attr :fill-color-gradient}]
[:& grad/gradient {:shape shape :attr :stroke-color-gradient}]
#_(when (:blur shape)
[:clipPath {:id (str "blur_" render-id)}
children])]
[:& background-blur {:shape shape}]
[:& grad/gradient {:shape shape :attr :stroke-color-gradient}]]
children]]))

View file

@ -16,38 +16,13 @@
[app.util.color :as uc]
[app.common.math :as mth]
[app.main.ui.icons :as i]
[app.util.code-gen :as code]
[app.util.webapi :as wapi]
[app.main.ui.components.color-bullet :refer [color-bullet color-name]]))
(defn copy-cb [values properties & {:keys [to-prop format] :or {to-prop {}}}]
(defn copy-cb [values properties & {:keys [to-prop format] :as params}]
(fn [event]
(let [
;; 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)
default-format (fn [value] (str (mth/precision value 2) "px"))
format-property (fn [prop]
(let [css-prop (or (prop to-prop) (name prop))]
(str/fmt " %s: %s;" css-prop ((or (prop format) default-format) (prop values) values))))
text-props (->> properties
(remove #(let [value (get values %)]
(or (nil? value) (= value 0))))
(map format-property)
(str/join "\n"))
result (str/fmt "{\n%s\n}" text-props)]
(let [result (code/generate-css-props values properties params)]
(wapi/write-to-clipboard result))))
(mf/defc color-row [{:keys [color format on-copy on-change-format]}]
@ -79,3 +54,4 @@
(when on-copy
[:button.attributes-copy-button {:on-click on-copy} i/copy])]))

View file

@ -18,8 +18,8 @@
(defn copy-layout [shape]
(copy-cb shape
[:width :height :x :y :rotation]
:to-prop {:x "left" :y "top" :rotation "transform"}
[:width :height :x :y :radius :rx]
:to-prop {:x "left" :y "top" :rotation "transform" :rx "border-radius"}
:format {:rotation #(str/fmt "rotate(%sdeg)" %)}))
(mf/defc layout-block
@ -55,6 +55,14 @@
{:on-click (copy-cb shape :y :to-prop "top")}
i/copy]])
(when (not= (:rx shape) 0)
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.layout.radius")]
[:div.attributes-value (mth/precision (:rx shape) 2) "px"]
[:button.attributes-copy-button
{:on-click (copy-cb shape :rx :to-prop "border-radius")}
i/copy]])
(when (not= (:rotation shape 0) 0)
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.layout.rotation")]

View file

@ -12,21 +12,13 @@
[rumext.alpha :as mf]
[cuerdas.core :as str]
[app.util.i18n :refer [t]]
[app.util.color :as uc]
[app.util.code-gen :as cg]
[app.main.ui.icons :as i]
[app.main.ui.viewer.handoff.attributes.common :refer [copy-cb color-row]]))
(defn has-shadow? [shape]
(:shadow shape))
(defn shadow->css [shadow]
(let [{:keys [style offset-x offset-y blur spread]} shadow
css-color (uc/color->background (:color shadow))]
(str
(if (= style :inner-shadow) "inset " "")
(str/fmt "%spx %spx %spx %spx %s" offset-x offset-y blur spread css-color))))
(mf/defc shadow-block [{:keys [shape locale shadow]}]
(let [color-format (mf/use-state :hex)]
[:div.attributes-shadow-block
@ -52,7 +44,7 @@
{:on-click (copy-cb shadow
:style
:to-prop "box-shadow"
:format #(shadow->css shadow))}
:format #(cg/shadow->css shadow))}
i/copy]]
[:& color-row {:color (:color shadow)
:format @color-format
@ -64,7 +56,7 @@
(copy-cb (first shapes)
:shadow
:to-prop "box-shadow"
:format #(str/join ", " (map shadow->css (:shadow (first shapes))))))]
:format #(str/join ", " (map cg/shadow->css (:shadow (first shapes))))))]
(when (seq shapes)
[:div.attributes-block
[:div.attributes-block-title

View file

@ -10,31 +10,33 @@
(ns app.main.ui.viewer.handoff.code
(:require
["highlight.js" :as hljs]
[cuerdas.core :as str]
[rumext.alpha :as mf]
[app.util.i18n :as i18n]
[app.util.color :as uc]
[app.util.webapi :as wapi]
[app.util.code-gen :as cg]
[app.main.ui.icons :as i]
[app.common.geom.shapes :as gsh]))
(def css-example
"/* text layer name */
.shape {
width: 142px;
height: 40px;
border-radius: 20px;
background-color: var(--tiffany-blue);
}")
(def svg-example
"<g class=\"shape\">
<rect fill=\"#ffffff\" fill-opacity=\"1\" x=\"629\" y=\"169\" id=\"shape-eee5fa10-5336-11ea-
8394-2dd26e322db3\" width=\"176\" height=\"211\">
</rect>
</g>")
"<rect
x=\"629\"
y=\"169\"
width=\"176\"
height=\"211\"
fill=\"#ffffff\"
fill-opacity=\"1\">
</rect>")
(defn generate-markup-code [type shapes]
svg-example)
(mf/defc code-block [{:keys [code type]}]
(let [block-ref (mf/use-ref)]
(mf/use-effect
(mf/deps block-ref)
(mf/deps code type block-ref)
(fn []
(hljs/highlightBlock (mf/ref-val block-ref))))
[:pre.code-display {:class type
@ -42,39 +44,46 @@
(mf/defc code
[{:keys [shapes frame]}]
(let [locale (mf/deref i18n/locale)
(let [style-type (mf/use-state "css")
markup-type (mf/use-state "svg")
locale (mf/deref i18n/locale)
shapes (->> shapes
(map #(gsh/translate-to-frame % frame)))]
(map #(gsh/translate-to-frame % frame)))
style-code (cg/generate-style-code @style-type shapes)
markup-code (generate-markup-code @markup-type shapes)]
[:div.element-options
[:div.code-block
[:div.code-row-lang
[:select.code-selection
[:option "CSS"]
[:option "SASS"]
[:option "Less"]
[:option "Stylus"]]
[:option {:value "css"} "CSS"]
#_[:option {:value "sass"} "SASS"]
#_[:option {:value "less"} "Less"]
#_[:option {:value "stylus"} "Stylus"]]
[:button.attributes-copy-button
{:on-click #(prn "??")}
{:on-click #(wapi/write-to-clipboard style-code)}
i/copy]]
[:div.code-row-display
[:& code-block {:type "css"
:code css-example}]]]
[:& code-block {:type @style-type
:code style-code}]]]
[:div.code-block
[:div.code-row-lang
[:select.code-selection
[:option "SVG"]
[:option "HTML"]]
#_[:option "HTML"]]
[:button.attributes-copy-button
{:on-click #(prn "??")}
{:on-click #(wapi/write-to-clipboard markup-code)}
i/copy]]
[:div.code-row-display
[:& code-block {:type "svg"
:code svg-example}]]]
[:& code-block {:type @markup-type
:code markup-code}]]]
]))

View file

@ -33,7 +33,7 @@
(mf/defc right-sidebar
[{:keys [frame]}]
(let [locale (mf/deref i18n/locale)
section (mf/use-state :info #_:code)
section (mf/use-state #_:info :code)
selected-ref (mf/use-memo (make-selected-shapes-iref))
shapes (mf/deref selected-ref)]
[:aside.settings-bar.settings-bar-right

View file

@ -0,0 +1,164 @@
;; 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/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns app.util.code-gen
(:require
[cuerdas.core :as str]
[app.common.math :as mth]
[app.util.text :as ut]
[app.util.color :as uc]))
(declare format-fill-color)
(declare format-stroke)
(declare shadow->css)
(def styles-data
{:layout {:props [:width :height :x :y :radius :rx]
:to-prop {:x "left" :y "top" :rotation "transform" :rx "border-radius"}
:format {:rotation #(str/fmt "rotate(%sdeg)" %)}}
:fill {:props [:fill-color :fill-color-gradient]
:to-prop {:fill-color "background" :fill-color-gradient "background"}
:format {:fill-color format-fill-color :fill-color-gradient format-fill-color}}
:stroke {:props [:stroke-color]
:to-prop {:stroke-color "border"}
:format {:stroke-color 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 %))}}})
(def style-text
{:props [:fill-color
:font-family
:font-style
:font-size
:line-height
:letter-spacing
:text-decoration
:text-transform]
:to-prop {:fill-color "color" }
:format {:font-family #(str "'" % "'")
:font-style #(str "'" % "'")
:font-size #(str % "px")
:line-height #(str % "px")
:letter-spacing #(str % "px")
:text-decoration name
:text-transform name
:fill-color format-fill-color}})
(defn shadow->css [shadow]
(let [{:keys [style offset-x offset-y blur spread]} shadow
css-color (uc/color->background (:color shadow))]
(str
(if (= style :inner-shadow) "inset " "")
(str/fmt "%spx %spx %spx %spx %s" offset-x offset-y blur spread css-color))))
(defn format-fill-color [_ shape]
(let [color {:color (:fill-color shape)
:opacity (:fill-opacity shape)
:gradient (:fill-color-gradient shape)
:id (:fill-ref-id shape)
:file-id (:fill-ref-file-id shape)}]
(uc/color->background color)))
(defn format-stroke [_ shape]
(let [width (:stroke-width shape)
style (name (:stroke-style shape))
color {:color (:stroke-color shape)
:opacity (:stroke-opacity shape)
:gradient (:stroke-color-gradient shape)
:id (:stroke-ref-id shape)
:file-id (:stroke-ref-file-id shape)}]
(str/format "%spx %s %s" width style (uc/color->background color))))
(defn generate-css-props [values properties params]
(let [{:keys [to-prop format tab-size] :or {to-prop {} tab-size 0}} 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)
default-format (fn [value] (str (mth/precision value 2) "px"))
format-property (fn [prop]
(let [css-prop (or (prop to-prop) (name prop))
format-fn (or (prop format) default-format)]
(str
(str/repeat " " tab-size)
(str/fmt "%s: %s;" css-prop (format-fn (prop values) values)))))]
(->> properties
(remove #(let [value (get values %)]
(or (nil? value) (= value 0))))
(map format-property)
(str/join "\n"))))
(defn shape->properties [shape]
(let [props (->> styles-data vals (mapcat :props))
to-prop (->> styles-data vals (map :to-prop) (reduce merge))
format (->> styles-data vals (map :format) (reduce merge))]
(generate-css-props shape props {:to-prop to-prop
:format format
:tab-size 2})))
(defn text->properties [shape]
(let [text-shape-style (select-keys styles-data [: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))
text-values (->> (ut/search-text-attrs (:content shape) (:props style-text))
(merge ut/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"))))