mirror of
https://github.com/penpot/penpot.git
synced 2025-03-27 23:21:47 -05:00
♻️ Refactor context-menu component
This commit is contained in:
parent
782d733bc9
commit
88d85706ad
12 changed files with 460 additions and 449 deletions
|
@ -9,90 +9,107 @@
|
|||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.schema :as sm]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.ui.components.dropdown :refer [dropdown']]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.keyboard :as kbd]
|
||||
[app.util.object :as obj]
|
||||
[app.util.timers :as tm]
|
||||
[goog.object :as gobj]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defn generate-ids-group
|
||||
[options parent-name]
|
||||
(let [ids (->> options
|
||||
(map :id)
|
||||
(filter some?))]
|
||||
(if parent-name
|
||||
(cons "go-back-sub-option" ids)
|
||||
ids)))
|
||||
(def ^:private xf:options
|
||||
(comp
|
||||
(map :id)
|
||||
(filter some?)))
|
||||
|
||||
(mf/defc context-menu-a11y-item
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(defn- generate-ids-group
|
||||
[options has-parents?]
|
||||
(let [ids (sequence xf:options options)
|
||||
ids (if has-parents?
|
||||
(cons "go-back-sub-option" ids)
|
||||
ids)]
|
||||
(vec ids)))
|
||||
|
||||
(let [children (gobj/get props "children")
|
||||
on-click (gobj/get props "on-click")
|
||||
on-key-down (gobj/get props "on-key-down")
|
||||
id (gobj/get props "id")
|
||||
klass (gobj/get props "class")
|
||||
key-index (gobj/get props "key-index")
|
||||
data-testid (gobj/get props "data-testid")]
|
||||
[:li {:id id
|
||||
:class klass
|
||||
:tab-index "0"
|
||||
:on-key-down on-key-down
|
||||
:on-click on-click
|
||||
:key key-index
|
||||
:role "menuitem"
|
||||
:data-testid data-testid}
|
||||
children]))
|
||||
(def ^:private schema:option
|
||||
[:schema {:registry
|
||||
{::option
|
||||
[:or
|
||||
:nil
|
||||
[:map [:name [:= :separator]]]
|
||||
[:and
|
||||
[:map
|
||||
[:name :string]
|
||||
[:id :string]
|
||||
[:handler {:optional true} fn?]
|
||||
[:options {:optional true}
|
||||
[:sequential [:ref ::option]]]]
|
||||
[::sm/contains-any #{:handler :options}]]]}}
|
||||
[:ref ::option]])
|
||||
|
||||
(def ^:private valid-option?
|
||||
(sm/lazy-validator schema:option))
|
||||
|
||||
(mf/defc context-menu*
|
||||
{::mf/props :obj}
|
||||
|
||||
[{:keys [show on-close options selectable selected
|
||||
top left fixed min-width origin width]
|
||||
:as props}]
|
||||
|
||||
(assert (every? valid-option? options) "expected valid options")
|
||||
(assert (fn? on-close) "missing `on-close` prop")
|
||||
(assert (boolean? show) "missing `show` prop")
|
||||
(assert (vector? options) "missing `options` prop")
|
||||
|
||||
(let [width (d/nilv width "initial")
|
||||
min-width (d/nilv min-width false)
|
||||
left (d/nilv left 0)
|
||||
top (d/nilv top 0)
|
||||
|
||||
(mf/defc context-menu-a11y'
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(assert (fn? (gobj/get props "on-close")) "missing `on-close` prop")
|
||||
(assert (boolean? (gobj/get props "show")) "missing `show` prop")
|
||||
(assert (vector? (gobj/get props "options")) "missing `options` prop")
|
||||
(let [open? (gobj/get props "show")
|
||||
on-close (gobj/get props "on-close")
|
||||
options (gobj/get props "options")
|
||||
is-selectable (gobj/get props "selectable")
|
||||
selected (gobj/get props "selected")
|
||||
top (gobj/get props "top" 0)
|
||||
left (gobj/get props "left" 0)
|
||||
fixed? (gobj/get props "fixed?" false)
|
||||
min-width? (gobj/get props "min-width?" false)
|
||||
origin (gobj/get props "origin")
|
||||
route (mf/deref refs/route)
|
||||
in-dashboard? (= :dashboard-projects (:name (:data route)))
|
||||
local (mf/use-state {:offset-y 0
|
||||
:offset-x 0
|
||||
:levels nil})
|
||||
width (gobj/get props "width" "initial")
|
||||
|
||||
state* (mf/use-state
|
||||
#(-> {:offset-y 0
|
||||
:offset-x 0
|
||||
:levels nil}))
|
||||
|
||||
state (deref state*)
|
||||
offset-x (get state :offset-x)
|
||||
offset-y (get state :offset-y)
|
||||
levels (get state :levels)
|
||||
|
||||
on-local-close
|
||||
(mf/use-callback
|
||||
(mf/use-fn
|
||||
(mf/deps on-close)
|
||||
(fn []
|
||||
(swap! local assoc :levels [{:parent-option nil
|
||||
:options options}])
|
||||
(swap! state* assoc :levels [{:parent nil
|
||||
:options options}])
|
||||
(on-close)))
|
||||
|
||||
props (obj/merge props #js {:on-close on-local-close})
|
||||
props
|
||||
(mf/spread props :on-close on-local-close)
|
||||
|
||||
ids
|
||||
(mf/with-memo [levels]
|
||||
(let [last-level (last levels)]
|
||||
(generate-ids-group (:options last-level)
|
||||
(:parent last-level))))
|
||||
|
||||
ids (generate-ids-group (:options (last (:levels @local))) (:parent-option (last (:levels @local))))
|
||||
check-menu-offscreen
|
||||
(mf/use-callback
|
||||
(mf/deps top (:offset-y @local) left (:offset-x @local))
|
||||
(mf/use-fn
|
||||
(mf/deps top left offset-x offset-y)
|
||||
(fn [node]
|
||||
(when (some? node)
|
||||
(let [bounding_rect (dom/get-bounding-rect node)
|
||||
window_size (dom/get-window-size)
|
||||
{node-height :height node-width :width} bounding_rect
|
||||
{window-height :height window-width :width} window_size
|
||||
(let [bounding-rect (dom/get-bounding-rect node)
|
||||
window-size (dom/get-window-size)
|
||||
node-height (dm/get-prop bounding-rect :height)
|
||||
node-width (dm/get-prop bounding-rect :width)
|
||||
window-height (get window-size :height)
|
||||
window-width (get window-size :width)
|
||||
|
||||
target-offset-y (if (> (+ top node-height) window-height)
|
||||
(- node-height)
|
||||
0)
|
||||
|
@ -100,74 +117,86 @@
|
|||
(- node-width)
|
||||
0)]
|
||||
|
||||
(when (or (not= target-offset-y (:offset-y @local)) (not= target-offset-x (:offset-x @local)))
|
||||
(swap! local assoc :offset-y target-offset-y :offset-x target-offset-x))))))
|
||||
(when (or (not= target-offset-y offset-y)
|
||||
(not= target-offset-x offset-x))
|
||||
(swap! state* assoc
|
||||
:offset-y target-offset-y
|
||||
:offset-x target-offset-x))))))
|
||||
|
||||
;; NOTE: this function is used for build navigation callbacks
|
||||
;; so we don't really need to use the use-fn here. It is not
|
||||
;; an efficient approach but this manages a reasonable small
|
||||
;; list of objects, so doing it this way has no real
|
||||
;; implications on performance but facilitates a lot the
|
||||
;; implementation
|
||||
enter-submenu
|
||||
(mf/use-callback
|
||||
(mf/deps options)
|
||||
(fn [option-name sub-options]
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(swap! local update :levels
|
||||
conj {:parent-option option-name
|
||||
:options sub-options}))))
|
||||
|
||||
exit-submenu
|
||||
(mf/use-callback
|
||||
(fn [name options]
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(swap! state* update :levels conj {:parent name
|
||||
:options options})))
|
||||
on-submenu-exit
|
||||
(mf/use-fn
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(swap! local update :levels pop)))
|
||||
(swap! state* update :levels pop)))
|
||||
|
||||
;; NOTE: this function is used for build navigation callbacks
|
||||
;; so we don't really need to use the use-fn here. It is not
|
||||
;; an efficient approach but this manages a reasonable small
|
||||
;; list of objects, so doing it this way has no real
|
||||
;; implications on performance but facilitates a lot the
|
||||
;; implementation
|
||||
on-key-down
|
||||
(fn [options-original parent-original]
|
||||
(fn [event]
|
||||
(let [ids (generate-ids-group options-original parent-original)
|
||||
first-id (dom/get-element (first ids))
|
||||
first-element (dom/get-element first-id)
|
||||
len (count ids)
|
||||
parent (dom/get-target event)
|
||||
parent-id (dom/get-attribute parent "id")
|
||||
option (first (filter #(= parent-id (:id %)) options-original))
|
||||
sub-options (:sub-options option)
|
||||
has-suboptions? (some? (:sub-options option))
|
||||
option-handler (:option-handler option)
|
||||
is-back-option (= "go-back-sub-option" parent-id)]
|
||||
(let [ids (generate-ids-group options-original
|
||||
parent-original)
|
||||
first-id (dom/get-element (first ids))
|
||||
first-element (dom/get-element first-id)
|
||||
len (count ids)
|
||||
|
||||
parent (dom/get-target event)
|
||||
parent-id (dom/get-attribute parent "id")
|
||||
|
||||
option (d/seek #(= parent-id (:id %)) options-original)
|
||||
sub-options (not-empty (:options option))
|
||||
handler (:handler option)
|
||||
is-back-option? (= "go-back-sub-option" parent-id)]
|
||||
|
||||
(when (kbd/home? event)
|
||||
(when first-element
|
||||
(dom/focus! first-element)))
|
||||
|
||||
(when (kbd/enter? event)
|
||||
(if is-back-option
|
||||
(exit-submenu event)
|
||||
(if is-back-option?
|
||||
(on-submenu-exit event)
|
||||
|
||||
(if has-suboptions?
|
||||
(if sub-options
|
||||
(do
|
||||
(dom/stop-propagation event)
|
||||
(swap! local update :levels
|
||||
conj {:parent-option (:option-name option)
|
||||
:options sub-options}))
|
||||
(swap! state* update :levels conj {:parent (:name option)
|
||||
:options sub-options}))
|
||||
|
||||
(do
|
||||
(dom/stop-propagation event)
|
||||
(option-handler event)))))
|
||||
(handler event)))))
|
||||
|
||||
(when (and is-back-option
|
||||
(kbd/left-arrow? event))
|
||||
(exit-submenu event))
|
||||
(when (and is-back-option? (kbd/left-arrow? event))
|
||||
(on-submenu-exit event))
|
||||
|
||||
(when (and has-suboptions? (kbd/right-arrow? event))
|
||||
(when (and sub-options (kbd/right-arrow? event))
|
||||
(dom/stop-propagation event)
|
||||
(swap! local update :levels
|
||||
conj {:parent-option (:option-name option)
|
||||
:options sub-options}))
|
||||
(swap! state* update :levels conj {:parent (:name option)
|
||||
:options sub-options}))
|
||||
|
||||
(when (kbd/up-arrow? event)
|
||||
(let [actual-selected (dom/get-active)
|
||||
actual-id (dom/get-attribute actual-selected "id")
|
||||
actual-index (d/index-of ids actual-id)
|
||||
previous-id (if (= 0 actual-index)
|
||||
(last ids)
|
||||
(nth ids (- actual-index 1)))]
|
||||
actual-id (dom/get-attribute actual-selected "id")
|
||||
actual-index (d/index-of ids actual-id)
|
||||
previous-id (if (= 0 actual-index)
|
||||
(last ids)
|
||||
(nth ids (- actual-index 1)))]
|
||||
(dom/focus! (dom/get-element previous-id))))
|
||||
|
||||
(when (kbd/down-arrow? event)
|
||||
|
@ -180,98 +209,87 @@
|
|||
(dom/focus! (dom/get-element next-id))))
|
||||
|
||||
(when (or (kbd/esc? event) (kbd/tab? event))
|
||||
(on-close)
|
||||
(on-close event)
|
||||
(dom/focus! (dom/get-element origin))))))]
|
||||
|
||||
(mf/with-effect [options]
|
||||
(swap! local assoc :levels [{:parent-option nil
|
||||
:options options}]))
|
||||
(swap! state* assoc :levels [{:parent nil
|
||||
:options options}]))
|
||||
|
||||
(mf/with-effect [ids]
|
||||
(tm/schedule-on-idle
|
||||
#(dom/focus! (dom/get-element (first ids)))))
|
||||
|
||||
(when (and open? (some? (:levels @local)))
|
||||
(when (and show (some? levels))
|
||||
[:> dropdown' props
|
||||
(let [level (-> @local :levels peek)
|
||||
original-options (:options level)
|
||||
parent-original (:parent-option level)]
|
||||
[:div {:class (stl/css-case :is-selectable is-selectable
|
||||
:context-menu true
|
||||
:is-open open?
|
||||
:fixed fixed?)
|
||||
:style {:top (+ top (:offset-y @local))
|
||||
:left (+ left (:offset-x @local))}
|
||||
:on-key-down (on-key-down original-options parent-original)}
|
||||
(let [level (-> @local :levels peek)]
|
||||
[:ul {:class (stl/css-case :min-width min-width?
|
||||
:context-menu-items true)
|
||||
:style {:width width}
|
||||
:role "menu"
|
||||
:ref check-menu-offscreen}
|
||||
(when-let [parent-option (:parent-option level)]
|
||||
[:*
|
||||
[:& context-menu-a11y-item
|
||||
{:id "go-back-sub-option"
|
||||
:class (stl/css :context-menu-item)
|
||||
:tab-index "0"
|
||||
:on-key-down (fn [event]
|
||||
(dom/prevent-default event))}
|
||||
[:button {:class (stl/css :context-menu-action :submenu-back)
|
||||
(let [level (peek levels)
|
||||
options (:options level)
|
||||
parent (:parent level)]
|
||||
|
||||
[:div {:class (stl/css-case
|
||||
:is-selectable selectable
|
||||
:context-menu true
|
||||
:is-open show
|
||||
:fixed fixed)
|
||||
:style {:top (+ top offset-y)
|
||||
:left (+ left offset-x)}
|
||||
:on-key-down (on-key-down options parent)}
|
||||
|
||||
[:ul {:class (stl/css-case :min-width min-width
|
||||
:context-menu-items true)
|
||||
:style {:width width}
|
||||
:role "menu"
|
||||
:ref check-menu-offscreen}
|
||||
|
||||
(when-let [parent (:parent level)]
|
||||
[:*
|
||||
[:li {:id "go-back-sub-option"
|
||||
:class (stl/css :context-menu-item)
|
||||
:role "menuitem"
|
||||
:tab-index "0"
|
||||
:on-key-down dom/prevent-default}
|
||||
[:button {:class (stl/css :context-menu-action :submenu-back)
|
||||
:data-no-close true
|
||||
:on-click on-submenu-exit}
|
||||
[:span {:class (stl/css :submenu-icon-back)} i/arrow]
|
||||
parent]]
|
||||
|
||||
[:li {:class (stl/css :separator)}]])
|
||||
|
||||
(for [[index option] (d/enumerate (:options level))]
|
||||
(let [name (:name option)
|
||||
id (:id option)
|
||||
sub-options (:options option)
|
||||
handler (:handler option)]
|
||||
(when name
|
||||
(if (= name :separator)
|
||||
[:li {:key (dm/str "context-item-" index)
|
||||
:class (stl/css :separator)}]
|
||||
[:li {:id id
|
||||
:key id
|
||||
:class (stl/css-case
|
||||
:is-selected (and selected (= name selected))
|
||||
:selected (and selected (= id selected))
|
||||
:context-menu-item true)
|
||||
:tab-index "0"
|
||||
:role "menuitem"
|
||||
:on-key-down dom/prevent-default}
|
||||
(if-not sub-options
|
||||
[:a {:class (stl/css :context-menu-action)
|
||||
:on-click #(do (dom/stop-propagation %)
|
||||
(on-close %)
|
||||
(handler %))
|
||||
:data-testid id}
|
||||
(if (and in-dashboard? (= name "Default"))
|
||||
(tr "dashboard.default-team-name")
|
||||
name)
|
||||
|
||||
(when (and selected (= id selected))
|
||||
[:span {:class (stl/css :selected-icon)} i/tick])]
|
||||
|
||||
[:a {:class (stl/css :context-menu-action :submenu)
|
||||
:data-no-close true
|
||||
:on-click exit-submenu}
|
||||
[:span {:class (stl/css :submenu-icon-back)} i/arrow]
|
||||
parent-option]]
|
||||
|
||||
[:li {:class (stl/css :separator)}]])
|
||||
|
||||
(for [[index option] (d/enumerate (:options level))]
|
||||
(let [option-name (:option-name option)
|
||||
id (:id option)
|
||||
sub-options (:sub-options option)
|
||||
option-handler (:option-handler option)
|
||||
data-testid (:data-testid option)]
|
||||
(when option-name
|
||||
(if (= option-name :separator)
|
||||
[:li {:key (dm/str "context-item-" index)
|
||||
:class (stl/css :separator)}]
|
||||
[:& context-menu-a11y-item
|
||||
{:id id
|
||||
:key id
|
||||
:class (stl/css-case
|
||||
:is-selected (and selected (= option-name selected))
|
||||
:selected (and selected (= data-testid selected))
|
||||
:context-menu-item true)
|
||||
:key-index (dm/str "context-item-" index)
|
||||
:tab-index "0"
|
||||
:on-key-down (fn [event]
|
||||
(dom/prevent-default event))}
|
||||
(if-not sub-options
|
||||
[:a {:class (stl/css :context-menu-action)
|
||||
:on-click #(do (dom/stop-propagation %)
|
||||
(on-close)
|
||||
(option-handler %))
|
||||
:data-testid data-testid}
|
||||
(if (and in-dashboard? (= option-name "Default"))
|
||||
(tr "dashboard.default-team-name")
|
||||
option-name)
|
||||
|
||||
(when (and selected (= data-testid selected))
|
||||
[:span {:class (stl/css :selected-icon)} i/tick])]
|
||||
|
||||
[:a {:class (stl/css :context-menu-action :submenu)
|
||||
:data-no-close true
|
||||
:on-click (enter-submenu option-name sub-options)
|
||||
:data-testid data-testid}
|
||||
option-name
|
||||
[:span {:class (stl/css :submenu-icon)} i/arrow]])]))))])])])))
|
||||
|
||||
(mf/defc context-menu-a11y
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(assert (fn? (gobj/get props "on-close")) "missing `on-close` prop")
|
||||
(assert (boolean? (gobj/get props "show")) "missing `show` prop")
|
||||
(assert (vector? (gobj/get props "options")) "missing `options` prop")
|
||||
|
||||
(when (gobj/get props "show")
|
||||
(mf/element context-menu-a11y' props)))
|
||||
:on-click (enter-submenu name sub-options)
|
||||
:data-testid id}
|
||||
name
|
||||
[:span {:class (stl/css :submenu-icon)} i/arrow]])]))))]])])))
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
[app.main.refs :as refs]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.context-menu-a11y :refer [context-menu-a11y]]
|
||||
[app.main.ui.components.context-menu-a11y :refer [context-menu*]]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
|
@ -221,113 +221,118 @@
|
|||
(reset! teams %)))))))
|
||||
|
||||
(when current-team
|
||||
(let [sub-options (concat (vec (for [project current-projects]
|
||||
{:option-name (get-project-name project)
|
||||
:id (get-project-id project)
|
||||
:option-handler (on-move (:id current-team)
|
||||
(:id project))}))
|
||||
(when (seq other-teams)
|
||||
[{:option-name (tr "dashboard.move-to-other-team")
|
||||
:id "move-to-other-team"
|
||||
:sub-options
|
||||
(for [team other-teams]
|
||||
{:option-name (get-team-name team)
|
||||
:id (get-project-id team)
|
||||
:sub-options
|
||||
(for [sub-project (:projects team)]
|
||||
{:option-name (get-project-name sub-project)
|
||||
:id (get-project-id sub-project)
|
||||
:option-handler (on-move (:id team)
|
||||
(:id sub-project))})})}]))
|
||||
(let [sub-options
|
||||
(concat
|
||||
(for [project current-projects]
|
||||
{:name (get-project-name project)
|
||||
:id (get-project-id project)
|
||||
:handler (on-move (:id current-team)
|
||||
(:id project))})
|
||||
(when (seq other-teams)
|
||||
[{:name (tr "dashboard.move-to-other-team")
|
||||
:id "move-to-other-team"
|
||||
:options
|
||||
(for [team other-teams]
|
||||
{:name (get-team-name team)
|
||||
:id (get-project-id team)
|
||||
:options
|
||||
(for [sub-project (:projects team)]
|
||||
{:name (get-project-name sub-project)
|
||||
:id (get-project-id sub-project)
|
||||
:handler (on-move (:id team)
|
||||
(:id sub-project))})})}]))
|
||||
|
||||
options (if multi?
|
||||
[(when-not you-viewer?
|
||||
{:option-name (tr "dashboard.duplicate-multi" file-count)
|
||||
:id "file-duplicate-multi"
|
||||
:option-handler on-duplicate
|
||||
:data-testid "duplicate-multi"})
|
||||
(when (and (or (seq current-projects) (seq other-teams))
|
||||
(not you-viewer?))
|
||||
{:option-name (tr "dashboard.move-to-multi" file-count)
|
||||
:id "file-move-multi"
|
||||
:sub-options sub-options
|
||||
:data-testid "move-to-multi"})
|
||||
{:option-name (tr "dashboard.export-binary-multi" file-count)
|
||||
:id "file-binari-export-multi"
|
||||
:option-handler on-export-binary-files}
|
||||
{:option-name (tr "dashboard.export-standard-multi" file-count)
|
||||
:id "file-standard-export-multi"
|
||||
:option-handler on-export-standard-files}
|
||||
(when (and (:is-shared file)
|
||||
(not you-viewer?))
|
||||
{:option-name (tr "labels.unpublish-multi-files" file-count)
|
||||
:id "file-unpublish-multi"
|
||||
:option-handler on-del-shared
|
||||
:data-testid "file-del-shared"})
|
||||
(when (and (not is-lib-page?)
|
||||
(not you-viewer?))
|
||||
{:option-name :separator}
|
||||
{:option-name (tr "labels.delete-multi-files" file-count)
|
||||
:id "file-delete-multi"
|
||||
:option-handler on-delete
|
||||
:data-testid "delete-multi-files"})]
|
||||
options
|
||||
(if multi?
|
||||
[(when-not you-viewer?
|
||||
{:name (tr "dashboard.duplicate-multi" file-count)
|
||||
:id "duplicate-multi"
|
||||
:handler on-duplicate})
|
||||
|
||||
[{:option-name (tr "dashboard.open-in-new-tab")
|
||||
:id "file-open-new-tab"
|
||||
:option-handler on-new-tab}
|
||||
(when (and (not is-search-page?)
|
||||
(not you-viewer?))
|
||||
{:option-name (tr "labels.rename")
|
||||
:id "file-rename"
|
||||
:option-handler on-edit
|
||||
:data-testid "file-rename"})
|
||||
(when (and (not is-search-page?)
|
||||
(not you-viewer?))
|
||||
{:option-name (tr "dashboard.duplicate")
|
||||
:id "file-duplicate"
|
||||
:option-handler on-duplicate
|
||||
:data-testid "file-duplicate"})
|
||||
(when (and (not is-lib-page?)
|
||||
(not is-search-page?)
|
||||
(or (seq current-projects) (seq other-teams))
|
||||
(not you-viewer?))
|
||||
{:option-name (tr "dashboard.move-to")
|
||||
:id "file-move-to"
|
||||
:sub-options sub-options
|
||||
:data-testid "file-move-to"})
|
||||
(when (and (not is-search-page?)
|
||||
(not you-viewer?))
|
||||
(if (:is-shared file)
|
||||
{:option-name (tr "dashboard.unpublish-shared")
|
||||
:id "file-del-shared"
|
||||
:option-handler on-del-shared
|
||||
:data-testid "file-del-shared"}
|
||||
{:option-name (tr "dashboard.add-shared")
|
||||
:id "file-add-shared"
|
||||
:option-handler on-add-shared
|
||||
:data-testid "file-add-shared"}))
|
||||
{:option-name :separator}
|
||||
{:option-name (tr "dashboard.download-binary-file")
|
||||
:id "file-download-binary"
|
||||
:option-handler on-export-binary-files
|
||||
:data-testid "download-binary-file"}
|
||||
{:option-name (tr "dashboard.download-standard-file")
|
||||
:id "file-download-standard"
|
||||
:option-handler on-export-standard-files
|
||||
:data-testid "download-standard-file"}
|
||||
(when (and (not is-lib-page?) (not is-search-page?) (not you-viewer?))
|
||||
{:option-name :separator}
|
||||
{:option-name (tr "labels.delete")
|
||||
:id "file-delete"
|
||||
:option-handler on-delete
|
||||
:data-testid "file-delete"})])]
|
||||
(when (and (or (seq current-projects) (seq other-teams))
|
||||
(not you-viewer?))
|
||||
{:name (tr "dashboard.move-to-multi" file-count)
|
||||
:id "file-move-multi"
|
||||
:options sub-options})
|
||||
|
||||
[:& context-menu-a11y {:on-close on-menu-close
|
||||
:show show?
|
||||
:fixed? (or (not= top 0) (not= left 0))
|
||||
:min-width? true
|
||||
:top top
|
||||
:left left
|
||||
:options options
|
||||
:origin parent-id
|
||||
:workspace? false}]))))
|
||||
{:name (tr "dashboard.export-binary-multi" file-count)
|
||||
:id "file-binari-export-multi"
|
||||
:handler on-export-binary-files}
|
||||
|
||||
{:name (tr "dashboard.export-standard-multi" file-count)
|
||||
:id "file-standard-export-multi"
|
||||
:handler on-export-standard-files}
|
||||
|
||||
(when (and (:is-shared file)
|
||||
(not you-viewer?))
|
||||
{:name (tr "labels.unpublish-multi-files" file-count)
|
||||
:id "file-unpublish-multi"
|
||||
:handler on-del-shared})
|
||||
|
||||
(when (and (not is-lib-page?)
|
||||
(not you-viewer?))
|
||||
{:name :separator}
|
||||
{:name (tr "labels.delete-multi-files" file-count)
|
||||
:id "file-delete-multi"
|
||||
:handler on-delete})]
|
||||
|
||||
[{:name (tr "dashboard.open-in-new-tab")
|
||||
:id "file-open-new-tab"
|
||||
:handler on-new-tab}
|
||||
(when (and (not is-search-page?)
|
||||
(not you-viewer?))
|
||||
{:name (tr "labels.rename")
|
||||
:id "file-rename"
|
||||
:handler on-edit})
|
||||
|
||||
(when (and (not is-search-page?)
|
||||
(not you-viewer?))
|
||||
{:name (tr "dashboard.duplicate")
|
||||
:id "file-duplicate"
|
||||
:handler on-duplicate})
|
||||
|
||||
(when (and (not is-lib-page?)
|
||||
(not is-search-page?)
|
||||
(or (seq current-projects) (seq other-teams))
|
||||
(not you-viewer?))
|
||||
{:name (tr "dashboard.move-to")
|
||||
:id "file-move-to"
|
||||
:options sub-options})
|
||||
|
||||
(when (and (not is-search-page?)
|
||||
(not you-viewer?))
|
||||
(if (:is-shared file)
|
||||
{:name (tr "dashboard.unpublish-shared")
|
||||
:id "file-del-shared"
|
||||
:handler on-del-shared}
|
||||
{:name (tr "dashboard.add-shared")
|
||||
:id "file-add-shared"
|
||||
:handler on-add-shared}))
|
||||
|
||||
{:name :separator}
|
||||
|
||||
{:name (tr "dashboard.download-binary-file")
|
||||
:id "download-binary-file"
|
||||
:handler on-export-binary-files}
|
||||
|
||||
{:name (tr "dashboard.download-standard-file")
|
||||
:id "download-standard-file"
|
||||
:handler on-export-standard-files}
|
||||
|
||||
(when (and (not is-lib-page?) (not is-search-page?) (not you-viewer?))
|
||||
{:name :separator})
|
||||
|
||||
(when (and (not is-lib-page?) (not is-search-page?) (not you-viewer?))
|
||||
{:name (tr "labels.delete")
|
||||
:id "file-delete"
|
||||
:handler on-delete})])]
|
||||
|
||||
[:> context-menu*
|
||||
{:on-close on-menu-close
|
||||
:show show?
|
||||
:fixed (or (not= top 0) (not= left 0))
|
||||
:min-width true
|
||||
:top top
|
||||
:left left
|
||||
:options options
|
||||
:origin parent-id}]))))
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
[app.main.refs :as refs]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.context-menu-a11y :refer [context-menu-a11y]]
|
||||
[app.main.ui.components.context-menu-a11y :refer [context-menu*]]
|
||||
[app.main.ui.components.file-uploader :refer [file-uploader]]
|
||||
[app.main.ui.ds.product.empty-placeholder :refer [empty-placeholder*]]
|
||||
[app.main.ui.icons :as i]
|
||||
|
@ -250,21 +250,20 @@
|
|||
::mf/private true}
|
||||
[{:keys [is-open on-close on-edit on-delete]}]
|
||||
(let [options (mf/with-memo [on-edit on-delete]
|
||||
[{:option-name (tr "labels.edit")
|
||||
:id "font-edit"
|
||||
:option-handler on-edit}
|
||||
{:option-name (tr "labels.delete")
|
||||
:id "font-delete"
|
||||
:option-handler on-delete}])]
|
||||
[:& context-menu-a11y
|
||||
[{:name (tr "labels.edit")
|
||||
:id "font-edit"
|
||||
:handler on-edit}
|
||||
{:name (tr "labels.delete")
|
||||
:id "font-delete"
|
||||
:handler on-delete}])]
|
||||
[:> context-menu*
|
||||
{:on-close on-close
|
||||
:show is-open
|
||||
:fixed? false
|
||||
:min-width? true
|
||||
:fixed false
|
||||
:min-width true
|
||||
:top -15
|
||||
:left -115
|
||||
:options options
|
||||
:workspace? false}]))
|
||||
:options options}]))
|
||||
|
||||
(mf/defc installed-font
|
||||
{::mf/props :obj
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
[app.main.data.notifications :as ntf]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.context-menu-a11y :refer [context-menu-a11y]]
|
||||
[app.main.ui.components.context-menu-a11y :refer [context-menu*]]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.dashboard.import :as udi]
|
||||
[app.util.dom :as dom]
|
||||
|
@ -81,53 +81,50 @@
|
|||
(fn []
|
||||
(when (fn? on-import) (on-import))))
|
||||
|
||||
options [(when-not (:is-default project)
|
||||
{:option-name (tr "labels.rename")
|
||||
:id "project-menu-rename"
|
||||
:option-handler on-edit
|
||||
:data-testid "project-rename"})
|
||||
(when-not (:is-default project)
|
||||
{:option-name (tr "dashboard.duplicate")
|
||||
:id "project-menu-duplicated"
|
||||
:option-handler on-duplicate
|
||||
:data-testid "project-duplicate"})
|
||||
(when-not (:is-default project)
|
||||
{:option-name (tr "dashboard.pin-unpin")
|
||||
:id "project-menu-pin"
|
||||
:option-handler toggle-pin})
|
||||
options
|
||||
[(when-not (:is-default project)
|
||||
{:name (tr "labels.rename")
|
||||
:id "project-rename"
|
||||
:handler on-edit})
|
||||
(when-not (:is-default project)
|
||||
{:name (tr "dashboard.duplicate")
|
||||
:id "project-duplicate"
|
||||
:handler on-duplicate})
|
||||
(when-not (:is-default project)
|
||||
{:name (tr "dashboard.pin-unpin")
|
||||
:id "project-pin"
|
||||
:handler toggle-pin})
|
||||
|
||||
(when (and (seq teams) (not (:is-default project)))
|
||||
{:option-name (tr "dashboard.move-to")
|
||||
:id "project-menu-move-to"
|
||||
:sub-options (for [team teams]
|
||||
{:option-name (:name team)
|
||||
:id (:name team)
|
||||
:option-handler (on-move (:id team))})
|
||||
:data-testid "project-move-to"})
|
||||
(when (some? on-import)
|
||||
{:option-name (tr "dashboard.import")
|
||||
:id "project-menu-import"
|
||||
:option-handler on-import-files
|
||||
:data-testid "file-import"})
|
||||
(when-not (:is-default project)
|
||||
{:option-name :separator})
|
||||
(when-not (:is-default project)
|
||||
{:option-name (tr "labels.delete")
|
||||
:id "project-menu-delete"
|
||||
:option-handler on-delete
|
||||
:data-testid "project-delete"})]]
|
||||
(when (and (seq teams) (not (:is-default project)))
|
||||
{:name (tr "dashboard.move-to")
|
||||
:id "project-move-to"
|
||||
:options (for [team teams]
|
||||
{:name (:name team)
|
||||
:id (str "move-to-" (:id team))
|
||||
:handler (on-move (:id team))})})
|
||||
|
||||
(when (some? on-import)
|
||||
{:name (tr "dashboard.import")
|
||||
:id "file-import"
|
||||
:handler on-import-files})
|
||||
(when-not (:is-default project)
|
||||
{:name :separator})
|
||||
(when-not (:is-default project)
|
||||
{:name (tr "labels.delete")
|
||||
:id "project-delete"
|
||||
:handler on-delete})]]
|
||||
|
||||
[:*
|
||||
[:& context-menu-a11y
|
||||
[:> context-menu*
|
||||
{:on-close on-menu-close
|
||||
:show show?
|
||||
:fixed? (or (not= top 0) (not= left 0))
|
||||
:min-width? true
|
||||
:fixed (or (not= top 0) (not= left 0))
|
||||
:min-width true
|
||||
:top top
|
||||
:left left
|
||||
:options options
|
||||
:workspace false}]
|
||||
:options options}]
|
||||
[:& udi/import-form {:ref file-input
|
||||
:project-id (:id project)
|
||||
:on-finish-import on-finish-import}]]))
|
||||
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
[app.main.data.notifications :as ntf]
|
||||
[app.main.data.users :as du]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.context-menu-a11y :refer [context-menu-a11y]]
|
||||
[app.main.ui.components.context-menu-a11y :refer [context-menu*]]
|
||||
[app.main.ui.components.forms :as fm]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.util.dom :as dom]
|
||||
|
@ -195,9 +195,9 @@
|
|||
(let [local (mf/use-state {:menu-open false})
|
||||
show? (:menu-open @local)
|
||||
options (mf/with-memo [on-delete]
|
||||
[{:option-name (tr "labels.delete")
|
||||
:id "access-token-delete"
|
||||
:option-handler on-delete}])
|
||||
[{:name (tr "labels.delete")
|
||||
:id "access-token-delete"
|
||||
:handler on-delete}])
|
||||
|
||||
menu-ref (mf/use-ref)
|
||||
|
||||
|
@ -224,11 +224,11 @@
|
|||
:on-click on-menu-click
|
||||
:on-key-down on-keydown}
|
||||
menu-icon
|
||||
[:& context-menu-a11y
|
||||
[:> context-menu*
|
||||
{:on-close on-menu-close
|
||||
:show show?
|
||||
:fixed? true
|
||||
:min-width? true
|
||||
:fixed true
|
||||
:min-width true
|
||||
:top "auto"
|
||||
:left "auto"
|
||||
:options options}]]))
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
[app.main.data.workspace.assets :as dwa]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.context-menu-a11y :refer [context-menu-a11y]]
|
||||
[app.main.ui.components.context-menu-a11y :refer [context-menu*]]
|
||||
[app.main.ui.components.search-bar :refer [search-bar]]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.icons :as i]
|
||||
|
@ -130,32 +130,26 @@
|
|||
on-menu-close
|
||||
(mf/use-fn #(swap! filters* assoc :open-menu false))
|
||||
|
||||
options (into [] (remove nil?
|
||||
[{:option-name (tr "workspace.assets.box-filter-all")
|
||||
:id "section-all"
|
||||
:option-handler on-section-filter-change
|
||||
:data-testid "all"}
|
||||
options
|
||||
[{:name (tr "workspace.assets.box-filter-all")
|
||||
:id "section-all"
|
||||
:handler on-section-filter-change}
|
||||
{:name (tr "workspace.assets.components")
|
||||
:id "section-components"
|
||||
:handler on-section-filter-change}
|
||||
|
||||
{:option-name (tr "workspace.assets.components")
|
||||
:id "section-components"
|
||||
:option-handler on-section-filter-change
|
||||
:data-testid "components"}
|
||||
(when (not components-v2)
|
||||
{:name (tr "workspace.assets.graphics")
|
||||
:id "section-graphics"
|
||||
:handler on-section-filter-change})
|
||||
|
||||
(when (not components-v2)
|
||||
{:option-name (tr "workspace.assets.graphics")
|
||||
:id "section-graphics"
|
||||
:option-handler on-section-filter-change
|
||||
:data-testid "graphics"})
|
||||
{:name (tr "workspace.assets.colors")
|
||||
:id "section-colors"
|
||||
:handler on-section-filter-change}
|
||||
|
||||
{:option-name (tr "workspace.assets.colors")
|
||||
:id "section-color"
|
||||
:option-handler on-section-filter-change
|
||||
:data-testid "colors"}
|
||||
|
||||
{:option-name (tr "workspace.assets.typography")
|
||||
:id "section-typography"
|
||||
:option-handler on-section-filter-change
|
||||
:data-testid "typographies"}]))]
|
||||
{:name (tr "workspace.assets.typography")
|
||||
:id "section-typographies"
|
||||
:handler on-section-filter-change}]]
|
||||
|
||||
[:article {:class (stl/css :assets-bar)}
|
||||
[:div {:class (stl/css :assets-header)}
|
||||
|
@ -177,18 +171,17 @@
|
|||
:class (stl/css-case :section-button true
|
||||
:opened menu-open?)}
|
||||
i/filter-icon]]
|
||||
[:& context-menu-a11y
|
||||
[:> context-menu*
|
||||
{:on-close on-menu-close
|
||||
:selectable true
|
||||
:selected section
|
||||
:show menu-open?
|
||||
:fixed? true
|
||||
:min-width? true
|
||||
:fixed true
|
||||
:min-width true
|
||||
:width size
|
||||
:top 158
|
||||
:left 18
|
||||
:options options
|
||||
:workspace? true}]
|
||||
:options options}]
|
||||
[:button {:class (stl/css :sort-button)
|
||||
:title (tr "workspace.assets.sort")
|
||||
:on-click toggle-ordering}
|
||||
|
|
|
@ -240,21 +240,21 @@
|
|||
{:on-close on-close-menu
|
||||
:state @menu-state
|
||||
:options [(when-not (or multi-colors? multi-assets?)
|
||||
{:option-name (tr "workspace.assets.rename")
|
||||
:id "assets-rename-color"
|
||||
:option-handler rename-color-clicked})
|
||||
{:name (tr "workspace.assets.rename")
|
||||
:id "assets-rename-color"
|
||||
:handler rename-color-clicked})
|
||||
(when-not (or multi-colors? multi-assets?)
|
||||
{:option-name (tr "workspace.assets.edit")
|
||||
:id "assets-edit-color"
|
||||
:option-handler edit-color-clicked})
|
||||
{:name (tr "workspace.assets.edit")
|
||||
:id "assets-edit-color"
|
||||
:handler edit-color-clicked})
|
||||
|
||||
{:option-name (tr "workspace.assets.delete")
|
||||
:id "assets-delete-color"
|
||||
:option-handler delete-color}
|
||||
{:name (tr "workspace.assets.delete")
|
||||
:id "assets-delete-color"
|
||||
:handler delete-color}
|
||||
(when-not multi-assets?
|
||||
{:option-name (tr "workspace.assets.group")
|
||||
:id "assets-group-color"
|
||||
:option-handler (on-group (:id color))})]}])
|
||||
{:name (tr "workspace.assets.group")
|
||||
:id "assets-group-color"
|
||||
:handler (on-group (:id color))})]}])
|
||||
|
||||
(when ^boolean dragging?
|
||||
[:div {:class (stl/css :dragging)}])]))
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
[app.main.refs :as refs]
|
||||
[app.main.render :refer [component-svg component-svg-thumbnail]]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.context-menu-a11y :refer [context-menu-a11y]]
|
||||
[app.main.ui.components.context-menu-a11y :refer [context-menu*]]
|
||||
[app.main.ui.components.title-bar :refer [title-bar]]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.icons :as i]
|
||||
|
@ -111,14 +111,13 @@
|
|||
(mf/defc assets-context-menu
|
||||
{::mf/wrap-props false}
|
||||
[{:keys [options state on-close]}]
|
||||
[:& context-menu-a11y
|
||||
[:> context-menu*
|
||||
{:show (:open? state)
|
||||
:fixed? (or (not= (:top state) 0) (not= (:left state) 0))
|
||||
:fixed (or (not= (:top state) 0) (not= (:left state) 0))
|
||||
:on-close on-close
|
||||
:top (:top state)
|
||||
:left (:left state)
|
||||
:options options
|
||||
:workspace? true}])
|
||||
:options options}])
|
||||
|
||||
(mf/defc section-icon
|
||||
{::mf/wrap-props false}
|
||||
|
|
|
@ -559,26 +559,26 @@
|
|||
{:on-close on-close-menu
|
||||
:state @menu-state
|
||||
:options [(when (and local? (not (or multi-components? multi-assets? read-only?)))
|
||||
{:option-name (tr "workspace.assets.rename")
|
||||
:id "assets-rename-component"
|
||||
:option-handler on-rename})
|
||||
{:name (tr "workspace.assets.rename")
|
||||
:id "assets-rename-component"
|
||||
:handler on-rename})
|
||||
(when (and local? (not (or multi-assets? read-only?)))
|
||||
{:option-name (if components-v2
|
||||
(tr "workspace.assets.duplicate-main")
|
||||
(tr "workspace.assets.duplicate"))
|
||||
:id "assets-duplicate-component"
|
||||
:option-handler on-duplicate})
|
||||
{:name (if components-v2
|
||||
(tr "workspace.assets.duplicate-main")
|
||||
(tr "workspace.assets.duplicate"))
|
||||
:id "assets-duplicate-component"
|
||||
:handler on-duplicate})
|
||||
|
||||
(when (and local? (not read-only?))
|
||||
{:option-name (tr "workspace.assets.delete")
|
||||
:id "assets-delete-component"
|
||||
:option-handler on-delete})
|
||||
{:name (tr "workspace.assets.delete")
|
||||
:id "assets-delete-component"
|
||||
:handler on-delete})
|
||||
(when (and local? (not (or multi-assets? read-only?)))
|
||||
{:option-name (tr "workspace.assets.group")
|
||||
:id "assets-group-component"
|
||||
:option-handler on-group})
|
||||
{:name (tr "workspace.assets.group")
|
||||
:id "assets-group-component"
|
||||
:handler on-group})
|
||||
|
||||
(when (and components-v2 (not multi-assets?))
|
||||
{:option-name (tr "workspace.shape.menu.show-main")
|
||||
:id "assets-show-main-component"
|
||||
:option-handler on-show-main})]}]]]))
|
||||
{:name (tr "workspace.shape.menu.show-main")
|
||||
:id "assets-show-main-component"
|
||||
:handler on-show-main})]}]]]))
|
||||
|
|
|
@ -418,13 +418,13 @@
|
|||
{:on-close on-close-menu
|
||||
:state @menu-state
|
||||
:options [(when-not (or multi-objects? multi-assets?)
|
||||
{:option-name (tr "workspace.assets.rename")
|
||||
:id "assets-rename-graphics"
|
||||
:option-handler on-rename})
|
||||
{:option-name (tr "workspace.assets.delete")
|
||||
:id "assets-delete-graphics"
|
||||
:option-handler on-delete}
|
||||
{:name (tr "workspace.assets.rename")
|
||||
:id "assets-rename-graphics"
|
||||
:handler on-rename})
|
||||
{:name (tr "workspace.assets.delete")
|
||||
:id "assets-delete-graphics"
|
||||
:handler on-delete}
|
||||
(when-not multi-assets?
|
||||
{:option-name (tr "workspace.assets.group")
|
||||
:id "assets-group-graphics"
|
||||
:option-handler on-group})]}])]]))
|
||||
{:name (tr "workspace.assets.group")
|
||||
:id "assets-group-graphics"
|
||||
:handler on-group})]}])]]))
|
||||
|
|
|
@ -59,12 +59,12 @@
|
|||
[:& cmm/assets-context-menu
|
||||
{:on-close on-close-menu
|
||||
:state @menu-state
|
||||
:options [{:option-name (tr "workspace.assets.rename")
|
||||
:id "assets-rename-group"
|
||||
:option-handler #(on-rename % path last-path)}
|
||||
{:option-name (tr "workspace.assets.ungroup")
|
||||
:id "assets-ungroup-group"
|
||||
:option-handler #(on-ungroup path)}]}]])))
|
||||
:options [{:name (tr "workspace.assets.rename")
|
||||
:id "assets-rename-group"
|
||||
:handler #(on-rename % path last-path)}
|
||||
{:name (tr "workspace.assets.ungroup")
|
||||
:id "assets-ungroup-group"
|
||||
:handler #(on-ungroup path)}]}]])))
|
||||
|
||||
(defn group-assets
|
||||
"Convert a list of assets in a nested structure like this:
|
||||
|
|
|
@ -434,27 +434,27 @@
|
|||
{:on-close on-close-menu
|
||||
:state @menu-state
|
||||
:options [(when-not (or multi-typographies? multi-assets?)
|
||||
{:option-name (tr "workspace.assets.rename")
|
||||
:id "assets-rename-typography"
|
||||
:option-handler handle-rename-typography-clicked})
|
||||
{:name (tr "workspace.assets.rename")
|
||||
:id "assets-rename-typography"
|
||||
:handler handle-rename-typography-clicked})
|
||||
|
||||
(when-not (or multi-typographies? multi-assets?)
|
||||
{:option-name (tr "workspace.assets.edit")
|
||||
:id "assets-edit-typography"
|
||||
:option-handler handle-edit-typography-clicked})
|
||||
{:name (tr "workspace.assets.edit")
|
||||
:id "assets-edit-typography"
|
||||
:handler handle-edit-typography-clicked})
|
||||
|
||||
{:option-name (tr "workspace.assets.delete")
|
||||
:id "assets-delete-typography"
|
||||
:option-handler handle-delete-typography}
|
||||
{:name (tr "workspace.assets.delete")
|
||||
:id "assets-delete-typography"
|
||||
:handler handle-delete-typography}
|
||||
|
||||
(when-not multi-assets?
|
||||
{:option-name (tr "workspace.assets.group")
|
||||
:id "assets-group-typography"
|
||||
:option-handler on-group})]}]
|
||||
{:name (tr "workspace.assets.group")
|
||||
:id "assets-group-typography"
|
||||
:handler on-group})]}]
|
||||
|
||||
[:& cmm/assets-context-menu
|
||||
{:on-close on-close-menu
|
||||
:state @menu-state
|
||||
:options [{:option-name "show info"
|
||||
:id "assets-rename-typography"
|
||||
:option-handler handle-edit-typography-clicked}]}])]]]))
|
||||
:options [{:name "show info"
|
||||
:id "assets-rename-typography"
|
||||
:handler handle-edit-typography-clicked}]}])]]]))
|
||||
|
|
Loading…
Add table
Reference in a new issue