diff --git a/CHANGES.md b/CHANGES.md index 249dfb925b..fc27453cd6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,7 @@ ### :boom: Breaking changes ### :sparkles: New features +- Add actions to go to main component context menu option [Taiga #2053](https://tree.taiga.io/project/penpot/us/2053). - Add contrast between component select color and shape select color [Taiga #2121](https://tree.taiga.io/project/penpot/issue/2121). - Add animations in interactions [Taiga #2244](https://tree.taiga.io/project/penpot/us/2244). diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index f26ded73f0..6c345448b5 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -44,10 +44,12 @@ [app.main.repo :as rp] [app.main.streams :as ms] [app.main.worker :as uw] + [app.util.dom :as dom] [app.util.globals :as ug] [app.util.http :as http] [app.util.i18n :as i18n] [app.util.router :as rt] + [app.util.timers :as tm] [app.util.webapi :as wapi] [beicon.core :as rx] [cljs.spec.alpha :as s] @@ -111,6 +113,10 @@ {:zoom 1 :flags #{} :selected (d/ordered-set) + :selected-assets {:components #{} + :graphics #{} + :colors #{} + :typographies #{}} :expanded {} :tooltip nil :options-mode :design @@ -1305,6 +1311,66 @@ qparams {:page-id page-id :layout (name layout)}] (rx/of (rt/nav :workspace pparams qparams)))))) +(defn check-in-asset + [set element] + (if (contains? set element) + (disj set element) + (conj set element))) + +(defn toggle-selected-assets + [asset type] + (ptk/reify ::toggle-selected-assets + ptk/UpdateEvent + (update [_ state] + (update-in state [:workspace-local :selected-assets type] #(check-in-asset % asset))))) + +(defn select-single-asset + [asset type] + (ptk/reify ::select-single-asset + ptk/UpdateEvent + (update [_ state] + (assoc-in state [:workspace-local :selected-assets type] #{asset})))) + +(defn select-assets + [assets type] + (ptk/reify ::select-assets + ptk/UpdateEvent + (update [_ state] + (assoc-in state [:workspace-local :selected-assets type] (into #{} assets))))) + +(defn unselect-all-assets + [] + (ptk/reify ::unselect-all-assets + ptk/UpdateEvent + (update [_ state] + (assoc-in state [:workspace-local :selected-assets] {:components #{} + :graphics #{} + :colors #{} + :typographies #{}})))) + +(defn go-to-component + [objs] + (ptk/reify ::set-workspace-layout-component + IDeref + (-deref [_] {:layout :assets}) + ptk/WatchEvent + (watch [_ state _] + (let [project-id (get-in state [:workspace-project :id]) + file-id (get-in state [:workspace-file :id]) + page-id (get state :current-page-id) + component-id (get (first objs) :component-id) + pparams {:file-id file-id :project-id project-id} + qparams {:page-id page-id :layout :assets}] + (rx/of (rt/nav :workspace pparams qparams) + (dwl/set-assets-box-open file-id :library true) + (dwl/set-assets-box-open file-id :components true) + (select-single-asset component-id :components)))) + ptk/EffectEvent + (effect [_ _ _] + (let [component-id (get (first objs) :component-id) + wrapper-id (str "component-shape-id-" component-id)] + (tm/schedule-on-idle #(dom/scroll-into-view-if-needed! (dom/get-element wrapper-id))))))) + (def go-to-file (ptk/reify ::go-to-file ptk/WatchEvent diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 96432175a8..a456a14778 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -155,6 +155,9 @@ (def editors (l/derived :editors workspace-local)) +(def selected-assets + (l/derived :selected-assets workspace-local)) + (def workspace-layout (l/derived :workspace-layout st/state)) diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs index 9266420997..24b1f4751c 100644 --- a/frontend/src/app/main/ui/workspace/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/context_menu.cljs @@ -168,7 +168,7 @@ :accept-label (tr "modals.update-remote-component.accept") :accept-style :primary :on-accept confirm-update-remote-component})) - do-show-component (st/emitf (dw/go-to-layout :assets)) + do-show-component (st/emitf (dw/go-to-component selected-objects)) do-navigate-component-file (st/emitf (dwl/nav-to-component-file (:component-file shape))) diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index 917f4f519b..8e701404e3 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -273,6 +273,7 @@ :selected (contains? selected-components (:id component)) :grid-cell @listing-thumbs? :enum-item (not @listing-thumbs?)) + :id (str "component-shape-id-" (:id component)) :draggable true :on-click #(on-asset-click % (:id component) nil) :on-context-menu (on-context-menu (:id component)) @@ -1402,15 +1403,12 @@ reverse-sort? (mf/use-state false) listing-thumbs? (mf/use-state true) - selected-assets (mf/use-state {:components #{} - :graphics #{} - :colors #{} - :typographies #{}}) + selected-assets (mf/deref refs/selected-assets) - selected-count (+ (count (:components @selected-assets)) - (count (:graphics @selected-assets)) - (count (:colors @selected-assets)) - (count (:typographies @selected-assets))) + selected-count (+ (count (:components selected-assets)) + (count (:graphics selected-assets)) + (count (:colors selected-assets)) + (count (:typographies selected-assets))) toggle-open (st/emitf (dwl/set-assets-box-open (:id file) :library (not open?))) @@ -1441,92 +1439,81 @@ (fn [_] (swap! listing-thumbs? not))) - toggle-selected-asset - (mf/use-callback - (mf/deps @selected-assets) - (fn [asset-type asset-id] - (swap! selected-assets update asset-type - (fn [selected] - (if (contains? selected asset-id) - (disj selected asset-id) - (conj selected asset-id)))))) - extend-selected-assets (mf/use-callback - (mf/deps @selected-assets) - (fn [asset-type asset-groups asset-id] - (letfn [(flatten-groups - [groups] - (concat - (get groups "" []) - (reduce concat - [] - (->> (filter #(seq (first %)) groups) - (map second) - (map flatten-groups)))))] - (swap! selected-assets update asset-type - (fn [selected] - (let [all-assets (flatten-groups asset-groups) - clicked-idx (d/index-of-pred all-assets #(= (:id %) asset-id)) - selected-idx (->> selected - (map (fn [id] - (d/index-of-pred all-assets - #(= (:id %) id))))) - min-idx (apply min (conj selected-idx clicked-idx)) - max-idx (apply max (conj selected-idx clicked-idx))] + (mf/deps selected-assets) + (fn [asset-type asset-groups asset-id] + (letfn [(flatten-groups + [groups] + (concat + (get groups "" []) + (reduce concat + (into [] + (->> (filter #(seq (first %)) groups) + (map second) + (mapcat flatten-groups))))))] + (let [selected-assets-type (get selected-assets asset-type) + count-assets (count selected-assets-type)] + (if (<= count-assets 0) + (st/emit! (dw/select-single-asset asset-id asset-type)) + (let [all-assets (flatten-groups asset-groups) + clicked-idx (d/index-of-pred all-assets #(= (:id %) asset-id)) + components (get selected-assets asset-type) - (->> all-assets - d/enumerate - (filter #(<= min-idx (first %) max-idx)) - (map #(-> % second :id)) - set))))))) + first-idx (first (sort (map (fn [asset] (d/index-of-pred all-assets #(= (:id %) asset))) components))) + selected-idx (vector first-idx clicked-idx) + min-idx (apply min (conj selected-idx clicked-idx)) + max-idx (apply max (conj selected-idx clicked-idx)) + values (->> all-assets + d/enumerate + (filter #(<= min-idx (first %) max-idx)) + (map #(-> % second :id)) + set)] + + (st/emit! (dw/select-assets values asset-type)))))))) unselect-all (mf/use-callback - (fn [] - (swap! selected-assets {:components #{} - :graphics #{} - :colors #{} - :typographies #{}}))) + (fn [] + (st/emit! (dw/unselect-all-assets)))) on-asset-click (mf/use-callback - (mf/deps toggle-selected-asset extend-selected-assets) - (fn [asset-type asset-groups event asset-id default-click] - (cond - (kbd/ctrl? event) - (do - (dom/stop-propagation event) - (toggle-selected-asset asset-type asset-id)) + (mf/deps extend-selected-assets selected-assets) + (fn [asset-type asset-groups event asset-id default-click] + (cond + (kbd/ctrl? event) + (do + (dom/stop-propagation event) + (st/emit! (dw/toggle-selected-assets asset-id asset-type))) - (kbd/shift? event) - (do - (dom/stop-propagation event) - (extend-selected-assets asset-type asset-groups asset-id)) + (kbd/shift? event) + (do + (dom/stop-propagation event) + (extend-selected-assets asset-type asset-groups asset-id)) - :else - (when default-click - (default-click event))))) + :else + (when default-click + (default-click event))))) on-assets-delete (mf/use-callback - (mf/deps @selected-assets) - (fn [] - (let [selected-assets @selected-assets] - (st/emit! (dwu/start-undo-transaction)) - (apply st/emit! (map #(dwl/delete-component {:id %}) - (:components selected-assets))) - (apply st/emit! (map #(dwl/delete-media {:id %}) - (:graphics selected-assets))) - (apply st/emit! (map #(dwl/delete-color {:id %}) - (:colors selected-assets))) - (apply st/emit! (map #(dwl/delete-typography %) - (:typographies selected-assets))) - (when (or (d/not-empty? (:components selected-assets)) - (d/not-empty? (:colors selected-assets)) - (d/not-empty? (:typographies selected-assets))) - (st/emit! (dwl/sync-file (:id file) (:id file)))) - (st/emit! (dwu/commit-undo-transaction)))))] + (mf/deps selected-assets) + (fn [] + (st/emit! (dwu/start-undo-transaction)) + (apply st/emit! (map #(dwl/delete-component {:id %}) + (:components selected-assets))) + (apply st/emit! (map #(dwl/delete-media {:id %}) + (:graphics selected-assets))) + (apply st/emit! (map #(dwl/delete-color {:id %}) + (:colors selected-assets))) + (apply st/emit! (map #(dwl/delete-typography %) + (:typographies selected-assets))) + (when (or (d/not-empty? (:components selected-assets)) + (d/not-empty? (:colors selected-assets)) + (d/not-empty? (:typographies selected-assets))) + (st/emit! (dwl/sync-file (:id file) (:id file)))) + (st/emit! (dwu/commit-undo-transaction))))] [:div.tool-window {:on-context-menu #(dom/prevent-default %) :on-click unselect-all} @@ -1588,7 +1575,7 @@ :open? (open-box? :components) :open-groups (open-groups :components) :reverse-sort? @reverse-sort? - :selected-assets @selected-assets + :selected-assets selected-assets :on-asset-click (partial on-asset-click :components) :on-assets-delete on-assets-delete :on-clear-selection unselect-all}]) @@ -1601,7 +1588,7 @@ :open? (open-box? :graphics) :open-groups (open-groups :graphics) :reverse-sort? @reverse-sort? - :selected-assets @selected-assets + :selected-assets selected-assets :on-asset-click (partial on-asset-click :graphics) :on-assets-delete on-assets-delete :on-clear-selection unselect-all}]) @@ -1612,7 +1599,7 @@ :open? (open-box? :colors) :open-groups (open-groups :colors) :reverse-sort? @reverse-sort? - :selected-assets @selected-assets + :selected-assets selected-assets :on-asset-click (partial on-asset-click :colors) :on-assets-delete on-assets-delete :on-clear-selection unselect-all}]) @@ -1625,7 +1612,7 @@ :open? (open-box? :typographies) :open-groups (open-groups :typographies) :reverse-sort? @reverse-sort? - :selected-assets @selected-assets + :selected-assets selected-assets :on-asset-click (partial on-asset-click :typographies) :on-assets-delete on-assets-delete :on-clear-selection unselect-all}]) diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs index cf9650acc8..080e8293f2 100644 --- a/frontend/src/app/util/dom.cljs +++ b/frontend/src/app/util/dom.cljs @@ -354,6 +354,12 @@ ([element scroll-top] (.scrollIntoView ^js element scroll-top))) +(defn scroll-into-view-if-needed! + ([element] + (.scrollIntoViewIfNeeded ^js element false)) + ([element scroll-top] + (.scrollIntoViewIfNeeded ^js element scroll-top))) + (defn is-in-viewport? [element] (let [rect (.getBoundingClientRect element)