mirror of
https://github.com/penpot/penpot.git
synced 2025-05-09 20:56:37 +02:00
✨ Import pages with imported svgs
This commit is contained in:
parent
e880d94f51
commit
3aa5fda695
9 changed files with 422 additions and 99 deletions
|
@ -274,8 +274,7 @@
|
||||||
[{:keys [pool] :as cfg} {:keys [id profile-id] :as params}]
|
[{:keys [pool] :as cfg} {:keys [id profile-id] :as params}]
|
||||||
(db/with-atomic [conn pool]
|
(db/with-atomic [conn pool]
|
||||||
(db/xact-lock! conn id)
|
(db/xact-lock! conn id)
|
||||||
(let [{:keys [id] :as file} (db/get-by-id conn :file id {:for-key-share true
|
(let [{:keys [id] :as file} (db/get-by-id conn :file id {:for-key-share true})]
|
||||||
:uncheked true})]
|
|
||||||
(files/check-edition-permissions! conn profile-id id)
|
(files/check-edition-permissions! conn profile-id id)
|
||||||
(update-file (assoc cfg :conn conn)
|
(update-file (assoc cfg :conn conn)
|
||||||
(assoc params :file file)))))
|
(assoc params :file file)))))
|
||||||
|
@ -399,10 +398,10 @@
|
||||||
(proj/check-edition-permissions! conn profile-id project-id)
|
(proj/check-edition-permissions! conn profile-id project-id)
|
||||||
(create-file conn (assoc params :deleted-at (dt/in-future {:days 1})))))
|
(create-file conn (assoc params :deleted-at (dt/in-future {:days 1})))))
|
||||||
|
|
||||||
(s/def ::make-permanent
|
(s/def ::persist-temp-file
|
||||||
(s/keys :req-un [::id ::profile-id]))
|
(s/keys :req-un [::id ::profile-id]))
|
||||||
|
|
||||||
(sv/defmethod ::make-permanent
|
(sv/defmethod ::persist-temp-file
|
||||||
[{:keys [pool] :as cfg} {:keys [id profile-id] :as params}]
|
[{:keys [pool] :as cfg} {:keys [id profile-id] :as params}]
|
||||||
(db/with-atomic [conn pool]
|
(db/with-atomic [conn pool]
|
||||||
(files/check-edition-permissions! conn profile-id id)
|
(files/check-edition-permissions! conn profile-id id)
|
||||||
|
|
|
@ -503,3 +503,51 @@
|
||||||
|
|
||||||
(->> keys
|
(->> keys
|
||||||
(reduce diff-attr {}))))
|
(reduce diff-attr {}))))
|
||||||
|
|
||||||
|
(defn- extract-numeric-suffix
|
||||||
|
[basename]
|
||||||
|
(if-let [[match p1 p2] (re-find #"(.*)-([0-9]+)$" basename)]
|
||||||
|
[p1 (+ 1 (parse-integer p2))]
|
||||||
|
[basename 1]))
|
||||||
|
|
||||||
|
(defn unique-name
|
||||||
|
"A unique name generator"
|
||||||
|
([basename used]
|
||||||
|
(unique-name basename used false))
|
||||||
|
|
||||||
|
([basename used prefix-first?]
|
||||||
|
(assert (string? basename))
|
||||||
|
(assert (set? used))
|
||||||
|
|
||||||
|
(let [[prefix initial] (extract-numeric-suffix basename)]
|
||||||
|
(if (and (not prefix-first?)
|
||||||
|
(not (contains? used basename)))
|
||||||
|
basename
|
||||||
|
(loop [counter initial]
|
||||||
|
(let [candidate (if (and (= 1 counter) prefix-first?)
|
||||||
|
(str prefix)
|
||||||
|
(str prefix "-" counter))]
|
||||||
|
(if (contains? used candidate)
|
||||||
|
(recur (inc counter))
|
||||||
|
candidate)))))))
|
||||||
|
|
||||||
|
(defn deep-mapm
|
||||||
|
"Applies a map function to an associative map and recurses over its children
|
||||||
|
when it's a vector or a map"
|
||||||
|
[mfn m]
|
||||||
|
(let [do-map
|
||||||
|
(fn [[k v]]
|
||||||
|
(cond
|
||||||
|
(or (vector? v) (map? v))
|
||||||
|
[k (deep-mapm mfn v)]
|
||||||
|
:else
|
||||||
|
(mfn [k v])))]
|
||||||
|
(cond
|
||||||
|
(map? m)
|
||||||
|
(into {} (map do-map) m)
|
||||||
|
|
||||||
|
(vector? m)
|
||||||
|
(into [] (map (partial deep-mapm mfn)) m)
|
||||||
|
|
||||||
|
:else
|
||||||
|
m)))
|
||||||
|
|
|
@ -13,7 +13,8 @@
|
||||||
[app.common.pages.init :as init]
|
[app.common.pages.init :as init]
|
||||||
[app.common.pages.spec :as spec]
|
[app.common.pages.spec :as spec]
|
||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
[app.common.uuid :as uuid]))
|
[app.common.uuid :as uuid]
|
||||||
|
[cuerdas.core :as str]))
|
||||||
|
|
||||||
(def root-frame uuid/zero)
|
(def root-frame uuid/zero)
|
||||||
|
|
||||||
|
@ -51,6 +52,27 @@
|
||||||
:parent-id parent-id
|
:parent-id parent-id
|
||||||
:obj obj}))))
|
:obj obj}))))
|
||||||
|
|
||||||
|
(defn generate-name
|
||||||
|
[type data]
|
||||||
|
(if (= type :svg-raw)
|
||||||
|
(let [tag (get-in data [:content :tag])]
|
||||||
|
(str "svg-" (cond (string? tag) tag
|
||||||
|
(keyword? tag) (d/name tag)
|
||||||
|
(nil? tag) "node"
|
||||||
|
:else (str tag))))
|
||||||
|
(str/capital (d/name type))))
|
||||||
|
|
||||||
|
(defn check-name
|
||||||
|
"Given a tag returns its layer name"
|
||||||
|
[data file type]
|
||||||
|
|
||||||
|
(cond-> data
|
||||||
|
(nil? (:name data))
|
||||||
|
(assoc :name (generate-name type data))
|
||||||
|
|
||||||
|
:always
|
||||||
|
(update :name d/unique-name (:unames file))))
|
||||||
|
|
||||||
;; PUBLIC API
|
;; PUBLIC API
|
||||||
|
|
||||||
(defn create-file
|
(defn create-file
|
||||||
|
@ -82,14 +104,31 @@
|
||||||
(assoc :current-frame-id root-frame)
|
(assoc :current-frame-id root-frame)
|
||||||
|
|
||||||
;; Current parent stack we'll be nesting
|
;; Current parent stack we'll be nesting
|
||||||
(assoc :parent-stack [root-frame]))))
|
(assoc :parent-stack [root-frame])
|
||||||
|
|
||||||
|
;; Last object id added
|
||||||
|
(assoc :last-id nil)
|
||||||
|
|
||||||
|
;; Current used names
|
||||||
|
(assoc :unames #{}))))
|
||||||
|
|
||||||
|
(defn close-page [file]
|
||||||
|
(-> file
|
||||||
|
(dissoc :current-page-id)
|
||||||
|
(dissoc :parent-stack)
|
||||||
|
(dissoc :last-id)
|
||||||
|
(dissoc :unames)))
|
||||||
|
|
||||||
(defn add-artboard [file data]
|
(defn add-artboard [file data]
|
||||||
(let [obj (-> (init/make-minimal-shape :frame)
|
(let [obj (-> (init/make-minimal-shape :frame)
|
||||||
(merge data))]
|
(merge data)
|
||||||
|
(check-name file :frame)
|
||||||
|
(d/without-nils))]
|
||||||
(-> file
|
(-> file
|
||||||
(commit-shape obj)
|
(commit-shape obj)
|
||||||
(assoc :current-frame-id (:id obj))
|
(assoc :current-frame-id (:id obj))
|
||||||
|
(assoc :last-id (:id obj))
|
||||||
|
(update :unames conj (:name obj))
|
||||||
(update :parent-stack conj (:id obj)))))
|
(update :parent-stack conj (:id obj)))))
|
||||||
|
|
||||||
(defn close-artboard [file]
|
(defn close-artboard [file]
|
||||||
|
@ -102,9 +141,13 @@
|
||||||
selrect init/empty-selrect
|
selrect init/empty-selrect
|
||||||
name (:name data)
|
name (:name data)
|
||||||
obj (-> (init/make-minimal-group frame-id selrect name)
|
obj (-> (init/make-minimal-group frame-id selrect name)
|
||||||
(merge data))]
|
(merge data)
|
||||||
|
(check-name file :group)
|
||||||
|
(d/without-nils))]
|
||||||
(-> file
|
(-> file
|
||||||
(commit-shape obj)
|
(commit-shape obj)
|
||||||
|
(assoc :last-id (:id obj))
|
||||||
|
(update :unames conj (:name obj))
|
||||||
(update :parent-stack conj (:id obj)))))
|
(update :parent-stack conj (:id obj)))))
|
||||||
|
|
||||||
(defn close-group [file]
|
(defn close-group [file]
|
||||||
|
@ -115,13 +158,14 @@
|
||||||
points (gsh/rect->points selrect)]
|
points (gsh/rect->points selrect)]
|
||||||
|
|
||||||
(-> file
|
(-> file
|
||||||
(commit-change
|
(cond-> (not (empty? shapes))
|
||||||
{:type :mod-obj
|
(commit-change
|
||||||
:page-id (:current-page-id file)
|
{:type :mod-obj
|
||||||
:id group-id
|
:page-id (:current-page-id file)
|
||||||
:operations
|
:id group-id
|
||||||
[{:type :set :attr :selrect :val selrect}
|
:operations
|
||||||
{:type :set :attr :points :val points}]})
|
[{:type :set :attr :selrect :val selrect}
|
||||||
|
{:type :set :attr :points :val points}]}))
|
||||||
(update :parent-stack pop))))
|
(update :parent-stack pop))))
|
||||||
|
|
||||||
(defn create-shape [file type data]
|
(defn create-shape [file type data]
|
||||||
|
@ -130,10 +174,14 @@
|
||||||
(lookup-shape file frame-id))
|
(lookup-shape file frame-id))
|
||||||
obj (-> (init/make-minimal-shape type)
|
obj (-> (init/make-minimal-shape type)
|
||||||
(merge data)
|
(merge data)
|
||||||
(d/without-nils)
|
(check-name file :type)
|
||||||
(cond-> frame
|
(d/without-nils))
|
||||||
(gsh/translate-from-frame frame)))]
|
obj (cond-> obj
|
||||||
(commit-shape file obj)))
|
frame (gsh/translate-from-frame frame))]
|
||||||
|
(-> file
|
||||||
|
(commit-shape obj)
|
||||||
|
(assoc :last-id (:id obj))
|
||||||
|
(update :unames conj (:name obj)))))
|
||||||
|
|
||||||
(defn create-rect [file data]
|
(defn create-rect [file data]
|
||||||
(create-shape file :rect data))
|
(create-shape file :rect data))
|
||||||
|
@ -150,10 +198,27 @@
|
||||||
(defn create-image [file data]
|
(defn create-image [file data]
|
||||||
(create-shape file :image data))
|
(create-shape file :image data))
|
||||||
|
|
||||||
(defn close-page [file]
|
(declare close-svg-raw)
|
||||||
|
|
||||||
|
(defn create-svg-raw [file data]
|
||||||
|
(let [file (as-> file $
|
||||||
|
(create-shape $ :svg-raw data)
|
||||||
|
(update $ :parent-stack conj (:last-id $)))
|
||||||
|
|
||||||
|
create-child
|
||||||
|
(fn [file child]
|
||||||
|
(-> file
|
||||||
|
(create-svg-raw (assoc data :content child))
|
||||||
|
(close-svg-raw)))]
|
||||||
|
|
||||||
|
;; First :content is the the shape attribute, the other content is the
|
||||||
|
;; XML children
|
||||||
|
(reduce create-child file (get-in data [:content :content]))))
|
||||||
|
|
||||||
|
(defn close-svg-raw [file]
|
||||||
(-> file
|
(-> file
|
||||||
(dissoc :current-page-id)
|
(update :parent-stack pop)))
|
||||||
(dissoc :parent-stack)))
|
|
||||||
|
|
||||||
(defn generate-changes
|
(defn generate-changes
|
||||||
[file]
|
[file]
|
||||||
|
|
|
@ -85,9 +85,15 @@
|
||||||
(mf/fnc svg-raw-wrapper
|
(mf/fnc svg-raw-wrapper
|
||||||
[{:keys [shape frame] :as props}]
|
[{:keys [shape frame] :as props}]
|
||||||
(let [childs (mapv #(get objects %) (:shapes shape))]
|
(let [childs (mapv #(get objects %) (:shapes shape))]
|
||||||
[:& svg-raw-shape {:frame frame
|
(if (and (contains? shape :svg-attrs) (map? (:content shape)))
|
||||||
:shape shape
|
[:> shape-container {:shape shape}
|
||||||
:childs childs}]))))
|
[:& svg-raw-shape {:frame frame
|
||||||
|
:shape shape
|
||||||
|
:childs childs}]]
|
||||||
|
|
||||||
|
[:& svg-raw-shape {:frame frame
|
||||||
|
:shape shape
|
||||||
|
:childs childs}])))))
|
||||||
|
|
||||||
(defn shape-wrapper-factory
|
(defn shape-wrapper-factory
|
||||||
[objects]
|
[objects]
|
||||||
|
|
|
@ -10,8 +10,25 @@
|
||||||
[app.common.geom.matrix :as gmt]
|
[app.common.geom.matrix :as gmt]
|
||||||
[app.util.json :as json]
|
[app.util.json :as json]
|
||||||
[app.util.object :as obj]
|
[app.util.object :as obj]
|
||||||
|
[app.util.svg :as usvg]
|
||||||
|
[cuerdas.core :as str]
|
||||||
[rumext.alpha :as mf]))
|
[rumext.alpha :as mf]))
|
||||||
|
|
||||||
|
(mf/defc render-xml
|
||||||
|
[{{:keys [tag attrs content] :as node} :xml}]
|
||||||
|
|
||||||
|
(cond
|
||||||
|
(map? node)
|
||||||
|
[:> (d/name tag) (clj->js (usvg/clean-attrs attrs))
|
||||||
|
(for [child content]
|
||||||
|
[:& render-xml {:xml child}])]
|
||||||
|
|
||||||
|
(string? node)
|
||||||
|
node
|
||||||
|
|
||||||
|
:else
|
||||||
|
nil))
|
||||||
|
|
||||||
(defn add-data
|
(defn add-data
|
||||||
"Adds as metadata properties that we cannot deduce from the exported SVG"
|
"Adds as metadata properties that we cannot deduce from the exported SVG"
|
||||||
[props shape]
|
[props shape]
|
||||||
|
@ -22,7 +39,7 @@
|
||||||
(obj/set! ns-attr val))))
|
(obj/set! ns-attr val))))
|
||||||
frame? (= :frame (:type shape))
|
frame? (= :frame (:type shape))
|
||||||
group? (= :group (:type shape))
|
group? (= :group (:type shape))
|
||||||
rect? (= :text (:type shape))
|
rect? (= :rect (:type shape))
|
||||||
text? (= :text (:type shape))
|
text? (= :text (:type shape))
|
||||||
mask? (and group? (:masked-group? shape))]
|
mask? (and group? (:masked-group? shape))]
|
||||||
(-> props
|
(-> props
|
||||||
|
@ -74,5 +91,33 @@
|
||||||
(for [{:keys [scale suffix type]} (:exports shape)]
|
(for [{:keys [scale suffix type]} (:exports shape)]
|
||||||
[:> "penpot:export" #js {:penpot:type (d/name type)
|
[:> "penpot:export" #js {:penpot:type (d/name type)
|
||||||
:penpot:suffix suffix
|
:penpot:suffix suffix
|
||||||
:penpot:scale (str scale)}])]))
|
:penpot:scale (str scale)}])
|
||||||
|
|
||||||
|
(when (contains? shape :svg-attrs)
|
||||||
|
(let [svg-transform (get shape :svg-transform)
|
||||||
|
svg-attrs (->> shape :svg-attrs keys (mapv d/name) (str/join ",") )
|
||||||
|
svg-defs (->> shape :svg-defs keys (mapv d/name) (str/join ","))]
|
||||||
|
[:> "penpot:svg-import" #js {:penpot:svg-attrs (when-not (empty? svg-attrs) svg-attrs)
|
||||||
|
:penpot:svg-defs (when-not (empty? svg-defs) svg-defs)
|
||||||
|
:penpot:svg-transform (when svg-transform (str svg-transform))
|
||||||
|
:penpot:svg-viewbox-x (get-in shape [:svg-viewbox :x])
|
||||||
|
:penpot:svg-viewbox-y (get-in shape [:svg-viewbox :y])
|
||||||
|
:penpot:svg-viewbox-width (get-in shape [:svg-viewbox :width])
|
||||||
|
:penpot:svg-viewbox-height (get-in shape [:svg-viewbox :height])}
|
||||||
|
(for [[def-id def-xml] (:svg-defs shape)]
|
||||||
|
[:> "penpot:svg-def" #js {:def-id def-id}
|
||||||
|
[:& render-xml {:xml def-xml}]])]))
|
||||||
|
|
||||||
|
(when (= (:type shape) :svg-raw)
|
||||||
|
(let [props (-> (obj/new)
|
||||||
|
(obj/set! "penpot:x" (:x shape))
|
||||||
|
(obj/set! "penpot:y" (:y shape))
|
||||||
|
(obj/set! "penpot:width" (:width shape))
|
||||||
|
(obj/set! "penpot:height" (:height shape))
|
||||||
|
(obj/set! "penpot:tag" (-> (get-in shape [:content :tag]) d/name))
|
||||||
|
(obj/merge! (-> (get-in shape [:content :attrs])
|
||||||
|
(clj->js))))]
|
||||||
|
[:> "penpot:svg-content" props
|
||||||
|
(for [leaf (->> shape :content :content (filter string?))]
|
||||||
|
[:> "penpot:svg-child" {} leaf])]))]))
|
||||||
|
|
||||||
|
|
|
@ -18,12 +18,13 @@
|
||||||
(mf/defc linear-gradient [{:keys [id gradient shape]}]
|
(mf/defc linear-gradient [{:keys [id gradient shape]}]
|
||||||
(let [{:keys [x y width height]} (:selrect shape)
|
(let [{:keys [x y width height]} (:selrect shape)
|
||||||
transform (when (= :path (:type shape)) (gsh/transform-matrix shape nil (gpt/point 0.5 0.5)))]
|
transform (when (= :path (:type shape)) (gsh/transform-matrix shape nil (gpt/point 0.5 0.5)))]
|
||||||
[:linearGradient {:id id
|
[:> :linearGradient #js {:id id
|
||||||
:x1 (:start-x gradient)
|
:x1 (:start-x gradient)
|
||||||
:y1 (:start-y gradient)
|
:y1 (:start-y gradient)
|
||||||
:x2 (:end-x gradient)
|
:x2 (:end-x gradient)
|
||||||
:y2 (:end-y gradient)
|
:y2 (:end-y gradient)
|
||||||
:gradientTransform transform}
|
:gradientTransform transform
|
||||||
|
:penpot:gradient "true"}
|
||||||
(for [{:keys [offset color opacity]} (:stops gradient)]
|
(for [{:keys [offset color opacity]} (:stops gradient)]
|
||||||
[:stop {:key (str id "-stop-" offset)
|
[:stop {:key (str id "-stop-" offset)
|
||||||
:offset (or offset 0)
|
:offset (or offset 0)
|
||||||
|
@ -32,6 +33,7 @@
|
||||||
|
|
||||||
(defn add-metadata [props gradient]
|
(defn add-metadata [props gradient]
|
||||||
(-> props
|
(-> props
|
||||||
|
(obj/set! "penpot:gradient" "true")
|
||||||
(obj/set! "penpot:start-x" (:start-x gradient))
|
(obj/set! "penpot:start-x" (:start-x gradient))
|
||||||
(obj/set! "penpot:start-x" (:start-x gradient))
|
(obj/set! "penpot:start-x" (:start-x gradient))
|
||||||
(obj/set! "penpot:start-y" (:start-y gradient))
|
(obj/set! "penpot:start-y" (:start-y gradient))
|
||||||
|
|
|
@ -35,23 +35,13 @@
|
||||||
|
|
||||||
def-ctx? (mf/use-ctx muc/def-ctx)]
|
def-ctx? (mf/use-ctx muc/def-ctx)]
|
||||||
|
|
||||||
(cond
|
(if (or (= (get-in shape [:content :tag]) :svg)
|
||||||
(and (svg-raw/graphic-element? tag) (not def-ctx?))
|
(and (contains? shape :svg-attrs) (map? (:content shape))))
|
||||||
[:> shape-container { :shape shape }
|
[:> shape-container {:shape shape}
|
||||||
[:& svg-raw-shape
|
|
||||||
{:frame frame
|
|
||||||
:shape shape
|
|
||||||
:childs childs}]]
|
|
||||||
|
|
||||||
;; 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
|
|
||||||
(= tag :defs)
|
|
||||||
[:& (mf/provider muc/def-ctx) {:value true}
|
|
||||||
[:& svg-raw-shape {:frame frame
|
[:& svg-raw-shape {:frame frame
|
||||||
:shape shape
|
:shape shape
|
||||||
:childs childs}]]
|
:childs childs}]]
|
||||||
|
|
||||||
:else
|
|
||||||
[:& svg-raw-shape {:frame frame
|
[:& svg-raw-shape {:frame frame
|
||||||
:shape shape
|
:shape shape
|
||||||
:childs childs}])))))
|
:childs childs}])))))
|
||||||
|
|
|
@ -29,8 +29,13 @@
|
||||||
(and (vector? node)
|
(and (vector? node)
|
||||||
(= ::close (first node))))
|
(= ::close (first node))))
|
||||||
|
|
||||||
(defn get-data [node]
|
(defn get-data
|
||||||
(->> node :content (d/seek #(= :penpot:shape (:tag %)))))
|
([node]
|
||||||
|
(->> node :content (d/seek #(= :penpot:shape (:tag %)))))
|
||||||
|
([node tag]
|
||||||
|
(->> (get-data node)
|
||||||
|
:content
|
||||||
|
(d/seek #(= tag (:tag %))))))
|
||||||
|
|
||||||
(defn get-type
|
(defn get-type
|
||||||
[node]
|
[node]
|
||||||
|
@ -65,9 +70,6 @@
|
||||||
[content]
|
[content]
|
||||||
(->> content (tree-seq branch? get-children)))
|
(->> content (tree-seq branch? get-children)))
|
||||||
|
|
||||||
(defn get-transform
|
|
||||||
[type node])
|
|
||||||
|
|
||||||
(defn parse-style
|
(defn parse-style
|
||||||
"Transform style list into a map"
|
"Transform style list into a map"
|
||||||
[style-str]
|
[style-str]
|
||||||
|
@ -97,13 +99,19 @@
|
||||||
[type node]
|
[type node]
|
||||||
|
|
||||||
(let [node-attrs (add-attrs {} (:attrs node))]
|
(let [node-attrs (add-attrs {} (:attrs node))]
|
||||||
(if (search-data-node? type)
|
(cond
|
||||||
|
(search-data-node? type)
|
||||||
(let [data-tags #{:ellipse :rect :path :text :foreignObject :image}]
|
(let [data-tags #{:ellipse :rect :path :text :foreignObject :image}]
|
||||||
(->> node
|
(->> node
|
||||||
(node-seq)
|
(node-seq)
|
||||||
(filter #(contains? data-tags (:tag %)))
|
(filter #(contains? data-tags (:tag %)))
|
||||||
(map #(:attrs %))
|
(map #(:attrs %))
|
||||||
(reduce add-attrs node-attrs)))
|
(reduce add-attrs node-attrs)))
|
||||||
|
|
||||||
|
(= type :svg-raw)
|
||||||
|
(->> node :content last)
|
||||||
|
|
||||||
|
:else
|
||||||
node-attrs)))
|
node-attrs)))
|
||||||
|
|
||||||
(def has-position? #{:frame :rect :image :text})
|
(def has-position? #{:frame :rect :image :text})
|
||||||
|
@ -153,26 +161,42 @@
|
||||||
|
|
||||||
(defn parse-gradient
|
(defn parse-gradient
|
||||||
[node ref-url]
|
[node ref-url]
|
||||||
(let [[_ url] (re-matches url-regex ref-url)
|
(let [[_ url] (re-find url-regex ref-url)
|
||||||
gradient-node (->> node (node-seq) (seek-node url))
|
gradient-node (->> node (node-seq) (seek-node url))
|
||||||
stops (parse-stops gradient-node)]
|
stops (parse-stops gradient-node)]
|
||||||
|
|
||||||
(cond-> {:stops stops}
|
(when (contains? (:attrs gradient-node) :penpot:gradient)
|
||||||
(= :linearGradient (:tag gradient-node))
|
(cond-> {:stops stops}
|
||||||
(assoc :type :linear
|
(= :linearGradient (:tag gradient-node))
|
||||||
:start-x (-> gradient-node :attrs :x1 d/parse-double)
|
(assoc :type :linear
|
||||||
:start-y (-> gradient-node :attrs :y1 d/parse-double)
|
:start-x (-> gradient-node :attrs :x1 d/parse-double)
|
||||||
:end-x (-> gradient-node :attrs :x2 d/parse-double)
|
:start-y (-> gradient-node :attrs :y1 d/parse-double)
|
||||||
:end-y (-> gradient-node :attrs :y2 d/parse-double)
|
:end-x (-> gradient-node :attrs :x2 d/parse-double)
|
||||||
:width 1)
|
:end-y (-> gradient-node :attrs :y2 d/parse-double)
|
||||||
|
:width 1)
|
||||||
|
|
||||||
(= :radialGradient (:tag gradient-node))
|
(= :radialGradient (:tag gradient-node))
|
||||||
(assoc :type :radial
|
(assoc :type :radial
|
||||||
:start-x (get-meta gradient-node :start-x d/parse-double)
|
:start-x (get-meta gradient-node :start-x d/parse-double)
|
||||||
:start-y (get-meta gradient-node :start-y d/parse-double)
|
:start-y (get-meta gradient-node :start-y d/parse-double)
|
||||||
:end-x (get-meta gradient-node :end-x d/parse-double)
|
:end-x (get-meta gradient-node :end-x d/parse-double)
|
||||||
:end-y (get-meta gradient-node :end-y d/parse-double)
|
:end-y (get-meta gradient-node :end-y d/parse-double)
|
||||||
:width (get-meta gradient-node :width d/parse-double)))))
|
:width (get-meta gradient-node :width d/parse-double))))))
|
||||||
|
|
||||||
|
(defn add-svg-position [props node]
|
||||||
|
(let [svg-content (get-data node :penpot:svg-content)]
|
||||||
|
(cond-> props
|
||||||
|
(contains? (:attrs svg-content) :penpot:x)
|
||||||
|
(assoc :x (-> svg-content :attrs :penpot:x d/parse-double))
|
||||||
|
|
||||||
|
(contains? (:attrs svg-content) :penpot:y)
|
||||||
|
(assoc :y (-> svg-content :attrs :penpot:y d/parse-double))
|
||||||
|
|
||||||
|
(contains? (:attrs svg-content) :penpot:width)
|
||||||
|
(assoc :width (-> svg-content :attrs :penpot:width d/parse-double))
|
||||||
|
|
||||||
|
(contains? (:attrs svg-content) :penpot:height)
|
||||||
|
(assoc :height (-> svg-content :attrs :penpot:height d/parse-double)))))
|
||||||
|
|
||||||
(defn add-position
|
(defn add-position
|
||||||
[props type node svg-data]
|
[props type node svg-data]
|
||||||
|
@ -181,6 +205,10 @@
|
||||||
(-> (parse-position svg-data)
|
(-> (parse-position svg-data)
|
||||||
(gsh/setup-selrect))
|
(gsh/setup-selrect))
|
||||||
|
|
||||||
|
(= type :svg-raw)
|
||||||
|
(-> (add-svg-position node)
|
||||||
|
(gsh/setup-selrect))
|
||||||
|
|
||||||
(= type :circle)
|
(= type :circle)
|
||||||
(-> (parse-circle svg-data)
|
(-> (parse-circle svg-data)
|
||||||
(gsh/setup-selrect))
|
(gsh/setup-selrect))
|
||||||
|
@ -191,14 +219,16 @@
|
||||||
(defn add-fill
|
(defn add-fill
|
||||||
[props node svg-data]
|
[props node svg-data]
|
||||||
|
|
||||||
(let [fill (:fill svg-data)]
|
(let [fill (:fill svg-data)
|
||||||
|
gradient (when (str/starts-with? fill "url")
|
||||||
|
(parse-gradient node fill))]
|
||||||
(cond-> props
|
(cond-> props
|
||||||
(= fill "none")
|
:always
|
||||||
(assoc :fill-color nil
|
(assoc :fill-color nil
|
||||||
:fill-opacity nil)
|
:fill-opacity nil)
|
||||||
|
|
||||||
(str/starts-with? fill "url")
|
(some? gradient)
|
||||||
(assoc :fill-color-gradient (parse-gradient node fill)
|
(assoc :fill-color-gradient gradient
|
||||||
:fill-color nil
|
:fill-color nil
|
||||||
:fill-opacity nil)
|
:fill-opacity nil)
|
||||||
|
|
||||||
|
@ -211,24 +241,44 @@
|
||||||
|
|
||||||
(let [stroke-style (get-meta node :stroke-style keyword)
|
(let [stroke-style (get-meta node :stroke-style keyword)
|
||||||
stroke-alignment (get-meta node :stroke-alignment keyword)
|
stroke-alignment (get-meta node :stroke-alignment keyword)
|
||||||
stroke (:stroke svg-data)]
|
stroke (:stroke svg-data)
|
||||||
|
gradient (when (str/starts-with? stroke "url")
|
||||||
|
(parse-gradient node stroke))]
|
||||||
|
|
||||||
(cond-> props
|
(cond-> props
|
||||||
:always
|
:always
|
||||||
(assoc :stroke-alignment stroke-alignment
|
(assoc :stroke-alignment stroke-alignment
|
||||||
:stroke-style stroke-style
|
:stroke-style stroke-style
|
||||||
:stroke-color (-> svg-data (:stroke "#000000"))
|
:stroke-color (-> svg-data :stroke)
|
||||||
:stroke-opacity (-> svg-data (:stroke-opacity "1") d/parse-double)
|
:stroke-opacity (-> svg-data :stroke-opacity d/parse-double)
|
||||||
:stroke-width (-> svg-data (:stroke-width "0") d/parse-double))
|
:stroke-width (-> svg-data :stroke-width d/parse-double))
|
||||||
|
|
||||||
(str/starts-with? stroke "url")
|
(some? gradient)
|
||||||
(assoc :stroke-color-gradient (parse-gradient node stroke)
|
(assoc :stroke-color-gradient gradient
|
||||||
:stroke-color nil
|
:stroke-color nil
|
||||||
:stroke-opacity nil)
|
:stroke-opacity nil)
|
||||||
|
|
||||||
(= stroke-alignment :inner)
|
(= stroke-alignment :inner)
|
||||||
(update :stroke-width / 2))))
|
(update :stroke-width / 2))))
|
||||||
|
|
||||||
|
(defn add-rect-data
|
||||||
|
[props node svg-data]
|
||||||
|
(let [r1 (get-meta node :r1 d/parse-double)
|
||||||
|
r2 (get-meta node :r2 d/parse-double)
|
||||||
|
r3 (get-meta node :r3 d/parse-double)
|
||||||
|
r4 (get-meta node :r4 d/parse-double)
|
||||||
|
|
||||||
|
rx (-> (get svg-data :rx) d/parse-double)
|
||||||
|
ry (-> (get svg-data :ry) d/parse-double)]
|
||||||
|
|
||||||
|
(cond-> props
|
||||||
|
(some? r1)
|
||||||
|
(assoc :r1 r1 :r2 r2 :r3 r3 :r4 r4
|
||||||
|
:rx nil :ry nil)
|
||||||
|
|
||||||
|
(and (nil? r1) (some? rx))
|
||||||
|
(assoc :rx rx :ry ry))))
|
||||||
|
|
||||||
(defn add-image-data
|
(defn add-image-data
|
||||||
[props node]
|
[props node]
|
||||||
(-> props
|
(-> props
|
||||||
|
@ -276,12 +326,16 @@
|
||||||
:suffix (get-meta node :suffix)
|
:suffix (get-meta node :suffix)
|
||||||
:scale (get-meta node :scale d/parse-double)})
|
:scale (get-meta node :scale d/parse-double)})
|
||||||
|
|
||||||
(defn extract-from-data [node tag parse-fn]
|
(defn extract-from-data
|
||||||
(let [shape-data (get-data node)]
|
([node tag]
|
||||||
(->> shape-data
|
(extract-from-data node tag identity))
|
||||||
(node-seq)
|
|
||||||
(filter #(= (:tag %) tag))
|
([node tag parse-fn]
|
||||||
(mapv parse-fn))))
|
(let [shape-data (get-data node)]
|
||||||
|
(->> shape-data
|
||||||
|
(node-seq)
|
||||||
|
(filter #(= (:tag %) tag))
|
||||||
|
(mapv parse-fn)))))
|
||||||
|
|
||||||
(defn add-shadows
|
(defn add-shadows
|
||||||
[props node]
|
[props node]
|
||||||
|
@ -313,8 +367,111 @@
|
||||||
(some? blend-mode)
|
(some? blend-mode)
|
||||||
(assoc :blend-mode (keyword blend-mode))
|
(assoc :blend-mode (keyword blend-mode))
|
||||||
|
|
||||||
(some? opacity)
|
(some? opacity)
|
||||||
(assoc :opacity opacity))))
|
(assoc :opacity opacity))))
|
||||||
|
|
||||||
|
(defn remove-prefix [s]
|
||||||
|
(cond-> s
|
||||||
|
(string? s)
|
||||||
|
(str/replace #"\w{8}-\w{4}-\w{4}-\w{4}-\w{12}-" "")))
|
||||||
|
|
||||||
|
(defn get-svg-attrs
|
||||||
|
[svg-data svg-attrs]
|
||||||
|
(let [assoc-key
|
||||||
|
(fn [acc prop]
|
||||||
|
(let [key (keyword prop)]
|
||||||
|
(if-let [v (or (get svg-data key)
|
||||||
|
(get-in svg-data [:attrs key]))]
|
||||||
|
(assoc acc key (remove-prefix v))
|
||||||
|
acc)))]
|
||||||
|
|
||||||
|
(->> (str/split svg-attrs ",")
|
||||||
|
(reduce assoc-key {}))))
|
||||||
|
|
||||||
|
(defn get-svg-defs
|
||||||
|
[node svg-defs]
|
||||||
|
|
||||||
|
(let [svg-import (get-data node :penpot:svg-import)]
|
||||||
|
(->> svg-import
|
||||||
|
:content
|
||||||
|
(filter #(= (:tag %) :penpot:svg-def))
|
||||||
|
(map #(vector (-> % :attrs :def-id)
|
||||||
|
(-> % :content first)))
|
||||||
|
(into {}))))
|
||||||
|
|
||||||
|
(defn add-svg-attrs
|
||||||
|
[props node svg-data]
|
||||||
|
|
||||||
|
(let [svg-import (get-data node :penpot:svg-import)]
|
||||||
|
(if (some? svg-import)
|
||||||
|
(let [svg-attrs (get-in svg-import [:attrs :penpot:svg-attrs])
|
||||||
|
svg-defs (get-in svg-import [:attrs :penpot:svg-defs])
|
||||||
|
svg-transform (get-in svg-import [:attrs :penpot:svg-transform])
|
||||||
|
viewbox-x (get-in svg-import [:attrs :penpot:svg-viewbox-x])
|
||||||
|
viewbox-y (get-in svg-import [:attrs :penpot:svg-viewbox-y])
|
||||||
|
viewbox-width (get-in svg-import [:attrs :penpot:svg-viewbox-width])
|
||||||
|
viewbox-height (get-in svg-import [:attrs :penpot:svg-viewbox-height])]
|
||||||
|
|
||||||
|
(cond-> props
|
||||||
|
:true
|
||||||
|
(assoc :svg-attrs (get-svg-attrs svg-data svg-attrs))
|
||||||
|
|
||||||
|
(some? viewbox-x)
|
||||||
|
(assoc :svg-viewbox {:x (d/parse-double viewbox-x)
|
||||||
|
:y (d/parse-double viewbox-y)
|
||||||
|
:width (d/parse-double viewbox-width)
|
||||||
|
:height (d/parse-double viewbox-height)})
|
||||||
|
|
||||||
|
(some? svg-transform)
|
||||||
|
(assoc :svg-transform (gmt/str->matrix svg-transform))
|
||||||
|
|
||||||
|
|
||||||
|
(some? svg-defs)
|
||||||
|
(assoc :svg-defs (get-svg-defs node svg-defs))))
|
||||||
|
|
||||||
|
props)))
|
||||||
|
|
||||||
|
(defn without-penpot-prefix
|
||||||
|
[m]
|
||||||
|
(let [no-penpot-prefix?
|
||||||
|
(fn [[k v]]
|
||||||
|
(not (str/starts-with? (d/name k) "penpot:")))]
|
||||||
|
(into {} (filter no-penpot-prefix?) m)))
|
||||||
|
|
||||||
|
(defn camelize [[k v]]
|
||||||
|
[(-> k d/name str/camel keyword) v])
|
||||||
|
|
||||||
|
(defn camelize-keys
|
||||||
|
[m]
|
||||||
|
(assert (map? m) (str m))
|
||||||
|
|
||||||
|
(into {} (map camelize) m))
|
||||||
|
|
||||||
|
(defn fix-style-attr
|
||||||
|
[m]
|
||||||
|
(let [fix-style
|
||||||
|
(fn [[k v]]
|
||||||
|
(if (= k :style)
|
||||||
|
[k (-> v parse-style camelize-keys)]
|
||||||
|
[k v]))]
|
||||||
|
|
||||||
|
(d/deep-mapm (comp camelize fix-style) m)))
|
||||||
|
|
||||||
|
(defn add-svg-content
|
||||||
|
[props node]
|
||||||
|
(let [svg-content (get-data node :penpot:svg-content)
|
||||||
|
attrs (-> (:attrs svg-content) (without-penpot-prefix))
|
||||||
|
tag (-> svg-content :attrs :penpot:tag keyword)
|
||||||
|
content {:attrs attrs
|
||||||
|
:tag tag
|
||||||
|
:content (cond
|
||||||
|
(= tag :svg)
|
||||||
|
(->> node :content last :content last :content fix-style-attr)
|
||||||
|
|
||||||
|
(= tag :text)
|
||||||
|
(-> node :content last :content))}]
|
||||||
|
(-> props
|
||||||
|
(assoc :content content))))
|
||||||
|
|
||||||
(defn get-image-name
|
(defn get-image-name
|
||||||
[node]
|
[node]
|
||||||
|
@ -337,6 +494,9 @@
|
||||||
svg-data (get-svg-data type node)]
|
svg-data (get-svg-data type node)]
|
||||||
|
|
||||||
(-> {}
|
(-> {}
|
||||||
|
(assoc :name name)
|
||||||
|
(assoc :blocked blocked)
|
||||||
|
(assoc :hidden hidden)
|
||||||
(add-position type node svg-data)
|
(add-position type node svg-data)
|
||||||
(add-fill node svg-data)
|
(add-fill node svg-data)
|
||||||
(add-stroke node svg-data)
|
(add-stroke node svg-data)
|
||||||
|
@ -344,13 +504,17 @@
|
||||||
(add-shadows node)
|
(add-shadows node)
|
||||||
(add-blur node)
|
(add-blur node)
|
||||||
(add-exports node)
|
(add-exports node)
|
||||||
(assoc :name name)
|
(add-svg-attrs node svg-data)
|
||||||
(assoc :blocked blocked)
|
|
||||||
(assoc :hidden hidden)
|
(cond-> (= :svg-raw type)
|
||||||
|
(add-svg-content node))
|
||||||
|
|
||||||
(cond-> (= :group type)
|
(cond-> (= :group type)
|
||||||
(add-group-data node))
|
(add-group-data node))
|
||||||
|
|
||||||
|
(cond-> (= :rect type)
|
||||||
|
(add-rect-data node svg-data))
|
||||||
|
|
||||||
(cond-> (= :image type)
|
(cond-> (= :image type)
|
||||||
(add-image-data node))
|
(add-image-data node))
|
||||||
|
|
||||||
|
|
|
@ -48,17 +48,16 @@
|
||||||
(rx/concat
|
(rx/concat
|
||||||
(->> (rx/from changes-batches)
|
(->> (rx/from changes-batches)
|
||||||
(rx/mapcat
|
(rx/mapcat
|
||||||
(fn [cur-changes-batch]
|
#(rp/mutation
|
||||||
(rp/mutation
|
:update-file
|
||||||
:update-file
|
{:id file-id
|
||||||
{:id file-id
|
:session-id session-id
|
||||||
:session-id session-id
|
:revn @revn
|
||||||
:revn @revn
|
:changes %}))
|
||||||
:changes cur-changes-batch})))
|
(rx/map first)
|
||||||
|
|
||||||
(rx/tap #(reset! revn (:revn %))))
|
(rx/tap #(reset! revn (:revn %))))
|
||||||
|
|
||||||
(rp/mutation :make-permanent {:id (:id file)}))))
|
(rp/mutation :persist-temp-file {:id (:id file)}))))
|
||||||
|
|
||||||
(defn upload-media-files
|
(defn upload-media-files
|
||||||
"Upload a image to the backend and returns its id"
|
"Upload a image to the backend and returns its id"
|
||||||
|
@ -91,6 +90,9 @@
|
||||||
:group
|
:group
|
||||||
(fb/close-group file)
|
(fb/close-group file)
|
||||||
|
|
||||||
|
:svg-raw
|
||||||
|
(fb/close-svg-raw file)
|
||||||
|
|
||||||
;; default
|
;; default
|
||||||
file)
|
file)
|
||||||
|
|
||||||
|
@ -102,6 +104,7 @@
|
||||||
:path (fb/create-path file data)
|
:path (fb/create-path file data)
|
||||||
:text (fb/create-text file data)
|
:text (fb/create-text file data)
|
||||||
:image (fb/create-image file data)
|
:image (fb/create-image file data)
|
||||||
|
:svg-raw (fb/create-svg-raw file data)
|
||||||
|
|
||||||
;; default
|
;; default
|
||||||
file))))
|
file))))
|
||||||
|
@ -127,7 +130,8 @@
|
||||||
(assoc-in [:attrs :penpot:media-mtype] (:mtype media)))))))
|
(assoc-in [:attrs :penpot:media-mtype] (:mtype media)))))))
|
||||||
|
|
||||||
;; If the node is not an image just return the node
|
;; If the node is not an image just return the node
|
||||||
(rx/of node)))
|
(->> (rx/of node)
|
||||||
|
(rx/observe-on :async))))
|
||||||
|
|
||||||
(defn import-page
|
(defn import-page
|
||||||
[file [page-name content]]
|
[file [page-name content]]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue