mirror of
https://github.com/penpot/penpot.git
synced 2025-02-03 21:09:00 -05:00
⚡ Add performance improvements to file thumbnails
Mainly addresing unnecesary object transmission. The new code strips unnecesary data to be transferred from back to front. Additionally it removes some legacy code and simplifies other parts of code.
This commit is contained in:
parent
27c8f883ff
commit
b91c42e186
6 changed files with 72 additions and 118 deletions
|
@ -320,7 +320,7 @@
|
||||||
_ (mtx/run! metrics {:id :update-file-changes :inc (count changes)})
|
_ (mtx/run! metrics {:id :update-file-changes :inc (count changes)})
|
||||||
|
|
||||||
ts (dt/now)
|
ts (dt/now)
|
||||||
file (-> (files/retrieve-data cfg file)
|
file (-> file
|
||||||
(update :revn inc)
|
(update :revn inc)
|
||||||
(update :data (fn [data]
|
(update :data (fn [data]
|
||||||
;; Trace the length of bytes of processed data
|
;; Trace the length of bytes of processed data
|
||||||
|
|
|
@ -16,11 +16,9 @@
|
||||||
[app.rpc.queries.projects :as projects]
|
[app.rpc.queries.projects :as projects]
|
||||||
[app.rpc.queries.share-link :refer [retrieve-share-link]]
|
[app.rpc.queries.share-link :refer [retrieve-share-link]]
|
||||||
[app.rpc.queries.teams :as teams]
|
[app.rpc.queries.teams :as teams]
|
||||||
[app.storage.impl :as simpl]
|
|
||||||
[app.util.blob :as blob]
|
[app.util.blob :as blob]
|
||||||
[app.util.services :as sv]
|
[app.util.services :as sv]
|
||||||
[clojure.spec.alpha :as s]
|
[clojure.spec.alpha :as s]))
|
||||||
[promesa.core :as p]))
|
|
||||||
|
|
||||||
(declare decode-row)
|
(declare decode-row)
|
||||||
(declare decode-row-xf)
|
(declare decode-row-xf)
|
||||||
|
@ -186,25 +184,12 @@
|
||||||
|
|
||||||
;; --- Query: File (By ID)
|
;; --- Query: File (By ID)
|
||||||
|
|
||||||
(defn- retrieve-data*
|
|
||||||
[{:keys [storage] :as cfg} file]
|
|
||||||
(p/do
|
|
||||||
(when-let [backend (simpl/resolve-backend storage (:data-backend file))]
|
|
||||||
(simpl/get-object-bytes backend file))))
|
|
||||||
|
|
||||||
(defn retrieve-data
|
|
||||||
[cfg file]
|
|
||||||
(if (bytes? (:data file))
|
|
||||||
file
|
|
||||||
(p/->> (retrieve-data* cfg file)
|
|
||||||
(assoc file :data))))
|
|
||||||
|
|
||||||
(defn retrieve-file
|
(defn retrieve-file
|
||||||
[{:keys [pool] :as cfg} id]
|
[{:keys [pool] :as cfg} id]
|
||||||
(p/->> (db/get-by-id pool :file id)
|
(let [item (db/get-by-id pool :file id)]
|
||||||
(retrieve-data cfg)
|
(->> item
|
||||||
(decode-row)
|
(decode-row)
|
||||||
(pmg/migrate-file)))
|
(pmg/migrate-file))))
|
||||||
|
|
||||||
(s/def ::file
|
(s/def ::file
|
||||||
(s/keys :req-un [::profile-id ::id]))
|
(s/keys :req-un [::profile-id ::id]))
|
||||||
|
@ -214,8 +199,8 @@
|
||||||
[{:keys [pool] :as cfg} {:keys [profile-id id] :as params}]
|
[{:keys [pool] :as cfg} {:keys [profile-id id] :as params}]
|
||||||
(let [perms (get-permissions pool profile-id id)]
|
(let [perms (get-permissions pool profile-id id)]
|
||||||
(check-read-permissions! perms)
|
(check-read-permissions! perms)
|
||||||
(p/-> (retrieve-file cfg id)
|
(-> (retrieve-file cfg id)
|
||||||
(assoc :permissions perms))))
|
(assoc :permissions perms))))
|
||||||
|
|
||||||
(declare trim-file-data)
|
(declare trim-file-data)
|
||||||
|
|
||||||
|
@ -233,9 +218,9 @@
|
||||||
[{:keys [pool] :as cfg} {:keys [profile-id id] :as params}]
|
[{:keys [pool] :as cfg} {:keys [profile-id id] :as params}]
|
||||||
(let [perms (get-permissions pool profile-id id)]
|
(let [perms (get-permissions pool profile-id id)]
|
||||||
(check-read-permissions! perms)
|
(check-read-permissions! perms)
|
||||||
(p/-> (retrieve-file cfg id)
|
(-> (retrieve-file cfg id)
|
||||||
(trim-file-data params)
|
(trim-file-data params)
|
||||||
(assoc :permissions perms))))
|
(assoc :permissions perms))))
|
||||||
|
|
||||||
(defn- trim-file-data
|
(defn- trim-file-data
|
||||||
[file {:keys [page-id object-id]}]
|
[file {:keys [page-id object-id]}]
|
||||||
|
@ -248,9 +233,12 @@
|
||||||
(update :data assoc :pages-index {page-id page})
|
(update :data assoc :pages-index {page-id page})
|
||||||
(update :data assoc :pages [page-id]))))
|
(update :data assoc :pages [page-id]))))
|
||||||
|
|
||||||
|
;; --- FILE THUMBNAIL
|
||||||
|
|
||||||
(declare strip-frames-with-thumbnails)
|
(declare strip-frames-with-thumbnails)
|
||||||
(declare extract-file-thumbnail)
|
(declare extract-file-thumbnail)
|
||||||
(declare get-first-page-data)
|
(declare get-first-page-data)
|
||||||
|
(declare get-thumbnail-data)
|
||||||
|
|
||||||
(s/def ::strip-frames-with-thumbnails ::us/boolean)
|
(s/def ::strip-frames-with-thumbnails ::us/boolean)
|
||||||
|
|
||||||
|
@ -258,6 +246,17 @@
|
||||||
(s/keys :req-un [::profile-id ::file-id]
|
(s/keys :req-un [::profile-id ::file-id]
|
||||||
:opt-un [::strip-frames-with-thumbnails]))
|
:opt-un [::strip-frames-with-thumbnails]))
|
||||||
|
|
||||||
|
(sv/defmethod ::page
|
||||||
|
"Retrieves the first page of the file. Used mainly for render
|
||||||
|
thumbnails on dashboard.
|
||||||
|
|
||||||
|
DEPRECATED: still here for backward compatibility."
|
||||||
|
[{:keys [pool] :as cfg} {:keys [profile-id file-id] :as props}]
|
||||||
|
(check-read-permissions! pool profile-id file-id)
|
||||||
|
(let [file (retrieve-file cfg file-id)
|
||||||
|
data (get-first-page-data file props)]
|
||||||
|
data))
|
||||||
|
|
||||||
(s/def ::file-data-for-thumbnail
|
(s/def ::file-data-for-thumbnail
|
||||||
(s/keys :req-un [::profile-id ::file-id]
|
(s/keys :req-un [::profile-id ::file-id]
|
||||||
:opt-un [::strip-frames-with-thumbnails]))
|
:opt-un [::strip-frames-with-thumbnails]))
|
||||||
|
@ -267,20 +266,29 @@
|
||||||
thumbnails on dashboard."
|
thumbnails on dashboard."
|
||||||
[{:keys [pool] :as cfg} {:keys [profile-id file-id] :as props}]
|
[{:keys [pool] :as cfg} {:keys [profile-id file-id] :as props}]
|
||||||
(check-read-permissions! pool profile-id file-id)
|
(check-read-permissions! pool profile-id file-id)
|
||||||
(p/let [file (retrieve-file cfg file-id)
|
(let [file (retrieve-file cfg file-id)]
|
||||||
data (get-first-page-data file props)
|
(get-thumbnail-data file props)))
|
||||||
file-thumbnail (extract-file-thumbnail (get-in file [:data :pages-index]))]
|
|
||||||
|
|
||||||
(assoc data :file-thumbnail file-thumbnail)))
|
(defn get-thumbnail-data
|
||||||
|
[{:keys [data] :as file} props]
|
||||||
|
(if-let [[page frame] (first
|
||||||
|
(for [page (-> data :pages-index vals)
|
||||||
|
frame (-> page :objects cph/get-frames)
|
||||||
|
:when (:file-thumbnail frame)]
|
||||||
|
[page frame]))]
|
||||||
|
(let [objects (->> (cph/get-children-with-self (:objects page) (:id frame))
|
||||||
|
(d/index-by :id))]
|
||||||
|
(cond-> (assoc page :objects objects)
|
||||||
|
(:strip-frames-with-thumbnails props)
|
||||||
|
(strip-frames-with-thumbnails)
|
||||||
|
|
||||||
(sv/defmethod ::page
|
:always
|
||||||
"Retrieves the first page of the file. Used mainly for render
|
(assoc :thumbnail-frame frame)))
|
||||||
thumbnails on dashboard."
|
|
||||||
[{:keys [pool] :as cfg} {:keys [profile-id file-id] :as props}]
|
(let [page-id (-> data :pages first)]
|
||||||
(check-read-permissions! pool profile-id file-id)
|
(cond-> (get-in data [:pages-index page-id])
|
||||||
(p/let [file (retrieve-file cfg file-id)
|
(:strip-frames-with-thumbnails props)
|
||||||
data (get-first-page-data file props)]
|
(strip-frames-with-thumbnails)))))
|
||||||
data))
|
|
||||||
|
|
||||||
(defn get-first-page-data
|
(defn get-first-page-data
|
||||||
[file props]
|
[file props]
|
||||||
|
@ -318,16 +326,6 @@
|
||||||
(update data :objects update-objects)))
|
(update data :objects update-objects)))
|
||||||
|
|
||||||
|
|
||||||
(defn extract-file-thumbnail
|
|
||||||
"Extract the frame marked as file-thumbnail"
|
|
||||||
[pages]
|
|
||||||
(->> pages
|
|
||||||
vals
|
|
||||||
(mapcat :objects)
|
|
||||||
vals
|
|
||||||
(filter :file-thumbnail)
|
|
||||||
first))
|
|
||||||
|
|
||||||
;; --- Query: Shared Library Files
|
;; --- Query: Shared Library Files
|
||||||
|
|
||||||
(def ^:private sql:team-shared-files
|
(def ^:private sql:team-shared-files
|
||||||
|
@ -384,7 +382,6 @@
|
||||||
[{:keys [pool] :as cfg} is-indirect file-id]
|
[{:keys [pool] :as cfg} is-indirect file-id]
|
||||||
(let [xform (comp
|
(let [xform (comp
|
||||||
(map #(assoc % :is-indirect is-indirect))
|
(map #(assoc % :is-indirect is-indirect))
|
||||||
(map #(retrieve-data cfg %))
|
|
||||||
(map decode-row))]
|
(map decode-row))]
|
||||||
(into #{} xform (db/exec! pool [sql:file-libraries file-id]))))
|
(into #{} xform (db/exec! pool [sql:file-libraries file-id]))))
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,7 @@
|
||||||
|
|
||||||
(s/def ::hide-fill-on-export boolean?)
|
(s/def ::hide-fill-on-export boolean?)
|
||||||
|
|
||||||
|
(s/def ::file-thumbnail boolean?)
|
||||||
(s/def ::masked-group? boolean?)
|
(s/def ::masked-group? boolean?)
|
||||||
(s/def ::font-family string?)
|
(s/def ::font-family string?)
|
||||||
(s/def ::font-size ::us/safe-integer)
|
(s/def ::font-size ::us/safe-integer)
|
||||||
|
@ -301,7 +302,8 @@
|
||||||
|
|
||||||
(defmethod shape-spec :frame [_]
|
(defmethod shape-spec :frame [_]
|
||||||
(s/and ::shape-attrs
|
(s/and ::shape-attrs
|
||||||
(s/keys :opt-un [::hide-fill-on-export])))
|
(s/keys :opt-un [::file-thumbnail
|
||||||
|
::hide-fill-on-export])))
|
||||||
|
|
||||||
(s/def ::shape
|
(s/def ::shape
|
||||||
(s/and (s/multi-spec shape-spec :type)
|
(s/and (s/multi-spec shape-spec :type)
|
||||||
|
|
|
@ -957,35 +957,23 @@
|
||||||
(let [selected (wsh/lookup-selected state)]
|
(let [selected (wsh/lookup-selected state)]
|
||||||
(rx/of (dch/update-shapes selected #(update % :blocked not)))))))
|
(rx/of (dch/update-shapes selected #(update % :blocked not)))))))
|
||||||
|
|
||||||
(defn extract-file-thumbnails-from-page
|
|
||||||
[state selected page]
|
|
||||||
(let [extract-frames (fn [page-id]
|
|
||||||
(let [objects (wsh/lookup-page-objects state page-id)]
|
|
||||||
(cph/get-frames objects)))
|
|
||||||
page-id (key page)
|
|
||||||
frames-with-thumbnail (->> (extract-frames page-id)
|
|
||||||
(filter (comp true? :file-thumbnail))
|
|
||||||
(map :id)
|
|
||||||
(remove #(some #{%} selected))
|
|
||||||
(map #(into {} {:id % :page-id page-id})))]
|
|
||||||
(when frames-with-thumbnail frames-with-thumbnail)))
|
|
||||||
|
|
||||||
|
|
||||||
(defn toggle-file-thumbnail-selected
|
(defn toggle-file-thumbnail-selected
|
||||||
[]
|
[]
|
||||||
(ptk/reify ::toggle-file-thumbnail-selected
|
(ptk/reify ::toggle-file-thumbnail-selected
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state _]
|
(watch [_ state _]
|
||||||
(let [selected (wsh/lookup-selected state)
|
(let [selected (wsh/lookup-selected state)
|
||||||
pages (get-in state [:workspace-data
|
pages (-> state :workspace-data :pages-index vals)
|
||||||
:pages-index])
|
extract (fn [{:keys [objects id] :as page}]
|
||||||
file-thumbnails (->> pages
|
(->> (cph/get-frames objects)
|
||||||
(mapcat #(extract-file-thumbnails-from-page state selected %)))]
|
(filter :file-thumbnail)
|
||||||
|
(map :id)
|
||||||
|
(remove selected)
|
||||||
|
(map (fn [frame-id] [id frame-id]))))]
|
||||||
(rx/concat
|
(rx/concat
|
||||||
(rx/from
|
(rx/from (for [[page-id frame-id] (mapcat extract pages)]
|
||||||
(for [ft file-thumbnails]
|
(dch/update-shapes [frame-id] #(dissoc % :file-thumbnail) page-id nil)))
|
||||||
(dch/update-shapes [(:id ft)] #(update % :file-thumbnail not) (:page-id ft) nil)))
|
(rx/of (dch/update-shapes selected #(assoc % :file-thumbnail true))))))))
|
||||||
(rx/of (dch/update-shapes selected #(update % :file-thumbnail not))))))))
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; Navigation
|
;; Navigation
|
||||||
|
|
|
@ -214,31 +214,6 @@
|
||||||
[:& shape-wrapper {:shape item
|
[:& shape-wrapper {:shape item
|
||||||
:key (:id item)}])))]]]))
|
:key (:id item)}])))]]]))
|
||||||
|
|
||||||
(mf/defc file-thumbnail-svg
|
|
||||||
{::mf/wrap [mf/memo]}
|
|
||||||
[{:keys [data embed? include-metadata?] :as props
|
|
||||||
:or {embed? false include-metadata? false}}]
|
|
||||||
(let [data (assoc data :x 0 :y 0)
|
|
||||||
vbox (format-viewbox {:width (:width data 0) :height (:height data 0)})
|
|
||||||
background-color (get-in data [:options :background] default-color)]
|
|
||||||
|
|
||||||
[:& (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")
|
|
||||||
:style {:width "100%"
|
|
||||||
:height "100%"
|
|
||||||
:background background-color}}
|
|
||||||
|
|
||||||
(when include-metadata?
|
|
||||||
[:& export/export-page {:options (:options data)}])
|
|
||||||
|
|
||||||
[:> shape-container {:shape data}
|
|
||||||
[:& frame/frame-thumbnail {:shape data}]]]]]))
|
|
||||||
|
|
||||||
(mf/defc frame-svg
|
(mf/defc frame-svg
|
||||||
{::mf/wrap [mf/memo]}
|
{::mf/wrap [mf/memo]}
|
||||||
[{:keys [objects frame zoom show-thumbnails?] :or {zoom 1} :as props}]
|
[{:keys [objects frame zoom show-thumbnails?] :or {zoom 1} :as props}]
|
||||||
|
|
|
@ -31,36 +31,28 @@
|
||||||
|
|
||||||
(defn- request-thumbnail
|
(defn- request-thumbnail
|
||||||
[file-id]
|
[file-id]
|
||||||
(let [uri (u/join (cfg/get-public-uri) "api/rpc/query/file-data-for-thumbnail")
|
(let [uri (u/join (cfg/get-public-uri) "api/rpc/query/file-data-for-thumbnail")
|
||||||
params {:file-id file-id
|
params {:file-id file-id
|
||||||
:strip-frames-with-thumbnails true}]
|
:strip-frames-with-thumbnails true}
|
||||||
(->> (http/send!
|
request {:method :get
|
||||||
{:method :get
|
:uri uri
|
||||||
:uri uri
|
:credentials "include"
|
||||||
:credentials "include"
|
:query params}]
|
||||||
:query params})
|
(->> (http/send! request)
|
||||||
(rx/map http/conditional-decode-transit)
|
(rx/map http/conditional-decode-transit)
|
||||||
(rx/mapcat handle-response))))
|
(rx/mapcat handle-response))))
|
||||||
|
|
||||||
(defonce cache (atom {}))
|
|
||||||
|
|
||||||
(defn render-frame
|
(defn render-frame
|
||||||
[data ckey]
|
[data]
|
||||||
(let [prev (get @cache ckey)]
|
(let [elem (if-let [frame (:thumbnail-frame data)]
|
||||||
(if (= (:data prev) data)
|
(mf/element render/frame-svg #js {:objects (:objects data) :frame frame})
|
||||||
(:result prev)
|
(mf/element render/page-svg #js {:data data :width "290" :height "150" :thumbnails? true}))]
|
||||||
(let [file-thumbnail (:file-thumbnail data)
|
(rds/renderToStaticMarkup elem)))
|
||||||
elem (if file-thumbnail
|
|
||||||
(mf/element render/file-thumbnail-svg #js {:data file-thumbnail :width "290" :height "150"})
|
|
||||||
(mf/element render/page-svg #js {:data data :width "290" :height "150" :thumbnails? true}))
|
|
||||||
result (rds/renderToStaticMarkup elem)]
|
|
||||||
(swap! cache assoc ckey {:data data :result result})
|
|
||||||
result))))
|
|
||||||
|
|
||||||
(defmethod impl/handler :thumbnails/generate
|
(defmethod impl/handler :thumbnails/generate
|
||||||
[{:keys [file-id] :as message}]
|
[{:keys [file-id] :as message}]
|
||||||
(->> (request-thumbnail file-id)
|
(->> (request-thumbnail file-id)
|
||||||
(rx/map
|
(rx/map
|
||||||
(fn [data]
|
(fn [data]
|
||||||
{:svg (render-frame data #{file-id})
|
{:svg (render-frame data)
|
||||||
:fonts @fonts/loaded}))))
|
:fonts @fonts/loaded}))))
|
||||||
|
|
Loading…
Add table
Reference in a new issue