From 4d0dcc587609ca08218f7daf0f58b3af0f22d8cb Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 17 Jun 2021 16:24:39 +0200 Subject: [PATCH 1/4] :sparkles: Process interactions on import --- common/src/app/common/file_builder.cljc | 25 +++ frontend/src/app/main/ui/shapes/export.cljs | 159 +++++++++++--------- frontend/src/app/util/import/parser.cljc | 27 +++- frontend/src/app/worker/import.cljs | 76 +++++++--- 4 files changed, 192 insertions(+), 95 deletions(-) diff --git a/common/src/app/common/file_builder.cljc b/common/src/app/common/file_builder.cljc index 17d2fef26..3b0c077ba 100644 --- a/common/src/app/common/file_builder.cljc +++ b/common/src/app/common/file_builder.cljc @@ -291,6 +291,31 @@ (-> file (update :parent-stack pop))) +(defn add-interaction + [file action-type event-type from-id destination-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)) + + (let [interactions (->> (lookup-shape file from-id) + :interactions + (filterv #(or (not= (:action-type %) action-type) + (not= (:event-type %) event-type)))) + conj (fnil conj []) + interactions (-> interactions + (conj + {:action-type action-type + :event-type event-type + :destination destination-id}))] + (commit-change + file + {:type :mod-obj + :page-id (:current-page-id file) + :id from-id + + :operations + [{:type :set :attr :interactions :val interactions}]}))) + (defn generate-changes [file] (:changes file)) diff --git a/frontend/src/app/main/ui/shapes/export.cljs b/frontend/src/app/main/ui/shapes/export.cljs index c7d60b3c2..a27009fd1 100644 --- a/frontend/src/app/main/ui/shapes/export.cljs +++ b/frontend/src/app/main/ui/shapes/export.cljs @@ -91,20 +91,22 @@ [(str "penpot:" (d/name k)) v])] (into {} (map prefix-entry) m))) + (mf/defc export-grid-data [{:keys [grids]}] - [:> "penpot:grids" #js {} - (for [{:keys [type display params]} grids] - (let [props (->> (d/without-keys params [:color]) - (prefix-keys) - (clj->js))] - [:> "penpot:grid" - (-> props - (obj/set! "penpot:color" (get-in params [:color :color])) - (obj/set! "penpot:opacity" (get-in params [:color :opacity])) - (obj/set! "penpot:type" (d/name type)) - (cond-> (some? display) - (obj/set! "penpot:display" (str display))))]))]) + (when-not (empty? grids) + [:> "penpot:grids" #js {} + (for [{:keys [type display params]} grids] + (let [props (->> (d/without-keys params [:color]) + (prefix-keys) + (clj->js))] + [:> "penpot:grid" + (-> props + (obj/set! "penpot:color" (get-in params [:color :color])) + (obj/set! "penpot:opacity" (get-in params [:color :opacity])) + (obj/set! "penpot:type" (d/name type)) + (cond-> (some? display) + (obj/set! "penpot:display" (str display))))]))])) (mf/defc export-page [{:keys [options]}] @@ -117,62 +119,85 @@ [:> "penpot:page" #js {} [:& export-grid-data {:grids grids}]])))) +(mf/defc export-shadow-data + [{:keys [shadow]}] + (for [{:keys [style hidden color offset-x offset-y blur spread]} shadow] + [:> "penpot:shadow" + #js {:penpot:shadow-type (d/name style) + :penpot:hidden (str hidden) + :penpot:color (str (:color color)) + :penpot:opacity (str (:opacity color)) + :penpot:offset-x (str offset-x) + :penpot:offset-y (str offset-y) + :penpot:blur (str blur) + :penpot:spread (str spread)}])) + +(mf/defc export-blur-data [{:keys [blur]}] + (when (some? blur) + (let [{:keys [type hidden value]} blur] + [:> "penpot:blur" + #js {:penpot:blur-type (d/name type) + :penpot:hidden (str hidden) + :penpot:value (str value)}]))) + +(mf/defc export-exports-data [{:keys [exports]}] + (for [{:keys [scale suffix type]} exports] + [:> "penpot:export" + #js {:penpot:type (d/name type) + :penpot:suffix suffix + :penpot:scale (str scale)}])) + +(mf/defc export-svg-data [shape] + [:* + (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])]))]) + +(mf/defc export-interactions-data + [{:keys [interactions]}] + (when-not (empty? interactions) + [:> "penpot:interactions" #js {} + (for [{:keys [action-type destination event-type]} interactions] + [:> "penpot:interaction" + #js {:penpot:action-type (d/name action-type) + :penpot:destination (str destination) + :penpot:event-type (d/name event-type)}])])) + (mf/defc export-data [{:keys [shape]}] - (let [props (-> (obj/new) - (add-data shape))] + (let [props (-> (obj/new) (add-data shape)) + frame? (= (:type shape) :frame)] [:> "penpot:shape" props - (for [{:keys [style hidden color offset-x offset-y blur spread]} (:shadow shape)] - [:> "penpot:shadow" #js {:penpot:shadow-type (d/name style) - :penpot:hidden (str hidden) - :penpot:color (str (:color color)) - :penpot:opacity (str (:opacity color)) - :penpot:offset-x (str offset-x) - :penpot:offset-y (str offset-y) - :penpot:blur (str blur) - :penpot:spread (str spread)}]) - - (when (some? (:blur shape)) - (let [{:keys [type hidden value]} (:blur shape)] - [:> "penpot:blur" #js {:penpot:blur-type (d/name type) - :penpot:hidden (str hidden) - :penpot:value (str value)}])) - - (for [{:keys [scale suffix type]} (:exports shape)] - [:> "penpot:export" #js {:penpot:type (d/name type) - :penpot:suffix suffix - :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])])) - - - (when (and (= (:type shape) :frame) - (seq (:grids shape))) - [:& export-grid-data {:grids (:grids shape)}])])) + [:& export-shadow-data shape] + [:& export-blur-data shape] + [:& export-exports-data shape] + [:& export-svg-data shape] + [:& export-interactions-data shape] + [:& export-grid-data shape]])) diff --git a/frontend/src/app/util/import/parser.cljc b/frontend/src/app/util/import/parser.cljc index 1ffc490d4..84af3163b 100644 --- a/frontend/src/app/util/import/parser.cljc +++ b/frontend/src/app/util/import/parser.cljc @@ -15,6 +15,12 @@ [app.util.path.parser :as upp] [cuerdas.core :as str])) +(def url-regex + #"url\(#([^\)]*)\)") + +(def uuid-regex + #"\w{8}-\w{4}-\w{4}-\w{4}-\w{12}") + (defn valid? [root] (contains? (:attrs root) :xmlns:penpot)) @@ -41,7 +47,7 @@ (defn find-all-nodes [node tag] (when (some? node) - (->> node :content (filterv #(= (:tag %) :defs))))) + (->> node :content (filterv #(= (:tag %) tag))))) (defn get-data ([node] @@ -65,6 +71,11 @@ (or (close? node) (some? (get-data node)))) +(defn get-id + [node] + (when-let [id (re-find uuid-regex (get-in node [:attrs :id]))] + (uuid/uuid id))) + (defn str->bool [val] (when (some? val) (= val "true"))) @@ -193,9 +204,6 @@ (assoc :content content) (assoc :center center)))) -(def url-regex #"url\(#([^\)]*)\)") - - (defn parse-stops [gradient-node] (->> gradient-node @@ -483,7 +491,7 @@ (defn remove-prefix [s] (cond-> s (string? s) - (str/replace #"\w{8}-\w{4}-\w{4}-\w{4}-\w{12}-" ""))) + (str/replace (re-pattern (str uuid-regex "-")) ""))) (defn get-svg-attrs [svg-data svg-attrs] @@ -640,3 +648,12 @@ (not (empty? grids)) (assoc-in [:options :saved-grids] grids)))) + +(defn parse-interactions + [node] + (let [interactions-node (get-data node :penpot:interactions)] + (->> (find-all-nodes interactions-node :penpot:interaction) + (mapv (fn [node] + {:destination (get-meta node :destination uuid/uuid) + :action-type (get-meta node :action-type keyword) + :event-type (get-meta node :event-type keyword)}))))) diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs index f9ee680fa..e54098b43 100644 --- a/frontend/src/app/worker/import.cljs +++ b/frontend/src/app/worker/import.cljs @@ -78,36 +78,65 @@ (defn add-shape-file [file node] - (let [type (cip/get-type node) - close? (cip/close? node) - data (cip/parse-data type node)] - + (let [type (cip/get-type node) + close? (cip/close? node)] (if close? (case type - :frame - (fb/close-artboard file) + :frame (fb/close-artboard file) + :group (fb/close-group file) + :svg-raw (fb/close-svg-raw file) + #_default file) - :group - (fb/close-group file) + (let [data (cip/parse-data type node) + old-id (cip/get-id node) + interactions (cip/parse-interactions node) - :svg-raw - (fb/close-svg-raw file) + file (case type + :frame (fb/add-artboard file data) + :group (fb/add-group file data) + :rect (fb/create-rect file data) + :circle (fb/create-circle file data) + :path (fb/create-path file data) + :text (fb/create-text file data) + :image (fb/create-image file data) + :svg-raw (fb/create-svg-raw file data) + #_default file)] - ;; default - file) + (assert (some? old-id) "ID not found") - (case type - :frame (fb/add-artboard file data) - :group (fb/add-group file data) - :rect (fb/create-rect file data) - :circle (fb/create-circle file data) - :path (fb/create-path file data) - :text (fb/create-text file data) - :image (fb/create-image file data) - :svg-raw (fb/create-svg-raw file data) + ;; We store this data for post-processing after every shape has been + ;; added + (cond-> file + (some? (:last-id file)) + (assoc-in [:id-mapping old-id] (:last-id file)) - ;; default - file)))) + (not (empty? interactions)) + (assoc-in [:interactions old-id] interactions)))))) + +(defn post-process-file + [file] + + (letfn [(add-interaction + [id file {:keys [action-type event-type destination] :as interaction}] + (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 + (mapv (fn [interaction] + (let [id (get-in file [:id-mapping (:destination interaction)])] + (assoc interaction :destination id)))) + (reduce + (partial add-interaction id) file)))) + + (process-interactions + [file] + (reduce add-interactions file (:interactions file)))] + + (-> file + (process-interactions) + (dissoc :id-mapping :interactions)))) (defn merge-reduce [f seed ob] (->> (rx/concat @@ -145,6 +174,7 @@ (rx/filter cip/shape?) (rx/mapcat (partial resolve-images file-id)) (rx/reduce add-shape-file (fb/add-page file page-data)) + (rx/map post-process-file) (rx/map fb/close-page))) (rx/empty))) From fbf1c10077bf86e51d22ef5447323b990cf3c8c2 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 18 Jun 2021 14:49:14 +0200 Subject: [PATCH 2/4] :sparkles: Export library data (images, typographies, colors) --- common/src/app/common/data.cljc | 4 + frontend/src/app/worker/export.cljs | 126 +++++++++++++++++++++++----- frontend/src/app/worker/import.cljs | 7 +- 3 files changed, 113 insertions(+), 24 deletions(-) diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index bdf35dc00..39abab9d4 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -556,3 +556,7 @@ :else m))) + +(defn not-empty? + [coll] + (boolean (seq coll))) diff --git a/frontend/src/app/worker/export.cljs b/frontend/src/app/worker/export.cljs index 5c2d485a0..abd5e2753 100644 --- a/frontend/src/app/worker/export.cljs +++ b/frontend/src/app/worker/export.cljs @@ -6,36 +6,57 @@ (ns app.worker.export (:require + [app.common.data :as d] + [app.config :as cfg] [app.main.render :as r] [app.main.repo :as rp] [app.util.dom :as dom] - [app.util.zip :as uz] + [app.util.http :as http] [app.util.json :as json] + [app.util.zip :as uz] [app.worker.impl :as impl] - [beicon.core :as rx])) + [beicon.core :as rx] + [cuerdas.core :as str])) (defn create-manifest "Creates a manifest entry for the given files" - [team-id files] + [team-id file-id files] (letfn [(format-page [manifest page] (-> manifest (assoc (str (:id page)) {:name (:name page)}))) (format-file [manifest file] - (let [name (:name file) - pages (->> (get-in file [:data :pages]) (mapv str)) - index (->> (get-in file [:data :pages-index]) (vals) - (reduce format-page {}))] + (let [name (:name file) + is-shared (:is-shared file) + pages (->> (get-in file [:data :pages]) + (mapv str)) + index (->> (get-in file [:data :pages-index]) + (vals) + (reduce format-page {}))] (-> manifest (assoc (str (:id file)) - {:name name - :pages pages - :pagesIndex index}))))] + {:name name + :shared is-shared + :pages pages + :pagesIndex index + :hasComponents (d/not-empty? (get-in file [:data :components])) + :hasImages (d/not-empty? (get-in file [:data :media])) + :hasColors (d/not-empty? (get-in file [:data :colors])) + :hasTypographies (d/not-empty? (get-in file [:data :typographies]))}))))] (let [manifest {:teamId (str team-id) + :fileId (str file-id) :files (->> (vals files) (reduce format-file {}))}] (json/encode manifest)))) +(defn process-pages [file] + (let [pages (get-in file [:data :pages]) + pages-index (get-in file [:data :pages-index])] + (->> pages + (map #(hash-map + :file-id (:id file) + :data (get pages-index %)))))) + (defn get-page-data [{file-id :file-id {:keys [id name] :as data} :data}] (->> (r/render-page data) @@ -45,30 +66,45 @@ :file-id file-id :markup markup})))) -(defn process-pages [file] - (let [pages (get-in file [:data :pages]) - pages-index (get-in file [:data :pages-index])] - (->> pages - (map #(hash-map - :file-id (:id file) - :data (get pages-index %)))))) - (defn collect-page [{:keys [id file-id markup] :as page}] [(str file-id "/" id ".svg") markup]) +(defn collect-color + [result color] + (-> result + (assoc (str (:id color)) + (->> (select-keys color [:name :color :opacity :gradient]) + (d/deep-mapm + (fn [[k v]] + [(-> k str/camel) v])))))) + +(def ^:const typography-keys [:name :font-family :font-id :font-size + :font-style :font-variant-id :font-weight + :letter-spacing :line-height :text-transform]) +(defn collect-typography + [result typography] + (-> result + (assoc (str (:id typography)) + (->> (select-keys typography typography-keys) + (d/deep-mapm + (fn [[k v]] + [(-> k str/camel) v])))))) (defn export-file [team-id file-id] (let [files-stream - (->> (rp/query :file {:id 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)) manifest-stream (->> files-stream - (rx/map #(create-manifest team-id %)) + (rx/map #(create-manifest team-id file-id %)) (rx/map #(vector "manifest.json" %))) render-stream @@ -79,6 +115,48 @@ (rx/flat-map get-page-data) (rx/share)) + colors-stream + (->> files-stream + (rx/flat-map vals) + (rx/map #(vector (:id %) (get-in % [:data :colors]))) + (rx/filter #(d/not-empty? (second %))) + (rx/map (fn [[file-id colors]] + (let [markup + (->> (vals colors) + (reduce collect-color {}) + (json/encode))] + [(str file-id "/colors.json") markup])))) + + typographies-stream + (->> files-stream + (rx/flat-map vals) + (rx/map #(vector (:id %) (get-in % [:data :typographies]))) + (rx/filter #(d/not-empty? (second %))) + (rx/map (fn [[file-id typographies]] + (let [markup + (->> (vals typographies) + (reduce collect-typography {}) + (json/encode))] + [(str file-id "/typographies.json") markup])))) + + media-stream + (->> files-stream + (rx/flat-map vals) + (rx/map #(vector (:id %) (get-in % [:data :media]))) + (rx/filter #(d/not-empty? (second %))) + (rx/flat-map + (fn [[file-id media]] + (->> media vals (mapv #(assoc % :file-id file-id))))) + + (rx/flat-map + (fn [media] + (->> (http/send! + {:uri (cfg/resolve-file-media media) + :response-type :blob + :method :get}) + (rx/map :body) + (rx/map #(vector (str file-id "/images/" (:id media)) %)))))) + pages-stream (->> render-stream (rx/map collect-page))] @@ -90,7 +168,13 @@ :file file-id :data (str "Render " (:file-name %) " - " (:name %))))) - (->> (rx/merge manifest-stream pages-stream) + (->> (rx/merge + manifest-stream + pages-stream + #_components-stream + media-stream + colors-stream + typographies-stream) (rx/reduce conj []) (rx/with-latest-from files-stream) (rx/flat-map (fn [[data files]] diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs index e54098b43..0be111877 100644 --- a/frontend/src/app/worker/import.cljs +++ b/frontend/src/app/worker/import.cljs @@ -25,12 +25,13 @@ (defn create-file "Create a new file on the back-end" - [project-id name] + [project-id file-desc] (let [file-id (uuid/next)] (rp/mutation :create-temp-file {:id file-id - :name name + :name (:name file-desc) + :is-shared (:shared file-desc) :project-id project-id :data (-> cp/empty-file-data (assoc :id file-id))}))) @@ -203,7 +204,7 @@ (rx/flat-map (comp :files json/decode :content)) (rx/flat-map (fn [[file-id file-desc]] - (->> (create-file project-id (:name file-desc)) + (->> (create-file project-id file-desc) (rx/flat-map #(process-file % file-id file-desc zip-file))))))) (defmethod impl/handler :import-file From 234a698538e682f6c5170d1a8941bb243d88267b Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 18 Jun 2021 15:29:07 +0200 Subject: [PATCH 3/4] :sparkle: Fix linter warnings --- .clj-kondo/config.edn | 9 +--- frontend/src/app/libs/file_builder.cljs | 30 ++++++------ .../app/main/data/workspace/path/edition.cljs | 4 +- .../main/data/workspace/path/selection.cljs | 36 +------------- .../app/main/data/workspace/transforms.cljs | 49 ++++++------------- frontend/src/app/main/ui/shapes/export.cljs | 3 +- .../main/ui/workspace/viewport/selection.cljs | 8 +-- .../util/import/{parser.cljc => parser.cljs} | 26 +++++----- frontend/src/app/worker/export.cljs | 2 +- frontend/src/app/worker/import.cljs | 3 +- 10 files changed, 55 insertions(+), 115 deletions(-) rename frontend/src/app/util/import/{parser.cljc => parser.cljs} (97%) diff --git a/.clj-kondo/config.edn b/.clj-kondo/config.edn index ec3b66f77..cbe153b34 100644 --- a/.clj-kondo/config.edn +++ b/.clj-kondo/config.edn @@ -17,14 +17,7 @@ {:exclude-files ["data_readers.clj" "app/util/perf.cljs" - "app/common/exceptions.cljc" - "app/util/import/.*" - "app/worker/export.cljs" - "app/worker/import.cljs" - "app/libs/.*" - "app/main/data/workspace/path/selection.cljs" - "app/main/data/workspace/transforms.cljs" - ]} + "app/common/exceptions.cljc"]} :linters {:unsorted-required-namespaces diff --git a/frontend/src/app/libs/file_builder.cljs b/frontend/src/app/libs/file_builder.cljs index 0560b919a..1dbee9c55 100644 --- a/frontend/src/app/libs/file_builder.cljs +++ b/frontend/src/app/libs/file_builder.cljs @@ -25,59 +25,59 @@ (deftype File [^:mutable file] Object - (addPage [self name] + (addPage [_ name] (set! file (fb/add-page file {:name name})) (str (:current-page-id file))) - (addPage [self name options] + (addPage [_ name options] (set! file (fb/add-page file {:name name :options options})) (str (:current-page-id file))) - (closePage [self] + (closePage [_] (set! file (fb/close-page file))) - (addArtboard [self data] + (addArtboard [_ data] (set! file (fb/add-artboard file (parse-data data))) (str (:last-id file))) - (closeArtboard [self data] + (closeArtboard [_] (set! file (fb/close-artboard file))) - (addGroup [self data] + (addGroup [_ data] (set! file (fb/add-group file (parse-data data))) (str (:last-id file))) - (closeGroup [self] + (closeGroup [_] (set! file (fb/close-group file))) - (createRect [self data] + (createRect [_ data] (set! file (fb/create-rect file (parse-data data))) (str (:last-id file))) - (createCircle [self data] + (createCircle [_ data] (set! file (fb/create-circle file (parse-data data))) (str (:last-id file))) - (createPath [self data] + (createPath [_ data] (set! file (fb/create-path file (parse-data data))) (str (:last-id file))) - (createText [self data] + (createText [_ data] (set! file (fb/create-text file (parse-data data))) (str (:last-id file))) - (createImage [self data] + (createImage [_ data] (set! file (fb/create-image file (parse-data data))) (str (:last-id file))) - (createSVG [self data] + (createSVG [_ data] (set! file (fb/create-svg-raw file (parse-data data))) (str (:last-id file))) - (closeSVG [self] + (closeSVG [_] (set! file (fb/close-svg-raw file))) - (asMap [self] + (asMap [_] (clj->js file))) (defn create-file-export [^string name] diff --git a/frontend/src/app/main/data/workspace/path/edition.cljs b/frontend/src/app/main/data/workspace/path/edition.cljs index 2005ed10c..94bd92f58 100644 --- a/frontend/src/app/main/data/workspace/path/edition.cljs +++ b/frontend/src/app/main/data/workspace/path/edition.cljs @@ -141,9 +141,9 @@ selected? (contains? selected-points position)] (streams/drag-stream (rx/of - (when-not selected? (selection/select-node position shift? "drag")) + (when-not selected? (selection/select-node position shift?)) (drag-selected-points @ms/mouse-position)) - (rx/of (selection/select-node position shift? "click"))))))) + (rx/of (selection/select-node position shift?))))))) (defn drag-selected-points [start-position] diff --git a/frontend/src/app/main/data/workspace/path/selection.cljs b/frontend/src/app/main/data/workspace/path/selection.cljs index dec4027ea..ab23ae536 100644 --- a/frontend/src/app/main/data/workspace/path/selection.cljs +++ b/frontend/src/app/main/data/workspace/path/selection.cljs @@ -60,7 +60,7 @@ (some? id) (assoc-in [:workspace-local :edit-path id :selected-points] positions)))))) -(defn select-node [position shift? kk] +(defn select-node [position shift?] (ptk/reify ::select-node ptk/UpdateEvent (update [_ state] @@ -79,38 +79,6 @@ (some? id) (assoc-in [:workspace-local :edit-path id :selected-points] selected-points)))))) -(defn deselect-node [position shift?] - (ptk/reify ::deselect-node - ptk/UpdateEvent - (update [_ state] - (let [id (get-in state [:workspace-local :edition])] - (-> state - (update-in [:workspace-local :edit-path id :selected-points] (fnil disj #{}) position)))))) - -(defn add-to-selection-handler [index type] - (ptk/reify ::add-to-selection-handler - ptk/UpdateEvent - (update [_ state] - state))) - -(defn add-to-selection-node [index] - (ptk/reify ::add-to-selection-node - ptk/UpdateEvent - (update [_ state] - state))) - -(defn remove-from-selection-handler [index] - (ptk/reify ::remove-from-selection-handler - ptk/UpdateEvent - (update [_ state] - state))) - -(defn remove-from-selection-node [index] - (ptk/reify ::remove-from-selection-handler - ptk/UpdateEvent - (update [_ state] - state))) - (defn deselect-all [] (ptk/reify ::deselect-all ptk/UpdateEvent @@ -140,7 +108,7 @@ (ptk/reify ::handle-selection ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ stream] (let [stop? (fn [event] (or (dwc/interrupt? event) (ms/mouse-up? event))) stoper (->> stream (rx/filter stop?)) from-p @ms/mouse-position] diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index b1b391899..95d783480 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -98,8 +98,8 @@ ;; -- RESIZE (defn start-resize - [handler initial ids shape] - (letfn [(resize [shape initial resizing-shapes layout [point lock? point-snap]] + [handler ids shape] + (letfn [(resize [shape initial layout [point lock? point-snap]] (let [{:keys [width height]} (:selrect shape) {:keys [rotation]} shape rotation (or rotation 0) @@ -181,7 +181,7 @@ (rx/switch-map (fn [[point :as current]] (->> (snap/closest-snap-point page-id resizing-shapes layout zoom point) (rx/map #(conj current %))))) - (rx/mapcat (partial resize shape initial-position resizing-shapes layout)) + (rx/mapcat (partial resize shape initial-position layout)) (rx/take-until stoper)) (rx/of (apply-modifiers ids) (finish-transform)))))))) @@ -196,7 +196,7 @@ (assoc-in [:workspace-local :transform] :rotate))) ptk/WatchEvent - (watch [it state stream] + (watch [_ _ stream] (let [stoper (rx/filter ms/mouse-up? stream) group (gsh/selection-rect shapes) group-center (gsh/center-selrect group) @@ -235,7 +235,7 @@ [] (ptk/reify ::start-move-selected ptk/WatchEvent - (watch [it state stream] + (watch [_ state stream] (let [initial (deref ms/mouse-position) selected (wsh/lookup-selected state {:omit-blocked? true}) stopper (rx/filter ms/mouse-up? stream)] @@ -259,7 +259,7 @@ (defn start-move-duplicate [from-position] (ptk/reify ::start-move-selected ptk/WatchEvent - (watch [it state stream] + (watch [_ _ stream] (->> stream (rx/filter (ptk/type? ::dws/duplicate-selected)) (rx/first) @@ -268,7 +268,7 @@ (defn calculate-frame-for-move [ids] (ptk/reify ::calculate-frame-for-move ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (let [position @ms/mouse-position page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) @@ -313,7 +313,7 @@ (assoc-in [:workspace-local :transform] :move))) ptk/WatchEvent - (watch [it state stream] + (watch [_ state stream] (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) selected (wsh/lookup-selected state {:omit-blocked? true}) @@ -349,20 +349,6 @@ (calculate-frame-for-move ids) (finish-transform))))))))) -(defn- get-displacement-with-grid - "Retrieve the correct displacement delta point for the - provided direction speed and distances thresholds." - [shape direction options] - (let [grid-x (:grid-x options 10) - grid-y (:grid-y options 10) - x-mod (mod (:x shape) grid-x) - y-mod (mod (:y shape) grid-y)] - (case direction - :up (gpt/point 0 (- (if (zero? y-mod) grid-y y-mod))) - :down (gpt/point 0 (- grid-y y-mod)) - :left (gpt/point (- (if (zero? x-mod) grid-x x-mod)) 0) - :right (gpt/point (- grid-x x-mod) 0)))) - (defn- get-displacement "Retrieve the correct displacement delta point for the provided direction speed and distances thresholds." @@ -394,7 +380,7 @@ state)) ptk/WatchEvent - (watch [it state stream] + (watch [_ state stream] (if (= same-event (get-in state [:workspace-local :current-move-selected])) (let [selected (wsh/lookup-selected state {:omit-blocked? true}) move-events (->> stream @@ -539,7 +525,7 @@ (defn increase-rotation [ids rotation] (ptk/reify ::increase-rotation ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) @@ -559,7 +545,7 @@ (us/verify (s/coll-of uuid?) ids) (ptk/reify ::apply-modifiers ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (let [objects (wsh/lookup-page-objects state) children-ids (->> ids (mapcat #(cp/get-children % objects))) ids-with-children (d/concat [] children-ids ids) @@ -610,11 +596,6 @@ (update [_ state] (let [page-id (:current-page-id state) - objects (wsh/lookup-page-objects state page-id) - - update-children - (fn [objects ids modifiers] - (reduce #(assoc-in %1 [%2 :modifiers] modifiers) objects ids)) ;; For each shape updates the modifiers given as arguments update-shape @@ -629,7 +610,7 @@ #(reduce update-shape % ids)))) ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) ids (d/concat [] ids (mapcat #(cp/get-children % objects) ids))] @@ -638,7 +619,7 @@ (defn flip-horizontal-selected [] (ptk/reify ::flip-horizontal-selected ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (let [objects (wsh/lookup-page-objects state) selected (wsh/lookup-selected state {:omit-blocked? true}) shapes (map #(get objects %) selected) @@ -654,7 +635,7 @@ (defn flip-vertical-selected [] (ptk/reify ::flip-vertical-selected ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (let [objects (wsh/lookup-page-objects state) selected (wsh/lookup-selected state {:omit-blocked? true}) shapes (map #(get objects %) selected) @@ -687,6 +668,6 @@ [] (ptk/reify ::selected-to-path ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [ids (wsh/lookup-selected state {:omit-blocked? true})] (rx/of (dch/update-shapes ids ups/convert-to-path)))))) diff --git a/frontend/src/app/main/ui/shapes/export.cljs b/frontend/src/app/main/ui/shapes/export.cljs index a27009fd1..45068e47c 100644 --- a/frontend/src/app/main/ui/shapes/export.cljs +++ b/frontend/src/app/main/ui/shapes/export.cljs @@ -191,8 +191,7 @@ (mf/defc export-data [{:keys [shape]}] - (let [props (-> (obj/new) (add-data shape)) - frame? (= (:type shape) :frame)] + (let [props (-> (obj/new) (add-data shape))] [:> "penpot:shape" props [:& export-shadow-data shape] [:& export-blur-data shape] diff --git a/frontend/src/app/main/ui/workspace/viewport/selection.cljs b/frontend/src/app/main/ui/workspace/viewport/selection.cljs index ad547c9e5..4f853229c 100644 --- a/frontend/src/app/main/ui/workspace/viewport/selection.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/selection.cljs @@ -286,9 +286,9 @@ shape-center (geom/center-shape shape) - on-resize (fn [current-position initial-position event] + on-resize (fn [current-position _initial-position event] (dom/stop-propagation event) - (st/emit! (dw/start-resize current-position initial-position selected shape))) + (st/emit! (dw/start-resize current-position selected shape))) on-rotate #(do (dom/stop-propagation %) (st/emit! (dw/start-rotate shapes)))] @@ -311,9 +311,9 @@ shape (geom/transform-shape shape {:round-coords? false}) shape' (if (debug? :simple-selection) (geom/setup {:type :rect} (geom/selection-rect [shape])) shape) - on-resize (fn [current-position initial-position event] + on-resize (fn [current-position _initial-position event] (dom/stop-propagation event) - (st/emit! (dw/start-resize current-position initial-position #{shape-id} shape'))) + (st/emit! (dw/start-resize current-position #{shape-id} shape'))) on-rotate #(do (dom/stop-propagation %) diff --git a/frontend/src/app/util/import/parser.cljc b/frontend/src/app/util/import/parser.cljs similarity index 97% rename from frontend/src/app/util/import/parser.cljc rename to frontend/src/app/util/import/parser.cljs index 84af3163b..34de143e8 100644 --- a/frontend/src/app/util/import/parser.cljc +++ b/frontend/src/app/util/import/parser.cljs @@ -126,7 +126,7 @@ (defn without-penpot-prefix [m] (let [no-penpot-prefix? - (fn [[k v]] + (fn [[k _]] (not (str/starts-with? (d/name k) "penpot:")))] (into {} (filter no-penpot-prefix?) m))) @@ -191,11 +191,11 @@ [props svg-data] (let [values (->> (select-keys svg-data [:cx :cy :rx :ry]) (d/mapm (fn [_ val] (d/parse-double val))))] - - {:x (- (:cx values) (:rx values)) - :y (- (:cy values) (:ry values)) - :width (* (:rx values) 2) - :height (* (:ry values) 2)})) + (-> props + (assoc :x (- (:cx values) (:rx values)) + :y (- (:cy values) (:ry values)) + :width (* (:rx values) 2) + :height (* (:ry values) 2))))) (defn parse-path [props center svg-data] @@ -459,7 +459,7 @@ [props node] (let [shadows (extract-from-data node :penpot:shadow parse-shadow)] (cond-> props - (not (empty? shadows)) + (d/not-empty? shadows) (assoc :shadow shadows)))) (defn add-blur @@ -473,7 +473,7 @@ [props node] (let [exports (extract-from-data node :penpot:export parse-export)] (cond-> props - (not (empty? exports)) + (d/not-empty? exports) (assoc :exports exports)))) (defn add-layer-options @@ -507,7 +507,7 @@ (reduce assoc-key {})))) (defn get-svg-defs - [node svg-defs] + [node] (let [svg-import (get-data node :penpot:svg-import)] (->> svg-import @@ -545,7 +545,7 @@ (some? svg-defs) - (assoc :svg-defs (get-svg-defs node svg-defs)))) + (assoc :svg-defs (get-svg-defs node)))) props))) @@ -571,7 +571,7 @@ (defn add-frame-data [props node] (let [grids (parse-grids node)] (cond-> props - (not (empty? grids)) + (d/not-empty? grids) (assoc :grids grids)))) (defn has-image? @@ -641,12 +641,12 @@ background (:background style) grids (->> (parse-grids node) (group-by :type) - (d/mapm (fn [k v] (-> v first :params))))] + (d/mapm (fn [_ v] (-> v first :params))))] (cond-> {} (some? background) (assoc-in [:options :background] background) - (not (empty? grids)) + (d/not-empty? grids) (assoc-in [:options :saved-grids] grids)))) (defn parse-interactions diff --git a/frontend/src/app/worker/export.cljs b/frontend/src/app/worker/export.cljs index abd5e2753..894d69c46 100644 --- a/frontend/src/app/worker/export.cljs +++ b/frontend/src/app/worker/export.cljs @@ -182,7 +182,7 @@ (rx/map #(vector (get files file-id) %))))))))) (defmethod impl/handler :export-file - [{:keys [team-id project-id files] :as message}] + [{:keys [team-id files] :as message}] (->> (rx/from files) (rx/mapcat #(export-file team-id %)) diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs index 0be111877..9bbf1a4c6 100644 --- a/frontend/src/app/worker/import.cljs +++ b/frontend/src/app/worker/import.cljs @@ -17,7 +17,6 @@ [app.util.zip :as uz] [app.worker.impl :as impl] [beicon.core :as rx] - [cuerdas.core :as str] [tubax.core :as tubax])) ;; Upload changes batches size @@ -111,7 +110,7 @@ (some? (:last-id file)) (assoc-in [:id-mapping old-id] (:last-id file)) - (not (empty? interactions)) + (d/not-empty? interactions) (assoc-in [:interactions old-id] interactions)))))) (defn post-process-file From 9b9959da9a7651ade1e65e556c9baf05568cec99 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 22 Jun 2021 09:58:18 +0200 Subject: [PATCH 4/4] :sparkles: Export library components --- frontend/src/app/main/exports.cljs | 68 +++++++++++++++-- frontend/src/app/main/render.cljs | 13 +++- frontend/src/app/main/ui.cljs | 9 +++ frontend/src/app/main/ui/render.cljs | 27 +++++++ frontend/src/app/worker/export.cljs | 110 ++++++++++++++++++--------- 5 files changed, 183 insertions(+), 44 deletions(-) diff --git a/frontend/src/app/main/exports.cljs b/frontend/src/app/main/exports.cljs index f3ae1e818..ed0907b09 100644 --- a/frontend/src/app/main/exports.cljs +++ b/frontend/src/app/main/exports.cljs @@ -25,6 +25,7 @@ [app.main.ui.shapes.shape :refer [shape-container]] [app.main.ui.shapes.svg-raw :as svg-raw] [app.main.ui.shapes.text :as text] + [app.util.object :as obj] [app.util.timers :as ts] [cuerdas.core :as str] [rumext.alpha :as mf])) @@ -73,10 +74,11 @@ (mf/fnc group-wrapper [{:keys [shape frame] :as props}] (let [childs (mapv #(get objects %) (:shapes shape))] - [:& group-shape {:frame frame - :shape shape - :is-child-selected? true - :childs childs}])))) + [:& shape-container {:shape shape} + [:& group-shape {:frame frame + :shape shape + :is-child-selected? true + :childs childs}]])))) (defn svg-raw-wrapper-factory [objects] @@ -207,7 +209,8 @@ :height height :version "1.1" :xmlnsXlink "http://www.w3.org/1999/xlink" - :xmlns "http://www.w3.org/2000/svg"} + :xmlns "http://www.w3.org/2000/svg" + :xmlns:penpot "https://penpot.app/xmlns"} [:& wrapper {:shape frame :view-box vbox}]])) (mf/defc component-svg @@ -238,5 +241,58 @@ :height height :version "1.1" :xmlnsXlink "http://www.w3.org/1999/xlink" - :xmlns "http://www.w3.org/2000/svg"} + :xmlns "http://www.w3.org/2000/svg" + :xmlns:penpot "https://penpot.app/xmlns"} [:& wrapper {:shape group :view-box vbox}]])) + +(mf/defc component-symbol + [{:keys [id data] :as props}] + + (let [{:keys [name objects]} data + root (get objects id) + + {:keys [width height]} (:selrect root) + vbox (str "0 0 " width " " height) + + modifier (-> (gpt/point (:x root) (:y root)) + (gpt/negate) + (gmt/translate-matrix)) + + modifier-ids (concat [id] (cp/get-children id objects)) + update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier) + objects (reduce update-fn objects modifier-ids) + root (assoc-in root [:modifiers :displacement] modifier) + + group-wrapper + (mf/use-memo + (mf/deps objects) + #(group-wrapper-factory objects))] + + [:symbol {:id (str id) + :viewBox vbox} + [:title name] + [:& group-wrapper {:shape root :view-box vbox}]])) + +(mf/defc components-sprite-svg + {::mf/wrap-props false} + [props] + + (let [data (obj/get props "data") + children (obj/get props "children") + embed? (obj/get props "embed?")] + [:& (mf/provider embed/context) {:value embed?} + [:svg {:version "1.1" + :xmlns "http://www.w3.org/2000/svg" + :xmlnsXlink "http://www.w3.org/1999/xlink" + :xmlns:penpot "https://penpot.app/xmlns" + :style {:width "100vw" + :height "100vh" + :display (when-not (some? children) "none")}} + + [:defs + (for [[component-id component-data] (:components data)] + [:& component-symbol {:id component-id + :key (str component-id) + :data component-data}])] + + children]])) diff --git a/frontend/src/app/main/render.cljs b/frontend/src/app/main/render.cljs index 26e62bacc..64b760cfa 100644 --- a/frontend/src/app/main/render.cljs +++ b/frontend/src/app/main/render.cljs @@ -66,5 +66,16 @@ (let [elem (mf/element exports/page-svg #js {:data data :embed? true})] (rds/renderToStaticMarkup elem))))))) +(defn render-components + [data] + (rx/concat + (->> (rx/merge + (populate-images-cache data) + (populate-fonts-cache data)) + (rx/ignore)) - + (->> (rx/of data) + (rx/map + (fn [data] + (let [elem (mf/element exports/components-sprite-svg #js {:data data :embed? true})] + (rds/renderToStaticMarkup elem))))))) diff --git a/frontend/src/app/main/ui.cljs b/frontend/src/app/main/ui.cljs index 8859dacd3..3bf450847 100644 --- a/frontend/src/app/main/ui.cljs +++ b/frontend/src/app/main/ui.cljs @@ -82,6 +82,7 @@ ;; Used for export ["/render-object/:file-id/:page-id/:object-id" :render-object] + ["/render-sprite/:file-id" :render-sprite] ["/dashboard/team/:team-id" ["/members" :dashboard-team-members] @@ -172,6 +173,14 @@ :page-id page-id :object-id object-id}])) + :render-sprite + (do + (let [file-id (uuid (get-in route [:path-params :file-id])) + component-id (get-in route [:query-params :component-id]) + component-id (when (some? component-id) (uuid component-id))] + [:& render/render-sprite {:file-id file-id + :component-id component-id}])) + :workspace (let [project-id (some-> params :path :project-id uuid) file-id (some-> params :path :file-id uuid) diff --git a/frontend/src/app/main/ui/render.cljs b/frontend/src/app/main/ui/render.cljs index cb09796fc..64fd56ec2 100644 --- a/frontend/src/app/main/ui/render.cljs +++ b/frontend/src/app/main/ui/render.cljs @@ -123,3 +123,30 @@ [:& object-svg {:objects @objects :object-id object-id :zoom 1}]))) + +(mf/defc render-sprite + [{:keys [file-id component-id] :as props}] + (let [file (mf/use-state nil)] + (mf/use-effect + (mf/deps file-id) + (fn [] + (->> (repo/query! :file {:id file-id}) + (rx/subs + (fn [result] + (reset! file result)))) + (constantly nil))) + + (when @file + [:* + [:& exports/components-sprite-svg {:data (:data @file) :embed true} + + (when (some? component-id) + [:use {:x 0 :y 0 + :xlinkHref (str "#" component-id)}])] + + (when-not (some? component-id) + [:ul + (for [[id data] (get-in @file [:data :components])] + (let [url (str "#/render-sprite/" (:id @file) "?component-id=" id)] + [:li [:a {:href url} (:name data)]]))])]))) + diff --git a/frontend/src/app/worker/export.cljs b/frontend/src/app/worker/export.cljs index 894d69c46..3e9ee110d 100644 --- a/frontend/src/app/worker/export.cljs +++ b/frontend/src/app/worker/export.cljs @@ -70,26 +70,77 @@ [{:keys [id file-id markup] :as page}] [(str file-id "/" id ".svg") markup]) -(defn collect-color - [result color] +(defn collect-entries [result data keys] (-> result - (assoc (str (:id color)) - (->> (select-keys color [:name :color :opacity :gradient]) + (assoc (str (:id data)) + (->> (select-keys data keys) (d/deep-mapm (fn [[k v]] [(-> k str/camel) v])))))) -(def ^:const typography-keys [:name :font-family :font-id :font-size - :font-style :font-variant-id :font-weight - :letter-spacing :line-height :text-transform]) +(def ^:const color-keys + [:name :color :opacity :gradient]) + +(def ^:const typography-keys + [:name :font-family :font-id :font-size :font-style :font-variant-id :font-weight + :letter-spacing :line-height :text-transform]) + +(def ^:const media-keys + [:name :mtype :width :height]) + +(defn collect-color + [result color] + (collect-entries result color color-keys)) + (defn collect-typography [result typography] - (-> result - (assoc (str (:id typography)) - (->> (select-keys typography typography-keys) - (d/deep-mapm - (fn [[k v]] - [(-> k str/camel) v])))))) + (collect-entries result typography typography-keys)) + +(defn collect-media + [result media] + (collect-entries result media media-keys)) + +(defn parse-library-color + [[file-id colors]] + (let [markup + (->> (vals colors) + (reduce collect-color {}) + (json/encode))] + [(str file-id "/colors.json") markup])) + +(defn parse-library-typographies + [[file-id typographies]] + (let [markup + (->> (vals typographies) + (reduce collect-typography {}) + (json/encode))] + [(str file-id "/typographies.json") markup])) + +(defn parse-library-media + [[file-id media]] + (rx/merge + (let [markup + (->> (vals media) + (reduce collect-media {}) + (json/encode))] + (rx/of (vector (str file-id "/images.json") markup))) + + (->> (rx/from (vals media)) + (rx/map #(assoc % :file-id file-id)) + (rx/flat-map + (fn [media] + (let [file-path (str file-id "/images/" (:id media) "." (dom/mtype->extension (:mtype media)))] + (->> (http/send! + {:uri (cfg/resolve-file-media media) + :response-type :blob + :method :get}) + (rx/map :body) + (rx/map #(vector file-path %))))))))) + +(defn parse-library-components + [file] + (->> (r/render-components (:data file)) + (rx/map #(vector (str (:id file) "/components.svg") %)))) (defn export-file [team-id file-id] @@ -120,42 +171,27 @@ (rx/flat-map vals) (rx/map #(vector (:id %) (get-in % [:data :colors]))) (rx/filter #(d/not-empty? (second %))) - (rx/map (fn [[file-id colors]] - (let [markup - (->> (vals colors) - (reduce collect-color {}) - (json/encode))] - [(str file-id "/colors.json") markup])))) + (rx/map parse-library-color)) typographies-stream (->> files-stream (rx/flat-map vals) (rx/map #(vector (:id %) (get-in % [:data :typographies]))) (rx/filter #(d/not-empty? (second %))) - (rx/map (fn [[file-id typographies]] - (let [markup - (->> (vals typographies) - (reduce collect-typography {}) - (json/encode))] - [(str file-id "/typographies.json") markup])))) + (rx/map parse-library-typographies)) media-stream (->> files-stream (rx/flat-map vals) (rx/map #(vector (:id %) (get-in % [:data :media]))) (rx/filter #(d/not-empty? (second %))) - (rx/flat-map - (fn [[file-id media]] - (->> media vals (mapv #(assoc % :file-id file-id))))) + (rx/flat-map parse-library-media)) - (rx/flat-map - (fn [media] - (->> (http/send! - {:uri (cfg/resolve-file-media media) - :response-type :blob - :method :get}) - (rx/map :body) - (rx/map #(vector (str file-id "/images/" (:id media)) %)))))) + components-stream + (->> files-stream + (rx/flat-map vals) + (rx/filter #(d/not-empty? (get-in % [:data :components]))) + (rx/flat-map parse-library-components)) pages-stream (->> render-stream @@ -171,7 +207,7 @@ (->> (rx/merge manifest-stream pages-stream - #_components-stream + components-stream media-stream colors-stream typographies-stream)