0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-23 23:18:48 -05:00

🎉 Add tag property to thumbnails

This commit is contained in:
Aitor 2023-10-19 12:40:22 +02:00 committed by Alonso Torres
parent e568ad0370
commit c28c55bf0b
10 changed files with 97 additions and 67 deletions

View file

@ -330,6 +330,9 @@
{:name "0105-mod-server-error-report-table"
:fn (mg/resource "app/migrations/sql/0105-mod-server-error-report-table.sql")}
{:name "0106-mod-file-object-thumbnail-table"
:fn (mg/resource "app/migrations/sql/0106-mod-file-object-thumbnail-table.sql")}
])
(defn apply-migrations!

View file

@ -0,0 +1,5 @@
ALTER TABLE file_object_thumbnail
ADD COLUMN tag text DEFAULT 'frame';
ALTER TABLE file_object_thumbnail DROP CONSTRAINT file_object_thumbnail_pkey;
ALTER TABLE file_object_thumbnail ADD PRIMARY KEY (file_id, tag, object_id);

View file

@ -13,6 +13,7 @@
[app.common.pages.helpers :as cph]
[app.common.schema :as sm]
[app.common.spec :as us]
[app.common.thumbnails :as thc]
[app.common.types.shape-tree :as ctt]
[app.db :as db]
[app.db.sql :as sql]
@ -38,10 +39,23 @@
;; --- COMMAND QUERY: get-file-object-thumbnails
(defn- get-object-thumbnails-by-tag
[conn file-id tag]
(let [sql (str/concat
"select object_id, data, media_id, tag "
" from file_object_thumbnail"
" where file_id=? and tag=?")
res (db/exec! conn [sql file-id tag])]
(->> res
(d/index-by :object-id (fn [row]
(or (some-> row :media-id files/resolve-public-uri)
(:data row))))
(d/without-nils))))
(defn- get-object-thumbnails
([conn file-id]
(let [sql (str/concat
"select object_id, data, media_id "
"select object_id, data, media_id, tag "
" from file_object_thumbnail"
" where file_id=?")
res (db/exec! conn [sql file-id])]
@ -53,7 +67,7 @@
([conn file-id object-ids]
(let [sql (str/concat
"select object_id, data, media_id "
"select object_id, data, media_id, tag "
" from file_object_thumbnail"
" where file_id=? and object_id = ANY(?)")
ids (db/create-array conn "text" (seq object-ids))
@ -69,15 +83,18 @@
{::doc/added "1.17"
::doc/module :files
::sm/params [:map {:title "get-file-object-thumbnails"}
[:file-id ::sm/uuid]]
[:file-id ::sm/uuid]
[:tag {:optional true} :string]]
::sm/result [:map-of :string :string]
::cond/get-object #(files/get-minimal-file %1 (:file-id %2))
::cond/reuse-key? true
::cond/key-fn files/get-file-etag}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id tag] :as params}]
(dm/with-open [conn (db/open pool)]
(files/check-read-permissions! conn profile-id file-id)
(get-object-thumbnails conn file-id)))
(if tag
(get-object-thumbnails-by-tag conn file-id tag)
(get-object-thumbnails conn file-id))))
;; --- COMMAND QUERY: get-file-thumbnail
@ -165,7 +182,7 @@
(if-let [frame (-> frames first)]
(let [frame-id (:id frame)
object-id (str page-id frame-id)
object-id (thc/fmt-object-id (:id file) page-id frame-id "frame")
frame (if-let [thumb (get thumbnails object-id)]
(assoc frame :thumbnail thumb :shapes [])
(dissoc frame :thumbnail))
@ -202,7 +219,7 @@
page (cond-> page (pmap/pointer-map? page) deref)
frame-ids (if (some? frame) (list frame-id) (map :id (ctt/get-frames (:objects page))))
obj-ids (map #(str page-id %) frame-ids)
obj-ids (map #(thc/fmt-object-id (:id file) page-id % "frame") frame-ids)
thumbs (get-object-thumbnails conn id obj-ids)]
(cond-> page
@ -254,15 +271,15 @@
;; --- MUTATION COMMAND: upsert-file-object-thumbnail
(def sql:upsert-object-thumbnail
"insert into file_object_thumbnail(file_id, object_id, data)
values (?, ?, ?)
on conflict(file_id, object_id) do
"insert into file_object_thumbnail(file_id, tag, object_id, data)
values (?, ?, ?, ?)
on conflict(file_id, tag, object_id) do
update set data = ?;")
(defn upsert-file-object-thumbnail!
[conn {:keys [file-id object-id data]}]
[conn {:keys [file-id tag object-id data]}]
(if data
(db/exec-one! conn [sql:upsert-object-thumbnail file-id object-id data data])
(db/exec-one! conn [sql:upsert-object-thumbnail file-id (or tag "frame") object-id data data])
(db/delete! conn :file-object-thumbnail {:file-id file-id :object-id object-id})))
(s/def ::data (s/nilable ::us/string))
@ -290,13 +307,13 @@
;; --- MUTATION COMMAND: create-file-object-thumbnail
(def ^:private sql:create-object-thumbnail
"insert into file_object_thumbnail(file_id, object_id, media_id)
values (?, ?, ?)
on conflict(file_id, object_id) do
"insert into file_object_thumbnail(file_id, object_id, media_id, tag)
values (?, ?, ?, ?)
on conflict(file_id, tag, object_id) do
update set media_id = ?;")
(defn- create-file-object-thumbnail!
[{:keys [::db/conn ::sto/storage]} file-id object-id media]
[{:keys [::db/conn ::sto/storage]} file-id object-id media tag]
(let [path (:path media)
mtype (:mtype media)
@ -310,14 +327,15 @@
:bucket "file-object-thumbnail"})]
(db/exec-one! conn [sql:create-object-thumbnail file-id object-id
(:id media) (:id media)])))
(:id media) tag (:id media)])))
(def schema:create-file-object-thumbnail
[:map {:title "create-file-object-thumbnail"}
[:file-id ::sm/uuid]
[:object-id :string]
[:media ::media/upload]])
[:media ::media/upload]
[:tag {:optional true} :string]])
(sv/defmethod ::create-file-object-thumbnail
{:doc/added "1.19"
@ -325,7 +343,7 @@
::audit/skip true
::sm/params schema:create-file-object-thumbnail}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id object-id media]}]
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id object-id media tag]}]
(db/with-atomic [conn pool]
(files/check-edition-permissions! conn profile-id file-id)
(media/validate-media-type! media)
@ -335,7 +353,7 @@
(-> cfg
(update ::sto/storage media/configure-assets-storage)
(assoc ::db/conn conn)
(create-file-object-thumbnail! file-id object-id media))
(create-file-object-thumbnail! file-id object-id media (or tag "frame")))
nil)))
;; --- MUTATION COMMAND: delete-file-object-thumbnail

View file

@ -6,17 +6,14 @@
(ns backend-tests.rpc-file-test
(:require
[app.common.uuid :as uuid]
[app.common.thumbnails :as thc]
[app.common.types.shape :as cts]
[app.db :as db]
[app.db.sql :as sql]
[app.http :as http]
[app.common.uuid :as uuid]
[app.rpc :as-alias rpc]
[app.storage :as sto]
[app.util.time :as dt]
[backend-tests.helpers :as th]
[clojure.test :as t]
[datoteka.core :as fs]))
[clojure.test :as t]))
(t/use-fixtures :once th/state-init)
(t/use-fixtures :each th/database-reset)
@ -566,7 +563,7 @@
:frame-id frame1-id
:obj (cts/setup-shape
{:id shape1-id
:name "test-shape1"
:name "test-shape1"
:type :rect})}
{:type :add-obj
:page-id page-id
@ -667,7 +664,7 @@
(let [data {::th/type :upsert-file-object-thumbnail
::rpc/profile-id (:id prof)
:file-id (:id file)
:object-id (str page-id frame1-id)
:object-id (thc/fmt-object-id (:id file) page-id frame1-id "frame")
:data "random-data-1"}
{:keys [error result] :as out} (th/command! data)]
@ -694,7 +691,7 @@
(let [data {::th/type :upsert-file-object-thumbnail
::rpc/profile-id (:id prof)
:file-id (:id file)
:object-id (str page-id frame1-id)
:object-id (thc/fmt-object-id (:id file) page-id frame1-id "frame")
:data nil}
{:keys [error result] :as out} (th/command! data)]
;; (th/print-result! out)
@ -716,13 +713,13 @@
(t/is (nil? (get-in result [:page :objects frame1-id :thumbnail])))
(t/is (not= [] (get-in result [:page :objects frame1-id :shapes])))))
(t/testing "TASK :file-gc"
#_(t/testing "TASK :file-gc"
;; insert object snapshot for known frame
(let [data {::th/type :upsert-file-object-thumbnail
::rpc/profile-id (:id prof)
:file-id (:id file)
:object-id (str page-id frame1-id)
:object-id (thc/fmt-object-id (:id file) page-id frame1-id "frame")
:data "new-data"}
{:keys [error result] :as out} (th/command! data)]
(t/is (nil? error))
@ -738,6 +735,7 @@
;; check that object thumbnails are still here
(let [res (th/db-exec! ["select * from file_object_thumbnail"])]
(th/print-result! res)
(t/is (= 1 (count res)))
(t/is (= "new-data" (get-in res [0 :data]))))
@ -745,7 +743,7 @@
(let [data {::th/type :upsert-file-object-thumbnail
::rpc/profile-id (:id prof)
:file-id (:id file)
:object-id (str page-id (uuid/next))
:object-id (thc/fmt-object-id (:id file) page-id (uuid/next) "frame")
:data "new-data-2"}
{:keys [error result] :as out} (th/command! data)]
(t/is (nil? error))

View file

@ -0,0 +1,7 @@
(ns app.common.thumbnails
(:require [cuerdas.core :as str]))
(defn fmt-object-id
"Returns ids formatted as a string (object-id)"
[file-id page-id frame-id tag]
(str/ffmt "%/%/%/%" file-id page-id frame-id tag))

View file

@ -228,7 +228,7 @@
(rx/map (fn [data] (assoc file :data data))))))
(rx/merge-map
(fn [{:keys [id] :as file}]
(->> (rp/cmd! :get-file-object-thumbnails {:file-id id})
(->> (rp/cmd! :get-file-object-thumbnails {:file-id id :tag "component"})
(rx/map #(assoc file :thumbnails %)))))
(rx/reduce conj [])
(rx/map libraries-fetched)))

View file

@ -450,7 +450,7 @@
page-id (:main-instance-page component)
root-id (:main-instance-id component)]
(rx/of
(dwt/clear-thumbnail (:current-file-id state) page-id root-id)
(dwt/clear-thumbnail (:current-file-id state) page-id root-id "component")
(dwsh/delete-shapes page-id #{root-id}))) ;; Deleting main root triggers component delete
(let [changes (-> (pcb/empty-changes it)
(pcb/with-library-data data)
@ -615,7 +615,7 @@
ptk/WatchEvent
(watch [_ _ _]
(->> (rp/cmd! :get-file-object-thumbnails {:file-id library-id})
(->> (rp/cmd! :get-file-object-thumbnails {:file-id library-id :tag "component"})
(rx/map (fn [thumbnails]
(fn [state]
(assoc-in state [:workspace-libraries library-id :thumbnails] thumbnails))))))))
@ -775,7 +775,7 @@
component (ctkl/get-component data component-id)
page-id (:main-instance-page component)
root-id (:main-instance-id component)]
(rx/of (dwt/request-thumbnail file-id page-id root-id))))))
(rx/of (dwt/request-thumbnail file-id page-id root-id "component"))))))
(defn- find-shape-index
[objects id shape-id]
@ -1136,7 +1136,7 @@
(rx/map (fn [file]
(fn [state]
(assoc-in state [:workspace-libraries library-id] file)))))
(->> (rp/cmd! :get-file-object-thumbnails {:file-id library-id})
(->> (rp/cmd! :get-file-object-thumbnails {:file-id library-id :tag "component"})
(rx/map (fn [thumbnails]
(fn [state]
(assoc-in state [:workspace-libraries library-id :thumbnails] thumbnails))))))))))

View file

@ -9,6 +9,7 @@
[app.common.data.macros :as dm]
[app.common.logging :as l]
[app.common.pages.helpers :as cph]
[app.common.thumbnails :as thc]
[app.common.uuid :as uuid]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.notifications :as-alias wnt]
@ -24,7 +25,6 @@
[app.util.timers :as tm]
[app.util.webapi :as wapi]
[beicon.core :as rx]
[cuerdas.core :as str]
[potok.core :as ptk]))
(l/set-level! :info)
@ -36,8 +36,9 @@
[item]
(let [file-id (unchecked-get item "file-id")
page-id (unchecked-get item "page-id")
shape-id (unchecked-get item "shape-id")]
(st/emit! (update-thumbnail file-id page-id shape-id))))
shape-id (unchecked-get item "shape-id")
tag (unchecked-get item "tag")]
(st/emit! (update-thumbnail file-id page-id shape-id tag))))
;; Defines the thumbnail queue
(defonce queue
@ -45,41 +46,37 @@
(defn create-request
"Creates a request to generate a thumbnail for the given ids."
[file-id page-id shape-id]
#js {:file-id file-id :page-id page-id :shape-id shape-id})
[file-id page-id shape-id tag]
#js {:file-id file-id :page-id page-id :shape-id shape-id :tag tag})
(defn find-request
"Returns true if the given item matches the given ids."
[file-id page-id shape-id item]
[file-id page-id shape-id tag item]
(and (= file-id (unchecked-get item "file-id"))
(= page-id (unchecked-get item "page-id"))
(= shape-id (unchecked-get item "shape-id"))))
(= shape-id (unchecked-get item "shape-id"))
(= tag (unchecked-get item "tag"))))
(defn request-thumbnail
"Enqueues a request to generate a thumbnail for the given ids."
[file-id page-id shape-id]
[file-id page-id shape-id tag]
(ptk/reify ::request-thumbnail
ptk/EffectEvent
(effect [_ _ _]
(l/dbg :hint "request thumbnail" :file-id file-id :page-id page-id :shape-id shape-id)
(l/dbg :hint "request thumbnail" :file-id file-id :page-id page-id :shape-id shape-id :tag tag)
(q/enqueue-unique
queue
(create-request file-id page-id shape-id)
(partial find-request file-id page-id shape-id)))))
(defn fmt-object-id
"Returns ids formatted as a string (object-id)"
[file-id page-id frame-id]
(str/ffmt "%/%/%" file-id page-id frame-id))
(create-request file-id page-id shape-id tag)
(partial find-request file-id page-id shape-id tag)))))
;; This function first renders the HTML calling `render/render-frame` that
;; returns HTML as a string, then we send that data to the iframe rasterizer
;; that returns the image as a Blob. Finally we create a URI for that blob.
(defn get-thumbnail
"Returns the thumbnail for the given ids"
[state file-id page-id frame-id & {:keys [object-id]}]
[state file-id page-id frame-id tag & {:keys [object-id]}]
(let [object-id (or object-id (fmt-object-id file-id page-id frame-id))
(let [object-id (or object-id (thc/fmt-object-id file-id page-id frame-id tag))
tp (tp/tpoint-ms)
objects (wsh/lookup-page-objects state page-id)
shape (get objects frame-id)]
@ -93,8 +90,8 @@
:elapsed (dm/str (tp) "ms"))))))
(defn clear-thumbnail
([file-id page-id frame-id]
(clear-thumbnail file-id (fmt-object-id file-id page-id frame-id)))
([file-id page-id frame-id tag]
(clear-thumbnail file-id (thc/fmt-object-id file-id page-id frame-id tag)))
([file-id object-id]
(let [emit-rpc? (volatile! false)]
(ptk/reify ::clear-thumbnail
@ -152,15 +149,15 @@
(defn update-thumbnail
"Updates the thumbnail information for the given `id`"
[file-id page-id frame-id]
(let [object-id (fmt-object-id file-id page-id frame-id)]
[file-id page-id frame-id tag]
(let [object-id (thc/fmt-object-id file-id page-id frame-id tag)]
(ptk/reify ::update-thumbnail
cljs.core/IDeref
(-deref [_] object-id)
ptk/WatchEvent
(watch [_ state stream]
(l/dbg :hint "update thumbnail" :object-id object-id)
(l/dbg :hint "update thumbnail" :object-id object-id :tag tag)
;; Send the update to the back-end
(->> (get-thumbnail state file-id page-id frame-id {:object-id object-id})
(rx/mapcat (fn [uri]
@ -172,7 +169,8 @@
;; Send the data to backend
(let [params {:file-id file-id
:object-id object-id
:media blob}]
:media blob
:tag (or tag "frame")}]
(rp/cmd! :create-file-object-thumbnail params))))
(rx/catch rx/empty)
(rx/ignore)))))
@ -290,7 +288,7 @@
;; related to current frame-id
(->> changes-s
(rx/map (fn [frame-id]
(clear-thumbnail file-id page-id frame-id))))
(clear-thumbnail file-id page-id frame-id "frame"))))
;; Generate thumbnails in batchs, once user becomes
;; inactive for some instant
@ -298,6 +296,6 @@
(rx/buffer-until notifier-s)
(rx/mapcat #(into #{} %))
(rx/map (fn [frame-id]
(request-thumbnail file-id page-id frame-id)))))
(request-thumbnail file-id page-id frame-id "frame")))))
(rx/take-until stopper-s))))))

View file

@ -11,6 +11,7 @@
[app.common.geom.rect :as grc]
[app.common.geom.shapes :as gsh]
[app.common.pages.helpers :as cph]
[app.common.thumbnails :as thc]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.data.workspace.thumbnails :as dwt]
[app.main.refs :as refs]
@ -110,7 +111,7 @@
height (dm/get-prop bounds :height)
thumbnail-uri* (mf/with-memo [file-id page-id frame-id]
(let [object-id (dwt/fmt-object-id file-id page-id frame-id)]
(let [object-id (thc/fmt-object-id file-id page-id frame-id "frame")]
(refs/workspace-thumbnail-by-id object-id)))
thumbnail-uri (mf/deref thumbnail-uri*)
@ -125,7 +126,7 @@
(mf/with-effect []
(when-not (some? thumbnail-uri)
(tm/schedule-on-idle
#(st/emit! (dwt/request-thumbnail file-id page-id frame-id)))))
#(st/emit! (dwt/request-thumbnail file-id page-id frame-id "frame")))))
(fdm/use-dynamic-modifiers objects (mf/ref-val content-ref) modifiers)

View file

@ -11,12 +11,12 @@
[app.common.data.macros :as dm]
[app.common.pages.helpers :as cph]
[app.common.spec :as us]
[app.common.thumbnails :as thc]
[app.common.types.component :as ctk]
[app.common.types.file :as ctf]
[app.main.data.modal :as modal]
[app.main.data.workspace :as dw]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.thumbnails :as dwt]
[app.main.data.workspace.undo :as dwu]
[app.main.refs :as refs]
[app.main.render :refer [component-svg]]
@ -272,7 +272,7 @@
[file-id component]
(let [page-id (:main-instance-page component)
root-id (:main-instance-id component)
object-id (dwt/fmt-object-id file-id page-id root-id)]
object-id (thc/fmt-object-id file-id page-id root-id "component")]
(if (= file-id (:id @refs/workspace-file))
(mf/deref (refs/workspace-thumbnail-by-id object-id))
(let [thumbnails (dm/get-in @refs/workspace-libraries [file-id :thumbnails (dm/str object-id)])]