From d5886123d88aafe3c3c75ea65489fc07c682e243 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 28 Dec 2021 17:33:22 +0100 Subject: [PATCH] :zap: Improved thumbnails handling --- .../app/main/data/workspace/persistence.cljs | 109 ++++++++++-------- .../app/main/data/workspace/selection.cljs | 6 +- .../app/main/data/workspace/transforms.cljs | 5 + .../app/main/ui/workspace/shapes/frame.cljs | 57 ++++----- .../viewport/thumbnail_renderer.cljs | 16 ++- .../app/main/ui/workspace/viewport/utils.cljs | 11 +- frontend/src/app/util/dom.cljs | 3 +- frontend/src/app/util/timers.cljs | 14 ++- 8 files changed, 124 insertions(+), 97 deletions(-) diff --git a/frontend/src/app/main/data/workspace/persistence.cljs b/frontend/src/app/main/data/workspace/persistence.cljs index cff4864e7..eb89d83b2 100644 --- a/frontend/src/app/main/data/workspace/persistence.cljs +++ b/frontend/src/app/main/data/workspace/persistence.cljs @@ -81,32 +81,32 @@ (obj/set! js/window "onbeforeunload" nil) (st/emit! (update-persistence-status {:status :saved})))] (->> (rx/merge - (->> stream - (rx/filter dch/commit-changes?) - (rx/map deref) - (rx/filter local-file?) - (rx/tap on-dirty) - (rx/buffer-until notifier) - (rx/filter (complement empty?)) - (rx/map (fn [buf] - (->> (into [] (comp (map #(assoc % :id (uuid/next))) - (map #(assoc % :file-id file-id))) - buf) - (persist-changes file-id)))) - (rx/tap on-saving) - (rx/take-until (rx/delay 100 stoper))) - (->> stream - (rx/filter dch/commit-changes?) - (rx/map deref) - (rx/filter library-file?) - (rx/filter (complement #(empty? (:changes %)))) - (rx/map persist-synchronous-changes) - (rx/take-until (rx/delay 100 stoper))) - (->> stream - (rx/filter (ptk/type? ::changes-persisted)) - (rx/tap on-saved) - (rx/ignore) - (rx/take-until stoper))) + (->> stream + (rx/filter dch/commit-changes?) + (rx/map deref) + (rx/filter local-file?) + (rx/tap on-dirty) + (rx/buffer-until notifier) + (rx/filter (complement empty?)) + (rx/map (fn [buf] + (->> (into [] (comp (map #(assoc % :id (uuid/next))) + (map #(assoc % :file-id file-id))) + buf) + (persist-changes file-id)))) + (rx/tap on-saving) + (rx/take-until (rx/delay 100 stoper))) + (->> stream + (rx/filter dch/commit-changes?) + (rx/map deref) + (rx/filter library-file?) + (rx/filter (complement #(empty? (:changes %)))) + (rx/map persist-synchronous-changes) + (rx/take-until (rx/delay 100 stoper))) + (->> stream + (rx/filter (ptk/type? ::changes-persisted)) + (rx/tap on-saved) + (rx/ignore) + (rx/take-until stoper))) (rx/subs #(st/emit! %) (constantly nil) (fn [] @@ -520,25 +520,25 @@ (defn clone-media-object [{:keys [file-id object-id] :as params}] (us/assert ::clone-media-objects-params params) - (ptk/reify ::clone-media-objects - ptk/WatchEvent - (watch [_ _ _] - (let [{:keys [on-success on-error] - :or {on-success identity - on-error identity}} (meta params) - params {:is-local true - :file-id file-id - :id object-id}] + (ptk/reify ::clone-media-objects + ptk/WatchEvent + (watch [_ _ _] + (let [{:keys [on-success on-error] + :or {on-success identity + on-error identity}} (meta params) + params {:is-local true + :file-id file-id + :id object-id}] - (rx/concat - (rx/of (dm/show {:content (tr "media.loading") - :type :info - :timeout nil - :tag :media-loading})) - (->> (rp/mutation! :clone-file-media-object params) - (rx/do on-success) - (rx/catch on-error) - (rx/finalize #(st/emit! (dm/hide-tag :media-loading))))))))) + (rx/concat + (rx/of (dm/show {:content (tr "media.loading") + :type :info + :timeout nil + :tag :media-loading})) + (->> (rp/mutation! :clone-file-media-object params) + (rx/do on-success) + (rx/catch on-error) + (rx/finalize #(st/emit! (dm/hide-tag :media-loading))))))))) ;; --- Helpers @@ -555,12 +555,17 @@ [ids] (ptk/reify ::remove-thumbnails ptk/WatchEvent - (watch [_ _ _] + (watch [_ state _] ;; Removes the thumbnail while it's regenerated - (rx/of (dch/update-shapes - ids - #(dissoc % :thumbnail) - {:save-undo? false}))))) + (let [moving? (= :move (get-in state [:workspace-local :transform])) + selected? (wsh/lookup-selected state) + ;; When we're moving the current frame it's safe to keep the thumbnail + ;; if it's resize we need to remove it immeditely + ids (cond->> ids moving? (remove selected?))] + + (if (empty? ids) + (rx/empty) + (rx/of (dch/update-shapes ids #(dissoc % :thumbnail) {:save-undo? false}))))))) (defn update-frame-thumbnail [frame-id] @@ -607,7 +612,8 @@ xform (comp (mapcat extract-ids) (map get-frame-id) (remove nil?) - (filter #(not= uuid/zero %)))] + (filter #(not= uuid/zero %)) + (filter #(contains? new-objects %)))] (into #{} xform changes))) @@ -647,7 +653,8 @@ (rx/filter dch/commit-changes?) (rx/filter (comp not thumbnail-change?)) (rx/with-latest-from objects-stream) - (rx/map extract-frame-changes)) + (rx/map extract-frame-changes) + (rx/share)) frames (-> state wsh/lookup-page-objects cp/select-frames) no-thumb-frames (->> frames diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index cc8331ffa..890d576f7 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -449,7 +449,7 @@ (ptk/reify ::duplicate-selected ptk/WatchEvent (watch [it state _] - (when (nil? (get-in state [:workspace-local :transform])) + (when (or (not move-delta?) (nil? (get-in state [:workspace-local :transform]))) (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) selected (wsh/lookup-selected state) @@ -475,10 +475,10 @@ id-duplicated (when (= (count selected) 1) (first selected))] - (rx/of (dch/commit-changes {:redo-changes rchanges + (rx/of (select-shapes selected) + (dch/commit-changes {:redo-changes rchanges :undo-changes uchanges :origin it}) - (select-shapes selected) (memorize-duplicated id-original id-duplicated))))))) (defn change-hover-state diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 9862703ae..250fa536d 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -510,6 +510,11 @@ (defn- start-move-duplicate [from-position] (ptk/reify ::start-move-duplicate + ptk/UpdateEvent + (update [_ state] + (-> state + (assoc-in [:workspace-local :transform] :move))) + ptk/WatchEvent (watch [_ _ stream] (->> stream diff --git a/frontend/src/app/main/ui/workspace/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/shapes/frame.cljs index f3fad5640..827462f0c 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame.cljs @@ -90,41 +90,46 @@ (mf/create-element component props) (mf/create-element frame-placeholder props))))) -(defn frame-wrapper-factory +;; Draw the frame proper as a deferred component +(defn deferred-frame-shape-factory [shape-wrapper] (let [frame-shape (frame/frame-shape shape-wrapper)] + (mf/fnc defered-frame-wrapper + {::mf/wrap-props false + ::mf/wrap [#(mf/memo' % (mf/check-props ["shape" "childs"])) + custom-deferred]} + [props] + (let [shape (unchecked-get props "shape") + childs (unchecked-get props "childs")] + [:& frame-shape {:shape shape + :childs childs}])))) + +(defn frame-wrapper-factory + [shape-wrapper] + (let [deferred-frame-shape (deferred-frame-shape-factory shape-wrapper)] (mf/fnc frame-wrapper - {::mf/wrap [#(mf/memo' % check-frame-props) custom-deferred] + {::mf/wrap [#(mf/memo' % check-frame-props)] ::mf/wrap-props false} [props] - (let [shape (unchecked-get props "shape") - objects (unchecked-get props "objects") - thumbnail? (unchecked-get props "thumbnail?") - children (-> (mapv (d/getf objects) (:shapes shape)) - (hooks/use-equal-memo)) + (let [shape (unchecked-get props "shape") + objects (unchecked-get props "objects") + thumbnail? (unchecked-get props "thumbnail?") - all-children (-> (cp/get-children-objects (:id shape) objects) - (hooks/use-equal-memo)) + children (-> (mapv (d/getf objects) (:shapes shape)) + (hooks/use-equal-memo)) + all-children (-> (cp/get-children-objects (:id shape) objects) + (hooks/use-equal-memo)) - rendered? (mf/use-state false) - - show-thumbnail? (and thumbnail? (some? (:thumbnail shape))) - - on-dom - (mf/use-callback - (fn [node] - (ts/schedule-on-idle #(reset! rendered? (some? node)))))] + show-thumbnail? (and thumbnail? (some? (:thumbnail shape)))] (when (some? shape) [:g.frame-wrapper {:display (when (:hidden shape) "none")} - - (when-not show-thumbnail? - [:> shape-container {:shape shape :ref on-dom} - [:& ff/fontfaces-style {:shapes all-children}] - [:& frame-shape {:shape shape - :childs children}]]) - - (when (or (not @rendered?) show-thumbnail?) - [:& thumbnail {:shape shape}])]))))) + [:> shape-container {:shape shape} + [:& ff/fontfaces-style {:shapes all-children}] + (if show-thumbnail? + [:& thumbnail {:shape shape}] + [:& deferred-frame-shape + {:shape shape + :childs children}])]]))))) diff --git a/frontend/src/app/main/ui/workspace/viewport/thumbnail_renderer.cljs b/frontend/src/app/main/ui/workspace/viewport/thumbnail_renderer.cljs index a3a992e6e..8b53a9e21 100644 --- a/frontend/src/app/main/ui/workspace/viewport/thumbnail_renderer.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/thumbnail_renderer.cljs @@ -28,10 +28,14 @@ (when node (let [img-node (mf/ref-val thumbnail-img)] (timers/schedule-on-idle - #(let [frame-node (dom/get-element (str "shape-" (:id shape))) - loading-node (when frame-node - (dom/query frame-node "[data-loading=\"true\"]"))] - (if (and (some? frame-node) (not (some? loading-node))) + #(let [frame-node (dom/get-element (str "shape-" (:id shape))) + thumb-node (dom/query frame-node ".frame-thumbnail") + loading-node (dom/query frame-node "[data-loading=\"true\"]")] + (if (and (some? frame-node) + ;; Not render if the thumbnail is in display + (nil? thumb-node) + ;; Not render if some image is still loading + (nil? loading-node)) (let [frame-html (-> (js/XMLSerializer.) (.serializeToString frame-node)) @@ -125,7 +129,9 @@ ;; after a time (reset! shape-id nil) (rx/push! next :next) - (timers/schedule-on-idle (st/emitf (dwp/update-frame-thumbnail frame-id)))))] + (timers/schedule-on-idle + 100 + (st/emitf (dwp/update-frame-thumbnail frame-id)))))] (mf/use-effect (mf/deps render-frame) diff --git a/frontend/src/app/main/ui/workspace/viewport/utils.cljs b/frontend/src/app/main/ui/workspace/viewport/utils.cljs index a7e15507c..074be0bb2 100644 --- a/frontend/src/app/main/ui/workspace/viewport/utils.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/utils.cljs @@ -52,17 +52,16 @@ ;; When the shape is a frame we maybe need to move its thumbnail thumb-node (when frame? (dom/get-element (str "thumbnail-" id)))] - (cond - (some? thumb-node) - [(.-parentNode thumb-node)] - (and (some? shape-node) frame?) - [(dom/query shape-node ".frame-background") + (cond + frame? + [thumb-node + (dom/query shape-node ".frame-background") (dom/query shape-node ".frame-clip")] ;; For groups we don't want to transform the whole group but only ;; its filters/masks - (and (some? shape-node) mask?) + mask? [(dom/query shape-node ".mask-clip-path") (dom/query shape-node ".mask-shape")] diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs index 080e8293f..25a59a199 100644 --- a/frontend/src/app/util/dom.cljs +++ b/frontend/src/app/util/dom.cljs @@ -186,7 +186,8 @@ (defn query [el query] - (.querySelector el query)) + (when (some? el) + (.querySelector el query))) (defn get-client-position [event] diff --git a/frontend/src/app/util/timers.cljs b/frontend/src/app/util/timers.cljs index be8cfdbc8..b1314bf9e 100644 --- a/frontend/src/app/util/timers.cljs +++ b/frontend/src/app/util/timers.cljs @@ -44,11 +44,15 @@ (def ^:private cancel-idle-callback #(js/clearTimeout %)))) (defn schedule-on-idle - [func] - (let [sem (request-idle-callback #(func))] - (reify rx/IDisposable - (-dispose [_] - (cancel-idle-callback sem))))) + ([ms func] + ;; Schedule on idle after `ms` time + (schedule ms #(schedule-on-idle func))) + + ([func] + (let [sem (request-idle-callback #(func))] + (reify rx/IDisposable + (-dispose [_] + (cancel-idle-callback sem)))))) (def ^:private request-animation-frame (or (and (exists? js/window) (.-requestAnimationFrame js/window))