mirror of
https://github.com/penpot/penpot.git
synced 2025-06-02 03:31:37 +02:00
281 lines
8 KiB
Clojure
281 lines
8 KiB
Clojure
;; This Source Code Form is subject to the terms of the Mozilla Public
|
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
;;
|
|
;; Copyright (c) KALEIDOS INC
|
|
|
|
(ns app.libs.file-builder
|
|
(:require
|
|
[app.common.data :as d]
|
|
[app.common.features :as cfeat]
|
|
[app.common.files.builder :as fb]
|
|
[app.common.media :as cm]
|
|
[app.common.types.components-list :as ctkl]
|
|
[app.common.uuid :as uuid]
|
|
[app.util.json :as json]
|
|
[app.util.webapi :as wapi]
|
|
[app.util.zip :as uz]
|
|
[app.worker.export :as e]
|
|
[beicon.v2.core :as rx]
|
|
[cuerdas.core :as str]
|
|
[promesa.core :as p]))
|
|
|
|
(defn parse-data [data]
|
|
(as-> data $
|
|
(js->clj $ :keywordize-keys true)
|
|
;; Transforms camelCase to kebab-case
|
|
(d/deep-mapm
|
|
(fn [[key value]]
|
|
(let [value (if (= (type value) js/Symbol)
|
|
(keyword (js/Symbol.keyFor value))
|
|
value)
|
|
key (-> key d/name str/kebab keyword)]
|
|
[key value])) $)))
|
|
|
|
(defn data-uri->blob
|
|
[data-uri]
|
|
(let [[mtype b64-data] (str/split data-uri ";base64,")
|
|
mtype (subs mtype (inc (str/index-of mtype ":")))
|
|
decoded (.atob js/window b64-data)
|
|
size (.-length ^js decoded)
|
|
content (js/Uint8Array. size)]
|
|
|
|
(doseq [i (range 0 size)]
|
|
(aset content i (.charCodeAt decoded i)))
|
|
|
|
(wapi/create-blob content mtype)))
|
|
|
|
(defn parse-library-media
|
|
[[file-id media]]
|
|
(rx/merge
|
|
(let [markup
|
|
(->> (vals media)
|
|
(reduce e/collect-media {})
|
|
(json/encode))]
|
|
(rx/of (vector (str file-id "/media.json") markup)))
|
|
|
|
(->> (rx/from (vals media))
|
|
(rx/map #(assoc % :file-id file-id))
|
|
(rx/merge-map
|
|
(fn [media]
|
|
(let [file-path (str/concat file-id "/media/" (:id media) (cm/mtype->extension (:mtype media)))
|
|
blob (data-uri->blob (:uri media))]
|
|
(rx/of (vector file-path blob))))))))
|
|
|
|
(defn export-file
|
|
[file]
|
|
(let [file (assoc file
|
|
:name (:name file)
|
|
:file-name (:name file)
|
|
:is-shared false)
|
|
|
|
files-stream (->> (rx/of {(:id file) file})
|
|
(rx/share))
|
|
|
|
manifest-stream
|
|
(->> files-stream
|
|
(rx/map #(e/create-manifest (uuid/next) (:id file) :all % cfeat/default-features))
|
|
(rx/map (fn [a]
|
|
(vector "manifest.json" a))))
|
|
|
|
render-stream
|
|
(->> files-stream
|
|
(rx/merge-map vals)
|
|
(rx/merge-map e/process-pages)
|
|
(rx/observe-on :async)
|
|
(rx/merge-map e/get-page-data)
|
|
(rx/share))
|
|
|
|
colors-stream
|
|
(->> files-stream
|
|
(rx/merge-map vals)
|
|
(rx/map #(vector (:id %) (get-in % [:data :colors])))
|
|
(rx/filter #(d/not-empty? (second %)))
|
|
(rx/map e/parse-library-color))
|
|
|
|
typographies-stream
|
|
(->> files-stream
|
|
(rx/merge-map vals)
|
|
(rx/map #(vector (:id %) (get-in % [:data :typographies])))
|
|
(rx/filter #(d/not-empty? (second %)))
|
|
(rx/map e/parse-library-typographies))
|
|
|
|
media-stream
|
|
(->> files-stream
|
|
(rx/merge-map vals)
|
|
(rx/map #(vector (:id %) (get-in % [:data :media])))
|
|
(rx/filter #(d/not-empty? (second %)))
|
|
(rx/merge-map parse-library-media))
|
|
|
|
components-stream
|
|
(->> files-stream
|
|
(rx/merge-map vals)
|
|
(rx/filter #(d/not-empty? (ctkl/components-seq (:data %))))
|
|
(rx/merge-map e/parse-library-components))
|
|
|
|
pages-stream
|
|
(->> render-stream
|
|
(rx/map e/collect-page))]
|
|
|
|
(rx/merge
|
|
(->> render-stream
|
|
(rx/map #(hash-map
|
|
:type :progress
|
|
:file (:id file)
|
|
:data (str "Render " (:file-name %) " - " (:name %)))))
|
|
|
|
(->> (rx/merge
|
|
manifest-stream
|
|
pages-stream
|
|
components-stream
|
|
media-stream
|
|
colors-stream
|
|
typographies-stream)
|
|
(rx/reduce conj [])
|
|
(rx/with-latest-from files-stream)
|
|
(rx/merge-map (fn [[data _]]
|
|
(->> (uz/compress-files data)
|
|
(rx/map #(vector file %)))))))))
|
|
|
|
(deftype File [^:mutable file]
|
|
Object
|
|
|
|
(addPage [_ name]
|
|
(set! file (fb/add-page file {:name name}))
|
|
(str (:current-page-id file)))
|
|
|
|
(addPage [_ name options]
|
|
(set! file (fb/add-page file {:name name :options (parse-data options)}))
|
|
(str (:current-page-id file)))
|
|
|
|
(closePage [_]
|
|
(set! file (fb/close-page file)))
|
|
|
|
(addArtboard [_ data]
|
|
(set! file (fb/add-artboard file (parse-data data)))
|
|
(str (:last-id file)))
|
|
|
|
(closeArtboard [_]
|
|
(set! file (fb/close-artboard file)))
|
|
|
|
(addGroup [_ data]
|
|
(set! file (fb/add-group file (parse-data data)))
|
|
(str (:last-id file)))
|
|
|
|
(closeGroup [_]
|
|
(set! file (fb/close-group file)))
|
|
|
|
(addBool [_ data]
|
|
(set! file (fb/add-bool file (parse-data data)))
|
|
(str (:last-id file)))
|
|
|
|
(closeBool [_]
|
|
(set! file (fb/close-bool file)))
|
|
|
|
(createRect [_ data]
|
|
(set! file (fb/create-rect file (parse-data data)))
|
|
(str (:last-id file)))
|
|
|
|
(createCircle [_ data]
|
|
(set! file (fb/create-circle file (parse-data data)))
|
|
(str (:last-id file)))
|
|
|
|
(createPath [_ data]
|
|
(set! file (fb/create-path file (parse-data data)))
|
|
(str (:last-id file)))
|
|
|
|
(createText [_ data]
|
|
(set! file (fb/create-text file (parse-data data)))
|
|
(str (:last-id file)))
|
|
|
|
(createImage [_ data]
|
|
(set! file (fb/create-image file (parse-data data)))
|
|
(str (:last-id file)))
|
|
|
|
(createSVG [_ data]
|
|
(set! file (fb/create-svg-raw file (parse-data data)))
|
|
(str (:last-id file)))
|
|
|
|
(closeSVG [_]
|
|
(set! file (fb/close-svg-raw file)))
|
|
|
|
(addLibraryColor [_ data]
|
|
(set! file (fb/add-library-color file (parse-data data)))
|
|
(str (:last-id file)))
|
|
|
|
(updateLibraryColor [_ data]
|
|
(set! file (fb/update-library-color file (parse-data data)))
|
|
(str (:last-id file)))
|
|
|
|
(deleteLibraryColor [_ data]
|
|
(set! file (fb/delete-library-color file (parse-data data)))
|
|
(str (:last-id file)))
|
|
|
|
(addLibraryMedia [_ data]
|
|
(set! file (fb/add-library-media file (parse-data data)))
|
|
(str (:last-id file)))
|
|
|
|
(deleteLibraryMedia [_ data]
|
|
(set! file (fb/delete-library-media file (parse-data data)))
|
|
(str (:last-id file)))
|
|
|
|
(addLibraryTypography [_ data]
|
|
(set! file (fb/add-library-typography file (parse-data data)))
|
|
(str (:last-id file)))
|
|
|
|
(deleteLibraryTypography [_ data]
|
|
(set! file (fb/delete-library-typography file (parse-data data)))
|
|
(str (:last-id file)))
|
|
|
|
(startComponent [_ data]
|
|
(set! file (fb/start-component file (parse-data data)))
|
|
(str (:current-component-id file)))
|
|
|
|
(finishComponent [_]
|
|
(set! file (fb/finish-component file)))
|
|
|
|
(createComponentInstance [_ data]
|
|
(set! file (fb/create-component-instance file (parse-data data)))
|
|
(str (:last-id file)))
|
|
|
|
(lookupShape [_ shape-id]
|
|
(clj->js (fb/lookup-shape file (uuid/uuid shape-id))))
|
|
|
|
(updateObject [_ id new-obj]
|
|
(let [old-obj (fb/lookup-shape file (uuid/uuid id))
|
|
new-obj (d/deep-merge old-obj (parse-data new-obj))]
|
|
(set! file (fb/update-object file old-obj new-obj))))
|
|
|
|
(deleteObject [_ id]
|
|
(set! file (fb/delete-object file (uuid/uuid id))))
|
|
|
|
(getId [_]
|
|
(:id file))
|
|
|
|
(getCurrentPageId [_]
|
|
(:current-page-id file))
|
|
|
|
(asMap [_]
|
|
(clj->js file))
|
|
|
|
(newId [_]
|
|
(uuid/next))
|
|
|
|
(export [_]
|
|
(p/create
|
|
(fn [resolve reject]
|
|
(->> (export-file file)
|
|
(rx/take 1)
|
|
(rx/subs!
|
|
(fn [value]
|
|
(when (not (contains? value :type))
|
|
(let [[_ export-blob] value]
|
|
(resolve export-blob))))
|
|
reject))))))
|
|
|
|
(defn create-file-export [^string name]
|
|
(binding [cfeat/*current* cfeat/default-features]
|
|
(File. (fb/create-file name))))
|
|
|
|
(defn exports []
|
|
#js {:createFile create-file-export})
|