mirror of
https://github.com/penpot/penpot.git
synced 2025-06-02 06:31:37 +02:00
♻️ Refactor svg uploads
This commit is contained in:
parent
b999c05d1e
commit
25824629f2
8 changed files with 216 additions and 84 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -33,3 +33,4 @@ node_modules
|
||||||
/deploy
|
/deploy
|
||||||
/web
|
/web
|
||||||
/_dump
|
/_dump
|
||||||
|
/vendor/svgclean/bundle*.js
|
File diff suppressed because one or more lines are too long
|
@ -350,3 +350,7 @@
|
||||||
;; (when (:macro m#)
|
;; (when (:macro m#)
|
||||||
;; (.setMacro (var ~n)))
|
;; (.setMacro (var ~n)))
|
||||||
~vr))))
|
~vr))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn any-key? [element & rest]
|
||||||
|
(some #(contains? element %) rest))
|
||||||
|
|
|
@ -647,78 +647,3 @@
|
||||||
(rx/of (create-and-add-shape :image x y shape))))))
|
(rx/of (create-and-add-shape :image x y shape))))))
|
||||||
|
|
||||||
|
|
||||||
(defn- svg-dimensions [data]
|
|
||||||
(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]))
|
|
||||||
|
|
||||||
(defn svg-uploaded [data x y]
|
|
||||||
(ptk/reify ::svg-uploaded
|
|
||||||
ptk/WatchEvent
|
|
||||||
(watch [_ state stream]
|
|
||||||
(let [page-id (:current-page-id state)
|
|
||||||
objects (lookup-page-objects state page-id)
|
|
||||||
frame-id (cp/frame-id-by-position objects {:x x :y y})
|
|
||||||
|
|
||||||
[width height] (svg-dimensions data)
|
|
||||||
x (- x (/ width 2))
|
|
||||||
y (- y (/ height 2))
|
|
||||||
|
|
||||||
create-svg-raw
|
|
||||||
(fn [{:keys [tag] :as data} unames root-id]
|
|
||||||
(let [base (cond (string? tag) tag
|
|
||||||
(keyword? tag) (name tag)
|
|
||||||
(nil? tag) "node"
|
|
||||||
:else (str tag))]
|
|
||||||
(-> {:id (uuid/next)
|
|
||||||
:type :svg-raw
|
|
||||||
:name (generate-unique-name unames (str "svg-" base))
|
|
||||||
:frame-id frame-id
|
|
||||||
;; For svg children we set its coordinates as the root of the svg
|
|
||||||
:width width
|
|
||||||
:height height
|
|
||||||
:x x
|
|
||||||
:y y
|
|
||||||
:content data
|
|
||||||
:root-id root-id}
|
|
||||||
(gsh/setup-selrect))))
|
|
||||||
|
|
||||||
add-svg-child
|
|
||||||
(fn add-svg-child [parent-id root-id [unames [rchs uchs]] [index {:keys [content] :as data}]]
|
|
||||||
(let [shape (create-svg-raw data unames root-id)
|
|
||||||
shape-id (:id shape)
|
|
||||||
[rch1 uch1] (add-shape-changes page-id 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-id) [unames changes] (d/enumerate (:content data)))))
|
|
||||||
|
|
||||||
unames (retrieve-used-names objects)
|
|
||||||
|
|
||||||
svg-name (->> (str/replace (:name data) ".svg" "")
|
|
||||||
(generate-unique-name unames))
|
|
||||||
|
|
||||||
root-shape (create-svg-raw data unames nil)
|
|
||||||
root-shape (-> root-shape
|
|
||||||
(assoc :name svg-name))
|
|
||||||
root-id (:id root-shape)
|
|
||||||
|
|
||||||
changes (add-shape-changes page-id root-shape)
|
|
||||||
|
|
||||||
[_ [rchanges uchanges]] (reduce (partial add-svg-child root-id root-id) [unames changes] (d/enumerate (:content data)))]
|
|
||||||
(rx/of (commit-changes rchanges uchanges {:commit-local? true})
|
|
||||||
(select-shapes (d/ordered-set root-id)))))))
|
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
[app.main.data.media :as di]
|
[app.main.data.media :as di]
|
||||||
[app.main.data.messages :as dm]
|
[app.main.data.messages :as dm]
|
||||||
[app.main.data.workspace.common :as dwc]
|
[app.main.data.workspace.common :as dwc]
|
||||||
|
[app.main.data.workspace.svg-upload :as svg]
|
||||||
[app.main.data.workspace.libraries :as dwl]
|
[app.main.data.workspace.libraries :as dwl]
|
||||||
[app.main.repo :as rp]
|
[app.main.repo :as rp]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
|
@ -498,7 +499,7 @@
|
||||||
{:on-image
|
{:on-image
|
||||||
#(st/emit! (dwc/image-uploaded % x y))
|
#(st/emit! (dwc/image-uploaded % x y))
|
||||||
:on-svg
|
:on-svg
|
||||||
#(st/emit! (dwc/svg-uploaded % x y))}))]
|
#(st/emit! (svg/svg-uploaded % x y))}))]
|
||||||
(upload-media-objects params)))
|
(upload-media-objects params)))
|
||||||
|
|
||||||
|
|
||||||
|
|
151
frontend/src/app/main/data/workspace/svg_upload.cljs
Normal file
151
frontend/src/app/main/data/workspace/svg_upload.cljs
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
;; 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/.
|
||||||
|
;;
|
||||||
|
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
|
;; defined by the Mozilla Public License, v. 2.0.
|
||||||
|
;;
|
||||||
|
;; Copyright (c) 2020 UXBOX Labs SL
|
||||||
|
|
||||||
|
(ns app.main.data.workspace.svg-upload
|
||||||
|
(:require
|
||||||
|
[app.common.data :as d]
|
||||||
|
[app.util.data :as ud]
|
||||||
|
[app.common.geom.shapes :as gsh]
|
||||||
|
[app.common.pages :as cp]
|
||||||
|
[app.common.uuid :as uuid]
|
||||||
|
[app.main.data.workspace.common :as dwc]
|
||||||
|
[beicon.core :as rx]
|
||||||
|
[cuerdas.core :as str]
|
||||||
|
[potok.core :as ptk]
|
||||||
|
|
||||||
|
[app.util.geom.path :as ugp]))
|
||||||
|
|
||||||
|
(defn- svg-dimensions [data]
|
||||||
|
(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]))
|
||||||
|
|
||||||
|
|
||||||
|
(defn tag-name [{:keys [tag]}]
|
||||||
|
(cond (string? tag) tag
|
||||||
|
(keyword? tag) (name tag)
|
||||||
|
(nil? tag) "node"
|
||||||
|
:else (str tag)))
|
||||||
|
|
||||||
|
(defn setup-fill [shape attrs]
|
||||||
|
(-> shape
|
||||||
|
(assoc :fill-color (:fill attrs "#000000"))
|
||||||
|
(assoc :fill-opacity (ud/parse-float (:fill-opacity attrs "1")))))
|
||||||
|
|
||||||
|
(defn setup-stroke [shape attrs]
|
||||||
|
(-> shape
|
||||||
|
(assoc :stroke-color (:stroke attrs "#000000"))
|
||||||
|
(assoc :stroke-opacity (ud/parse-float (:stroke-opacity attrs 1)))
|
||||||
|
(assoc :stroke-style :solid)
|
||||||
|
(assoc :stroke-width (ud/parse-float (:stroke-width attrs "1")))
|
||||||
|
(assoc :stroke-alignment :center)))
|
||||||
|
|
||||||
|
(defn add-style-attributes [shape {:keys [attrs]}]
|
||||||
|
(cond-> shape
|
||||||
|
(d/any-key? attrs :fill :fill-opacity)
|
||||||
|
(setup-fill attrs)
|
||||||
|
|
||||||
|
(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 data}
|
||||||
|
(gsh/setup-selrect)))
|
||||||
|
|
||||||
|
(defn parse-path [name frame-id {:keys [attrs] :as data}]
|
||||||
|
(let [content (ugp/path->content (:d attrs))
|
||||||
|
selrect (gsh/content->selrect content)
|
||||||
|
points (gsh/rect->points selrect)]
|
||||||
|
(-> {:id (uuid/next)
|
||||||
|
:type :path
|
||||||
|
:name name
|
||||||
|
:frame-id frame-id
|
||||||
|
;; :width width
|
||||||
|
;; :height height
|
||||||
|
;; :x x
|
||||||
|
;; :y y
|
||||||
|
:content content
|
||||||
|
:selrect selrect
|
||||||
|
:points points}
|
||||||
|
|
||||||
|
(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))]
|
||||||
|
|
||||||
|
(-> shape
|
||||||
|
(assoc :svg-id root-id))))
|
||||||
|
|
||||||
|
(defn svg-uploaded [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})
|
||||||
|
|
||||||
|
[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)
|
||||||
|
[rch1 uch1] (dwc/add-shape-changes page-id 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" "")
|
||||||
|
(dwc/generate-unique-name unames))
|
||||||
|
|
||||||
|
root-shape (create-raw-svg svg-name frame-id x y width height data)
|
||||||
|
root-id (:id root-shape)
|
||||||
|
|
||||||
|
changes (dwc/add-shape-changes page-id root-shape)
|
||||||
|
|
||||||
|
[_ [rchanges uchanges]] (reduce (partial add-svg-child root-id root-shape) [unames changes] (d/enumerate (:content data)))]
|
||||||
|
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})
|
||||||
|
(dwc/select-shapes (d/ordered-set root-id)))))))
|
|
@ -180,6 +180,48 @@
|
||||||
param-list (command->param-list entry)]
|
param-list (command->param-list entry)]
|
||||||
(str/fmt "%s%s" command-str (str/join " " param-list))))
|
(str/fmt "%s%s" command-str (str/join " " param-list))))
|
||||||
|
|
||||||
|
(defn cmd-pos [{:keys [params]}]
|
||||||
|
(when (and (contains? params :x)
|
||||||
|
(contains? params :y))
|
||||||
|
(gpt/point params)))
|
||||||
|
|
||||||
|
(defn simplify-commands
|
||||||
|
"Removes some commands and convert relative to absolute coordinates"
|
||||||
|
[commands]
|
||||||
|
|
||||||
|
(let [simplify-command
|
||||||
|
(fn [[pos result] [command prev]]
|
||||||
|
(let [command
|
||||||
|
(cond-> command
|
||||||
|
(= :line-to-horizontal (:command command))
|
||||||
|
(-> (assoc :command :line-to)
|
||||||
|
(update :params dissoc :value)
|
||||||
|
(assoc-in [:params :x] (get-in command [:params :value]))
|
||||||
|
(assoc-in [:params :y] (if (:relative command) 0 (:y pos))))
|
||||||
|
|
||||||
|
(= :line-to-vertical (:command command))
|
||||||
|
(-> (assoc :command :line-to)
|
||||||
|
(update :params dissoc :value)
|
||||||
|
(assoc-in [:params :y] (get-in command [:params :value]))
|
||||||
|
(assoc-in [:params :x] (if (:relative command) 0 (:x pos))))
|
||||||
|
|
||||||
|
(:relative command)
|
||||||
|
(-> (assoc :relative false)
|
||||||
|
(cd/update-in-when [:params :x] + (:x pos))
|
||||||
|
(cd/update-in-when [:params :y] + (:y pos)))
|
||||||
|
|
||||||
|
|
||||||
|
)]
|
||||||
|
[(cmd-pos command) (conj result command)]))
|
||||||
|
|
||||||
|
start (first commands)
|
||||||
|
start-pos (cmd-pos start)]
|
||||||
|
|
||||||
|
|
||||||
|
(->> (map vector (rest commands) commands)
|
||||||
|
(reduce simplify-command [start-pos [start]])
|
||||||
|
(second))))
|
||||||
|
|
||||||
(defn path->content [string]
|
(defn path->content [string]
|
||||||
(let [clean-string (-> string
|
(let [clean-string (-> string
|
||||||
(str/trim)
|
(str/trim)
|
||||||
|
@ -188,7 +230,8 @@
|
||||||
;; Remove all consecutive spaces
|
;; Remove all consecutive spaces
|
||||||
(str/replace #"\s+" " "))
|
(str/replace #"\s+" " "))
|
||||||
commands (re-seq commands-regex clean-string)]
|
commands (re-seq commands-regex clean-string)]
|
||||||
(mapcat parse-command commands)))
|
(-> (mapcat parse-command commands)
|
||||||
|
(simplify-commands))))
|
||||||
|
|
||||||
(defn content->path [content]
|
(defn content->path [content]
|
||||||
(->> content
|
(->> content
|
||||||
|
|
17
vendor/svgclean/main.js
vendored
17
vendor/svgclean/main.js
vendored
|
@ -1,9 +1,16 @@
|
||||||
const plugins = [
|
const plugins = [
|
||||||
{removeDimensions: true},
|
{removeDimensions: true},
|
||||||
{removeXMLNS: true},
|
{removeXMLNS: true},
|
||||||
{removeScriptElement: true},
|
{removeScriptElement: true},
|
||||||
{removeViewBox: false},
|
{removeViewBox: false},
|
||||||
{moveElemsAttrsToGroup: false}
|
{moveElemsAttrsToGroup: false},
|
||||||
|
{
|
||||||
|
convertPathData: {
|
||||||
|
lineShorthands: false,
|
||||||
|
curveSmoothShorthands: false,
|
||||||
|
forceAbsolutePath: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const svgc = require("./src/svgclean.js");
|
const svgc = require("./src/svgclean.js");
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue