From 1eb6e30369e8d34e2df72767f62a1754d6f436a7 Mon Sep 17 00:00:00 2001 From: Akshay Gupta Date: Wed, 17 May 2023 23:22:38 +0530 Subject: [PATCH 1/5] :tada: Add ability to change shadows order and place new shadows at top by default Signed-off-by: Akshay Gupta --- .../src/app/main/data/workspace/colors.cljs | 19 ++ .../sidebar/options/menus/shadow.cljs | 242 +++++++++++------- 2 files changed, 164 insertions(+), 97 deletions(-) diff --git a/frontend/src/app/main/data/workspace/colors.cljs b/frontend/src/app/main/data/workspace/colors.cljs index 04abaad50..6c54bae46 100644 --- a/frontend/src/app/main/data/workspace/colors.cljs +++ b/frontend/src/app/main/data/workspace/colors.cljs @@ -255,6 +255,16 @@ new-attrs (merge (get-in shape [:shadow index :color]) attrs)] (assoc-in shape [:shadow index :color] new-attrs)))))))) +(defn add-shadow + [ids shadow] + (ptk/reify ::add-shadow + ptk/WatchEvent + (watch [_ _ _] + (let [add (fn [shape attrs] (assoc shape :shadow (into [attrs] (:shadow shape))))] + (rx/of (dch/update-shapes + ids + #(add % shadow))))))) + (defn add-stroke [ids stroke] (ptk/reify ::add-stroke @@ -289,6 +299,15 @@ ids #(remove-all %))))))) +(defn reorder-shadows + [ids index new-index] + (ptk/reify ::reorder-shadow + ptk/WatchEvent + (watch [_ _ _] + (rx/of (dch/update-shapes + ids + #(swap-attrs % :shadow index new-index)))))) + (defn reorder-strokes [ids index new-index] (ptk/reify ::reorder-strokes diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs index b518d9763..3a122a53f 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs @@ -10,9 +10,11 @@ [app.common.data :as d] [app.common.uuid :as uuid] [app.main.data.workspace.changes :as dch] + [app.main.data.workspace.colors :as dc] [app.main.data.workspace.undo :as dwu] [app.main.store :as st] [app.main.ui.components.numeric-input :refer [numeric-input]] + [app.main.ui.hooks :as h] [app.main.ui.icons :as i] [app.main.ui.workspace.sidebar.options.common :refer [advanced-options]] [app.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row]] @@ -37,7 +39,7 @@ (or (number? value) (not (js/isNaN (js/parseInt value))))) (mf/defc shadow-entry - [{:keys [ids index value]}] + [{:keys [ids index value on-reorder select-all disable-drag on-blur]}] (let [open-shadow (mf/use-state false) basic-offset-x-ref (mf/use-ref nil) @@ -64,6 +66,21 @@ select-text (fn [ref] (fn [_] (dom/select-text! (mf/ref-val ref)))) + on-drop + (fn [_ data] + (on-reorder (:index data))) + + [dprops dref] (if (some? on-reorder) + (h/use-sortable + :data-type "penpot/shadow-entry" + :on-drop on-drop + :disabled @disable-drag + :detect-center? false + :data {:id (str "shadow-" index) + :index index + :name (str "Border row" index)}) + [nil nil]) + update-attr (fn update-attr ([index attr valid?] @@ -98,111 +115,137 @@ (fn [] (st/emit! (dch/update-shapes ids #(update-in % [:shadow index :hidden] not)))))] [:* - [:div.element-set-options-group {:style {:display (when @open-shadow "none")}} - [:div.element-set-actions-button - {:on-click #(reset! open-shadow true)} - i/actions] + [:div.border-data {:class (dom/classnames + :dnd-over-top (= (:over dprops) :top) + :dnd-over-bot (= (:over dprops) :bot)) + :ref dref} + [:div.element-set-options-group {:style {:display (when @open-shadow "none")}} + [:div.element-set-actions-button + {:on-click #(reset! open-shadow true)} + i/actions] - ;; [:> numeric-input {:ref basic-offset-x-ref - ;; :on-change (update-attr index :offset-x valid-number?) - ;; :on-click (select-text basic-offset-x-ref) - ;; :value (:offset-x value)}] - ;; [:> numeric-input {:ref basic-offset-y-ref - ;; :on-change (update-attr index :offset-y valid-number?) - ;; :on-click (select-text basic-offset-y-ref) - ;; :value (:offset-y value)}] - ;; [:> numeric-input {:ref basic-blur-ref - ;; :on-click (select-text basic-blur-ref) - ;; :on-change (update-attr index :blur valid-number?) - ;; :min 0 - ;; :value (:blur value)}] + ;; [:> numeric-input {:ref basic-offset-x-ref + ;; :on-change (update-attr index :offset-x valid-number?) + ;; :on-click (select-text basic-offset-x-ref) + ;; :value (:offset-x value)}] + ;; [:> numeric-input {:ref basic-offset-y-ref + ;; :on-change (update-attr index :offset-y valid-number?) + ;; :on-click (select-text basic-offset-y-ref) + ;; :value (:offset-y value)}] + ;; [:> numeric-input {:ref basic-blur-ref + ;; :on-click (select-text basic-blur-ref) + ;; :on-change (update-attr index :blur valid-number?) + ;; :min 0 + ;; :value (:blur value)}] - [:select.input-select - {:default-value shadow-style - :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" :selected (when (= shadow-style ":drop-shadow") "selected")} (tr "workspace.options.shadow-options.drop-shadow")] - [:option {:value ":inner-shadow" :selected (when (= shadow-style ":inner-shadow") "selected")} (tr "workspace.options.shadow-options.inner-shadow")]] + [:select.input-select + {:default-value shadow-style + :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" :selected (when (= shadow-style ":drop-shadow") "selected")} (tr "workspace.options.shadow-options.drop-shadow")] + [:option {:value ":inner-shadow" :selected (when (= shadow-style ":inner-shadow") "selected")} (tr "workspace.options.shadow-options.inner-shadow")]] - [:div.element-set-actions - [:div.element-set-actions-button {:on-click (toggle-visibility index)} - (if (:hidden value) i/eye-closed i/eye)] - [:div.element-set-actions-button {:on-click (on-remove-shadow index)} - i/minus]]] + [:div.element-set-actions + [:div.element-set-actions-button {:on-click (toggle-visibility index)} + (if (:hidden value) i/eye-closed i/eye)] + [:div.element-set-actions-button {:on-click (on-remove-shadow index)} + i/minus]]] - [:& advanced-options {:visible? @open-shadow - :on-close #(reset! open-shadow false)} - [:div.color-data - [:div.element-set-actions-button - {: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 + :on-close #(reset! open-shadow false)} + [:div.color-data + [:div.element-set-actions-button + {: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")]]] - [:div.row-grid-2 - [:div.input-element {:title (tr "workspace.options.shadow-options.offsetx")} - [:> numeric-input {:ref adv-offset-x-ref - :no-validate true - :placeholder "--" - :on-focus (select-text adv-offset-x-ref) - :on-change (update-attr index :offset-x valid-number? basic-offset-x-ref) - :value (:offset-x value)}] - [:span.after (tr "workspace.options.shadow-options.offsetx")]] + [:div.row-grid-2 + [:div.input-element {:title (tr "workspace.options.shadow-options.offsetx")} + [:> numeric-input {:ref adv-offset-x-ref + :no-validate true + :placeholder "--" + :on-focus (select-text adv-offset-x-ref) + :on-change (update-attr index :offset-x valid-number? basic-offset-x-ref) + :on-blur on-blur + :value (:offset-x value)}] + [:span.after (tr "workspace.options.shadow-options.offsetx")]] - [:div.input-element {:title (tr "workspace.options.shadow-options.offsety")} - [:> numeric-input {:ref adv-offset-y-ref - :no-validate true - :placeholder "--" - :on-focus (select-text adv-offset-y-ref) - :on-change (update-attr index :offset-y valid-number? basic-offset-y-ref) - :value (:offset-y value)}] - [:span.after (tr "workspace.options.shadow-options.offsety")]]] + [:div.input-element {:title (tr "workspace.options.shadow-options.offsety")} + [:> numeric-input {:ref adv-offset-y-ref + :no-validate true + :placeholder "--" + :on-focus (select-text adv-offset-y-ref) + :on-change (update-attr index :offset-y valid-number? basic-offset-y-ref) + :on-blur on-blur + :value (:offset-y value)}] + [:span.after (tr "workspace.options.shadow-options.offsety")]]] - [:div.row-grid-2 - [:div.input-element {:title (tr "workspace.options.shadow-options.blur")} - [:> numeric-input {:ref adv-blur-ref - :no-validate true - :placeholder "--" - :on-focus (select-text adv-blur-ref) - :on-change (update-attr index :blur valid-number? basic-blur-ref) - :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.blur")} + [:> numeric-input {:ref adv-blur-ref + :no-validate true + :placeholder "--" + :on-focus (select-text adv-blur-ref) + :on-change (update-attr index :blur valid-number? basic-blur-ref) + :on-blur on-blur + :min 0 + :value (:blur value)}] + [:span.after (tr "workspace.options.shadow-options.blur")]] - [:div.input-element {:title (tr "workspace.options.shadow-options.spread")} - [:> numeric-input {:ref adv-spread-ref - :no-validate true - :placeholder "--" - :on-focus (select-text adv-spread-ref) - :on-change (update-attr index :spread valid-number?) - :value (:spread value)}] - [:span.after (tr "workspace.options.shadow-options.spread")]]] + [:div.input-element {:title (tr "workspace.options.shadow-options.spread")} + [:> numeric-input {:ref adv-spread-ref + :no-validate true + :placeholder "--" + :on-focus (select-text adv-spread-ref) + :on-change (update-attr index :spread valid-number?) + :on-blur on-blur + :value (:spread value)}] + [:span.after (tr "workspace.options.shadow-options.spread")]]] - [:div.color-row-wrap - [:& 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))}]]]])) + [:div.color-row-wrap + [:& 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 [{:keys [ids type values] :as props}] (let [on-remove-all-shadows (fn [_] (st/emit! (dch/update-shapes ids #(dissoc % :shadow)))) + handle-reorder + (mf/use-callback + (mf/deps ids) + (fn [new-index] + (fn [index] + (st/emit! (dc/reorder-shadows ids index new-index))))) + + disable-drag (mf/use-state false) + + select-all (fn [event] + (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 - (fn [] - (st/emit! (dch/update-shapes ids #(update % :shadow (fnil conj []) (create-shadow)) )))] + (fn [_] + (st/emit! (dc/add-shadow ids (create-shadow))))] + [:div.element-set.shadow-options [:div.element-set-title [:span @@ -224,9 +267,14 @@ i/minus]]]] (seq (:shadow values)) - [:div.element-set-content - (for [[index value] (d/enumerate (:shadow values []))] - [:& shadow-entry {:key (str "shadow-" index) - :ids ids - :value value - :index index}])])])) + [:& h/sortable-container {} + [:div.element-set-content + (for [[index value] (d/enumerate (:shadow values []))] + [:& shadow-entry {:key (str "shadow-" index) + :ids ids + :value value + :on-reorder (handle-reorder index) + :select-all select-all + :disable-drag disable-drag + :on-blur on-blur + :index index}])]])])) From 98a6c63ad6afdfbb07769d808a2d72c5c159f6ba Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 22 May 2023 13:01:34 +0200 Subject: [PATCH 2/5] :lipstick: Add cosmetic changes to shadow-add and reorder-shadow fns --- .../src/app/main/data/workspace/colors.cljs | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/frontend/src/app/main/data/workspace/colors.cljs b/frontend/src/app/main/data/workspace/colors.cljs index 6c54bae46..8dd6a5018 100644 --- a/frontend/src/app/main/data/workspace/colors.cljs +++ b/frontend/src/app/main/data/workspace/colors.cljs @@ -10,6 +10,7 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.pages.helpers :as cph] + [app.common.schema :as sm] [app.main.broadcast :as mbc] [app.main.data.modal :as md] [app.main.data.workspace.changes :as dch] @@ -248,22 +249,27 @@ (ptk/reify ::change-shadow ptk/WatchEvent (watch [_ _ _] - (rx/of (dch/update-shapes ids (fn [shape] - (let [;; If we try to set a gradient to a shadow (for example using the color selection from multiple shapes) let's use the first stop color - attrs (cond-> attrs - (:gradient attrs) (get-in [:gradient :stops 0])) - new-attrs (merge (get-in shape [:shadow index :color]) attrs)] - (assoc-in shape [:shadow index :color] new-attrs)))))))) + (rx/of (dch/update-shapes + ids + (fn [shape] + (let [;; If we try to set a gradient to a shadow (for + ;; example using the color selection from + ;; multiple shapes) let's use the first stop + ;; color + attrs (cond-> attrs + (:gradient attrs) (get-in [:gradient :stops 0])) + new-attrs (merge (get-in shape [:shadow index :color]) attrs)] + (assoc-in shape [:shadow index :color] new-attrs)))))))) (defn add-shadow [ids shadow] + (dm/assert! (sm/coll-of-uuid? ids)) (ptk/reify ::add-shadow ptk/WatchEvent (watch [_ _ _] - (let [add (fn [shape attrs] (assoc shape :shadow (into [attrs] (:shadow shape))))] - (rx/of (dch/update-shapes - ids - #(add % shadow))))))) + (let [add-shadow (fn [shape] + (update shape :shadow #(into [shadow] %)))] + (rx/of (dch/update-shapes ids add-shadow)))))) (defn add-stroke [ids stroke] @@ -304,9 +310,9 @@ (ptk/reify ::reorder-shadow ptk/WatchEvent (watch [_ _ _] - (rx/of (dch/update-shapes - ids - #(swap-attrs % :shadow index new-index)))))) + (rx/of (dch/update-shapes + ids + #(swap-attrs % :shadow index new-index)))))) (defn reorder-strokes [ids index new-index] From 73ed37f57adf4647ee09685fc06d0e36cf557fca Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 22 May 2023 13:04:40 +0200 Subject: [PATCH 3/5] :lipstick: Add cosmetic changes to stoke related functions frontend --- .../src/app/main/data/workspace/colors.cljs | 62 +++++++++---------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/frontend/src/app/main/data/workspace/colors.cljs b/frontend/src/app/main/data/workspace/colors.cljs index 8dd6a5018..53471eb08 100644 --- a/frontend/src/app/main/data/workspace/colors.cljs +++ b/frontend/src/app/main/data/workspace/colors.cljs @@ -201,7 +201,6 @@ (if (= (:type shape) :frame) (d/merge shape attrs) shape)))))))) - (defn change-stroke [ids attrs index] (ptk/reify ::change-stroke @@ -225,24 +224,28 @@ attrs (merge attrs color-attrs)] - (rx/of (dch/update-shapes ids (fn [shape] - (let [new-attrs (merge (get-in shape [:strokes index]) attrs) - new-attrs (cond-> new-attrs - (not (contains? new-attrs :stroke-width)) - (assoc :stroke-width 1) + (rx/of (dch/update-shapes + ids + (fn [shape] + (let [new-attrs (merge (get-in shape [:strokes index]) attrs) + new-attrs (cond-> new-attrs + (not (contains? new-attrs :stroke-width)) + (assoc :stroke-width 1) - (not (contains? new-attrs :stroke-style)) - (assoc :stroke-style :solid) + (not (contains? new-attrs :stroke-style)) + (assoc :stroke-style :solid) - (not (contains? new-attrs :stroke-alignment)) - (assoc :stroke-alignment :center) + (not (contains? new-attrs :stroke-alignment)) + (assoc :stroke-alignment :center) - :always - (d/without-nils))] - (-> shape - (cond-> (not (contains? shape :strokes)) - (assoc :strokes [])) - (assoc-in [:strokes index] new-attrs)))))))))) + :always + (d/without-nils))] + (cond-> shape + (not (contains? shape :strokes)) + (assoc :strokes []) + + :always + (assoc-in [:strokes index] new-attrs)))))))))) (defn change-shadow [ids attrs index] @@ -276,34 +279,29 @@ (ptk/reify ::add-stroke ptk/WatchEvent (watch [_ _ _] - (let [add (fn [shape attrs] (assoc shape :strokes (into [attrs] (:strokes shape))))] - (rx/of (dch/update-shapes - ids - #(add % stroke))))))) + (let [add-stroke (fn [shape] (update shape :strokes #(into [stroke] %)))] + (rx/of (dch/update-shapes ids add-stroke)))))) (defn remove-stroke [ids position] (ptk/reify ::remove-stroke ptk/WatchEvent (watch [_ _ _] - (let [remove-fill-by-index (fn [values index] (->> (d/enumerate values) - (filterv (fn [[idx _]] (not= idx index))) - (mapv second))) - - remove (fn [shape] (update shape :strokes remove-fill-by-index position))] - (rx/of (dch/update-shapes - ids - #(remove %))))))) + (letfn [(remove-fill-by-index [values index] + (->> (d/enumerate values) + (filterv (fn [[idx _]] (not= idx index))) + (mapv second))) + (remove-stroke [shape] + (update shape :strokes remove-fill-by-index position))] + (rx/of (dch/update-shapes ids remove-stroke)))))) (defn remove-all-strokes [ids] (ptk/reify ::remove-all-strokes ptk/WatchEvent (watch [_ _ _] - (let [remove-all (fn [shape] (assoc shape :strokes []))] - (rx/of (dch/update-shapes - ids - #(remove-all %))))))) + (let [remove-all #(assoc % :strokes [])] + (rx/of (dch/update-shapes ids remove-all)))))) (defn reorder-shadows [ids index new-index] From a09dd953ff5633780831a18aca8064200c988a65 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 22 May 2023 14:12:25 +0200 Subject: [PATCH 4/5] :zap: Add incomplete performance enhancements to shadow menu It is imposible to make this commponent efficient because of the design limitations of numeric-input component --- .../app/main/ui/components/numeric_input.cljs | 19 +- .../sidebar/options/menus/shadow.cljs | 373 +++++++++--------- 2 files changed, 202 insertions(+), 190 deletions(-) diff --git a/frontend/src/app/main/ui/components/numeric_input.cljs b/frontend/src/app/main/ui/components/numeric_input.cljs index 9b9034979..4218a6b37 100644 --- a/frontend/src/app/main/ui/components/numeric_input.cljs +++ b/frontend/src/app/main/ui/components/numeric_input.cljs @@ -42,7 +42,7 @@ ;; We need to store the handle-blur ref so we can call it on unmount 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 ;; initil value for the simple math expression evaluation. @@ -106,10 +106,11 @@ apply-value (mf/use-callback (mf/deps on-change update-input value) - (fn [new-value] + (fn [new-value event] (mf/set-ref-val! dirty-ref false) - (when (and (not= new-value value) (some? on-change)) - (on-change new-value)) + (when (and (not= new-value value) + (fn? on-change)) + (on-change new-value event)) (update-input new-value))) set-delta @@ -146,7 +147,7 @@ :else new-value)] - (apply-value new-value)))))) + (apply-value new-value event)))))) handle-key-down (mf/use-callback @@ -180,12 +181,12 @@ handle-blur (mf/use-callback (mf/deps parse-value apply-value update-input on-blur) - (fn [_] + (fn [event] (let [new-value (or (parse-value) default-val)] (if (or nillable new-value) - (apply-value new-value) + (apply-value new-value event) (update-input new-value))) - (when on-blur (on-blur)))) + (when on-blur (on-blur event)))) on-click (mf/use-callback @@ -236,7 +237,7 @@ (events/listen globals/window EventType.CLICK on-click)]] #(doseq [key keys] (events/unlistenByKey key))))) - + (mf/use-layout-effect (mf/deps handle-mouse-wheel) (fn [] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs index 3a122a53f..63cace23b 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs @@ -8,6 +8,8 @@ (:require [app.common.colors :as clr] [app.common.data :as d] + [app.common.data.macros :as dm] + [app.common.math :as mth] [app.common.uuid :as uuid] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.colors :as dc] @@ -24,75 +26,85 @@ (def shadow-attrs [:shadow]) -(defn create-shadow [] - (let [id (uuid/next)] - {:id id - :style :drop-shadow - :color {:color clr/black :opacity 0.2} - :offset-x 4 - :offset-y 4 - :blur 4 - :spread 0 - :hidden false})) +(defn- create-shadow + [] + {:id (uuid/next) + :style :drop-shadow + :color {:color clr/black + :opacity 0.2} + :offset-x 4 + :offset-y 4 + :blur 4 + :spread 0 + :hidden false}) -(defn valid-number? [value] - (or (number? value) (not (js/isNaN (js/parseInt value))))) +(defn- remove-shadow-by-index + [values index] + (->> (d/enumerate values) + (filterv (fn [[idx _]] (not= idx index))) + (mapv second))) (mf/defc shadow-entry - [{:keys [ids index value on-reorder select-all disable-drag on-blur]}] - (let [open-shadow (mf/use-state false) + [{:keys [ids index value on-reorder disable-drag? on-blur]}] + (let [open-shadow (mf/use-state false) basic-offset-x-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-y-ref (mf/use-ref nil) - adv-blur-ref (mf/use-ref nil) - adv-spread-ref (mf/use-ref nil) + adv-offset-x-ref (mf/use-ref nil) + adv-offset-y-ref (mf/use-ref nil) + adv-blur-ref (mf/use-ref nil) + adv-spread-ref (mf/use-ref nil) - shadow-style (str (:style value)) - - remove-shadow-by-index - (fn [values index] (->> (d/enumerate values) - (filterv (fn [[idx _]] (not= idx index))) - (mapv second))) + shadow-style (dm/str (:style value)) on-remove-shadow - (fn [index] - (fn [] - (st/emit! (dch/update-shapes ids #(update % :shadow remove-shadow-by-index index))))) - - select-text - (fn [ref] (fn [_] (dom/select-text! (mf/ref-val ref)))) + (mf/use-fn + (mf/deps ids) + (fn [event] + (let [index (-> (dom/get-current-target event) + (dom/get-data "index") + (parse-long))] + (st/emit! (dch/update-shapes ids #(update % :shadow remove-shadow-by-index index)))))) on-drop - (fn [_ data] - (on-reorder (:index data))) + (mf/use-fn + (mf/deps on-reorder index) + (fn [_ data] + (on-reorder index (:index data)))) - [dprops dref] (if (some? on-reorder) - (h/use-sortable - :data-type "penpot/shadow-entry" - :on-drop on-drop - :disabled @disable-drag - :detect-center? false - :data {:id (str "shadow-" index) - :index index - :name (str "Border row" index)}) - [nil nil]) + [dprops dref] + (h/use-sortable + :data-type "penpot/shadow-entry" + :on-drop on-drop + :disabled disable-drag? + :detect-center? false + :data {:id (dm/str "shadow-" index) + :index index + :name (dm/str "Border row" index)}) + ;; 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 (fn update-attr - ([index attr valid?] - (update-attr index attr valid? nil)) - - ([index attr valid? update-ref] + ([index attr] + (update-attr index attr nil)) + ([index attr update-ref] (fn [value] - (when (or (not valid?) (valid? value)) - (do (st/emit! (dch/update-shapes ids #(assoc-in % [:shadow index attr] value))) - (let [update-node (and update-ref (mf/ref-val update-ref))] - (when update-node - (dom/set-value! update-node value)))))))) + (when (mth/finite? value) + (st/emit! (dch/update-shapes ids #(assoc-in % [:shadow index attr] value))) + (when-let [update-node (and update-ref (mf/ref-val update-ref))] + (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 (fn [index] @@ -115,136 +127,135 @@ (fn [] (st/emit! (dch/update-shapes ids #(update-in % [:shadow index :hidden] not)))))] [:* - [:div.border-data {:class (dom/classnames - :dnd-over-top (= (:over dprops) :top) - :dnd-over-bot (= (:over dprops) :bot)) - :ref dref} - [:div.element-set-options-group {:style {:display (when @open-shadow "none")}} - [:div.element-set-actions-button - {:on-click #(reset! open-shadow true)} - i/actions] + [:div.border-data {:class (dom/classnames + :dnd-over-top (= (:over dprops) :top) + :dnd-over-bot (= (:over dprops) :bot)) + :ref dref} + [:div.element-set-options-group {:style {:display (when @open-shadow "none")}} + [:div.element-set-actions-button + {:on-click #(reset! open-shadow true)} + i/actions] - ;; [:> numeric-input {:ref basic-offset-x-ref - ;; :on-change (update-attr index :offset-x valid-number?) - ;; :on-click (select-text basic-offset-x-ref) - ;; :value (:offset-x value)}] - ;; [:> numeric-input {:ref basic-offset-y-ref - ;; :on-change (update-attr index :offset-y valid-number?) - ;; :on-click (select-text basic-offset-y-ref) - ;; :value (:offset-y value)}] - ;; [:> numeric-input {:ref basic-blur-ref - ;; :on-click (select-text basic-blur-ref) - ;; :on-change (update-attr index :blur valid-number?) - ;; :min 0 - ;; :value (:blur value)}] + [:select.input-select + {:default-value shadow-style + :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" + :selected (when (= shadow-style ":drop-shadow") "selected")} + (tr "workspace.options.shadow-options.drop-shadow")] + [:option {:value ":inner-shadow" + :selected (when (= shadow-style ":inner-shadow") "selected")} + (tr "workspace.options.shadow-options.inner-shadow")]] - [:select.input-select - {:default-value shadow-style - :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" :selected (when (= shadow-style ":drop-shadow") "selected")} (tr "workspace.options.shadow-options.drop-shadow")] - [:option {:value ":inner-shadow" :selected (when (= shadow-style ":inner-shadow") "selected")} (tr "workspace.options.shadow-options.inner-shadow")]] + [:div.element-set-actions + [:div.element-set-actions-button {:on-click (toggle-visibility index)} + (if (:hidden value) i/eye-closed i/eye)] + [:div.element-set-actions-button + {:data-index index + :on-click on-remove-shadow} + i/minus]]] - [:div.element-set-actions - [:div.element-set-actions-button {:on-click (toggle-visibility index)} - (if (:hidden value) i/eye-closed i/eye)] - [:div.element-set-actions-button {:on-click (on-remove-shadow index)} - i/minus]]] + [:& advanced-options {:visible? @open-shadow + :on-close #(reset! open-shadow false)} + [:div.color-data + [:div.element-set-actions-button + {: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 - :on-close #(reset! open-shadow false)} - [:div.color-data - [:div.element-set-actions-button - {: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")]]] + [:div.row-grid-2 + [:div.input-element {:title (tr "workspace.options.shadow-options.offsetx")} + [:> numeric-input {:ref adv-offset-x-ref + :no-validate true + :placeholder "--" + :on-focus (select-text adv-offset-x-ref) + :on-change (update-attr index :offset-x basic-offset-x-ref) + :on-blur on-blur + :value (:offset-x value)}] + [:span.after (tr "workspace.options.shadow-options.offsetx")]] - [:div.row-grid-2 - [:div.input-element {:title (tr "workspace.options.shadow-options.offsetx")} - [:> numeric-input {:ref adv-offset-x-ref - :no-validate true - :placeholder "--" - :on-focus (select-text adv-offset-x-ref) - :on-change (update-attr index :offset-x valid-number? basic-offset-x-ref) - :on-blur on-blur - :value (:offset-x value)}] - [:span.after (tr "workspace.options.shadow-options.offsetx")]] + [:div.input-element {:title (tr "workspace.options.shadow-options.offsety")} + [:> numeric-input {:ref adv-offset-y-ref + :no-validate true + :placeholder "--" + :on-focus (select-text adv-offset-y-ref) + :on-change (update-attr index :offset-y basic-offset-y-ref) + :on-blur on-blur + :value (:offset-y value)}] + [:span.after (tr "workspace.options.shadow-options.offsety")]]] - [:div.input-element {:title (tr "workspace.options.shadow-options.offsety")} - [:> numeric-input {:ref adv-offset-y-ref - :no-validate true - :placeholder "--" - :on-focus (select-text adv-offset-y-ref) - :on-change (update-attr index :offset-y valid-number? basic-offset-y-ref) - :on-blur on-blur - :value (:offset-y value)}] - [:span.after (tr "workspace.options.shadow-options.offsety")]]] + [:div.row-grid-2 + [:div.input-element {:title (tr "workspace.options.shadow-options.blur")} + [:> numeric-input {:ref adv-blur-ref + :no-validate true + :placeholder "--" + :on-focus (select-text adv-blur-ref) + :on-change (update-attr index :blur basic-blur-ref) + :on-blur on-blur + :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.blur")} - [:> numeric-input {:ref adv-blur-ref - :no-validate true - :placeholder "--" - :on-focus (select-text adv-blur-ref) - :on-change (update-attr index :blur valid-number? basic-blur-ref) - :on-blur on-blur - :min 0 - :value (:blur value)}] - [:span.after (tr "workspace.options.shadow-options.blur")]] + [:div.input-element {:title (tr "workspace.options.shadow-options.spread")} + [:> numeric-input {:ref adv-spread-ref + :no-validate true + :placeholder "--" + :on-focus (select-text adv-spread-ref) + :on-change (update-attr index :spread) + :on-blur on-blur + :value (:spread value)}] + [:span.after (tr "workspace.options.shadow-options.spread")]]] - [:div.input-element {:title (tr "workspace.options.shadow-options.spread")} - [:> numeric-input {:ref adv-spread-ref - :no-validate true - :placeholder "--" - :on-focus (select-text adv-spread-ref) - :on-change (update-attr index :spread valid-number?) - :on-blur on-blur - :value (:spread value)}] - [:span.after (tr "workspace.options.shadow-options.spread")]]] - - [:div.color-row-wrap - [:& 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))}]]]]])) + [:div.color-row-wrap + [:& 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 - [{:keys [ids type values] :as props}] - (let [on-remove-all-shadows - (fn [_] (st/emit! (dch/update-shapes ids #(dissoc % :shadow)))) + {::mf/wrap-props false} + [props] + (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 - (mf/use-callback - (mf/deps ids) - (fn [new-index] - (fn [index] - (st/emit! (dc/reorder-shadows ids index new-index))))) + (mf/use-fn + (mf/deps ids) + (fn [new-index index] + (st/emit! (dc/reorder-shadows ids index new-index)))) - disable-drag (mf/use-state false) - - select-all (fn [event] - (when (not @disable-drag) - (dom/select-text! (dom/get-target event))) - (reset! disable-drag true)) - - on-blur (fn [_] - (reset! disable-drag false)) + on-blur + (mf/use-fn + #(reset! disable-drag* false)) on-add-shadow - (fn [_] - (st/emit! (dc/add-shadow ids (create-shadow))))] + (mf/use-fn + (mf/deps ids) + #(st/emit! (dc/add-shadow ids (create-shadow))))] [:div.element-set.shadow-options [:div.element-set-title @@ -254,27 +265,27 @@ :group (tr "workspace.options.shadow-options.title.group") (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])] (cond - (= :multiple (:shadow values)) + (= :multiple shadows) [:div.element-set-content [:div.element-set-options-group [:div.element-set-label (tr "settings.multiple")] [: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]]]] - (seq (:shadow values)) + (seq shadows) [:& h/sortable-container {} [:div.element-set-content - (for [[index value] (d/enumerate (:shadow values []))] - [:& shadow-entry {:key (str "shadow-" index) - :ids ids - :value value - :on-reorder (handle-reorder index) - :select-all select-all - :disable-drag disable-drag - :on-blur on-blur - :index index}])]])])) + (for [[index value] (d/enumerate shadows)] + [:& shadow-entry + {:key (dm/str "shadow-" index) + :ids ids + :value value + :on-reorder handle-reorder + :disable-drag? disable-drag? + :on-blur on-blur + :index index}])]])])) From 1dab570907e071932074f9c9d41e3dad2694a678 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 24 May 2023 07:08:52 +0200 Subject: [PATCH 5/5] :bug: Fix some limit situations on shadow reorder --- CHANGES.md | 1 + .../partials/sidebar-element-options.scss | 21 ++++-- .../sidebar/options/menus/shadow.cljs | 65 ++++++++++++------- 3 files changed, 58 insertions(+), 29 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index c517fbf8e..e6c8c676d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -24,6 +24,7 @@ - Removed sizing variables from radius (by @ondrejkonec) [Github #3184](https://github.com/penpot/penpot/pull/3184) - Dashboard search, set focus after shortcut (by @akshay-gupta7) [Github #3196](https://github.com/penpot/penpot/pull/3196) - Library name dropdown arrow is overlapped by library name (by @ondrejkonec) [Taiga #5200](https://tree.taiga.io/project/penpot/issue/5200) +- Reorder shadows (by @akshay-gupta7) [Github #3236](https://github.com/penpot/penpot/pull/3236) ## 1.18.3 diff --git a/frontend/resources/styles/main/partials/sidebar-element-options.scss b/frontend/resources/styles/main/partials/sidebar-element-options.scss index e611d4a76..720cb859e 100644 --- a/frontend/resources/styles/main/partials/sidebar-element-options.scss +++ b/frontend/resources/styles/main/partials/sidebar-element-options.scss @@ -786,7 +786,8 @@ } } -.grid-option { +.grid-option, +.shadow-option { margin-bottom: 0.5rem; .advanced-options { .row-flex { @@ -797,6 +798,15 @@ position: absolute; top: 12px; } + .element-set-actions-button { + min-width: auto; + min-height: auto; + padding-right: 10px; + svg { + width: 12px; + height: 12px; + } + } } } @@ -807,7 +817,8 @@ margin-left: 0.25rem; } -.element-set-content .grid-option-main { +.element-set-content .grid-option-main, +.element-set-content .shadow-option-main { align-items: center; display: flex; padding: 0.3rem 0; @@ -857,11 +868,13 @@ } } -.grid-option-main-actions { +.grid-option-main-actions, +.shadow-option-main-actions { display: flex; visibility: hidden; - .grid-option:hover & { + .grid-option:hover &, + .shadow-option:hover & { visibility: visible; } } diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs index 63cace23b..83cc9a414 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs @@ -22,6 +22,7 @@ [app.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row]] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] + [okulary.core :as l] [rumext.v2 :as mf])) (def shadow-attrs [:shadow]) @@ -45,10 +46,8 @@ (mapv second))) (mf/defc shadow-entry - [{:keys [ids index value on-reorder disable-drag? on-blur]}] - (let [open-shadow (mf/use-state false) - - basic-offset-x-ref (mf/use-ref nil) + [{:keys [ids index value on-reorder disable-drag? on-blur open-state-ref]}] + (let [basic-offset-x-ref (mf/use-ref nil) basic-offset-y-ref (mf/use-ref nil) basic-blur-ref (mf/use-ref nil) @@ -58,6 +57,12 @@ adv-spread-ref (mf/use-ref nil) shadow-style (dm/str (:style value)) + shadow-id (:id value) + + open-status-ref (mf/with-memo [open-state-ref shadow-id] + (-> (l/key shadow-id) + (l/derived open-state-ref))) + open-shadow (mf/deref open-status-ref) on-remove-shadow (mf/use-fn @@ -125,15 +130,19 @@ toggle-visibility (fn [index] (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))))) + + on-toggle-open-shadow + (fn [] + (swap! open-state-ref update shadow-id not))] [:* - [:div.border-data {:class (dom/classnames - :dnd-over-top (= (:over dprops) :top) - :dnd-over-bot (= (:over dprops) :bot)) - :ref dref} - [:div.element-set-options-group {:style {:display (when @open-shadow "none")}} + [:div.shadow-option {:class (dom/classnames + :dnd-over-top (= (:over dprops) :top) + :dnd-over-bot (= (:over dprops) :bot)) + :ref dref} + [:div.shadow-option-main {:style {:display (when open-shadow "none")}} [:div.element-set-actions-button - {:on-click #(reset! open-shadow true)} + {:on-click on-toggle-open-shadow} i/actions] [:select.input-select @@ -148,7 +157,7 @@ :selected (when (= shadow-style ":inner-shadow") "selected")} (tr "workspace.options.shadow-options.inner-shadow")]] - [:div.element-set-actions + [:div.shadow-option-main-actions [:div.element-set-actions-button {:on-click (toggle-visibility index)} (if (:hidden value) i/eye-closed i/eye)] [:div.element-set-actions-button @@ -156,19 +165,23 @@ :on-click on-remove-shadow} i/minus]]] - [:& advanced-options {:visible? @open-shadow - :on-close #(reset! open-shadow false)} + [:& advanced-options {:visible? open-shadow + :on-close on-toggle-open-shadow} [:div.color-data [:div.element-set-actions-button - {:on-click #(reset! open-shadow false)} + {:on-click on-toggle-open-shadow} i/actions] [:select.input-select - {:default-value (str (:style value)) + {:default-value shadow-style :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")]]] + [:option {:value ":drop-shadow" + :selected (when (= shadow-style ":drop-shadow") "selected")} + (tr "workspace.options.shadow-options.drop-shadow")] + [:option {:value ":inner-shadow" + :selected (when (= shadow-style ":inner-shadow") "selected")} + (tr "workspace.options.shadow-options.inner-shadow")]]] [:div.row-grid-2 [:div.input-element {:title (tr "workspace.options.shadow-options.offsetx")} @@ -227,14 +240,15 @@ (mf/defc shadow-menu {::mf/wrap-props false} [props] - (let [ids (unchecked-get props "ids") - type (unchecked-get props "type") - values (unchecked-get props "values") + (let [ids (unchecked-get props "ids") + type (unchecked-get props "type") + values (unchecked-get props "values") - shadows (:shadow values []) + shadows (:shadow values []) + open-state-ref (mf/with-memo [] (l/atom {})) - disable-drag* (mf/use-state false) - disable-drag? (deref disable-drag*) + disable-drag* (mf/use-state false) + disable-drag? (deref disable-drag*) on-remove-all (mf/use-fn @@ -288,4 +302,5 @@ :on-reorder handle-reorder :disable-drag? disable-drag? :on-blur on-blur - :index index}])]])])) + :index index + :open-state-ref open-state-ref}])]])]))