mirror of
https://github.com/penpot/penpot.git
synced 2025-05-28 03:16:10 +02:00
♻️ Refactor exportation process, make it considerably faster
This commit is contained in:
parent
d6abd2202c
commit
9140fc71b9
33 changed files with 1096 additions and 1090 deletions
|
@ -10,10 +10,12 @@
|
|||
funcool/beicon {:mvn/version "2021.07.05-1"}
|
||||
funcool/okulary {:mvn/version "2020.04.14-0"}
|
||||
funcool/potok {:mvn/version "2021.09.20-0"}
|
||||
funcool/rumext {:mvn/version "2022.01.20.128"}
|
||||
funcool/rumext {:mvn/version "2022.03.28-131"}
|
||||
funcool/tubax {:mvn/version "2021.05.20-0"}
|
||||
|
||||
instaparse/instaparse {:mvn/version "1.4.10"}
|
||||
garden/garden {:mvn/version "1.3.10"}
|
||||
|
||||
}
|
||||
|
||||
:aliases
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
(ns app.main.data.exports
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.workspace.persistence :as dwp]
|
||||
|
@ -47,6 +46,7 @@
|
|||
state
|
||||
(dissoc state :export))))))
|
||||
|
||||
|
||||
(defn show-workspace-export-dialog
|
||||
([] (show-workspace-export-dialog nil))
|
||||
([{:keys [selected]}]
|
||||
|
@ -55,8 +55,6 @@
|
|||
(watch [_ state _]
|
||||
(let [file-id (:current-file-id state)
|
||||
page-id (:current-page-id state)
|
||||
|
||||
filename (-> (wsh/lookup-page state page-id) :name)
|
||||
selected (or selected (wsh/lookup-selected state page-id {}))
|
||||
|
||||
shapes (if (seq selected)
|
||||
|
@ -74,11 +72,10 @@
|
|||
(assoc :name (:name shape))))]
|
||||
|
||||
(rx/of (modal/show :export-shapes
|
||||
{:exports (vec exports)
|
||||
:filename filename})))))))
|
||||
{:exports (vec exports)})))))))
|
||||
|
||||
(defn show-viewer-export-dialog
|
||||
[{:keys [shapes filename page-id file-id exports]}]
|
||||
[{:keys [shapes page-id file-id exports]}]
|
||||
(ptk/reify ::show-viewer-export-dialog
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
|
@ -91,51 +88,44 @@
|
|||
(assoc :object-id (:id shape))
|
||||
(assoc :shape (dissoc shape :exports))
|
||||
(assoc :name (:name shape))))]
|
||||
(rx/of (modal/show :export-shapes {:exports (vec exports)
|
||||
:filename filename}))))))
|
||||
(rx/of (modal/show :export-shapes {:exports (vec exports)}))))))
|
||||
|
||||
(defn show-workspace-export-frames-dialog
|
||||
([frames]
|
||||
(ptk/reify ::show-workspace-export-frames-dialog
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [file-id (:current-file-id state)
|
||||
page-id (:current-page-id state)
|
||||
filename (-> (wsh/lookup-page state page-id)
|
||||
:name
|
||||
(dm/str ".pdf"))
|
||||
[frames]
|
||||
(ptk/reify ::show-workspace-export-frames-dialog
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [file-id (:current-file-id state)
|
||||
page-id (:current-page-id state)
|
||||
exports (for [frame frames]
|
||||
{:enabled true
|
||||
:page-id page-id
|
||||
:file-id file-id
|
||||
:object-id (:id frame)
|
||||
:shape frame
|
||||
:name (:name frame)})]
|
||||
|
||||
exports (for [frame frames]
|
||||
{:enabled true
|
||||
:page-id page-id
|
||||
:file-id file-id
|
||||
:frame-id (:id frame)
|
||||
:shape frame
|
||||
:name (:name frame)})]
|
||||
|
||||
(rx/of (modal/show :export-frames
|
||||
{:exports (vec exports)
|
||||
:filename filename})))))))
|
||||
(rx/of (modal/show :export-frames
|
||||
{:exports (vec exports)}))))))
|
||||
|
||||
(defn- initialize-export-status
|
||||
[exports filename resource-id query-name]
|
||||
[exports cmd resource]
|
||||
(ptk/reify ::initialize-export-status
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc state :export {:in-progress true
|
||||
:resource-id resource-id
|
||||
:resource-id (:id resource)
|
||||
:healthy? true
|
||||
:error false
|
||||
:progress 0
|
||||
:widget-visible true
|
||||
:detail-visible true
|
||||
:exports exports
|
||||
:filename filename
|
||||
:last-update (dt/now)
|
||||
:query-name query-name}))))
|
||||
:cmd cmd}))))
|
||||
|
||||
(defn- update-export-status
|
||||
[{:keys [progress status resource-id name] :as data}]
|
||||
[{:keys [done status resource-id filename] :as data}]
|
||||
(ptk/reify ::update-export-status
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
|
@ -144,7 +134,7 @@
|
|||
healthy? (< time-diff (dt/duration {:seconds 6}))]
|
||||
(cond-> state
|
||||
(= status "running")
|
||||
(update :export assoc :progress (:done progress) :last-update (dt/now) :healthy? healthy?)
|
||||
(update :export assoc :progress done :last-update (dt/now) :healthy? healthy?)
|
||||
|
||||
(= status "error")
|
||||
(update :export assoc :error (:cause data) :last-update (dt/now) :healthy? healthy?)
|
||||
|
@ -155,12 +145,12 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(when (= status "ended")
|
||||
(->> (rp/query! :download-export-resource resource-id)
|
||||
(->> (rp/query! :exporter {:cmd :get-resource :blob? true :id resource-id})
|
||||
(rx/delay 500)
|
||||
(rx/map #(dom/trigger-download name %)))))))
|
||||
(rx/map #(dom/trigger-download filename %)))))))
|
||||
|
||||
(defn request-simple-export
|
||||
[{:keys [export filename]}]
|
||||
[{:keys [export]}]
|
||||
(ptk/reify ::request-simple-export
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
|
@ -170,22 +160,26 @@
|
|||
(watch [_ state _]
|
||||
(let [profile-id (:profile-id state)
|
||||
params {:exports [export]
|
||||
:profile-id profile-id}]
|
||||
:profile-id profile-id
|
||||
:cmd :export-shapes
|
||||
:wait true}]
|
||||
(rx/concat
|
||||
(rx/of ::dwp/force-persist)
|
||||
(->> (rp/query! :export-shapes-simple params)
|
||||
(rx/map (fn [data]
|
||||
(dom/trigger-download filename data)
|
||||
(clear-export-state uuid/zero)))
|
||||
(->> (rp/query! :export-shapes params)
|
||||
(rx/mapcat (fn [{:keys [id filename]}]
|
||||
(->> (rp/query! :exporter {:cmd :get-resource :blob? true :id id})
|
||||
(rx/map (fn [data]
|
||||
(dom/trigger-download filename data)
|
||||
(clear-export-state uuid/zero))))))
|
||||
(rx/catch (fn [cause]
|
||||
(prn "KKKK" cause)
|
||||
(rx/concat
|
||||
(rx/of (clear-export-state uuid/zero))
|
||||
(rx/throw cause))))))))))
|
||||
|
||||
|
||||
(defn request-multiple-export
|
||||
[{:keys [filename exports query-name]
|
||||
:or {query-name :export-shapes-multiple}
|
||||
[{:keys [exports cmd]
|
||||
:or {cmd :export-shapes}
|
||||
:as params}]
|
||||
(ptk/reify ::request-multiple-export
|
||||
ptk/WatchEvent
|
||||
|
@ -194,7 +188,7 @@
|
|||
profile-id (:profile-id state)
|
||||
ws-conn (:ws-conn state)
|
||||
params {:exports exports
|
||||
:name filename
|
||||
:cmd cmd
|
||||
:profile-id profile-id
|
||||
:wait false}
|
||||
|
||||
|
@ -219,11 +213,10 @@
|
|||
|
||||
;; Launch the exportation process and stores the resource id
|
||||
;; locally.
|
||||
(->> (rp/query! query-name params)
|
||||
(rx/tap (fn [{:keys [id]}]
|
||||
(vreset! resource-id id)))
|
||||
(rx/map (fn [{:keys [id]}]
|
||||
(initialize-export-status exports filename id query-name))))
|
||||
(->> (rp/query! :exporter params)
|
||||
(rx/map (fn [{:keys [id] :as resource}]
|
||||
(vreset! resource-id id)
|
||||
(initialize-export-status exports cmd resource))))
|
||||
|
||||
;; We proceed to update the export state with incoming
|
||||
;; progress updates. We delay the stoper for give some time
|
||||
|
@ -246,13 +239,12 @@
|
|||
(rx/map #(clear-export-state @resource-id))
|
||||
(rx/take-until (rx/delay 6000 stoper))))))))
|
||||
|
||||
|
||||
(defn retry-last-export
|
||||
[]
|
||||
(ptk/reify ::retry-last-export
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [params (select-keys (:export state) [:filename :exports :query-name])]
|
||||
(let [params (select-keys (:export state) [:exports :cmd])]
|
||||
(when (seq params)
|
||||
(rx/of (request-multiple-export params)))))))
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
[app.util.i18n :refer [tr]]
|
||||
[app.util.router :as rt]
|
||||
[app.util.timers :as ts]
|
||||
[expound.alpha :as expound]
|
||||
[fipp.edn :as fpp]
|
||||
[potok.core :as ptk]))
|
||||
|
||||
|
@ -113,13 +112,12 @@
|
|||
(ts/schedule
|
||||
(st/emitf
|
||||
(msg/show {:content "Internal error: assertion."
|
||||
:type :error
|
||||
:timeout 3000})))
|
||||
:type :error
|
||||
:timeout 3000})))
|
||||
|
||||
;; Print to the console some debugging info
|
||||
(js/console.group message)
|
||||
(js/console.info context)
|
||||
(js/console.error (with-out-str (expound/printer error)))
|
||||
(js/console.groupEnd message)))
|
||||
|
||||
;; That are special case server-errors that should be treated
|
||||
|
|
|
@ -14,7 +14,8 @@
|
|||
(:require
|
||||
["react-dom/server" :as rds]
|
||||
[app.common.colors :as clr]
|
||||
[app.common.geom.align :as gal]
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
|
@ -22,10 +23,12 @@
|
|||
[app.common.pages.helpers :as cph]
|
||||
[app.config :as cfg]
|
||||
[app.main.fonts :as fonts]
|
||||
[app.main.ui.context :as muc]
|
||||
[app.main.ui.shapes.bool :as bool]
|
||||
[app.main.ui.shapes.circle :as circle]
|
||||
[app.main.ui.shapes.embed :as embed]
|
||||
[app.main.ui.shapes.export :as export]
|
||||
[app.main.ui.shapes.filters :as filters]
|
||||
[app.main.ui.shapes.frame :as frame]
|
||||
[app.main.ui.shapes.group :as group]
|
||||
[app.main.ui.shapes.image :as image]
|
||||
|
@ -57,11 +60,9 @@
|
|||
:fill color}])
|
||||
|
||||
(defn- calculate-dimensions
|
||||
[{:keys [objects] :as data} vport]
|
||||
(let [shapes (cph/get-immediate-children objects)
|
||||
rect (cond->> (gsh/selection-rect shapes)
|
||||
(some? vport)
|
||||
(gal/adjust-to-viewport vport))]
|
||||
[objects]
|
||||
(let [shapes (cph/get-immediate-children objects)
|
||||
rect (gsh/selection-rect shapes)]
|
||||
(-> rect
|
||||
(update :x mth/finite 0)
|
||||
(update :y mth/finite 0)
|
||||
|
@ -156,24 +157,63 @@
|
|||
(->> [x y width height]
|
||||
(map #(ust/format-precision % viewbox-decimal-precision)))))
|
||||
|
||||
(defn adapt-root-frame
|
||||
[objects object]
|
||||
(let [shapes (cph/get-immediate-children objects)
|
||||
srect (gsh/selection-rect shapes)
|
||||
object (merge object (select-keys srect [:x :y :width :height]))
|
||||
object (gsh/transform-shape object)]
|
||||
(assoc object :fill-color "#f0f0f0")))
|
||||
|
||||
(defn adapt-objects-for-shape
|
||||
[objects object-id]
|
||||
(let [object (get objects object-id)
|
||||
object (cond->> object
|
||||
(cph/root-frame? object)
|
||||
(adapt-root-frame objects))
|
||||
|
||||
;; Replace the previous object with the new one
|
||||
objects (assoc objects object-id object)
|
||||
|
||||
modifier (-> (gpt/point (:x object) (:y object))
|
||||
(gpt/negate)
|
||||
(gmt/translate-matrix))
|
||||
|
||||
mod-ids (cons object-id (cph/get-children-ids objects object-id))
|
||||
updt-fn #(-> %1
|
||||
(assoc-in [%2 :modifiers :displacement] modifier)
|
||||
(update %2 gsh/transform-shape))]
|
||||
|
||||
(reduce updt-fn objects mod-ids)))
|
||||
|
||||
(defn get-object-bounds
|
||||
[objects object-id]
|
||||
(let [object (get objects object-id)
|
||||
padding (filters/calculate-padding object)
|
||||
bounds (-> (filters/get-filters-bounds object)
|
||||
(update :x - (:horizontal padding))
|
||||
(update :y - (:vertical padding))
|
||||
(update :width + (* 2 (:horizontal padding)))
|
||||
(update :height + (* 2 (:vertical padding))))]
|
||||
|
||||
(if (cph/group-shape? object)
|
||||
(if (:masked-group? object)
|
||||
(get-object-bounds objects (-> object :shapes first))
|
||||
(->> (:shapes object)
|
||||
(into [bounds] (map (partial get-object-bounds objects)))
|
||||
(gsh/join-rects)))
|
||||
bounds)))
|
||||
|
||||
(mf/defc page-svg
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [data width height thumbnails? embed? include-metadata?] :as props
|
||||
:or {embed? false include-metadata? false}}]
|
||||
[{:keys [data thumbnails? render-embed? include-metadata?] :as props
|
||||
:or {render-embed? false include-metadata? false}}]
|
||||
(let [objects (:objects data)
|
||||
shapes (cph/get-immediate-children objects)
|
||||
|
||||
root-children
|
||||
(->> shapes
|
||||
(remove cph/frame-shape?)
|
||||
(mapcat #(cph/get-children-with-self objects (:id %))))
|
||||
|
||||
vport (when (and (some? width) (some? height))
|
||||
{:width width :height height})
|
||||
|
||||
dim (calculate-dimensions data vport)
|
||||
dim (calculate-dimensions objects)
|
||||
vbox (format-viewbox dim)
|
||||
background-color (get-in data [:options :background] default-color)
|
||||
bgcolor (dm/get-in data [:options :background] default-color)
|
||||
|
||||
frame-wrapper
|
||||
(mf/use-memo
|
||||
|
@ -185,7 +225,7 @@
|
|||
(mf/deps objects)
|
||||
#(shape-wrapper-factory objects))]
|
||||
|
||||
[:& (mf/provider embed/context) {:value embed?}
|
||||
[:& (mf/provider embed/context) {:value render-embed?}
|
||||
[:& (mf/provider export/include-metadata-ctx) {:value include-metadata?}
|
||||
[:svg {:view-box vbox
|
||||
:version "1.1"
|
||||
|
@ -194,12 +234,17 @@
|
|||
:xmlns:penpot (when include-metadata? "https://penpot.app/xmlns")
|
||||
:style {:width "100%"
|
||||
:height "100%"
|
||||
:background background-color}}
|
||||
:background bgcolor}}
|
||||
|
||||
(when include-metadata?
|
||||
[:& export/export-page {:options (:options data)}])
|
||||
|
||||
[:& ff/fontfaces-style {:shapes root-children}]
|
||||
|
||||
(let [shapes (->> shapes
|
||||
(remove cph/frame-shape?)
|
||||
(mapcat #(cph/get-children-with-self objects (:id %))))]
|
||||
[:& ff/fontfaces-style {:shapes shapes}])
|
||||
|
||||
(for [item shapes]
|
||||
(let [frame? (= (:type item) :frame)]
|
||||
(cond
|
||||
|
@ -214,6 +259,10 @@
|
|||
[:& shape-wrapper {:shape item
|
||||
:key (:id item)}])))]]]))
|
||||
|
||||
|
||||
;; Component that serves for render frame thumbnails, mainly used in
|
||||
;; the viewer and handoff
|
||||
|
||||
(mf/defc frame-svg
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [objects frame zoom show-thumbnails?] :or {zoom 1} :as props}]
|
||||
|
@ -260,6 +309,10 @@
|
|||
[:> shape-container {:shape frame}
|
||||
[:& frame/frame-thumbnail {:shape frame}]]))]))
|
||||
|
||||
|
||||
;; Component for rendering a thumbnail of a single componenent. Mainly
|
||||
;; used to render thumbnails on assets panel.
|
||||
|
||||
(mf/defc component-svg
|
||||
{::mf/wrap [mf/memo #(mf/deferred % ts/idle-then-raf)]}
|
||||
[{:keys [objects group zoom] :or {zoom 1} :as props}]
|
||||
|
@ -304,81 +357,122 @@
|
|||
[:> shape-container {:shape group}
|
||||
[:& group-wrapper {:shape group :view-box vbox}]]]))
|
||||
|
||||
(mf/defc object-svg
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [objects object zoom render-texts? render-embed?]
|
||||
:or {zoom 1 render-embed? false}
|
||||
:as props}]
|
||||
(let [object (cond-> object
|
||||
(:hide-fill-on-export object)
|
||||
(assoc :fills []))
|
||||
|
||||
obj-id (:id object)
|
||||
x (* (:x object) zoom)
|
||||
y (* (:y object) zoom)
|
||||
width (* (:width object) zoom)
|
||||
height (* (:height object) zoom)
|
||||
|
||||
vbox (dm/str x " " y " " width " " height)
|
||||
|
||||
frame-wrapper
|
||||
(mf/with-memo [objects]
|
||||
(frame-wrapper-factory objects))
|
||||
|
||||
group-wrapper
|
||||
(mf/with-memo [objects]
|
||||
(group-wrapper-factory objects))
|
||||
|
||||
shape-wrapper
|
||||
(mf/with-memo [objects]
|
||||
(shape-wrapper-factory objects))
|
||||
|
||||
text-shapes (sequence (filter cph/text-shape?) (vals objects))
|
||||
render-texts? (and render-texts? (d/seek (comp nil? :position-data) text-shapes))]
|
||||
|
||||
[:& (mf/provider embed/context) {:value render-embed?}
|
||||
[:svg {:id (dm/str "screenshot-" obj-id)
|
||||
:view-box vbox
|
||||
:width width
|
||||
:height height
|
||||
:version "1.1"
|
||||
:xmlns "http://www.w3.org/2000/svg"
|
||||
:xmlnsXlink "http://www.w3.org/1999/xlink"
|
||||
;; Fix Chromium bug about color of html texts
|
||||
;; https://bugs.chromium.org/p/chromium/issues/detail?id=1244560#c5
|
||||
:style {:-webkit-print-color-adjust :exact}}
|
||||
|
||||
(let [shapes (cph/get-children objects obj-id)]
|
||||
[:& ff/fontfaces-style {:shapes shapes}])
|
||||
|
||||
(case (:type object)
|
||||
:frame [:& frame-wrapper {:shape object :view-box vbox}]
|
||||
:group [:> shape-container {:shape object}
|
||||
[:& group-wrapper {:shape object}]]
|
||||
[:& shape-wrapper {:shape object}])]
|
||||
|
||||
;; Auxiliary SVG for rendering text-shapes
|
||||
(when render-texts?
|
||||
(for [object text-shapes]
|
||||
[:& (mf/provider muc/text-plain-colors-ctx) {:value true}
|
||||
[:svg
|
||||
{:id (dm/str "screenshot-text-" (:id object))
|
||||
:view-box (dm/str "0 0 " (:width object) " " (:height object))
|
||||
:width (:width object)
|
||||
:height (:height object)
|
||||
:version "1.1"
|
||||
:xmlns "http://www.w3.org/2000/svg"
|
||||
:xmlnsXlink "http://www.w3.org/1999/xlink"}
|
||||
[:& shape-wrapper {:shape (assoc object :x 0 :y 0)}]]]))]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; SPRITES (DEBUG)
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(mf/defc component-symbol
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [id (obj/get props "id")
|
||||
data (obj/get props "data")
|
||||
name (:name data)
|
||||
path (:path data)
|
||||
objects (:objects data)
|
||||
root (get objects id)
|
||||
selrect (:selrect root)
|
||||
[{:keys [id data] :as props}]
|
||||
(let [name (:name data)
|
||||
objects (-> (:objects data)
|
||||
(adapt-objects-for-shape id))
|
||||
object (get objects id)
|
||||
selrect (:selrect object)
|
||||
|
||||
vbox
|
||||
(format-viewbox
|
||||
{:width (:width selrect)
|
||||
:height (:height selrect)})
|
||||
|
||||
modifier
|
||||
(mf/use-memo
|
||||
(mf/deps (:x root) (:y root))
|
||||
(fn []
|
||||
(-> (gpt/point (:x root) (:y root))
|
||||
(gpt/negate)
|
||||
(gmt/translate-matrix))))
|
||||
|
||||
objects
|
||||
(mf/use-memo
|
||||
(mf/deps modifier id objects)
|
||||
(fn []
|
||||
(let [modifier-ids (cons id (cph/get-children-ids objects id))
|
||||
update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier)]
|
||||
(reduce update-fn objects modifier-ids))))
|
||||
|
||||
root
|
||||
(mf/use-memo
|
||||
(mf/deps modifier root)
|
||||
(fn [] (assoc-in root [:modifiers :displacement] modifier)))
|
||||
|
||||
group-wrapper
|
||||
(mf/use-memo
|
||||
(mf/deps objects)
|
||||
(fn [] (group-wrapper-factory objects)))]
|
||||
|
||||
[:> "symbol" #js {:id (str id)
|
||||
:viewBox vbox
|
||||
"penpot:path" path}
|
||||
[:> "symbol" #js {:id (str id) :viewBox vbox}
|
||||
[:title name]
|
||||
[:> shape-container {:shape root}
|
||||
[:& group-wrapper {:shape root :view-box vbox}]]]))
|
||||
[:> shape-container {:shape object}
|
||||
[:& group-wrapper {:shape object :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?")
|
||||
render-embed? (obj/get props "render-embed?")
|
||||
include-metadata? (obj/get props "include-metadata?")]
|
||||
[:& (mf/provider embed/context) {:value embed?}
|
||||
[:& (mf/provider embed/context) {:value render-embed?}
|
||||
[:& (mf/provider export/include-metadata-ctx) {:value include-metadata?}
|
||||
[:svg {:version "1.1"
|
||||
:xmlns "http://www.w3.org/2000/svg"
|
||||
:xmlnsXlink "http://www.w3.org/1999/xlink"
|
||||
:xmlns:penpot (when include-metadata? "https://penpot.app/xmlns")
|
||||
:style {:width "100vw"
|
||||
:height "100vh"
|
||||
:display (when-not (some? children) "none")}}
|
||||
:style {: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}])]
|
||||
(for [[id data] (:components data)]
|
||||
[:& component-symbol {:id id :key (dm/str id) :data data}])]
|
||||
|
||||
children]]]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; RENDERING
|
||||
;; RENDER FOR DOWNLOAD (wrongly called exportation)
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn- get-image-data [shape]
|
||||
|
@ -426,7 +520,7 @@
|
|||
(->> (rx/of data)
|
||||
(rx/map
|
||||
(fn [data]
|
||||
(let [elem (mf/element page-svg #js {:data data :embed? true :include-metadata? true})]
|
||||
(let [elem (mf/element page-svg #js {:data data :render-embed? true :include-metadata? true})]
|
||||
(rds/renderToStaticMarkup elem)))))))
|
||||
|
||||
(defn render-components
|
||||
|
@ -445,5 +539,6 @@
|
|||
(->> (rx/of data)
|
||||
(rx/map
|
||||
(fn [data]
|
||||
(let [elem (mf/element components-sprite-svg #js {:data data :embed? true :include-metadata? true})]
|
||||
(let [elem (mf/element components-sprite-svg
|
||||
#js {:data data :render-embed? true :include-metadata? true})]
|
||||
(rds/renderToStaticMarkup elem))))))))
|
||||
|
|
|
@ -105,34 +105,22 @@
|
|||
(rx/map http/conditional-decode-transit)
|
||||
(rx/mapcat handle-response)))
|
||||
|
||||
(defn- send-export-command
|
||||
[& {:keys [cmd params blob?]}]
|
||||
(defn- send-export
|
||||
[{:keys [blob?] :as params}]
|
||||
(->> (http/send! {:method :post
|
||||
:uri (u/join base-uri "api/export")
|
||||
:body (http/transit-data (assoc params :cmd cmd))
|
||||
:body (http/transit-data (dissoc params :blob?))
|
||||
:credentials "include"
|
||||
:response-type (if blob? :blob :text)})
|
||||
(rx/map http/conditional-decode-transit)
|
||||
(rx/mapcat handle-response)))
|
||||
|
||||
(defmethod query :export-shapes-simple
|
||||
(defmethod query :exporter
|
||||
[_ params]
|
||||
(let [params (merge {:wait true} params)]
|
||||
(->> (rx/of params)
|
||||
(rx/mapcat #(send-export-command :cmd :export-shapes :params % :blob? false))
|
||||
(rx/mapcat #(send-export-command :cmd :get-resource :params % :blob? true)))))
|
||||
|
||||
(defmethod query :export-shapes-multiple
|
||||
[_ params]
|
||||
(send-export-command :cmd :export-shapes :params params :blob? false))
|
||||
|
||||
(defmethod query :export-frames-multiple
|
||||
[_ params]
|
||||
(send-export-command :cmd :export-frames :params (assoc params :uri (str base-uri)) :blob? false))
|
||||
|
||||
(defmethod query :download-export-resource
|
||||
[_ id]
|
||||
(send-export-command :cmd :get-resource :params {:id id} :blob? true))
|
||||
(let [default {:wait false
|
||||
:blob? false
|
||||
:uri (str base-uri)}]
|
||||
(send-export (merge default params))))
|
||||
|
||||
(derive :upload-file-media-object ::multipart-upload)
|
||||
(derive :update-profile-photo ::multipart-upload)
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
[app.main.ui.onboarding]
|
||||
[app.main.ui.onboarding.questions]
|
||||
[app.main.ui.releases]
|
||||
[app.main.ui.render :as render]
|
||||
[app.main.ui.settings :as settings]
|
||||
[app.main.ui.static :as static]
|
||||
[app.main.ui.viewer :as viewer]
|
||||
|
@ -110,15 +109,6 @@
|
|||
:index index
|
||||
:share-id share-id}]))
|
||||
|
||||
;; TODO: maybe move to `app.render` entrypoint (handled by render.html)
|
||||
: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)
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
[rumext.alpha :as mf]))
|
||||
|
||||
(mf/defc export-multiple-dialog
|
||||
[{:keys [exports filename title query-name no-selection]}]
|
||||
[{:keys [exports title cmd no-selection]}]
|
||||
(let [lstate (mf/deref refs/export)
|
||||
in-progress? (:in-progress lstate)
|
||||
|
||||
|
@ -33,7 +33,10 @@
|
|||
all-checked? (every? :enabled all-exports)
|
||||
all-unchecked? (every? (complement :enabled) all-exports)
|
||||
|
||||
enabled-exports (into [] (filter :enabled) all-exports)
|
||||
enabled-exports (into []
|
||||
(comp (filter :enabled)
|
||||
(map #(dissoc % :shape :enabled)))
|
||||
all-exports)
|
||||
|
||||
cancel-fn
|
||||
(fn [event]
|
||||
|
@ -45,9 +48,8 @@
|
|||
(dom/prevent-default event)
|
||||
(st/emit! (modal/hide)
|
||||
(de/request-multiple-export
|
||||
{:filename filename
|
||||
:exports enabled-exports
|
||||
:query-name query-name})))
|
||||
{:exports enabled-exports
|
||||
:cmd cmd})))
|
||||
|
||||
on-toggle-enabled
|
||||
(fn [index]
|
||||
|
@ -145,25 +147,23 @@
|
|||
(mf/defc export-shapes-dialog
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :export-shapes}
|
||||
[{:keys [exports filename]}]
|
||||
[{:keys [exports]}]
|
||||
(let [title (tr "dashboard.export-shapes.title")]
|
||||
[:& export-multiple-dialog
|
||||
{:exports exports
|
||||
:filename filename
|
||||
:title title
|
||||
:query-name :export-shapes-multiple
|
||||
:cmd :export-shapes
|
||||
:no-selection shapes-no-selection}]))
|
||||
|
||||
(mf/defc export-frames
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :export-frames}
|
||||
[{:keys [exports filename]}]
|
||||
[{:keys [exports]}]
|
||||
(let [title (tr "dashboard.export-frames.title")]
|
||||
[:& export-multiple-dialog
|
||||
{:exports exports
|
||||
:filename filename
|
||||
:title title
|
||||
:query-name :export-frames-multiple}]))
|
||||
:cmd :export-frames}]))
|
||||
|
||||
(mf/defc export-progress-widget
|
||||
{::mf/wrap [mf/memo]}
|
||||
|
|
|
@ -1,203 +0,0 @@
|
|||
;; 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) UXBOX Labs SL
|
||||
|
||||
(ns app.main.ui.render
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.math :as mth]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.fonts :as df]
|
||||
[app.main.render :as render]
|
||||
[app.main.repo :as repo]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.context :as muc]
|
||||
[app.main.ui.shapes.embed :as embed]
|
||||
[app.main.ui.shapes.filters :as filters]
|
||||
[app.main.ui.shapes.shape :refer [shape-container]]
|
||||
[app.main.ui.shapes.text.fontfaces :as ff]
|
||||
[app.util.dom :as dom]
|
||||
[beicon.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
(defn calc-bounds
|
||||
[object objects]
|
||||
(let [xf-get-bounds (comp (map #(get objects %)) (map #(calc-bounds % objects)))
|
||||
padding (filters/calculate-padding object)
|
||||
obj-bounds (-> (filters/get-filters-bounds object)
|
||||
(update :x - (:horizontal padding))
|
||||
(update :y - (:vertical padding))
|
||||
(update :width + (* 2 (:horizontal padding)))
|
||||
(update :height + (* 2 (:vertical padding))))]
|
||||
|
||||
(cond
|
||||
(and (= :group (:type object))
|
||||
(:masked-group? object))
|
||||
(calc-bounds (get objects (first (:shapes object))) objects)
|
||||
|
||||
(= :group (:type object))
|
||||
(->> (:shapes object)
|
||||
(into [obj-bounds] xf-get-bounds)
|
||||
(gsh/join-rects))
|
||||
|
||||
:else
|
||||
obj-bounds)))
|
||||
|
||||
(mf/defc object-svg
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [objects object-id zoom render-texts? embed?]
|
||||
:or {zoom 1 embed? false}
|
||||
:as props}]
|
||||
(let [object (get objects object-id)
|
||||
frame-id (if (= :frame (:type object))
|
||||
(:id object)
|
||||
(:frame-id object))
|
||||
|
||||
modifier (-> (gpt/point (:x object) (:y object))
|
||||
(gpt/negate)
|
||||
(gmt/translate-matrix))
|
||||
|
||||
mod-ids (cons frame-id (cph/get-children-ids objects frame-id))
|
||||
updt-fn #(-> %1
|
||||
(assoc-in [%2 :modifiers :displacement] modifier)
|
||||
(update %2 gsh/transform-shape))
|
||||
|
||||
objects (reduce updt-fn objects mod-ids)
|
||||
object (get objects object-id)
|
||||
|
||||
object (cond-> object
|
||||
(:hide-fill-on-export object)
|
||||
(assoc :fills []))
|
||||
|
||||
all-children (cph/get-children objects object-id)
|
||||
|
||||
{:keys [x y width height] :as bs} (calc-bounds object objects)
|
||||
[_ _ width height :as coords] (->> [x y width height] (map #(* % zoom)))
|
||||
|
||||
vbox (str/join " " coords)
|
||||
|
||||
frame-wrapper
|
||||
(mf/with-memo [objects]
|
||||
(render/frame-wrapper-factory objects))
|
||||
|
||||
group-wrapper
|
||||
(mf/with-memo [objects]
|
||||
(render/group-wrapper-factory objects))
|
||||
|
||||
shape-wrapper
|
||||
(mf/with-memo [objects]
|
||||
(render/shape-wrapper-factory objects))
|
||||
|
||||
is-text? (fn [shape] (= :text (:type shape)))
|
||||
|
||||
text-shapes (sequence (comp (map second) (filter is-text?)) objects)
|
||||
|
||||
render-texts? (and render-texts? (d/seek (comp nil? :position-data) text-shapes))]
|
||||
|
||||
(mf/with-effect [width height]
|
||||
(dom/set-page-style!
|
||||
{:size (dm/str (mth/ceil width) "px "
|
||||
(mth/ceil height) "px")}))
|
||||
|
||||
[:& (mf/provider embed/context) {:value embed?}
|
||||
[:svg {:id "screenshot"
|
||||
:view-box vbox
|
||||
:width width
|
||||
:height height
|
||||
:version "1.1"
|
||||
:xmlns "http://www.w3.org/2000/svg"
|
||||
:xmlnsXlink "http://www.w3.org/1999/xlink"
|
||||
;; Fix Chromium bug about color of html texts
|
||||
;; https://bugs.chromium.org/p/chromium/issues/detail?id=1244560#c5
|
||||
:style {:-webkit-print-color-adjust :exact}}
|
||||
|
||||
[:& ff/fontfaces-style {:shapes all-children}]
|
||||
|
||||
(case (:type object)
|
||||
:frame [:& frame-wrapper {:shape object :view-box vbox}]
|
||||
:group [:> shape-container {:shape object}
|
||||
[:& group-wrapper {:shape object}]]
|
||||
[:& shape-wrapper {:shape object}])]
|
||||
|
||||
;; Auxiliary SVG for rendering text-shapes
|
||||
(when render-texts?
|
||||
(for [object text-shapes]
|
||||
[:& (mf/provider muc/text-plain-colors-ctx) {:value true}
|
||||
[:svg {:id (str "screenshot-text-" (:id object))
|
||||
:view-box (str "0 0 " (:width object) " " (:height object))
|
||||
:width (:width object)
|
||||
:height (:height object)
|
||||
:version "1.1"
|
||||
:xmlns "http://www.w3.org/2000/svg"
|
||||
:xmlnsXlink "http://www.w3.org/1999/xlink"}
|
||||
[:& shape-wrapper {:shape (assoc object :x 0 :y 0)}]]]))]))
|
||||
|
||||
(defn- adapt-root-frame
|
||||
[objects object-id]
|
||||
(if (uuid/zero? object-id)
|
||||
(let [object (get objects object-id)
|
||||
shapes (cph/get-immediate-children objects)
|
||||
srect (gsh/selection-rect shapes)
|
||||
object (merge object (select-keys srect [:x :y :width :height]))
|
||||
object (gsh/transform-shape object)
|
||||
object (assoc object :fill-color "#f0f0f0")]
|
||||
(assoc objects (:id object) object))
|
||||
objects))
|
||||
|
||||
(mf/defc render-object
|
||||
[{:keys [file-id page-id object-id render-texts? embed?] :as props}]
|
||||
(let [objects (mf/use-state nil)]
|
||||
|
||||
(mf/with-effect [file-id page-id object-id]
|
||||
(->> (rx/zip
|
||||
(repo/query! :font-variants {:file-id file-id})
|
||||
(repo/query! :trimmed-file {:id file-id :page-id page-id :object-id object-id}))
|
||||
(rx/subs
|
||||
(fn [[fonts {:keys [data]}]]
|
||||
(when (seq fonts)
|
||||
(st/emit! (df/fonts-fetched fonts)))
|
||||
(let [objs (get-in data [:pages-index page-id :objects])
|
||||
objs (adapt-root-frame objs object-id)]
|
||||
(reset! objects objs)))))
|
||||
(constantly nil))
|
||||
|
||||
(when @objects
|
||||
[:& object-svg {:objects @objects
|
||||
:object-id object-id
|
||||
:embed? embed?
|
||||
:render-texts? render-texts?
|
||||
:zoom 1}])))
|
||||
|
||||
(mf/defc render-sprite
|
||||
[{:keys [file-id component-id] :as props}]
|
||||
(let [file (mf/use-state nil)]
|
||||
|
||||
(mf/with-effect [file-id]
|
||||
(->> (repo/query! :file {:id file-id})
|
||||
(rx/subs
|
||||
(fn [result]
|
||||
(reset! file result))))
|
||||
(constantly nil))
|
||||
|
||||
(when @file
|
||||
[:*
|
||||
[:& render/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)]]))])])))
|
||||
|
|
@ -61,7 +61,6 @@
|
|||
["/debug/icons-preview" :debug-icons-preview])
|
||||
|
||||
;; Used for export
|
||||
["/render-object/:file-id/:page-id/:object-id" :render-object]
|
||||
["/render-sprite/:file-id" :render-sprite]
|
||||
|
||||
["/dashboard/team/:team-id"
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
[app.util.code-gen :as cg]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
(defn has-image? [shape]
|
||||
|
@ -34,12 +35,10 @@
|
|||
[:div.attributes-value (-> shape :metadata :height) "px"]
|
||||
[:& copy-button {:data (cg/generate-css-props shape :height)}]]
|
||||
|
||||
(let [mtype (-> shape :metadata :mtype)
|
||||
name (:name shape)
|
||||
(let [mtype (-> shape :metadata :mtype)
|
||||
name (:name shape)
|
||||
extension (dom/mtype->extension mtype)]
|
||||
[:a.download-button {:target "_blank"
|
||||
:download (if extension
|
||||
(str name "." extension)
|
||||
name)
|
||||
:download (cond-> name extension (str/concat extension))
|
||||
:href (cfg/resolve-file-media (-> shape :metadata))}
|
||||
(tr "handoff.attributes.image.download")])])))
|
||||
|
|
|
@ -390,34 +390,3 @@
|
|||
:bool [:> bool-container {:shape shape :frame frame :objects objects}]
|
||||
:svg-raw [:> svg-raw-container {:shape shape :frame frame :objects objects}])))))))
|
||||
|
||||
(mf/defc frame-svg
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [objects frame zoom] :or {zoom 1} :as props}]
|
||||
(let [modifier (-> (gpt/point (:x frame) (:y frame))
|
||||
(gpt/negate)
|
||||
(gmt/translate-matrix))
|
||||
|
||||
update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier)
|
||||
|
||||
frame-id (:id frame)
|
||||
modifier-ids (into [frame-id] (cph/get-children-ids objects frame-id))
|
||||
objects (reduce update-fn objects modifier-ids)
|
||||
frame (assoc-in frame [:modifiers :displacement] modifier)
|
||||
width (* (:width frame) zoom)
|
||||
height (* (:height frame) zoom)
|
||||
|
||||
vbox (str "0 0 " (:width frame 0)
|
||||
" " (:height frame 0))
|
||||
wrapper (mf/use-memo
|
||||
(mf/deps objects)
|
||||
#(frame-container-factory objects))]
|
||||
|
||||
[:svg {:view-box vbox
|
||||
:width width
|
||||
:height height
|
||||
:version "1.1"
|
||||
:xmlnsXlink "http://www.w3.org/1999/xlink"
|
||||
:xmlns "http://www.w3.org/2000/svg"}
|
||||
[:& wrapper {:shape frame
|
||||
:view-box vbox}]]))
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
state (mf/deref refs/export)
|
||||
in-progress? (:in-progress state)
|
||||
|
||||
filename (when (seqable? exports)
|
||||
sname (when (seqable? exports)
|
||||
(let [shapes (wsh/lookup-shapes @st/state ids)
|
||||
sname (-> shapes first :name)
|
||||
suffix (-> exports first :suffix)]
|
||||
|
@ -56,13 +56,13 @@
|
|||
;; separatelly by the export-modal.
|
||||
(let [defaults {:page-id page-id
|
||||
:file-id file-id
|
||||
:name filename
|
||||
:name sname
|
||||
:object-id (first ids)}
|
||||
exports (mapv #(merge % defaults) exports)]
|
||||
(if (= 1 (count exports))
|
||||
(let [export (first exports)]
|
||||
(st/emit! (de/request-simple-export {:export export :filename (:name export)})))
|
||||
(st/emit! (de/request-multiple-export {:exports exports :filename filename})))))))
|
||||
(st/emit! (de/request-simple-export {:export export})))
|
||||
(st/emit! (de/request-multiple-export {:exports exports})))))))
|
||||
|
||||
;; TODO: maybe move to specific events for avoid to have this logic here?
|
||||
add-export
|
||||
|
|
|
@ -7,27 +7,38 @@
|
|||
(ns app.render
|
||||
"The main entry point for UI part needed by the exporter."
|
||||
(:require
|
||||
[app.common.logging :as log]
|
||||
[app.common.logging :as l]
|
||||
[app.common.math :as mth]
|
||||
[app.common.spec :as us]
|
||||
[app.common.uri :as u]
|
||||
[app.config :as cf]
|
||||
[app.main.ui.render :as render]
|
||||
[app.main.data.fonts :as df]
|
||||
[app.main.render :as render]
|
||||
[app.main.repo :as repo]
|
||||
[app.main.store :as st]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.globals :as glob]
|
||||
[beicon.core :as rx]
|
||||
[clojure.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[garden.core :refer [css]]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
(log/initialize!)
|
||||
(log/set-level! :root :warn)
|
||||
(log/set-level! :app :info)
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; SETUP
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(declare reinit)
|
||||
(l/initialize!)
|
||||
(l/set-level! :root :warn)
|
||||
(l/set-level! :app :info)
|
||||
|
||||
(declare ^:private render-object)
|
||||
(declare ^:private render-single-object)
|
||||
(declare ^:private render-components)
|
||||
(declare ^:private render-objects)
|
||||
|
||||
(log/info :hint "Welcome to penpot (Export)"
|
||||
:version (:full @cf/version)
|
||||
:public-uri (str cf/public-uri))
|
||||
(l/info :hint "Welcome to penpot (Export)"
|
||||
:version (:full @cf/version)
|
||||
:public-uri (str cf/public-uri))
|
||||
|
||||
(defn- parse-params
|
||||
[loc]
|
||||
|
@ -38,7 +49,8 @@
|
|||
[]
|
||||
(when-let [params (parse-params glob/location)]
|
||||
(when-let [component (case (:route params)
|
||||
"render-object" (render-object params)
|
||||
"objects" (render-objects params)
|
||||
"components" (render-components params)
|
||||
nil)]
|
||||
(mf/mount component (dom/get-element "app")))))
|
||||
|
||||
|
@ -55,23 +67,225 @@
|
|||
[]
|
||||
(reinit))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; COMPONENTS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;; ---- SINGLE OBJECT
|
||||
|
||||
(defn use-resource
|
||||
"A general purpose hook for retrieve or subscribe to remote changes
|
||||
using the reactive-streams mechanism mechanism.
|
||||
|
||||
It receives a function to execute for retrieve the stream that will
|
||||
be used for creating the subscription. The function should be
|
||||
stable, so is the responsability of the user of this hook to
|
||||
properly memoize it.
|
||||
|
||||
TODO: this should be placed in some generic hooks namespace but his
|
||||
right now is pending of refactor and it will be done later."
|
||||
[f]
|
||||
(let [[state ^js update-state!] (mf/useState {:loaded? false})]
|
||||
(mf/with-effect [f]
|
||||
(update-state! (fn [prev] (assoc prev :refreshing? true)))
|
||||
(let [on-value (fn [data]
|
||||
(update-state! #(-> %
|
||||
(assoc :refreshing? false)
|
||||
(assoc :loaded? true)
|
||||
(merge data))))
|
||||
subs (rx/subscribe (f) on-value)]
|
||||
#(rx/dispose! subs)))
|
||||
state))
|
||||
|
||||
(mf/defc object-svg
|
||||
[{:keys [page-id file-id object-id render-embed? render-texts?]}]
|
||||
(let [fetch-state (mf/use-fn
|
||||
(mf/deps file-id page-id object-id)
|
||||
(fn []
|
||||
(->> (rx/zip
|
||||
(repo/query! :font-variants {:file-id file-id})
|
||||
(repo/query! :page {:file-id file-id
|
||||
:page-id page-id
|
||||
:object-id object-id
|
||||
:prune-thumbnails true}))
|
||||
(rx/tap (fn [[fonts]]
|
||||
(when (seq fonts)
|
||||
(st/emit! (df/fonts-fetched fonts)))))
|
||||
(rx/map (comp :objects second))
|
||||
(rx/map (fn [objects]
|
||||
(let [objects (render/adapt-objects-for-shape objects object-id)
|
||||
bounds (render/get-object-bounds objects object-id)
|
||||
object (get objects object-id)]
|
||||
{:objects objects
|
||||
:object (merge object bounds)}))))))
|
||||
|
||||
{:keys [objects object]} (use-resource fetch-state)]
|
||||
|
||||
;; Set the globa CSS to assign the page size, needed for PDF
|
||||
;; exportation process.
|
||||
(mf/with-effect [object]
|
||||
(when object
|
||||
(dom/set-page-style!
|
||||
{:size (str/concat
|
||||
(mth/ceil (:width object)) "px "
|
||||
(mth/ceil (:height object)) "px")})))
|
||||
|
||||
(when objects
|
||||
[:& render/object-svg
|
||||
{:objects objects
|
||||
:object object
|
||||
:render-embed? render-embed?
|
||||
:render-texts? render-texts?
|
||||
:zoom 1}])))
|
||||
|
||||
(mf/defc objects-svg
|
||||
[{:keys [page-id file-id object-ids render-embed? render-texts?]}]
|
||||
(let [fetch-state (mf/use-fn
|
||||
(mf/deps file-id page-id)
|
||||
(fn []
|
||||
(->> (rx/zip
|
||||
(repo/query! :font-variants {:file-id file-id})
|
||||
(repo/query! :page {:file-id file-id
|
||||
:page-id page-id
|
||||
:prune-thumbnails true}))
|
||||
(rx/tap (fn [[fonts]]
|
||||
(when (seq fonts)
|
||||
(st/emit! (df/fonts-fetched fonts)))))
|
||||
(rx/map (comp :objects second)))))
|
||||
|
||||
objects (use-resource fetch-state)]
|
||||
|
||||
(when objects
|
||||
(for [object-id object-ids]
|
||||
(let [objects (render/adapt-objects-for-shape objects object-id)
|
||||
bounds (render/get-object-bounds objects object-id)
|
||||
object (merge (get objects object-id) bounds)]
|
||||
[:& render/object-svg
|
||||
{:objects objects
|
||||
:key (str object-id)
|
||||
:object object
|
||||
:render-embed? render-embed?
|
||||
:render-texts? render-texts?
|
||||
:zoom 1}])))))
|
||||
|
||||
(s/def ::page-id ::us/uuid)
|
||||
(s/def ::file-id ::us/uuid)
|
||||
(s/def ::object-id ::us/uuid)
|
||||
(s/def ::object-id
|
||||
(s/or :single ::us/uuid
|
||||
:multiple (s/coll-of ::us/uuid)))
|
||||
(s/def ::render-text ::us/boolean)
|
||||
(s/def ::embed ::us/boolean)
|
||||
|
||||
(s/def ::render-object-params
|
||||
(s/def ::render-objects
|
||||
(s/keys :req-un [::file-id ::page-id ::object-id]
|
||||
:opt-un [::render-text ::embed]))
|
||||
:opt-un [::render-text ::render-embed]))
|
||||
|
||||
(defn- render-object
|
||||
(defn- render-objects
|
||||
[params]
|
||||
(let [{:keys [page-id file-id object-id render-texts embed]} (us/conform ::render-object-params params)]
|
||||
(let [{:keys [file-id
|
||||
page-id
|
||||
render-embed
|
||||
render-texts]
|
||||
:as params}
|
||||
(us/conform ::render-objects params)
|
||||
|
||||
[type object-id] (:object-id params)]
|
||||
|
||||
(case type
|
||||
:single
|
||||
(mf/html
|
||||
[:& object-svg
|
||||
{:file-id file-id
|
||||
:page-id page-id
|
||||
:object-id object-id
|
||||
:render-embed? render-embed
|
||||
:render-texts? render-texts}])
|
||||
|
||||
:multiple
|
||||
(mf/html
|
||||
[:& objects-svg
|
||||
{:file-id file-id
|
||||
:page-id page-id
|
||||
:object-ids (into #{} object-id)
|
||||
:render-embed? render-embed
|
||||
:render-texts? render-texts}]))))
|
||||
|
||||
;; ---- COMPONENTS SPRITE
|
||||
|
||||
(mf/defc components-sprite-svg
|
||||
[{:keys [file-id embed] :as props}]
|
||||
(let [fetch (mf/use-fn
|
||||
(mf/deps file-id)
|
||||
(fn [] (repo/query! :file {:id file-id})))
|
||||
|
||||
file (use-resource fetch)
|
||||
state (mf/use-state nil)]
|
||||
|
||||
(when file
|
||||
[:*
|
||||
[:style
|
||||
(css [[:body
|
||||
{:margin 0
|
||||
:overflow "hidden"
|
||||
:width "100vw"
|
||||
:height "100vh"}]
|
||||
|
||||
[:main
|
||||
{:overflow "auto"
|
||||
:display "flex"
|
||||
:justify-content "center"
|
||||
:align-items "center"
|
||||
:height "calc(100vh - 200px)"}
|
||||
[:svg {:width "50%"
|
||||
:height "50%"}]]
|
||||
[:.nav
|
||||
{:display "flex"
|
||||
:margin 0
|
||||
:padding "10px"
|
||||
:flex-direction "column"
|
||||
:flex-wrap "wrap"
|
||||
:height "200px"
|
||||
:list-style "none"
|
||||
:overflow-x "scroll"
|
||||
:border-bottom "1px dotted #e6e6e6"}
|
||||
[:a {:cursor :pointer
|
||||
:text-overflow "ellipsis"
|
||||
:white-space "nowrap"
|
||||
:overflow "hidden"
|
||||
:text-decoration "underline"}]
|
||||
[:li {:display "flex"
|
||||
:width "150px"
|
||||
:padding "5px"
|
||||
:border "0px solid black"}]]])]
|
||||
|
||||
[:ul.nav
|
||||
(for [[id data] (get-in file [:data :components])]
|
||||
(let [on-click (fn [event]
|
||||
(dom/prevent-default event)
|
||||
(swap! state assoc :component-id id))]
|
||||
[:li {:key (str id)}
|
||||
[:a {:on-click on-click} (:name data)]]))]
|
||||
|
||||
[:main
|
||||
[:& render/components-sprite-svg
|
||||
{:data (:data file)
|
||||
:embed embed}
|
||||
|
||||
(when-let [component-id (:component-id @state)]
|
||||
[:use {:x 0 :y 0 :xlinkHref (str "#" component-id)}])]]
|
||||
|
||||
])))
|
||||
|
||||
(s/def ::component-id ::us/uuid)
|
||||
(s/def ::render-components
|
||||
(s/keys :req-un [::file-id]
|
||||
:opt-un [::embed ::component-id]))
|
||||
|
||||
(defn render-components
|
||||
[params]
|
||||
(let [{:keys [file-id component-id embed]} (us/conform ::render-components params)]
|
||||
(mf/html
|
||||
[:& render/render-object
|
||||
[:& components-sprite-svg
|
||||
{:file-id file-id
|
||||
:page-id page-id
|
||||
:object-id object-id
|
||||
:embed? embed
|
||||
:render-texts? render-texts}])))
|
||||
:component-id component-id
|
||||
:embed embed}])))
|
||||
|
|
|
@ -403,16 +403,16 @@
|
|||
(defn mtype->extension [mtype]
|
||||
;; https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types
|
||||
(case mtype
|
||||
"image/apng" "apng"
|
||||
"image/avif" "avif"
|
||||
"image/gif" "gif"
|
||||
"image/jpeg" "jpg"
|
||||
"image/png" "png"
|
||||
"image/svg+xml" "svg"
|
||||
"image/webp" "webp"
|
||||
"application/zip" "zip"
|
||||
"application/penpot" "penpot"
|
||||
"application/pdf" "pdf"
|
||||
"image/apng" ".apng"
|
||||
"image/avif" ".avif"
|
||||
"image/gif" ".gif"
|
||||
"image/jpeg" ".jpg"
|
||||
"image/png" ".png"
|
||||
"image/svg+xml" ".svg"
|
||||
"image/webp" ".webp"
|
||||
"application/zip" ".zip"
|
||||
"application/penpot" ".penpot"
|
||||
"application/pdf" ".pdf"
|
||||
nil))
|
||||
|
||||
(defn set-attribute! [^js node ^string attr value]
|
||||
|
@ -464,11 +464,11 @@
|
|||
|
||||
(defn trigger-download-uri
|
||||
[filename mtype uri]
|
||||
(let [link (create-element "a")
|
||||
(let [link (create-element "a")
|
||||
extension (mtype->extension mtype)
|
||||
filename (if extension
|
||||
(str filename "." extension)
|
||||
filename)]
|
||||
filename (if (and extension (not (str/ends-with? filename extension)))
|
||||
(str/concat filename "." extension)
|
||||
filename)]
|
||||
(obj/set! link "href" uri)
|
||||
(obj/set! link "download" filename)
|
||||
(obj/set! (.-style ^js link) "display" "none")
|
||||
|
|
|
@ -135,7 +135,7 @@
|
|||
(rx/map #(assoc % :file-id file-id))
|
||||
(rx/flat-map
|
||||
(fn [media]
|
||||
(let [file-path (str file-id "/media/" (:id media) "." (dom/mtype->extension (:mtype media)))]
|
||||
(let [file-path (str/concat file-id "/media/" (:id media) (dom/mtype->extension (:mtype media)))]
|
||||
(->> (http/send!
|
||||
{:uri (cfg/resolve-file-media media)
|
||||
:response-type :blob
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
: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))
|
||||
(str/concat file-id "/media/" id ext))
|
||||
:components (str file-id "/components.svg"))
|
||||
|
||||
parse-svg? (and (not= type :media) (str/ends-with? path "svg"))
|
||||
|
|
|
@ -56,15 +56,16 @@
|
|||
:uri (u/join (cfg/get-public-uri) path)
|
||||
:credentials "include"
|
||||
:query params}]
|
||||
|
||||
(->> (http/send! request)
|
||||
(rx/map http/conditional-decode-transit)
|
||||
(rx/mapcat handle-response))))
|
||||
|
||||
(defn- render-thumbnail
|
||||
[{:keys [data file-id revn] :as params}]
|
||||
(let [elem (if-let [frame (:thumbnail-frame data)]
|
||||
(mf/element render/frame-svg #js {:objects (:objects data) :frame frame})
|
||||
(mf/element render/page-svg #js {:data data :width "290" :height "150" :thumbnails? true}))]
|
||||
[{:keys [page file-id revn] :as params}]
|
||||
(let [elem (if-let [frame (:thumbnail-frame page)]
|
||||
(mf/element render/frame-svg #js {:objects (:objects page) :frame frame})
|
||||
(mf/element render/page-svg #js {:data page :thumbnails? true}))]
|
||||
{:data (rds/renderToStaticMarkup elem)
|
||||
:fonts @fonts/loaded
|
||||
:file-id file-id
|
||||
|
@ -81,6 +82,7 @@
|
|||
:uri (u/join (cfg/get-public-uri) path)
|
||||
:credentials "include"
|
||||
:body (http/transit-data params)}]
|
||||
|
||||
(->> (http/send! request)
|
||||
(rx/map http/conditional-decode-transit)
|
||||
(rx/mapcat handle-response)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue