diff --git a/common/src/app/common/pages/helpers.cljc b/common/src/app/common/pages/helpers.cljc index d7a86f7a4..1d5e6cc2f 100644 --- a/common/src/app/common/pages/helpers.cljc +++ b/common/src/app/common/pages/helpers.cljc @@ -14,6 +14,8 @@ [app.common.uuid :as uuid] [cuerdas.core :as str])) +(declare reduce-objects) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; GENERIC SHAPE SELECTORS AND PREDICATES ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -187,12 +189,21 @@ function of `get-immediate-children` for performance reasons. This function is executed in the render hot path." [objects] - (let [lookup (d/getf objects) - xform (comp (keep lookup) - (filter frame-shape?) - (map :id))] - (->> (:shapes (lookup uuid/zero)) - (into [] xform)))) + (let [add-frame + (fn [result shape] + (cond-> result + (frame-shape? shape) + (conj (:id shape))))] + (reduce-objects objects (complement frame-shape?) add-frame []))) + +(defn get-root-shapes-ids + [objects] + (let [add-shape + (fn [result shape] + (cond-> result + (not (frame-shape? shape)) + (conj (:id shape))))] + (reduce-objects objects (complement frame-shape?) add-shape []))) (defn get-root-frames "Retrieves all frame objects as vector. It is not implemented in @@ -631,3 +642,28 @@ [objects parent-id candidate-child-id] (let [parents (get-parents-seq objects candidate-child-id)] (some? (d/seek #(= % parent-id) parents)))) + +(defn reduce-objects + ([objects reducer-fn init-val] + (reduce-objects objects nil reducer-fn init-val)) + + ([objects check-children? reducer-fn init-val] + (let [root-children (get-in objects [uuid/zero :shapes])] + (if (empty? root-children) + init-val + + (loop [current-val init-val + current-id (first root-children) + pending-ids (rest root-children)] + + + (let [current-shape (get objects current-id) + next-val (reducer-fn current-val current-shape) + next-pending-ids + (if (or (nil? check-children?) (check-children? current-shape)) + (concat (or (:shapes current-shape) []) pending-ids) + pending-ids)] + + (if (empty? next-pending-ids) + next-val + (recur next-val (first next-pending-ids) (rest next-pending-ids))))))))) diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 4e6998827..114ac6744 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -270,6 +270,14 @@ (into [] (keep (d/getf objects)) children-ids))) workspace-page-objects =)) +(defn all-children-objects + [id] + (l/derived + (fn [objects] + (let [children-ids (cph/get-children-ids objects id)] + (into [] (keep (d/getf objects)) children-ids))) + workspace-page-objects =)) + (def workspace-page-options (l/derived :options workspace-page)) diff --git a/frontend/src/app/main/ui/context.cljs b/frontend/src/app/main/ui/context.cljs index 634739944..d430ea5f2 100644 --- a/frontend/src/app/main/ui/context.cljs +++ b/frontend/src/app/main/ui/context.cljs @@ -21,4 +21,5 @@ (def current-project-id (mf/create-context nil)) (def current-page-id (mf/create-context nil)) (def current-file-id (mf/create-context nil)) -(def scroll-ctx (mf/create-context nil)) \ No newline at end of file +(def scroll-ctx (mf/create-context nil)) +(def active-frames-ctx (mf/create-context nil)) diff --git a/frontend/src/app/main/ui/shapes/frame.cljs b/frontend/src/app/main/ui/shapes/frame.cljs index 5eb258c54..8fb3772df 100644 --- a/frontend/src/app/main/ui/shapes/frame.cljs +++ b/frontend/src/app/main/ui/shapes/frame.cljs @@ -45,7 +45,8 @@ (mf/defc frame-thumbnail {::mf/wrap-props false} [props] - (let [shape (obj/get props "shape")] + (let [shape (obj/get props "shape") + bounds (or (obj/get props "bounds") (:selrect shape))] (when (:thumbnail shape) (let [{:keys [x y width height show-content]} shape transform (gsh/transform-str shape) @@ -72,10 +73,10 @@ [:image.frame-thumbnail {:id (dm/str "thumbnail-" (:id shape)) :href (:thumbnail shape) - :x (:x shape) - :y (:y shape) - :width (:width shape) - :height (:height shape) + :x (:x bounds) + :y (:y bounds) + :width (:width bounds) + :height (:height bounds) ;; DEBUG :style {:filter (when (debug? :thumbnails) "sepia(1)")}}]] diff --git a/frontend/src/app/main/ui/viewer/comments.cljs b/frontend/src/app/main/ui/viewer/comments.cljs index 27bf9a08b..8ce744c9e 100644 --- a/frontend/src/app/main/ui/viewer/comments.cljs +++ b/frontend/src/app/main/ui/viewer/comments.cljs @@ -6,9 +6,9 @@ (ns app.main.ui.viewer.comments (:require - [app.common.geom.shapes :as gsh] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] + [app.common.geom.shapes :as gsh] [app.main.data.comments :as dcm] [app.main.data.events :as ev] [app.main.refs :as refs] diff --git a/frontend/src/app/main/ui/workspace/shapes.cljs b/frontend/src/app/main/ui/workspace/shapes.cljs index 7905df951..166aba242 100644 --- a/frontend/src/app/main/ui/workspace/shapes.cljs +++ b/frontend/src/app/main/ui/workspace/shapes.cljs @@ -13,6 +13,7 @@ common." (:require [app.common.pages.helpers :as cph] + [app.main.ui.context :as ctx] [app.main.ui.shapes.circle :as circle] [app.main.ui.shapes.image :as image] [app.main.ui.shapes.rect :as rect] @@ -52,7 +53,8 @@ (mf/use-memo (mf/deps objects) #(cph/objects-by-frame objects))] - [:* + + [:& (mf/provider ctx/active-frames-ctx) {:value active-frames} ;; Render font faces only for shapes that are part of the root ;; frame but don't belongs to any other frame. (let [xform (comp @@ -75,7 +77,15 @@ ::mf/wrap-props false} [props] (let [shape (obj/get props "shape") - opts #js {:shape shape}] + + active-frames + (when (cph/root-frame? shape) (mf/use-ctx ctx/active-frames-ctx)) + + thumbnail? + (and (some? active-frames) + (not (contains? active-frames (:id shape)))) + + opts #js {:shape shape :thumbnail? thumbnail?}] (when (and (some? shape) (not (:hidden shape))) [:* (case (:type shape) 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 c61ca1d84..f7df70e1b 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 @@ -8,6 +8,7 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] + [app.common.geom.shapes :as gsh] [app.common.math :as mth] [app.main.data.workspace.thumbnails :as dwt] [app.main.refs :as refs] @@ -54,7 +55,7 @@ (defn use-render-thumbnail "Hook that will create the thumbnail thata" - [page-id {:keys [id x y width height] :as shape} node-ref rendered? disable? force-render] + [page-id {:keys [id] :as shape} node-ref rendered? disable? force-render] (let [frame-canvas-ref (mf/use-ref nil) frame-image-ref (mf/use-ref nil) @@ -63,13 +64,21 @@ regenerate-thumbnail (mf/use-var false) - fixed-width (mth/clamp (:width shape) 250 2000) - fixed-height (/ (* (:height shape) fixed-width) (:width shape)) + all-children-ref (mf/use-memo (mf/deps id) #(refs/all-children-objects id)) + all-children (mf/deref all-children-ref) + + {:keys [x y width height] :as shape-bb} + (if (:show-content shape) + (gsh/selection-rect all-children) + (-> shape :points gsh/points->selrect)) + + fixed-width (mth/clamp width 250 2000) + fixed-height (/ (* height fixed-width) width) image-url (mf/use-state nil) observer-ref (mf/use-var nil) - shape-ref (hooks/use-update-var shape) + shape-bb-ref (hooks/use-update-var shape-bb) updates-str (mf/use-memo #(rx/subject)) @@ -101,7 +110,8 @@ (fn [] (let [node @node-ref frame-html (dom/node->xml node) - {:keys [x y width height]} @shape-ref + + {:keys [x y width height]} @shape-bb-ref style-node (dom/query (dm/str "#frame-container-" (:id shape) " style")) style-str (or (-> style-node dom/node->xml) "") @@ -201,6 +211,7 @@ (mf/html [:* [:> frame/frame-thumbnail {:key (dm/str (:id shape)) + :bounds shape-bb :shape (cond-> shape (some? thumbnail-data) (assoc :thumbnail thumbnail-data))}] @@ -220,9 +231,9 @@ (when (some? @image-url) [:image {:ref frame-image-ref - :x (:x shape) - :y (:y shape) + :x x + :y y :href @image-url - :width (:width shape) - :height (:height shape) + :width width + :height height :on-load on-image-load}])])])) diff --git a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs index 6dca71f82..72683d8e7 100644 --- a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs @@ -10,7 +10,6 @@ [app.common.geom.shapes :as gsh] [app.common.pages :as cp] [app.common.pages.helpers :as cph] - [app.common.uuid :as uuid] [app.main.data.shortcuts :as dsc] [app.main.data.workspace :as dw] [app.main.data.workspace.path.shortcuts :as psc] @@ -189,23 +188,28 @@ grouped? (fn [id] (contains? #{:group :bool} (get-in objects [id :type]))) + selected-with-parents + (into #{} (mapcat #(cph/get-parent-ids objects %)) selected) - remove-xfm (mapcat #(cph/get-parent-ids objects %)) - remove-id? (cond-> (into #{} remove-xfm selected) - (not mod?) - (into - (filter #(or (and (cph/root-frame? objects %) (d/not-empty? (get-in objects [% :shapes]))) - (group-empty-space? % objects ids))) - ids) + root-frame-with-data? #(and (cph/root-frame? objects %) (d/not-empty? (get-in objects [% :shapes]))) - mod? - (into (filter grouped?) ids)) + ;; Set with the elements to remove from the hover list + remove-id? + (cond-> selected-with-parents + (not mod?) + (into (filter #(or (root-frame-with-data? %) + (group-empty-space? % objects ids))) + ids) - hover-shape (->> ids - (remove remove-id?) - (filter #(or (empty? focus) (cp/is-in-focus? objects focus %))) - (first) - (get objects))] + mod? + (into (filter grouped?) ids)) + + hover-shape + (->> ids + (remove remove-id?) + (filter #(or (empty? focus) (cp/is-in-focus? objects focus %))) + (first) + (get objects))] (reset! hover hover-shape) (reset! hover-ids ids)))))) @@ -214,13 +218,7 @@ (let [root-frame-ids (mf/use-memo (mf/deps objects) - (fn [] - (let [frame? (into #{} (cph/get-frames-ids objects)) - ;; Removes from zero/shapes attribute all the frames so we can ask only for - ;; the non-frame children - objects (-> objects - (update-in [uuid/zero :shapes] #(filterv (comp not frame?) %)))] - (cph/get-children-ids objects uuid/zero)))) + #(cph/get-root-shapes-ids objects)) modifiers (select-keys modifiers root-frame-ids)] (sfd/use-dynamic-modifiers objects globals/document modifiers))) @@ -238,14 +236,13 @@ selected-shapes-frames (mf/use-memo (mf/deps selected) #(into #{} xf-selected-frame selected)) active-selection (when (and (not= transform :move) (= (count selected-frames) 1)) (first selected-frames)) - hover-frame (last @hover-ids) - last-hover-frame (mf/use-var nil)] + last-hover-ids (mf/use-var nil)] (mf/use-effect - (mf/deps hover-frame) + (mf/deps @hover-ids) (fn [] - (when (some? hover-frame) - (reset! last-hover-frame hover-frame)))) + (when (d/not-empty? @hover-ids) + (reset! last-hover-ids (set @hover-ids))))) (mf/use-effect (mf/deps objects @hover-ids selected zoom transform vbox) @@ -258,7 +255,9 @@ ;; - If no hovering over any frames we keep the previous active one ;; - Check always that the active frames are inside the vbox - (let [is-active-frame? + (let [hover-ids? (set @hover-ids) + + is-active-frame? (fn [id] (or ;; Zoom > 130% shows every frame @@ -267,7 +266,7 @@ ;; Zoom >= 25% will show frames hovering (and (>= zoom 0.25) - (or (= id hover-frame) (= id @last-hover-frame))) + (or (contains? hover-ids? id) (contains? @last-hover-ids id))) ;; Otherwise, if it's a selected frame (= id active-selection)