diff --git a/frontend/resources/locales.json b/frontend/resources/locales.json index 8a849f409..43cd97eee 100644 --- a/frontend/resources/locales.json +++ b/frontend/resources/locales.json @@ -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" : { diff --git a/frontend/resources/styles/main/partials/handoff.scss b/frontend/resources/styles/main/partials/handoff.scss index 899dd818b..70ba1be4e 100644 --- a/frontend/resources/styles/main/partials/handoff.scss +++ b/frontend/resources/styles/main/partials/handoff.scss @@ -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; } diff --git a/frontend/src/app/main/ui/shapes/filters.cljs b/frontend/src/app/main/ui/shapes/filters.cljs index 89a7374f3..140b5fb57 100644 --- a/frontend/src/app/main/ui/shapes/filters.cljs +++ b/frontend/src/app/main/ui/shapes/filters.cljs @@ -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]}] diff --git a/frontend/src/app/main/ui/shapes/shape.cljs b/frontend/src/app/main/ui/shapes/shape.cljs index 086f37bcb..fd046afb1 100644 --- a/frontend/src/app/main/ui/shapes/shape.cljs +++ b/frontend/src/app/main/ui/shapes/shape.cljs @@ -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]])) diff --git a/frontend/src/app/main/ui/viewer/handoff/attributes/common.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/common.cljs index b7ccd21fb..43972d237 100644 --- a/frontend/src/app/main/ui/viewer/handoff/attributes/common.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/attributes/common.cljs @@ -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])])) + diff --git a/frontend/src/app/main/ui/viewer/handoff/attributes/layout.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/layout.cljs index 748905449..3a2dcf01f 100644 --- a/frontend/src/app/main/ui/viewer/handoff/attributes/layout.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/attributes/layout.cljs @@ -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")] diff --git a/frontend/src/app/main/ui/viewer/handoff/attributes/shadow.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/shadow.cljs index d5537f153..d9b020635 100644 --- a/frontend/src/app/main/ui/viewer/handoff/attributes/shadow.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/attributes/shadow.cljs @@ -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 diff --git a/frontend/src/app/main/ui/viewer/handoff/code.cljs b/frontend/src/app/main/ui/viewer/handoff/code.cljs index 22a2d5577..ed9daf0a4 100644 --- a/frontend/src/app/main/ui/viewer/handoff/code.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/code.cljs @@ -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 - " - - -") + " +") + + +(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}]]] ])) + diff --git a/frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs b/frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs index 1fbb6e1cb..9fecb1734 100644 --- a/frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs @@ -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 diff --git a/frontend/src/app/util/code_gen.cljs b/frontend/src/app/util/code_gen.cljs new file mode 100644 index 000000000..1d2ac42b1 --- /dev/null +++ b/frontend/src/app/util/code_gen.cljs @@ -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"))))