0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-03-28 15:41:25 -05:00

🐛 Fix features related issues with render entrypoint (exporter)

This commit is contained in:
Andrey Antukh 2023-11-13 18:09:25 +01:00
parent 0a656e9e62
commit 26d3d7f1a8
8 changed files with 264 additions and 227 deletions

View file

@ -443,10 +443,17 @@
"Given the page data and the object-id returns the page data with all
other not needed objects removed from the `:objects` data
structure."
[{:keys [objects] :as page} object-id]
(let [objects (->> (cph/get-children-with-self objects object-id)
(filter some?))]
(assoc page :objects (d/index-by :id objects))))
[page id-or-ids]
(update page :objects (fn [objects]
(reduce (fn [result object-id]
(->> (cph/get-children-with-self objects object-id)
(filter some?)
(d/index-by :id)
(merge result)))
{}
(if (uuid? id-or-ids)
[id-or-ids]
id-or-ids)))))
(defn- prune-thumbnails
"Given the page data, removes the `:thumbnail` prop from all
@ -480,7 +487,7 @@
page)))]
(cond-> (prune-thumbnails page)
(uuid? object-id)
(some? object-id)
(prune-objects object-id))))
(def schema:get-page
@ -488,7 +495,7 @@
[:file-id ::sm/uuid]
[:page-id {:optional true} ::sm/uuid]
[:share-id {:optional true} ::sm/uuid]
[:object-id {:optional true} ::sm/uuid]
[:object-id {:optional true} [:or ::sm/uuid ::sm/coll-of-uuid]]
[:features {:optional true} ::cfeat/features]])
(sv/defmethod ::get-page
@ -500,7 +507,8 @@
If you specify the object-id, the page-id parameter becomes
mandatory.
Mainly used for rendering purposes."
Mainly used for rendering purposes on the exporter. It does not
accepts client features."
{::doc/added "1.17"
::sm/params schema:get-page}
[cfg {:keys [::rpc/profile-id file-id share-id] :as params}]

View file

@ -138,14 +138,20 @@
(declare get-team)
(def ^:private schema:get-team
[:map {:title "get-team"}
[:id ::sm/uuid]])
[:and
[:map {:title "get-team"}
[:id {:optional true} ::sm/uuid]
[:file-id {:optional true} ::sm/uuid]]
[:fn (fn [params]
(or (contains? params :id)
(contains? params :file-id)))]])
(sv/defmethod ::get-team
{::doc/added "1.17"
::sm/params schema:get-team}
[cfg {:keys [::rpc/profile-id id]}]
(db/tx-run! cfg #(get-team % :profile-id profile-id :team-id id)))
[cfg {:keys [::rpc/profile-id id file-id]}]
(db/tx-run! cfg #(get-team % :profile-id profile-id :team-id id :file-id file-id)))
(defn get-team
[conn & {:keys [profile-id team-id project-id file-id] :as params}]

View file

@ -143,6 +143,11 @@
([s options transformer]
(m/decoder s options transformer)))
(defn lazy-decoder
[s transformer]
(let [vfn (delay (decoder s transformer))]
(fn [v] (@vfn v))))
(defn humanize-data
[{:keys [schema errors value]} & {:keys [length level]}]
(let [errors (mapv #(update % :schema form) errors)]

View file

@ -326,8 +326,8 @@
(rx/map deref)
(rx/map bundle-fetched)))
(rx/take-until
(rx/filter (ptk/type? ::fetch-bundle) stream))))))
(rx/take-until
(rx/filter (ptk/type? ::fetch-bundle) stream))))))
(defn initialize-file
[project-id file-id]

View file

@ -43,7 +43,6 @@
[app.main.ui.shapes.text :as text]
[app.main.ui.shapes.text.fontfaces :as ff]
[app.util.http :as http]
[app.util.object :as obj]
[app.util.strings :as ust]
[app.util.thumbnails :as th]
[app.util.timers :as ts]
@ -83,11 +82,11 @@
(let [shape-wrapper (shape-wrapper-factory objects)
frame-shape (frame/frame-shape shape-wrapper)]
(mf/fnc frame-wrapper
[{:keys [shape] :as props}]
(let [render-thumbnails? (mf/use-ctx muc/render-thumbnails)
childs (mapv #(get objects %) (:shapes shape))]
(if (and render-thumbnails? (some? (:thumbnail shape)))
{::mf/wrap-props false}
[{:keys [shape]}]
(let [thumbnails? (mf/use-ctx muc/render-thumbnails)
childs (mapv (d/getf objects) (:shapes shape))]
(if (and thumbnails? (some? (:thumbnail shape)))
[:& frame/frame-thumbnail {:shape shape :bounds (:children-bounds shape)}]
[:& frame-shape {:shape shape :childs childs}])))))
@ -193,8 +192,8 @@
(mf/defc page-svg
{::mf/wrap [mf/memo]}
[{:keys [data thumbnails? render-embed? include-metadata?] :as props
:or {render-embed? false include-metadata? false}}]
[{:keys [data use-thumbnails embed include-metadata] :as props
:or {embed false include-metadata false}}]
(let [objects (:objects data)
shapes (cph/get-immediate-children objects)
dim (calculate-dimensions objects)
@ -206,20 +205,20 @@
(mf/deps objects)
#(shape-wrapper-factory objects))]
[:& (mf/provider muc/render-thumbnails) {:value thumbnails?}
[:& (mf/provider embed/context) {:value render-embed?}
[:& (mf/provider export/include-metadata-ctx) {:value include-metadata?}
[:& (mf/provider muc/render-thumbnails) {:value use-thumbnails}
[:& (mf/provider embed/context) {:value embed}
[:& (mf/provider export/include-metadata-ctx) {:value include-metadata}
[:svg {:view-box vbox
: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")
:xmlns:penpot (when include-metadata "https://penpot.app/xmlns")
:style {:width "100%"
:height "100%"
:background bgcolor}
:fill "none"}
(when include-metadata?
(when include-metadata
[:& export/export-page {:id (:id data) :options (:options data)}])
(let [shapes (->> shapes
@ -250,9 +249,9 @@
;; the viewer and inspector
(mf/defc frame-svg
{::mf/wrap [mf/memo]}
[{:keys [objects frame zoom show-thumbnails?] :or {zoom 1} :as props}]
(let [frame-id (:id frame)
include-metadata? (mf/use-ctx export/include-metadata-ctx)
[{:keys [objects frame zoom use-thumbnails] :or {zoom 1} :as props}]
(let [frame-id (:id frame)
include-metadata (mf/use-ctx export/include-metadata-ctx)
bounds (gsb/get-object-bounds objects frame)
@ -294,14 +293,14 @@
height (* (:height bounds) zoom)
vbox (format-viewbox {:width (:width bounds 0) :height (:height bounds 0)})]
[:& (mf/provider muc/render-thumbnails) {:value show-thumbnails?}
[:& (mf/provider muc/render-thumbnails) {:value use-thumbnails}
[:svg {:view-box vbox
:width (ust/format-precision width viewbox-decimal-precision)
:height (ust/format-precision height viewbox-decimal-precision)
: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")
:xmlns:penpot (when include-metadata "https://penpot.app/xmlns")
:fill "none"}
[:& shape-wrapper {:shape frame}]]]))
@ -312,7 +311,7 @@
[{:keys [objects root-shape zoom] :or {zoom 1} :as props}]
(when root-shape
(let [root-shape-id (:id root-shape)
include-metadata? (mf/use-ctx export/include-metadata-ctx)
include-metadata (mf/use-ctx export/include-metadata-ctx)
vector
(mf/use-memo
@ -348,7 +347,7 @@
: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")
:xmlns:penpot (when include-metadata "https://penpot.app/xmlns")
:fill "none"}
[:> shape-container {:shape root-shape'}
@ -357,8 +356,8 @@
(mf/defc object-svg
{::mf/wrap [mf/memo]}
[{:keys [objects object-id render-embed?]
:or {render-embed? false}
[{:keys [objects object-id embed]
:or {embed false}
:as props}]
(let [object (get objects object-id)
object (cond-> object
@ -375,7 +374,7 @@
(shape-wrapper-factory objects))]
[:& (mf/provider export/include-metadata-ctx) {:value false}
[:& (mf/provider embed/context) {:value render-embed?}
[:& (mf/provider embed/context) {:value embed}
[:svg {:id (dm/str "screenshot-" object-id)
:view-box vbox
:width (ust/format-precision width viewbox-decimal-precision)
@ -439,20 +438,16 @@
:group [:& group-wrapper {:shape root-shape :view-box vbox}]
:frame [:& frame-wrapper {:shape root-shape :view-box vbox}])]]))
(mf/defc components-sprite-svg
(mf/defc components-svg
{::mf/wrap-props false}
[props]
(let [data (obj/get props "data")
children (obj/get props "children")
render-embed? (obj/get props "render-embed?")
include-metadata? (obj/get props "include-metadata?")
source (keyword (obj/get props "source" "components"))]
[:& (mf/provider embed/context) {:value render-embed?}
[:& (mf/provider export/include-metadata-ctx) {:value include-metadata?}
[{:keys [data children embed include-metadata source]}]
(let [source (keyword (d/nilv source "components"))]
[:& (mf/provider embed/context) {:value 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")
:xmlns:penpot (when include-metadata "https://penpot.app/xmlns")
:style {:display (when-not (some? children) "none")}
:fill "none"}
[:defs
@ -511,7 +506,7 @@
(->> (rx/of data)
(rx/map
(fn [data]
(let [elem (mf/element page-svg #js {:data data :render-embed? true :include-metadata? true})]
(let [elem (mf/element page-svg #js {:data data :embed true :include-metadata true})]
(rds/renderToStaticMarkup elem)))))))
(defn render-components
@ -531,8 +526,8 @@
(->> (rx/of data)
(rx/map
(fn [data]
(let [elem (mf/element components-sprite-svg
#js {:data data :render-embed? true :include-metadata? true
(let [elem (mf/element components-svg
#js {:data data :embed true :include-metadata true
:source (name source)})]
(rds/renderToStaticMarkup elem))))))))

View file

@ -88,7 +88,7 @@
(assoc :thumbnail (get thumbnail-data (dm/str page-id (:id frame))))
(assoc :children-bounds children-bounds))
:objects objects
:show-thumbnails? true}]]
:use-thumbnails true}]]
[:div.thumbnail-info
[:span.name {:title (:name frame)} (:name frame)]]]))

View file

@ -8,125 +8,54 @@
"The main entry point for UI part needed by the exporter."
(:require
[app.common.geom.shapes.bounds :as gsb]
[app.common.logging :as l]
[app.common.logging :as log]
[app.common.math :as mth]
[app.common.spec :as us]
[app.common.schema :as sm]
[app.common.types.components-list :as ctkl]
[app.common.uri :as u]
[app.main.data.fonts :as df]
[app.main.features :as feat]
[app.main.data.users :as du]
[app.main.features :as features]
[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]]
[okulary.core :as l]
[potok.core :as ptk]
[rumext.v2 :as mf]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; SETUP
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(log/setup! {:app :info})
(l/setup! {:app :info})
(defonce app-root
(let [el (dom/get-element "app")]
(mf/create-root el)))
(declare ^:private render-single-object)
(declare ^:private render-components)
(declare ^:private render-objects)
(defn- parse-params
[loc]
(let [href (unchecked-get loc "href")]
(some-> href u/uri :query u/query-string->map)))
(defn init-ui
[]
(when-let [params (parse-params glob/location)]
(when-let [component (case (:route params)
"objects" (render-objects params)
"components" (render-components params)
nil)]
(mf/render! app-root component))))
(defn ^:export init
[]
(st/emit! (feat/initialize))
(init-ui))
(defn reinit
[]
(mf/unmount! app-root)
(init-ui))
(defn ^:dev/after-load after-load
[]
(reinit))
(defn- fetch-team
[& {:keys [file-id]}]
(ptk/reify ::fetch-team
ptk/WatchEvent
(watch [_ _ _]
(->> (repo/cmd! :get-team {:file-id file-id})
(rx/mapcat (fn [team]
(rx/of (du/set-current-team team)
(ptk/data-event ::team-fetched team))))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 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 responsibility 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))
(def ^:private ref:objects
(l/derived :objects st/state))
(mf/defc object-svg
[{:keys [page-id file-id share-id object-id render-embed?]}]
(let [components-v2 (feat/use-feature "components/v2")
fetch-state (mf/use-fn
(mf/deps file-id page-id share-id object-id components-v2)
(fn []
(let [features (cond-> #{} components-v2 (conj "components/v2"))]
(->> (rx/zip
(repo/cmd! :get-font-variants {:file-id file-id :share-id share-id})
(repo/cmd! :get-page {:file-id file-id
:page-id page-id
:share-id share-id
:object-id object-id
:features features}))
(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)]
{:objects objects
:object (get objects object-id)})))))))
{:keys [objects object]} (use-resource fetch-state)]
{::mf/wrap-props false}
[{:keys [object-id embed]}]
(let [objects (mf/deref ref:objects)]
;; Set the globa CSS to assign the page size, needed for PDF
;; exportation process.
(mf/with-effect [object]
(when object
(mf/with-effect [objects]
(when-let [object (get objects object-id)]
(let [{:keys [width height]} (gsb/get-object-bounds [objects] object)]
(dom/set-page-style!
{:size (str/concat
@ -137,90 +66,107 @@
[:& render/object-svg
{:objects objects
:object-id object-id
:render-embed? render-embed?}])))
:embed embed}])))
(mf/defc objects-svg
[{:keys [page-id file-id share-id object-ids render-embed?]}]
(let [components-v2 (feat/use-feature "components/v2")
fetch-state (mf/use-fn
(mf/deps file-id page-id share-id components-v2)
(fn []
(let [features (cond-> #{} components-v2 (conj "components/v2"))]
(->> (rx/zip
(repo/cmd! :get-font-variants {:file-id file-id :share-id share-id})
(repo/cmd! :get-page {:file-id file-id
:page-id page-id
:share-id share-id
:features features}))
(rx/tap (fn [[fonts]]
(when (seq fonts)
(st/emit! (df/fonts-fetched fonts)))))
(rx/map (fn [[_ page]] {:objects (:objects page)}))))))
{::mf/wrap-props false}
[{:keys [object-ids embed]}]
(when-let [objects (mf/deref ref:objects)]
(for [object-id object-ids]
(let [objects (render/adapt-objects-for-shape objects object-id)]
[:& render/object-svg
{:objects objects
:key (str object-id)
:object-id object-id
:embed embed}]))))
{:keys [objects]} (use-resource fetch-state)]
(defn- fetch-objects-bundle
[& {:keys [file-id page-id share-id object-id] :as options}]
(ptk/reify ::fetch-objects-bundle
ptk/WatchEvent
(watch [_ state _]
(let [features (features/get-team-enabled-features state)]
(->> (rx/zip
(repo/cmd! :get-font-variants {:file-id file-id :share-id share-id})
(repo/cmd! :get-page {:file-id file-id
:page-id page-id
:share-id share-id
:object-id object-id
:features features}))
(rx/tap (fn [[fonts]]
(when (seq fonts)
(st/emit! (df/fonts-fetched fonts)))))
(rx/observe-on :async)
(rx/map (comp :objects second))
(rx/map (fn [objects]
(let [objects (render/adapt-objects-for-shape objects object-id)]
#(assoc % :objects objects)))))))))
(when objects
(for [object-id object-ids]
(let [objects (render/adapt-objects-for-shape objects object-id)]
[:& render/object-svg
{:objects objects
:key (str object-id)
:object-id object-id
:render-embed? render-embed?}])))))
(def ^:private schema:render-objects
[:map {:title "render-objets"}
[:page-id ::sm/uuid]
[:file-id ::sm/uuid]
[:share-id {:optional true} ::sm/uuid]
[:embed {:optional true} :boolean]
[:object-id
[:or
::sm/uuid
::sm/coll-of-uuid]]])
(s/def ::page-id ::us/uuid)
(s/def ::file-id ::us/uuid)
(s/def ::share-id ::us/uuid)
(s/def ::object-id
(s/or :single ::us/uuid
:multiple (s/coll-of ::us/uuid)))
(s/def ::embed ::us/boolean)
(def ^:private render-objects-decoder
(sm/lazy-decoder schema:render-objects
sm/default-transformer))
(s/def ::render-objects
(s/keys :req-un [::file-id ::page-id ::object-id]
:opt-un [::render-embed ::share-id]))
(def ^:private render-objects-validator
(sm/lazy-validator schema:render-objects))
(defn- render-objects
[params]
(let [{:keys [file-id
page-id
render-embed
share-id]
:as params}
(us/conform ::render-objects params)
(let [{:keys [file-id page-id embed share-id object-id] :as params} (render-objects-decoder params)]
(if-not (render-objects-validator params)
(do
(js/console.error "invalid arguments")
(sm/pretty-explain schema:render-objects params)
nil)
[type object-id] (:object-id params)]
(case type
:single
(mf/html
[:& object-svg
{:file-id file-id
:page-id page-id
:share-id share-id
:object-id object-id
:render-embed? render-embed}])
(do
(st/emit! (ptk/reify ::initialize-render-objects
ptk/WatchEvent
(watch [_ _ stream]
(rx/merge
(rx/of (fetch-team :file-id file-id))
:multiple
(mf/html
[:& objects-svg
{:file-id file-id
:page-id page-id
:share-id share-id
:object-ids (into #{} object-id)
:render-embed? render-embed}]))))
(->> stream
(rx/filter (ptk/type? ::team-fetched))
(rx/observe-on :async)
(rx/map (constantly params))
(rx/map fetch-objects-bundle))))))
(if (uuid? object-id)
(mf/html
[:& object-svg
{:file-id file-id
:page-id page-id
:share-id share-id
:object-id object-id
:embed embed}])
(mf/html
[:& objects-svg
{:file-id file-id
:page-id page-id
:share-id share-id
:object-ids (into #{} object-id)
:embed embed}]))))))
;; ---- COMPONENTS SPRITE
(mf/defc components-sprite-svg
[{:keys [file-id embed] :as props}]
(let [fetch (mf/use-fn
(mf/deps file-id)
(fn [] (repo/cmd! :get-file {:id file-id})))
file (use-resource fetch)
state (mf/use-state nil)]
(when file
(mf/defc components-svg
{::mf/wrap-props false}
[{:keys [embed component-id]}]
(let [file-ref (mf/with-memo [] (l/derived :file st/state))
state (mf/use-state {:component-id component-id})]
(when-let [file (mf/deref file-ref)]
[:*
[:style
(css [[:body
@ -266,7 +212,7 @@
[:a {:on-click on-click} (:name data)]]))]
[:main
[:& render/components-sprite-svg
[:& render/components-svg
{:data (:data file)
:embed embed}
@ -275,16 +221,93 @@
])))
(s/def ::component-id ::us/uuid)
(s/def ::render-components
(s/keys :req-un [::file-id]
:opt-un [::embed ::component-id]))
(defn- fetch-components-bundle
[& {:keys [file-id]}]
(ptk/reify ::fetch-components-bundle
ptk/WatchEvent
(watch [_ state _]
(let [features (features/get-team-enabled-features state)]
(->> (repo/cmd! :get-file {:id file-id :features features})
(rx/map (fn [file] #(assoc % :file file))))))))
(def ^:private schema:render-components
[:map {:title "render-components"}
[:file-id ::sm/uuid]
[:embed {:optional true} :boolean]
[:component-id {:optional true} ::sm/uuid]])
(def ^:private render-components-decoder
(sm/lazy-decoder schema:render-components
sm/default-transformer))
(def ^:private render-components-validator
(sm/lazy-validator schema:render-components))
(defn render-components
[params]
(let [{:keys [file-id component-id embed]} (us/conform ::render-components params)]
(mf/html
[:& components-sprite-svg
{:file-id file-id
:component-id component-id
:embed embed}])))
(let [{:keys [file-id component-id embed] :as params} (render-components-decoder params)]
(if-not (render-components-validator params)
(do
(js/console.error "invalid arguments")
(sm/pretty-explain schema:render-components params)
nil)
(do
(st/emit! (ptk/reify ::initialize-render-components
ptk/WatchEvent
(watch [_ _ stream]
(rx/merge
(rx/of (fetch-team :file-id file-id))
(->> stream
(rx/filter (ptk/type? ::team-fetched))
(rx/observe-on :async)
(rx/map (constantly params))
(rx/map fetch-components-bundle))))))
(mf/html
[:& components-svg
{:component-id component-id
:embed embed}])))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; SETUP
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defonce app-root
(let [el (dom/get-element "app")]
(mf/create-root el)))
(declare ^:private render-single-object)
(declare ^:private render-components)
(declare ^:private render-objects)
(defn- parse-params
[loc]
(let [href (unchecked-get loc "href")]
(some-> href u/uri :query u/query-string->map)))
(defn init-ui
[]
(when-let [params (parse-params glob/location)]
(when-let [component (case (:route params)
"objects" (render-objects params)
"components" (render-components params)
nil)]
(mf/render! app-root component))))
(defn ^:export init
[]
(st/emit! (features/initialize))
(init-ui))
(defn reinit
[]
(init-ui))
(defn ^:dev/after-load after-load
[]
(reinit))

View file

@ -63,8 +63,8 @@
(let [objects (:objects page)
frame (some->> page :thumbnail-frame-id (get objects))
element (if frame
(mf/element render/frame-svg #js {:objects objects :frame frame :show-thumbnails? true})
(mf/element render/page-svg #js {:data page :thumbnails? true :render-embed? true}))
(mf/element render/frame-svg #js {:objects objects :frame frame :use-thumbnails true})
(mf/element render/page-svg #js {:data page :use-thumbnails true :embed true}))
data (rds/renderToStaticMarkup element)]
{:data data
:fonts @fonts/loaded-hints