mirror of
https://github.com/penpot/penpot.git
synced 2025-04-16 00:41:25 -05:00
♻️ Refactor thumbnails
This commit is contained in:
parent
0721fc9d80
commit
0494dc843f
18 changed files with 259 additions and 460 deletions
frontend
resources/styles/common
src/app
main.cljs
main
util
worker
|
@ -262,3 +262,8 @@ input[type="number"] {
|
|||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
[data-hidden="true"] {
|
||||
display: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
[app.main.data.websocket :as ws]
|
||||
[app.main.errors]
|
||||
[app.main.features :as feat]
|
||||
[app.main.imposters :as imp]
|
||||
[app.main.rasterizer :as thr]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui :as ui]
|
||||
|
@ -113,6 +114,7 @@
|
|||
(theme/init! cf/themes)
|
||||
(cur/init-styles)
|
||||
(thr/init!)
|
||||
(imp/init!)
|
||||
(init-ui)
|
||||
(st/emit! (initialize)))
|
||||
|
||||
|
|
|
@ -169,7 +169,7 @@
|
|||
(->> (rx/from frame-updates)
|
||||
(rx/mapcat (fn [[page-id frames]]
|
||||
(->> frames (map #(vector page-id %)))))
|
||||
(rx/map (fn [[page-id frame-id]] (dwt/update-thumbnail file-id page-id frame-id))))
|
||||
(rx/map (fn [[_ frame-id]] (dwt/update-thumbnail frame-id))))
|
||||
|
||||
(->> (rx/from (concat lagged commits))
|
||||
(rx/merge-map
|
||||
|
|
|
@ -7,65 +7,61 @@
|
|||
(ns app.main.data.workspace.thumbnails
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.logging :as log]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace.changes :as dch]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
[app.main.rasterizer :as thr]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.store :as st]
|
||||
[app.main.worker :as uw]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.http :as http]
|
||||
[app.util.imposters :as imps]
|
||||
[app.util.time :as tp]
|
||||
[app.util.timers :as tm]
|
||||
[app.util.webapi :as wapi]
|
||||
[beicon.core :as rx]
|
||||
[potok.core :as ptk]))
|
||||
|
||||
(defn force-render-stream
|
||||
"Stream that will inform the frame-wrapper to mount into memory"
|
||||
[id]
|
||||
(->> st/stream
|
||||
(rx/filter (ptk/type? ::force-render))
|
||||
(rx/map deref)
|
||||
(rx/filter #(= % id))
|
||||
(rx/take 1)))
|
||||
(log/set-level! :debug)
|
||||
|
||||
(declare set-workspace-thumbnail)
|
||||
|
||||
(defn get-thumbnail
|
||||
[object-id]
|
||||
;; Look for the thumbnail canvas to send the data to the backend
|
||||
(let [node (dom/query (dm/fmt "image.thumbnail-bitmap[data-object-id='%']" object-id))
|
||||
stopper (->> st/stream
|
||||
(rx/filter (ptk/type? :app.main.data.workspace/finalize-page))
|
||||
(rx/take 1))]
|
||||
;; renders #svg image
|
||||
(if (some? node)
|
||||
(->> (rx/from (wapi/create-image-bitmap-with-workaround node))
|
||||
(rx/switch-map #(uw/ask! {:cmd :thumbnails/render-offscreen-canvas} %))
|
||||
(rx/map :result))
|
||||
|
||||
;; Not found, we retry after delay
|
||||
(->> (rx/timer 250)
|
||||
(rx/merge-map (partial get-thumbnail object-id))
|
||||
(rx/take-until stopper)))))
|
||||
[id]
|
||||
(let [object-id (dm/str id)
|
||||
tp (tp/tpoint-ms)]
|
||||
(->> (rx/of id)
|
||||
(rx/mapcat @imps/render-fn)
|
||||
(rx/filter #(= object-id (unchecked-get % "id")))
|
||||
(rx/take 1)
|
||||
(rx/map (fn [imposter]
|
||||
{:data (unchecked-get imposter "data")
|
||||
:styles (unchecked-get imposter "styles")
|
||||
:width (unchecked-get imposter "width")}))
|
||||
(rx/mapcat thr/render)
|
||||
(rx/map (fn [blob] (wapi/create-uri blob)))
|
||||
(rx/tap #(log/debug :hint "generated thumbnail" :elapsed (dm/str (tp) "ms"))))))
|
||||
|
||||
(defn clear-thumbnail
|
||||
[page-id frame-id]
|
||||
[frame-id]
|
||||
(ptk/reify ::clear-thumbnail
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [object-id (dm/str page-id frame-id)]
|
||||
(when-let [uri (dm/get-in state [:workspace-thumbnails object-id])]
|
||||
(tm/schedule-on-idle (partial wapi/revoke-uri uri)))
|
||||
(update state :workspace-thumbnails dissoc object-id)))))
|
||||
(let [object-id (dm/str frame-id)]
|
||||
(when-let [uri (dm/get-in state [:workspace-thumbnails object-id])]
|
||||
(tm/schedule-on-idle (partial wapi/revoke-uri uri)))
|
||||
(update state :workspace-thumbnails dissoc object-id)))))
|
||||
|
||||
(defn set-workspace-thumbnail
|
||||
[object-id uri]
|
||||
[id uri]
|
||||
(let [prev-uri* (volatile! nil)]
|
||||
(ptk/reify ::set-workspace-thumbnail
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [prev-uri (dm/get-in state [:workspace-thumbnails object-id])]
|
||||
(let [object-id (dm/str id)
|
||||
prev-uri (dm/get-in state [:workspace-thumbnails object-id])]
|
||||
(some->> prev-uri (vreset! prev-uri*))
|
||||
(update state :workspace-thumbnails assoc object-id uri)))
|
||||
|
||||
|
@ -78,29 +74,26 @@
|
|||
(ptk/reify ::duplicate-thumbnail
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [page-id (:current-page-id state)
|
||||
thumbnail (dm/get-in state [:workspace-thumbnails (dm/str page-id old-id)])]
|
||||
(update state :workspace-thumbnails assoc (dm/str page-id new-id) thumbnail)))))
|
||||
(let [old-id (dm/str old-id)
|
||||
new-id (dm/str new-id)
|
||||
thumbnail (dm/get-in state [:workspace-thumbnails old-id])]
|
||||
(update state :workspace-thumbnails assoc new-id thumbnail)))))
|
||||
|
||||
(defn update-thumbnail
|
||||
"Updates the thumbnail information for the given frame `id`"
|
||||
([page-id frame-id]
|
||||
(update-thumbnail nil page-id frame-id))
|
||||
|
||||
([file-id page-id frame-id]
|
||||
([id]
|
||||
(ptk/reify ::update-thumbnail
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [object-id (dm/str page-id frame-id)
|
||||
file-id (or file-id (:current-file-id state))]
|
||||
|
||||
(let [object-id (dm/str id)
|
||||
file-id (:current-file-id state)]
|
||||
(rx/concat
|
||||
;; Delete the thumbnail first so if we interrupt we can regenerate after
|
||||
(->> (rp/cmd! :delete-file-object-thumbnail {:file-id file-id :object-id object-id})
|
||||
(rx/catch rx/empty))
|
||||
|
||||
;; Send the update to the back-end
|
||||
(->> (get-thumbnail object-id)
|
||||
(->> (get-thumbnail id)
|
||||
(rx/filter (fn [data] (and (some? data) (some? file-id))))
|
||||
(rx/merge-map
|
||||
(fn [uri]
|
||||
|
@ -110,7 +103,7 @@
|
|||
(->> (http/send! {:uri uri :response-type :blob :method :get})
|
||||
(rx/map :body)
|
||||
(rx/mapcat (fn [blob]
|
||||
;; Send the data to backend
|
||||
;; Send the data to backend
|
||||
(let [params {:file-id file-id
|
||||
:object-id object-id
|
||||
:media blob}]
|
||||
|
@ -193,9 +186,9 @@
|
|||
(->> (rx/merge
|
||||
(->> frame-changes-s
|
||||
(rx/filter (fn [[page-id _]] (not= page-id (:current-page-id @st/state))))
|
||||
(rx/map (fn [[page-id frame-id]] (clear-thumbnail page-id frame-id))))
|
||||
(rx/map (fn [[_ frame-id]] (clear-thumbnail frame-id))))
|
||||
|
||||
(->> frame-changes-s
|
||||
(rx/filter (fn [[page-id _]] (= page-id (:current-page-id @st/state))))
|
||||
(rx/map (fn [[_ frame-id]] (ptk/data-event ::force-render frame-id)))))
|
||||
(rx/map (fn [[_ frame-id]] (update-thumbnail frame-id)))))
|
||||
(rx/take-until stopper))))))
|
||||
|
|
|
@ -297,3 +297,25 @@
|
|||
(->> (rx/from font-refs)
|
||||
(rx/mapcat fetch-font-css)
|
||||
(rx/reduce (fn [acc css] (dm/str acc "\n" css)) "")))
|
||||
|
||||
(defonce font-styles (js/Map.))
|
||||
|
||||
(defn get-font-style-id
|
||||
[{:keys [font-id font-variant-id]
|
||||
:or {font-variant-id "regular"}}]
|
||||
(dm/fmt "%:%" font-id font-variant-id))
|
||||
|
||||
(defn get-font-styles-by-font-ref
|
||||
[font-ref]
|
||||
(let [id (get-font-style-id font-ref)]
|
||||
(if (.has font-styles id)
|
||||
(rx/of (.get font-styles id))
|
||||
(->> (rx/of font-ref)
|
||||
(rx/mapcat fetch-font-css)
|
||||
(rx/tap (fn [css] (.set font-styles id css)))))))
|
||||
|
||||
(defn render-font-styles-cached
|
||||
[font-refs]
|
||||
(->> (rx/from font-refs)
|
||||
(rx/merge-map get-font-styles-by-font-ref)
|
||||
(rx/reduce (fn [acc css] (dm/str acc "\n" css)) "")))
|
||||
|
|
77
frontend/src/app/main/imposters.cljs
Normal file
77
frontend/src/app/main/imposters.cljs
Normal file
|
@ -0,0 +1,77 @@
|
|||
;; 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.main.imposters
|
||||
(:require ["react-dom/server" :as rds]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.rect :as grc]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
[app.main.fonts :as fonts]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.render :as render]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.shapes.text.fontfaces :as ff]
|
||||
[app.util.imposters :as imps]
|
||||
[app.util.thumbnails :as th]
|
||||
[beicon.core :as rx]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defn render
|
||||
"Render the frame and store it in the imposter map"
|
||||
([id shape objects]
|
||||
(render id shape objects nil))
|
||||
([id shape objects fonts]
|
||||
(let [object-id (dm/str id)
|
||||
shape (if (nil? shape) (get objects id) shape)
|
||||
fonts (if (nil? fonts) (ff/shape->fonts shape objects) fonts)
|
||||
|
||||
all-children (deref (refs/all-children-objects id))
|
||||
|
||||
bounds
|
||||
(if (:show-content shape)
|
||||
(gsh/shapes->rect (cons shape all-children))
|
||||
(-> shape :points grc/points->rect))
|
||||
|
||||
x (dm/get-prop bounds :x)
|
||||
y (dm/get-prop bounds :y)
|
||||
width (dm/get-prop bounds :width)
|
||||
height (dm/get-prop bounds :height)
|
||||
|
||||
viewbox (dm/fmt "% % % %" x y width height)
|
||||
|
||||
[fixed-width fixed-height] (th/get-proportional-size width height)
|
||||
|
||||
data (rds/renderToStaticMarkup
|
||||
(mf/element render/frame-imposter-svg
|
||||
{:objects objects
|
||||
:frame shape
|
||||
:vbox viewbox
|
||||
:width width
|
||||
:height height
|
||||
:show-thumbnails? false}))]
|
||||
(->> (fonts/render-font-styles-cached fonts)
|
||||
(rx/catch rx/empty)
|
||||
(rx/map (fn [styles] #js {:id object-id
|
||||
:data data
|
||||
:viewbox viewbox
|
||||
:width fixed-width
|
||||
:height fixed-height
|
||||
:styles styles}))))))
|
||||
|
||||
(defn render-by-id
|
||||
"Render the shape by its id (IMPORTANT! id as uuid, not string)"
|
||||
[id]
|
||||
(dm/assert! "expected uuid" (uuid? id))
|
||||
(let [objects (wsh/lookup-page-objects @st/state)
|
||||
shape (get objects id)
|
||||
fonts (ff/shape->fonts shape objects)]
|
||||
(render id shape objects fonts)))
|
||||
|
||||
(defn init!
|
||||
"Initializes the render function"
|
||||
[]
|
||||
(imps/init! render-by-id))
|
|
@ -7,8 +7,8 @@
|
|||
(ns app.main.rasterizer
|
||||
"A main entry point for the rasterizer API interface.
|
||||
|
||||
This ns is responsible to provide an API for create thumbnail
|
||||
renderer iframes and interact with them using asyncrhonous
|
||||
This ns is responsible to provide an API for create rasterizer
|
||||
iframes and interact with them using asyncrhonous
|
||||
messages."
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
|
@ -36,7 +36,7 @@
|
|||
(recur (.shift ^js queue)))))
|
||||
|
||||
(defn- on-message
|
||||
"Handles a message from the thumbnail renderer."
|
||||
"Handles a message from the rasterizer."
|
||||
[event]
|
||||
(let [evorigin (unchecked-get event "origin")
|
||||
evdata (unchecked-get event "data")]
|
||||
|
@ -51,7 +51,7 @@
|
|||
(rx/push! msgbus evdata))))))
|
||||
|
||||
(defn- send-message!
|
||||
"Sends a message to the thumbnail renderer."
|
||||
"Sends a message to the rasterizer."
|
||||
[message]
|
||||
(let [window (.-contentWindow ^js instance)]
|
||||
(.postMessage ^js window message origin)))
|
||||
|
@ -94,7 +94,7 @@
|
|||
(render {:data data :styles styles :width width :result result})))
|
||||
|
||||
(defn init!
|
||||
"Initializes the thumbnail renderer."
|
||||
"Initializes the rasterizer."
|
||||
[]
|
||||
(let [iframe (dom/create-element "iframe")]
|
||||
(dom/set-attribute! iframe "src" origin)
|
||||
|
|
|
@ -479,10 +479,10 @@
|
|||
(l/derived #(get % :workspace-thumbnails {}) st/state))
|
||||
|
||||
(defn thumbnail-frame-data
|
||||
[page-id frame-id]
|
||||
[frame-id]
|
||||
(l/derived
|
||||
(fn [thumbnails]
|
||||
(get thumbnails (dm/str page-id frame-id)))
|
||||
(get thumbnails (dm/str frame-id)))
|
||||
thumbnail-data))
|
||||
|
||||
(def workspace-text-modifier
|
||||
|
|
|
@ -229,6 +229,25 @@
|
|||
[:& shape-wrapper {:shape item
|
||||
:key (:id item)}])]]]]))
|
||||
|
||||
;; Component that serves for render frame thumbnails, mainly used in
|
||||
;; the viewer and inspector
|
||||
(mf/defc frame-imposter-svg
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [objects frame vbox width height show-thumbnails?] :as props}]
|
||||
(let [shape-wrapper
|
||||
(mf/use-memo
|
||||
(mf/deps objects)
|
||||
#(shape-wrapper-factory objects))]
|
||||
|
||||
[:& (mf/provider muc/render-thumbnails) {:value show-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"
|
||||
:fill "none"}
|
||||
[:& shape-wrapper {:shape frame}]]]))
|
||||
|
||||
;; Component that serves for render frame thumbnails, mainly used in
|
||||
;; the viewer and inspector
|
||||
|
@ -443,7 +462,7 @@
|
|||
(for [[id component] (source data)]
|
||||
(let [component (ctf/load-component-objects data component)]
|
||||
[:& component-symbol {:key (dm/str id) :component component}]))]
|
||||
|
||||
|
||||
children]]]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
|
|
@ -35,8 +35,8 @@
|
|||
(declare group-wrapper)
|
||||
(declare svg-raw-wrapper)
|
||||
(declare bool-wrapper)
|
||||
(declare root-frame-wrapper)
|
||||
(declare nested-frame-wrapper)
|
||||
(declare root-frame-wrapper)
|
||||
|
||||
(def circle-wrapper (common/generic-wrapper-factory circle/circle-shape))
|
||||
(def image-wrapper (common/generic-wrapper-factory image/image-shape))
|
||||
|
@ -121,5 +121,5 @@
|
|||
(def group-wrapper (group/group-wrapper-factory shape-wrapper))
|
||||
(def svg-raw-wrapper (svg-raw/svg-raw-wrapper-factory shape-wrapper))
|
||||
(def bool-wrapper (bool/bool-wrapper-factory shape-wrapper))
|
||||
(def root-frame-wrapper (frame/root-frame-wrapper-factory shape-wrapper))
|
||||
(def nested-frame-wrapper (frame/nested-frame-wrapper-factory shape-wrapper))
|
||||
(def root-frame-wrapper (frame/root-frame-wrapper-factory shape-wrapper))
|
||||
|
|
|
@ -6,24 +6,20 @@
|
|||
|
||||
(ns app.main.ui.workspace.shapes.frame
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.rect :as grc]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
[app.main.data.workspace.thumbnails :as dwt]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.main.ui.shapes.embed :as embed]
|
||||
[app.main.ui.shapes.frame :as frame]
|
||||
[app.main.ui.shapes.shape :refer [shape-container]]
|
||||
[app.main.ui.shapes.text.fontfaces :as ff]
|
||||
[app.main.ui.workspace.shapes.common :refer [check-shape-props]]
|
||||
[app.main.ui.workspace.shapes.frame.dynamic-modifiers :as fdm]
|
||||
[app.main.ui.workspace.shapes.frame.node-store :as fns]
|
||||
[app.main.ui.workspace.shapes.frame.thumbnail-render :as ftr]
|
||||
[beicon.core :as rx]
|
||||
[debug :refer [debug?]]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defn frame-shape-factory
|
||||
|
@ -44,7 +40,7 @@
|
|||
|
||||
[:& (mf/provider embed/context) {:value true}
|
||||
[:& shape-container {:shape shape :ref ref :disable-shadows? (cph/is-direct-child-of-root? shape)}
|
||||
[:& frame-shape {:shape shape :childs childs} ]]]))))
|
||||
[:& frame-shape {:shape shape :childs childs}]]]))))
|
||||
|
||||
(defn check-props
|
||||
[new-props old-props]
|
||||
|
@ -86,74 +82,75 @@
|
|||
(let [shape (unchecked-get props "shape")
|
||||
thumbnail? (unchecked-get props "thumbnail?")
|
||||
|
||||
page-id (mf/use-ctx ctx/current-page-id)
|
||||
frame-id (dm/get-prop shape :id)
|
||||
;; page-id (mf/use-ctx ctx/current-page-id)
|
||||
frame-id (:id shape)
|
||||
|
||||
objects (wsh/lookup-page-objects @st/state page-id)
|
||||
objects (wsh/lookup-page-objects @st/state)
|
||||
|
||||
node-ref (mf/use-ref nil)
|
||||
root-ref (mf/use-ref nil)
|
||||
container-ref (mf/use-ref nil)
|
||||
content-ref (mf/use-ref nil)
|
||||
|
||||
force-render* (mf/use-state false)
|
||||
force-render? (deref force-render*)
|
||||
all-children-ref (mf/with-memo [frame-id]
|
||||
(refs/all-children-objects frame-id))
|
||||
all-children (mf/deref all-children-ref)
|
||||
|
||||
;; when `true` we've called the mount for the frame
|
||||
rendered-ref (mf/use-ref false)
|
||||
bounds
|
||||
(if (:show-content shape)
|
||||
(gsh/shapes->rect (cons shape all-children))
|
||||
(-> shape :points grc/points->rect))
|
||||
|
||||
x (dm/get-prop bounds :x)
|
||||
y (dm/get-prop bounds :y)
|
||||
width (dm/get-prop bounds :width)
|
||||
height (dm/get-prop bounds :height)
|
||||
|
||||
thumbnail-uri* (mf/with-memo [frame-id]
|
||||
(refs/thumbnail-frame-data frame-id))
|
||||
thumbnail-uri (mf/deref thumbnail-uri*)
|
||||
|
||||
modifiers-ref (mf/with-memo [frame-id]
|
||||
(refs/workspace-modifiers-by-frame-id frame-id))
|
||||
modifiers (mf/deref modifiers-ref)
|
||||
|
||||
fonts (mf/with-memo [shape objects]
|
||||
(ff/shape->fonts shape objects))
|
||||
fonts (hooks/use-equal-memo fonts)
|
||||
debug? (debug? :thumbnails)]
|
||||
|
||||
disable-thumbnail? (d/not-empty? (dm/get-in modifiers [frame-id :modifiers]))
|
||||
(when-not (some? thumbnail-uri)
|
||||
(st/emit! (dwt/update-thumbnail frame-id)))
|
||||
|
||||
[on-load-frame-dom render-frame? children]
|
||||
(ftr/use-render-thumbnail page-id shape root-ref node-ref rendered-ref disable-thumbnail? force-render?)
|
||||
|
||||
on-frame-load
|
||||
(fns/use-node-store node-ref rendered-ref thumbnail? render-frame?)
|
||||
|
||||
]
|
||||
|
||||
(fdm/use-dynamic-modifiers objects (mf/ref-val node-ref) modifiers)
|
||||
|
||||
(mf/with-effect []
|
||||
;; When a change in the data is received a "force-render" event is emitted
|
||||
;; that will force the component to be mounted in memory
|
||||
(let [sub (->> (dwt/force-render-stream frame-id)
|
||||
(rx/take-while #(not (mf/ref-val rendered-ref)))
|
||||
(rx/subs #(reset! force-render* true)))]
|
||||
#(some-> sub rx/dispose!)))
|
||||
|
||||
(mf/with-effect [shape fonts thumbnail? on-load-frame-dom force-render? render-frame?]
|
||||
(when (and (some? (mf/ref-val node-ref))
|
||||
(or (mf/ref-val rendered-ref)
|
||||
(false? thumbnail?)
|
||||
(true? force-render?)
|
||||
(true? render-frame?)))
|
||||
|
||||
(when (false? (mf/ref-val rendered-ref))
|
||||
(when-let [node (mf/ref-val node-ref)]
|
||||
(mf/set-ref-val! root-ref (mf/create-root node))
|
||||
(mf/set-ref-val! rendered-ref true)))
|
||||
|
||||
(when-let [root (mf/ref-val root-ref)]
|
||||
(mf/render! root (mf/element frame-shape #js {:ref on-load-frame-dom :shape shape :fonts fonts})))
|
||||
|
||||
(constantly nil)))
|
||||
(fdm/use-dynamic-modifiers objects (mf/ref-val content-ref) modifiers)
|
||||
|
||||
[:& shape-container {:shape shape}
|
||||
[:g.frame-container
|
||||
{:id (dm/str "frame-container-" frame-id)
|
||||
:key "frame-container"
|
||||
:ref on-frame-load
|
||||
;; :ref on-container-ref
|
||||
:opacity (when (:hidden shape) 0)}
|
||||
[:& ff/fontfaces-style {:fonts fonts}]
|
||||
[:g.frame-thumbnail-wrapper
|
||||
{:id (dm/str "thumbnail-container-" frame-id)
|
||||
;; Hide the thumbnail when not displaying
|
||||
:opacity (when-not thumbnail? 0)}
|
||||
children]]]))))
|
||||
|
||||
;; When thumbnail is enabled.
|
||||
[:g.frame-imposter
|
||||
;; Render thumbnail image.
|
||||
[:image.thumbnail-bitmap
|
||||
{;; :ref on-imposter-ref
|
||||
:x x
|
||||
:y y
|
||||
:width width
|
||||
:height height
|
||||
:href thumbnail-uri
|
||||
:style {:display (when-not thumbnail? "none")}}]
|
||||
|
||||
;; Render border around image when we are debugging
|
||||
;; thumbnails.
|
||||
(when ^boolean debug?
|
||||
[:rect {:x (+ x 2)
|
||||
:y (+ y 2)
|
||||
:width (- width 4)
|
||||
:height (- height 4)
|
||||
:stroke "#f0f"
|
||||
:stroke-width 2}])]
|
||||
|
||||
;; When thumbnail is disabled.
|
||||
(when-not thumbnail?
|
||||
[:g.frame-content
|
||||
{:id (dm/str "frame-content-" frame-id)
|
||||
:ref container-ref}
|
||||
[:& frame-shape {:shape shape :ref content-ref}]])]]))))
|
||||
|
|
|
@ -1,55 +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) KALEIDOS INC
|
||||
|
||||
(ns app.main.ui.workspace.shapes.frame.node-store
|
||||
(:require
|
||||
[app.util.dom :as dom]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defn use-node-store
|
||||
"Hook responsible of storing the rendered DOM node in memory while not being used"
|
||||
[node-ref rendered-ref thumbnail? render-frame?]
|
||||
|
||||
(let [re-render* (mf/use-state 0)
|
||||
parent-ref (mf/use-ref nil)
|
||||
present-ref (mf/use-ref false)
|
||||
|
||||
on-frame-load
|
||||
(mf/use-fn
|
||||
(fn [node]
|
||||
(when (and (some? node)
|
||||
(nil? (mf/ref-val node-ref)))
|
||||
(let [content (-> (dom/create-element "http://www.w3.org/2000/svg" "g")
|
||||
(dom/add-class! "frame-content"))]
|
||||
(mf/set-ref-val! node-ref content)
|
||||
(mf/set-ref-val! parent-ref node)
|
||||
(swap! re-render* inc)))))]
|
||||
|
||||
(mf/with-effect [thumbnail? render-frame?]
|
||||
(let [rendered? (mf/ref-val rendered-ref)
|
||||
present? (mf/ref-val present-ref)]
|
||||
|
||||
(when (and (true? rendered?)
|
||||
(true? thumbnail?)
|
||||
(false? render-frame?)
|
||||
(true? present?))
|
||||
(when-let [parent (mf/ref-val parent-ref)]
|
||||
(when-let [node (mf/ref-val node-ref)]
|
||||
(dom/remove-child! parent node)
|
||||
(mf/set-ref-val! present-ref false)
|
||||
(swap! re-render* inc))))
|
||||
|
||||
(when (and (false? present?)
|
||||
(or (false? thumbnail?)
|
||||
(true? render-frame?)))
|
||||
(when-let [parent (mf/ref-val parent-ref)]
|
||||
(when-let [node (mf/ref-val node-ref)]
|
||||
(when-not (dom/child? parent node)
|
||||
(dom/append-child! parent node)
|
||||
(mf/set-ref-val! present-ref true)
|
||||
(swap! re-render* inc)))))))
|
||||
|
||||
on-frame-load))
|
|
@ -1,265 +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) KALEIDOS INC
|
||||
|
||||
(ns app.main.ui.workspace.shapes.frame.thumbnail-render
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.rect :as grc]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.config :as cf]
|
||||
[app.main.data.workspace.thumbnails :as dwt]
|
||||
[app.main.rasterizer :as thr]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.main.ui.shapes.frame :as frame]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.thumbnails :as th]
|
||||
[app.util.timers :as ts]
|
||||
[app.util.webapi :as wapi]
|
||||
[beicon.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
[debug :refer [debug?]]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defn- remove-image-loading
|
||||
"Remove the changes related to change a url for its embed value. This is necessary
|
||||
so we don't have to recalculate the thumbnail when the image loads."
|
||||
[value]
|
||||
(if (.isArray js/Array value)
|
||||
(->> value
|
||||
(remove (fn [change]
|
||||
(or (= "data-loading" (.-attributeName change))
|
||||
(and (= "attributes" (.-type change))
|
||||
(= "href" (.-attributeName change))
|
||||
(str/starts-with? (.-oldValue change) "http"))))))
|
||||
[value]))
|
||||
|
||||
(defn- create-svg-blob-uri-from
|
||||
[rect node style-node]
|
||||
(let [{:keys [x y width height]} rect
|
||||
viewbox (dm/str x " " y " " width " " height)
|
||||
|
||||
;; Calculate the fixed width and height
|
||||
;; We don't want to generate thumbnails
|
||||
;; bigger than 2000px
|
||||
[fixed-width fixed-height] (th/get-proportional-size width height)
|
||||
|
||||
;; This is way faster than creating a node
|
||||
;; through the DOM API
|
||||
svg-data
|
||||
(dm/fmt "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"%\" width=\"%\" height=\"%\" fill=\"none\">% %</svg>"
|
||||
viewbox
|
||||
fixed-width
|
||||
fixed-height
|
||||
(if (some? style-node) (dom/node->xml style-node) "")
|
||||
(dom/node->xml node))]
|
||||
|
||||
(->> (rx/of {:data svg-data :width fixed-width})
|
||||
(rx/mapcat thr/render)
|
||||
(rx/map wapi/create-uri))))
|
||||
|
||||
(defn use-render-thumbnail
|
||||
"Hook that will create the thumbnail data"
|
||||
[page-id {:keys [id] :as shape} root-ref node-ref rendered-ref disable? force-render]
|
||||
|
||||
(let [;; frame-image-ref (mf/use-ref nil)
|
||||
|
||||
disable-ref (mf/use-ref disable?)
|
||||
regenerate-ref (mf/use-ref false)
|
||||
|
||||
all-children-ref (mf/with-memo [id]
|
||||
(refs/all-children-objects id))
|
||||
all-children (mf/deref all-children-ref)
|
||||
|
||||
bounds
|
||||
(if (:show-content shape)
|
||||
(gsh/shapes->rect (cons shape all-children))
|
||||
(-> shape :points grc/points->rect))
|
||||
|
||||
x (dm/get-prop bounds :x)
|
||||
y (dm/get-prop bounds :y)
|
||||
width (dm/get-prop bounds :width)
|
||||
height (dm/get-prop bounds :height)
|
||||
|
||||
;; TODO: Si usamos rasterizer ya no necesitamos esto.
|
||||
;; svg-uri* (mf/use-state nil)
|
||||
;; svg-uri (deref svg-uri*)
|
||||
|
||||
bitmap-uri* (mf/use-state nil)
|
||||
bitmap-uri (deref bitmap-uri*)
|
||||
|
||||
observer-ref (mf/use-ref nil)
|
||||
|
||||
bounds-ref (hooks/use-update-ref bounds)
|
||||
updates-s (mf/use-memo rx/subject)
|
||||
|
||||
thumbnail-uri* (mf/with-memo [page-id id]
|
||||
(refs/thumbnail-frame-data page-id id))
|
||||
thumbnail-uri (mf/deref thumbnail-uri*)
|
||||
|
||||
;; State to indicate to the parent that should render the frame
|
||||
render-frame* (mf/use-state (not thumbnail-uri))
|
||||
render-frame? (deref render-frame*)
|
||||
|
||||
debug? (debug? :thumbnails)
|
||||
|
||||
;; FIXME: Esto habría que sacarlo de aquí y llamarlo inmediatamente cuando se crea el frame
|
||||
;; además ya no tiene sentido utilizar el elemento del DOM porque no lo vamos a usar
|
||||
;; para nada.
|
||||
on-bitmap-load
|
||||
(mf/use-fn
|
||||
(mf/deps thumbnail-uri)
|
||||
(fn []
|
||||
;; If we don't have the thumbnail data saved (normally the first load) we update the data
|
||||
;; when available
|
||||
(when-not (some? thumbnail-uri)
|
||||
(st/emit! (dwt/update-thumbnail page-id id)))
|
||||
|
||||
(reset! render-frame* false)))
|
||||
|
||||
generate-thumbnail
|
||||
(mf/use-fn
|
||||
(mf/deps id)
|
||||
(fn generate-thumbnail []
|
||||
(try
|
||||
;; When starting generating the canvas we mark it as not ready so its not send to back until
|
||||
;; we have time to update it.
|
||||
(when-let [node (mf/ref-val node-ref)]
|
||||
(if (dom/has-children? node)
|
||||
;; The frame-content need to have children in order to generate the thumbnail.
|
||||
(let [style-node (dom/query (dm/str "#frame-container-" id " style"))
|
||||
bounds (mf/ref-val bounds-ref)
|
||||
stream (create-svg-blob-uri-from bounds node style-node)]
|
||||
|
||||
(->> stream
|
||||
(rx/subs (fn [uri] (reset! bitmap-uri* uri)))))
|
||||
|
||||
;; Node not yet ready, we schedule a new generation.
|
||||
(ts/raf generate-thumbnail)))
|
||||
(catch :default e
|
||||
(.error js/console e)))))
|
||||
|
||||
on-change-frame
|
||||
(mf/use-fn
|
||||
(mf/deps id generate-thumbnail)
|
||||
(fn []
|
||||
(when (and (some? (mf/ref-val node-ref))
|
||||
(some? (mf/ref-val rendered-ref))
|
||||
(some? (mf/ref-val regenerate-ref)))
|
||||
(let [node (mf/ref-val node-ref)
|
||||
loading-images? (some? (dom/query node "[data-loading='true']"))
|
||||
loading-fonts? (some? (dom/query (dm/str "#frame-container-" id " > style[data-loading='true']")))]
|
||||
(when (and (not loading-images?)
|
||||
(not loading-fonts?))
|
||||
;; (reset! svg-uri* nil)
|
||||
(reset! bitmap-uri* nil)
|
||||
(generate-thumbnail)
|
||||
(mf/set-ref-val! regenerate-ref false))))))
|
||||
|
||||
;; When the frame is updated, it is marked as not ready
|
||||
;; so that it is not sent to the background until
|
||||
;; it is regenerated.
|
||||
on-update-frame
|
||||
(mf/use-fn
|
||||
(fn []
|
||||
(when-not ^boolean (mf/ref-val disable-ref)
|
||||
;; (reset! svg-uri* nil)
|
||||
(reset! bitmap-uri* nil)
|
||||
(reset! render-frame* true)
|
||||
(mf/set-ref-val! regenerate-ref true))))
|
||||
|
||||
on-load-frame-dom
|
||||
(mf/use-fn
|
||||
(fn [node]
|
||||
(when (and (nil? (mf/ref-val observer-ref)) (some? node))
|
||||
(when-not (some? @thumbnail-uri*)
|
||||
(rx/push! updates-s :update))
|
||||
|
||||
(let [observer (js/MutationObserver. (partial rx/push! updates-s))]
|
||||
(.observe observer node #js {:childList true
|
||||
:attributes true
|
||||
:attributeOldValue true
|
||||
:characterData true
|
||||
:subtree true})
|
||||
(mf/set-ref-val! observer-ref observer)))))]
|
||||
|
||||
(mf/with-effect [thumbnail-uri]
|
||||
(when (some? thumbnail-uri)
|
||||
(reset! bitmap-uri* thumbnail-uri)))
|
||||
|
||||
(mf/with-effect [force-render]
|
||||
(when ^boolean force-render
|
||||
(rx/push! updates-s :update)))
|
||||
|
||||
(mf/with-effect []
|
||||
(let [subid (->> updates-s
|
||||
(rx/map remove-image-loading)
|
||||
(rx/filter d/not-empty?)
|
||||
(rx/catch (fn [err] (.error js/console err)))
|
||||
(rx/subs on-update-frame))]
|
||||
(partial rx/dispose! subid)))
|
||||
|
||||
;; on-change-frame will get every change in the frame
|
||||
(mf/with-effect []
|
||||
(let [subid (->> updates-s
|
||||
(rx/debounce 400)
|
||||
(rx/observe-on :af)
|
||||
(rx/catch (fn [err] (.error js/console err)))
|
||||
(rx/subs on-change-frame))]
|
||||
(partial rx/dispose! subid)))
|
||||
|
||||
(mf/with-effect [disable?]
|
||||
(when (and ^boolean disable? (not (mf/ref-val disable-ref)))
|
||||
(rx/push! updates-s :update))
|
||||
|
||||
(mf/set-ref-val! disable-ref disable?)
|
||||
nil)
|
||||
|
||||
(mf/with-effect []
|
||||
(fn []
|
||||
(when (and (some? (mf/ref-val node-ref))
|
||||
(true? (mf/ref-val rendered-ref)))
|
||||
(when-let [root (mf/ref-val root-ref)]
|
||||
;; NOTE: the unmount should be always scheduled to be
|
||||
;; executed asynchronously of the current flow (react
|
||||
;; rules).
|
||||
(ts/schedule #(mf/unmount! ^js root)))
|
||||
|
||||
(mf/set-ref-val! node-ref nil)
|
||||
(mf/set-ref-val! rendered-ref false)
|
||||
|
||||
(when-let [observer (mf/ref-val observer-ref)]
|
||||
(.disconnect ^js observer)
|
||||
(mf/set-ref-val! observer-ref nil)))))
|
||||
|
||||
[on-load-frame-dom render-frame?
|
||||
(mf/html
|
||||
[:& frame/frame-container {:bounds bounds :shape shape}
|
||||
|
||||
;; Safari don't support filters so instead we add a rectangle around the thumbnail
|
||||
(when (and (cf/check-browser? :safari)
|
||||
^boolean debug?)
|
||||
[:rect {:x (+ x 2)
|
||||
:y (+ y 2)
|
||||
:width (- width 4)
|
||||
:height (- height 4)
|
||||
:stroke "blue"
|
||||
:stroke-width 2}])
|
||||
|
||||
;; The frame content is the only thing that we want to render
|
||||
(when (some? bitmap-uri)
|
||||
[:image.thumbnail-bitmap
|
||||
{:x x
|
||||
:y y
|
||||
:key (dm/str "thumbnail-canvas-" id)
|
||||
:data-object-id (dm/str page-id id)
|
||||
:width width
|
||||
:height height
|
||||
:href bitmap-uri
|
||||
:style {:filter (when ^boolean debug? "sepia(1)")}
|
||||
:on-load on-bitmap-load}])])]))
|
|
@ -240,7 +240,9 @@
|
|||
|
||||
dest-shape-id (:id dest-shape)
|
||||
|
||||
thumbnail-data-ref (mf/use-memo (mf/deps page-id dest-shape-id) #(refs/thumbnail-frame-data page-id dest-shape-id))
|
||||
thumbnail-data-ref (mf/use-memo
|
||||
(mf/deps page-id dest-shape-id)
|
||||
#(refs/thumbnail-frame-data dest-shape-id))
|
||||
thumbnail-data (mf/deref thumbnail-data-ref)
|
||||
|
||||
dest-shape (cond-> dest-shape
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
(mf/deps viewport-node)
|
||||
(fn [event]
|
||||
(when-let [image-data (mf/ref-val canvas-image-data)]
|
||||
(when-let [zoom-view-node (.getElementById js/document "picker-detail")]
|
||||
(when-let [zoom-view-node (dom/get-element "picker-detail")]
|
||||
(when-not (mf/ref-val zoom-view-context)
|
||||
(mf/set-ref-val! zoom-view-context (.getContext zoom-view-node "2d")))
|
||||
(let [{brx :left bry :top} (dom/get-bounding-rect viewport-node)
|
||||
|
|
|
@ -326,6 +326,11 @@
|
|||
(.removeChild ^js el child))
|
||||
el)
|
||||
|
||||
(defn remove!
|
||||
[^js el]
|
||||
(when (some? el)
|
||||
(.remove ^js el)))
|
||||
|
||||
(defn get-first-child
|
||||
[^js el]
|
||||
(when (some? el)
|
||||
|
|
15
frontend/src/app/util/imposters.cljs
Normal file
15
frontend/src/app/util/imposters.cljs
Normal file
|
@ -0,0 +1,15 @@
|
|||
;; 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.util.imposters)
|
||||
|
||||
;; This is needed to avoid a circular dependency between
|
||||
;; app.main.ui.workspace.shapes.frame and app.util.imposters
|
||||
(defonce render-fn (atom nil))
|
||||
|
||||
(defn init!
|
||||
[fn]
|
||||
(reset! render-fn fn))
|
|
@ -7,19 +7,15 @@
|
|||
(ns app.worker.thumbnails
|
||||
(:require
|
||||
["react-dom/server" :as rds]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.logging :as log]
|
||||
[app.common.uri :as u]
|
||||
[app.config :as cf]
|
||||
[app.main.fonts :as fonts]
|
||||
[app.main.render :as render]
|
||||
[app.util.http :as http]
|
||||
[app.util.time :as ts]
|
||||
[app.util.webapi :as wapi]
|
||||
[app.worker.impl :as impl]
|
||||
[beicon.core :as rx]
|
||||
[okulary.core :as l]
|
||||
[promesa.core :as p]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(log/set-level! :trace)
|
||||
|
@ -82,17 +78,3 @@
|
|||
[{:keys [file-id revn features] :as message} _]
|
||||
(->> (request-data-for-thumbnail file-id revn features)
|
||||
(rx/map render-thumbnail)))
|
||||
|
||||
(defmethod impl/handler :thumbnails/render-offscreen-canvas
|
||||
[_ ibpm]
|
||||
(let [canvas (js/OffscreenCanvas. (.-width ^js ibpm) (.-height ^js ibpm))
|
||||
ctx (.getContext ^js canvas "bitmaprenderer")
|
||||
tp (ts/tpoint-ms)]
|
||||
(.transferFromImageBitmap ^js ctx ibpm)
|
||||
(->> (.convertToBlob ^js canvas #js {:type "image/png"})
|
||||
(p/fmap (fn [blob]
|
||||
{:result (wapi/create-uri blob)}))
|
||||
(p/fnly (fn [_]
|
||||
(log/debug :hint "generated thumbnail" :elapsed (dm/str (tp) "ms"))
|
||||
(.close ^js ibpm))))))
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue