mirror of
https://github.com/penpot/penpot.git
synced 2025-05-14 22:56:40 +02:00
commit
462ec0c12a
10 changed files with 140 additions and 84 deletions
|
@ -8,6 +8,14 @@
|
||||||
### :arrow_up: Deps updates
|
### :arrow_up: Deps updates
|
||||||
### :heart: Community contributions by (Thank you!)
|
### :heart: Community contributions by (Thank you!)
|
||||||
|
|
||||||
|
## 1.13.4-beta
|
||||||
|
|
||||||
|
### :bug: Bugs fixed
|
||||||
|
|
||||||
|
- Fix undo when drawing curves [Taiga #3523](https://tree.taiga.io/project/penpot/issue/3523)
|
||||||
|
- Fix issue with text edition and certain fonts (WorkSans, Raleway, ...) and foreign objects [Taiga #3521](https://tree.taiga.io/project/penpot/issue/3521)
|
||||||
|
- Fix thumbnail generation when concurrent edition [Taiga #3522](https://tree.taiga.io/project/penpot/issue/3522)
|
||||||
|
|
||||||
## 1.13.3-beta
|
## 1.13.3-beta
|
||||||
|
|
||||||
### :bug: Bugs fixed
|
### :bug: Bugs fixed
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
:http-server-port 6061
|
:http-server-port 6061
|
||||||
:http-server-host "localhost"
|
:http-server-host "localhost"
|
||||||
:redis-uri "redis://redis/0"
|
:redis-uri "redis://redis/0"
|
||||||
:exporter-domain-whitelist #{"localhost:3449"}})
|
:domain-white-list #{"localhost:3449"}})
|
||||||
|
|
||||||
(s/def ::http-server-port ::us/integer)
|
(s/def ::http-server-port ::us/integer)
|
||||||
(s/def ::http-server-host ::us/string)
|
(s/def ::http-server-host ::us/string)
|
||||||
|
@ -45,7 +45,7 @@
|
||||||
::http-server-host
|
::http-server-host
|
||||||
::browser-pool-max
|
::browser-pool-max
|
||||||
::browser-pool-min
|
::browser-pool-min
|
||||||
::domain-whitelist]))
|
::domain-white-list]))
|
||||||
|
|
||||||
(defn- read-env
|
(defn- read-env
|
||||||
[prefix]
|
[prefix]
|
||||||
|
|
|
@ -124,7 +124,7 @@
|
||||||
(let [edition (get-in state [:workspace-local :edition])
|
(let [edition (get-in state [:workspace-local :edition])
|
||||||
drawing (get state :workspace-drawing)]
|
drawing (get state :workspace-drawing)]
|
||||||
;; Editors handle their own undo's
|
;; Editors handle their own undo's
|
||||||
(when-not (or (some? edition) (and (not-empty drawing) (nil? (:object drawing))))
|
(when (and (nil? edition) (nil? (:object drawing)))
|
||||||
(let [undo (:workspace-undo state)
|
(let [undo (:workspace-undo state)
|
||||||
items (:items undo)
|
items (:items undo)
|
||||||
index (or (:index undo) (dec (count items)))]
|
index (or (:index undo) (dec (count items)))]
|
||||||
|
|
|
@ -189,10 +189,14 @@
|
||||||
(s/def ::file-change-event
|
(s/def ::file-change-event
|
||||||
(s/keys :req-un [::type ::profile-id ::file-id ::session-id ::revn ::changes]))
|
(s/keys :req-un [::type ::profile-id ::file-id ::session-id ::revn ::changes]))
|
||||||
|
|
||||||
|
|
||||||
(defn handle-file-change
|
(defn handle-file-change
|
||||||
[{:keys [file-id changes] :as msg}]
|
[{:keys [file-id changes] :as msg}]
|
||||||
(us/assert ::file-change-event msg)
|
(us/assert ::file-change-event msg)
|
||||||
(ptk/reify ::handle-file-change
|
(ptk/reify ::handle-file-change
|
||||||
|
IDeref
|
||||||
|
(-deref [_] {:changes changes})
|
||||||
|
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ _ _]
|
(watch [_ _ _]
|
||||||
(let [position-data-operation?
|
(let [position-data-operation?
|
||||||
|
|
|
@ -18,6 +18,10 @@
|
||||||
([state page-id]
|
([state page-id]
|
||||||
(get-in state [:workspace-data :pages-index page-id])))
|
(get-in state [:workspace-data :pages-index page-id])))
|
||||||
|
|
||||||
|
(defn lookup-data-objects
|
||||||
|
[data page-id]
|
||||||
|
(dm/get-in data [:pages-index page-id :objects]))
|
||||||
|
|
||||||
(defn lookup-page-objects
|
(defn lookup-page-objects
|
||||||
([state]
|
([state]
|
||||||
(lookup-page-objects state (:current-page-id state)))
|
(lookup-page-objects state (:current-page-id state)))
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
[app.common.pages.helpers :as cph]
|
[app.common.pages.helpers :as cph]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[app.main.data.workspace.changes :as dch]
|
[app.main.data.workspace.changes :as dch]
|
||||||
|
[app.main.data.workspace.state-helpers :as wsh]
|
||||||
[app.main.refs :as refs]
|
[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]
|
||||||
|
@ -31,7 +32,9 @@
|
||||||
[object-id]
|
[object-id]
|
||||||
(rx/create
|
(rx/create
|
||||||
(fn [subs]
|
(fn [subs]
|
||||||
(let [node (dom/query (dm/fmt "canvas.thumbnail-canvas[data-object-id='%'" object-id))]
|
;; 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='%']:not([data-empty])" object-id))]
|
||||||
(if (some? node)
|
(if (some? node)
|
||||||
(-> node
|
(-> node
|
||||||
(.toBlob (fn [blob]
|
(.toBlob (fn [blob]
|
||||||
|
@ -43,6 +46,14 @@
|
||||||
(do (rx/push! subs nil)
|
(do (rx/push! subs nil)
|
||||||
(rx/end! subs)))))))
|
(rx/end! subs)))))))
|
||||||
|
|
||||||
|
(defn clear-thumbnail
|
||||||
|
[page-id frame-id]
|
||||||
|
(ptk/reify ::clear-thumbnail
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(let [object-id (dm/str page-id frame-id)]
|
||||||
|
(assoc-in state [:workspace-file :thumbnails object-id] nil)))))
|
||||||
|
|
||||||
(defn update-thumbnail
|
(defn update-thumbnail
|
||||||
"Updates the thumbnail information for the given frame `id`"
|
"Updates the thumbnail information for the given frame `id`"
|
||||||
[page-id frame-id]
|
[page-id frame-id]
|
||||||
|
@ -71,50 +82,39 @@
|
||||||
|
|
||||||
(defn- extract-frame-changes
|
(defn- extract-frame-changes
|
||||||
"Process a changes set in a commit to extract the frames that are changing"
|
"Process a changes set in a commit to extract the frames that are changing"
|
||||||
[[event [old-objects new-objects]]]
|
[[event [old-data new-data]]]
|
||||||
(let [changes (-> event deref :changes)
|
(let [changes (-> event deref :changes)
|
||||||
|
|
||||||
extract-ids
|
extract-ids
|
||||||
(fn [{type :type :as change}]
|
(fn [{:keys [page-id type] :as change}]
|
||||||
(case type
|
(case type
|
||||||
:add-obj [(:id change)]
|
:add-obj [[page-id (:id change)]]
|
||||||
:mod-obj [(:id change)]
|
:mod-obj [[page-id (:id change)]]
|
||||||
:del-obj [(:id change)]
|
:del-obj [[page-id (:id change)]]
|
||||||
:reg-objects (:shapes change)
|
:mov-objects (->> (:shapes change) (map #(vector page-id %)))
|
||||||
:mov-objects (:shapes change)
|
|
||||||
[]))
|
[]))
|
||||||
|
|
||||||
get-frame-id
|
get-frame-id
|
||||||
(fn [id]
|
(fn [[page-id id]]
|
||||||
(let [shape (or (get new-objects id)
|
(let [old-objects (wsh/lookup-data-objects old-data page-id)
|
||||||
(get old-objects id))]
|
new-objects (wsh/lookup-data-objects new-data page-id)
|
||||||
(or (and (cph/frame-shape? shape) id) (:frame-id shape))))
|
|
||||||
|
|
||||||
;; Extracts the frames and then removes nils and the root frame
|
new-shape (get new-objects id)
|
||||||
xform (comp (mapcat extract-ids)
|
old-shape (get old-objects id)
|
||||||
(map get-frame-id)
|
|
||||||
(remove nil?)
|
|
||||||
(filter #(not= uuid/zero %))
|
|
||||||
(filter #(contains? new-objects %)))]
|
|
||||||
|
|
||||||
(into #{} xform changes)))
|
old-frame-id (if (cph/frame-shape? old-shape) id (:frame-id old-shape))
|
||||||
|
new-frame-id (if (cph/frame-shape? new-shape) id (:frame-id new-shape))]
|
||||||
|
|
||||||
(defn thumbnail-change?
|
(cond-> #{}
|
||||||
"Checks if a event is only updating thumbnails to ignore in the thumbnail generation process"
|
(and old-frame-id (not= uuid/zero old-frame-id))
|
||||||
[event]
|
(conj [page-id old-frame-id])
|
||||||
(let [changes (-> event deref :changes)
|
|
||||||
|
|
||||||
is-thumbnail-op?
|
(and new-frame-id (not= uuid/zero new-frame-id))
|
||||||
(fn [{type :type attr :attr}]
|
(conj [page-id new-frame-id]))))]
|
||||||
(and (= type :set)
|
(into #{}
|
||||||
(= attr :thumbnail)))
|
(comp (mapcat extract-ids)
|
||||||
|
(mapcat get-frame-id))
|
||||||
is-thumbnail-change?
|
changes)))
|
||||||
(fn [change]
|
|
||||||
(and (= (:type change) :mod-obj)
|
|
||||||
(->> change :operations (every? is-thumbnail-op?))))]
|
|
||||||
|
|
||||||
(->> changes (every? is-thumbnail-change?))))
|
|
||||||
|
|
||||||
(defn watch-state-changes
|
(defn watch-state-changes
|
||||||
"Watch the state for changes inside frames. If a change is detected will force a rendering
|
"Watch the state for changes inside frames. If a change is detected will force a rendering
|
||||||
|
@ -123,32 +123,39 @@
|
||||||
(ptk/reify ::watch-state-changes
|
(ptk/reify ::watch-state-changes
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ _ stream]
|
(watch [_ _ stream]
|
||||||
(let [stopper (->> stream
|
(let [stopper
|
||||||
|
(->> stream
|
||||||
(rx/filter #(or (= :app.main.data.workspace/finalize-page (ptk/type %))
|
(rx/filter #(or (= :app.main.data.workspace/finalize-page (ptk/type %))
|
||||||
(= ::watch-state-changes (ptk/type %)))))
|
(= ::watch-state-changes (ptk/type %)))))
|
||||||
|
|
||||||
objects-stream (->> (rx/concat
|
workspace-data-str
|
||||||
|
(->> (rx/concat
|
||||||
(rx/of nil)
|
(rx/of nil)
|
||||||
(rx/from-atom refs/workspace-page-objects {:emit-current-value? true}))
|
(rx/from-atom refs/workspace-data {:emit-current-value? true}))
|
||||||
;; We need to keep the old-objects so we can check the frame for the
|
;; We need to keep the old-objects so we can check the frame for the
|
||||||
;; deleted objects
|
;; deleted objects
|
||||||
(rx/buffer 2 1))
|
(rx/buffer 2 1))
|
||||||
|
|
||||||
frame-changes (->> stream
|
change-str
|
||||||
(rx/filter dch/commit-changes?)
|
(->> stream
|
||||||
|
(rx/filter #(or (dch/commit-changes? %)
|
||||||
|
(= (ptk/type %) :app.main.data.workspace.notifications/handle-file-change)))
|
||||||
|
(rx/observe-on :async))
|
||||||
|
|
||||||
;; Async so we wait for additional side-effects of commit-changes
|
frame-changes-str
|
||||||
(rx/observe-on :async)
|
(->> change-str
|
||||||
(rx/filter (complement thumbnail-change?))
|
(rx/with-latest-from workspace-data-str)
|
||||||
(rx/with-latest-from objects-stream)
|
(rx/flat-map extract-frame-changes)
|
||||||
(rx/map extract-frame-changes)
|
|
||||||
(rx/share))]
|
(rx/share))]
|
||||||
|
|
||||||
(->> frame-changes
|
(->> (rx/merge
|
||||||
(rx/flat-map
|
(->> frame-changes-str
|
||||||
(fn [ids]
|
(rx/filter (fn [[page-id _]] (not= page-id (:current-page-id @st/state))))
|
||||||
(->> (rx/from ids)
|
(rx/map (fn [[page-id frame-id]] (clear-thumbnail page-id frame-id))))
|
||||||
(rx/map #(ptk/data-event ::force-render %)))))
|
|
||||||
|
(->> frame-changes-str
|
||||||
|
(rx/filter (fn [[page-id _]] (= page-id (:current-page-id @st/state))))
|
||||||
|
(rx/map (fn [[_ frame-id]] (ptk/data-event ::force-render frame-id)))))
|
||||||
(rx/take-until stopper))))))
|
(rx/take-until stopper))))))
|
||||||
|
|
||||||
(defn duplicate-thumbnail
|
(defn duplicate-thumbnail
|
||||||
|
|
|
@ -82,6 +82,7 @@
|
||||||
|
|
||||||
frame? (= :frame type)
|
frame? (= :frame type)
|
||||||
group? (= :group type)
|
group? (= :group type)
|
||||||
|
text? (= :text type)
|
||||||
mask? (and group? masked-group?)]
|
mask? (and group? masked-group?)]
|
||||||
|
|
||||||
(cond
|
(cond
|
||||||
|
@ -103,6 +104,10 @@
|
||||||
(dom/query-all shape-defs ".svg-def")
|
(dom/query-all shape-defs ".svg-def")
|
||||||
(dom/query-all shape-defs ".svg-mask-wrapper")))
|
(dom/query-all shape-defs ".svg-mask-wrapper")))
|
||||||
|
|
||||||
|
text?
|
||||||
|
[shape-node
|
||||||
|
(dom/query shape-node ".text-container")]
|
||||||
|
|
||||||
:else
|
:else
|
||||||
[shape-node])))
|
[shape-node])))
|
||||||
|
|
||||||
|
@ -185,6 +190,15 @@
|
||||||
(dom/class? node "frame-children")
|
(dom/class? node "frame-children")
|
||||||
(set-transform-att! node "transform" (gmt/inverse transform))
|
(set-transform-att! node "transform" (gmt/inverse transform))
|
||||||
|
|
||||||
|
(dom/class? node "text-container")
|
||||||
|
(let [modifiers (dissoc modifiers :displacement :rotation)]
|
||||||
|
(when (not (gsh/empty-modifiers? modifiers))
|
||||||
|
(let [mtx (-> shape
|
||||||
|
(assoc :modifiers modifiers)
|
||||||
|
(gsh/transform-shape)
|
||||||
|
(gsh/transform-matrix {:no-flip true}))]
|
||||||
|
(override-transform-att! node "transform" mtx))))
|
||||||
|
|
||||||
(or (= (dom/get-tag-name node) "mask")
|
(or (= (dom/get-tag-name node) "mask")
|
||||||
(= (dom/get-tag-name node) "filter"))
|
(= (dom/get-tag-name node) "filter"))
|
||||||
(transform-region! node modifiers)
|
(transform-region! node modifiers)
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
|
|
||||||
(.clearRect canvas-context 0 0 canvas-width canvas-height)
|
(.clearRect canvas-context 0 0 canvas-width canvas-height)
|
||||||
(.drawImage canvas-context img-node 0 0 canvas-width canvas-height)
|
(.drawImage canvas-context img-node 0 0 canvas-width canvas-height)
|
||||||
|
(.removeAttribute canvas-node "data-empty")
|
||||||
true))
|
true))
|
||||||
(catch :default err
|
(catch :default err
|
||||||
(.error js/console err)
|
(.error js/console err)
|
||||||
|
@ -75,6 +76,8 @@
|
||||||
thumbnail-data-ref (mf/use-memo (mf/deps page-id id) #(refs/thumbnail-frame-data page-id id))
|
thumbnail-data-ref (mf/use-memo (mf/deps page-id id) #(refs/thumbnail-frame-data page-id id))
|
||||||
thumbnail-data (mf/deref thumbnail-data-ref)
|
thumbnail-data (mf/deref thumbnail-data-ref)
|
||||||
|
|
||||||
|
prev-thumbnail-data (hooks/use-previous thumbnail-data)
|
||||||
|
|
||||||
render-frame? (mf/use-state (not thumbnail-data))
|
render-frame? (mf/use-state (not thumbnail-data))
|
||||||
|
|
||||||
on-image-load
|
on-image-load
|
||||||
|
@ -141,6 +144,12 @@
|
||||||
(.observe observer node #js {:childList true :attributes true :attributeOldValue true :characterData true :subtree true})
|
(.observe observer node #js {:childList true :attributes true :attributeOldValue true :characterData true :subtree true})
|
||||||
(reset! observer-ref observer)))))]
|
(reset! observer-ref observer)))))]
|
||||||
|
|
||||||
|
(mf/use-effect
|
||||||
|
(mf/deps thumbnail-data)
|
||||||
|
(fn []
|
||||||
|
(when (and (some? prev-thumbnail-data) (nil? thumbnail-data))
|
||||||
|
(rx/push! updates-str :update))))
|
||||||
|
|
||||||
(mf/use-effect
|
(mf/use-effect
|
||||||
(mf/deps @render-frame? thumbnail-data)
|
(mf/deps @render-frame? thumbnail-data)
|
||||||
(fn []
|
(fn []
|
||||||
|
@ -198,8 +207,10 @@
|
||||||
|
|
||||||
[:foreignObject {:x x :y y :width width :height height}
|
[:foreignObject {:x x :y y :width width :height height}
|
||||||
[:canvas.thumbnail-canvas
|
[:canvas.thumbnail-canvas
|
||||||
{:ref frame-canvas-ref
|
{:key (dm/str "thumbnail-canvas-" (:id shape))
|
||||||
|
:ref frame-canvas-ref
|
||||||
:data-object-id (dm/str page-id (:id shape))
|
:data-object-id (dm/str page-id (:id shape))
|
||||||
|
:data-empty true
|
||||||
:width fixed-width
|
:width fixed-width
|
||||||
:height fixed-height
|
:height fixed-height
|
||||||
;; DEBUG
|
;; DEBUG
|
||||||
|
|
|
@ -7,9 +7,10 @@
|
||||||
(ns app.main.ui.workspace.shapes.text.editor
|
(ns app.main.ui.workspace.shapes.text.editor
|
||||||
(:require
|
(:require
|
||||||
["draft-js" :as draft]
|
["draft-js" :as draft]
|
||||||
[app.common.geom.matrix :as gmt]
|
[app.common.data.macros :as dm]
|
||||||
[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.common.geom.shapes.text :as gsht]
|
||||||
[app.common.text :as txt]
|
[app.common.text :as txt]
|
||||||
[app.main.data.workspace :as dw]
|
[app.main.data.workspace :as dw]
|
||||||
[app.main.data.workspace.texts :as dwt]
|
[app.main.data.workspace.texts :as dwt]
|
||||||
|
@ -255,30 +256,37 @@
|
||||||
(-> (gpt/subtract pt box)
|
(-> (gpt/subtract pt box)
|
||||||
(gpt/multiply zoom)))))
|
(gpt/multiply zoom)))))
|
||||||
|
|
||||||
(mf/defc text-editor-viewport
|
(mf/defc text-editor-svg
|
||||||
{::mf/wrap-props false}
|
{::mf/wrap-props false}
|
||||||
[props]
|
[props]
|
||||||
(let [shape (obj/get props "shape")
|
(let [shape (obj/get props "shape")
|
||||||
viewport-ref (obj/get props "viewport-ref")
|
|
||||||
zoom (obj/get props "zoom")
|
|
||||||
|
|
||||||
position
|
clip-id
|
||||||
(-> (gpt/point (-> shape :selrect :x)
|
(dm/str "text-edition-clip" (:id shape))
|
||||||
(-> shape :selrect :y))
|
|
||||||
(translate-point-from-viewport (mf/ref-val viewport-ref) zoom))
|
|
||||||
|
|
||||||
top-left-corner (gpt/point (/ (:width shape) 2) (/ (:height shape) 2))
|
text-modifier-ref
|
||||||
|
(mf/use-memo (mf/deps (:id shape)) #(refs/workspace-text-modifier-by-id (:id shape)))
|
||||||
|
|
||||||
transform
|
text-modifier
|
||||||
(-> (gmt/matrix)
|
(mf/deref text-modifier-ref)
|
||||||
(gmt/scale (gpt/point zoom))
|
|
||||||
(gmt/multiply (gsh/transform-matrix shape nil top-left-corner)))]
|
|
||||||
|
|
||||||
|
bounding-box
|
||||||
|
(gsht/position-data-bounding-box text-modifier)]
|
||||||
|
|
||||||
|
[:g.text-editor {:clip-path (dm/fmt "url(#%)" clip-id)
|
||||||
|
:transform (dm/str (gsh/transform-matrix shape))}
|
||||||
|
[:defs
|
||||||
|
[:clipPath {:id clip-id}
|
||||||
|
[:rect {:x (min (:x bounding-box) (:x shape))
|
||||||
|
:y (min (:y bounding-box) (:y shape))
|
||||||
|
:width (max (:width bounding-box) (:width shape))
|
||||||
|
:height (max (:height bounding-box) (:height shape))
|
||||||
|
:fill "red"}]]]
|
||||||
|
|
||||||
|
[:foreignObject {:x (:x shape) :y (:y shape) :width "100%" :height "100%"
|
||||||
|
:externalResourcesRequired true}
|
||||||
[:div {:style {:position "absolute"
|
[:div {:style {:position "absolute"
|
||||||
:left (str (:x position) "px")
|
:left 0
|
||||||
:top (str (:y position) "px")
|
:top 0
|
||||||
:pointer-events "all"
|
:pointer-events "all"}}
|
||||||
:transform (str transform)
|
[:& text-shape-edit-html {:shape shape :key (str (:id shape))}]]]]))
|
||||||
:transform-origin "left top"}}
|
|
||||||
|
|
||||||
[:& text-shape-edit-html {:shape shape :key (str (:id shape))}]]))
|
|
||||||
|
|
|
@ -187,10 +187,7 @@
|
||||||
|
|
||||||
[:div.viewport
|
[:div.viewport
|
||||||
[:div.viewport-overlays {:ref overlays-ref}
|
[:div.viewport-overlays {:ref overlays-ref}
|
||||||
(when show-text-editor?
|
|
||||||
[:& editor/text-editor-viewport {:shape editing-shape
|
|
||||||
:viewport-ref viewport-ref
|
|
||||||
:zoom zoom}])
|
|
||||||
(when show-comments?
|
(when show-comments?
|
||||||
[:& comments/comments-layer {:vbox vbox
|
[:& comments/comments-layer {:vbox vbox
|
||||||
:vport vport
|
:vport vport
|
||||||
|
@ -275,6 +272,9 @@
|
||||||
:on-pointer-up on-pointer-up}
|
:on-pointer-up on-pointer-up}
|
||||||
|
|
||||||
[:g {:style {:pointer-events (if disable-events? "none" "auto")}}
|
[:g {:style {:pointer-events (if disable-events? "none" "auto")}}
|
||||||
|
(when show-text-editor?
|
||||||
|
[:& editor/text-editor-svg {:shape editing-shape}])
|
||||||
|
|
||||||
(when show-outlines?
|
(when show-outlines?
|
||||||
[:& outline/shape-outlines
|
[:& outline/shape-outlines
|
||||||
{:objects base-objects
|
{:objects base-objects
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue