mirror of
https://github.com/penpot/penpot.git
synced 2025-04-16 00:41:25 -05:00
✨ Makes import SVG groups
This commit is contained in:
parent
507f3c06e7
commit
741d67c30b
8 changed files with 151 additions and 100 deletions
backend/resources
exporter/src/app/http
frontend/src/app
main
data/workspace
ui
util
vendor/svgclean
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)]
|
||||
[width height]))
|
||||
|
||||
(defn tag-name [{:keys [tag]}]
|
||||
(defn tag-name [tag]
|
||||
(cond (string? tag) tag
|
||||
(keyword? tag) (name tag)
|
||||
(nil? tag) "node"
|
||||
|
@ -63,17 +63,35 @@
|
|||
(d/any-key? attrs :stroke :stroke-width :stroke-opacity)
|
||||
(setup-stroke attrs)))
|
||||
|
||||
(defn create-raw-svg [name frame-id x y width height data]
|
||||
(-> {:id (uuid/next)
|
||||
:type :svg-raw
|
||||
:name name
|
||||
:frame-id frame-id
|
||||
:width width
|
||||
:height height
|
||||
:x x
|
||||
:y y
|
||||
:content (if (map? data) (update data :attrs usvg/clean-attrs) data)}
|
||||
(gsh/setup-selrect)))
|
||||
(defn create-raw-svg [name frame-id svg-data element-data]
|
||||
(let [{:keys [x y width height]} svg-data]
|
||||
(-> {:id (uuid/next)
|
||||
:type :svg-raw
|
||||
:name name
|
||||
:frame-id frame-id
|
||||
:width width
|
||||
:height height
|
||||
:x x
|
||||
:y y
|
||||
:root-attrs (select-keys svg-data [:width :height])
|
||||
: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}]
|
||||
(let [content (ugp/path->content (:d attrs))
|
||||
|
@ -89,23 +107,38 @@
|
|||
|
||||
(add-style-attributes data))))
|
||||
|
||||
(defn parse-svg-element [root-shape data unames]
|
||||
(let [root-id (:id root-shape)
|
||||
frame-id (:frame-id root-shape)
|
||||
{: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
|
||||
;; :rect (parse-rect data)
|
||||
;; :path (parse-path name frame-id data)
|
||||
(create-raw-svg name frame-id x y width height data))]
|
||||
(defn parse-svg-element [frame-id svg-data element-data unames]
|
||||
(let [{:keys [tag]} element-data
|
||||
name (dwc/generate-unique-name unames (str "svg-" (tag-name tag)))]
|
||||
|
||||
(-> shape
|
||||
(assoc :svg-id root-id))))
|
||||
(case tag
|
||||
;; :rect (parse-rect data)
|
||||
;; :path (parse-path name frame-id data)
|
||||
(create-raw-svg name frame-id svg-data element-data))))
|
||||
|
||||
(defn svg-uploaded [data x y]
|
||||
(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
|
||||
data (update data :attrs usvg/replace-attrs-ids ids-mappings)
|
||||
shape (parse-svg-element frame-id svg-data data unames)
|
||||
shape-id (:id shape)
|
||||
[rch1 uch1] (dwc/add-shape-changes page-id objects selected shape)
|
||||
|
||||
;; Mov-objects won't have undo because we "delete" the object in the undo of the
|
||||
;; previous operation
|
||||
rch2 [{:type :mov-objects
|
||||
:parent-id parent-id
|
||||
:frame-id frame-id
|
||||
:page-id page-id
|
||||
:index index
|
||||
:shapes [shape-id]}]
|
||||
|
||||
;; 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)))))
|
||||
|
||||
(defn svg-uploaded [svg-data x y]
|
||||
(ptk/reify ::svg-uploaded
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
|
@ -114,40 +147,29 @@
|
|||
frame-id (cp/frame-id-by-position objects {:x x :y y})
|
||||
selected (get-in state [:workspace-local :selected])
|
||||
|
||||
[width height] (svg-dimensions data)
|
||||
[width height] (svg-dimensions svg-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)
|
||||
[rch1 uch1] (dwc/add-shape-changes page-id objects selected shape)
|
||||
|
||||
;; Mov-objects won't have undo because we "delete" the object in the undo of the
|
||||
;; previous operation
|
||||
rch2 [{:type :mov-objects
|
||||
:parent-id parent-id
|
||||
:frame-id frame-id
|
||||
:page-id page-id
|
||||
:index index
|
||||
:shapes [shape-id]}]
|
||||
|
||||
;; 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))]
|
||||
(reduce (partial add-svg-child shape-id root-shape) [unames changes] (d/enumerate content))))
|
||||
|
||||
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))
|
||||
|
||||
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)
|
||||
|
||||
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})
|
||||
(dwc/select-shapes (d/ordered-set root-id)))))))
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.ui.shapes.attrs :as usa]
|
||||
[app.util.data :as ud]
|
||||
[app.util.object :as obj]
|
||||
|
@ -21,33 +20,12 @@
|
|||
[cuerdas.core :as str]
|
||||
[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
|
||||
(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]
|
||||
(let [custom-attrs (usa/extract-style-attrs shape)
|
||||
attrs (cond-> attrs
|
||||
|
@ -58,6 +36,20 @@
|
|||
(obj/merge! custom-attrs)
|
||||
(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/wrap-props false}
|
||||
[props]
|
||||
|
@ -68,7 +60,7 @@
|
|||
{:keys [x y width height]} 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)
|
||||
(obj/set! "x" x)
|
||||
|
@ -91,11 +83,16 @@
|
|||
{:keys [attrs tag]} content
|
||||
|
||||
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)
|
||||
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]))
|
||||
|
||||
(defn svg-raw-shape [shape-wrapper]
|
||||
|
|
|
@ -57,19 +57,18 @@
|
|||
:shape shape
|
||||
:childs childs}]
|
||||
|
||||
(when (= tag :svg)
|
||||
[:rect.group-actions
|
||||
{:x x
|
||||
:y y
|
||||
:transform transform
|
||||
:width width
|
||||
:height height
|
||||
:fill "transparent"
|
||||
:on-mouse-down handle-mouse-down
|
||||
:on-double-click handle-double-click
|
||||
:on-context-menu handle-context-menu
|
||||
:on-pointer-over handle-pointer-enter
|
||||
:on-pointer-out handle-pointer-leave}])]
|
||||
[:rect.actions
|
||||
{:x x
|
||||
:y y
|
||||
:transform transform
|
||||
:width width
|
||||
:height height
|
||||
:fill "transparent"
|
||||
:on-mouse-down handle-mouse-down
|
||||
:on-double-click handle-double-click
|
||||
:on-context-menu handle-context-menu
|
||||
:on-pointer-over handle-pointer-enter
|
||||
:on-pointer-out handle-pointer-leave}]]
|
||||
|
||||
;; 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
|
||||
|
|
|
@ -99,11 +99,9 @@
|
|||
|
||||
(when (contains? svg-elements tag)
|
||||
[:*
|
||||
(when (= tag :svg)
|
||||
[:*
|
||||
[:& measures-menu {:ids ids
|
||||
:type type
|
||||
:values measure-values}]])
|
||||
[:& measures-menu {:ids ids
|
||||
:type type
|
||||
:values measure-values}]
|
||||
|
||||
[:& fill-menu {:ids ids
|
||||
:type type
|
||||
|
|
|
@ -9,8 +9,12 @@
|
|||
|
||||
(ns app.util.svg
|
||||
(:require
|
||||
[app.common.uuid :as uuid]
|
||||
[app.common.data :as cd]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
(defonce replace-regex #"[^#]*#([^)\s]+).*")
|
||||
|
||||
(defn clean-attrs
|
||||
"Transforms attributes to their react equivalent"
|
||||
[attrs]
|
||||
|
@ -40,3 +44,33 @@
|
|||
(->> attrs
|
||||
(map map-fn)
|
||||
(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},
|
||||
{moveElemsAttrsToGroup: false},
|
||||
{convertStyleToAttrs: false},
|
||||
{removeUselessDefs: false},
|
||||
{convertPathData: {
|
||||
lineShorthands: false,
|
||||
curveSmoothShorthands: false,
|
||||
|
|
Loading…
Add table
Reference in a new issue