diff --git a/common/src/app/common/svg/shapes_builder.cljc b/common/src/app/common/svg/shapes_builder.cljc index 619a81b94..41f25e1e2 100644 --- a/common/src/app/common/svg/shapes_builder.cljc +++ b/common/src/app/common/svg/shapes_builder.cljc @@ -22,6 +22,7 @@ [app.common.svg :as csvg] [app.common.svg.path :as path] [app.common.types.shape :as cts] + [app.common.uuid :as uuid] [cuerdas.core :as str])) (def default-rect @@ -78,67 +79,68 @@ (declare parse-svg-element) (defn create-svg-shapes - [svg-data {:keys [x y]} objects frame-id parent-id selected center?] - (let [[vb-x vb-y vb-width vb-height] (svg-dimensions svg-data) + ([svg-data pos objects frame-id parent-id selected center?] + (create-svg-shapes (uuid/next) svg-data pos objects frame-id parent-id selected center?)) + ([id svg-data {:keys [x y]} objects frame-id parent-id selected center?] + (let [[vb-x vb-y vb-width vb-height] (svg-dimensions svg-data) + unames (cfh/get-used-names objects) + svg-name (str/replace (:name svg-data) ".svg" "") - unames (cfh/get-used-names objects) - svg-name (str/replace (:name svg-data) ".svg" "") + svg-data (-> svg-data + (assoc :x (mth/round + (if center? + (- x vb-x (/ vb-width 2)) + x))) + (assoc :y (mth/round + (if center? + (- y vb-y (/ vb-height 2)) + y))) + (assoc :offset-x vb-x) + (assoc :offset-y vb-y) + (assoc :width vb-width) + (assoc :height vb-height) + (assoc :name svg-name)) - svg-data (-> svg-data - (assoc :x (mth/round - (if center? - (- x vb-x (/ vb-width 2)) - x))) - (assoc :y (mth/round - (if center? - (- y vb-y (/ vb-height 2)) - y))) - (assoc :offset-x vb-x) - (assoc :offset-y vb-y) - (assoc :width vb-width) - (assoc :height vb-height) - (assoc :name svg-name)) + [def-nodes svg-data] + (-> svg-data + (csvg/fix-default-values) + (csvg/fix-percents) + (csvg/extract-defs)) - [def-nodes svg-data] - (-> svg-data - (csvg/fix-default-values) - (csvg/fix-percents) - (csvg/extract-defs)) + ;; In penpot groups have the size of their children. To + ;; respect the imported svg size and empty space let's create + ;; a transparent shape as background to respect the imported + ;; size + background + {:tag :rect + :attrs {:x (dm/str vb-x) + :y (dm/str vb-y) + :width (dm/str vb-width) + :height (dm/str vb-height) + :fill "none" + :id "base-background"} + :hidden true + :content []} - ;; In penpot groups have the size of their children. To - ;; respect the imported svg size and empty space let's create - ;; a transparent shape as background to respect the imported - ;; size - background - {:tag :rect - :attrs {:x (dm/str vb-x) - :y (dm/str vb-y) - :width (dm/str vb-width) - :height (dm/str vb-height) - :fill "none" - :id "base-background"} - :hidden true - :content []} + svg-data (-> svg-data + (assoc :defs def-nodes) + (assoc :content (into [background] (:content svg-data)))) - svg-data (-> svg-data - (assoc :defs def-nodes) - (assoc :content (into [background] (:content svg-data)))) + root-shape (create-svg-root id frame-id parent-id svg-data) + root-id (:id root-shape) - root-shape (create-svg-root frame-id parent-id svg-data) - root-id (:id root-shape) + ;; Create the root shape + root-attrs (-> (:attrs svg-data) + (csvg/format-styles)) - ;; Create the root shape - root-attrs (-> (:attrs svg-data) - (csvg/format-styles)) + [_ children] + (reduce (partial create-svg-children objects selected frame-id root-id svg-data) + [unames []] + (d/enumerate (->> (:content svg-data) + (mapv #(csvg/inherit-attributes root-attrs %)))))] - [_ children] - (reduce (partial create-svg-children objects selected frame-id root-id svg-data) - [unames []] - (d/enumerate (->> (:content svg-data) - (mapv #(csvg/inherit-attributes root-attrs %)))))] - - [root-shape children])) + [root-shape children]))) (defn create-raw-svg [name frame-id {:keys [x y width height offset-x offset-y]} {:keys [attrs] :as data}] @@ -157,12 +159,13 @@ :svg-viewbox vbox}))) (defn create-svg-root - [frame-id parent-id {:keys [name x y width height offset-x offset-y attrs]}] + [id frame-id parent-id {:keys [name x y width height offset-x offset-y attrs]}] (let [props (-> (dissoc attrs :viewBox :view-box :xmlns) (d/without-keys csvg/inheritable-props) (csvg/attrs->props))] (cts/setup-shape - {:type :group + {:id id + :type :group :name name :frame-id frame-id :parent-id parent-id diff --git a/frontend/src/app/main/data/workspace/groups.cljs b/frontend/src/app/main/data/workspace/groups.cljs index be758443e..43bed0489 100644 --- a/frontend/src/app/main/data/workspace/groups.cljs +++ b/frontend/src/app/main/data/workspace/groups.cljs @@ -203,7 +203,7 @@ ptk/WatchEvent (watch [_ state _] (let [selected (wsh/lookup-selected state)] - (rx/of group-shapes nil selected))))) + (rx/of (group-shapes nil selected)))))) (defn ungroup-shapes [ids & {:keys [change-selection?] :or {change-selection? false}}] diff --git a/frontend/src/app/main/data/workspace/media.cljs b/frontend/src/app/main/data/workspace/media.cljs index 815619059..50105e9b0 100644 --- a/frontend/src/app/main/data/workspace/media.cljs +++ b/frontend/src/app/main/data/workspace/media.cljs @@ -459,3 +459,12 @@ (rx/tap on-success) (rx/catch on-error) (rx/finalize #(st/emit! (msg/hide-tag :media-loading))))))))) + +(defn create-svg-shape + [id name svg-string position] + (ptk/reify ::create-svg-shape + ptk/WatchEvent + (watch [_ _ _] + (->> (svg->clj [name svg-string]) + (rx/take 1) + (rx/map #(svg/add-svg-shapes id % position {:change-selection? false})))))) diff --git a/frontend/src/app/main/data/workspace/svg_upload.cljs b/frontend/src/app/main/data/workspace/svg_upload.cljs index fa159cf04..2517a73aa 100644 --- a/frontend/src/app/main/data/workspace/svg_upload.cljs +++ b/frontend/src/app/main/data/workspace/svg_upload.cljs @@ -13,6 +13,7 @@ [app.common.svg :as csvg] [app.common.svg.shapes-builder :as csvg.shapes-builder] [app.common.types.shape-tree :as ctst] + [app.common.uuid :as uuid] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.selection :as dws] [app.main.data.workspace.state-helpers :as wsh] @@ -60,52 +61,58 @@ (rx/reduce conj {}))) (defn add-svg-shapes - [svg-data position] - (ptk/reify ::add-svg-shapes - ptk/WatchEvent - (watch [it state _] - (try - (let [page-id (:current-page-id state) - objects (wsh/lookup-page-objects state page-id) - frame-id (ctst/top-nested-frame objects position) - selected (wsh/lookup-selected state) - base (cfh/get-base-shape objects selected) + ([svg-data position] + (add-svg-shapes nil svg-data position nil)) - selected-id (first selected) - selected-frame? (and (= 1 (count selected)) - (= :frame (dm/get-in objects [selected-id :type]))) + ([id svg-data position {:keys [change-selection?] :or {change-selection? false}}] + (ptk/reify ::add-svg-shapes + ptk/WatchEvent + (watch [it state _] + (try + (let [id (d/nilv id (uuid/next)) + page-id (:current-page-id state) + objects (wsh/lookup-page-objects state page-id) + frame-id (ctst/top-nested-frame objects position) + selected (wsh/lookup-selected state) + base (cfh/get-base-shape objects selected) - parent-id (if (or selected-frame? (empty? selected)) - frame-id - (:parent-id base)) + selected-id (first selected) + selected-frame? (and (= 1 (count selected)) + (= :frame (dm/get-in objects [selected-id :type]))) - [new-shape new-children] - (csvg.shapes-builder/create-svg-shapes svg-data position objects frame-id parent-id selected true) + parent-id (if (or selected-frame? (empty? selected)) + frame-id + (:parent-id base)) - changes (-> (pcb/empty-changes it page-id) - (pcb/with-objects objects) - (pcb/add-object new-shape)) + [new-shape new-children] + (csvg.shapes-builder/create-svg-shapes id svg-data position objects frame-id parent-id selected true) - changes (reduce (fn [changes new-child] - (pcb/add-object changes new-child)) - changes - new-children) + changes (-> (pcb/empty-changes it page-id) + (pcb/with-objects objects) + (pcb/add-object new-shape)) - changes (pcb/resize-parents changes - (->> (:redo-changes changes) - (filter #(= :add-obj (:type %))) - (map :id) - (reverse) - (vec))) - undo-id (js/Symbol)] + changes (reduce (fn [changes new-child] + (pcb/add-object changes new-child)) + changes + new-children) - (rx/of (dwu/start-undo-transaction undo-id) - (dch/commit-changes changes) - (dws/select-shapes (d/ordered-set (:id new-shape))) - (ptk/data-event :layout/update {:ids [(:id new-shape)]}) - (dwu/commit-undo-transaction undo-id))) + changes (pcb/resize-parents changes + (->> (:redo-changes changes) + (filter #(= :add-obj (:type %))) + (map :id) + (reverse) + (vec))) + undo-id (js/Symbol)] + + (rx/of (dwu/start-undo-transaction undo-id) + (dch/commit-changes changes) + (when change-selection? + (dws/select-shapes (d/ordered-set (:id new-shape)))) + (ptk/data-event :layout/update {:ids [(:id new-shape)]}) + (dwu/commit-undo-transaction undo-id))) + + (catch :default cause + (js/console.log (.-stack cause)) + (rx/throw {:type :svg-parser + :data cause}))))))) - (catch :default cause - (js/console.log (.-stack cause)) - (rx/throw {:type :svg-parser - :data cause})))))) diff --git a/frontend/src/app/plugins/api.cljs b/frontend/src/app/plugins/api.cljs index 5efaddfc9..5c06da859 100644 --- a/frontend/src/app/plugins/api.cljs +++ b/frontend/src/app/plugins/api.cljs @@ -9,6 +9,7 @@ (:require [app.common.data.macros :as dm] [app.common.files.changes-builder :as cb] + [app.common.geom.point :as gpt] [app.common.record :as cr] [app.common.types.shape :as cts] [app.common.uuid :as uuid] @@ -127,7 +128,14 @@ (createRectangle [_] (create-shape :rect)) - ) + + (createShapeFromSvg + [_ svg-string] + (let [id (uuid/next) + page-id (:current-page-id @st/state)] + (st/emit! (dwm/create-svg-shape id "svg" svg-string (gpt/point 0 0))) + (shape/data->shape-proxy + (dm/get-in @st/state [:workspace-data :pages-index page-id :objects id]))))) (defn create-context [] diff --git a/frontend/src/app/plugins/shape.cljs b/frontend/src/app/plugins/shape.cljs index 56383e38a..27dc930c5 100644 --- a/frontend/src/app/plugins/shape.cljs +++ b/frontend/src/app/plugins/shape.cljs @@ -67,12 +67,12 @@ (appendChild [self child] (let [parent-id (get-data self :id) child-id (uuid/uuid (obj/get child "id"))] - (st/emit! (udw/relocate-shapes #{ child-id } parent-id 0)))) + (st/emit! (udw/relocate-shapes #{child-id} parent-id 0)))) (insertChild [self index child] (let [parent-id (get-data self :id) child-id (uuid/uuid (obj/get child "id"))] - (st/emit! (udw/relocate-shapes #{ child-id } parent-id index))))) + (st/emit! (udw/relocate-shapes #{child-id} parent-id index))))) (crc/define-properties! ShapeProxy @@ -124,16 +124,14 @@ :set (fn [self value] (let [id (get-data self :id) value (mapv #(utils/from-js %) value)] - (st/emit! (dwc/update-shapes [id] #(assoc % :fills value))))) - } + (st/emit! (dwc/update-shapes [id] #(assoc % :fills value)))))} {:name "strokes" :get #(get-state % :strokes make-strokes) :set (fn [self value] (let [id (get-data self :id) value (mapv #(utils/from-js %) value)] - (st/emit! (dwc/update-shapes [id] #(assoc % :strokes value))))) - }) + (st/emit! (dwc/update-shapes [id] #(assoc % :strokes value)))))}) (cond-> (or (cfh/frame-shape? data) (cfh/group-shape? data) (cfh/svg-raw-shape? data) (cfh/bool-shape? data)) (crc/add-properties!