mirror of
https://github.com/penpot/penpot.git
synced 2025-04-09 13:31:23 -05:00
✨ Undo transactions to batch changes
This commit is contained in:
parent
a70e4eeb01
commit
0b5ae3632e
13 changed files with 198 additions and 106 deletions
frontend
|
@ -27,7 +27,7 @@
|
|||
overflow: auto;
|
||||
margin: 0.5rem;
|
||||
|
||||
&.selected {
|
||||
&.transaction {
|
||||
border: 2px solid $color-primary;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -166,14 +166,14 @@
|
|||
(assoc-in [:workspace-local :picked-shift?] shift?)))))
|
||||
|
||||
|
||||
(defn change-fill-selected [color id file-id]
|
||||
(ptk/reify ::change-fill-selected
|
||||
(defn change-fill [ids color id file-id]
|
||||
(ptk/reify ::change-fill
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(let [selected (get-in state [:workspace-local :selected])
|
||||
objects (get-in state [:workspace-data :pages-index (:current-page-id state) :objects])
|
||||
children (mapcat #(cph/get-children % objects) selected)
|
||||
ids (into selected children)
|
||||
(let [pid (:current-page-id state)
|
||||
objects (get-in state [:workspace-data :pages-index pid :objects])
|
||||
children (mapcat #(cph/get-children % objects) ids)
|
||||
ids (into ids children)
|
||||
|
||||
is-text? #(= :text (:type (get objects %)))
|
||||
text-ids (filter is-text? ids)
|
||||
|
@ -183,19 +183,22 @@
|
|||
:fill-color-ref-id id
|
||||
:fill-color-ref-file file-id))
|
||||
editor (get-in state [:workspace-local :editor])
|
||||
converted-attrs {:fill color}]
|
||||
converted-attrs {:fill color}
|
||||
|
||||
reduce-fn (fn [state id]
|
||||
(update-in state [:workspace-data :pages-index pid :objects id] update-fn))]
|
||||
|
||||
(rx/from (conj
|
||||
(map #(dwt/update-text-attrs {:id % :editor editor :attrs converted-attrs}) text-ids)
|
||||
(dwc/update-shapes shape-ids update-fn)))))))
|
||||
|
||||
(defn change-stroke-selected [color id file-id]
|
||||
(ptk/reify ::change-stroke-selected
|
||||
(defn change-stroke [ids color id file-id]
|
||||
(ptk/reify ::change-stroke
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(let [selected (get-in state [:workspace-local :selected])
|
||||
objects (get-in state [:workspace-data :pages-index (:current-page-id state) :objects])
|
||||
children (mapcat #(cph/get-children % objects) selected)
|
||||
ids (into selected children)
|
||||
(let [objects (get-in state [:workspace-data :pages-index (:current-page-id state) :objects])
|
||||
children (mapcat #(cph/get-children % objects) ids)
|
||||
ids (into ids children)
|
||||
|
||||
update-fn (fn [s]
|
||||
(cond-> s
|
||||
|
@ -211,12 +214,14 @@
|
|||
(rx/of (dwc/update-shapes ids update-fn))))))
|
||||
|
||||
(defn picker-for-selected-shape []
|
||||
;; TODO: replace st/emit! by a subject push and set that in the WatchEvent
|
||||
(let [handle-change-color (fn [color _ shift?]
|
||||
(st/emit!
|
||||
(if shift?
|
||||
(change-stroke-selected color nil nil)
|
||||
(change-fill-selected color nil nil))
|
||||
(md/hide-modal)))]
|
||||
(let [ids (get-in @st/state [:workspace-local :selected])]
|
||||
(st/emit!
|
||||
(if shift?
|
||||
(change-stroke ids color nil nil)
|
||||
(change-fill ids color nil nil))
|
||||
(md/hide-modal))))]
|
||||
(ptk/reify ::start-picker
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
|
@ -224,5 +229,5 @@
|
|||
(assoc-in [:workspace-local :picking-color?] true)
|
||||
(assoc ::md/modal {:id (random-uuid)
|
||||
:type :colorpicker
|
||||
:props {:on-change handle-change-color}
|
||||
:props {:on-close handle-change-color}
|
||||
:allow-click-outside true}))))))
|
||||
|
|
|
@ -1456,6 +1456,7 @@
|
|||
:option :background
|
||||
:value color}]
|
||||
[{:type :set-option
|
||||
:page-id page-id
|
||||
:option :background
|
||||
:value ccolor}]
|
||||
{:commit-local? true}))))))
|
||||
|
|
|
@ -265,14 +265,49 @@
|
|||
(update :workspace-undo dissoc :undo-index)
|
||||
(update-in [:workspace-undo :items] (fn [queue] (into [] (take (inc index) queue))))))))
|
||||
|
||||
(defn- add-undo-entry [state entry]
|
||||
(if entry
|
||||
(let [state (update-in state [:workspace-undo :items] (fnil conj-undo-entry []) entry)]
|
||||
(assoc-in state [:workspace-undo :index] (dec (count (get-in state [:workspace-undo :items])))))
|
||||
state))
|
||||
|
||||
(defn- accumulate-undo-entry [state {:keys [undo-changes redo-changes]}]
|
||||
(-> state
|
||||
(update-in [:workspace-undo :transaction :undo-changes] #(into undo-changes %))
|
||||
(update-in [:workspace-undo :transaction :redo-changes] #(into % redo-changes))))
|
||||
|
||||
(defn- append-undo
|
||||
[entry]
|
||||
(us/verify ::undo-entry entry)
|
||||
(ptk/reify ::append-undo
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [state (update-in state [:workspace-undo :items] (fnil conj-undo-entry []) entry)]
|
||||
(assoc-in state [:workspace-undo :index] (dec (count (get-in state [:workspace-undo :items]))))))))
|
||||
(if (get-in state [:workspace-undo :transaction])
|
||||
(accumulate-undo-entry state entry)
|
||||
(add-undo-entry state entry)))))
|
||||
|
||||
(def start-undo-transaction
|
||||
(ptk/reify ::start-undo-transaction
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
;; We commit the old transaction before starting the new one
|
||||
(-> state
|
||||
(add-undo-entry (get-in state [:workspace-undo :transaction]))
|
||||
(assoc-in [:workspace-undo :transaction] {:undo-changes []
|
||||
:redo-changes []})))))
|
||||
(def discard-undo-transaction
|
||||
(ptk/reify ::discard-undo-transaction
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :workspace-undo dissoc :transaction))))
|
||||
|
||||
(def commit-undo-transaction
|
||||
(ptk/reify ::commit-undo-transaction
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(add-undo-entry (get-in state [:workspace-undo :transaction]))
|
||||
(update :workspace-undo dissoc :transaction)))))
|
||||
|
||||
(def undo
|
||||
(ptk/reify ::undo
|
||||
|
|
|
@ -427,5 +427,7 @@
|
|||
uchanges (conj (dwc/generate-changes page-id objects2 objects0) regchg)
|
||||
]
|
||||
|
||||
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})
|
||||
(dwc/rehash-shape-frame-relationship ids))))))
|
||||
(rx/of dwc/start-undo-transaction
|
||||
(dwc/commit-changes rchanges uchanges {:commit-local? true})
|
||||
(dwc/rehash-shape-frame-relationship ids)
|
||||
dwc/commit-undo-transaction)))))
|
||||
|
|
|
@ -369,30 +369,41 @@
|
|||
(t locale "workspace.libraries.colors.save-color")]])])
|
||||
)
|
||||
|
||||
(defn calculate-position
|
||||
"Calculates the style properties for the given coordinates and position"
|
||||
[position x y]
|
||||
(cond
|
||||
(or (nil? x) (nil? y)) {:left "auto" :right "16rem" :top "4rem"}
|
||||
(= position :left) {:left (str (- x 270) "px") :top (str (- y 50) "px")}
|
||||
:else {:left (str (+ x 24) "px") :top (str (- y 50) "px")}))
|
||||
|
||||
|
||||
(mf/defc colorpicker-modal
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :colorpicker}
|
||||
[{:keys [x y default value opacity page on-change disable-opacity position on-accept] :as props}]
|
||||
(let [position (or position :left)
|
||||
style (cond
|
||||
(or (nil? x) (nil? y))
|
||||
{:left "auto"
|
||||
:right "16rem"
|
||||
:top "4rem"}
|
||||
[{:keys [x y default value opacity page on-change on-close disable-opacity position on-accept] :as props}]
|
||||
(let [dirty? (mf/use-var false)
|
||||
last-change (mf/use-var nil)
|
||||
position (or position :left)
|
||||
style (calculate-position position x y)
|
||||
|
||||
(= position :left)
|
||||
{:left (str (- x 270) "px")
|
||||
:top (str (- y 50) "px")}
|
||||
handle-change (fn [new-value new-opacity op1 op2]
|
||||
(when (or (not= new-value value) (not= new-opacity opacity))
|
||||
(reset! dirty? true))
|
||||
(reset! last-change [new-value new-opacity op1 op2])
|
||||
(on-change new-value new-opacity op1 op2))]
|
||||
|
||||
(mf/use-effect
|
||||
(fn []
|
||||
#(when (and @dirty? on-close)
|
||||
(when-let [[value opacity op1 op2] @last-change]
|
||||
(on-close value opacity op1 op2)))))
|
||||
|
||||
:else
|
||||
{:left (str (+ x 24) "px")
|
||||
:top (str (- y 50) "px")})
|
||||
]
|
||||
[:div.colorpicker-tooltip
|
||||
{:style (clj->js style)}
|
||||
[:& colorpicker {:value (or value default)
|
||||
:opacity (or opacity 1)
|
||||
:on-change on-change
|
||||
:on-change handle-change
|
||||
:on-accept on-accept
|
||||
:disable-opacity disable-opacity}]]))
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
[rumext.alpha :as mf]
|
||||
[app.common.data :as d]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.common :as dwc]
|
||||
[app.main.data.workspace.texts :as dwt]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
|
@ -273,9 +274,12 @@
|
|||
(fn []
|
||||
(let [lkey1 (events/listen js/document EventType.CLICK on-click)
|
||||
lkey2 (events/listen js/document EventType.KEYUP on-key-up)]
|
||||
(st/emit! (dwt/assign-editor id editor))
|
||||
(st/emit! (dwt/assign-editor id editor)
|
||||
dwc/start-undo-transaction)
|
||||
|
||||
#(do
|
||||
(st/emit! (dwt/assign-editor id nil))
|
||||
(st/emit! (dwt/assign-editor id nil)
|
||||
dwc/commit-undo-transaction)
|
||||
(events/unlistenByKey lkey1)
|
||||
(events/unlistenByKey lkey2))))
|
||||
|
||||
|
|
|
@ -136,12 +136,12 @@
|
|||
:top nil
|
||||
:left nil
|
||||
:editing rename?})
|
||||
|
||||
click-color
|
||||
(fn [event]
|
||||
(if (kbd/shift? event)
|
||||
(st/emit! (dc/change-stroke-selected (:value color) id (if local? nil file-id)))
|
||||
(st/emit! (dc/change-fill-selected (:value color) id (if local? nil file-id)))))
|
||||
(let [ids (get-in @st/state [:workspace-local :selected])]
|
||||
(if (kbd/shift? event)
|
||||
(st/emit! (dc/change-stroke ids (:value color) id (if local? nil file-id)))
|
||||
(st/emit! (dc/change-fill ids (:value color) id (if local? nil file-id))))))
|
||||
|
||||
rename-color
|
||||
(fn [name]
|
||||
|
|
|
@ -27,24 +27,40 @@
|
|||
(def workspace-undo
|
||||
(l/derived :workspace-undo st/state))
|
||||
|
||||
(mf/defc undo-entry [{:keys [index entry objects is-transaction?] :or {is-transaction? false}}]
|
||||
(let [{:keys [redo-changes]} entry]
|
||||
[:li.undo-entry {:class (when is-transaction? "transaction")}
|
||||
(for [[idx-change {:keys [type id operations]}] (map-indexed vector redo-changes)]
|
||||
[:div.undo-entry-change
|
||||
[:div.undo-entry-change-data (when type (str type)) " " (when id (str (get-in objects [id :name] (subs (str id) 0 8))))]
|
||||
(when operations
|
||||
[:div.undo-entry-change-data (str/join ", " (map (comp name :attr) operations))])])]))
|
||||
|
||||
(mf/defc history-toolbox []
|
||||
(let [locale (mf/deref i18n/locale)
|
||||
{:keys [items index]} (mf/deref workspace-undo)
|
||||
{:keys [items index transaction]} (mf/deref workspace-undo)
|
||||
objects (mf/deref refs/workspace-page-objects)]
|
||||
[:div.history-toolbox
|
||||
[:div.history-toolbox-title "History"]
|
||||
(when (> (count items) 0)
|
||||
[:ul.undo-history
|
||||
[:*
|
||||
(when (or (nil? index) (>= index (count items))) [:hr.separator])
|
||||
(for [[idx-entry {:keys [redo-changes]}] (->> items (map-indexed vector) reverse)]
|
||||
[:*
|
||||
(when (= index idx-entry) [:hr.separator {:data-index index}])
|
||||
[:li.undo-entry {:key (str "entry-" idx-entry)}
|
||||
(for [[idx-change {:keys [type id operations]}] (map-indexed vector redo-changes)]
|
||||
[:div.undo-entry-change
|
||||
[:div.undo-entry-change-data (when type (str type)) " " (when id (str (get-in objects [id :name] (subs (str id) 0 8))))]
|
||||
(when operations
|
||||
[:div.undo-entry-change-data (str/join ", " (map (comp name :attr) operations))])])]])
|
||||
(when (= index -1) [:hr.separator])]])]))
|
||||
[:ul.undo-history
|
||||
[:*
|
||||
(when (and
|
||||
(> (count items) 0)
|
||||
(or (nil? index)
|
||||
(>= index (count items))))
|
||||
[:hr.separator])
|
||||
|
||||
(when transaction
|
||||
[:& undo-entry {:key (str "transaction")
|
||||
:objects objects
|
||||
:is-transaction? true
|
||||
:entry transaction}])
|
||||
|
||||
(for [[idx-entry entry] (->> items (map-indexed vector) reverse)]
|
||||
[:*
|
||||
(when (= index idx-entry) [:hr.separator {:data-index index}])
|
||||
[:& undo-entry {:key (str "entry-" idx-entry)
|
||||
:objects objects
|
||||
:entry entry}]])
|
||||
(when (= index -1) [:hr.separator])]]]))
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
[app.common.pages :as cp]
|
||||
[app.main.data.workspace.common :as dwc]
|
||||
[app.main.data.workspace.texts :as dwt]
|
||||
[app.main.data.colors :as dc]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.icons :as i]
|
||||
|
@ -37,14 +38,11 @@
|
|||
(= (:fill-opacity new-values)
|
||||
(:fill-opacity old-values)))))
|
||||
|
||||
|
||||
(mf/defc fill-menu
|
||||
{::mf/wrap [#(mf/memo' % fill-menu-props-equals?)]}
|
||||
[{:keys [ids type values editor] :as props}]
|
||||
(let [locale (mf/deref i18n/locale)
|
||||
shapes (deref (refs/objects-by-id ids))
|
||||
is-text? #(= (:type %) :text)
|
||||
text-ids (map :id (filter is-text? shapes))
|
||||
other-ids (map :id (filter (comp not is-text?) shapes))
|
||||
show? (not (nil? (:fill-color values)))
|
||||
|
||||
label (case type
|
||||
|
@ -61,51 +59,32 @@
|
|||
(mf/use-callback
|
||||
(mf/deps ids)
|
||||
(fn [event]
|
||||
(when-not (empty? other-ids)
|
||||
(st/emit! (dwc/update-shapes other-ids #(assoc % :fill-color cp/default-color))))
|
||||
|
||||
(when-not (empty? text-ids)
|
||||
(run! #(st/emit! (dwt/update-text-attrs
|
||||
{:id %
|
||||
:editor editor
|
||||
:attrs {:fill cp/default-color}}))
|
||||
text-ids))))
|
||||
(st/emit! (dc/change-fill ids cp/default-color nil nil))))
|
||||
|
||||
on-delete
|
||||
(mf/use-callback
|
||||
(mf/deps ids)
|
||||
(fn [event]
|
||||
(when-not (empty? other-ids)
|
||||
(st/emit! (dwc/update-shapes other-ids #(dissoc % :fill-color))))
|
||||
|
||||
(when-not (empty? text-ids)
|
||||
(run! #(st/emit! (dwt/update-text-attrs
|
||||
{:id %
|
||||
:editor editor
|
||||
:attrs {:fill nil}}))
|
||||
text-ids))))
|
||||
(st/emit! (dc/change-fill ids nil nil nil))))
|
||||
|
||||
on-change
|
||||
(mf/use-callback
|
||||
(mf/deps ids)
|
||||
(fn [value opacity id file-id]
|
||||
(let [change #(cond-> %
|
||||
value (assoc :fill-color value
|
||||
:fill-color-ref-id id
|
||||
:fill-color-ref-file file-id)
|
||||
opacity (assoc :fill-opacity opacity))
|
||||
converted-attrs (cond-> {}
|
||||
value (assoc :fill value)
|
||||
opacity (assoc :opacity opacity))]
|
||||
(st/emit! (dc/change-fill ids value id file-id))))
|
||||
|
||||
on-open-picker
|
||||
(mf/use-callback
|
||||
(mf/deps ids)
|
||||
(fn [value opacity id file-id]
|
||||
(st/emit! dwc/start-undo-transaction)))
|
||||
|
||||
on-close-picker
|
||||
(mf/use-callback
|
||||
(mf/deps ids)
|
||||
(fn [value opacity id file-id]
|
||||
(st/emit! dwc/commit-undo-transaction)))]
|
||||
|
||||
(when-not (empty? other-ids)
|
||||
(st/emit! (dwc/update-shapes ids change)))
|
||||
(when-not (empty? text-ids)
|
||||
(run! #(st/emit! (dwt/update-text-attrs
|
||||
{:id %
|
||||
:editor editor
|
||||
:attrs converted-attrs}))
|
||||
text-ids)))))]
|
||||
(if show?
|
||||
[:div.element-set
|
||||
[:div.element-set-title
|
||||
|
@ -113,10 +92,14 @@
|
|||
[:div.add-page {:on-click on-delete} i/minus]]
|
||||
|
||||
[:div.element-set-content
|
||||
[:& color-row {:color color :on-change on-change}]]]
|
||||
[:& color-row {:color color
|
||||
:on-change on-change
|
||||
:on-open on-open-picker
|
||||
:on-close on-close-picker}]]]
|
||||
|
||||
[:div.element-set
|
||||
[:div.element-set-title
|
||||
[:span label]
|
||||
[:div.add-page {:on-click on-add} i/close]]])))
|
||||
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.common :as dwc]
|
||||
[app.util.i18n :as i18n :refer [t]]
|
||||
[app.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row]]))
|
||||
|
||||
|
@ -28,12 +29,25 @@
|
|||
[{:keys [page-id] :as props}]
|
||||
(let [locale (i18n/use-locale)
|
||||
options (mf/deref refs/workspace-page-options)
|
||||
handle-change-color (use-change-color page-id)]
|
||||
handle-change-color (use-change-color page-id)
|
||||
|
||||
on-open
|
||||
(mf/use-callback
|
||||
(mf/deps page-id)
|
||||
#(st/emit! dwc/start-undo-transaction))
|
||||
|
||||
on-close
|
||||
(mf/use-callback
|
||||
(mf/deps page-id)
|
||||
#(st/emit! dwc/commit-undo-transaction))]
|
||||
|
||||
[:div.element-set
|
||||
[:div.element-set-title (t locale "workspace.options.canvas-background")]
|
||||
[:div.element-set-content
|
||||
[:& color-row {:disable-opacity true
|
||||
:color {:value (get options :background "#E8E9EA")
|
||||
:opacity 1}
|
||||
:on-change handle-change-color}]]]))
|
||||
:on-change handle-change-color
|
||||
:on-open on-open
|
||||
:on-close on-close}]]]))
|
||||
|
||||
|
|
|
@ -19,16 +19,18 @@
|
|||
[app.main.refs :as refs]))
|
||||
|
||||
(defn color-picker-callback
|
||||
[color handle-change-color disable-opacity]
|
||||
[color handle-change-color handle-open handle-close disable-opacity]
|
||||
(fn [event]
|
||||
(let [x (.-clientX event)
|
||||
y (.-clientY event)
|
||||
props {:x x
|
||||
:y y
|
||||
:on-change handle-change-color
|
||||
:on-close handle-close
|
||||
:value (:value color)
|
||||
:opacity (:opacity color)
|
||||
:disable-opacity disable-opacity}]
|
||||
(handle-open)
|
||||
(modal/show! :colorpicker props))))
|
||||
|
||||
(defn value-to-background [value]
|
||||
|
@ -57,7 +59,7 @@
|
|||
(if (= v :multiple) nil v))
|
||||
|
||||
(mf/defc color-row
|
||||
[{:keys [color on-change disable-opacity]}]
|
||||
[{:keys [color on-change on-open on-close disable-opacity]}]
|
||||
(let [;;
|
||||
file-colors (mf/deref refs/workspace-file-colors)
|
||||
shared-libs (mf/deref refs/workspace-libraries)
|
||||
|
@ -91,6 +93,11 @@
|
|||
(reset! state {:value new-value :opacity new-opacity})
|
||||
(when on-change (on-change new-value new-opacity id file-id)))
|
||||
|
||||
handle-open (fn [] (when on-open (on-open)))
|
||||
|
||||
handle-close (fn [value opacity id file-id]
|
||||
(when on-close (on-close value opacity id file-id)))
|
||||
|
||||
handle-value-change (fn [event]
|
||||
(let [target (dom/get-target event)]
|
||||
(when (dom/valid? target)
|
||||
|
@ -119,7 +126,7 @@
|
|||
[:span.color-th
|
||||
{:class (when (:id color) "color-name")
|
||||
:style {:background-color (-> value value-to-background)}
|
||||
:on-click (color-picker-callback @state handle-pick-color disable-opacity)}
|
||||
:on-click (color-picker-callback @state handle-pick-color handle-open handle-close disable-opacity)}
|
||||
(when (= value :multiple) "?")]
|
||||
|
||||
(if (:id color)
|
||||
|
|
|
@ -119,7 +119,19 @@
|
|||
|
||||
on-del-stroke
|
||||
(fn [event]
|
||||
(st/emit! (dwc/update-shapes ids #(assoc % :stroke-style :none))))]
|
||||
(st/emit! (dwc/update-shapes ids #(assoc % :stroke-style :none))))
|
||||
|
||||
on-open-picker
|
||||
(mf/use-callback
|
||||
(mf/deps ids)
|
||||
(fn [value opacity id file-id]
|
||||
(st/emit! dwc/start-undo-transaction)))
|
||||
|
||||
on-close-picker
|
||||
(mf/use-callback
|
||||
(mf/deps ids)
|
||||
(fn [value opacity id file-id]
|
||||
(st/emit! dwc/commit-undo-transaction)))]
|
||||
|
||||
(if show-options
|
||||
[:div.element-set
|
||||
|
@ -130,7 +142,9 @@
|
|||
[:div.element-set-content
|
||||
;; Stroke Color
|
||||
[:& color-row {:color current-stroke-color
|
||||
:on-change handle-change-stroke-color}]
|
||||
:on-change handle-change-stroke-color
|
||||
:on-open on-open-picker
|
||||
:on-close on-close-picker}]
|
||||
|
||||
;; Stroke Width, Alignment & Style
|
||||
[:div.row-flex
|
||||
|
|
Loading…
Add table
Reference in a new issue