Add incomplete performance enhancements to shadow menu

It is imposible to make this commponent efficient because of
the design limitations of numeric-input component
This commit is contained in:
Andrey Antukh 2023-05-22 14:12:25 +02:00
parent 73ed37f57a
commit a09dd953ff
2 changed files with 202 additions and 190 deletions

View file

@ -42,7 +42,7 @@
;; We need to store the handle-blur ref so we can call it on unmount ;; We need to store the handle-blur ref so we can call it on unmount
handle-blur-ref (mf/use-ref nil) handle-blur-ref (mf/use-ref nil)
dirty-ref (mf/use-ref false) dirty-ref (mf/use-ref false)
;; This `value` represents the previous value and is used as ;; This `value` represents the previous value and is used as
;; initil value for the simple math expression evaluation. ;; initil value for the simple math expression evaluation.
@ -106,10 +106,11 @@
apply-value apply-value
(mf/use-callback (mf/use-callback
(mf/deps on-change update-input value) (mf/deps on-change update-input value)
(fn [new-value] (fn [new-value event]
(mf/set-ref-val! dirty-ref false) (mf/set-ref-val! dirty-ref false)
(when (and (not= new-value value) (some? on-change)) (when (and (not= new-value value)
(on-change new-value)) (fn? on-change))
(on-change new-value event))
(update-input new-value))) (update-input new-value)))
set-delta set-delta
@ -146,7 +147,7 @@
:else new-value)] :else new-value)]
(apply-value new-value)))))) (apply-value new-value event))))))
handle-key-down handle-key-down
(mf/use-callback (mf/use-callback
@ -180,12 +181,12 @@
handle-blur handle-blur
(mf/use-callback (mf/use-callback
(mf/deps parse-value apply-value update-input on-blur) (mf/deps parse-value apply-value update-input on-blur)
(fn [_] (fn [event]
(let [new-value (or (parse-value) default-val)] (let [new-value (or (parse-value) default-val)]
(if (or nillable new-value) (if (or nillable new-value)
(apply-value new-value) (apply-value new-value event)
(update-input new-value))) (update-input new-value)))
(when on-blur (on-blur)))) (when on-blur (on-blur event))))
on-click on-click
(mf/use-callback (mf/use-callback
@ -236,7 +237,7 @@
(events/listen globals/window EventType.CLICK on-click)]] (events/listen globals/window EventType.CLICK on-click)]]
#(doseq [key keys] #(doseq [key keys]
(events/unlistenByKey key))))) (events/unlistenByKey key)))))
(mf/use-layout-effect (mf/use-layout-effect
(mf/deps handle-mouse-wheel) (mf/deps handle-mouse-wheel)
(fn [] (fn []

View file

@ -8,6 +8,8 @@
(:require (:require
[app.common.colors :as clr] [app.common.colors :as clr]
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.math :as mth]
[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.colors :as dc] [app.main.data.workspace.colors :as dc]
@ -24,75 +26,85 @@
(def shadow-attrs [:shadow]) (def shadow-attrs [:shadow])
(defn create-shadow [] (defn- create-shadow
(let [id (uuid/next)] []
{:id id {:id (uuid/next)
:style :drop-shadow :style :drop-shadow
:color {:color clr/black :opacity 0.2} :color {:color clr/black
:offset-x 4 :opacity 0.2}
:offset-y 4 :offset-x 4
:blur 4 :offset-y 4
:spread 0 :blur 4
:hidden false})) :spread 0
:hidden false})
(defn valid-number? [value] (defn- remove-shadow-by-index
(or (number? value) (not (js/isNaN (js/parseInt value))))) [values index]
(->> (d/enumerate values)
(filterv (fn [[idx _]] (not= idx index)))
(mapv second)))
(mf/defc shadow-entry (mf/defc shadow-entry
[{:keys [ids index value on-reorder select-all disable-drag on-blur]}] [{:keys [ids index value on-reorder disable-drag? on-blur]}]
(let [open-shadow (mf/use-state false) (let [open-shadow (mf/use-state false)
basic-offset-x-ref (mf/use-ref nil) basic-offset-x-ref (mf/use-ref nil)
basic-offset-y-ref (mf/use-ref nil) basic-offset-y-ref (mf/use-ref nil)
basic-blur-ref (mf/use-ref nil) basic-blur-ref (mf/use-ref nil)
adv-offset-x-ref (mf/use-ref nil) adv-offset-x-ref (mf/use-ref nil)
adv-offset-y-ref (mf/use-ref nil) adv-offset-y-ref (mf/use-ref nil)
adv-blur-ref (mf/use-ref nil) adv-blur-ref (mf/use-ref nil)
adv-spread-ref (mf/use-ref nil) adv-spread-ref (mf/use-ref nil)
shadow-style (str (:style value)) shadow-style (dm/str (:style value))
remove-shadow-by-index
(fn [values index] (->> (d/enumerate values)
(filterv (fn [[idx _]] (not= idx index)))
(mapv second)))
on-remove-shadow on-remove-shadow
(fn [index] (mf/use-fn
(fn [] (mf/deps ids)
(st/emit! (dch/update-shapes ids #(update % :shadow remove-shadow-by-index index))))) (fn [event]
(let [index (-> (dom/get-current-target event)
select-text (dom/get-data "index")
(fn [ref] (fn [_] (dom/select-text! (mf/ref-val ref)))) (parse-long))]
(st/emit! (dch/update-shapes ids #(update % :shadow remove-shadow-by-index index))))))
on-drop on-drop
(fn [_ data] (mf/use-fn
(on-reorder (:index data))) (mf/deps on-reorder index)
(fn [_ data]
(on-reorder index (:index data))))
[dprops dref] (if (some? on-reorder) [dprops dref]
(h/use-sortable (h/use-sortable
:data-type "penpot/shadow-entry" :data-type "penpot/shadow-entry"
:on-drop on-drop :on-drop on-drop
:disabled @disable-drag :disabled disable-drag?
:detect-center? false :detect-center? false
:data {:id (str "shadow-" index) :data {:id (dm/str "shadow-" index)
:index index :index index
:name (str "Border row" index)}) :name (dm/str "Border row" index)})
[nil nil])
;; FIXME: this function causes the numeric-input rerender
;; ALWAYS, this is causes because numeric-input design makes
;; imposible implement efficiently any component that uses it;
;; it should be refactored
update-attr update-attr
(fn update-attr (fn update-attr
([index attr valid?] ([index attr]
(update-attr index attr valid? nil)) (update-attr index attr nil))
([index attr update-ref]
([index attr valid? update-ref]
(fn [value] (fn [value]
(when (or (not valid?) (valid? value)) (when (mth/finite? value)
(do (st/emit! (dch/update-shapes ids #(assoc-in % [:shadow index attr] value))) (st/emit! (dch/update-shapes ids #(assoc-in % [:shadow index attr] value)))
(let [update-node (and update-ref (mf/ref-val update-ref))] (when-let [update-node (and update-ref (mf/ref-val update-ref))]
(when update-node (dom/set-value! update-node value))))))
(dom/set-value! update-node value))))))))
;; FIXME: the same as previous function, imposible to
;; implement efficiently because of numeric-input component
;; and probably this affects all callbacks that that component
;; receives
select-text
(fn [ref] (fn [_] (dom/select-text! (mf/ref-val ref))))
update-color update-color
(fn [index] (fn [index]
@ -115,136 +127,135 @@
(fn [] (fn []
(st/emit! (dch/update-shapes ids #(update-in % [:shadow index :hidden] not)))))] (st/emit! (dch/update-shapes ids #(update-in % [:shadow index :hidden] not)))))]
[:* [:*
[:div.border-data {:class (dom/classnames [:div.border-data {:class (dom/classnames
:dnd-over-top (= (:over dprops) :top) :dnd-over-top (= (:over dprops) :top)
:dnd-over-bot (= (:over dprops) :bot)) :dnd-over-bot (= (:over dprops) :bot))
:ref dref} :ref dref}
[:div.element-set-options-group {:style {:display (when @open-shadow "none")}} [:div.element-set-options-group {:style {:display (when @open-shadow "none")}}
[:div.element-set-actions-button [:div.element-set-actions-button
{:on-click #(reset! open-shadow true)} {:on-click #(reset! open-shadow true)}
i/actions] i/actions]
;; [:> numeric-input {:ref basic-offset-x-ref [:select.input-select
;; :on-change (update-attr index :offset-x valid-number?) {:default-value shadow-style
;; :on-click (select-text basic-offset-x-ref) :on-change (fn [event]
;; :value (:offset-x value)}] (let [value (-> event dom/get-target dom/get-value d/read-string)]
;; [:> numeric-input {:ref basic-offset-y-ref (st/emit! (dch/update-shapes ids #(assoc-in % [:shadow index :style] value)))))}
;; :on-change (update-attr index :offset-y valid-number?) [:option {:value ":drop-shadow"
;; :on-click (select-text basic-offset-y-ref) :selected (when (= shadow-style ":drop-shadow") "selected")}
;; :value (:offset-y value)}] (tr "workspace.options.shadow-options.drop-shadow")]
;; [:> numeric-input {:ref basic-blur-ref [:option {:value ":inner-shadow"
;; :on-click (select-text basic-blur-ref) :selected (when (= shadow-style ":inner-shadow") "selected")}
;; :on-change (update-attr index :blur valid-number?) (tr "workspace.options.shadow-options.inner-shadow")]]
;; :min 0
;; :value (:blur value)}]
[:select.input-select [:div.element-set-actions
{:default-value shadow-style [:div.element-set-actions-button {:on-click (toggle-visibility index)}
:on-change (fn [event] (if (:hidden value) i/eye-closed i/eye)]
(let [value (-> event dom/get-target dom/get-value d/read-string)] [:div.element-set-actions-button
(st/emit! (dch/update-shapes ids #(assoc-in % [:shadow index :style] value)))))} {:data-index index
[:option {:value ":drop-shadow" :selected (when (= shadow-style ":drop-shadow") "selected")} (tr "workspace.options.shadow-options.drop-shadow")] :on-click on-remove-shadow}
[:option {:value ":inner-shadow" :selected (when (= shadow-style ":inner-shadow") "selected")} (tr "workspace.options.shadow-options.inner-shadow")]] i/minus]]]
[:div.element-set-actions [:& advanced-options {:visible? @open-shadow
[:div.element-set-actions-button {:on-click (toggle-visibility index)} :on-close #(reset! open-shadow false)}
(if (:hidden value) i/eye-closed i/eye)] [:div.color-data
[:div.element-set-actions-button {:on-click (on-remove-shadow index)} [:div.element-set-actions-button
i/minus]]] {:on-click #(reset! open-shadow false)}
i/actions]
[:select.input-select
{:default-value (str (:style value))
:on-change (fn [event]
(let [value (-> event dom/get-target dom/get-value d/read-string)]
(st/emit! (dch/update-shapes ids #(assoc-in % [:shadow index :style] value)))))}
[:option {:value ":drop-shadow"} (tr "workspace.options.shadow-options.drop-shadow")]
[:option {:value ":inner-shadow"} (tr "workspace.options.shadow-options.inner-shadow")]]]
[:& advanced-options {:visible? @open-shadow [:div.row-grid-2
:on-close #(reset! open-shadow false)} [:div.input-element {:title (tr "workspace.options.shadow-options.offsetx")}
[:div.color-data [:> numeric-input {:ref adv-offset-x-ref
[:div.element-set-actions-button :no-validate true
{:on-click #(reset! open-shadow false)} :placeholder "--"
i/actions] :on-focus (select-text adv-offset-x-ref)
[:select.input-select :on-change (update-attr index :offset-x basic-offset-x-ref)
{:default-value (str (:style value)) :on-blur on-blur
:on-change (fn [event] :value (:offset-x value)}]
(let [value (-> event dom/get-target dom/get-value d/read-string)] [:span.after (tr "workspace.options.shadow-options.offsetx")]]
(st/emit! (dch/update-shapes ids #(assoc-in % [:shadow index :style] value)))))}
[:option {:value ":drop-shadow"} (tr "workspace.options.shadow-options.drop-shadow")]
[:option {:value ":inner-shadow"} (tr "workspace.options.shadow-options.inner-shadow")]]]
[:div.row-grid-2 [:div.input-element {:title (tr "workspace.options.shadow-options.offsety")}
[:div.input-element {:title (tr "workspace.options.shadow-options.offsetx")} [:> numeric-input {:ref adv-offset-y-ref
[:> numeric-input {:ref adv-offset-x-ref :no-validate true
:no-validate true :placeholder "--"
:placeholder "--" :on-focus (select-text adv-offset-y-ref)
:on-focus (select-text adv-offset-x-ref) :on-change (update-attr index :offset-y basic-offset-y-ref)
:on-change (update-attr index :offset-x valid-number? basic-offset-x-ref) :on-blur on-blur
:on-blur on-blur :value (:offset-y value)}]
:value (:offset-x value)}] [:span.after (tr "workspace.options.shadow-options.offsety")]]]
[:span.after (tr "workspace.options.shadow-options.offsetx")]]
[:div.input-element {:title (tr "workspace.options.shadow-options.offsety")} [:div.row-grid-2
[:> numeric-input {:ref adv-offset-y-ref [:div.input-element {:title (tr "workspace.options.shadow-options.blur")}
:no-validate true [:> numeric-input {:ref adv-blur-ref
:placeholder "--" :no-validate true
:on-focus (select-text adv-offset-y-ref) :placeholder "--"
:on-change (update-attr index :offset-y valid-number? basic-offset-y-ref) :on-focus (select-text adv-blur-ref)
:on-blur on-blur :on-change (update-attr index :blur basic-blur-ref)
:value (:offset-y value)}] :on-blur on-blur
[:span.after (tr "workspace.options.shadow-options.offsety")]]] :min 0
:value (:blur value)}]
[:span.after (tr "workspace.options.shadow-options.blur")]]
[:div.row-grid-2 [:div.input-element {:title (tr "workspace.options.shadow-options.spread")}
[:div.input-element {:title (tr "workspace.options.shadow-options.blur")} [:> numeric-input {:ref adv-spread-ref
[:> numeric-input {:ref adv-blur-ref :no-validate true
:no-validate true :placeholder "--"
:placeholder "--" :on-focus (select-text adv-spread-ref)
:on-focus (select-text adv-blur-ref) :on-change (update-attr index :spread)
:on-change (update-attr index :blur valid-number? basic-blur-ref) :on-blur on-blur
:on-blur on-blur :value (:spread value)}]
:min 0 [:span.after (tr "workspace.options.shadow-options.spread")]]]
:value (:blur value)}]
[:span.after (tr "workspace.options.shadow-options.blur")]]
[:div.input-element {:title (tr "workspace.options.shadow-options.spread")} [:div.color-row-wrap
[:> numeric-input {:ref adv-spread-ref [:& color-row {:color (if (string? (:color value))
:no-validate true ;; Support for old format colors
:placeholder "--" {:color (:color value) :opacity (:opacity value)}
:on-focus (select-text adv-spread-ref) (:color value))
:on-change (update-attr index :spread valid-number?) :title (tr "workspace.options.shadow-options.color")
:on-blur on-blur :disable-gradient true
:value (:spread value)}] :on-change (update-color index)
[:span.after (tr "workspace.options.shadow-options.spread")]]] :on-detach (detach-color index)
:on-open #(st/emit! (dwu/start-undo-transaction :color-row))
[:div.color-row-wrap :on-close #(st/emit! (dwu/commit-undo-transaction :color-row))}]]]]]))
[:& color-row {:color (if (string? (:color value))
;; Support for old format colors
{:color (:color value) :opacity (:opacity value)}
(:color value))
:title (tr "workspace.options.shadow-options.color")
:disable-gradient true
:on-change (update-color index)
:on-detach (detach-color index)
:on-open #(st/emit! (dwu/start-undo-transaction :color-row))
:on-close #(st/emit! (dwu/commit-undo-transaction :color-row))}]]]]]))
(mf/defc shadow-menu (mf/defc shadow-menu
[{:keys [ids type values] :as props}] {::mf/wrap-props false}
(let [on-remove-all-shadows [props]
(fn [_] (st/emit! (dch/update-shapes ids #(dissoc % :shadow)))) (let [ids (unchecked-get props "ids")
type (unchecked-get props "type")
values (unchecked-get props "values")
shadows (:shadow values [])
disable-drag* (mf/use-state false)
disable-drag? (deref disable-drag*)
on-remove-all
(mf/use-fn
(mf/deps ids)
(fn []
(st/emit! (dch/update-shapes ids #(dissoc % :shadow)))))
handle-reorder handle-reorder
(mf/use-callback (mf/use-fn
(mf/deps ids) (mf/deps ids)
(fn [new-index] (fn [new-index index]
(fn [index] (st/emit! (dc/reorder-shadows ids index new-index))))
(st/emit! (dc/reorder-shadows ids index new-index)))))
disable-drag (mf/use-state false) on-blur
(mf/use-fn
select-all (fn [event] #(reset! disable-drag* false))
(when (not @disable-drag)
(dom/select-text! (dom/get-target event)))
(reset! disable-drag true))
on-blur (fn [_]
(reset! disable-drag false))
on-add-shadow on-add-shadow
(fn [_] (mf/use-fn
(st/emit! (dc/add-shadow ids (create-shadow))))] (mf/deps ids)
#(st/emit! (dc/add-shadow ids (create-shadow))))]
[:div.element-set.shadow-options [:div.element-set.shadow-options
[:div.element-set-title [:div.element-set-title
@ -254,27 +265,27 @@
:group (tr "workspace.options.shadow-options.title.group") :group (tr "workspace.options.shadow-options.title.group")
(tr "workspace.options.shadow-options.title"))] (tr "workspace.options.shadow-options.title"))]
(when-not (= :multiple (:shadow values)) (when-not (= :multiple shadows)
[:div.add-page {:on-click on-add-shadow} i/close])] [:div.add-page {:on-click on-add-shadow} i/close])]
(cond (cond
(= :multiple (:shadow values)) (= :multiple shadows)
[:div.element-set-content [:div.element-set-content
[:div.element-set-options-group [:div.element-set-options-group
[:div.element-set-label (tr "settings.multiple")] [:div.element-set-label (tr "settings.multiple")]
[:div.element-set-actions [:div.element-set-actions
[:div.element-set-actions-button {:on-click on-remove-all-shadows} [:div.element-set-actions-button {:on-click on-remove-all}
i/minus]]]] i/minus]]]]
(seq (:shadow values)) (seq shadows)
[:& h/sortable-container {} [:& h/sortable-container {}
[:div.element-set-content [:div.element-set-content
(for [[index value] (d/enumerate (:shadow values []))] (for [[index value] (d/enumerate shadows)]
[:& shadow-entry {:key (str "shadow-" index) [:& shadow-entry
:ids ids {:key (dm/str "shadow-" index)
:value value :ids ids
:on-reorder (handle-reorder index) :value value
:select-all select-all :on-reorder handle-reorder
:disable-drag disable-drag :disable-drag? disable-drag?
:on-blur on-blur :on-blur on-blur
:index index}])]])])) :index index}])]])]))