mirror of
https://github.com/penpot/penpot.git
synced 2026-09-09 21:49:10 +00:00
Merge remote-tracking branch 'origin/staging' into staging
This commit is contained in:
commit
7ebd7cc0d5
@ -15,6 +15,7 @@
|
|||||||
[app.common.geom.point :as gpt]
|
[app.common.geom.point :as gpt]
|
||||||
[app.common.geom.rect :as grc]
|
[app.common.geom.rect :as grc]
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes :as gsh]
|
||||||
|
[app.common.logging :as log]
|
||||||
[app.common.math :as mth]
|
[app.common.math :as mth]
|
||||||
[app.common.types.component :as ctk]
|
[app.common.types.component :as ctk]
|
||||||
[app.common.types.container :as ctn]
|
[app.common.types.container :as ctn]
|
||||||
@ -579,44 +580,57 @@
|
|||||||
modifiers (calculate-modifiers state ignore-constraints ignore-snap-pixel modif-tree page-id params)]
|
modifiers (calculate-modifiers state ignore-constraints ignore-snap-pixel modif-tree page-id params)]
|
||||||
(assoc state :workspace-modifiers modifiers))))))
|
(assoc state :workspace-modifiers modifiers))))))
|
||||||
|
|
||||||
|
(defn- without-nil-ids
|
||||||
|
"Drop nil-keyed entries from a modif-tree. A nil shape id (possible in
|
||||||
|
production builds, where the upstream asserts are elided) would crash
|
||||||
|
the WASM heap write with `uuid/get-u32` being called on nil."
|
||||||
|
[modif-tree]
|
||||||
|
(if (contains? modif-tree nil)
|
||||||
|
(do (log/warn :hint "modif-tree contains a nil shape id; ignoring entry")
|
||||||
|
(dissoc modif-tree nil))
|
||||||
|
modif-tree))
|
||||||
|
|
||||||
(defn- parse-structure-modifiers
|
(defn- parse-structure-modifiers
|
||||||
[modif-tree]
|
[modif-tree]
|
||||||
(into
|
(into
|
||||||
[]
|
[]
|
||||||
(mapcat
|
(comp
|
||||||
(fn [[parent-id data]]
|
(mapcat
|
||||||
(when (ctm/has-structure? (:modifiers data))
|
(fn [[parent-id data]]
|
||||||
(->> (concat
|
(when (ctm/has-structure? (:modifiers data))
|
||||||
(get-in data [:modifiers :structure-parent])
|
(->> (concat
|
||||||
(get-in data [:modifiers :structure-child]))
|
(get-in data [:modifiers :structure-parent])
|
||||||
(mapcat
|
(get-in data [:modifiers :structure-child]))
|
||||||
(fn [modifier]
|
(mapcat
|
||||||
(case (:type modifier)
|
(fn [modifier]
|
||||||
:remove-children
|
(case (:type modifier)
|
||||||
(->> (:value modifier)
|
:remove-children
|
||||||
(map (fn [child-id]
|
(->> (:value modifier)
|
||||||
{:type :remove-children
|
(map (fn [child-id]
|
||||||
:parent parent-id
|
{:type :remove-children
|
||||||
:id child-id
|
:parent parent-id
|
||||||
:index 0
|
:id child-id
|
||||||
:value 0})))
|
:index 0
|
||||||
|
:value 0})))
|
||||||
|
|
||||||
:add-children
|
:add-children
|
||||||
(->> (:value modifier)
|
(->> (:value modifier)
|
||||||
(map (fn [child-id]
|
(map (fn [child-id]
|
||||||
{:type :add-children
|
{:type :add-children
|
||||||
:parent parent-id
|
:parent parent-id
|
||||||
:id child-id
|
:id child-id
|
||||||
:index (:index modifier)
|
:index (:index modifier)
|
||||||
:value 0})))
|
:value 0})))
|
||||||
|
|
||||||
:scale-content
|
:scale-content
|
||||||
[{:type :scale-content
|
[{:type :scale-content
|
||||||
:parent parent-id
|
:parent parent-id
|
||||||
:id parent-id
|
:id parent-id
|
||||||
:index 0
|
:index 0
|
||||||
:value (:value modifier)}]
|
:value (:value modifier)}]
|
||||||
nil)))))))
|
nil)))))))
|
||||||
|
(filter (fn [{:keys [id parent]}]
|
||||||
|
(and (some? id) (some? parent)))))
|
||||||
modif-tree))
|
modif-tree))
|
||||||
|
|
||||||
|
|
||||||
@ -624,7 +638,7 @@
|
|||||||
(let [default-transform (gmt/matrix)]
|
(let [default-transform (gmt/matrix)]
|
||||||
(keep (fn [[id data]]
|
(keep (fn [[id data]]
|
||||||
(cond
|
(cond
|
||||||
(= id uuid/zero)
|
(or (nil? id) (= id uuid/zero))
|
||||||
nil
|
nil
|
||||||
|
|
||||||
(ctm/has-geometry? (:modifiers data))
|
(ctm/has-geometry? (:modifiers data))
|
||||||
@ -693,65 +707,66 @@
|
|||||||
subtree-ids-by-id selection-rect-cache]
|
subtree-ids-by-id selection-rect-cache]
|
||||||
:or {ignore-constraints false ignore-snap-pixel false}
|
:or {ignore-constraints false ignore-snap-pixel false}
|
||||||
:as params}]
|
:as params}]
|
||||||
(ptk/reify ::set-wasm-modifiers
|
(let [modif-tree (without-nil-ids modif-tree)]
|
||||||
ptk/UpdateEvent
|
(ptk/reify ::set-wasm-modifiers
|
||||||
(update [_ state]
|
ptk/UpdateEvent
|
||||||
(let [property-changes (extract-property-changes modif-tree)]
|
(update [_ state]
|
||||||
(if (d/not-empty? property-changes)
|
(let [property-changes (extract-property-changes modif-tree)]
|
||||||
(-> state
|
(if (d/not-empty? property-changes)
|
||||||
(assoc :prev-wasm-props (:wasm-props state))
|
(-> state
|
||||||
(assoc :wasm-props property-changes))
|
(assoc :prev-wasm-props (:wasm-props state))
|
||||||
state)))
|
(assoc :wasm-props property-changes))
|
||||||
|
state)))
|
||||||
|
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state _]
|
(watch [_ state _]
|
||||||
;; Entering an interactive transform (drag/resize/rotate). Flip
|
;; Entering an interactive transform (drag/resize/rotate). Flip
|
||||||
;; the renderer into fast + atlas-backdrop mode so the live
|
;; the renderer into fast + atlas-backdrop mode so the live
|
||||||
;; preview is cheap, tiles never appear sequentially and the main
|
;; preview is cheap, tiles never appear sequentially and the main
|
||||||
;; thread is not blocked. The pair is closed in
|
;; thread is not blocked. The pair is closed in
|
||||||
;; `clear-local-transform`.
|
;; `clear-local-transform`.
|
||||||
(ensure-interactive-transform-start!)
|
(ensure-interactive-transform-start!)
|
||||||
(let [snap-pixel? (and (not ignore-snap-pixel) (contains? (:workspace-layout state) :snap-pixel-grid))
|
(let [snap-pixel? (and (not ignore-snap-pixel) (contains? (:workspace-layout state) :snap-pixel-grid))
|
||||||
translation? (every? #(ctm/only-move? (:modifiers %)) (vals modif-tree))]
|
translation? (every? #(ctm/only-move? (:modifiers %)) (vals modif-tree))]
|
||||||
|
|
||||||
(if translation?
|
(if translation?
|
||||||
;; Pure translation: no structure changes needed. If structure
|
;; Pure translation: no structure changes needed. If structure
|
||||||
;; modifiers were active from a previous non-translation frame
|
;; modifiers were active from a previous non-translation frame
|
||||||
;; (e.g. shape hovered over a frame then dragged back out),
|
;; (e.g. shape hovered over a frame then dragged back out),
|
||||||
;; clear them now so the shape is not clipped by the old frame.
|
;; clear them now so the shape is not clipped by the old frame.
|
||||||
(when @wasm-structure-modifiers-active?
|
(when @wasm-structure-modifiers-active?
|
||||||
(wasm.api/clean-modifiers)
|
(wasm.api/clean-modifiers)
|
||||||
(vreset! wasm-structure-modifiers-active? false))
|
(vreset! wasm-structure-modifiers-active? false))
|
||||||
(let [objects (dsh/lookup-page-objects state)]
|
(let [objects (dsh/lookup-page-objects state)]
|
||||||
(set-wasm-props! objects (:prev-wasm-props state) (:wasm-props state))
|
(set-wasm-props! objects (:prev-wasm-props state) (:wasm-props state))
|
||||||
(wasm.api/clean-modifiers)
|
(wasm.api/clean-modifiers)
|
||||||
(wasm.api/set-structure-modifiers (parse-structure-modifiers modif-tree))
|
(wasm.api/set-structure-modifiers (parse-structure-modifiers modif-tree))
|
||||||
(vreset! wasm-structure-modifiers-active? true)))
|
(vreset! wasm-structure-modifiers-active? true)))
|
||||||
(let [geometry-entries (parse-geometry-modifiers modif-tree)
|
(let [geometry-entries (parse-geometry-modifiers modif-tree)
|
||||||
root-modifiers (into [] (map (fn [[id data]] [id (:transform data)])) geometry-entries)
|
root-modifiers (into [] (map (fn [[id data]] [id (:transform data)])) geometry-entries)
|
||||||
wasm-ready? (wasm.api/initialized?)
|
wasm-ready? (wasm.api/initialized?)
|
||||||
;; While the GL context is down (lost / mid-reload), keep the
|
;; While the GL context is down (lost / mid-reload), keep the
|
||||||
;; root transforms so SVG selection/preview can still move.
|
;; root transforms so SVG selection/preview can still move.
|
||||||
;; `propagate-modifiers` returns [] when not ready, do not
|
;; `propagate-modifiers` returns [] when not ready, do not
|
||||||
;; treat that as "no modifiers".
|
;; treat that as "no modifiers".
|
||||||
modifiers
|
modifiers
|
||||||
(cond
|
(cond
|
||||||
(or (not wasm-ready?)
|
(or (not wasm-ready?)
|
||||||
(and translation? (not snap-pixel?)))
|
(and translation? (not snap-pixel?)))
|
||||||
root-modifiers
|
root-modifiers
|
||||||
|
|
||||||
:else
|
:else
|
||||||
(let [propagated (wasm.api/propagate-modifiers geometry-entries snap-pixel?)]
|
(let [propagated (wasm.api/propagate-modifiers geometry-entries snap-pixel?)]
|
||||||
(if (seq propagated) propagated root-modifiers)))]
|
(if (seq propagated) propagated root-modifiers)))]
|
||||||
(when wasm-ready?
|
(when wasm-ready?
|
||||||
(wasm.api/set-modifiers modifiers))
|
(wasm.api/set-modifiers modifiers))
|
||||||
(let [ids (into [] xf:map-key geometry-entries)
|
(let [ids (into [] xf:map-key geometry-entries)
|
||||||
selrect (when wasm-ready?
|
selrect (when wasm-ready?
|
||||||
(if (and translation? (not snap-pixel?) selection-rect-cache (seq modifiers))
|
(if (and translation? (not snap-pixel?) selection-rect-cache (seq modifiers))
|
||||||
(cached-translation-selrect ids (second (first modifiers)) selection-rect-cache)
|
(cached-translation-selrect ids (second (first modifiers)) selection-rect-cache)
|
||||||
(wasm.api/get-selection-rect ids)))]
|
(wasm.api/get-selection-rect ids)))]
|
||||||
(rx/of (set-temporary-selrect selrect)
|
(rx/of (set-temporary-selrect selrect)
|
||||||
(set-temporary-modifiers modifiers))))))))
|
(set-temporary-modifiers modifiers)))))))))
|
||||||
|
|
||||||
(defn propagate-structure-modifiers
|
(defn propagate-structure-modifiers
|
||||||
[modif-tree objects]
|
[modif-tree objects]
|
||||||
@ -782,58 +797,44 @@
|
|||||||
subtree-ids-by-id]
|
subtree-ids-by-id]
|
||||||
:or {ignore-constraints false ignore-snap-pixel false snap-ignore-axis nil undo-transation? true}
|
:or {ignore-constraints false ignore-snap-pixel false snap-ignore-axis nil undo-transation? true}
|
||||||
:as params}]
|
:as params}]
|
||||||
(ptk/reify ::apply-wasm-modifiers
|
(let [modif-tree (without-nil-ids modif-tree)]
|
||||||
ptk/WatchEvent
|
(ptk/reify ::apply-wasm-modifiers
|
||||||
(watch [_ state _]
|
ptk/WatchEvent
|
||||||
(let [translation?
|
(watch [_ state _]
|
||||||
(every? #(ctm/only-move? (:modifiers %)) (vals modif-tree))]
|
(let [translation?
|
||||||
(wasm.api/clean-modifiers)
|
(every? #(ctm/only-move? (:modifiers %)) (vals modif-tree))]
|
||||||
(when-not translation?
|
(wasm.api/clean-modifiers)
|
||||||
(wasm.api/set-structure-modifiers (parse-structure-modifiers modif-tree)))
|
(when-not translation?
|
||||||
|
(wasm.api/set-structure-modifiers (parse-structure-modifiers modif-tree)))
|
||||||
|
|
||||||
;; Apply property changes (e.g. grow-type) to WASM shapes before
|
;; Apply property changes (e.g. grow-type) to WASM shapes before
|
||||||
;; propagating geometry, so propagate_modifiers sees the updated state.
|
;; propagating geometry, so propagate_modifiers sees the updated state.
|
||||||
(doseq [[id {:keys [property value]}] (extract-property-changes modif-tree)]
|
(doseq [[id {:keys [property value]}] (extract-property-changes modif-tree)]
|
||||||
(when (= property :grow-type)
|
(when (= property :grow-type)
|
||||||
(wasm.api/use-shape id)
|
(wasm.api/use-shape id)
|
||||||
(wasm.api/set-shape-grow-type value)))
|
(wasm.api/set-shape-grow-type value)))
|
||||||
|
|
||||||
(let [objects (dsh/lookup-page-objects state)
|
(let [objects (dsh/lookup-page-objects state)
|
||||||
|
|
||||||
geometry-entries
|
geometry-entries
|
||||||
(parse-geometry-modifiers modif-tree)
|
(parse-geometry-modifiers modif-tree)
|
||||||
|
|
||||||
snap-pixel?
|
snap-pixel?
|
||||||
(and (not ignore-snap-pixel) (contains? (:workspace-layout state) :snap-pixel-grid))
|
(and (not ignore-snap-pixel) (contains? (:workspace-layout state) :snap-pixel-grid))
|
||||||
|
|
||||||
transforms
|
transforms
|
||||||
(cond
|
(cond
|
||||||
(and translation? (not snap-pixel?))
|
(and translation? (not snap-pixel?))
|
||||||
;; Mirror WASM `propagate_modifiers` in CLJS: splat the
|
;; Mirror WASM `propagate_modifiers` in CLJS: splat the
|
||||||
;; translation matrix onto every descendant. Without
|
;; translation matrix onto every descendant. Without
|
||||||
;; this step the commit would only touch the dragged
|
;; this step the commit would only touch the dragged
|
||||||
;; primaries and descendants would snap back to their
|
;; primaries and descendants would snap back to their
|
||||||
;; pre-drag positions on drop.
|
;; pre-drag positions on drop.
|
||||||
;;
|
;;
|
||||||
;; Skipped when `snap-pixel?` is on: WASM applies
|
;; Skipped when `snap-pixel?` is on: WASM applies
|
||||||
;; per-shape pixel correction (different scale/translate
|
;; per-shape pixel correction (different scale/translate
|
||||||
;; per descendant) which we can't replicate cheaply on
|
;; per descendant) which we can't replicate cheaply on
|
||||||
;; the CLJS side.
|
;; the CLJS side.
|
||||||
(reduce
|
|
||||||
(fn [acc [id data]]
|
|
||||||
(let [t (:transform data)
|
|
||||||
subtree-ids
|
|
||||||
(or (get subtree-ids-by-id id)
|
|
||||||
(cfh/get-children-ids-with-self objects id))]
|
|
||||||
(reduce (fn [a sid] (assoc a sid t)) acc subtree-ids)))
|
|
||||||
{}
|
|
||||||
geometry-entries)
|
|
||||||
|
|
||||||
;; Context lost / mid-reload: do not call into WASM. Use
|
|
||||||
;; root transforms (and splat translation onto descendants
|
|
||||||
;; when we can) so the commit still lands in file data.
|
|
||||||
(not (wasm.api/initialized?))
|
|
||||||
(if translation?
|
|
||||||
(reduce
|
(reduce
|
||||||
(fn [acc [id data]]
|
(fn [acc [id data]]
|
||||||
(let [t (:transform data)
|
(let [t (:transform data)
|
||||||
@ -843,71 +844,87 @@
|
|||||||
(reduce (fn [a sid] (assoc a sid t)) acc subtree-ids)))
|
(reduce (fn [a sid] (assoc a sid t)) acc subtree-ids)))
|
||||||
{}
|
{}
|
||||||
geometry-entries)
|
geometry-entries)
|
||||||
(into {}
|
|
||||||
(map (fn [[id data]] [id (:transform data)]))
|
|
||||||
geometry-entries))
|
|
||||||
|
|
||||||
:else
|
;; Context lost / mid-reload: do not call into WASM. Use
|
||||||
(into {} (wasm.api/propagate-modifiers geometry-entries snap-pixel?)))
|
;; root transforms (and splat translation onto descendants
|
||||||
|
;; when we can) so the commit still lands in file data.
|
||||||
|
(not (wasm.api/initialized?))
|
||||||
|
(if translation?
|
||||||
|
(reduce
|
||||||
|
(fn [acc [id data]]
|
||||||
|
(let [t (:transform data)
|
||||||
|
subtree-ids
|
||||||
|
(or (get subtree-ids-by-id id)
|
||||||
|
(cfh/get-children-ids-with-self objects id))]
|
||||||
|
(reduce (fn [a sid] (assoc a sid t)) acc subtree-ids)))
|
||||||
|
{}
|
||||||
|
geometry-entries)
|
||||||
|
(into {}
|
||||||
|
(map (fn [[id data]] [id (:transform data)]))
|
||||||
|
geometry-entries))
|
||||||
|
|
||||||
ignore-tree
|
:else
|
||||||
(calculate-ignore-tree-wasm transforms objects)
|
(into {} (wasm.api/propagate-modifiers geometry-entries snap-pixel?)))
|
||||||
|
|
||||||
options
|
ignore-tree
|
||||||
(-> params
|
(calculate-ignore-tree-wasm transforms objects)
|
||||||
(assoc :reg-objects? true)
|
|
||||||
(assoc :ignore-tree ignore-tree)
|
|
||||||
(assoc :translation? translation?)
|
|
||||||
;; Attributes that can change in the transform. This
|
|
||||||
;; way we don't have to check all the attributes
|
|
||||||
(assoc :attrs transform-attrs))
|
|
||||||
|
|
||||||
modif-tree
|
options
|
||||||
(propagate-structure-modifiers modif-tree (dsh/lookup-page-objects state))
|
(-> params
|
||||||
|
(assoc :reg-objects? true)
|
||||||
|
(assoc :ignore-tree ignore-tree)
|
||||||
|
(assoc :translation? translation?)
|
||||||
|
;; Attributes that can change in the transform. This
|
||||||
|
;; way we don't have to check all the attributes
|
||||||
|
(assoc :attrs transform-attrs))
|
||||||
|
|
||||||
ids
|
modif-tree
|
||||||
(into (set (keys modif-tree)) xf:without-uuid-zero (keys transforms))
|
(propagate-structure-modifiers modif-tree (dsh/lookup-page-objects state))
|
||||||
|
|
||||||
update-shape
|
ids
|
||||||
(fn [shape]
|
(into (set (keys modif-tree)) xf:without-uuid-zero (keys transforms))
|
||||||
(let [shape-id (dm/get-prop shape :id)
|
|
||||||
transform (get transforms shape-id)
|
|
||||||
modifiers (dm/get-in modif-tree [shape-id :modifiers])]
|
|
||||||
(-> shape
|
|
||||||
(gsh/apply-transform transform)
|
|
||||||
(ctm/apply-structure-modifiers modifiers))))
|
|
||||||
|
|
||||||
bool-ids
|
update-shape
|
||||||
(into #{}
|
(fn [shape]
|
||||||
(comp
|
(let [shape-id (dm/get-prop shape :id)
|
||||||
(mapcat (partial cfh/get-parents-with-self objects))
|
transform (get transforms shape-id)
|
||||||
(filter cfh/bool-shape?)
|
modifiers (dm/get-in modif-tree [shape-id :modifiers])]
|
||||||
(map :id))
|
(-> shape
|
||||||
ids)
|
(gsh/apply-transform transform)
|
||||||
|
(ctm/apply-structure-modifiers modifiers))))
|
||||||
|
|
||||||
undo-id (js/Symbol)]
|
bool-ids
|
||||||
(rx/concat
|
(into #{}
|
||||||
(if undo-transation?
|
(comp
|
||||||
(rx/of (dwu/start-undo-transaction undo-id))
|
(mapcat (partial cfh/get-parents-with-self objects))
|
||||||
(rx/empty))
|
(filter cfh/bool-shape?)
|
||||||
(rx/of
|
(map :id))
|
||||||
(clear-local-transform)
|
ids)
|
||||||
(ptk/event ::dwg/move-frame-guides {:ids ids :transforms transforms})
|
|
||||||
(ptk/event ::dwcm/move-frame-comment-threads transforms)
|
|
||||||
(dwsh/update-shapes ids update-shape options)
|
|
||||||
|
|
||||||
;; The update to the bool path needs to be in a different operation because it
|
undo-id (js/Symbol)]
|
||||||
;; needs to have the updated children info.
|
|
||||||
;; `update-layout? false`: recalculating a bool path can never change
|
|
||||||
;; `:hidden`, and the layout check would recompute the whole boolean
|
|
||||||
;; path in WASM once per bool shape just to find that out.
|
|
||||||
(dwsh/update-shapes bool-ids path/update-bool-shape (assoc options
|
|
||||||
:with-objects? true
|
|
||||||
:update-layout? false)))
|
|
||||||
|
|
||||||
(if undo-transation?
|
(rx/concat
|
||||||
(rx/of (dwu/commit-undo-transaction undo-id))
|
(if undo-transation?
|
||||||
(rx/empty))))))))
|
(rx/of (dwu/start-undo-transaction undo-id))
|
||||||
|
(rx/empty))
|
||||||
|
(rx/of
|
||||||
|
(clear-local-transform)
|
||||||
|
(ptk/event ::dwg/move-frame-guides {:ids ids :transforms transforms})
|
||||||
|
(ptk/event ::dwcm/move-frame-comment-threads transforms)
|
||||||
|
(dwsh/update-shapes ids update-shape options)
|
||||||
|
|
||||||
|
;; The update to the bool path needs to be in a different operation because it
|
||||||
|
;; needs to have the updated children info.
|
||||||
|
;; `update-layout? false`: recalculating a bool path can never change
|
||||||
|
;; `:hidden`, and the layout check would recompute the whole boolean
|
||||||
|
;; path in WASM once per bool shape just to find that out.
|
||||||
|
(dwsh/update-shapes bool-ids path/update-bool-shape (assoc options
|
||||||
|
:with-objects? true
|
||||||
|
:update-layout? false)))
|
||||||
|
|
||||||
|
(if undo-transation?
|
||||||
|
(rx/of (dwu/commit-undo-transaction undo-id))
|
||||||
|
(rx/empty)))))))))
|
||||||
|
|
||||||
(def ^:private
|
(def ^:private
|
||||||
xf-rotation-shape
|
xf-rotation-shape
|
||||||
|
|||||||
@ -335,14 +335,15 @@
|
|||||||
(let [page-id (:current-page-id state)
|
(let [page-id (:current-page-id state)
|
||||||
objects (dsh/lookup-page-objects state page-id)
|
objects (dsh/lookup-page-objects state page-id)
|
||||||
shape (get objects shape-id)
|
shape (get objects shape-id)
|
||||||
container (get objects (:parent-id shape))
|
container (get objects (:parent-id shape))]
|
||||||
width (+ (:width container) (:width shape) 20) ;; 20 is the default gap for variants
|
(when (and (some? shape) (some? container))
|
||||||
x (- width (+ (:width shape) 30))] ;; 30 is the default margin for variants
|
(let [width (+ (:width container) (:width shape) 20) ;; 20 is the default gap for variants
|
||||||
(rx/of
|
x (- width (+ (:width shape) 30))] ;; 30 is the default margin for variants
|
||||||
(dwt/update-dimensions [(:parent-id shape)] :width width)
|
(rx/of
|
||||||
(dwt/update-position shape-id
|
(dwt/update-dimensions [(:parent-id shape)] :width width)
|
||||||
{:x x}
|
(dwt/update-position shape-id
|
||||||
{:absolute? false}))))))
|
{:x x}
|
||||||
|
{:absolute? false}))))))))
|
||||||
|
|
||||||
(defn add-new-variant
|
(defn add-new-variant
|
||||||
"Create a new variant and add it to the variant-container"
|
"Create a new variant and add it to the variant-container"
|
||||||
@ -359,39 +360,40 @@
|
|||||||
shape (get objects shape-id)
|
shape (get objects shape-id)
|
||||||
shape (if (ctc/is-variant-container? shape)
|
shape (if (ctc/is-variant-container? shape)
|
||||||
(get objects (last (:shapes shape)))
|
(get objects (last (:shapes shape)))
|
||||||
shape)
|
shape)]
|
||||||
component-id (:component-id shape)
|
(when (some? shape)
|
||||||
component (ctkl/get-component data component-id)
|
(let [component-id (:component-id shape)
|
||||||
|
component (ctkl/get-component data component-id)
|
||||||
|
|
||||||
container-id (:parent-id shape)
|
container-id (:parent-id shape)
|
||||||
variant-container (get objects container-id)
|
variant-container (get objects container-id)
|
||||||
has-layout? (ctsl/any-layout? variant-container)
|
has-layout? (ctsl/any-layout? variant-container)
|
||||||
|
|
||||||
new-component-id (uuid/next)
|
new-component-id (uuid/next)
|
||||||
new-shape-id (uuid/next)
|
new-shape-id (uuid/next)
|
||||||
|
|
||||||
prop-num (dec (count (:variant-properties component)))
|
prop-num (dec (count (:variant-properties component)))
|
||||||
|
|
||||||
changes (-> (pcb/empty-changes it page-id)
|
changes (-> (pcb/empty-changes it page-id)
|
||||||
(pcb/with-library-data data)
|
(pcb/with-library-data data)
|
||||||
(pcb/with-objects objects)
|
(pcb/with-objects objects)
|
||||||
(pcb/with-page-id page-id)
|
(pcb/with-page-id page-id)
|
||||||
(clv/generate-add-new-variant shape (:variant-id component) new-component-id new-shape-id prop-num))
|
(clv/generate-add-new-variant shape (:variant-id component) new-component-id new-shape-id prop-num))
|
||||||
|
|
||||||
undo-id (js/Symbol)]
|
undo-id (js/Symbol)]
|
||||||
(rx/concat
|
(rx/concat
|
||||||
(rx/of
|
(rx/of
|
||||||
(dwu/start-undo-transaction undo-id)
|
(dwu/start-undo-transaction undo-id)
|
||||||
(dch/commit-changes changes)
|
(dch/commit-changes changes)
|
||||||
(when-not has-layout?
|
(when-not has-layout?
|
||||||
(resposition-and-resize-variant new-shape-id))
|
(resposition-and-resize-variant new-shape-id))
|
||||||
(dwu/commit-undo-transaction undo-id)
|
(dwu/commit-undo-transaction undo-id)
|
||||||
(ptk/data-event :layout/update {:ids [(:parent-id shape)]})
|
(ptk/data-event :layout/update {:ids [(:parent-id shape)]})
|
||||||
(if multiselect?
|
(if multiselect?
|
||||||
(dws/shift-select-shapes new-shape-id)
|
(dws/shift-select-shapes new-shape-id)
|
||||||
(dws/select-shape new-shape-id)))
|
(dws/select-shape new-shape-id)))
|
||||||
(->> (rx/of (focus-property (:id variant-container)))
|
(->> (rx/of (focus-property (:id variant-container)))
|
||||||
(rx/delay 250))))))))
|
(rx/delay 250))))))))))
|
||||||
|
|
||||||
(defn transform-in-variant
|
(defn transform-in-variant
|
||||||
"Given the id of a main shape of a component, creates a variant structure for
|
"Given the id of a main shape of a component, creates a variant structure for
|
||||||
|
|||||||
@ -0,0 +1,109 @@
|
|||||||
|
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
;;
|
||||||
|
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||||
|
|
||||||
|
(ns frontend-tests.logic.wasm-modifiers-nil-id-test
|
||||||
|
"Reproduces the production crash \"Cannot read properties of null
|
||||||
|
(reading '__u32_buffer')\".
|
||||||
|
|
||||||
|
A modif-tree containing a nil shape id (production builds elide the
|
||||||
|
asserts that catch this upstream, e.g. `update-dimensions` called
|
||||||
|
with `[(:parent-id shape)]` when `shape` is missing) reached
|
||||||
|
`wasm.api/propagate-modifiers` / `wasm.api/set-structure-modifiers`,
|
||||||
|
and `mem.h32/write-uuid` crashed calling `uuid/get-u32` on nil while
|
||||||
|
writing to the WASM heap.
|
||||||
|
|
||||||
|
These tests assert that no nil id ever crosses the WASM boundary and
|
||||||
|
that valid shapes in the same modif-tree are still processed."
|
||||||
|
(:require
|
||||||
|
[app.common.geom.rect :as grc]
|
||||||
|
[app.common.math :as mth]
|
||||||
|
[app.common.test-helpers.compositions :as ctho]
|
||||||
|
[app.common.test-helpers.files :as cthf]
|
||||||
|
[app.common.test-helpers.ids-map :as cthi]
|
||||||
|
[app.common.test-helpers.shapes :as cths]
|
||||||
|
[app.common.types.modifiers :as ctm]
|
||||||
|
[app.common.uuid :as uuid]
|
||||||
|
[app.main.data.workspace.modifiers :as dwm]
|
||||||
|
[app.render-wasm.api :as wasm.api]
|
||||||
|
[cljs.test :as t :include-macros true]
|
||||||
|
[frontend-tests.helpers.state :as ths]
|
||||||
|
[frontend-tests.helpers.wasm :as thw]))
|
||||||
|
|
||||||
|
(def ^:private captured-geometry-entries
|
||||||
|
"Entries passed to `wasm.api/propagate-modifiers` during a test."
|
||||||
|
(atom []))
|
||||||
|
|
||||||
|
(def ^:private captured-structure-entries
|
||||||
|
"Entries passed to `wasm.api/set-structure-modifiers` during a test."
|
||||||
|
(atom []))
|
||||||
|
|
||||||
|
(defn- install-capturing-spies!
|
||||||
|
"Replace the plain WASM mocks with variants that record their input.
|
||||||
|
Must run after `thw/setup-wasm-mocks!` so teardown still restores
|
||||||
|
the real implementations."
|
||||||
|
[]
|
||||||
|
(set! wasm.api/propagate-modifiers
|
||||||
|
(fn [entries _pixel-precision]
|
||||||
|
(swap! captured-geometry-entries into entries)
|
||||||
|
(into []
|
||||||
|
(map (fn [[id data]] [id (:transform data)]))
|
||||||
|
entries)))
|
||||||
|
(set! wasm.api/set-structure-modifiers
|
||||||
|
(fn [entries]
|
||||||
|
(swap! captured-structure-entries into entries)
|
||||||
|
nil)))
|
||||||
|
|
||||||
|
(t/use-fixtures :each
|
||||||
|
{:before (fn []
|
||||||
|
(cthi/reset-idmap!)
|
||||||
|
(reset! captured-geometry-entries [])
|
||||||
|
(reset! captured-structure-entries [])
|
||||||
|
(thw/setup-wasm-mocks!)
|
||||||
|
(install-capturing-spies!))
|
||||||
|
:after (fn []
|
||||||
|
(thw/teardown-wasm-mocks!))})
|
||||||
|
|
||||||
|
(t/deftest nil-id-does-not-reach-propagate-modifiers
|
||||||
|
;; A nil-keyed entry must be dropped before the WASM heap write while
|
||||||
|
;; the valid entry is still resized.
|
||||||
|
(t/async
|
||||||
|
done
|
||||||
|
(let [file (-> (cthf/sample-file :file1)
|
||||||
|
(ctho/add-rect :rect1 :x 10 :y 20 :width 100 :height 50))
|
||||||
|
store (ths/setup-store file)
|
||||||
|
rect (cths/get-shape file :rect1)
|
||||||
|
resize (ctm/change-dimensions-modifiers rect :width 200)
|
||||||
|
modif-tree {nil {:modifiers resize}
|
||||||
|
(:id rect) {:modifiers resize}}
|
||||||
|
events [(dwm/apply-wasm-modifiers modif-tree {:ignore-snap-pixel true})]]
|
||||||
|
(ths/run-store
|
||||||
|
store done events
|
||||||
|
(fn [new-state]
|
||||||
|
(let [entry-ids (into #{} (map first) @captured-geometry-entries)
|
||||||
|
file' (ths/get-file-from-state new-state)
|
||||||
|
rect' (cths/get-shape file' :rect1)
|
||||||
|
width (-> rect' :points grc/points->rect :width)]
|
||||||
|
(t/is (not (contains? entry-ids nil)))
|
||||||
|
(t/is (contains? entry-ids (:id rect)))
|
||||||
|
(t/is (mth/close? 200 width))))))))
|
||||||
|
|
||||||
|
(t/deftest nil-id-does-not-reach-set-structure-modifiers
|
||||||
|
;; A nil-keyed entry with structure modifiers must not produce
|
||||||
|
;; structure entries with a nil :parent or :id.
|
||||||
|
(t/async
|
||||||
|
done
|
||||||
|
(let [file (-> (cthf/sample-file :file1)
|
||||||
|
(ctho/add-rect :rect1 :x 10 :y 20 :width 100 :height 50))
|
||||||
|
store (ths/setup-store file)
|
||||||
|
rect (cths/get-shape file :rect1)
|
||||||
|
modif-tree {nil {:modifiers (ctm/add-children nil [(uuid/next)] 0)}
|
||||||
|
(:id rect) {:modifiers (ctm/change-dimensions-modifiers rect :width 200)}}
|
||||||
|
events [(dwm/apply-wasm-modifiers modif-tree {:ignore-snap-pixel true})]]
|
||||||
|
(ths/run-store
|
||||||
|
store done events
|
||||||
|
(fn [_new-state]
|
||||||
|
(t/is (every? #(some? (:parent %)) @captured-structure-entries))
|
||||||
|
(t/is (every? #(some? (:id %)) @captured-structure-entries)))))))
|
||||||
@ -39,6 +39,7 @@
|
|||||||
[frontend-tests.logic.pasting-in-containers-test]
|
[frontend-tests.logic.pasting-in-containers-test]
|
||||||
[frontend-tests.logic.sidebar-transform-coalescing-test]
|
[frontend-tests.logic.sidebar-transform-coalescing-test]
|
||||||
[frontend-tests.logic.update-position-test]
|
[frontend-tests.logic.update-position-test]
|
||||||
|
[frontend-tests.logic.wasm-modifiers-nil-id-test]
|
||||||
[frontend-tests.main-errors-test]
|
[frontend-tests.main-errors-test]
|
||||||
[frontend-tests.plugins.comments-test]
|
[frontend-tests.plugins.comments-test]
|
||||||
[frontend-tests.plugins.context-shapes-test]
|
[frontend-tests.plugins.context-shapes-test]
|
||||||
@ -138,6 +139,7 @@
|
|||||||
'frontend-tests.main-errors-test
|
'frontend-tests.main-errors-test
|
||||||
'frontend-tests.logic.sidebar-transform-coalescing-test
|
'frontend-tests.logic.sidebar-transform-coalescing-test
|
||||||
'frontend-tests.logic.update-position-test
|
'frontend-tests.logic.update-position-test
|
||||||
|
'frontend-tests.logic.wasm-modifiers-nil-id-test
|
||||||
'frontend-tests.plugins.comments-test
|
'frontend-tests.plugins.comments-test
|
||||||
'frontend-tests.plugins.context-shapes-test
|
'frontend-tests.plugins.context-shapes-test
|
||||||
'frontend-tests.plugins.file-test
|
'frontend-tests.plugins.file-test
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user