From 237ef2a2058af9e3a85f13816ccd10236532dc51 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 1 Mar 2021 22:15:17 +0100 Subject: [PATCH] :sparkles: Adds rects, ellipses and uses to svg elements --- .../src/app/main/data/workspace/common.cljs | 51 ++--- .../app/main/data/workspace/svg_upload.cljs | 186 ++++++++++++------ .../app/main/ui/workspace/shapes/svg_raw.cljs | 2 +- .../ui/workspace/sidebar/options/circle.cljs | 7 +- .../ui/workspace/sidebar/options/path.cljs | 1 - .../ui/workspace/sidebar/options/rect.cljs | 8 +- .../workspace/sidebar/options/svg_attrs.cljs | 27 ++- frontend/src/app/util/svg.cljs | 49 +++-- 8 files changed, 215 insertions(+), 116 deletions(-) diff --git a/frontend/src/app/main/data/workspace/common.cljs b/frontend/src/app/main/data/workspace/common.cljs index 499d25694b..be9196187e 100644 --- a/frontend/src/app/main/data/workspace/common.cljs +++ b/frontend/src/app/main/data/workspace/common.cljs @@ -578,36 +578,39 @@ [frame-id parent-id (inc index)]))))) (defn add-shape-changes - [page-id objects selected attrs] - (let [id (:id attrs) - shape (gpr/setup-proportions attrs) + ([page-id objects selected attrs] + (add-shape-changes page-id objects selected attrs true)) + ([page-id objects selected attrs reg-object?] + (let [id (:id attrs) + shape (gpr/setup-proportions attrs) - default-attrs (if (= :frame (:type shape)) - cp/default-frame-attrs - cp/default-shape-attrs) + default-attrs (if (= :frame (:type shape)) + cp/default-frame-attrs + cp/default-shape-attrs) - shape (merge default-attrs shape) + shape (merge default-attrs shape) - not-frame? #(not (= :frame (get-in objects [% :type]))) - selected (into #{} (filter not-frame?) selected) + not-frame? #(not (= :frame (get-in objects [% :type]))) + selected (into #{} (filter not-frame?) selected) - [frame-id parent-id index] (get-shape-layer-position objects selected attrs) + [frame-id parent-id index] (get-shape-layer-position objects selected attrs) - redo-changes [{:type :add-obj - :id id - :page-id page-id - :frame-id frame-id - :parent-id parent-id - :index index - :obj shape} - {:type :reg-objects - :page-id page-id - :shapes [id]}] - undo-changes [{:type :del-obj - :page-id page-id - :id id}]] + redo-changes (cond-> [{:type :add-obj + :id id + :page-id page-id + :frame-id frame-id + :parent-id parent-id + :index index + :obj shape}] + reg-object? + (conj {:type :reg-objects + :page-id page-id + :shapes [id]})) + undo-changes [{:type :del-obj + :page-id page-id + :id id}]] - [redo-changes undo-changes])) + [redo-changes undo-changes]))) (defn add-shape [attrs] diff --git a/frontend/src/app/main/data/workspace/svg_upload.cljs b/frontend/src/app/main/data/workspace/svg_upload.cljs index 3d0664fff5..f33cf7861c 100644 --- a/frontend/src/app/main/data/workspace/svg_upload.cljs +++ b/frontend/src/app/main/data/workspace/svg_upload.cljs @@ -28,10 +28,9 @@ (let [width (get-in data [:attrs :width] 100) height (get-in data [:attrs :height] 100) viewbox (get-in data [:attrs :viewBox] (str "0 0 " width " " height)) - [_ _ width-str height-str] (str/split viewbox " ") - width (d/parse-integer width-str) - height (d/parse-integer height-str)] - [width height])) + [x y width height] (->> (str/split viewbox " ") + (map d/parse-double))] + [x y width height])) (defn tag->name "Given a tag returns its layer name" @@ -61,15 +60,10 @@ (d/parse-double)))) (get-in shape [:svg-attrs :style :fill-opacity]) - (-> (update :svg-attrs dissoc :fill-opacity) + (-> (update-in [:svg-attrs :style] dissoc :fill-opacity) (assoc :fill-opacity (-> (get-in shape [:svg-attrs :style :fill-opacity]) (d/parse-double)))))) -(defonce default-stroke {:stroke-color "#000000" - :stroke-opacity 1 - :stroke-alignment :center - :stroke-style :svg}) - (defn setup-stroke [shape] (let [shape (cond-> shape @@ -90,9 +84,8 @@ (-> (update-in [:svg-attrs :style] dissoc :stroke-width) (assoc :stroke-width (-> (get-in shape [:svg-attrs :style :stroke-width]) (d/parse-double)))))] - shape - #_(if (d/any-key? shape :stroke-color :stroke-opacity :stroke-width) - (merge default-stroke shape) + (if (d/any-key? shape :stroke-color :stroke-opacity :stroke-width) + (merge {:stroke-style :svg} shape) shape))) (defn create-raw-svg [name frame-id svg-data {:keys [attrs] :as data}] @@ -112,27 +105,39 @@ (gsh/setup-selrect)))) (defn create-svg-root [frame-id svg-data] - (let [{:keys [name x y width height]} svg-data] + (let [{:keys [name x y width height offset-x offset-y]} svg-data] (-> {:id (uuid/next) :type :group :name name :frame-id frame-id :width width :height height - :x x - :y y} + :x (+ x offset-x) + :y (+ y offset-y)} (gsh/setup-selrect) (assoc :svg-attrs (-> (:attrs svg-data) (dissoc :viewBox :xmlns)))))) +(defn create-group [name frame-id svg-data {:keys [attrs]}] + (let [{:keys [x y width height offset-x offset-y]} svg-data] + (-> {:id (uuid/next) + :type :group + :name name + :frame-id frame-id + :x (+ x offset-x) + :y (+ y offset-y) + :width width + :height height} + (assoc :svg-attrs (dissoc attrs :transform)) + (assoc :svg-viewbox (select-keys svg-data [:x :y :width :height])) + (gsh/setup-selrect)))) + (defn create-path-shape [name frame-id svg-data {:keys [attrs] :as data}] (let [svg-transform (usvg/parse-transform (:transform attrs)) content (cond-> (ugp/path->content (:d attrs)) svg-transform (gsh/transform-content svg-transform)) - ;; attrs (d/update-when attrs :transform #(-> (usvg/parse-transform %) str)) - selrect (gsh/content->selrect content) points (gsh/rect->points selrect) @@ -149,7 +154,6 @@ (assoc :svg-transform svg-transform) (gsh/translate-to-frame origin)))) - (defn inverse-matrix [{:keys [a b c d e f]}] (let [dom-matrix (-> (js/DOMMatrix.) (obj/set! "a" a) @@ -212,51 +216,108 @@ :name name :frame-id frame-id} (cond-> - (contains? attrs :rx) (:rx attrs) - (contains? attrs :rx) (:rx attrs)) + (contains? attrs :rx) (assoc :rx (d/parse-double (:rx attrs))) + (contains? attrs :ry) (assoc :ry (d/parse-double (:ry attrs)))) (merge metadata) (assoc :svg-transform transform) (assoc :svg-viewbox (select-keys rect [:x :y :width :height])) (assoc :svg-attrs (dissoc attrs :x :y :width :height :rx :ry :transform))))) -(defn create-group [name frame-id svg-data {:keys [attrs]}] - (let [{:keys [x y width height]} svg-data] +(def default-circle {:r 0 :cx 0 :cy 0}) + +(defn create-circle-shape [name frame-id svg-data {:keys [attrs] :as data}] + (let [svg-transform (usvg/parse-transform (:transform attrs)) + transform (->> svg-transform + (gmt/transform-in (gpt/point svg-data))) + + circle (->> (select-keys attrs [:r :ry :rx :cx :cy]) + (d/mapm #(d/parse-double %2))) + + {:keys [cx cy]} circle + + rx (or (:r circle) (:rx circle)) + ry (or (:r circle) (:ry circle)) + + rect {:x (- cx rx) + :y (- cy ry) + :width (* 2 rx) + :height (* 2 ry)} + + origin (gpt/negate (gpt/point svg-data)) + + rect-data (-> rect + (update :x - (:x origin)) + (update :y - (:y origin))) + + metadata (calculate-rect-metadata rect-data transform)] (-> {:id (uuid/next) - :type :group + :type :circle :name name - :frame-id frame-id - :x x - :y y - :width width - :height height} - (assoc :svg-attrs attrs) - (assoc :svg-viewbox (select-keys svg-data [0 0 :width :height])) - (gsh/setup-selrect)))) + :frame-id frame-id} + + (merge metadata) + (assoc :svg-transform transform) + (assoc :svg-viewbox (select-keys rect [:x :y :width :height])) + (assoc :svg-attrs (dissoc attrs :cx :cy :r :rx :ry :transform))))) + +(defn add-transform [transform node] + (letfn [(append-transform [old-transform] + (if (or (nil? old-transform) (empty? old-transform)) + transform + (str old-transform " " transform)))] + + (cond-> node + transform + (update-in [:attrs :transform] append-transform)))) (defn parse-svg-element [frame-id svg-data element-data unames] (let [{:keys [tag attrs]} element-data + attrs (cond-> attrs (contains? attrs :style) usvg/format-styles) + element-data (cond-> element-data (map? element-data) (assoc :attrs attrs)) name (dwc/generate-unique-name unames (or (:id attrs) (tag->name tag)) true) att-refs (usvg/find-attr-references attrs) - references (usvg/find-def-references (:defs svg-data) att-refs)] + references (usvg/find-def-references (:defs svg-data) att-refs) - ;; SVG graphic elements - ;; :circle :ellipse :image :line :path :polygon :polyline :rect :text :use - (-> (case tag - :g (create-group name frame-id svg-data element-data) - :rect (create-rect-shape name frame-id svg-data element-data) - :path (create-path-shape name frame-id svg-data element-data) - #_other (create-raw-svg name frame-id svg-data element-data)) + href-id (-> (or (:href attrs) (:xlink:href attrs) "") + (subs 1)) + defs (:defs svg-data) - (assoc :svg-defs (select-keys (:defs svg-data) references)) - (setup-fill) - (setup-stroke)))) + use-tag? (and (= :use tag) (contains? defs href-id))] -(defn add-svg-child-changes [page-id objects selected frame-id parent-id svg-data ids-mappings result [index data]] - (let [[unames [rchs uchs]] result - shape (parse-svg-element frame-id svg-data data unames) + (if use-tag? + (let [use-data (get defs href-id) + translate (gpt/point (:x attrs 0) (:y attrs 0)) + attrs' (dissoc attrs :x :y :width :height :href :xlink:href) + ;; TODO: If the child is a symbol we've to take the width/height into account + use-data (update use-data :attrs #(d/deep-merge attrs' %)) + [shape children] (parse-svg-element frame-id svg-data use-data unames)] + [(-> shape (gsh/move translate)) children]) + + ;; SVG graphic elements + ;; :circle :ellipse :image :line :path :polygon :polyline :rect :text :use + (let [shape (-> (case tag + (:g :a) (create-group name frame-id svg-data element-data) + :rect (create-rect-shape name frame-id svg-data element-data) + (:circle + :ellipse) (create-circle-shape name frame-id svg-data element-data) + :path (create-path-shape name frame-id svg-data element-data) + #_other (create-raw-svg name frame-id svg-data element-data)) + + (assoc :svg-defs (select-keys (:defs svg-data) references)) + (setup-fill) + (setup-stroke)) + + children (cond->> (:content element-data) + (= tag :g) + (mapv #(add-transform (:transform attrs) %)))] + [shape children])))) + +(defn add-svg-child-changes [page-id objects selected frame-id parent-id svg-data [unames [rchs uchs]] [index data]] + (let [[shape children] (parse-svg-element frame-id svg-data data unames) shape-id (:id shape) - [rch1 uch1] (dwc/add-shape-changes page-id objects selected shape) + + [rch1 uch1] (dwc/add-shape-changes page-id objects selected shape false) ;; Mov-objects won't have undo because we "delete" the object in the undo of the ;; previous operation @@ -270,8 +331,8 @@ ;; Careful! the undo changes are concatenated reversed (we undo in reverse order changes [(d/concat rchs rch1 rch2) (d/concat uch1 uchs)] unames (conj unames (:name shape)) - reducer-fn (partial add-svg-child-changes page-id objects selected frame-id shape-id svg-data ids-mappings)] - (reduce reducer-fn [unames changes] (d/enumerate (:content data))))) + reducer-fn (partial add-svg-child-changes page-id objects selected frame-id shape-id svg-data)] + (reduce reducer-fn [unames changes] (d/enumerate children)))) (defn svg-uploaded [svg-data x y] (ptk/reify ::svg-uploaded @@ -283,21 +344,22 @@ frame-id (cp/frame-id-by-position objects {:x x :y y}) selected (get-in state [:workspace-local :selected]) - [width height] (svg-dimensions svg-data) - x (- x (/ width 2)) - y (- y (/ height 2)) + [vb-x vb-y vb-width vb-height] (svg-dimensions svg-data) + x (- x vb-x (/ vb-width 2)) + y (- y vb-y (/ vb-height 2)) unames (dwc/retrieve-used-names objects) svg-name (->> (str/replace (:name svg-data) ".svg" "") (dwc/generate-unique-name unames)) - ids-mappings (usvg/generate-id-mapping svg-data) svg-data (-> svg-data (assoc :x x :y y - :width width - :height height + :offset-x vb-x + :offset-y vb-y + :width vb-width + :height vb-height :name svg-name)) [def-nodes svg-data] (usvg/extract-defs svg-data) @@ -308,11 +370,17 @@ changes (dwc/add-shape-changes page-id objects selected root-shape) - reducer-fn (partial add-svg-child-changes page-id objects selected frame-id root-id svg-data ids-mappings) - [_ [rchanges uchanges]] (reduce reducer-fn [unames changes] (d/enumerate (:content svg-data)))] + reducer-fn (partial add-svg-child-changes page-id objects selected frame-id root-id svg-data) + [_ [rchanges uchanges]] (reduce reducer-fn [unames changes] (d/enumerate (:content svg-data))) + + reg-objects-action {:type :reg-objects + :page-id page-id + :shapes (->> rchanges (map :id) (remove nil?) (into #{root-id}) vec)} + + rchanges (conj rchanges reg-objects-action)] + (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}) (dwc/select-shapes (d/ordered-set root-id)))) (catch :default e - (.error js/console e)) - )))) + (.error js/console "Error upload" e)))))) diff --git a/frontend/src/app/main/ui/workspace/shapes/svg_raw.cljs b/frontend/src/app/main/ui/workspace/shapes/svg_raw.cljs index 5563f353be..637515d608 100644 --- a/frontend/src/app/main/ui/workspace/shapes/svg_raw.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/svg_raw.cljs @@ -19,7 +19,7 @@ ;; This is a list of svg tags that can be grouped in shape-container ;; this allows them to have gradients, shadows and masks -(def svg-elements #{:svg :circle :ellipse :image :line :path :polygon :polyline :rect :symbol :text :textPath}) +(def svg-elements #{:svg :circle :ellipse :image :line :path :polygon :polyline :rect :symbol :text :textPath :use}) (defn svg-raw-wrapper-factory [shape-wrapper] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/circle.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/circle.cljs index b184f5518e..d39e86a0b8 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/circle.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/circle.cljs @@ -14,7 +14,8 @@ [app.main.ui.workspace.sidebar.options.fill :refer [fill-attrs fill-menu]] [app.main.ui.workspace.sidebar.options.stroke :refer [stroke-attrs stroke-menu]] [app.main.ui.workspace.sidebar.options.shadow :refer [shadow-menu]] - [app.main.ui.workspace.sidebar.options.blur :refer [blur-menu]])) + [app.main.ui.workspace.sidebar.options.blur :refer [blur-menu]] + [app.main.ui.workspace.sidebar.options.svg-attrs :refer [svg-attrs-menu]])) (mf/defc options [{:keys [shape] :as props}] @@ -36,4 +37,6 @@ [:& shadow-menu {:ids ids :values (select-keys shape [:shadow])}] [:& blur-menu {:ids ids - :values (select-keys shape [:blur])}]])) + :values (select-keys shape [:blur])}] + [:& svg-attrs-menu {:ids ids + :values (select-keys shape [:svg-attrs])}]])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/path.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/path.cljs index 05ed93fe23..f224df3d7d 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/path.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/path.cljs @@ -38,6 +38,5 @@ :values (select-keys shape [:shadow])}] [:& blur-menu {:ids ids :values (select-keys shape [:blur])}] - [:& svg-attrs-menu {:ids ids :values (select-keys shape [:svg-attrs])}]])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/rect.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/rect.cljs index e735a8cc31..d3f1464bd0 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/rect.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/rect.cljs @@ -14,7 +14,8 @@ [app.main.ui.workspace.sidebar.options.fill :refer [fill-attrs fill-menu]] [app.main.ui.workspace.sidebar.options.stroke :refer [stroke-attrs stroke-menu]] [app.main.ui.workspace.sidebar.options.shadow :refer [shadow-menu]] - [app.main.ui.workspace.sidebar.options.blur :refer [blur-menu]])) + [app.main.ui.workspace.sidebar.options.blur :refer [blur-menu]] + [app.main.ui.workspace.sidebar.options.svg-attrs :refer [svg-attrs-menu]])) (mf/defc options {::mf/wrap [mf/memo]} @@ -41,4 +42,7 @@ :values (select-keys shape [:shadow])}] [:& blur-menu {:ids ids - :values (select-keys shape [:blur])}]])) + :values (select-keys shape [:blur])}] + + [:& svg-attrs-menu {:ids ids + :values (select-keys shape [:svg-attrs])}]])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/svg_attrs.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/svg_attrs.cljs index 4fc5d5f919..ccba80e8ba 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/svg_attrs.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/svg_attrs.cljs @@ -9,6 +9,7 @@ (ns app.main.ui.workspace.sidebar.options.svg-attrs (:require + [cuerdas.core :as str] [app.common.data :as d] [app.main.data.workspace.common :as dwc] [app.main.store :as st] @@ -22,13 +23,22 @@ (mf/use-callback (mf/deps attr on-change) (fn [event] - (on-change attr (dom/get-target-val event))))] + (on-change attr (dom/get-target-val event)))) + + label (->> attr (map name) (str/join "."))] [:div.element-set-content - [:& input-row {:label (name attr) - :type :text - :class "large" - :value (str value) - :on-change handle-change}]])) + (if (string? value) + [:& input-row {:label label + :type :text + :class "large" + :value (str value) + :on-change handle-change}] + + (for [[key value] value] + [:& attribute-value {:key key + :attr (conj attr key) + :value value + :on-change handle-change}]))])) (mf/defc svg-attrs-menu [{:keys [ids type values]}] (let [handle-change @@ -36,7 +46,7 @@ (mf/deps ids) (fn [attr value] (let [update-fn - (fn [shape] (assoc-in shape [:svg-attrs attr] value))] + (fn [shape] (assoc-in shape (concat [:svg-attrs] attr) value))] (st/emit! (dwc/update-shapes ids update-fn)))))] @@ -47,7 +57,6 @@ (for [[index [attr-key attr-value]] (d/enumerate (:svg-attrs values))] [:& attribute-value {:key attr-key - :ids ids - :attr attr-key + :attr [attr-key] :value attr-value :on-change handle-change}])]))) diff --git a/frontend/src/app/util/svg.cljs b/frontend/src/app/util/svg.cljs index 4d0e4dcf5c..0c09d78e79 100644 --- a/frontend/src/app/util/svg.cljs +++ b/frontend/src/app/util/svg.cljs @@ -35,6 +35,22 @@ :else num-str)) +(defn format-styles + "Transforms attributes to their react equivalent" + [attrs] + (letfn [(format-styles [style-str] + (if (string? style-str) + (->> (str/split style-str ";") + (map str/trim) + (map #(str/split % ":")) + (group-by first) + (map (fn [[key val]] + (vector (keyword key) (second (first val))))) + (into {})) + style-str))] + + (update attrs :style format-styles))) + (defn clean-attrs "Transforms attributes to their react equivalent" [attrs] @@ -60,6 +76,7 @@ (cond (= key :class) [:className val] (and (= key :style) (string? val)) [key (format-styles val)] + (and (= key :style) (map? val)) [key (clean-attrs val)] :else (vector (transform-key key) val))))] (->> attrs @@ -100,34 +117,30 @@ (reduce visit-node result (:content node))))] (visit-node {} content))) -(defn extract-defs [{:keys [tag content] :as node}] - +(defn extract-defs [{:keys [tag attrs content] :as node}] (if-not (map? node) [{} node] - (letfn [(def-tag? [{:keys [tag]}] (= tag :defs)) - (assoc-node [result node] - (assoc result (-> node :attrs :id) node)) + (let [remove-node? (fn [{:keys [tag]}] (= tag :defs)) - (node-data [node] - (->> (:content node) (reduce assoc-node {})))] + rec-result (->> (:content node) (map extract-defs)) + node (assoc node :content (->> rec-result (map second) (filterv (comp not remove-node?)))) - (let [current-def (->> content - (filterv def-tag?) - (map node-data) - (reduce merge)) - result (->> content - (filter (comp not def-tag?)) - (map extract-defs)) - current-def (->> result (map first) (reduce merge current-def)) - content (->> result (mapv second))] + current-node-defs (if (contains? attrs :id) + (hash-map (:id attrs) node) + (hash-map)) - [current-def (assoc node :content content)])))) + node-defs (->> rec-result (map first) (reduce merge current-node-defs))] + + [ node-defs node ]))) (defn find-attr-references [attrs] (->> attrs - (mapcat (fn [[_ attr-value]] (extract-ids attr-value))))) + (mapcat (fn [[_ attr-value]] + (if (string? attr-value) + (extract-ids attr-value) + (find-attr-references attr-value)))))) (defn find-node-references [node] (let [current (->> (find-attr-references (:attrs node)) (into #{}))