diff --git a/common/src/app/common/text.cljc b/common/src/app/common/text.cljc index da941a60e..abcb36631 100644 --- a/common/src/app/common/text.cljc +++ b/common/src/app/common/text.cljc @@ -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))) diff --git a/frontend/src/app/main/ui/formats.cljs b/frontend/src/app/main/ui/formats.cljs index 76fac5eda..28296b183 100644 --- a/frontend/src/app/main/ui/formats.cljs +++ b/frontend/src/app/main/ui/formats.cljs @@ -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] diff --git a/frontend/src/app/main/ui/shapes/text/html_text.cljs b/frontend/src/app/main/ui/shapes/text/html_text.cljs index 7a41869f7..301b56fb6 100644 --- a/frontend/src/app/main/ui/shapes/text/html_text.cljs +++ b/frontend/src/app/main/ui/shapes/text/html_text.cljs @@ -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?}]])) diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes.cljs index 19fc15311..d1196a718 100644 --- a/frontend/src/app/main/ui/viewer/inspect/attributes.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/attributes.cljs @@ -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 diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/blur.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/blur.cljs index 86d97d360..f8c3446da 100644 --- a/frontend/src/app/main/ui/viewer/inspect/attributes/blur.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/attributes/blur.cljs @@ -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)}]])]))) diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/common.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/common.cljs index 76e593f4c..468132b7e 100644 --- a/frontend/src/app/main/ui/viewer/inspect/attributes/common.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/attributes/common.cljs @@ -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)) diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/fill.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/fill.cljs index 500ddd500..0292569ed 100644 --- a/frontend/src/app/main/ui/viewer/inspect/attributes/fill.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/attributes/fill.cljs @@ -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 diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/geometry.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/geometry.cljs new file mode 100644 index 000000000..d24ea3e6a --- /dev/null +++ b/frontend/src/app/main/ui/viewer/inspect/attributes/geometry.cljs @@ -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)}])]) diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/image.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/image.cljs index 5d25e5e9d..878661a3f 100644 --- a/frontend/src/app/main/ui/viewer/inspect/attributes/image.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/attributes/image.cljs @@ -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) diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/layout.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/layout.cljs index f1d4490a2..29dd38582 100644 --- a/frontend/src/app/main/ui/viewer/inspect/attributes/layout.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/attributes/layout.cljs @@ -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)}])]))) diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/layout_element.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/layout_element.cljs new file mode 100644 index 000000000..f59e85656 --- /dev/null +++ b/frontend/src/app/main/ui/viewer/inspect/attributes/layout_element.cljs @@ -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)}])]))) diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/layout_flex.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/layout_flex.cljs deleted file mode 100644 index 4e4430f3c..000000000 --- a/frontend/src/app/main/ui/viewer/inspect/attributes/layout_flex.cljs +++ /dev/null @@ -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)}])]))) diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/layout_flex_element.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/layout_flex_element.cljs deleted file mode 100644 index 9e23c6a4c..000000000 --- a/frontend/src/app/main/ui/viewer/inspect/attributes/layout_flex_element.cljs +++ /dev/null @@ -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}]]))) diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/shadow.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/shadow.cljs index 25f04df95..ff8cf5a80 100644 --- a/frontend/src/app/main/ui/viewer/inspect/attributes/shadow.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/attributes/shadow.cljs @@ -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 diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/stroke.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/stroke.cljs index 34e1f70bc..59c08fa9c 100644 --- a/frontend/src/app/main/ui/viewer/inspect/attributes/stroke.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/attributes/stroke.cljs @@ -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}]))]]))) diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/text.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/text.cljs index d5c1c87b4..2721f2358 100644 --- a/frontend/src/app/main/ui/viewer/inspect/attributes/text.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/attributes/text.cljs @@ -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))))] diff --git a/frontend/src/app/main/ui/viewer/inspect/code.cljs b/frontend/src/app/main/ui/viewer/inspect/code.cljs index 99e881d77..daa7fd7ad 100644 --- a/frontend/src/app/main/ui/viewer/inspect/code.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/code.cljs @@ -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 diff --git a/frontend/src/app/main/ui/viewer/inspect/right_sidebar.cljs b/frontend/src/app/main/ui/viewer/inspect/right_sidebar.cljs index 2cceadd34..ad9509372 100644 --- a/frontend/src/app/main/ui/viewer/inspect/right_sidebar.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/right_sidebar.cljs @@ -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 diff --git a/frontend/src/app/main/ui/workspace/sidebar/options.cljs b/frontend/src/app/main/ui/workspace/sidebar/options.cljs index 19d98b8ac..37dc1c779 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options.cljs @@ -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 diff --git a/frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.cljs b/frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.cljs index 72dbd278d..f34634a0c 100644 --- a/frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.cljs @@ -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 diff --git a/frontend/src/app/util/code_gen.cljs b/frontend/src/app/util/code_gen.cljs index 8dfb5d364..e1c35d169 100644 --- a/frontend/src/app/util/code_gen.cljs +++ b/frontend/src/app/util/code_gen.cljs @@ -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 "%
\n%\n%
" - 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 "%\n%" - 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 "%
\n%\n%
" - indent - (selector-name shape) - svg-markup - indent)) - - (empty? (:shapes shape)) - (dm/fmt "%
\n%
" - indent - (selector-name shape) - indent) - - :else - (dm/fmt "%
\n%\n%
" - 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))) diff --git a/frontend/src/app/util/code_gen/common.cljs b/frontend/src/app/util/code_gen/common.cljs new file mode 100644 index 000000000..07eb37ce2 --- /dev/null +++ b/frontend/src/app/util/code_gen/common.cljs @@ -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)) diff --git a/frontend/src/app/util/code_gen/markup_html.cljs b/frontend/src/app/util/code_gen/markup_html.cljs new file mode 100644 index 000000000..bdbdb5f21 --- /dev/null +++ b/frontend/src/app/util/code_gen/markup_html.cljs @@ -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 "%
\n%\n%
" + 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 "%\n%" + 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 "%
\n%\n%
" + indent + (cgc/shape->selector shape) + svg-markup + indent)) + + (empty? (:shapes shape)) + (dm/fmt "%
\n%
" + indent + (cgc/shape->selector shape) + indent) + + :else + (dm/fmt "%
\n%\n%
" + 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"))) diff --git a/frontend/src/app/util/code_gen/markup_svg.cljs b/frontend/src/app/util/code_gen/markup_svg.cljs new file mode 100644 index 000000000..a25177bee --- /dev/null +++ b/frontend/src/app/util/code_gen/markup_svg.cljs @@ -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"))) diff --git a/frontend/src/app/util/code_gen/style_css.cljs b/frontend/src/app/util/code_gen/style_css.cljs new file mode 100644 index 000000000..de2c26e90 --- /dev/null +++ b/frontend/src/app/util/code_gen/style_css.cljs @@ -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))) diff --git a/frontend/src/app/util/code_gen/style_css_formats.cljs b/frontend/src/app/util/code_gen/style_css_formats.cljs new file mode 100644 index 000000000..cee500474 --- /dev/null +++ b/frontend/src/app/util/code_gen/style_css_formats.cljs @@ -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)) diff --git a/frontend/src/app/util/code_gen/style_css_values.cljs b/frontend/src/app/util/code_gen/style_css_values.cljs new file mode 100644 index 000000000..67674ca38 --- /dev/null +++ b/frontend/src/app/util/code_gen/style_css_values.cljs @@ -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)) + +