diff --git a/frontend/src/app/main/data/workspace/thumbnails.cljs b/frontend/src/app/main/data/workspace/thumbnails.cljs index 6149fd4d4..027b4b941 100644 --- a/frontend/src/app/main/data/workspace/thumbnails.cljs +++ b/frontend/src/app/main/data/workspace/thumbnails.cljs @@ -28,22 +28,27 @@ (rx/filter #(= % id)) (rx/take 1))) -(defn thumbnail-stream +(defn thumbnail-canvas-blob-stream [object-id] - (rx/create - (fn [subs] - ;; We look in the DOM a canvas that 1) matches the id and 2) that it's not empty - ;; will be empty on first rendering before drawing the thumbnail and we don't want to store that - (let [node (dom/query (dm/fmt "canvas.thumbnail-canvas[data-object-id='%'][data-ready='true']" object-id))] - (if (some? node) + ;; Look for the thumbnail canvas to send the data to the backend + + (let [node (dom/query (dm/fmt "canvas.thumbnail-canvas[data-object-id='%'][data-ready='true']" object-id)) + stopper (->> st/stream + (rx/filter (ptk/type? :app.main.data.workspace/finalize-page)) + (rx/take 1))] + (if (some? node) + ;; Success: we generate the blob (async call) + (rx/create + (fn [subs] (.toBlob node (fn [blob] (rx/push! subs blob) (rx/end! subs)) - "image/png") + "image/png"))) - ;; If we cannot find the node we send `nil` and the upsert will delete the thumbnail - (do (rx/push! subs nil) - (rx/end! subs))))))) + ;; Not found, we retry after delay + (->> (rx/timer 250) + (rx/flat-map #(thumbnail-canvas-blob-stream object-id)) + (rx/take-until stopper))))) (defn clear-thumbnail [page-id frame-id] @@ -64,7 +69,7 @@ (watch [_ state _] (let [object-id (dm/str page-id frame-id) file-id (or file-id (:current-file-id state)) - blob-result (thumbnail-stream object-id) + blob-result (thumbnail-canvas-blob-stream object-id) params {:file-id file-id :object-id object-id :data nil}] (rx/concat @@ -80,7 +85,7 @@ (rx/merge-map (fn [data] - (if (some? file-id) + (if (and (some? data) (some? file-id)) (let [params (assoc params :data data)] (rx/merge ;; Update the local copy of the thumbnails so we don't need to request it again diff --git a/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs b/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs index 040b94485..f6a990346 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs @@ -16,7 +16,6 @@ [app.main.ui.hooks :as hooks] [app.main.ui.shapes.frame :as frame] [app.util.dom :as dom] - [app.util.object :as obj] [app.util.timers :as ts] [beicon.core :as rx] [cuerdas.core :as str] @@ -114,26 +113,39 @@ generate-thumbnail (mf/use-callback - (fn [] - (let [node @node-ref - frame-html (dom/node->xml node) + (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 + (let [canvas-node (mf/ref-val frame-canvas-ref)] + (dom/set-property! canvas-node "data-ready" "false")) + (let [node @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 shape) " style")) - {:keys [x y width height]} @shape-bb-ref + {:keys [x y width height]} @shape-bb-ref + viewbox (dm/str x " " y " " width " " height) - style-node (dom/query (dm/str "#frame-container-" (:id shape) " style")) - style-str (or (-> style-node dom/node->xml) "") + ;; This is way faster than creating a node through the DOM API + svg-data + (dm/fmt "% %" + viewbox + width + height + (if (some? style-node) (dom/node->xml style-node) "") + (dom/node->xml node)) - svg-node - (-> (dom/make-node "http://www.w3.org/2000/svg" "svg") - (dom/set-property! "version" "1.1") - (dom/set-property! "viewBox" (dm/str x " " y " " width " " height)) - (dom/set-property! "width" width) - (dom/set-property! "height" height) - (dom/set-property! "fill" "none") - (obj/set! "innerHTML" (dm/str style-str frame-html))) - img-src (-> svg-node dom/node->xml dom/svg->data-uri)] + blob (js/Blob. #js [svg-data] #js {:type "image/svg+xml;charset=utf-8"}) - (reset! image-url img-src)))) + img-src (.createObjectURL js/URL blob)] + (reset! image-url img-src)) + + ;; Node not yet ready, we schedule a new generation + (ts/schedule generate-thumbnail))) + + (catch :default e + (.error js/console e))))) on-change-frame (mf/use-callback diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs index ae30372b4..36493ea16 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs @@ -248,16 +248,15 @@ (dissoc :content))) (defn- is-bool-descendant? - [shape all-shapes selected-shape-ids] + [[_ shape] objects selected-shape-ids] + (let [parent-id (:parent-id shape) - parent (->> all-shapes - (filter #(= (:id %) parent-id)) - first)] + parent (get objects parent-id)] (cond (nil? shape) false ;; failsafe - (some #{(:id shape)} selected-shape-ids) false ;; if it is one of the selected shapes, it is considerer not a bool descendant + (contains? selected-shape-ids (:id shape)) false ;; if it is one of the selected shapes, it is considerer not a bool descendant (= :bool (:type parent)) true ;; if its parent is of type bool, it is a bool descendant - :else (is-bool-descendant? parent all-shapes selected-shape-ids)))) ;; else, check its parent + :else (recur [parent-id parent] objects selected-shape-ids)))) ;; else, check its parent (mf/defc options {::mf/wrap [#(mf/memo' % (mf/check-props ["shapes" "shapes-with-children" "page-id" "file-id"]))] @@ -267,8 +266,13 @@ shapes-with-children (unchecked-get props "shapes-with-children") ;; remove children from bool shapes - shape-ids (map :id shapes) - shapes-with-children (filter #(not (is-bool-descendant? % shapes-with-children shape-ids)) shapes-with-children) + shape-ids (into #{} (map :id) shapes) + + objects (->> shapes-with-children (group-by :id) (d/mapm (fn [_ v] (first v)))) + objects + (into {} + (filter #(not (is-bool-descendant? % objects shape-ids))) + objects) workspace-modifiers (mf/deref refs/workspace-modifiers) shapes (map #(gsh/transform-shape % (get-in workspace-modifiers [(:id %) :modifiers])) shapes) @@ -276,7 +280,7 @@ page-id (unchecked-get props "page-id") file-id (unchecked-get props "file-id") shared-libs (unchecked-get props "shared-libs") - objects (->> shapes-with-children (group-by :id) (d/mapm (fn [_ v] (first v)))) + show-caps (some #(and (= :path (:type %)) (gsh/open-path? %)) shapes) ;; Selrect/points only used for measures and it's the one that changes the most. We separate it diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs index 60c62f863..b0a1488a3 100644 --- a/frontend/src/app/util/dom.cljs +++ b/frontend/src/app/util/dom.cljs @@ -264,12 +264,14 @@ (defn append-child! [^js el child] (when (some? el) - (.appendChild ^js el child))) + (.appendChild ^js el child)) + el) (defn remove-child! [^js el child] (when (some? el) - (.removeChild ^js el child))) + (.removeChild ^js el child)) + el) (defn get-first-child [^js el] @@ -639,3 +641,13 @@ {:ascent (.-fontBoundingBoxAscent measure) :descent (.-fontBoundingBoxDescent measure)})) + +(defn clone-node + ([^js node] + (clone-node node true)) + ([^js node deep?] + (.cloneNode node deep?))) + +(defn has-children? + [^js node] + (> (-> node .-children .-length) 0)) diff --git a/frontend/src/debug.cljs b/frontend/src/debug.cljs index 4e45f7ae5..e66d23dea 100644 --- a/frontend/src/debug.cljs +++ b/frontend/src/debug.cljs @@ -131,9 +131,7 @@ (defn ^:export ^boolean debug? [option] - (if *assert* - (boolean (@*debug* option)) - false)) + (boolean (@*debug* option))) (defn ^:export toggle-debug [name] (let [option (keyword name)] (if (debug? option)