mirror of
https://github.com/penpot/penpot.git
synced 2025-06-02 11:21:39 +02:00
✨ Makes import SVG groups
This commit is contained in:
parent
507f3c06e7
commit
741d67c30b
8 changed files with 151 additions and 100 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -30,7 +30,7 @@
|
||||||
height (d/parse-integer height-str)]
|
height (d/parse-integer height-str)]
|
||||||
[width height]))
|
[width height]))
|
||||||
|
|
||||||
(defn tag-name [{:keys [tag]}]
|
(defn tag-name [tag]
|
||||||
(cond (string? tag) tag
|
(cond (string? tag) tag
|
||||||
(keyword? tag) (name tag)
|
(keyword? tag) (name tag)
|
||||||
(nil? tag) "node"
|
(nil? tag) "node"
|
||||||
|
@ -63,7 +63,8 @@
|
||||||
(d/any-key? attrs :stroke :stroke-width :stroke-opacity)
|
(d/any-key? attrs :stroke :stroke-width :stroke-opacity)
|
||||||
(setup-stroke attrs)))
|
(setup-stroke attrs)))
|
||||||
|
|
||||||
(defn create-raw-svg [name frame-id x y width height data]
|
(defn create-raw-svg [name frame-id svg-data element-data]
|
||||||
|
(let [{:keys [x y width height]} svg-data]
|
||||||
(-> {:id (uuid/next)
|
(-> {:id (uuid/next)
|
||||||
:type :svg-raw
|
:type :svg-raw
|
||||||
:name name
|
:name name
|
||||||
|
@ -72,8 +73,25 @@
|
||||||
:height height
|
:height height
|
||||||
:x x
|
:x x
|
||||||
:y y
|
:y y
|
||||||
:content (if (map? data) (update data :attrs usvg/clean-attrs) data)}
|
:root-attrs (select-keys svg-data [:width :height])
|
||||||
(gsh/setup-selrect)))
|
:content (cond-> element-data
|
||||||
|
(map? element-data) (update :attrs usvg/clean-attrs))}
|
||||||
|
(gsh/setup-selrect))))
|
||||||
|
|
||||||
|
(defn create-svg-root [frame-id svg-data]
|
||||||
|
(let [{:keys [name x y width height]} svg-data]
|
||||||
|
(-> {:id (uuid/next)
|
||||||
|
:type :group
|
||||||
|
:name name
|
||||||
|
:frame-id frame-id
|
||||||
|
:width width
|
||||||
|
:height height
|
||||||
|
:x x
|
||||||
|
:y y
|
||||||
|
:attrs (-> (get svg-data :attrs) usvg/clean-attrs)
|
||||||
|
;;:content (if (map? data) (update data :attrs usvg/clean-attrs) data)
|
||||||
|
}
|
||||||
|
(gsh/setup-selrect))))
|
||||||
|
|
||||||
(defn parse-path [name frame-id {:keys [attrs] :as data}]
|
(defn parse-path [name frame-id {:keys [attrs] :as data}]
|
||||||
(let [content (ugp/path->content (:d attrs))
|
(let [content (ugp/path->content (:d attrs))
|
||||||
|
@ -89,38 +107,19 @@
|
||||||
|
|
||||||
(add-style-attributes data))))
|
(add-style-attributes data))))
|
||||||
|
|
||||||
(defn parse-svg-element [root-shape data unames]
|
(defn parse-svg-element [frame-id svg-data element-data unames]
|
||||||
(let [root-id (:id root-shape)
|
(let [{:keys [tag]} element-data
|
||||||
frame-id (:frame-id root-shape)
|
name (dwc/generate-unique-name unames (str "svg-" (tag-name tag)))]
|
||||||
{:keys [x y width height]} (:selrect root-shape)
|
|
||||||
{:keys [tag]} data
|
|
||||||
name (dwc/generate-unique-name unames (str "svg-" (tag-name data)))
|
|
||||||
|
|
||||||
shape
|
|
||||||
(case tag
|
(case tag
|
||||||
;; :rect (parse-rect data)
|
;; :rect (parse-rect data)
|
||||||
;; :path (parse-path name frame-id data)
|
;; :path (parse-path name frame-id data)
|
||||||
(create-raw-svg name frame-id x y width height data))]
|
(create-raw-svg name frame-id svg-data element-data))))
|
||||||
|
|
||||||
(-> shape
|
(defn add-svg-child-changes [page-id objects selected frame-id parent-id svg-data ids-mappings result [index data]]
|
||||||
(assoc :svg-id root-id))))
|
(let [[unames [rchs uchs]] result
|
||||||
|
data (update data :attrs usvg/replace-attrs-ids ids-mappings)
|
||||||
(defn svg-uploaded [data x y]
|
shape (parse-svg-element frame-id svg-data data unames)
|
||||||
(ptk/reify ::svg-uploaded
|
|
||||||
ptk/WatchEvent
|
|
||||||
(watch [_ state stream]
|
|
||||||
(let [page-id (:current-page-id state)
|
|
||||||
objects (dwc/lookup-page-objects state page-id)
|
|
||||||
frame-id (cp/frame-id-by-position objects {:x x :y y})
|
|
||||||
selected (get-in state [:workspace-local :selected])
|
|
||||||
|
|
||||||
[width height] (svg-dimensions data)
|
|
||||||
x (- x (/ width 2))
|
|
||||||
y (- y (/ height 2))
|
|
||||||
|
|
||||||
add-svg-child
|
|
||||||
(fn add-svg-child [parent-id root-shape [unames [rchs uchs]] [index {:keys [content] :as data}]]
|
|
||||||
(let [shape (parse-svg-element root-shape data unames)
|
|
||||||
shape-id (:id shape)
|
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)
|
||||||
|
|
||||||
|
@ -135,19 +134,42 @@
|
||||||
|
|
||||||
;; Careful! the undo changes are concatenated reversed (we undo in reverse order
|
;; Careful! the undo changes are concatenated reversed (we undo in reverse order
|
||||||
changes [(d/concat rchs rch1 rch2) (d/concat uch1 uchs)]
|
changes [(d/concat rchs rch1 rch2) (d/concat uch1 uchs)]
|
||||||
unames (conj unames (:name shape))]
|
unames (conj unames (:name shape))
|
||||||
(reduce (partial add-svg-child shape-id root-shape) [unames changes] (d/enumerate content))))
|
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)))))
|
||||||
|
|
||||||
|
(defn svg-uploaded [svg-data x y]
|
||||||
|
(ptk/reify ::svg-uploaded
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state stream]
|
||||||
|
(let [page-id (:current-page-id state)
|
||||||
|
objects (dwc/lookup-page-objects state page-id)
|
||||||
|
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))
|
||||||
|
|
||||||
unames (dwc/retrieve-used-names objects)
|
unames (dwc/retrieve-used-names objects)
|
||||||
|
|
||||||
svg-name (->> (str/replace (:name data) ".svg" "")
|
svg-name (->> (str/replace (:name svg-data) ".svg" "")
|
||||||
(dwc/generate-unique-name unames))
|
(dwc/generate-unique-name unames))
|
||||||
|
|
||||||
root-shape (create-raw-svg svg-name frame-id x y width height data)
|
ids-mappings (usvg/generate-id-mapping svg-data)
|
||||||
|
svg-data (-> svg-data
|
||||||
|
(assoc :x x
|
||||||
|
:y y
|
||||||
|
:width width
|
||||||
|
:height height
|
||||||
|
:name svg-name))
|
||||||
|
|
||||||
|
root-shape (create-svg-root frame-id svg-data)
|
||||||
root-id (:id root-shape)
|
root-id (:id root-shape)
|
||||||
|
|
||||||
changes (dwc/add-shape-changes page-id objects selected root-shape)
|
changes (dwc/add-shape-changes page-id objects selected root-shape)
|
||||||
|
|
||||||
[_ [rchanges uchanges]] (reduce (partial add-svg-child root-id root-shape) [unames changes] (d/enumerate (:content data)))]
|
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)))]
|
||||||
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})
|
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})
|
||||||
(dwc/select-shapes (d/ordered-set root-id)))))))
|
(dwc/select-shapes (d/ordered-set root-id)))))))
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
[app.common.geom.matrix :as gmt]
|
[app.common.geom.matrix :as gmt]
|
||||||
[app.common.geom.point :as gpt]
|
[app.common.geom.point :as gpt]
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes :as gsh]
|
||||||
[app.common.uuid :as uuid]
|
|
||||||
[app.main.ui.shapes.attrs :as usa]
|
[app.main.ui.shapes.attrs :as usa]
|
||||||
[app.util.data :as ud]
|
[app.util.data :as ud]
|
||||||
[app.util.object :as obj]
|
[app.util.object :as obj]
|
||||||
|
@ -21,33 +20,12 @@
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
[rumext.alpha :as mf]))
|
[rumext.alpha :as mf]))
|
||||||
|
|
||||||
|
;; Graphic tags
|
||||||
|
(defonce graphic-element? #{ :circle :ellipse :image :line :path :polygon :polyline :rect :text #_"use"})
|
||||||
|
|
||||||
;; Context to store a re-mapping of the ids
|
;; Context to store a re-mapping of the ids
|
||||||
(def svg-ids-ctx (mf/create-context nil))
|
(def svg-ids-ctx (mf/create-context nil))
|
||||||
|
|
||||||
(defn generate-id-mapping [content]
|
|
||||||
(letfn [(visit-node [result node]
|
|
||||||
(let [element-id (get-in node [:attrs :id])
|
|
||||||
result (cond-> result
|
|
||||||
element-id (assoc element-id (str (uuid/next))))]
|
|
||||||
(reduce visit-node result (:content node))))]
|
|
||||||
(visit-node {} content)))
|
|
||||||
|
|
||||||
|
|
||||||
(defonce replace-regex #"[^#]*#([^)\s]+).*")
|
|
||||||
|
|
||||||
(defn replace-attrs-ids
|
|
||||||
"Replaces the ids inside a property"
|
|
||||||
[ids-mapping attrs]
|
|
||||||
|
|
||||||
(letfn [(replace-ids [key val]
|
|
||||||
(if (map? val)
|
|
||||||
(cd/mapm replace-ids val)
|
|
||||||
(let [[_ from-id] (re-matches replace-regex val)]
|
|
||||||
(if (and from-id (contains? ids-mapping from-id))
|
|
||||||
(str/replace val from-id (get ids-mapping from-id))
|
|
||||||
val))))]
|
|
||||||
(cd/mapm replace-ids attrs)))
|
|
||||||
|
|
||||||
(defn set-styles [attrs shape]
|
(defn set-styles [attrs shape]
|
||||||
(let [custom-attrs (usa/extract-style-attrs shape)
|
(let [custom-attrs (usa/extract-style-attrs shape)
|
||||||
attrs (cond-> attrs
|
attrs (cond-> attrs
|
||||||
|
@ -58,6 +36,20 @@
|
||||||
(obj/merge! custom-attrs)
|
(obj/merge! custom-attrs)
|
||||||
(obj/set! "style" style))))
|
(obj/set! "style" style))))
|
||||||
|
|
||||||
|
(defn translate-shape [attrs shape]
|
||||||
|
(let [{svg-width :width svg-height :height :as root-shape} (:root-attrs shape)
|
||||||
|
{:keys [x y width height]} (:selrect shape)
|
||||||
|
transform (->> (:transform attrs "")
|
||||||
|
(str (gmt/multiply
|
||||||
|
(gmt/matrix)
|
||||||
|
(gsh/transform-matrix shape)
|
||||||
|
(gmt/translate-matrix (gpt/point x y))
|
||||||
|
(gmt/scale-matrix (gpt/point (/ width svg-width) (/ height svg-height))))
|
||||||
|
" "))]
|
||||||
|
(cond-> attrs
|
||||||
|
(and root-shape (graphic-element? (-> shape :content :tag)))
|
||||||
|
(assoc :transform transform))))
|
||||||
|
|
||||||
(mf/defc svg-root
|
(mf/defc svg-root
|
||||||
{::mf/wrap-props false}
|
{::mf/wrap-props false}
|
||||||
[props]
|
[props]
|
||||||
|
@ -68,7 +60,7 @@
|
||||||
{:keys [x y width height]} shape
|
{:keys [x y width height]} shape
|
||||||
{:keys [tag attrs] :as content} (:content shape)
|
{:keys [tag attrs] :as content} (:content shape)
|
||||||
|
|
||||||
ids-mapping (mf/use-memo #(generate-id-mapping content))
|
ids-mapping (mf/use-memo #(usvg/generate-id-mapping content))
|
||||||
|
|
||||||
attrs (-> (set-styles attrs shape)
|
attrs (-> (set-styles attrs shape)
|
||||||
(obj/set! "x" x)
|
(obj/set! "x" x)
|
||||||
|
@ -91,11 +83,16 @@
|
||||||
{:keys [attrs tag]} content
|
{:keys [attrs tag]} content
|
||||||
|
|
||||||
ids-mapping (mf/use-ctx svg-ids-ctx)
|
ids-mapping (mf/use-ctx svg-ids-ctx)
|
||||||
attrs (mf/use-memo #(replace-attrs-ids ids-mapping attrs))
|
|
||||||
element-id (get-in content [:attrs :id])
|
|
||||||
|
|
||||||
|
attrs (mf/use-memo #(usvg/replace-attrs-ids attrs ids-mapping))
|
||||||
|
|
||||||
|
attrs (translate-shape attrs shape)
|
||||||
|
element-id (get-in content [:attrs :id])
|
||||||
attrs (cond-> (set-styles attrs shape)
|
attrs (cond-> (set-styles attrs shape)
|
||||||
element-id (obj/set! "id" (get ids-mapping element-id)))]
|
(and element-id (contains? ids-mapping element-id))
|
||||||
|
(obj/set! "id" (get ids-mapping element-id)))
|
||||||
|
|
||||||
|
{:keys [x y width height]} (:selrect shape)]
|
||||||
[:> (name tag) attrs children]))
|
[:> (name tag) attrs children]))
|
||||||
|
|
||||||
(defn svg-raw-shape [shape-wrapper]
|
(defn svg-raw-shape [shape-wrapper]
|
||||||
|
|
|
@ -57,8 +57,7 @@
|
||||||
:shape shape
|
:shape shape
|
||||||
:childs childs}]
|
:childs childs}]
|
||||||
|
|
||||||
(when (= tag :svg)
|
[:rect.actions
|
||||||
[:rect.group-actions
|
|
||||||
{:x x
|
{:x x
|
||||||
:y y
|
:y y
|
||||||
:transform transform
|
:transform transform
|
||||||
|
@ -69,7 +68,7 @@
|
||||||
:on-double-click handle-double-click
|
:on-double-click handle-double-click
|
||||||
:on-context-menu handle-context-menu
|
:on-context-menu handle-context-menu
|
||||||
:on-pointer-over handle-pointer-enter
|
:on-pointer-over handle-pointer-enter
|
||||||
:on-pointer-out handle-pointer-leave}])]
|
:on-pointer-out handle-pointer-leave}]]
|
||||||
|
|
||||||
;; We cannot wrap inside groups the shapes that go inside the defs tag
|
;; We cannot wrap inside groups the shapes that go inside the defs tag
|
||||||
;; we use the context so we know when we should not render the container
|
;; we use the context so we know when we should not render the container
|
||||||
|
|
|
@ -98,12 +98,10 @@
|
||||||
stroke-values (get-stroke-values shape)]
|
stroke-values (get-stroke-values shape)]
|
||||||
|
|
||||||
(when (contains? svg-elements tag)
|
(when (contains? svg-elements tag)
|
||||||
[:*
|
|
||||||
(when (= tag :svg)
|
|
||||||
[:*
|
[:*
|
||||||
[:& measures-menu {:ids ids
|
[:& measures-menu {:ids ids
|
||||||
:type type
|
:type type
|
||||||
:values measure-values}]])
|
:values measure-values}]
|
||||||
|
|
||||||
[:& fill-menu {:ids ids
|
[:& fill-menu {:ids ids
|
||||||
:type type
|
:type type
|
||||||
|
|
|
@ -9,8 +9,12 @@
|
||||||
|
|
||||||
(ns app.util.svg
|
(ns app.util.svg
|
||||||
(:require
|
(:require
|
||||||
|
[app.common.uuid :as uuid]
|
||||||
|
[app.common.data :as cd]
|
||||||
[cuerdas.core :as str]))
|
[cuerdas.core :as str]))
|
||||||
|
|
||||||
|
(defonce replace-regex #"[^#]*#([^)\s]+).*")
|
||||||
|
|
||||||
(defn clean-attrs
|
(defn clean-attrs
|
||||||
"Transforms attributes to their react equivalent"
|
"Transforms attributes to their react equivalent"
|
||||||
[attrs]
|
[attrs]
|
||||||
|
@ -40,3 +44,33 @@
|
||||||
(->> attrs
|
(->> attrs
|
||||||
(map map-fn)
|
(map map-fn)
|
||||||
(into {}))))
|
(into {}))))
|
||||||
|
|
||||||
|
(defn replace-attrs-ids
|
||||||
|
"Replaces the ids inside a property"
|
||||||
|
[attrs ids-mapping]
|
||||||
|
(if (and ids-mapping (not (empty? ids-mapping)))
|
||||||
|
(letfn [(replace-ids [key val]
|
||||||
|
(cond
|
||||||
|
(map? val)
|
||||||
|
(cd/mapm replace-ids val)
|
||||||
|
|
||||||
|
(and (= key :id) (contains? ids-mapping val))
|
||||||
|
(get ids-mapping val)
|
||||||
|
|
||||||
|
:else
|
||||||
|
(let [[_ from-id] (re-matches replace-regex val)]
|
||||||
|
(if (and from-id (contains? ids-mapping from-id))
|
||||||
|
(str/replace val from-id (get ids-mapping from-id))
|
||||||
|
val))))]
|
||||||
|
(cd/mapm replace-ids attrs))
|
||||||
|
|
||||||
|
;; Ids-mapping is null
|
||||||
|
attrs))
|
||||||
|
|
||||||
|
(defn generate-id-mapping [content]
|
||||||
|
(letfn [(visit-node [result node]
|
||||||
|
(let [element-id (get-in node [:attrs :id])
|
||||||
|
result (cond-> result
|
||||||
|
element-id (assoc element-id (str (uuid/next))))]
|
||||||
|
(reduce visit-node result (:content node))))]
|
||||||
|
(visit-node {} content)))
|
||||||
|
|
1
vendor/svgclean/main.js
vendored
1
vendor/svgclean/main.js
vendored
|
@ -4,6 +4,7 @@ const plugins = [
|
||||||
{removeViewBox: false},
|
{removeViewBox: false},
|
||||||
{moveElemsAttrsToGroup: false},
|
{moveElemsAttrsToGroup: false},
|
||||||
{convertStyleToAttrs: false},
|
{convertStyleToAttrs: false},
|
||||||
|
{removeUselessDefs: false},
|
||||||
{convertPathData: {
|
{convertPathData: {
|
||||||
lineShorthands: false,
|
lineShorthands: false,
|
||||||
curveSmoothShorthands: false,
|
curveSmoothShorthands: false,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue