mirror of
https://github.com/penpot/penpot.git
synced 2025-05-15 22:26:36 +02:00
⚡ Frame thumbnails
This commit is contained in:
parent
b4351208cc
commit
e1dfd91e24
15 changed files with 427 additions and 50 deletions
|
@ -8,6 +8,7 @@
|
||||||
(:require
|
(:require
|
||||||
[app.common.pages.migrations :as pmg]
|
[app.common.pages.migrations :as pmg]
|
||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
|
[app.common.uuid :as uuid]
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.rpc.permissions :as perms]
|
[app.rpc.permissions :as perms]
|
||||||
[app.rpc.queries.projects :as projects]
|
[app.rpc.queries.projects :as projects]
|
||||||
|
@ -120,6 +121,7 @@
|
||||||
profile-id team-id
|
profile-id team-id
|
||||||
search-term]))
|
search-term]))
|
||||||
|
|
||||||
|
|
||||||
;; --- Query: Files
|
;; --- Query: Files
|
||||||
|
|
||||||
;; DEPRECATED: should be removed probably on 1.6.x
|
;; DEPRECATED: should be removed probably on 1.6.x
|
||||||
|
@ -185,13 +187,43 @@
|
||||||
(s/def ::page
|
(s/def ::page
|
||||||
(s/keys :req-un [::profile-id ::file-id]))
|
(s/keys :req-un [::profile-id ::file-id]))
|
||||||
|
|
||||||
|
(defn remove-thumbnails-frames
|
||||||
|
"Removes from data the children for frames that have a thumbnail set up"
|
||||||
|
[data]
|
||||||
|
(let [filter-shape?
|
||||||
|
(fn [objects [id shape]]
|
||||||
|
(let [frame-id (:frame-id shape)]
|
||||||
|
(or (= id uuid/zero)
|
||||||
|
(= frame-id uuid/zero)
|
||||||
|
(not (some? (get-in objects [frame-id :thumbnail]))))))
|
||||||
|
|
||||||
|
;; We need to remove from the attribute :shapes its childrens because
|
||||||
|
;; they will not be sent in the data
|
||||||
|
remove-frame-children
|
||||||
|
(fn [[id shape]]
|
||||||
|
[id (cond-> shape
|
||||||
|
(some? (:thumbnail shape))
|
||||||
|
(assoc :shapes []))])
|
||||||
|
|
||||||
|
update-objects
|
||||||
|
(fn [objects]
|
||||||
|
(into {}
|
||||||
|
(comp (map remove-frame-children)
|
||||||
|
(filter (partial filter-shape? objects)))
|
||||||
|
objects))]
|
||||||
|
|
||||||
|
(update data :objects update-objects)))
|
||||||
|
|
||||||
(sv/defmethod ::page
|
(sv/defmethod ::page
|
||||||
|
[{:keys [pool] :as cfg} {:keys [profile-id file-id id strip-thumbnails]}]
|
||||||
[{:keys [pool] :as cfg} {:keys [profile-id file-id]}]
|
[{:keys [pool] :as cfg} {:keys [profile-id file-id]}]
|
||||||
(db/with-atomic [conn pool]
|
(db/with-atomic [conn pool]
|
||||||
(check-edition-permissions! conn profile-id file-id)
|
(check-edition-permissions! conn profile-id file-id)
|
||||||
(let [file (retrieve-file conn file-id)
|
(let [file (retrieve-file conn file-id)
|
||||||
page-id (get-in file [:data :pages 0])]
|
page-id (get-in file [:data :pages 0])]
|
||||||
(get-in file [:data :pages-index page-id]))))
|
(cond-> (get-in file [:data :pages-index page-id])
|
||||||
|
strip-thumbnails
|
||||||
|
(remove-thumbnails-frames)))))
|
||||||
|
|
||||||
;; --- Query: Shared Library Files
|
;; --- Query: Shared Library Files
|
||||||
|
|
||||||
|
@ -244,6 +276,7 @@
|
||||||
|
|
||||||
(def ^:private sql:file-libraries
|
(def ^:private sql:file-libraries
|
||||||
"select fl.*,
|
"select fl.*,
|
||||||
|
|
||||||
flr.synced_at as synced_at
|
flr.synced_at as synced_at
|
||||||
from file as fl
|
from file as fl
|
||||||
inner join file_library_rel as flr on (flr.library_file_id = fl.id)
|
inner join file_library_rel as flr on (flr.library_file_id = fl.id)
|
||||||
|
@ -259,6 +292,7 @@
|
||||||
libraries
|
libraries
|
||||||
(map :id libraries))))
|
(map :id libraries))))
|
||||||
|
|
||||||
|
|
||||||
(s/def ::file-libraries
|
(s/def ::file-libraries
|
||||||
(s/keys :req-un [::profile-id ::file-id]))
|
(s/keys :req-un [::profile-id ::file-id]))
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
(:require
|
(:require
|
||||||
[linked.set :as lks]
|
[linked.set :as lks]
|
||||||
[app.common.math :as mth]
|
[app.common.math :as mth]
|
||||||
|
[clojure.set :as set]
|
||||||
#?(:clj [cljs.analyzer.api :as aapi])
|
#?(:clj [cljs.analyzer.api :as aapi])
|
||||||
#?(:cljs [cljs.reader :as r]
|
#?(:cljs [cljs.reader :as r]
|
||||||
:clj [clojure.edn :as r])
|
:clj [clojure.edn :as r])
|
||||||
|
@ -467,3 +468,44 @@
|
||||||
[f coll]
|
[f coll]
|
||||||
(f coll)
|
(f coll)
|
||||||
coll)
|
coll)
|
||||||
|
|
||||||
|
(defn map-diff
|
||||||
|
"Given two maps returns the diff of its attributes in a map where
|
||||||
|
the keys will be the attributes that change and the values the previous
|
||||||
|
and current value. For attributes which value is a map this will be recursive.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
(map-diff {:a 1 :b 2 :c { :foo 1 :var 2}
|
||||||
|
{:a 2 :c { :foo 10 } :d 10)
|
||||||
|
|
||||||
|
=> { :a [1 2]
|
||||||
|
:b [2 nil]
|
||||||
|
:c { :foo [1 10]
|
||||||
|
:var [2 nil]}
|
||||||
|
:d [nil 10] }
|
||||||
|
|
||||||
|
If both maps are identical the result will be an empty map
|
||||||
|
"
|
||||||
|
[m1 m2]
|
||||||
|
|
||||||
|
(let [m1ks (keys m1)
|
||||||
|
m2ks (keys m2)
|
||||||
|
keys (set/union m1ks m2ks)
|
||||||
|
|
||||||
|
diff-attr
|
||||||
|
(fn [diff key]
|
||||||
|
|
||||||
|
(let [v1 (get m1 key)
|
||||||
|
v2 (get m2 key)]
|
||||||
|
(cond
|
||||||
|
(= v1 v2)
|
||||||
|
diff
|
||||||
|
|
||||||
|
(and (map? v1) (map? v2))
|
||||||
|
(assoc diff key (map-diff v1 v2))
|
||||||
|
|
||||||
|
:else
|
||||||
|
(assoc diff key [(get m1 key) (get m2 key)]))))]
|
||||||
|
|
||||||
|
(->> keys
|
||||||
|
(reduce diff-attr {}))))
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes :as gsh]
|
||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
|
[clojure.set :as set]
|
||||||
[cuerdas.core :as str]))
|
[cuerdas.core :as str]))
|
||||||
|
|
||||||
(defn walk-pages
|
(defn walk-pages
|
||||||
|
@ -456,4 +457,3 @@
|
||||||
[path name]
|
[path name]
|
||||||
(let [path-split (split-path path)]
|
(let [path-split (split-path path)]
|
||||||
(merge-path-item (first path-split) name)))
|
(merge-path-item (first path-split) name)))
|
||||||
|
|
||||||
|
|
|
@ -161,6 +161,7 @@
|
||||||
(->> stream
|
(->> stream
|
||||||
(rx/filter (ptk/type? ::dwp/bundle-fetched))
|
(rx/filter (ptk/type? ::dwp/bundle-fetched))
|
||||||
(rx/take 1)
|
(rx/take 1)
|
||||||
|
|
||||||
(rx/map deref)
|
(rx/map deref)
|
||||||
(rx/mapcat (fn [bundle]
|
(rx/mapcat (fn [bundle]
|
||||||
(rx/of (dwn/initialize file-id)
|
(rx/of (dwn/initialize file-id)
|
||||||
|
@ -1185,6 +1186,7 @@
|
||||||
|
|
||||||
(defn go-to-page
|
(defn go-to-page
|
||||||
([]
|
([]
|
||||||
|
|
||||||
(ptk/reify ::go-to-page
|
(ptk/reify ::go-to-page
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [it state stream]
|
(watch [it state stream]
|
||||||
|
|
|
@ -62,7 +62,8 @@
|
||||||
|
|
||||||
(defn update-shapes
|
(defn update-shapes
|
||||||
([ids f] (update-shapes ids f nil))
|
([ids f] (update-shapes ids f nil))
|
||||||
([ids f {:keys [reg-objects?] :or {reg-objects? false}}]
|
([ids f {:keys [reg-objects? save-undo?]
|
||||||
|
:or {reg-objects? false save-undo? true}}]
|
||||||
(us/assert ::coll-of-uuid ids)
|
(us/assert ::coll-of-uuid ids)
|
||||||
(us/assert fn? f)
|
(us/assert fn? f)
|
||||||
(ptk/reify ::update-shapes
|
(ptk/reify ::update-shapes
|
||||||
|
@ -82,7 +83,8 @@
|
||||||
(when (and has-rch? has-uch?)
|
(when (and has-rch? has-uch?)
|
||||||
(commit-changes {:redo-changes rch
|
(commit-changes {:redo-changes rch
|
||||||
:undo-changes uch
|
:undo-changes uch
|
||||||
:origin it}))))
|
:origin it
|
||||||
|
:save-undo? save-undo?}))))
|
||||||
|
|
||||||
(let [id (first ids)
|
(let [id (first ids)
|
||||||
obj1 (get objects id)
|
obj1 (get objects id)
|
||||||
|
@ -164,6 +166,7 @@
|
||||||
[{:keys [redo-changes undo-changes origin save-undo? file-id]
|
[{:keys [redo-changes undo-changes origin save-undo? file-id]
|
||||||
:or {save-undo? true}}]
|
:or {save-undo? true}}]
|
||||||
|
|
||||||
|
|
||||||
(log/debug :msg "commit-changes"
|
(log/debug :msg "commit-changes"
|
||||||
:js/redo-changes redo-changes
|
:js/redo-changes redo-changes
|
||||||
:js/undo-changes undo-changes)
|
:js/undo-changes undo-changes)
|
||||||
|
@ -182,12 +185,14 @@
|
||||||
(let [current-file-id (get state :current-file-id)
|
(let [current-file-id (get state :current-file-id)
|
||||||
file-id (or file-id current-file-id)
|
file-id (or file-id current-file-id)
|
||||||
path (if (= file-id current-file-id)
|
path (if (= file-id current-file-id)
|
||||||
|
|
||||||
[:workspace-data]
|
[:workspace-data]
|
||||||
[:workspace-libraries file-id :data])]
|
[:workspace-libraries file-id :data])]
|
||||||
(try
|
(try
|
||||||
(us/assert ::spec/changes redo-changes)
|
(us/assert ::spec/changes redo-changes)
|
||||||
(us/assert ::spec/changes undo-changes)
|
(us/assert ::spec/changes undo-changes)
|
||||||
(update-in state path cp/process-changes redo-changes false)
|
(update-in state path cp/process-changes redo-changes false)
|
||||||
|
|
||||||
(catch :default e
|
(catch :default e
|
||||||
(vreset! error e)
|
(vreset! error e)
|
||||||
state))))
|
state))))
|
||||||
|
|
|
@ -16,11 +16,13 @@
|
||||||
[app.main.data.dashboard :as dd]
|
[app.main.data.dashboard :as dd]
|
||||||
[app.main.data.media :as di]
|
[app.main.data.media :as di]
|
||||||
[app.main.data.messages :as dm]
|
[app.main.data.messages :as dm]
|
||||||
[app.main.data.workspace.common :as dwc]
|
|
||||||
[app.main.data.workspace.changes :as dch]
|
[app.main.data.workspace.changes :as dch]
|
||||||
|
[app.main.data.workspace.common :as dwc]
|
||||||
[app.main.data.workspace.libraries :as dwl]
|
[app.main.data.workspace.libraries :as dwl]
|
||||||
[app.main.data.workspace.selection :as dws]
|
[app.main.data.workspace.selection :as dws]
|
||||||
|
[app.main.data.workspace.state-helpers :as wsh]
|
||||||
[app.main.data.workspace.svg-upload :as svg]
|
[app.main.data.workspace.svg-upload :as svg]
|
||||||
|
[app.main.refs :as refs]
|
||||||
[app.main.repo :as rp]
|
[app.main.repo :as rp]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.util.avatars :as avatars]
|
[app.util.avatars :as avatars]
|
||||||
|
@ -33,6 +35,7 @@
|
||||||
[app.util.uri :as uu]
|
[app.util.uri :as uu]
|
||||||
[beicon.core :as rx]
|
[beicon.core :as rx]
|
||||||
[cljs.spec.alpha :as s]
|
[cljs.spec.alpha :as s]
|
||||||
|
[clojure.set :as set]
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
[potok.core :as ptk]
|
[potok.core :as ptk]
|
||||||
[promesa.core :as p]))
|
[promesa.core :as p]))
|
||||||
|
@ -349,30 +352,6 @@
|
||||||
(->> (rp/mutation :unlink-file-from-library params)
|
(->> (rp/mutation :unlink-file-from-library params)
|
||||||
(rx/map (constantly unlinked)))))))
|
(rx/map (constantly unlinked)))))))
|
||||||
|
|
||||||
;; --- Fetch Pages
|
|
||||||
|
|
||||||
(declare page-fetched)
|
|
||||||
|
|
||||||
(defn fetch-page
|
|
||||||
[page-id]
|
|
||||||
(us/verify ::us/uuid page-id)
|
|
||||||
(ptk/reify ::fetch-pages
|
|
||||||
ptk/WatchEvent
|
|
||||||
(watch [it state s]
|
|
||||||
(->> (rp/query :page {:id page-id})
|
|
||||||
(rx/map page-fetched)))))
|
|
||||||
|
|
||||||
(defn page-fetched
|
|
||||||
[{:keys [id] :as page}]
|
|
||||||
(us/verify ::page page)
|
|
||||||
(ptk/reify ::page-fetched
|
|
||||||
IDeref
|
|
||||||
(-deref [_] page)
|
|
||||||
|
|
||||||
ptk/UpdateEvent
|
|
||||||
(update [_ state]
|
|
||||||
(assoc-in state [:workspace-pages id] page))))
|
|
||||||
|
|
||||||
|
|
||||||
;; --- Upload File Media objects
|
;; --- Upload File Media objects
|
||||||
|
|
||||||
|
@ -572,3 +551,101 @@
|
||||||
(update-in [:workspace-file :pages] #(filterv (partial not= id) %))
|
(update-in [:workspace-file :pages] #(filterv (partial not= id) %))
|
||||||
(update :workspace-pages dissoc id)))
|
(update :workspace-pages dissoc id)))
|
||||||
|
|
||||||
|
(def update-frame-thumbnail? (ptk/type? ::update-frame-thumbnail))
|
||||||
|
|
||||||
|
(defn remove-thumbnails
|
||||||
|
[ids]
|
||||||
|
(ptk/reify ::remove-thumbnails
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state stream]
|
||||||
|
;; Removes the thumbnail while it's regenerated
|
||||||
|
(rx/of (dch/update-shapes
|
||||||
|
ids
|
||||||
|
#(dissoc % :thumbnail)
|
||||||
|
{:save-undo? false})))))
|
||||||
|
|
||||||
|
(defn update-frame-thumbnail
|
||||||
|
[frame-id]
|
||||||
|
(ptk/event ::update-frame-thumbnail {:frame-id frame-id}))
|
||||||
|
|
||||||
|
(defn- extract-frame-changes
|
||||||
|
"Process a changes set in a commit to extract the frames that are channging"
|
||||||
|
[[event objects]]
|
||||||
|
(let [changes (-> event deref :changes)
|
||||||
|
|
||||||
|
extract-ids
|
||||||
|
(fn [{type :type :as change}]
|
||||||
|
(case type
|
||||||
|
:add-obj [(:id change)]
|
||||||
|
:mod-obj [(:id change)]
|
||||||
|
:del-obj [(:id change)]
|
||||||
|
:reg-objects (:shapes change)
|
||||||
|
:mov-objects (:shapes change)
|
||||||
|
[]))
|
||||||
|
|
||||||
|
get-frame-id
|
||||||
|
(fn [id]
|
||||||
|
(or (and (= :frame (get-in objects [id :type])) id)
|
||||||
|
(get-in objects [id :frame-id])))
|
||||||
|
|
||||||
|
;; Extracts the frames and then removes nils and the root frame
|
||||||
|
xform (comp (mapcat extract-ids)
|
||||||
|
(map get-frame-id)
|
||||||
|
(remove nil?)
|
||||||
|
(filter #(not= uuid/zero %)))]
|
||||||
|
|
||||||
|
(into #{} xform changes)))
|
||||||
|
|
||||||
|
(defn thumbnail-change?
|
||||||
|
"Checks if a event is only updating thumbnails to ignore in the thumbnail generation process"
|
||||||
|
[event]
|
||||||
|
(let [changes (-> event deref :changes)
|
||||||
|
|
||||||
|
is-thumbnail-op?
|
||||||
|
(fn [{type :type attr :attr}]
|
||||||
|
(and (= type :set)
|
||||||
|
(= attr :thumbnail)))
|
||||||
|
|
||||||
|
is-thumbnail-change?
|
||||||
|
(fn [change]
|
||||||
|
(and (= (:type change) :mod-obj)
|
||||||
|
(->> change :operations (every? is-thumbnail-op?))))]
|
||||||
|
|
||||||
|
(->> changes (every? is-thumbnail-change?))))
|
||||||
|
|
||||||
|
(defn watch-state-changes []
|
||||||
|
(ptk/reify ::watch-state-changes
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state stream]
|
||||||
|
(let [stopper (->> stream
|
||||||
|
(rx/filter #(or (= :app.main.data.workspace/finalize-page (ptk/type %))
|
||||||
|
(= ::watch-state-changes (ptk/type %)))))
|
||||||
|
|
||||||
|
objects-stream (rx/from-atom refs/workspace-page-objects {:emit-current-value? true})
|
||||||
|
|
||||||
|
frame-changes (->> stream
|
||||||
|
(rx/filter dch/commit-changes?)
|
||||||
|
(rx/filter (comp not thumbnail-change?))
|
||||||
|
(rx/with-latest-from objects-stream)
|
||||||
|
(rx/map extract-frame-changes))
|
||||||
|
|
||||||
|
frames (-> state wsh/lookup-page-objects cp/select-frames)
|
||||||
|
no-thumb-frames (->> frames
|
||||||
|
(filter (comp nil? :thumbnail))
|
||||||
|
(mapv :id))]
|
||||||
|
|
||||||
|
(rx/concat
|
||||||
|
(->> (rx/from no-thumb-frames)
|
||||||
|
(rx/map #(update-frame-thumbnail %)))
|
||||||
|
|
||||||
|
;; We remove the thumbnails inmediately but defer their generation
|
||||||
|
(rx/merge
|
||||||
|
(->> frame-changes
|
||||||
|
(rx/take-until stopper)
|
||||||
|
(rx/map #(remove-thumbnails %)))
|
||||||
|
|
||||||
|
(->> frame-changes
|
||||||
|
(rx/take-until stopper)
|
||||||
|
(rx/buffer-until (->> frame-changes (rx/debounce 1000)))
|
||||||
|
(rx/flat-map #(reduce set/union %))
|
||||||
|
(rx/map #(update-frame-thumbnail %)))))))))
|
||||||
|
|
|
@ -121,7 +121,7 @@
|
||||||
|
|
||||||
(mf/defc page-svg
|
(mf/defc page-svg
|
||||||
{::mf/wrap [mf/memo]}
|
{::mf/wrap [mf/memo]}
|
||||||
[{:keys [data width height] :as props}]
|
[{:keys [data width height thumbnails?] :as props}]
|
||||||
(let [objects (:objects data)
|
(let [objects (:objects data)
|
||||||
root (get objects uuid/zero)
|
root (get objects uuid/zero)
|
||||||
shapes (->> (:shapes root)
|
shapes (->> (:shapes root)
|
||||||
|
@ -146,11 +146,23 @@
|
||||||
:xmlns "http://www.w3.org/2000/svg"}
|
:xmlns "http://www.w3.org/2000/svg"}
|
||||||
[:& background {:vbox dim :color background-color}]
|
[:& background {:vbox dim :color background-color}]
|
||||||
(for [item shapes]
|
(for [item shapes]
|
||||||
(if (= (:type item) :frame)
|
(let [frame? (= (:type item) :frame)]
|
||||||
[:& frame-wrapper {:shape item
|
(cond
|
||||||
:key (:id item)}]
|
(and frame? thumbnails? (some? (:thumbnail item)))
|
||||||
[:& shape-wrapper {:shape item
|
[:image {:xlinkHref (:thumbnail item)
|
||||||
:key (:id item)}]))]))
|
:x (:x item)
|
||||||
|
:y (:y item)
|
||||||
|
:width (:width item)
|
||||||
|
:height (:height item)
|
||||||
|
;; DEBUG
|
||||||
|
;; :style {:filter "sepia(1)"}
|
||||||
|
}]
|
||||||
|
frame?
|
||||||
|
[:& frame-wrapper {:shape item
|
||||||
|
:key (:id item)}]
|
||||||
|
:else
|
||||||
|
[:& shape-wrapper {:shape item
|
||||||
|
:key (:id item)}])))]))
|
||||||
|
|
||||||
(mf/defc frame-svg
|
(mf/defc frame-svg
|
||||||
{::mf/wrap [mf/memo]}
|
{::mf/wrap [mf/memo]}
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
[props]
|
[props]
|
||||||
(let [childs (unchecked-get props "childs")
|
(let [childs (unchecked-get props "childs")
|
||||||
shape (unchecked-get props "shape")
|
shape (unchecked-get props "shape")
|
||||||
{:keys [id x y width height]} shape
|
{:keys [id width height]} shape
|
||||||
|
|
||||||
props (-> (merge frame-default-props shape)
|
props (-> (merge frame-default-props shape)
|
||||||
(attrs/extract-style-attrs)
|
(attrs/extract-style-attrs)
|
||||||
|
|
|
@ -106,7 +106,7 @@
|
||||||
|
|
||||||
(fn []
|
(fn []
|
||||||
(when page-id
|
(when page-id
|
||||||
(st/emitf (dw/finalize-page page-id))))))
|
(st/emit! (dw/finalize-page page-id))))))
|
||||||
|
|
||||||
(when page
|
(when page
|
||||||
[:& workspace-content {:key page-id
|
[:& workspace-content {:key page-id
|
||||||
|
@ -158,6 +158,9 @@
|
||||||
|
|
||||||
(if (and (and file project)
|
(if (and (and file project)
|
||||||
(:initialized file))
|
(:initialized file))
|
||||||
[:& workspace-page {:page-id page-id :file file :layout layout}]
|
[:& workspace-page {:key (str "page-" page-id)
|
||||||
|
:page-id page-id
|
||||||
|
:file file
|
||||||
|
:layout layout}]
|
||||||
[:& workspace-loader])]]]]]))
|
[:& workspace-loader])]]]]]))
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,8 @@
|
||||||
"Draws the root shape of the viewport and recursively all the shapes"
|
"Draws the root shape of the viewport and recursively all the shapes"
|
||||||
{::mf/wrap-props false}
|
{::mf/wrap-props false}
|
||||||
[props]
|
[props]
|
||||||
(let [objects (obj/get props "objects")
|
(let [objects (obj/get props "objects")
|
||||||
|
active-frames (obj/get props "active-frames")
|
||||||
root-shapes (get-in objects [uuid/zero :shapes])
|
root-shapes (get-in objects [uuid/zero :shapes])
|
||||||
shapes (->> root-shapes (mapv #(get objects %)))]
|
shapes (->> root-shapes (mapv #(get objects %)))]
|
||||||
|
|
||||||
|
@ -59,7 +60,8 @@
|
||||||
(if (= (:type item) :frame)
|
(if (= (:type item) :frame)
|
||||||
[:& frame-wrapper {:shape item
|
[:& frame-wrapper {:shape item
|
||||||
:key (:id item)
|
:key (:id item)
|
||||||
:objects objects}]
|
:objects objects
|
||||||
|
:thumbnail? (not (get active-frames (:id item) false))}]
|
||||||
|
|
||||||
[:& shape-wrapper {:shape item
|
[:& shape-wrapper {:shape item
|
||||||
:key (:id item)}]))))
|
:key (:id item)}]))))
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
[app.common.geom.point :as gpt]
|
[app.common.geom.point :as gpt]
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes :as gsh]
|
||||||
[app.main.data.workspace :as dw]
|
[app.main.data.workspace :as dw]
|
||||||
|
[app.main.data.workspace.changes :as dch]
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.main.ui.context :as muc]
|
[app.main.ui.context :as muc]
|
||||||
|
@ -17,11 +18,18 @@
|
||||||
[app.main.ui.shapes.text.embed :as ste]
|
[app.main.ui.shapes.text.embed :as ste]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
[app.util.keyboard :as kbd]
|
[app.util.keyboard :as kbd]
|
||||||
|
[app.util.object :as obj]
|
||||||
[app.util.timers :as ts]
|
[app.util.timers :as ts]
|
||||||
[beicon.core :as rx]
|
[beicon.core :as rx]
|
||||||
[okulary.core :as l]
|
[okulary.core :as l]
|
||||||
[rumext.alpha :as mf]))
|
[rumext.alpha :as mf]))
|
||||||
|
|
||||||
|
(def obs-config
|
||||||
|
#js {:attributes true
|
||||||
|
:childList true
|
||||||
|
:subtree true
|
||||||
|
:characterData true})
|
||||||
|
|
||||||
(defn make-is-moving-ref
|
(defn make-is-moving-ref
|
||||||
[id]
|
[id]
|
||||||
(let [check-moving (fn [local]
|
(let [check-moving (fn [local]
|
||||||
|
@ -43,14 +51,32 @@
|
||||||
(let [new-shape (unchecked-get new-props "shape")
|
(let [new-shape (unchecked-get new-props "shape")
|
||||||
old-shape (unchecked-get old-props "shape")
|
old-shape (unchecked-get old-props "shape")
|
||||||
|
|
||||||
|
new-thumbnail? (unchecked-get new-props "thumbnail?")
|
||||||
|
old-thumbnail? (unchecked-get old-props "thumbnail?")
|
||||||
|
|
||||||
new-objects (unchecked-get new-props "objects")
|
new-objects (unchecked-get new-props "objects")
|
||||||
old-objects (unchecked-get old-props "objects")
|
old-objects (unchecked-get old-props "objects")
|
||||||
|
|
||||||
new-children (->> new-shape :shapes (mapv #(get new-objects %)))
|
new-children (->> new-shape :shapes (mapv #(get new-objects %)))
|
||||||
old-children (->> old-shape :shapes (mapv #(get old-objects %)))]
|
old-children (->> old-shape :shapes (mapv #(get old-objects %)))]
|
||||||
(and (= new-shape old-shape)
|
(and (= new-shape old-shape)
|
||||||
|
(= new-thumbnail? old-thumbnail?)
|
||||||
(= new-children old-children))))
|
(= new-children old-children))))
|
||||||
|
|
||||||
|
(mf/defc thumbnail
|
||||||
|
{::mf/wrap-props false}
|
||||||
|
[props]
|
||||||
|
(let [shape (obj/get props "shape")]
|
||||||
|
(when (:thumbnail shape)
|
||||||
|
[:image {:xlinkHref (:thumbnail shape)
|
||||||
|
:x (:x shape)
|
||||||
|
:y (:y shape)
|
||||||
|
:width (:width shape)
|
||||||
|
:height (:height shape)
|
||||||
|
;; DEBUG
|
||||||
|
;; :style {:filter "sepia(1)"}
|
||||||
|
}])))
|
||||||
|
|
||||||
;; This custom deffered don't deffer rendering when ghost rendering is
|
;; This custom deffered don't deffer rendering when ghost rendering is
|
||||||
;; used.
|
;; used.
|
||||||
(defn custom-deferred
|
(defn custom-deferred
|
||||||
|
@ -76,6 +102,8 @@
|
||||||
[props]
|
[props]
|
||||||
(let [shape (unchecked-get props "shape")
|
(let [shape (unchecked-get props "shape")
|
||||||
objects (unchecked-get props "objects")
|
objects (unchecked-get props "objects")
|
||||||
|
thumbnail? (unchecked-get props "thumbnail?")
|
||||||
|
|
||||||
edition (mf/deref refs/selected-edition)
|
edition (mf/deref refs/selected-edition)
|
||||||
embed-fonts? (mf/use-ctx muc/embed-ctx)
|
embed-fonts? (mf/use-ctx muc/embed-ctx)
|
||||||
|
|
||||||
|
@ -90,10 +118,15 @@
|
||||||
|
|
||||||
(when (and shape (not (:hidden shape)))
|
(when (and shape (not (:hidden shape)))
|
||||||
[:g.frame-wrapper {:display (when (:hidden shape) "none")}
|
[:g.frame-wrapper {:display (when (:hidden shape) "none")}
|
||||||
[:> shape-container {:shape shape}
|
|
||||||
(when embed-fonts?
|
(if (and thumbnail? (some? (:thumbnail shape)))
|
||||||
[:& ste/embed-fontfaces-style {:shapes text-childs}])
|
[:& thumbnail {:shape shape}]
|
||||||
[:& frame-shape
|
|
||||||
{:shape shape
|
[:> shape-container {:shape shape }
|
||||||
:childs children}]]])))))
|
|
||||||
|
(when embed-fonts?
|
||||||
|
[:& ste/embed-fontfaces-style {:shapes text-childs}])
|
||||||
|
|
||||||
|
[:& frame-shape {:shape shape
|
||||||
|
:childs children}]])])))))
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
[app.main.ui.workspace.viewport.selection :as selection]
|
[app.main.ui.workspace.viewport.selection :as selection]
|
||||||
[app.main.ui.workspace.viewport.snap-distances :as snap-distances]
|
[app.main.ui.workspace.viewport.snap-distances :as snap-distances]
|
||||||
[app.main.ui.workspace.viewport.snap-points :as snap-points]
|
[app.main.ui.workspace.viewport.snap-points :as snap-points]
|
||||||
|
[app.main.ui.workspace.viewport.thumbnail-renderer :as wtr]
|
||||||
[app.main.ui.workspace.viewport.utils :as utils]
|
[app.main.ui.workspace.viewport.utils :as utils]
|
||||||
[app.main.ui.workspace.viewport.widgets :as widgets]
|
[app.main.ui.workspace.viewport.widgets :as widgets]
|
||||||
[beicon.core :as rx]
|
[beicon.core :as rx]
|
||||||
|
@ -69,6 +70,7 @@
|
||||||
hover-ids (mf/use-state nil)
|
hover-ids (mf/use-state nil)
|
||||||
hover (mf/use-state nil)
|
hover (mf/use-state nil)
|
||||||
frame-hover (mf/use-state nil)
|
frame-hover (mf/use-state nil)
|
||||||
|
active-frames (mf/use-state {})
|
||||||
|
|
||||||
;; REFS
|
;; REFS
|
||||||
viewport-ref (mf/use-ref nil)
|
viewport-ref (mf/use-ref nil)
|
||||||
|
@ -145,9 +147,12 @@
|
||||||
(hooks/setup-hover-shapes page-id move-stream selected objects transform selected ctrl? hover hover-ids)
|
(hooks/setup-hover-shapes page-id move-stream selected objects transform selected ctrl? hover hover-ids)
|
||||||
(hooks/setup-viewport-modifiers modifiers selected objects render-ref)
|
(hooks/setup-viewport-modifiers modifiers selected objects render-ref)
|
||||||
(hooks/setup-shortcuts path-editing? drawing-path?)
|
(hooks/setup-shortcuts path-editing? drawing-path?)
|
||||||
|
(hooks/setup-active-frames objects vbox hover active-frames)
|
||||||
|
|
||||||
[:div.viewport
|
[:div.viewport
|
||||||
[:div.viewport-overlays
|
[:div.viewport-overlays
|
||||||
|
[:& wtr/frame-renderer {:objects objects}]
|
||||||
|
|
||||||
(when show-comments?
|
(when show-comments?
|
||||||
[:& comments/comments-layer {:vbox vbox
|
[:& comments/comments-layer {:vbox vbox
|
||||||
:vport vport
|
:vport vport
|
||||||
|
@ -180,7 +185,8 @@
|
||||||
[:& (mf/provider muc/embed-ctx) {:value true}
|
[:& (mf/provider muc/embed-ctx) {:value true}
|
||||||
;; Render root shape
|
;; Render root shape
|
||||||
[:& shapes/root-shape {:key page-id
|
[:& shapes/root-shape {:key page-id
|
||||||
:objects objects}]]]
|
:objects objects
|
||||||
|
:active-frames @active-frames}]]]
|
||||||
|
|
||||||
[:svg.viewport-controls
|
[:svg.viewport-controls
|
||||||
{:xmlns "http://www.w3.org/2000/svg"
|
{:xmlns "http://www.w3.org/2000/svg"
|
||||||
|
|
|
@ -164,3 +164,34 @@
|
||||||
:else
|
:else
|
||||||
(dsc/bind-shortcuts wsc/shortcuts))
|
(dsc/bind-shortcuts wsc/shortcuts))
|
||||||
dsc/remove-shortcuts)))
|
dsc/remove-shortcuts)))
|
||||||
|
|
||||||
|
(defn inside-vbox [vbox objects frame-id]
|
||||||
|
(let [frame (get objects frame-id)]
|
||||||
|
|
||||||
|
(and (some? frame)
|
||||||
|
(gsh/overlaps? frame vbox))))
|
||||||
|
|
||||||
|
(defn setup-active-frames
|
||||||
|
[objects vbox hover active-frames]
|
||||||
|
|
||||||
|
(mf/use-effect
|
||||||
|
(mf/deps vbox)
|
||||||
|
|
||||||
|
(fn []
|
||||||
|
(swap! active-frames
|
||||||
|
(fn [active-frames]
|
||||||
|
(let [set-active-frames
|
||||||
|
(fn [active-frames id active?]
|
||||||
|
(cond-> active-frames
|
||||||
|
(and active? (inside-vbox vbox objects id))
|
||||||
|
(assoc id true)))]
|
||||||
|
(reduce-kv set-active-frames {} active-frames))))))
|
||||||
|
|
||||||
|
(mf/use-effect
|
||||||
|
(mf/deps @hover @active-frames)
|
||||||
|
(fn []
|
||||||
|
(let [frame-id (if (= :frame (:type @hover))
|
||||||
|
(:id @hover)
|
||||||
|
(:frame-id @hover))]
|
||||||
|
(when (not (contains? @active-frames frame-id))
|
||||||
|
(swap! active-frames assoc frame-id true))))))
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
;; 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.workspace.viewport.thumbnail-renderer
|
||||||
|
(:require
|
||||||
|
[app.main.data.workspace.changes :as dwc]
|
||||||
|
[app.main.data.workspace.persistence :as dwp]
|
||||||
|
[app.main.store :as st]
|
||||||
|
[app.util.dom :as dom]
|
||||||
|
[app.util.object :as obj]
|
||||||
|
[app.util.timers :as timers]
|
||||||
|
[beicon.core :as rx]
|
||||||
|
[rumext.alpha :as mf]))
|
||||||
|
|
||||||
|
(mf/defc frame-thumbnail
|
||||||
|
"Renders the canvas and image for a frame thumbnail and stores its value into the shape"
|
||||||
|
[{:keys [shape on-thumbnail-data on-frame-not-found]}]
|
||||||
|
|
||||||
|
(let [thumbnail-img (mf/use-ref nil)
|
||||||
|
thumbnail-canvas (mf/use-ref nil)
|
||||||
|
|
||||||
|
on-dom-rendered
|
||||||
|
(mf/use-callback
|
||||||
|
(mf/deps (:id shape))
|
||||||
|
(fn [node]
|
||||||
|
(when node
|
||||||
|
(let [img-node (mf/ref-val thumbnail-img)]
|
||||||
|
(timers/schedule-on-idle
|
||||||
|
#(if-let [frame-node (dom/get-element (str "shape-" (:id shape)))]
|
||||||
|
(let [xml (-> (js/XMLSerializer.)
|
||||||
|
(.serializeToString frame-node)
|
||||||
|
js/encodeURIComponent
|
||||||
|
js/unescape
|
||||||
|
js/btoa)
|
||||||
|
img-src (str "data:image/svg+xml;base64," xml)]
|
||||||
|
(obj/set! img-node "src" img-src))
|
||||||
|
|
||||||
|
(on-frame-not-found (:id shape))))))))
|
||||||
|
|
||||||
|
on-image-load
|
||||||
|
(mf/use-callback
|
||||||
|
(mf/deps on-thumbnail-data)
|
||||||
|
(fn []
|
||||||
|
(let [canvas-node (mf/ref-val thumbnail-canvas)
|
||||||
|
img-node (mf/ref-val thumbnail-img)
|
||||||
|
canvas-context (.getContext canvas-node "2d")
|
||||||
|
_ (.drawImage canvas-context img-node 0 0)
|
||||||
|
data (.toDataURL canvas-node "image/jpeg" 0.8)]
|
||||||
|
(on-thumbnail-data data))))]
|
||||||
|
|
||||||
|
[:div.frame-renderer {:ref on-dom-rendered
|
||||||
|
:style {:display "none"}}
|
||||||
|
[:img.thumbnail-img
|
||||||
|
{:ref thumbnail-img
|
||||||
|
:width (:width shape)
|
||||||
|
:height (:height shape)
|
||||||
|
:on-load on-image-load}]
|
||||||
|
|
||||||
|
[:canvas.thumbnail-canvas
|
||||||
|
{:ref thumbnail-canvas
|
||||||
|
:width (:width shape)
|
||||||
|
:height (:height shape)}]]))
|
||||||
|
|
||||||
|
(mf/defc frame-renderer
|
||||||
|
"Component in charge of creating thumbnails and storing them"
|
||||||
|
{::mf/wrap-props false}
|
||||||
|
[props]
|
||||||
|
(let [objects (obj/get props "objects")
|
||||||
|
|
||||||
|
;; Id of the current frame being rendered
|
||||||
|
shape-id (mf/use-state nil)
|
||||||
|
|
||||||
|
;; This subject will emit a value every time there is a free "slot" to render
|
||||||
|
;; a thumbnail
|
||||||
|
next (mf/use-memo #(rx/behavior-subject :next))
|
||||||
|
|
||||||
|
render-frame
|
||||||
|
(mf/use-callback
|
||||||
|
(fn [frame-id]
|
||||||
|
(reset! shape-id frame-id)))
|
||||||
|
|
||||||
|
updates-stream
|
||||||
|
(mf/use-memo
|
||||||
|
(fn []
|
||||||
|
(let [update-events
|
||||||
|
(->> st/stream
|
||||||
|
(rx/filter dwp/update-frame-thumbnail?))]
|
||||||
|
(->> (rx/zip update-events next)
|
||||||
|
(rx/map first)))))
|
||||||
|
|
||||||
|
on-thumbnail-data
|
||||||
|
(mf/use-callback
|
||||||
|
(mf/deps @shape-id)
|
||||||
|
(fn [data]
|
||||||
|
(reset! shape-id nil)
|
||||||
|
(timers/schedule
|
||||||
|
(fn []
|
||||||
|
(st/emit! (dwc/update-shapes [@shape-id]
|
||||||
|
#(assoc % :thumbnail data)))
|
||||||
|
(rx/push! next :next)))))
|
||||||
|
|
||||||
|
on-frame-not-found
|
||||||
|
(mf/use-callback
|
||||||
|
(fn [frame-id]
|
||||||
|
;; If we couldn't find the frame maybe is still rendering. We push the event again
|
||||||
|
;; after a time
|
||||||
|
(timers/schedule-on-idle #(dwp/update-frame-thumbnail frame-id))
|
||||||
|
(rx/push! next :next)))]
|
||||||
|
|
||||||
|
(mf/use-effect
|
||||||
|
(mf/deps render-frame)
|
||||||
|
(fn []
|
||||||
|
(let [sub (->> updates-stream
|
||||||
|
(rx/subs #(render-frame (-> (deref %) :frame-id))))]
|
||||||
|
|
||||||
|
#(rx/dispose! sub))))
|
||||||
|
|
||||||
|
(mf/use-layout-effect
|
||||||
|
(fn []
|
||||||
|
(timers/schedule-on-idle
|
||||||
|
#(st/emit! (dwp/watch-state-changes)))))
|
||||||
|
|
||||||
|
(when (and (some? @shape-id) (contains? objects @shape-id))
|
||||||
|
[:& frame-thumbnail {:key (str "thumbnail-" @shape-id)
|
||||||
|
:shape (get objects @shape-id)
|
||||||
|
:on-thumbnail-data on-thumbnail-data
|
||||||
|
:on-frame-not-found on-frame-not-found}])))
|
|
@ -34,7 +34,7 @@
|
||||||
(p/create
|
(p/create
|
||||||
(fn [resolve reject]
|
(fn [resolve reject]
|
||||||
(->> (http/send! {:uri uri
|
(->> (http/send! {:uri uri
|
||||||
:query {:file-id file-id :id page-id}
|
:query {:file-id file-id :id page-id :strip-thumbnails true}
|
||||||
:method :get})
|
:method :get})
|
||||||
(rx/map http/conditional-decode-transit)
|
(rx/map http/conditional-decode-transit)
|
||||||
(rx/mapcat handle-response)
|
(rx/mapcat handle-response)
|
||||||
|
@ -50,7 +50,7 @@
|
||||||
(let [prev (get @cache ckey)]
|
(let [prev (get @cache ckey)]
|
||||||
(if (= (:data prev) data)
|
(if (= (:data prev) data)
|
||||||
(:result prev)
|
(:result prev)
|
||||||
(let [elem (mf/element exports/page-svg #js {:data data :width "290" :height "150"})
|
(let [elem (mf/element exports/page-svg #js {:data data :width "290" :height "150" :thumbnails? true})
|
||||||
result (rds/renderToStaticMarkup elem)]
|
result (rds/renderToStaticMarkup elem)]
|
||||||
(swap! cache assoc ckey {:data data :result result})
|
(swap! cache assoc ckey {:data data :result result})
|
||||||
result))))
|
result))))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue