mirror of
https://github.com/penpot/penpot.git
synced 2025-05-05 23:45:53 +02:00
✨ Thumbnails for clipped and nested artboards
This commit is contained in:
parent
0bb0063be4
commit
a4cc57886b
8 changed files with 119 additions and 53 deletions
|
@ -14,6 +14,8 @@
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[cuerdas.core :as str]))
|
[cuerdas.core :as str]))
|
||||||
|
|
||||||
|
(declare reduce-objects)
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; GENERIC SHAPE SELECTORS AND PREDICATES
|
;; GENERIC SHAPE SELECTORS AND PREDICATES
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
@ -187,12 +189,21 @@
|
||||||
function of `get-immediate-children` for performance reasons. This
|
function of `get-immediate-children` for performance reasons. This
|
||||||
function is executed in the render hot path."
|
function is executed in the render hot path."
|
||||||
[objects]
|
[objects]
|
||||||
(let [lookup (d/getf objects)
|
(let [add-frame
|
||||||
xform (comp (keep lookup)
|
(fn [result shape]
|
||||||
(filter frame-shape?)
|
(cond-> result
|
||||||
(map :id))]
|
(frame-shape? shape)
|
||||||
(->> (:shapes (lookup uuid/zero))
|
(conj (:id shape))))]
|
||||||
(into [] xform))))
|
(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
|
(defn get-root-frames
|
||||||
"Retrieves all frame objects as vector. It is not implemented in
|
"Retrieves all frame objects as vector. It is not implemented in
|
||||||
|
@ -631,3 +642,28 @@
|
||||||
[objects parent-id candidate-child-id]
|
[objects parent-id candidate-child-id]
|
||||||
(let [parents (get-parents-seq objects candidate-child-id)]
|
(let [parents (get-parents-seq objects candidate-child-id)]
|
||||||
(some? (d/seek #(= % parent-id) parents))))
|
(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)))))))))
|
||||||
|
|
|
@ -270,6 +270,14 @@
|
||||||
(into [] (keep (d/getf objects)) children-ids)))
|
(into [] (keep (d/getf objects)) children-ids)))
|
||||||
workspace-page-objects =))
|
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
|
(def workspace-page-options
|
||||||
(l/derived :options workspace-page))
|
(l/derived :options workspace-page))
|
||||||
|
|
||||||
|
|
|
@ -22,3 +22,4 @@
|
||||||
(def current-page-id (mf/create-context nil))
|
(def current-page-id (mf/create-context nil))
|
||||||
(def current-file-id (mf/create-context nil))
|
(def current-file-id (mf/create-context nil))
|
||||||
(def scroll-ctx (mf/create-context nil))
|
(def scroll-ctx (mf/create-context nil))
|
||||||
|
(def active-frames-ctx (mf/create-context nil))
|
||||||
|
|
|
@ -45,7 +45,8 @@
|
||||||
(mf/defc frame-thumbnail
|
(mf/defc frame-thumbnail
|
||||||
{::mf/wrap-props false}
|
{::mf/wrap-props false}
|
||||||
[props]
|
[props]
|
||||||
(let [shape (obj/get props "shape")]
|
(let [shape (obj/get props "shape")
|
||||||
|
bounds (or (obj/get props "bounds") (:selrect shape))]
|
||||||
(when (:thumbnail shape)
|
(when (:thumbnail shape)
|
||||||
(let [{:keys [x y width height show-content]} shape
|
(let [{:keys [x y width height show-content]} shape
|
||||||
transform (gsh/transform-str shape)
|
transform (gsh/transform-str shape)
|
||||||
|
@ -72,10 +73,10 @@
|
||||||
[:image.frame-thumbnail
|
[:image.frame-thumbnail
|
||||||
{:id (dm/str "thumbnail-" (:id shape))
|
{:id (dm/str "thumbnail-" (:id shape))
|
||||||
:href (:thumbnail shape)
|
:href (:thumbnail shape)
|
||||||
:x (:x shape)
|
:x (:x bounds)
|
||||||
:y (:y shape)
|
:y (:y bounds)
|
||||||
:width (:width shape)
|
:width (:width bounds)
|
||||||
:height (:height shape)
|
:height (:height bounds)
|
||||||
;; DEBUG
|
;; DEBUG
|
||||||
:style {:filter (when (debug? :thumbnails) "sepia(1)")}}]]
|
:style {:filter (when (debug? :thumbnails) "sepia(1)")}}]]
|
||||||
|
|
||||||
|
|
|
@ -6,9 +6,9 @@
|
||||||
|
|
||||||
(ns app.main.ui.viewer.comments
|
(ns app.main.ui.viewer.comments
|
||||||
(:require
|
(:require
|
||||||
[app.common.geom.shapes :as gsh]
|
|
||||||
[app.common.geom.matrix :as gmt]
|
[app.common.geom.matrix :as gmt]
|
||||||
[app.common.geom.point :as gpt]
|
[app.common.geom.point :as gpt]
|
||||||
|
[app.common.geom.shapes :as gsh]
|
||||||
[app.main.data.comments :as dcm]
|
[app.main.data.comments :as dcm]
|
||||||
[app.main.data.events :as ev]
|
[app.main.data.events :as ev]
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
common."
|
common."
|
||||||
(:require
|
(:require
|
||||||
[app.common.pages.helpers :as cph]
|
[app.common.pages.helpers :as cph]
|
||||||
|
[app.main.ui.context :as ctx]
|
||||||
[app.main.ui.shapes.circle :as circle]
|
[app.main.ui.shapes.circle :as circle]
|
||||||
[app.main.ui.shapes.image :as image]
|
[app.main.ui.shapes.image :as image]
|
||||||
[app.main.ui.shapes.rect :as rect]
|
[app.main.ui.shapes.rect :as rect]
|
||||||
|
@ -52,7 +53,8 @@
|
||||||
(mf/use-memo
|
(mf/use-memo
|
||||||
(mf/deps objects)
|
(mf/deps objects)
|
||||||
#(cph/objects-by-frame 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
|
;; Render font faces only for shapes that are part of the root
|
||||||
;; frame but don't belongs to any other frame.
|
;; frame but don't belongs to any other frame.
|
||||||
(let [xform (comp
|
(let [xform (comp
|
||||||
|
@ -75,7 +77,15 @@
|
||||||
::mf/wrap-props false}
|
::mf/wrap-props false}
|
||||||
[props]
|
[props]
|
||||||
(let [shape (obj/get props "shape")
|
(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)))
|
(when (and (some? shape) (not (:hidden shape)))
|
||||||
[:*
|
[:*
|
||||||
(case (:type shape)
|
(case (:type shape)
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
|
[app.common.geom.shapes :as gsh]
|
||||||
[app.common.math :as mth]
|
[app.common.math :as mth]
|
||||||
[app.main.data.workspace.thumbnails :as dwt]
|
[app.main.data.workspace.thumbnails :as dwt]
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
|
@ -54,7 +55,7 @@
|
||||||
|
|
||||||
(defn use-render-thumbnail
|
(defn use-render-thumbnail
|
||||||
"Hook that will create the thumbnail thata"
|
"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)
|
(let [frame-canvas-ref (mf/use-ref nil)
|
||||||
frame-image-ref (mf/use-ref nil)
|
frame-image-ref (mf/use-ref nil)
|
||||||
|
@ -63,13 +64,21 @@
|
||||||
|
|
||||||
regenerate-thumbnail (mf/use-var false)
|
regenerate-thumbnail (mf/use-var false)
|
||||||
|
|
||||||
fixed-width (mth/clamp (:width shape) 250 2000)
|
all-children-ref (mf/use-memo (mf/deps id) #(refs/all-children-objects id))
|
||||||
fixed-height (/ (* (:height shape) fixed-width) (:width shape))
|
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)
|
image-url (mf/use-state nil)
|
||||||
observer-ref (mf/use-var 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))
|
updates-str (mf/use-memo #(rx/subject))
|
||||||
|
|
||||||
|
@ -101,7 +110,8 @@
|
||||||
(fn []
|
(fn []
|
||||||
(let [node @node-ref
|
(let [node @node-ref
|
||||||
frame-html (dom/node->xml node)
|
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-node (dom/query (dm/str "#frame-container-" (:id shape) " style"))
|
||||||
style-str (or (-> style-node dom/node->xml) "")
|
style-str (or (-> style-node dom/node->xml) "")
|
||||||
|
@ -201,6 +211,7 @@
|
||||||
(mf/html
|
(mf/html
|
||||||
[:*
|
[:*
|
||||||
[:> frame/frame-thumbnail {:key (dm/str (:id shape))
|
[:> frame/frame-thumbnail {:key (dm/str (:id shape))
|
||||||
|
:bounds shape-bb
|
||||||
:shape (cond-> shape
|
:shape (cond-> shape
|
||||||
(some? thumbnail-data)
|
(some? thumbnail-data)
|
||||||
(assoc :thumbnail thumbnail-data))}]
|
(assoc :thumbnail thumbnail-data))}]
|
||||||
|
@ -220,9 +231,9 @@
|
||||||
|
|
||||||
(when (some? @image-url)
|
(when (some? @image-url)
|
||||||
[:image {:ref frame-image-ref
|
[:image {:ref frame-image-ref
|
||||||
:x (:x shape)
|
:x x
|
||||||
:y (:y shape)
|
:y y
|
||||||
:href @image-url
|
:href @image-url
|
||||||
:width (:width shape)
|
:width width
|
||||||
:height (:height shape)
|
:height height
|
||||||
:on-load on-image-load}])])]))
|
:on-load on-image-load}])])]))
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes :as gsh]
|
||||||
[app.common.pages :as cp]
|
[app.common.pages :as cp]
|
||||||
[app.common.pages.helpers :as cph]
|
[app.common.pages.helpers :as cph]
|
||||||
[app.common.uuid :as uuid]
|
|
||||||
[app.main.data.shortcuts :as dsc]
|
[app.main.data.shortcuts :as dsc]
|
||||||
[app.main.data.workspace :as dw]
|
[app.main.data.workspace :as dw]
|
||||||
[app.main.data.workspace.path.shortcuts :as psc]
|
[app.main.data.workspace.path.shortcuts :as psc]
|
||||||
|
@ -189,19 +188,24 @@
|
||||||
grouped? (fn [id] (contains? #{:group :bool} (get-in objects [id :type])))
|
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 %))
|
root-frame-with-data? #(and (cph/root-frame? objects %) (d/not-empty? (get-in objects [% :shapes])))
|
||||||
remove-id? (cond-> (into #{} remove-xfm selected)
|
|
||||||
|
;; Set with the elements to remove from the hover list
|
||||||
|
remove-id?
|
||||||
|
(cond-> selected-with-parents
|
||||||
(not mod?)
|
(not mod?)
|
||||||
(into
|
(into (filter #(or (root-frame-with-data? %)
|
||||||
(filter #(or (and (cph/root-frame? objects %) (d/not-empty? (get-in objects [% :shapes])))
|
|
||||||
(group-empty-space? % objects ids)))
|
(group-empty-space? % objects ids)))
|
||||||
ids)
|
ids)
|
||||||
|
|
||||||
mod?
|
mod?
|
||||||
(into (filter grouped?) ids))
|
(into (filter grouped?) ids))
|
||||||
|
|
||||||
hover-shape (->> ids
|
hover-shape
|
||||||
|
(->> ids
|
||||||
(remove remove-id?)
|
(remove remove-id?)
|
||||||
(filter #(or (empty? focus) (cp/is-in-focus? objects focus %)))
|
(filter #(or (empty? focus) (cp/is-in-focus? objects focus %)))
|
||||||
(first)
|
(first)
|
||||||
|
@ -214,13 +218,7 @@
|
||||||
(let [root-frame-ids
|
(let [root-frame-ids
|
||||||
(mf/use-memo
|
(mf/use-memo
|
||||||
(mf/deps objects)
|
(mf/deps objects)
|
||||||
(fn []
|
#(cph/get-root-shapes-ids objects))
|
||||||
(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))))
|
|
||||||
modifiers (select-keys modifiers root-frame-ids)]
|
modifiers (select-keys modifiers root-frame-ids)]
|
||||||
(sfd/use-dynamic-modifiers objects globals/document modifiers)))
|
(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))
|
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))
|
active-selection (when (and (not= transform :move) (= (count selected-frames) 1)) (first selected-frames))
|
||||||
hover-frame (last @hover-ids)
|
last-hover-ids (mf/use-var nil)]
|
||||||
last-hover-frame (mf/use-var nil)]
|
|
||||||
|
|
||||||
(mf/use-effect
|
(mf/use-effect
|
||||||
(mf/deps hover-frame)
|
(mf/deps @hover-ids)
|
||||||
(fn []
|
(fn []
|
||||||
(when (some? hover-frame)
|
(when (d/not-empty? @hover-ids)
|
||||||
(reset! last-hover-frame hover-frame))))
|
(reset! last-hover-ids (set @hover-ids)))))
|
||||||
|
|
||||||
(mf/use-effect
|
(mf/use-effect
|
||||||
(mf/deps objects @hover-ids selected zoom transform vbox)
|
(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
|
;; - If no hovering over any frames we keep the previous active one
|
||||||
;; - Check always that the active frames are inside the vbox
|
;; - 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]
|
(fn [id]
|
||||||
(or
|
(or
|
||||||
;; Zoom > 130% shows every frame
|
;; Zoom > 130% shows every frame
|
||||||
|
@ -267,7 +266,7 @@
|
||||||
;; Zoom >= 25% will show frames hovering
|
;; Zoom >= 25% will show frames hovering
|
||||||
(and
|
(and
|
||||||
(>= zoom 0.25)
|
(>= 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
|
;; Otherwise, if it's a selected frame
|
||||||
(= id active-selection)
|
(= id active-selection)
|
||||||
|
|
Loading…
Add table
Reference in a new issue