mirror of
https://github.com/penpot/penpot.git
synced 2025-02-16 03:58:20 -05:00
✨ Path-point calculation
This commit is contained in:
parent
fc383664c7
commit
6db144e5ed
7 changed files with 212 additions and 27 deletions
|
@ -225,3 +225,92 @@
|
||||||
point))
|
point))
|
||||||
|
|
||||||
(conj result [prev-point last-start]))))
|
(conj result [prev-point last-start]))))
|
||||||
|
|
||||||
|
(defonce path-closest-point-accuracy 0.01)
|
||||||
|
(defn curve-closest-point
|
||||||
|
[position start end h1 h2]
|
||||||
|
(let [d (memoize (fn [t] (gpt/distance position (curve-values start end h1 h2 t))))]
|
||||||
|
(loop [t1 0
|
||||||
|
t2 1]
|
||||||
|
(if (<= (mth/abs (- t1 t2)) path-closest-point-accuracy)
|
||||||
|
(curve-values start end h1 h2 t1)
|
||||||
|
|
||||||
|
(let [ht (+ t1 (/ (- t2 t1) 2))
|
||||||
|
ht1 (+ t1 (/ (- t2 t1) 4))
|
||||||
|
ht2 (+ t1 (/ (* 3 (- t2 t1)) 4))
|
||||||
|
|
||||||
|
[t1 t2] (cond
|
||||||
|
(< (d ht1) (d ht2))
|
||||||
|
[t1 ht]
|
||||||
|
|
||||||
|
(< (d ht2) (d ht1))
|
||||||
|
[ht t2]
|
||||||
|
|
||||||
|
(and (< (d ht) (d t1)) (< (d ht) (d t2)))
|
||||||
|
[ht1 ht2]
|
||||||
|
|
||||||
|
(< (d t1) (d t2))
|
||||||
|
[t1 ht]
|
||||||
|
|
||||||
|
:else
|
||||||
|
[ht t2])]
|
||||||
|
(recur t1 t2))))))
|
||||||
|
|
||||||
|
(defn line-closest-point
|
||||||
|
"Point on line"
|
||||||
|
[position from-p to-p]
|
||||||
|
|
||||||
|
(let [{v1x :x v1y :y} from-p
|
||||||
|
{v2x :x v2y :y} to-p
|
||||||
|
{px :x py :y} position
|
||||||
|
|
||||||
|
e1 (gpt/point (- v2x v1x) (- v2y v1y))
|
||||||
|
e2 (gpt/point (- px v1x) (- py v1y))
|
||||||
|
|
||||||
|
len2 (+ (mth/sq (:x e1)) (mth/sq (:y e1)))
|
||||||
|
val-dp (/ (gpt/dot e1 e2) len2)]
|
||||||
|
|
||||||
|
(if (and (>= val-dp 0)
|
||||||
|
(<= val-dp 1)
|
||||||
|
(not (mth/almost-zero? len2)))
|
||||||
|
(gpt/point (+ v1x (* val-dp (:x e1)))
|
||||||
|
(+ v1y (* val-dp (:y e1))))
|
||||||
|
;; There is no perpendicular projection in the line so the closest
|
||||||
|
;; point will be one of the extremes
|
||||||
|
(if (<= (gpt/distance position from-p) (gpt/distance position to-p))
|
||||||
|
from-p
|
||||||
|
to-p))))
|
||||||
|
|
||||||
|
(defn path-closest-point
|
||||||
|
"Given a path and a position"
|
||||||
|
[shape position]
|
||||||
|
|
||||||
|
(let [point+distance (fn [[cur-cmd prev-cmd]]
|
||||||
|
(let [point
|
||||||
|
(case (:command cur-cmd)
|
||||||
|
:line-to (line-closest-point
|
||||||
|
position
|
||||||
|
(command->point prev-cmd)
|
||||||
|
(command->point cur-cmd))
|
||||||
|
:curve-to (curve-closest-point
|
||||||
|
position
|
||||||
|
(command->point prev-cmd)
|
||||||
|
(command->point cur-cmd)
|
||||||
|
(gpt/point (get-in cur-cmd [:params :c1x])
|
||||||
|
(get-in cur-cmd [:params :c1y]))
|
||||||
|
(gpt/point (get-in cur-cmd [:params :c2x])
|
||||||
|
(get-in cur-cmd [:params :c2y])))
|
||||||
|
nil)]
|
||||||
|
(when point
|
||||||
|
[point (gpt/distance point position)])))
|
||||||
|
|
||||||
|
find-min-point (fn [[min-p min-dist :as acc] [cur-p cur-dist :as cur]]
|
||||||
|
(if (and (some? acc) (or (not cur) (<= min-dist cur-dist)))
|
||||||
|
[min-p min-dist]
|
||||||
|
[cur-p cur-dist]))]
|
||||||
|
|
||||||
|
(->> (:content shape)
|
||||||
|
(d/with-prev)
|
||||||
|
(map point+distance)
|
||||||
|
(reduce find-min-point)
|
||||||
|
(first))))
|
||||||
|
|
|
@ -133,8 +133,11 @@
|
||||||
(->> stream (rx/filter #(or (helpers/end-path-event? %)
|
(->> stream (rx/filter #(or (helpers/end-path-event? %)
|
||||||
(ms/mouse-up? %))))
|
(ms/mouse-up? %))))
|
||||||
|
|
||||||
|
content (get-in state (st/get-path state :content))
|
||||||
|
points (ugp/content->points content)
|
||||||
|
|
||||||
drag-events-stream
|
drag-events-stream
|
||||||
(->> (streams/position-stream)
|
(->> (streams/position-stream points)
|
||||||
(rx/take-until stop-stream)
|
(rx/take-until stop-stream)
|
||||||
(rx/map #(drag-handler %)))]
|
(rx/map #(drag-handler %)))]
|
||||||
|
|
||||||
|
@ -163,7 +166,10 @@
|
||||||
zoom (get-in state [:workspace-local :zoom])
|
zoom (get-in state [:workspace-local :zoom])
|
||||||
mouse-up (->> stream (rx/filter #(or (helpers/end-path-event? %)
|
mouse-up (->> stream (rx/filter #(or (helpers/end-path-event? %)
|
||||||
(ms/mouse-up? %))))
|
(ms/mouse-up? %))))
|
||||||
drag-events (->> (streams/position-stream)
|
content (get-in state (st/get-path state :content))
|
||||||
|
points (ugp/content->points content)
|
||||||
|
|
||||||
|
drag-events (->> (streams/position-stream points)
|
||||||
(rx/take-until mouse-up)
|
(rx/take-until mouse-up)
|
||||||
(rx/map #(drag-handler %)))]
|
(rx/map #(drag-handler %)))]
|
||||||
|
|
||||||
|
@ -183,10 +189,10 @@
|
||||||
(rx/merge-map #(rx/empty))))
|
(rx/merge-map #(rx/empty))))
|
||||||
|
|
||||||
(defn make-drag-stream
|
(defn make-drag-stream
|
||||||
[stream down-event zoom]
|
[stream down-event zoom points]
|
||||||
(let [mouse-up (->> stream (rx/filter #(or (helpers/end-path-event? %)
|
(let [mouse-up (->> stream (rx/filter #(or (helpers/end-path-event? %)
|
||||||
(ms/mouse-up? %))))
|
(ms/mouse-up? %))))
|
||||||
drag-events (->> (streams/position-stream)
|
drag-events (->> (streams/position-stream points)
|
||||||
(rx/take-until mouse-up)
|
(rx/take-until mouse-up)
|
||||||
(rx/map #(drag-handler %)))]
|
(rx/map #(drag-handler %)))]
|
||||||
|
|
||||||
|
@ -213,9 +219,12 @@
|
||||||
mouse-down (->> stream (rx/filter ms/mouse-down?))
|
mouse-down (->> stream (rx/filter ms/mouse-down?))
|
||||||
end-path-events (->> stream (rx/filter helpers/end-path-event?))
|
end-path-events (->> stream (rx/filter helpers/end-path-event?))
|
||||||
|
|
||||||
|
content (get-in state (st/get-path state :content))
|
||||||
|
points (ugp/content->points content)
|
||||||
|
|
||||||
;; Mouse move preview
|
;; Mouse move preview
|
||||||
mousemove-events
|
mousemove-events
|
||||||
(->> (streams/position-stream)
|
(->> (streams/position-stream points)
|
||||||
(rx/take-until end-path-events)
|
(rx/take-until end-path-events)
|
||||||
(rx/map #(preview-next-point %)))
|
(rx/map #(preview-next-point %)))
|
||||||
|
|
||||||
|
@ -223,12 +232,12 @@
|
||||||
mousedown-events
|
mousedown-events
|
||||||
(->> mouse-down
|
(->> mouse-down
|
||||||
(rx/take-until end-path-events)
|
(rx/take-until end-path-events)
|
||||||
(rx/with-latest merge (streams/position-stream))
|
(rx/with-latest merge (streams/position-stream points))
|
||||||
|
|
||||||
;; We change to the stream that emits the first event
|
;; We change to the stream that emits the first event
|
||||||
(rx/switch-map
|
(rx/switch-map
|
||||||
#(rx/race (make-node-events-stream stream)
|
#(rx/race (make-node-events-stream stream)
|
||||||
(make-drag-stream stream % zoom))))]
|
(make-drag-stream stream % zoom points))))]
|
||||||
|
|
||||||
(rx/concat
|
(rx/concat
|
||||||
(rx/of (common/init-path))
|
(rx/of (common/init-path))
|
||||||
|
@ -269,6 +278,12 @@
|
||||||
"Creates a new path shape"
|
"Creates a new path shape"
|
||||||
[]
|
[]
|
||||||
(ptk/reify ::handle-new-shape
|
(ptk/reify ::handle-new-shape
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(let [id (st/get-path-id state)]
|
||||||
|
(-> state
|
||||||
|
(assoc-in [:workspace-local :edit-path id :snap-toggled] true))))
|
||||||
|
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(let [shape-id (get-in state [:workspace-drawing :object :id])]
|
(let [shape-id (get-in state [:workspace-drawing :object :id])]
|
||||||
|
|
|
@ -118,6 +118,9 @@
|
||||||
selected-points (get-in state [:workspace-local :edit-path id :selected-points] #{})
|
selected-points (get-in state [:workspace-local :edit-path id :selected-points] #{})
|
||||||
selected? (contains? selected-points position)
|
selected? (contains? selected-points position)
|
||||||
|
|
||||||
|
content (get-in state (st/get-path state :content))
|
||||||
|
points (ugp/content->points content)
|
||||||
|
|
||||||
mouse-drag-stream
|
mouse-drag-stream
|
||||||
(rx/concat
|
(rx/concat
|
||||||
;; If we're dragging a selected item we don't change the selection
|
;; If we're dragging a selected item we don't change the selection
|
||||||
|
@ -126,7 +129,7 @@
|
||||||
(rx/of (selection/select-node position shift?)))
|
(rx/of (selection/select-node position shift?)))
|
||||||
|
|
||||||
;; This stream checks the consecutive mouse positions to do the draging
|
;; This stream checks the consecutive mouse positions to do the draging
|
||||||
(->> (streams/position-stream)
|
(->> (streams/position-stream points)
|
||||||
(rx/take-until stopper)
|
(rx/take-until stopper)
|
||||||
(rx/map #(move-selected-path-point start-position %)))
|
(rx/map #(move-selected-path-point start-position %)))
|
||||||
(rx/of (apply-content-modifiers)))
|
(rx/of (apply-content-modifiers)))
|
||||||
|
@ -151,6 +154,8 @@
|
||||||
start-delta-y (get-in modifiers [index cy] 0)
|
start-delta-y (get-in modifiers [index cy] 0)
|
||||||
|
|
||||||
content (get-in state (st/get-path state :content))
|
content (get-in state (st/get-path state :content))
|
||||||
|
points (ugp/content->points content)
|
||||||
|
|
||||||
opposite-index (ugp/opposite-index content index prefix)
|
opposite-index (ugp/opposite-index content index prefix)
|
||||||
opposite-prefix (if (= prefix :c1) :c2 :c1)
|
opposite-prefix (if (= prefix :c1) :c2 :c1)
|
||||||
opposite-handler (-> content (get opposite-index) (ugp/get-handler opposite-prefix))
|
opposite-handler (-> content (get opposite-index) (ugp/get-handler opposite-prefix))
|
||||||
|
@ -163,7 +168,7 @@
|
||||||
|
|
||||||
(streams/drag-stream
|
(streams/drag-stream
|
||||||
(rx/concat
|
(rx/concat
|
||||||
(->> (streams/position-stream)
|
(->> (streams/position-stream points)
|
||||||
(rx/take-until (->> stream (rx/filter ms/mouse-up?)))
|
(rx/take-until (->> stream (rx/filter ms/mouse-up?)))
|
||||||
(rx/map
|
(rx/map
|
||||||
(fn [{:keys [x y alt? shift?]}]
|
(fn [{:keys [x y alt? shift?]}]
|
||||||
|
|
|
@ -15,7 +15,8 @@
|
||||||
[app.main.streams :as ms]
|
[app.main.streams :as ms]
|
||||||
[beicon.core :as rx]
|
[beicon.core :as rx]
|
||||||
[potok.core :as ptk]
|
[potok.core :as ptk]
|
||||||
[app.common.math :as mth]))
|
[app.common.math :as mth]
|
||||||
|
[app.main.snap :as snap]))
|
||||||
|
|
||||||
(defonce drag-threshold 5)
|
(defonce drag-threshold 5)
|
||||||
|
|
||||||
|
@ -53,11 +54,17 @@
|
||||||
(let [k 50]
|
(let [k 50]
|
||||||
(* (mth/floor (/ num k)) k)))
|
(* (mth/floor (/ num k)) k)))
|
||||||
|
|
||||||
(defn position-stream []
|
(defn position-stream
|
||||||
(->> ms/mouse-position
|
([points]
|
||||||
;; TODO: Prueba para el snap
|
(position-stream points #{}))
|
||||||
#_(rx/map #(-> %
|
|
||||||
(update :x to-dec)
|
([points selected-points]
|
||||||
(update :y to-dec)))
|
(let [zoom (get-in @st/state [:workspace-local :zoom] 1)]
|
||||||
|
(->> (snap/path-snap ms/mouse-position points selected-points zoom)
|
||||||
|
(rx/with-latest vector ms/mouse-position)
|
||||||
|
(rx/map (fn [[{[x] :x [y] :y} position]]
|
||||||
|
(cond-> position
|
||||||
|
(some? x) (assoc :x x)
|
||||||
|
(some? y) (assoc :y y))))
|
||||||
(rx/with-latest merge (->> ms/mouse-position-shift (rx/map #(hash-map :shift? %))))
|
(rx/with-latest merge (->> ms/mouse-position-shift (rx/map #(hash-map :shift? %))))
|
||||||
(rx/with-latest merge (->> ms/mouse-position-alt (rx/map #(hash-map :alt? %))))))
|
(rx/with-latest merge (->> ms/mouse-position-alt (rx/map #(hash-map :alt? %))))))))
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
[app.main.worker :as uw]
|
[app.main.worker :as uw]
|
||||||
[app.util.geom.snap-points :as sp]
|
[app.util.geom.snap-points :as sp]
|
||||||
|
[app.util.range-tree :as rt]
|
||||||
[beicon.core :as rx]
|
[beicon.core :as rx]
|
||||||
[clojure.set :as set]))
|
[clojure.set :as set]))
|
||||||
|
|
||||||
|
@ -240,3 +241,32 @@
|
||||||
(rx/reduce gpt/min)
|
(rx/reduce gpt/min)
|
||||||
(rx/map #(or % (gpt/point 0 0))))))
|
(rx/map #(or % (gpt/point 0 0))))))
|
||||||
|
|
||||||
|
(defn path-snap [position-stream points selected-points zoom]
|
||||||
|
(let [selected-points (or selected-points #{})
|
||||||
|
into-tree (fn [coord]
|
||||||
|
(fn [tree point]
|
||||||
|
(rt/insert tree (get point coord) point)))
|
||||||
|
|
||||||
|
ranges-x (->> points
|
||||||
|
(filter (comp not selected-points))
|
||||||
|
(reduce (into-tree :x) (rt/make-tree)))
|
||||||
|
|
||||||
|
ranges-y (->> points
|
||||||
|
(filter (comp not selected-points))
|
||||||
|
(reduce (into-tree :y) (rt/make-tree)))
|
||||||
|
|
||||||
|
min-match (fn [matches]
|
||||||
|
(->> matches
|
||||||
|
(reduce (fn [[cur-val :as current] [other-val :as other]]
|
||||||
|
(if (< cur-val other-val)
|
||||||
|
current
|
||||||
|
other)))))]
|
||||||
|
|
||||||
|
(->> position-stream
|
||||||
|
(rx/map
|
||||||
|
(fn [{:keys [x y]}]
|
||||||
|
(let [d-pos (/ snap-accuracy zoom)
|
||||||
|
x-match (rt/range-query ranges-x (- x d-pos) (+ x d-pos))
|
||||||
|
y-match (rt/range-query ranges-y (- y d-pos) (+ y d-pos))]
|
||||||
|
{:x (min-match x-match)
|
||||||
|
:y (min-match y-match)}))))))
|
||||||
|
|
|
@ -8,16 +8,19 @@
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.geom.point :as gpt]
|
[app.common.geom.point :as gpt]
|
||||||
|
[app.common.geom.shapes.path :as gshp]
|
||||||
[app.main.data.workspace.path :as drp]
|
[app.main.data.workspace.path :as drp]
|
||||||
|
[app.main.snap :as snap]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
|
[app.main.streams :as ms]
|
||||||
[app.main.ui.cursors :as cur]
|
[app.main.ui.cursors :as cur]
|
||||||
|
[app.main.ui.hooks :as hooks]
|
||||||
[app.main.ui.workspace.shapes.path.common :as pc]
|
[app.main.ui.workspace.shapes.path.common :as pc]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
[app.util.geom.path :as ugp]
|
[app.util.geom.path :as ugp]
|
||||||
|
[app.util.keyboard :as kbd]
|
||||||
[goog.events :as events]
|
[goog.events :as events]
|
||||||
[rumext.alpha :as mf]
|
[rumext.alpha :as mf])
|
||||||
|
|
||||||
[app.util.keyboard :as kbd])
|
|
||||||
(:import goog.events.EventType))
|
(:import goog.events.EventType))
|
||||||
|
|
||||||
(mf/defc path-point [{:keys [position zoom edit-mode hover? selected? preview? start-path? last-p?]}]
|
(mf/defc path-point [{:keys [position zoom edit-mode hover? selected? preview? start-path? last-p?]}]
|
||||||
|
@ -131,8 +134,9 @@
|
||||||
[:g.preview {:style {:pointer-events "none"}}
|
[:g.preview {:style {:pointer-events "none"}}
|
||||||
(when (not= :move-to (:command command))
|
(when (not= :move-to (:command command))
|
||||||
[:path {:style {:fill "transparent"
|
[:path {:style {:fill "transparent"
|
||||||
:stroke pc/secondary-color
|
:stroke pc/black-color
|
||||||
:stroke-width (/ 1 zoom)}
|
:stroke-width (/ 1 zoom)
|
||||||
|
:stroke-dasharray (/ 4 zoom)}
|
||||||
:d (ugp/content->path [{:command :move-to
|
:d (ugp/content->path [{:command :move-to
|
||||||
:params {:x (:x from)
|
:params {:x (:x from)
|
||||||
:y (:y from)}}
|
:y (:y from)}}
|
||||||
|
@ -141,11 +145,23 @@
|
||||||
:preview? true
|
:preview? true
|
||||||
:zoom zoom}]])
|
:zoom zoom}]])
|
||||||
|
|
||||||
|
(mf/defc snap-path-points [{:keys [snaps zoom]}]
|
||||||
|
[:g.snap-paths
|
||||||
|
(for [[from to] snaps]
|
||||||
|
[:line {:x1 (:x from)
|
||||||
|
:y1 (:y from)
|
||||||
|
:x2 (:x to)
|
||||||
|
:y2 (:y to)
|
||||||
|
:style {:stroke pc/secondary-color
|
||||||
|
:stroke-width (/ 1 zoom)}}])])
|
||||||
|
|
||||||
(mf/defc path-editor
|
(mf/defc path-editor
|
||||||
[{:keys [shape zoom]}]
|
[{:keys [shape zoom]}]
|
||||||
|
|
||||||
(let [editor-ref (mf/use-ref nil)
|
(let [editor-ref (mf/use-ref nil)
|
||||||
edit-path-ref (pc/make-edit-path-ref (:id shape))
|
edit-path-ref (pc/make-edit-path-ref (:id shape))
|
||||||
|
hover-point (mf/use-state nil)
|
||||||
|
|
||||||
{:keys [edit-mode
|
{:keys [edit-mode
|
||||||
drag-handler
|
drag-handler
|
||||||
prev-handler
|
prev-handler
|
||||||
|
@ -158,9 +174,9 @@
|
||||||
hover-points]
|
hover-points]
|
||||||
:as edit-path} (mf/deref edit-path-ref)
|
:as edit-path} (mf/deref edit-path-ref)
|
||||||
|
|
||||||
{:keys [content]} shape
|
{base-content :content} shape
|
||||||
content (ugp/apply-content-modifiers content content-modifiers)
|
content (ugp/apply-content-modifiers base-content content-modifiers)
|
||||||
points (->> content ugp/content->points (into #{}))
|
points (mf/use-memo (mf/deps content) #(->> content ugp/content->points (into #{})))
|
||||||
last-command (last content)
|
last-command (last content)
|
||||||
last-p (->> content last ugp/command->point)
|
last-p (->> content last ugp/command->point)
|
||||||
handlers (ugp/content->handlers content)
|
handlers (ugp/content->handlers content)
|
||||||
|
@ -177,12 +193,34 @@
|
||||||
#(doseq [key keys]
|
#(doseq [key keys]
|
||||||
(events/unlistenByKey key)))))
|
(events/unlistenByKey key)))))
|
||||||
|
|
||||||
|
#_(hooks/use-stream
|
||||||
|
ms/mouse-position
|
||||||
|
(mf/deps shape)
|
||||||
|
(fn [position]
|
||||||
|
(reset! hover-point (gshp/path-closest-point shape position))))
|
||||||
|
|
||||||
|
(hooks/use-stream
|
||||||
|
(mf/use-memo
|
||||||
|
(mf/deps base-content selected-points zoom)
|
||||||
|
#(snap/path-snap ms/mouse-position points selected-points zoom))
|
||||||
|
|
||||||
|
(fn [result]
|
||||||
|
(prn "??" result)))
|
||||||
|
|
||||||
[:g.path-editor {:ref editor-ref}
|
[:g.path-editor {:ref editor-ref}
|
||||||
|
#_[:& snap-points {}]
|
||||||
|
|
||||||
|
|
||||||
(when (and preview (not drag-handler))
|
(when (and preview (not drag-handler))
|
||||||
[:& path-preview {:command preview
|
[:& path-preview {:command preview
|
||||||
:from last-p
|
:from last-p
|
||||||
:zoom zoom}])
|
:zoom zoom}])
|
||||||
|
|
||||||
|
(when @hover-point
|
||||||
|
[:g.hover-point
|
||||||
|
[:& path-point {:position @hover-point
|
||||||
|
:zoom zoom}]])
|
||||||
|
|
||||||
(for [position points]
|
(for [position points]
|
||||||
(let [point-selected? (contains? selected-points position)
|
(let [point-selected? (contains? selected-points position)
|
||||||
point-hover? (contains? hover-points position)
|
point-hover? (contains? hover-points position)
|
||||||
|
|
|
@ -171,7 +171,8 @@
|
||||||
:width (:width vport 0)
|
:width (:width vport 0)
|
||||||
:height (:height vport 0)
|
:height (:height vport 0)
|
||||||
:view-box (utils/format-viewbox vbox)
|
:view-box (utils/format-viewbox vbox)
|
||||||
:style {:background-color (get options :background "#E8E9EA")}}
|
:style {:background-color (get options :background "#E8E9EA")
|
||||||
|
:pointer-events "none"}}
|
||||||
|
|
||||||
[:& (mf/provider muc/embed-ctx) {:value true}
|
[:& (mf/provider muc/embed-ctx) {:value true}
|
||||||
;; Render root shape
|
;; Render root shape
|
||||||
|
|
Loading…
Add table
Reference in a new issue