mirror of
https://github.com/penpot/penpot.git
synced 2025-05-13 14:36:38 +02:00
✨ Add library linking to export/import
This commit is contained in:
parent
484eb3a7c4
commit
4c84b18bb6
7 changed files with 485 additions and 231 deletions
|
@ -48,7 +48,7 @@
|
||||||
:opt-un [::id]))
|
:opt-un [::id]))
|
||||||
|
|
||||||
(sv/defmethod ::upload-file-media-object
|
(sv/defmethod ::upload-file-media-object
|
||||||
[{:keys [pool] :as cfg} {:keys [profile-id file-id id] :as params}]
|
[{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}]
|
||||||
(db/with-atomic [conn pool]
|
(db/with-atomic [conn pool]
|
||||||
(let [file (select-file conn file-id)]
|
(let [file (select-file conn file-id)]
|
||||||
(teams/check-edition-permissions! conn profile-id (:team-id file))
|
(teams/check-edition-permissions! conn profile-id (:team-id file))
|
||||||
|
|
|
@ -547,6 +547,7 @@
|
||||||
(cond
|
(cond
|
||||||
(or (vector? v) (map? v))
|
(or (vector? v) (map? v))
|
||||||
[k (deep-mapm mfn v)]
|
[k (deep-mapm mfn v)]
|
||||||
|
|
||||||
:else
|
:else
|
||||||
(mfn [k v]))))]
|
(mfn [k v]))))]
|
||||||
(cond
|
(cond
|
||||||
|
@ -567,4 +568,6 @@
|
||||||
(->> m
|
(->> m
|
||||||
(deep-mapm
|
(deep-mapm
|
||||||
(fn [[k v]]
|
(fn [[k v]]
|
||||||
[(keyword (str/kebab (name k))) v]))))
|
(if (or (keyword? k) (string? k))
|
||||||
|
[(keyword (str/kebab (name k))) v]
|
||||||
|
[k v])))))
|
||||||
|
|
|
@ -150,20 +150,22 @@
|
||||||
|
|
||||||
(defn create-file
|
(defn create-file
|
||||||
([name]
|
([name]
|
||||||
(let [id (uuid/next)]
|
(create-file (uuid/next) name))
|
||||||
|
|
||||||
|
([id name]
|
||||||
{:id id
|
{:id id
|
||||||
:name name
|
:name name
|
||||||
:data (-> init/empty-file-data
|
:data (-> init/empty-file-data
|
||||||
(assoc :id id))
|
(assoc :id id))
|
||||||
|
|
||||||
;; We keep the changes so we can send them to the backend
|
;; We keep the changes so we can send them to the backend
|
||||||
:changes []})))
|
:changes []}))
|
||||||
|
|
||||||
(defn add-page
|
(defn add-page
|
||||||
[file data]
|
[file data]
|
||||||
|
|
||||||
(assert (nil? (:current-component-id file)))
|
(assert (nil? (:current-component-id file)))
|
||||||
(let [page-id (uuid/next)
|
(let [page-id (or (:id data) (uuid/next))
|
||||||
page (-> init/empty-page-data
|
page (-> init/empty-page-data
|
||||||
(assoc :id page-id)
|
(assoc :id page-id)
|
||||||
(d/deep-merge data))]
|
(d/deep-merge data))]
|
||||||
|
@ -329,10 +331,10 @@
|
||||||
(update :parent-stack pop)))
|
(update :parent-stack pop)))
|
||||||
|
|
||||||
(defn add-interaction
|
(defn add-interaction
|
||||||
[file action-type event-type from-id destination-id]
|
[file from-id {:keys [action-type event-type destination]}]
|
||||||
|
|
||||||
(assert (some? (lookup-shape file from-id)) (str "Cannot locate shape with id " from-id))
|
(assert (some? (lookup-shape file from-id)) (str "Cannot locate shape with id " from-id))
|
||||||
(assert (some? (lookup-shape file destination-id)) (str "Cannot locate shape with id " destination-id))
|
(assert (some? (lookup-shape file destination)) (str "Cannot locate shape with id " destination))
|
||||||
|
|
||||||
(let [interactions (->> (lookup-shape file from-id)
|
(let [interactions (->> (lookup-shape file from-id)
|
||||||
:interactions
|
:interactions
|
||||||
|
@ -342,7 +344,7 @@
|
||||||
(conjv
|
(conjv
|
||||||
{:action-type action-type
|
{:action-type action-type
|
||||||
:event-type event-type
|
:event-type event-type
|
||||||
:destination destination-id}))]
|
:destination destination}))]
|
||||||
(commit-change
|
(commit-change
|
||||||
file
|
file
|
||||||
{:type :mod-obj
|
{:type :mod-obj
|
||||||
|
|
|
@ -29,13 +29,19 @@
|
||||||
:else
|
:else
|
||||||
nil))
|
nil))
|
||||||
|
|
||||||
|
(defn uuid->string [m]
|
||||||
|
(->> m
|
||||||
|
(d/deep-mapm
|
||||||
|
(fn [[k v]]
|
||||||
|
(if (uuid? v)
|
||||||
|
[k (str v)]
|
||||||
|
[k v])))))
|
||||||
|
|
||||||
(defn bool->str [val]
|
(defn bool->str [val]
|
||||||
(when (some? val) (str val)))
|
(when (some? val) (str val)))
|
||||||
|
|
||||||
(defn add-data
|
(defn add-factory [shape]
|
||||||
"Adds as metadata properties that we cannot deduce from the exported SVG"
|
(fn add!
|
||||||
[props shape]
|
|
||||||
(letfn [(add!
|
|
||||||
([props attr]
|
([props attr]
|
||||||
(add! props attr str))
|
(add! props attr str))
|
||||||
|
|
||||||
|
@ -45,8 +51,13 @@
|
||||||
ns-attr (str "penpot:" (-> attr d/name))]
|
ns-attr (str "penpot:" (-> attr d/name))]
|
||||||
(cond-> props
|
(cond-> props
|
||||||
(some? val)
|
(some? val)
|
||||||
(obj/set! ns-attr (trfn val))))))]
|
(obj/set! ns-attr (trfn val)))))))
|
||||||
(let [group? (= :group (:type shape))
|
|
||||||
|
(defn add-data
|
||||||
|
"Adds as metadata properties that we cannot deduce from the exported SVG"
|
||||||
|
[props shape]
|
||||||
|
(let [add! (add-factory shape)
|
||||||
|
group? (= :group (:type shape))
|
||||||
rect? (= :rect (: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))
|
||||||
|
@ -81,10 +92,25 @@
|
||||||
|
|
||||||
(cond-> text?
|
(cond-> text?
|
||||||
(-> (add! :grow-type)
|
(-> (add! :grow-type)
|
||||||
(add! :content json/encode)))
|
(add! :content (comp json/encode uuid->string))))
|
||||||
|
|
||||||
(cond-> mask?
|
(cond-> mask?
|
||||||
(obj/set! "penpot:masked-group" "true"))))))
|
(obj/set! "penpot:masked-group" "true")))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn add-library-refs [props shape]
|
||||||
|
(let [add! (add-factory shape)]
|
||||||
|
(-> props
|
||||||
|
(add! :fill-color-ref-id)
|
||||||
|
(add! :fill-color-ref-file)
|
||||||
|
(add! :stroke-color-ref-id)
|
||||||
|
(add! :stroke-color-ref-file)
|
||||||
|
(add! :typography-ref-id)
|
||||||
|
(add! :typography-ref-file)
|
||||||
|
(add! :component-file)
|
||||||
|
(add! :component-id)
|
||||||
|
(add! :component-root)
|
||||||
|
(add! :shape-ref))))
|
||||||
|
|
||||||
(defn prefix-keys [m]
|
(defn prefix-keys [m]
|
||||||
(letfn [(prefix-entry [[k v]]
|
(letfn [(prefix-entry [[k v]]
|
||||||
|
@ -191,7 +217,7 @@
|
||||||
|
|
||||||
(mf/defc export-data
|
(mf/defc export-data
|
||||||
[{:keys [shape]}]
|
[{:keys [shape]}]
|
||||||
(let [props (-> (obj/new) (add-data shape))]
|
(let [props (-> (obj/new) (add-data shape) (add-library-refs shape))]
|
||||||
[:> "penpot:shape" props
|
[:> "penpot:shape" props
|
||||||
[:& export-shadow-data shape]
|
[:& export-shadow-data shape]
|
||||||
[:& export-blur-data shape]
|
[:& export-blur-data shape]
|
||||||
|
|
|
@ -158,6 +158,27 @@
|
||||||
|
|
||||||
(d/deep-mapm (comp camelize fix-style) m)))
|
(d/deep-mapm (comp camelize fix-style) m)))
|
||||||
|
|
||||||
|
(defn string->uuid
|
||||||
|
"Looks in a map for keys or values that have uuid shape and converts them
|
||||||
|
into uuid objects"
|
||||||
|
[m]
|
||||||
|
(letfn [(convert [value]
|
||||||
|
(cond
|
||||||
|
(and (string? value) (re-matches uuid-regex value))
|
||||||
|
(uuid/uuid value)
|
||||||
|
|
||||||
|
(and (keyword? value) (re-matches uuid-regex (d/name value)))
|
||||||
|
(uuid/uuid (d/name value))
|
||||||
|
|
||||||
|
(vector? value)
|
||||||
|
(mapv convert value)
|
||||||
|
|
||||||
|
:else
|
||||||
|
value))]
|
||||||
|
(->> m
|
||||||
|
(d/deep-mapm
|
||||||
|
(fn [pair] (->> pair (mapv convert)))))))
|
||||||
|
|
||||||
(def search-data-node? #{:rect :image :path :text :circle})
|
(def search-data-node? #{:rect :image :path :text :circle})
|
||||||
|
|
||||||
(defn get-svg-data
|
(defn get-svg-data
|
||||||
|
@ -397,7 +418,7 @@
|
||||||
[props node]
|
[props node]
|
||||||
(-> props
|
(-> props
|
||||||
(assoc :grow-type (get-meta node :grow-type keyword))
|
(assoc :grow-type (get-meta node :grow-type keyword))
|
||||||
(assoc :content (get-meta node :content json/decode))))
|
(assoc :content (get-meta node :content (comp string->uuid json/decode)))))
|
||||||
|
|
||||||
(defn add-group-data
|
(defn add-group-data
|
||||||
[props node]
|
[props node]
|
||||||
|
@ -605,6 +626,37 @@
|
||||||
svg-data (or image-data pattern-data)]
|
svg-data (or image-data pattern-data)]
|
||||||
(:xlink:href svg-data)))
|
(:xlink:href svg-data)))
|
||||||
|
|
||||||
|
(defn add-library-refs
|
||||||
|
[props node]
|
||||||
|
|
||||||
|
(let [fill-color-ref-id (get-meta node :fill-color-ref-id uuid/uuid)
|
||||||
|
fill-color-ref-file (get-meta node :fill-color-ref-file uuid/uuid)
|
||||||
|
stroke-color-ref-id (get-meta node :stroke-color-ref-id uuid/uuid)
|
||||||
|
stroke-color-ref-file (get-meta node :stroke-color-ref-file uuid/uuid)
|
||||||
|
component-id (get-meta node :component-id uuid/uuid)
|
||||||
|
component-file (get-meta node :component-file uuid/uuid)
|
||||||
|
shape-ref (get-meta node :shape-ref uuid/uuid)
|
||||||
|
component-root? (get-meta node :component-root str->bool)]
|
||||||
|
|
||||||
|
(cond-> props
|
||||||
|
(some? fill-color-ref-id)
|
||||||
|
(assoc :fill-color-ref-id fill-color-ref-id
|
||||||
|
:fill-color-ref-file fill-color-ref-file)
|
||||||
|
|
||||||
|
(some? stroke-color-ref-id)
|
||||||
|
(assoc :stroke-color-ref-id stroke-color-ref-id
|
||||||
|
:stroke-color-ref-file stroke-color-ref-file)
|
||||||
|
|
||||||
|
(some? component-id)
|
||||||
|
(assoc :component-id component-id
|
||||||
|
:component-file component-file)
|
||||||
|
|
||||||
|
component-root?
|
||||||
|
(assoc :component-root? component-root?)
|
||||||
|
|
||||||
|
(some? shape-ref)
|
||||||
|
(assoc :shape-ref shape-ref))))
|
||||||
|
|
||||||
(defn parse-data
|
(defn parse-data
|
||||||
[type node]
|
[type node]
|
||||||
|
|
||||||
|
@ -620,6 +672,7 @@
|
||||||
(add-blur node)
|
(add-blur node)
|
||||||
(add-exports node)
|
(add-exports node)
|
||||||
(add-svg-attrs node svg-data)
|
(add-svg-attrs node svg-data)
|
||||||
|
(add-library-refs node)
|
||||||
|
|
||||||
(cond-> (= :svg-raw type)
|
(cond-> (= :svg-raw type)
|
||||||
(add-svg-content node))
|
(add-svg-content node))
|
||||||
|
@ -661,3 +714,4 @@
|
||||||
{:destination (get-meta node :destination uuid/uuid)
|
{:destination (get-meta node :destination uuid/uuid)
|
||||||
:action-type (get-meta node :action-type keyword)
|
:action-type (get-meta node :action-type keyword)
|
||||||
:event-type (get-meta node :event-type keyword)})))))
|
:event-type (get-meta node :event-type keyword)})))))
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,12 @@
|
||||||
[beicon.core :as rx]
|
[beicon.core :as rx]
|
||||||
[cuerdas.core :as str]))
|
[cuerdas.core :as str]))
|
||||||
|
|
||||||
|
(defn rx-expand
|
||||||
|
"Recursively projects each source value to an Observable
|
||||||
|
which is merged in the output Observable."
|
||||||
|
[f ob]
|
||||||
|
(.pipe ob (.expand ^js js/rxjsOperators f)))
|
||||||
|
|
||||||
(defn create-manifest
|
(defn create-manifest
|
||||||
"Creates a manifest entry for the given files"
|
"Creates a manifest entry for the given files"
|
||||||
[team-id file-id files]
|
[team-id file-id files]
|
||||||
|
@ -40,6 +46,7 @@
|
||||||
:shared is-shared
|
:shared is-shared
|
||||||
:pages pages
|
:pages pages
|
||||||
:pagesIndex index
|
:pagesIndex index
|
||||||
|
:libraries (->> (:libraries file) (into #{}) (mapv str))
|
||||||
:hasComponents (d/not-empty? (get-in file [:data :components]))
|
:hasComponents (d/not-empty? (get-in file [:data :components]))
|
||||||
:hasMedia (d/not-empty? (get-in file [:data :media]))
|
:hasMedia (d/not-empty? (get-in file [:data :media]))
|
||||||
:hasColors (d/not-empty? (get-in file [:data :colors]))
|
:hasColors (d/not-empty? (get-in file [:data :colors]))
|
||||||
|
@ -142,15 +149,48 @@
|
||||||
(->> (r/render-components (:data file))
|
(->> (r/render-components (:data file))
|
||||||
(rx/map #(vector (str (:id file) "/components.svg") %))))
|
(rx/map #(vector (str (:id file) "/components.svg") %))))
|
||||||
|
|
||||||
|
(defn fetch-file-with-libraries [file-id]
|
||||||
|
(->> (rx/zip (rp/query :file {:id file-id})
|
||||||
|
(rp/query :file-libraries {:file-id file-id}))
|
||||||
|
(rx/map
|
||||||
|
(fn [[file file-libraries]]
|
||||||
|
(let [libraries-ids (->> file-libraries (map :id) (filterv #(not= (:id file) %)))]
|
||||||
|
(-> file
|
||||||
|
(assoc :libraries libraries-ids)))))))
|
||||||
|
|
||||||
|
(defn collect-files
|
||||||
|
[file-id]
|
||||||
|
|
||||||
|
(letfn [(fetch-dependencies [[files pending]]
|
||||||
|
(if (empty? pending)
|
||||||
|
;; When not pending, we finish the generation
|
||||||
|
(rx/empty)
|
||||||
|
|
||||||
|
;; Still pending files, fetch the next one
|
||||||
|
(let [next (peek pending)
|
||||||
|
pending (pop pending)]
|
||||||
|
(if (contains? files next)
|
||||||
|
;; The file is already in the result
|
||||||
|
(rx/of [files pending])
|
||||||
|
|
||||||
|
(->> (fetch-file-with-libraries next)
|
||||||
|
(rx/map
|
||||||
|
(fn [file]
|
||||||
|
[(-> files
|
||||||
|
(assoc (:id file) file))
|
||||||
|
(as-> pending $
|
||||||
|
(reduce conj $ (:libraries file)))])))))))]
|
||||||
|
(let [files {}
|
||||||
|
pending [file-id]]
|
||||||
|
(->> (rx/of [files pending])
|
||||||
|
(rx-expand fetch-dependencies)
|
||||||
|
(rx/last)
|
||||||
|
(rx/map first)))))
|
||||||
|
|
||||||
(defn export-file
|
(defn export-file
|
||||||
[team-id file-id]
|
[team-id file-id]
|
||||||
|
|
||||||
(let [files-stream
|
(let [files-stream (->> (collect-files file-id)
|
||||||
(->> (rx/merge (rp/query :file {:id file-id})
|
|
||||||
(->> (rp/query :file-libraries {:file-id file-id})
|
|
||||||
(rx/flat-map identity)
|
|
||||||
(rx/map #(assoc % :is-shared true))))
|
|
||||||
(rx/reduce #(assoc %1 (:id %2) %2) {})
|
|
||||||
(rx/share))
|
(rx/share))
|
||||||
|
|
||||||
manifest-stream
|
manifest-stream
|
||||||
|
|
|
@ -5,10 +5,12 @@
|
||||||
;; Copyright (c) UXBOX Labs SL
|
;; Copyright (c) UXBOX Labs SL
|
||||||
|
|
||||||
(ns app.worker.import
|
(ns app.worker.import
|
||||||
|
(:refer-clojure :exclude [resolve])
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.file-builder :as fb]
|
[app.common.file-builder :as fb]
|
||||||
[app.common.pages :as cp]
|
[app.common.pages :as cp]
|
||||||
|
[app.common.text :as ct]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[app.main.repo :as rp]
|
[app.main.repo :as rp]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
|
@ -18,23 +20,110 @@
|
||||||
[app.util.zip :as uz]
|
[app.util.zip :as uz]
|
||||||
[app.worker.impl :as impl]
|
[app.worker.impl :as impl]
|
||||||
[beicon.core :as rx]
|
[beicon.core :as rx]
|
||||||
|
[cuerdas.core :as str]
|
||||||
[tubax.core :as tubax]))
|
[tubax.core :as tubax]))
|
||||||
|
|
||||||
|
;;; TODO: Move to funcool/beicon
|
||||||
|
|
||||||
|
(defn rx-merge-reduce [f seed ob]
|
||||||
|
(let [current-acc (atom seed)]
|
||||||
|
(->> (rx/concat
|
||||||
|
(rx/of seed)
|
||||||
|
(->> ob
|
||||||
|
(rx/mapcat #(f @current-acc %))
|
||||||
|
(rx/tap #(reset! current-acc %))))
|
||||||
|
(rx/last))))
|
||||||
|
|
||||||
|
(defn rx-skip-last
|
||||||
|
[n ob]
|
||||||
|
(.pipe ob (.skipLast js/rxjsOperators (int n))))
|
||||||
|
|
||||||
;; Upload changes batches size
|
;; Upload changes batches size
|
||||||
(def change-batch-size 100)
|
(def change-batch-size 100)
|
||||||
|
|
||||||
|
(defn get-file
|
||||||
|
"Resolves the file inside the context given its id and the data"
|
||||||
|
([context type]
|
||||||
|
(get-file context type nil nil))
|
||||||
|
|
||||||
|
([context type id]
|
||||||
|
(get-file context type id nil))
|
||||||
|
|
||||||
|
([context type id media]
|
||||||
|
(let [file-id (:file-id context)
|
||||||
|
path (case type
|
||||||
|
:manifest (str "manifest.json")
|
||||||
|
:page (str file-id "/" id ".svg")
|
||||||
|
:colors (str file-id "/colors.json")
|
||||||
|
:typographies (str file-id "/typographies.json")
|
||||||
|
:media-list (str file-id "/media.json")
|
||||||
|
:media (let [ext (dom/mtype->extension (:mtype media))]
|
||||||
|
(str file-id "/media/" id "." ext))
|
||||||
|
:components (str file-id "/components.svg"))
|
||||||
|
|
||||||
|
svg? (str/ends-with? path "svg")
|
||||||
|
json? (str/ends-with? path "json")
|
||||||
|
other? (not (or svg? json?))
|
||||||
|
|
||||||
|
file-type (if other? "blob" "text")]
|
||||||
|
|
||||||
|
(cond->> (uz/get-file (:zip context) path file-type)
|
||||||
|
svg?
|
||||||
|
(rx/map (comp tubax/xml->clj :content))
|
||||||
|
|
||||||
|
json?
|
||||||
|
(rx/map (comp json/decode :content))
|
||||||
|
|
||||||
|
other?
|
||||||
|
(rx/map :content)))))
|
||||||
|
|
||||||
|
(defn resolve-factory
|
||||||
|
"Creates a wrapper around the atom to remap ids to new ids and keep
|
||||||
|
their relationship so they ids are coherent."
|
||||||
|
[]
|
||||||
|
(let [id-mapping-atom (atom {})
|
||||||
|
resolve
|
||||||
|
(fn [id-mapping id]
|
||||||
|
(assert (uuid? id))
|
||||||
|
(get id-mapping id))
|
||||||
|
|
||||||
|
set-id
|
||||||
|
(fn [id-mapping id]
|
||||||
|
(assert (uuid? id))
|
||||||
|
(cond-> id-mapping
|
||||||
|
(nil? (resolve id-mapping id))
|
||||||
|
(assoc id (uuid/next))))]
|
||||||
|
|
||||||
|
(fn [id]
|
||||||
|
(swap! id-mapping-atom set-id id)
|
||||||
|
(resolve @id-mapping-atom id))))
|
||||||
|
|
||||||
(defn create-file
|
(defn create-file
|
||||||
"Create a new file on the back-end"
|
"Create a new file on the back-end"
|
||||||
[project-id file-desc]
|
[context file-id]
|
||||||
(let [file-id (uuid/next)]
|
(let [resolve (:resolve context)
|
||||||
|
file-id (resolve file-id)]
|
||||||
(rp/mutation
|
(rp/mutation
|
||||||
:create-temp-file
|
:create-temp-file
|
||||||
{:id file-id
|
{:id file-id
|
||||||
:name (:name file-desc)
|
:name (:name context)
|
||||||
:is-shared (:shared file-desc)
|
:is-shared (:shared context)
|
||||||
:project-id project-id
|
:project-id (:project-id context)
|
||||||
:data (-> cp/empty-file-data (assoc :id file-id))})))
|
:data (-> cp/empty-file-data (assoc :id file-id))})))
|
||||||
|
|
||||||
|
(defn persist-file [file]
|
||||||
|
(rp/mutation :persist-temp-file {:id (:id file)}))
|
||||||
|
|
||||||
|
(defn link-file-libraries
|
||||||
|
"Create a new file on the back-end"
|
||||||
|
[context file-id]
|
||||||
|
(let [resolve (:resolve context)
|
||||||
|
file-id (resolve file-id)
|
||||||
|
libraries (->> context :libraries (mapv resolve))]
|
||||||
|
(->> (rx/from libraries)
|
||||||
|
(rx/map #(hash-map :file-id file-id :library-id %))
|
||||||
|
(rx/flat-map (partial rp/mutation :link-file-to-library)))))
|
||||||
|
|
||||||
(defn send-changes
|
(defn send-changes
|
||||||
"Creates batches of changes to be sent to the backend"
|
"Creates batches of changes to be sent to the backend"
|
||||||
[file]
|
[file]
|
||||||
|
@ -58,7 +147,7 @@
|
||||||
(rx/map first)
|
(rx/map first)
|
||||||
(rx/tap #(reset! revn (:revn %))))
|
(rx/tap #(reset! revn (:revn %))))
|
||||||
|
|
||||||
(rp/mutation :persist-temp-file {:id (:id file)}))))
|
(rp/mutation :persist-temp-file {:id file-id}))))
|
||||||
|
|
||||||
(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"
|
||||||
|
@ -76,8 +165,34 @@
|
||||||
:is-local true}))
|
:is-local true}))
|
||||||
(rx/flat-map #(rp/mutation! :upload-file-media-object %))))
|
(rx/flat-map #(rp/mutation! :upload-file-media-object %))))
|
||||||
|
|
||||||
(defn add-shape-file
|
(defn resolve-text-content [node context]
|
||||||
[file node]
|
(let [resolve (:resolve context)]
|
||||||
|
(->> node
|
||||||
|
(ct/transform-nodes
|
||||||
|
(fn [item]
|
||||||
|
(-> item
|
||||||
|
(d/update-when :fill-color-ref-id resolve)
|
||||||
|
(d/update-when :fill-color-ref-file resolve)
|
||||||
|
(d/update-when :typography-ref-id resolve)
|
||||||
|
(d/update-when :typography-ref-file resolve)))))))
|
||||||
|
|
||||||
|
(defn resolve-data-ids
|
||||||
|
[data type context]
|
||||||
|
(let [resolve (:resolve context)]
|
||||||
|
(-> data
|
||||||
|
(d/update-when :fill-color-ref-id resolve)
|
||||||
|
(d/update-when :fill-color-ref-file resolve)
|
||||||
|
(d/update-when :stroke-color-ref-id resolve)
|
||||||
|
(d/update-when :stroke-color-ref-file resolve)
|
||||||
|
(d/update-when :component-id resolve)
|
||||||
|
(d/update-when :component-file resolve)
|
||||||
|
(d/update-when :shape-ref resolve)
|
||||||
|
|
||||||
|
(cond-> (= type :text)
|
||||||
|
(d/update-when :content resolve-text-content context)))))
|
||||||
|
|
||||||
|
(defn process-import-node
|
||||||
|
[context file node]
|
||||||
|
|
||||||
(let [type (cip/get-type node)
|
(let [type (cip/get-type node)
|
||||||
close? (cip/close? node)]
|
close? (cip/close? node)]
|
||||||
|
@ -88,9 +203,14 @@
|
||||||
:svg-raw (fb/close-svg-raw file)
|
:svg-raw (fb/close-svg-raw file)
|
||||||
#_default file)
|
#_default file)
|
||||||
|
|
||||||
(let [data (cip/parse-data type node)
|
(let [resolve (:resolve context)
|
||||||
old-id (cip/get-id node)
|
old-id (cip/get-id node)
|
||||||
interactions (cip/parse-interactions node)
|
interactions (->> (cip/parse-interactions node)
|
||||||
|
(mapv #(update % :destination resolve)))
|
||||||
|
|
||||||
|
data (-> (cip/parse-data type node)
|
||||||
|
(resolve-data-ids type context)
|
||||||
|
(assoc :id (resolve old-id)))
|
||||||
|
|
||||||
file (case type
|
file (case type
|
||||||
:frame (fb/add-artboard file data)
|
:frame (fb/add-artboard file data)
|
||||||
|
@ -108,49 +228,24 @@
|
||||||
;; We store this data for post-processing after every shape has been
|
;; We store this data for post-processing after every shape has been
|
||||||
;; added
|
;; added
|
||||||
(cond-> file
|
(cond-> file
|
||||||
(some? (:last-id file))
|
|
||||||
(assoc-in [:id-mapping old-id] (:last-id file))
|
|
||||||
|
|
||||||
(d/not-empty? interactions)
|
(d/not-empty? interactions)
|
||||||
(assoc-in [:interactions old-id] interactions))))))
|
(assoc-in [:interactions (:id data)] interactions))))))
|
||||||
|
|
||||||
(defn post-process-file
|
(defn setup-interactions
|
||||||
[file]
|
[file]
|
||||||
|
|
||||||
(letfn [(add-interaction
|
(letfn [(add-interactions
|
||||||
[id file {:keys [action-type event-type destination] :as interaction}]
|
[file [id interactions]]
|
||||||
(fb/add-interaction file action-type event-type id destination))
|
|
||||||
|
|
||||||
(add-interactions
|
|
||||||
[file [old-id interactions]]
|
|
||||||
(let [id (get-in file [:id-mapping old-id])]
|
|
||||||
(->> interactions
|
(->> interactions
|
||||||
(mapv (fn [interaction]
|
(reduce #(fb/add-interaction %1 id %2) file)))
|
||||||
(let [id (get-in file [:id-mapping (:destination interaction)])]
|
|
||||||
(assoc interaction :destination id))))
|
|
||||||
(reduce
|
|
||||||
(partial add-interaction id) file))))
|
|
||||||
|
|
||||||
(process-interactions
|
(process-interactions
|
||||||
[file]
|
[file]
|
||||||
(reduce add-interactions file (:interactions file)))]
|
(let [interactions (:interactions file)
|
||||||
|
file (dissoc file :interactions)]
|
||||||
|
(->> interactions (reduce add-interactions file))))]
|
||||||
|
|
||||||
(-> file
|
(-> file process-interactions)))
|
||||||
(process-interactions)
|
|
||||||
(dissoc :id-mapping :interactions))))
|
|
||||||
|
|
||||||
(defn merge-reduce [f seed ob]
|
|
||||||
(let [current-acc (atom seed)]
|
|
||||||
(->> (rx/concat
|
|
||||||
(rx/of seed)
|
|
||||||
(->> ob
|
|
||||||
(rx/mapcat #(f @current-acc %))
|
|
||||||
(rx/tap #(reset! current-acc %))))
|
|
||||||
(rx/last))))
|
|
||||||
|
|
||||||
(defn skip-last
|
|
||||||
[n ob]
|
|
||||||
(.pipe ob (.skipLast js/rxjsOperators (int n))))
|
|
||||||
|
|
||||||
(defn resolve-media
|
(defn resolve-media
|
||||||
[file-id node]
|
[file-id node]
|
||||||
|
@ -172,155 +267,189 @@
|
||||||
(rx/observe-on :async))))
|
(rx/observe-on :async))))
|
||||||
|
|
||||||
(defn import-page
|
(defn import-page
|
||||||
[file [page-name content]]
|
[context file [page-id page-name content]]
|
||||||
(if (cip/valid? content)
|
|
||||||
(let [nodes (->> content cip/node-seq)
|
(let [nodes (->> content cip/node-seq)
|
||||||
file-id (:id file)
|
file-id (:id file)
|
||||||
|
resolve (:resolve context)
|
||||||
page-data (-> (cip/parse-page-data content)
|
page-data (-> (cip/parse-page-data content)
|
||||||
(assoc :name page-name))]
|
(assoc :name page-name)
|
||||||
|
(assoc :id (resolve page-id)))
|
||||||
|
file (-> file (fb/add-page page-data))]
|
||||||
(->> (rx/from nodes)
|
(->> (rx/from nodes)
|
||||||
(rx/filter cip/shape?)
|
(rx/filter cip/shape?)
|
||||||
(rx/mapcat (partial resolve-media file-id))
|
(rx/mapcat (partial resolve-media file-id))
|
||||||
(rx/reduce add-shape-file (fb/add-page file page-data))
|
(rx/reduce (partial process-import-node context) file)
|
||||||
(rx/map post-process-file)
|
(rx/map (comp fb/close-page setup-interactions)))))
|
||||||
(rx/map fb/close-page)))
|
|
||||||
(rx/empty)))
|
|
||||||
|
|
||||||
(defn get-page-path [dir-id id]
|
(defn import-component [context file node]
|
||||||
(str dir-id "/" id ".svg"))
|
(let [resolve (:resolve context)
|
||||||
|
content (cip/find-node node :g)
|
||||||
|
file-id (:id file)
|
||||||
|
old-id (cip/get-id node)
|
||||||
|
id (resolve old-id)
|
||||||
|
data (-> (cip/parse-data :group content)
|
||||||
|
(assoc :id id))
|
||||||
|
|
||||||
(defn process-page [file-id zip [page-id page-name]]
|
file (-> file (fb/start-component data))
|
||||||
(let [path (get-page-path (d/name file-id) page-id)]
|
children (cip/node-seq node)]
|
||||||
(->> (uz/get-file zip path)
|
|
||||||
(rx/map (comp tubax/xml->clj :content))
|
(->> (rx/from children)
|
||||||
(rx/map #(vector page-name %)))))
|
(rx/filter cip/shape?)
|
||||||
|
(rx/skip 1)
|
||||||
|
(rx-skip-last 1)
|
||||||
|
(rx/mapcat (partial resolve-media file-id))
|
||||||
|
(rx/reduce (partial process-import-node context) file)
|
||||||
|
(rx/map fb/finish-component))))
|
||||||
|
|
||||||
|
(defn process-pages
|
||||||
|
[context file]
|
||||||
|
(let [index (:pages-index context)
|
||||||
|
get-page-data
|
||||||
|
(fn [page-id]
|
||||||
|
[page-id (get-in index [page-id :name])])
|
||||||
|
|
||||||
|
pages (->> (:pages context) (mapv get-page-data))]
|
||||||
|
|
||||||
(defn process-file-pages
|
|
||||||
[file file-id file-desc zip]
|
|
||||||
(let [index (:pages-index file-desc)
|
|
||||||
pages (->> (:pages file-desc)
|
|
||||||
(mapv #(vector % (get-in index [(keyword %) :name]))))]
|
|
||||||
(->> (rx/from pages)
|
(->> (rx/from pages)
|
||||||
(rx/mapcat #(process-page file-id zip %))
|
(rx/mapcat
|
||||||
(merge-reduce import-page file))))
|
(fn [[page-id page-name]]
|
||||||
|
(->> (get-file context :page page-id)
|
||||||
|
(rx/map (fn [page-data] [page-id page-name page-data])))))
|
||||||
|
(rx-merge-reduce (partial import-page context) file))))
|
||||||
|
|
||||||
(defn process-library-colors
|
(defn process-library-colors
|
||||||
[file file-id file-desc zip]
|
[context file]
|
||||||
(if (:has-colors file-desc)
|
(if (:has-colors context)
|
||||||
(let [add-color
|
(let [resolve (:resolve context)
|
||||||
|
add-color
|
||||||
(fn [file [id color]]
|
(fn [file [id color]]
|
||||||
(let [color (-> (d/kebab-keys color)
|
(let [color (-> color
|
||||||
(d/update-in-when [:gradient :type] keyword))
|
(d/update-in-when [:gradient :type] keyword)
|
||||||
file (fb/add-library-color file color)]
|
(assoc :id (resolve id)))]
|
||||||
(assoc file [:library-mapping id] (:last-id file))))
|
(fb/add-library-color file color)))]
|
||||||
|
(->> (get-file context :colors)
|
||||||
path (str (d/name file-id) "/colors.json")]
|
(rx/flat-map (comp d/kebab-keys cip/string->uuid))
|
||||||
(->> (uz/get-file zip path)
|
|
||||||
(rx/mapcat (comp json/decode :content))
|
|
||||||
(rx/reduce add-color file)))
|
(rx/reduce add-color file)))
|
||||||
|
|
||||||
(rx/of file)))
|
(rx/of file)))
|
||||||
|
|
||||||
(defn process-library-typographies
|
(defn process-library-typographies
|
||||||
[file file-id file-desc zip]
|
[context file]
|
||||||
(if (:has-typographies file-desc)
|
(if (:has-typographies context)
|
||||||
(let [add-typography
|
(let [resolve (:resolve context)]
|
||||||
(fn [file [id typography]]
|
(->> (get-file context :typographies)
|
||||||
(let [typography (d/kebab-keys typography)
|
(rx/flat-map (comp d/kebab-keys cip/string->uuid))
|
||||||
file (fb/add-library-typography file typography)]
|
(rx/map (fn [[id typography]]
|
||||||
(assoc file [:library-mapping id] (:last-id file))))
|
(-> typography
|
||||||
|
(d/kebab-keys)
|
||||||
path (str (d/name file-id) "/typographies.json")]
|
(assoc :id (resolve id)))))
|
||||||
(->> (uz/get-file zip path)
|
(rx/reduce fb/add-library-typography file)))
|
||||||
(rx/mapcat (comp json/decode :content))
|
|
||||||
(rx/reduce add-typography file)))
|
|
||||||
|
|
||||||
(rx/of file)))
|
(rx/of file)))
|
||||||
|
|
||||||
(defn process-library-media
|
(defn process-library-media
|
||||||
[file file-id file-desc zip]
|
[context file]
|
||||||
(if (:has-media file-desc)
|
(if (:has-media context)
|
||||||
(let [add-media
|
(let [resolve (:resolve context)]
|
||||||
(fn [file media]
|
(->> (get-file context :media-list)
|
||||||
(let [file (fb/add-library-media file (dissoc media :old-id))]
|
(rx/flat-map (comp d/kebab-keys cip/string->uuid))
|
||||||
(assoc file [:library-mapping (:old-id media)] (:last-id file))))
|
|
||||||
|
|
||||||
path (str (d/name file-id) "/media.json")]
|
|
||||||
|
|
||||||
(->> (uz/get-file zip path)
|
|
||||||
(rx/mapcat (comp json/decode :content))
|
|
||||||
(rx/flat-map
|
(rx/flat-map
|
||||||
(fn [[id media]]
|
(fn [[id media]]
|
||||||
(let [file-path (str (d/name file-id) "/media/" (d/name id) "." (dom/mtype->extension (:mtype media)))]
|
(let [media (assoc media :id (resolve id))]
|
||||||
(->> (uz/get-file zip file-path "blob")
|
(->> (get-file context :media id media)
|
||||||
(rx/map (fn [{blob :content}]
|
(rx/map (fn [blob]
|
||||||
(let [content (.slice blob 0 (.-size blob) (:mtype media))]
|
(let [content (.slice blob 0 (.-size blob) (:mtype media))]
|
||||||
{:name (:name media)
|
{:name (:name media)
|
||||||
|
:id (:id media)
|
||||||
:file-id (:id file)
|
:file-id (:id file)
|
||||||
:content content
|
:content content
|
||||||
:is-local false})))
|
:is-local false})))
|
||||||
(rx/flat-map #(rp/mutation! :upload-file-media-object %))
|
(rx/flat-map #(rp/mutation! :upload-file-media-object %))
|
||||||
(rx/map (fn [response]
|
(rx/map (constantly media))))))
|
||||||
(-> media
|
(rx/reduce fb/add-library-media file)))
|
||||||
(assoc :old-id id)
|
|
||||||
(assoc :id (:id response)))))))))
|
|
||||||
(rx/reduce add-media file)))
|
|
||||||
|
|
||||||
(rx/of file)))
|
(rx/of file)))
|
||||||
|
|
||||||
(defn add-component [file content]
|
|
||||||
(let [content (cip/find-node content :g)
|
|
||||||
data (cip/parse-data :group content)
|
|
||||||
file (-> file (fb/start-component data))
|
|
||||||
id (-> (get-in content [:attrs :id]) (uuid/uuid))
|
|
||||||
component-id (:last-id file)
|
|
||||||
file (assoc file [:library-mapping id] component-id)
|
|
||||||
nodes (cip/node-seq content)]
|
|
||||||
|
|
||||||
(->> (rx/from nodes)
|
|
||||||
(rx/filter cip/shape?)
|
|
||||||
(rx/skip 1)
|
|
||||||
(skip-last 1)
|
|
||||||
(rx/mapcat (partial resolve-media (:id file)))
|
|
||||||
(rx/reduce add-shape-file file)
|
|
||||||
(rx/map #(fb/finish-component %)))))
|
|
||||||
|
|
||||||
(defn process-library-components
|
(defn process-library-components
|
||||||
[file file-id file-desc zip]
|
[context file]
|
||||||
(if (:has-components file-desc)
|
(if (:has-components context)
|
||||||
(let [path (str (d/name file-id) "/components.svg")]
|
(let [split-components
|
||||||
(->> (uz/get-file zip path)
|
(fn [content] (->> (cip/node-seq content)
|
||||||
(rx/map (comp tubax/xml->clj :content))
|
(filter #(= :symbol (:tag %)))))]
|
||||||
(rx/flat-map (fn [content] (->> (cip/node-seq content) (filter #(= :symbol (:tag %))))))
|
|
||||||
(merge-reduce add-component file)))
|
(->> (get-file context :components)
|
||||||
|
(rx/flat-map split-components)
|
||||||
|
(rx-merge-reduce (partial import-component context) file)))
|
||||||
(rx/of file)))
|
(rx/of file)))
|
||||||
|
|
||||||
(defn process-file
|
(defn process-file
|
||||||
[file file-id file-desc zip]
|
[context file]
|
||||||
(->> (process-file-pages file file-id file-desc zip)
|
|
||||||
(rx/flat-map #(process-library-colors % file-id file-desc zip))
|
(->> (rx/of file)
|
||||||
(rx/flat-map #(process-library-typographies % file-id file-desc zip))
|
(rx/flat-map (partial process-pages context))
|
||||||
(rx/flat-map #(process-library-media % file-id file-desc zip))
|
(rx/flat-map (partial process-library-colors context))
|
||||||
(rx/flat-map #(process-library-components % file-id file-desc zip))
|
(rx/flat-map (partial process-library-typographies context))
|
||||||
|
(rx/flat-map (partial process-library-media context))
|
||||||
|
(rx/flat-map (partial process-library-components context))
|
||||||
(rx/flat-map send-changes)
|
(rx/flat-map send-changes)
|
||||||
(rx/ignore)))
|
(rx/ignore)))
|
||||||
|
|
||||||
(defn process-package
|
(defn create-files [context manifest]
|
||||||
[project-id zip-file]
|
(->> manifest :files rx/from
|
||||||
(->> (uz/get-file zip-file "manifest.json")
|
|
||||||
(rx/flat-map (comp :files json/decode :content))
|
|
||||||
(rx/flat-map
|
(rx/flat-map
|
||||||
(fn [[file-id file-desc]]
|
(fn [[file-id file-desc]]
|
||||||
(let [file-desc (d/kebab-keys file-desc)]
|
(create-file (merge context file-desc) file-id)))
|
||||||
(->> (create-file project-id file-desc)
|
(rx/reduce #(assoc %1 (:id %2) %2) {})))
|
||||||
(rx/flat-map #(process-file % file-id file-desc zip-file))))))))
|
|
||||||
|
(defn link-libraries [context manifest]
|
||||||
|
(->> manifest :files rx/from
|
||||||
|
(rx/flat-map
|
||||||
|
(fn [[file-id file-desc]]
|
||||||
|
(link-file-libraries (merge context file-desc) file-id)))))
|
||||||
|
|
||||||
|
(defn process-files [context manifest files]
|
||||||
|
(->> manifest :files rx/from
|
||||||
|
(rx/flat-map
|
||||||
|
(fn [[file-id file-desc]]
|
||||||
|
(let [resolve (:resolve context)
|
||||||
|
context (-> context
|
||||||
|
(merge file-desc)
|
||||||
|
(assoc :file-id file-id))
|
||||||
|
file (get files (resolve file-id))]
|
||||||
|
(process-file context file))))))
|
||||||
|
|
||||||
|
(defn process-package
|
||||||
|
[context]
|
||||||
|
(->> (get-file context :manifest)
|
||||||
|
(rx/map (comp d/kebab-keys cip/string->uuid))
|
||||||
|
|
||||||
|
;; Create the temporary files
|
||||||
|
(rx/mapcat (fn [manifest]
|
||||||
|
(->> (create-files context manifest)
|
||||||
|
(rx/map #(vector manifest %)))))
|
||||||
|
|
||||||
|
;; Set-up the files dependencies
|
||||||
|
(rx/mapcat (fn [[manifest files]]
|
||||||
|
(rx/concat
|
||||||
|
(link-libraries context manifest)
|
||||||
|
(rx/of [manifest files]))))
|
||||||
|
|
||||||
|
;; Creates files data
|
||||||
|
(rx/mapcat (fn [[manifest files]]
|
||||||
|
(process-files context manifest files)))
|
||||||
|
|
||||||
|
;; Mark temporary files as persisted
|
||||||
|
(rx/mapcat persist-file)))
|
||||||
|
|
||||||
(defmethod impl/handler :import-file
|
(defmethod impl/handler :import-file
|
||||||
[{:keys [project-id files]}]
|
[{:keys [project-id files]}]
|
||||||
|
|
||||||
|
(let [context {:project-id project-id
|
||||||
|
:resolve (resolve-factory)}]
|
||||||
(->> (rx/from files)
|
(->> (rx/from files)
|
||||||
(rx/flat-map uz/load-from-url)
|
(rx/flat-map uz/load-from-url)
|
||||||
(rx/flat-map (partial process-package project-id))
|
(rx/map #(assoc context :zip %))
|
||||||
|
(rx/flat-map process-package)
|
||||||
(rx/catch
|
(rx/catch
|
||||||
(fn [err]
|
(fn [err]
|
||||||
(.error js/console "ERROR" err (clj->js (.-data err)))))))
|
(.error js/console "ERROR" err (clj->js (.-data err))))))))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue