mirror of
https://github.com/penpot/penpot.git
synced 2025-06-02 00:11:37 +02:00
568 lines
22 KiB
Clojure
568 lines
22 KiB
Clojure
;; 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
|
|
|
|
(ns app.main.data.workspace.modifiers
|
|
"Events related with shapes transformations"
|
|
(:require
|
|
[app.common.data :as d]
|
|
[app.common.data.macros :as dm]
|
|
[app.common.files.helpers :as cfh]
|
|
[app.common.geom.modifiers :as gm]
|
|
[app.common.geom.point :as gpt]
|
|
[app.common.geom.rect :as grc]
|
|
[app.common.geom.shapes :as gsh]
|
|
[app.common.math :as mth]
|
|
[app.common.types.component :as ctk]
|
|
[app.common.types.container :as ctn]
|
|
[app.common.types.modifiers :as ctm]
|
|
[app.common.types.shape-tree :as ctst]
|
|
[app.common.types.shape.attrs :refer [editable-attrs]]
|
|
[app.common.types.shape.layout :as ctl]
|
|
[app.common.uuid :as uuid]
|
|
[app.main.constants :refer [zoom-half-pixel-precision]]
|
|
[app.main.data.workspace.comments :as-alias dwcm]
|
|
[app.main.data.workspace.guides :as-alias dwg]
|
|
[app.main.data.workspace.shapes :as dwsh]
|
|
[app.main.data.workspace.state-helpers :as wsh]
|
|
[app.main.data.workspace.undo :as dwu]
|
|
[beicon.v2.core :as rx]
|
|
[potok.v2.core :as ptk]))
|
|
|
|
;; -- temporary modifiers -------------------------------------------
|
|
|
|
;; During an interactive transformation of shapes (e.g. when resizing or rotating
|
|
;; a group with the mouse), there are a lot of objects that need to be modified
|
|
;; (in this case, the group and all its children).
|
|
;;
|
|
;; To avoid updating the shapes theirselves, and forcing redraw of all components
|
|
;; that depend on the "objects" global state, we set a "modifiers" structure, with
|
|
;; the changes that need to be applied, and store it in :workspace-modifiers global
|
|
;; variable. The viewport reads this and merges it into the objects list it uses to
|
|
;; paint the viewport content, redrawing only the objects that have new modifiers.
|
|
;;
|
|
;; When the interaction is finished (e.g. user releases mouse button), the
|
|
;; apply-modifiers event is done, that consolidates all modifiers into the base
|
|
;; geometric attributes of the shapes.
|
|
|
|
(defn- check-delta
|
|
"If the shape is a component instance, check its relative position and rotation respect
|
|
the root of the component, and see if it changes after applying a transformation."
|
|
[shape root transformed-shape transformed-root]
|
|
(let [shape-delta
|
|
(when root
|
|
(gpt/point (- (gsh/left-bound shape) (gsh/left-bound root))
|
|
(- (gsh/top-bound shape) (gsh/top-bound root))))
|
|
|
|
transformed-shape-delta
|
|
(when transformed-root
|
|
(gpt/point (- (gsh/left-bound transformed-shape) (gsh/left-bound transformed-root))
|
|
(- (gsh/top-bound transformed-shape) (gsh/top-bound transformed-root))))
|
|
|
|
distance
|
|
(if (and shape-delta transformed-shape-delta)
|
|
(gpt/distance-vector shape-delta transformed-shape-delta)
|
|
(gpt/point 0 0))
|
|
|
|
rotation-delta
|
|
(if (and (some? (:rotation shape)) (some? (:rotation shape)))
|
|
(- (:rotation transformed-shape) (:rotation shape))
|
|
0)
|
|
|
|
selrect (:selrect shape)
|
|
transformed-selrect (:selrect transformed-shape)]
|
|
|
|
;; There are cases in that the coordinates change slightly (e.g. when rounding
|
|
;; to pixel, or when recalculating text positions in different zoom levels).
|
|
;; To take this into account, we ignore movements smaller than 1 pixel.
|
|
;;
|
|
;; When the change is a resize, also has a transformation that may have the
|
|
;; shape position unchanged. But in this case we do not want to ignore it.
|
|
(and (and (< (:x distance) 1) (< (:y distance) 1))
|
|
(mth/close? (:width selrect) (:width transformed-selrect))
|
|
(mth/close? (:height selrect) (:height transformed-selrect))
|
|
(mth/close? rotation-delta 0))))
|
|
|
|
(defn calculate-ignore-tree
|
|
"Retrieves a map with the flag `ignore-geometry?` given a tree of modifiers"
|
|
[modif-tree objects]
|
|
|
|
(letfn [(get-ignore-tree
|
|
([ignore-tree shape]
|
|
(let [shape-id (dm/get-prop shape :id)
|
|
transformed-shape (gsh/transform-shape shape (dm/get-in modif-tree [shape-id :modifiers]))
|
|
|
|
root
|
|
(if (:component-root shape)
|
|
shape
|
|
(ctn/get-component-shape objects shape {:allow-main? true}))
|
|
|
|
transformed-root
|
|
(if (:component-root shape)
|
|
transformed-shape
|
|
(gsh/transform-shape root (dm/get-in modif-tree [(:id root) :modifiers])))]
|
|
|
|
(get-ignore-tree ignore-tree shape transformed-shape root transformed-root)))
|
|
|
|
([ignore-tree shape root transformed-root]
|
|
(let [shape-id (dm/get-prop shape :id)
|
|
transformed-shape (gsh/transform-shape shape (dm/get-in modif-tree [shape-id :modifiers]))]
|
|
(get-ignore-tree ignore-tree shape transformed-shape root transformed-root)))
|
|
|
|
([ignore-tree shape transformed-shape root transformed-root]
|
|
(let [shape-id (dm/get-prop shape :id)
|
|
|
|
ignore-tree
|
|
(cond-> ignore-tree
|
|
(and (some? root) (ctk/in-component-copy? shape))
|
|
(assoc
|
|
shape-id
|
|
(check-delta shape root transformed-shape transformed-root)))
|
|
|
|
set-child
|
|
(fn [ignore-tree child]
|
|
(get-ignore-tree ignore-tree child root transformed-root))]
|
|
|
|
(->> (:shapes shape)
|
|
(map (d/getf objects))
|
|
(reduce set-child ignore-tree)))))]
|
|
|
|
;; we check twice because we want only to search parents of components but once the
|
|
;; tree is traversed we only want to process the objects in components
|
|
(->> (keys modif-tree)
|
|
(map #(get objects %))
|
|
(reduce get-ignore-tree nil))))
|
|
|
|
(defn assoc-position-data
|
|
[shape position-data old-shape]
|
|
(let [deltav (gpt/to-vec (gpt/point (:selrect old-shape))
|
|
(gpt/point (:selrect shape)))
|
|
position-data
|
|
(-> position-data
|
|
(gsh/move-position-data deltav))]
|
|
(cond-> shape
|
|
(d/not-empty? position-data)
|
|
(assoc :position-data position-data))))
|
|
|
|
(defn update-grow-type
|
|
[shape old-shape]
|
|
(let [auto-width? (= :auto-width (:grow-type shape))
|
|
auto-height? (= :auto-height (:grow-type shape))
|
|
|
|
changed-width? (> (mth/abs (- (:width shape) (:width old-shape))) 0.1)
|
|
changed-height? (> (mth/abs (- (:height shape) (:height old-shape))) 0.1)
|
|
|
|
change-to-fixed? (or (and auto-width? (or changed-height? changed-width?))
|
|
(and auto-height? changed-height?))]
|
|
(cond-> shape
|
|
change-to-fixed?
|
|
(assoc :grow-type :fixed))))
|
|
|
|
(defn- clear-local-transform []
|
|
(ptk/reify ::clear-local-transform
|
|
ptk/UpdateEvent
|
|
(update [_ state]
|
|
(-> state
|
|
(dissoc :workspace-modifiers)
|
|
(dissoc :app.main.data.workspace.transforms/current-move-selected)))))
|
|
|
|
(defn create-modif-tree
|
|
[ids modifiers]
|
|
(dm/assert!
|
|
"expected valid coll of uuids"
|
|
(every? uuid? ids))
|
|
(into {} (map #(vector % {:modifiers modifiers})) ids))
|
|
|
|
(defn build-modif-tree
|
|
[ids objects get-modifier]
|
|
(dm/assert!
|
|
"expected valid coll of uuids"
|
|
(every? uuid? ids))
|
|
(into {} (map #(vector % {:modifiers (get-modifier (get objects %))})) ids))
|
|
|
|
(defn modifier-remove-from-parent
|
|
[modif-tree objects shapes]
|
|
(->> shapes
|
|
(reduce
|
|
(fn [modif-tree child-id]
|
|
(let [parent-id (get-in objects [child-id :parent-id])]
|
|
(update-in modif-tree [parent-id :modifiers] ctm/remove-children [child-id])))
|
|
modif-tree)))
|
|
|
|
(defn add-grid-children-modifiers
|
|
[modifiers frame-id shapes objects [row column :as cell]]
|
|
(let [frame (get objects frame-id)
|
|
ids (set shapes)
|
|
|
|
;; Temporary remove the children when moving them
|
|
frame (-> frame
|
|
(update :shapes #(d/removev ids %))
|
|
(ctl/assign-cells objects))
|
|
|
|
ids (->> ids
|
|
(remove #(ctl/position-absolute? objects %))
|
|
(ctst/sort-z-index objects)
|
|
reverse)
|
|
|
|
frame (-> frame
|
|
(update :shapes d/concat-vec ids)
|
|
(cond-> (some? cell)
|
|
(ctl/push-into-cell ids row column))
|
|
(ctl/assign-cells objects))]
|
|
(-> modifiers
|
|
(ctm/change-property :layout-grid-rows (:layout-grid-rows frame))
|
|
(ctm/change-property :layout-grid-columns (:layout-grid-columns frame))
|
|
(ctm/change-property :layout-grid-cells (:layout-grid-cells frame)))))
|
|
|
|
(defn build-change-frame-modifiers
|
|
[modif-tree objects selected target-frame-id drop-index cell-data]
|
|
|
|
(let [origin-frame-ids (->> selected (group-by #(get-in objects [% :frame-id])))
|
|
child-set (set (get-in objects [target-frame-id :shapes]))
|
|
|
|
target-frame (get objects target-frame-id)
|
|
target-flex-layout? (ctl/flex-layout? target-frame)
|
|
target-grid-layout? (ctl/grid-layout? target-frame)
|
|
|
|
children-ids (concat (:shapes target-frame) selected)
|
|
|
|
set-parent-ids
|
|
(fn [modif-tree shapes target-frame-id]
|
|
(reduce
|
|
(fn [modif-tree id]
|
|
(update-in
|
|
modif-tree
|
|
[id :modifiers]
|
|
#(-> %
|
|
(ctm/change-property :frame-id target-frame-id)
|
|
(ctm/change-property :parent-id target-frame-id))))
|
|
modif-tree
|
|
shapes))
|
|
|
|
update-frame-modifiers
|
|
(fn [modif-tree [original-frame shapes]]
|
|
(let [shapes (->> shapes (d/removev #(= target-frame-id %)))
|
|
shapes (cond->> shapes
|
|
(and (or target-grid-layout? target-flex-layout?)
|
|
(= original-frame target-frame-id))
|
|
;; When movining inside a layout frame remove the shapes that are not immediate children
|
|
(filterv #(contains? child-set %)))
|
|
children-ids (->> (dm/get-in objects [original-frame :shapes])
|
|
(remove (set selected)))
|
|
h-sizing? (and (ctl/flex-layout? objects original-frame)
|
|
(ctl/change-h-sizing? original-frame objects children-ids))
|
|
v-sizing? (and (ctl/flex-layout? objects original-frame)
|
|
(ctl/change-v-sizing? original-frame objects children-ids))]
|
|
(cond-> modif-tree
|
|
(not= original-frame target-frame-id)
|
|
(-> (modifier-remove-from-parent objects shapes)
|
|
(update-in [target-frame-id :modifiers] ctm/add-children shapes drop-index)
|
|
(set-parent-ids shapes target-frame-id)
|
|
(cond-> h-sizing?
|
|
(update-in [original-frame :modifiers] ctm/change-property :layout-item-h-sizing :fix))
|
|
(cond-> v-sizing?
|
|
(update-in [original-frame :modifiers] ctm/change-property :layout-item-v-sizing :fix)))
|
|
|
|
(and target-flex-layout? (= original-frame target-frame-id))
|
|
(update-in [target-frame-id :modifiers] ctm/add-children shapes drop-index)
|
|
|
|
;; Add the object to the cell
|
|
target-grid-layout?
|
|
(update-in [target-frame-id :modifiers] add-grid-children-modifiers target-frame-id shapes objects cell-data))))]
|
|
|
|
(as-> modif-tree $
|
|
(reduce update-frame-modifiers $ origin-frame-ids)
|
|
(cond-> $
|
|
;; Set fix position to target frame (horizontal)
|
|
(and (ctl/flex-layout? objects target-frame-id)
|
|
(ctl/change-h-sizing? target-frame-id objects children-ids))
|
|
(update-in [target-frame-id :modifiers] ctm/change-property :layout-item-h-sizing :fix)
|
|
|
|
;; Set fix position to target frame (vertical)
|
|
(and (ctl/flex-layout? objects target-frame-id)
|
|
(ctl/change-v-sizing? target-frame-id objects children-ids))
|
|
(update-in [target-frame-id :modifiers] ctm/change-property :layout-item-v-sizing :fix)))))
|
|
|
|
(defn modif->js
|
|
[modif-tree objects]
|
|
(clj->js (into {}
|
|
(map (fn [[k v]]
|
|
[(get-in objects [k :name]) v]))
|
|
modif-tree)))
|
|
|
|
(defn apply-text-modifier
|
|
[shape {:keys [width height]}]
|
|
(cond-> shape
|
|
(some? width)
|
|
(gsh/transform-shape (ctm/change-dimensions-modifiers shape :width width {:ignore-lock? true}))
|
|
|
|
(some? height)
|
|
(gsh/transform-shape (ctm/change-dimensions-modifiers shape :height height {:ignore-lock? true}))))
|
|
|
|
(defn apply-text-modifiers
|
|
[objects text-modifiers]
|
|
(loop [modifiers (seq text-modifiers)
|
|
result objects]
|
|
(if (empty? modifiers)
|
|
result
|
|
(let [[id text-modifier] (first modifiers)]
|
|
(recur (rest modifiers)
|
|
(d/update-when result id apply-text-modifier text-modifier))))))
|
|
|
|
#_(defn apply-path-modifiers
|
|
[objects path-modifiers]
|
|
(letfn [(apply-path-modifier
|
|
[shape {:keys [content-modifiers]}]
|
|
(let [shape (update shape :content upc/apply-content-modifiers content-modifiers)
|
|
[points selrect] (helpers/content->points+selrect shape (:content shape))]
|
|
(assoc shape :selrect selrect :points points)))]
|
|
(loop [modifiers (seq path-modifiers)
|
|
result objects]
|
|
(if (empty? modifiers)
|
|
result
|
|
(let [[id path-modifier] (first modifiers)]
|
|
(recur (rest modifiers)
|
|
(update objects id apply-path-modifier path-modifier)))))))
|
|
|
|
(defn- calculate-modifiers
|
|
([state modif-tree]
|
|
(calculate-modifiers state false false modif-tree))
|
|
|
|
([state ignore-constraints ignore-snap-pixel modif-tree]
|
|
(calculate-modifiers state ignore-constraints ignore-snap-pixel modif-tree nil))
|
|
|
|
([state ignore-constraints ignore-snap-pixel modif-tree params]
|
|
(let [objects
|
|
(wsh/lookup-page-objects state)
|
|
|
|
snap-pixel?
|
|
(and (not ignore-snap-pixel) (contains? (:workspace-layout state) :snap-pixel-grid))
|
|
|
|
zoom (dm/get-in state [:workspace-local :zoom])
|
|
snap-precision (if (>= zoom zoom-half-pixel-precision) 0.5 1)]
|
|
|
|
(as-> objects $
|
|
(apply-text-modifiers $ (get state :workspace-text-modifier))
|
|
;;(apply-path-modifiers $ (get-in state [:workspace-local :edit-path]))
|
|
(gm/set-objects-modifiers modif-tree $ (merge
|
|
params
|
|
{:ignore-constraints ignore-constraints
|
|
:snap-pixel? snap-pixel?
|
|
:snap-precision snap-precision}))))))
|
|
|
|
(defn- calculate-update-modifiers
|
|
[old-modif-tree state ignore-constraints ignore-snap-pixel modif-tree]
|
|
(let [objects
|
|
(wsh/lookup-page-objects state)
|
|
|
|
snap-pixel?
|
|
(and (not ignore-snap-pixel) (contains? (:workspace-layout state) :snap-pixel-grid))
|
|
|
|
zoom (dm/get-in state [:workspace-local :zoom])
|
|
|
|
snap-precision (if (>= zoom zoom-half-pixel-precision) 0.5 1)
|
|
objects
|
|
(-> objects
|
|
(apply-text-modifiers (get state :workspace-text-modifier)))]
|
|
|
|
(gm/set-objects-modifiers
|
|
old-modif-tree
|
|
modif-tree
|
|
objects
|
|
{:ignore-constraints ignore-constraints
|
|
:snap-pixel? snap-pixel?
|
|
:snap-precision snap-precision})))
|
|
|
|
(defn update-modifiers
|
|
([modif-tree]
|
|
(update-modifiers modif-tree false))
|
|
|
|
([modif-tree ignore-constraints]
|
|
(update-modifiers modif-tree ignore-constraints false))
|
|
|
|
([modif-tree ignore-constraints ignore-snap-pixel]
|
|
(ptk/reify ::update-modifiers
|
|
ptk/UpdateEvent
|
|
(update [_ state]
|
|
(update state :workspace-modifiers calculate-update-modifiers state ignore-constraints ignore-snap-pixel modif-tree)))))
|
|
|
|
(defn set-modifiers
|
|
([modif-tree]
|
|
(set-modifiers modif-tree false))
|
|
|
|
([modif-tree ignore-constraints]
|
|
(set-modifiers modif-tree ignore-constraints false))
|
|
|
|
([modif-tree ignore-constraints ignore-snap-pixel]
|
|
(set-modifiers modif-tree ignore-constraints ignore-snap-pixel nil))
|
|
|
|
([modif-tree ignore-constraints ignore-snap-pixel params]
|
|
(ptk/reify ::set-modifiers
|
|
ptk/UpdateEvent
|
|
(update [_ state]
|
|
(assoc state :workspace-modifiers (calculate-modifiers state ignore-constraints ignore-snap-pixel modif-tree params))))))
|
|
|
|
(def ^:private
|
|
xf-rotation-shape
|
|
(comp
|
|
(remove #(get % :blocked false))
|
|
(filter #(:rotation (get editable-attrs (:type %))))
|
|
(map :id)))
|
|
|
|
;; Rotation use different algorithm to calculate children
|
|
;; modifiers (and do not use child constraints).
|
|
(defn set-rotation-modifiers
|
|
([angle shapes]
|
|
(set-rotation-modifiers angle shapes (-> shapes gsh/shapes->rect grc/rect->center)))
|
|
|
|
([angle shapes center]
|
|
(ptk/reify ::set-rotation-modifiers
|
|
ptk/UpdateEvent
|
|
(update [_ state]
|
|
(let [objects (wsh/lookup-page-objects state)
|
|
ids (sequence xf-rotation-shape shapes)
|
|
|
|
get-modifier
|
|
(fn [shape]
|
|
(ctm/rotation-modifiers shape center angle))
|
|
|
|
modif-tree
|
|
(-> (build-modif-tree ids objects get-modifier)
|
|
(gm/set-objects-modifiers objects))]
|
|
|
|
(assoc state :workspace-modifiers modif-tree))))))
|
|
|
|
;; This function is similar to set-rotation-modifiers but:
|
|
;; - It consideres the center for everyshape instead of the center of the total selrect
|
|
;; - The angle param is the desired final value, not a delta
|
|
(defn set-delta-rotation-modifiers
|
|
[angle shapes {:keys [center delta?] :or {center nil delta? false}}]
|
|
(ptk/reify ::set-delta-rotation-modifiers
|
|
ptk/UpdateEvent
|
|
(update [_ state]
|
|
(let [objects (wsh/lookup-page-objects state)
|
|
ids
|
|
(->> shapes
|
|
(remove #(get % :blocked false))
|
|
(filter #(contains? (get editable-attrs (:type %)) :rotation))
|
|
(map :id))
|
|
|
|
get-modifier
|
|
(fn [shape]
|
|
(let [delta (if delta? angle (- angle (:rotation shape)))
|
|
center (or center (gsh/shape->center shape))]
|
|
(ctm/rotation-modifiers shape center delta)))
|
|
|
|
modif-tree
|
|
(-> (build-modif-tree ids objects get-modifier)
|
|
(gm/set-objects-modifiers objects))]
|
|
|
|
(assoc state :workspace-modifiers modif-tree)))))
|
|
|
|
(defn apply-modifiers
|
|
([]
|
|
(apply-modifiers nil))
|
|
|
|
([{:keys [modifiers undo-transation? stack-undo? ignore-constraints ignore-snap-pixel undo-group]
|
|
:or {undo-transation? true stack-undo? false ignore-constraints false ignore-snap-pixel false}}]
|
|
(ptk/reify ::apply-modifiers
|
|
ptk/WatchEvent
|
|
(watch [_ state _]
|
|
(let [text-modifiers (get state :workspace-text-modifier)
|
|
objects (wsh/lookup-page-objects state)
|
|
|
|
object-modifiers
|
|
(if (some? modifiers)
|
|
(calculate-modifiers state ignore-constraints ignore-snap-pixel modifiers)
|
|
(get state :workspace-modifiers))
|
|
|
|
ids
|
|
(into []
|
|
(remove #(= % uuid/zero))
|
|
(keys object-modifiers))
|
|
|
|
ids-with-children
|
|
(into ids
|
|
(mapcat (partial cfh/get-children-ids objects))
|
|
ids)
|
|
|
|
ignore-tree
|
|
(calculate-ignore-tree object-modifiers objects)
|
|
|
|
undo-id (js/Symbol)]
|
|
|
|
(rx/concat
|
|
(if undo-transation?
|
|
(rx/of (dwu/start-undo-transaction undo-id))
|
|
(rx/empty))
|
|
(rx/of (ptk/event ::dwg/move-frame-guides {:ids ids-with-children :modifiers object-modifiers})
|
|
(ptk/event ::dwcm/move-frame-comment-threads ids-with-children)
|
|
(dwsh/update-shapes
|
|
ids
|
|
(fn [shape]
|
|
(let [modif (get-in object-modifiers [(:id shape) :modifiers])
|
|
text-shape? (cfh/text-shape? shape)
|
|
position-data (when text-shape?
|
|
(dm/get-in text-modifiers [(:id shape) :position-data]))]
|
|
(-> shape
|
|
(gsh/transform-shape modif)
|
|
(cond-> (d/not-empty? position-data)
|
|
(assoc-position-data position-data shape))
|
|
(cond-> text-shape?
|
|
(update-grow-type shape)))))
|
|
{:reg-objects? true
|
|
:stack-undo? stack-undo?
|
|
:ignore-tree ignore-tree
|
|
:undo-group undo-group
|
|
;; Attributes that can change in the transform. This way we don't have to check
|
|
;; all the attributes
|
|
:attrs [:selrect
|
|
:points
|
|
:x
|
|
:y
|
|
:rx
|
|
:ry
|
|
:r1
|
|
:r2
|
|
:r3
|
|
:r4
|
|
:shadow
|
|
:blur
|
|
:strokes
|
|
:width
|
|
:height
|
|
:content
|
|
:transform
|
|
:transform-inverse
|
|
:rotation
|
|
:flip-x
|
|
:flip-y
|
|
:grow-type
|
|
:position-data
|
|
:layout-gap
|
|
:layout-padding
|
|
:layout-item-h-sizing
|
|
:layout-item-margin
|
|
:layout-item-max-h
|
|
:layout-item-max-w
|
|
:layout-item-min-h
|
|
:layout-item-min-w
|
|
:layout-item-v-sizing
|
|
:layout-padding-type
|
|
:layout-gap
|
|
:layout-item-margin
|
|
:layout-item-margin-type
|
|
:layout-grid-cells
|
|
:layout-grid-columns
|
|
:layout-grid-rows]})
|
|
;; We've applied the text-modifier so we can dissoc the temporary data
|
|
(fn [state]
|
|
(update state :workspace-text-modifier #(apply dissoc % ids))))
|
|
(if (nil? modifiers)
|
|
(rx/of (clear-local-transform))
|
|
(rx/empty))
|
|
(if undo-transation?
|
|
(rx/of (dwu/commit-undo-transaction undo-id))
|
|
(rx/empty))))))))
|