This commit is contained in:
Eva Marco 2026-06-16 17:17:22 +02:00
parent fac7e457bd
commit f891f4237d
10 changed files with 641 additions and 550 deletions

View File

@ -200,7 +200,8 @@
:settings-feedback
:settings-subscription
:settings-integrations
:settings-notifications)
:settings-notifications
:settings-shortcuts)
(let [params (get params :query)
error-report-id (some-> params :error-report-id uuid/parse*)]
[:? [:> settings-page*

View File

@ -41,7 +41,8 @@
["/options" :settings-options]
["/subscriptions" :settings-subscription]
["/integrations" :settings-integrations]
["/notifications" :settings-notifications]]
["/notifications" :settings-notifications]
["/shortcuts" :settings-shortcuts]]
["/frame-preview" :frame-preview]

View File

@ -21,6 +21,7 @@
[app.main.ui.settings.options :refer [options-page*]]
[app.main.ui.settings.password :refer [password-page*]]
[app.main.ui.settings.profile :refer [profile-page*]]
[app.main.ui.settings.shortcuts :refer [shortcuts-page*]]
[app.main.ui.settings.sidebar :refer [sidebar*]]
[app.main.ui.settings.subscription :refer [subscription-page*]]
[app.util.i18n :as i18n :refer [tr]]
@ -77,7 +78,10 @@
[:> integrations-page*]
:settings-notifications
[:> notifications-page* {:profile profile}])]]]]))
[:> notifications-page* {:profile profile}]
:settings-shortcuts
[:> shortcuts-page* {:profile profile}])]]]]))
(mf/defc settings-page*
{::mf/lazy-load true}

View File

@ -0,0 +1,179 @@
(ns app.main.ui.settings.shortcuts
(:require-macros [app.main.style :as stl])
(:require
[app.common.data :as d]
[app.main.data.dashboard.shortcuts]
[app.main.data.modal :as modal]
[app.main.data.shortcuts :as ds]
[app.main.data.viewer.shortcuts]
[app.main.data.workspace.path.shortcuts]
[app.main.data.workspace.shortcuts]
[app.main.data.workspace.shortcuts.customize :as customize]
[app.main.store :as st]
[app.main.ui.ds.foundations.typography :as t]
[app.main.ui.ds.foundations.typography.heading :refer [heading*]]
[app.main.ui.ds.layout.tab-switcher :refer [tab-switcher*]]
[app.main.ui.shortcuts :as ss]
[app.util.dom :as dom]
[rumext.v2 :as mf]))
(def ^:private workspace-shortcuts-raw
(d/deep-merge app.main.data.workspace.path.shortcuts/shortcuts
app.main.data.workspace.shortcuts/shortcuts))
(mf/defc shortcuts-list*
[{:keys [profile workspace-sc dashboard-sc viewer-sc]}]
(let [open-sections* (mf/use-state [[1]])
open-sections (deref open-sections*)
custom-shortcuts (get-in profile [:props :custom-shortcuts])
workspace-translated (->> workspace-sc
(ss/add-translation :sc)
(into {}))
dashboard-translated (->> dashboard-sc
(ss/add-translation :sc)
(into {}))
viewer-translated (->> viewer-sc
(ss/add-translation :sc)
(into {}))
filter-term (mf/use-state "")
{:keys [all-shortcuts]}
(ss/build-all-shortcuts workspace-translated dashboard-translated viewer-translated)
section-has-content?
(fn [section]
(let [children (:children section)]
(if (and (= (count children) 1) (contains? children :none))
(seq (:children (:none children)))
(seq children))))
all-shortcuts (into {} (filter (fn [[_ v]] (section-has-content? v)) all-shortcuts))
on-edit-shortcut
(mf/use-callback
(mf/deps custom-shortcuts workspace-sc)
(fn [shortcut-key]
(let [default-command (:command (get workspace-shortcuts-raw shortcut-key))
current-command (or (get custom-shortcuts shortcut-key) default-command)]
(st/emit! (modal/show :shortcut-edit
{:shortcut-key shortcut-key
:shortcut-name (ss/translation-keyname :sc shortcut-key)
:current-command current-command
:default-command default-command
:all-shortcuts all-shortcuts})))))
on-reset-shortcut
(mf/use-callback
(fn [shortcut-key]
(st/emit! (customize/reset-custom-shortcut shortcut-key))))
manage-sections
(fn [item]
(fn [event]
(dom/stop-propagation event)
(let [is-present? (some #(= % item) open-sections)
new-value (if is-present?
(filterv (fn [element] (not= element item)) open-sections)
(conj open-sections item))]
(reset! open-sections* new-value))))]
(for [section all-shortcuts]
(let [[section-key _] section]
[:> ss/shortcut-section* {:key (name section-key)
:section section
:manage-sections manage-sections
:open-sections open-sections*
:filter-term filter-term
:editable? true
:custom-shortcuts custom-shortcuts
:on-edit on-edit-shortcut
:on-reset on-reset-shortcut}]))))
(mf/defc all-shortcuts-section*
[{:keys [profile]}]
(let [custom-shortcuts (get-in profile [:props :custom-shortcuts])
workspace-shortcuts-custom (ds/apply-custom-overrides workspace-shortcuts-raw custom-shortcuts)]
[:div {:class (stl/css :shortcuts-section)}
[:> shortcuts-list* {:profile profile
:workspace-sc workspace-shortcuts-custom
:dashboard-sc app.main.data.dashboard.shortcuts/shortcuts
:viewer-sc app.main.data.viewer.shortcuts/shortcuts}]]))
(mf/defc personalized-shortcuts-section*
[{:keys [profile]}]
(let [custom-shortcuts (get-in profile [:props :custom-shortcuts])
workspace-shortcuts-custom (ds/apply-custom-overrides workspace-shortcuts-raw custom-shortcuts)
personalized (select-keys workspace-shortcuts-custom (keys custom-shortcuts))]
[:div {:class (stl/css :shortcuts-section)}
(if (seq personalized)
[:> shortcuts-list* {:profile profile
:workspace-sc personalized
:dashboard-sc {}
:viewer-sc {}}]
[:p "There are no personalized shortcuts."])]))
(mf/defc not-assigned-shortcuts-section*
[{:keys [profile]}]
(let [custom-shortcuts (get-in profile [:props :custom-shortcuts])
workspace-shortcuts-custom (ds/apply-custom-overrides workspace-shortcuts-raw custom-shortcuts)
not-assigned (into {} (filter (fn [[_ v]]
(empty? (:command v)))
workspace-shortcuts-custom))]
[:div {:class (stl/css :shortcuts-section)}
(if (seq not-assigned)
[:> shortcuts-list* {:profile profile
:workspace-sc not-assigned
:dashboard-sc {}
:viewer-sc {}}]
[:p "There are no not-assigned shortcuts."])]))
(mf/defc shortcuts-page*
[{:keys [profile]}]
(let [section* (mf/use-state :all)
section (deref section*)
tabs
(mf/with-memo []
[{:label "All shortcuts"
:id "all"}
{:label "Personalized shortcuts"
:data-testid "personalized"
:id "personalized"}
{:label "Not assigned shortcuts"
:data-testid "not-assigned"
:id "not-assigned"}])
handle-change-tab
(mf/use-fn
(mf/deps)
(fn [new-section]
(reset! section* (keyword new-section))))]
[:section {:class (stl/css :shortcuts-page)
:aria-label "Shortcuts page"}
[:> heading* {:level 1
:typography t/title-large
:class (stl/css :color-primary)}
"Shortcuts page"]
[:div {:class (stl/css :shortcuts-content)}
[:p "search-bar"]
[:button "restore all"]
[:p "This is the shortcuts page content."]
[:> tab-switcher* {:tabs tabs
:selected (name section)
:on-change handle-change-tab
:class (stl/css :viewer-tab-switcher)}
(case section
:all
[:> all-shortcuts-section* {:profile profile}]
:personalized
[:> personalized-shortcuts-section* {:profile profile}]
:not-assigned
[:> not-assigned-shortcuts-section* {:profile profile}])]]]))

View File

@ -0,0 +1,17 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) KALEIDOS INC Sucursal en España SL
.shortcuts-page {
height: 100%;
}
.shortcuts-content {
padding: var(--sp-xl);
}
.shortcuts-section {
margin-bottom: var(--sp-lg);
}

View File

@ -48,6 +48,9 @@
(def ^:private go-settings-notifications
#(st/emit! (rt/nav :settings-notifications)))
(def ^:private go-settings-shortcuts
#(st/emit! (rt/nav :settings-shortcuts)))
(defn- show-release-notes
[event]
(let [version (:main cf/version)]
@ -66,6 +69,7 @@
subscription? (= section :settings-subscription)
integrations? (= section :settings-integrations)
notifications? (= section :settings-notifications)
shortcuts? (= section :settings-shortcuts)
team-id (or (dtm/get-last-team-id)
(:default-team-id profile))
@ -99,6 +103,11 @@
:settings-item true)
:on-click go-settings-notifications}
[:span {:class (stl/css :element-title)} (tr "labels.notifications")]]
[:li {:class (stl/css-case :current shortcuts?
:settings-item true)
:on-click go-settings-shortcuts}
[:span {:class (stl/css :element-title)} "shortcuts"]]
[:li {:class (stl/css-case :current options?
:settings-item true)

View File

@ -0,0 +1,281 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC Sucursal en España SL
(ns app.main.ui.shortcuts
(:require-macros [app.main.style :as stl])
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.config :as cf]
[app.main.data.shortcuts :as ds]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.foundations.assets.icon :as i :refer [icon*]]
[app.util.dom :as dom]
[app.util.i18n :refer [tr]]
[app.util.strings :refer [matches-search]]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(defn translation-keyname
[type keyname]
(let [translat-pre (case type
:sc "shortcuts."
:sec "shortcut-section."
:sub-sec "shortcut-subsection.")]
(tr (str translat-pre (d/name keyname)))))
(defn add-translation
[type item]
(map (fn [[k v]] [k (assoc v :translation (translation-keyname type k))]) item))
(defn shortcuts->subsections
[shortcuts]
(let [subsections (into #{} (mapcat :subsections) (vals shortcuts))
get-sc-by-subsection
(fn [subsection [k v]]
(when (some #(= subsection %) (:subsections v)) {k v}))
reduce-sc
(fn [acc subsection]
(let [shortcuts-by-subsection (into {} (keep (partial get-sc-by-subsection subsection) shortcuts))]
(assoc acc subsection {:children shortcuts-by-subsection})))]
(reduce reduce-sc {} subsections)))
(defn build-all-shortcuts
[workspace-sc dashboard-sc viewer-sc]
(let [walk (fn walk [element parent-id]
(if (nil? element)
element
(let [rec-fn (fn [index [k item]]
(let [item-id (if (nil? parent-id)
[index]
(conj parent-id index))]
[k (assoc item :id item-id :children (walk (:children item) item-id))]))]
(into {} (map-indexed (partial rec-fn) element)))))
ws-subs (->> (shortcuts->subsections workspace-sc)
(add-translation :sub-sec)
(into {}))
db-subs (->> (shortcuts->subsections dashboard-sc)
(add-translation :sub-sec)
(into {}))
vw-subs (->> (shortcuts->subsections viewer-sc)
(add-translation :sub-sec)
(into {}))
basics (into {} (concat (:children (:basics ws-subs))
(:children (:basics db-subs))
(:children (:basics vw-subs))))
ws-subs (dissoc ws-subs :basics)
db-subs (dissoc db-subs :basics)
vw-subs (dissoc vw-subs :basics)
all {:basics {:id [1]
:children {:none {:children basics}}
:translation (tr "shortcut-section.basics")}
:workspace {:id [2]
:children ws-subs
:translation (tr "shortcut-section.workspace")}
:dashboard {:id [3]
:children db-subs
:translation (tr "shortcut-section.dashboard")}
:viewer {:id [4]
:children vw-subs
:translation (tr "shortcut-section.viewer")}}
all (walk all nil)
all-sc-names (map #(translation-keyname :sc %)
(concat (keys workspace-sc)
(keys dashboard-sc)
(keys viewer-sc)))
all-sub-names (map #(translation-keyname :sub-sec %)
(concat (keys ws-subs)
(keys db-subs)
(keys vw-subs)))
all-section-names (map #(translation-keyname :sec %) (keys all))]
{:all-shortcuts all
:all-sc-names all-sc-names
:all-sub-names all-sub-names
:all-section-names all-section-names}))
(mf/defc converted-chars*
[{:keys [char command]}]
(let [modified-keys {:up ds/up-arrow
:down ds/down-arrow
:left ds/left-arrow
:right ds/right-arrow
:plus "+"}
macos-keys {:command "\u2318"
:option "\u2325"
:alt "\u2325"
:delete "\u232B"
:del "\u232B"
:shift "\u21E7"
:control "\u2303"
:esc "\u238B"
:enter "\u23CE"}
is-macos? (cf/check-platform? :macos)
char (if (contains? modified-keys (keyword char)) ((keyword char) modified-keys) char)
char (if (and is-macos? (contains? macos-keys (keyword char))) ((keyword char) macos-keys) char)
unique-key (str (d/name command) "-" char)]
[:span {:class (stl/css :key)
:key unique-key} char]))
(mf/defc shortcuts-keys*
[{:keys [content command]}]
(let [managed-list (if (coll? content)
content
(conj () content))
chars-list (map ds/split-sc managed-list)
last-element (last chars-list)
short-char-list (if (= 1 (count chars-list))
chars-list
(drop-last chars-list))
penultimate (last short-char-list)]
[:span {:class (stl/css :keys)}
(for [chars short-char-list]
[:* {:key (str/join chars)}
(for [char chars]
[:> converted-chars* {:key (dm/str char "-" (name command))
:char char
:command command}])
(when (not= chars penultimate) [:span {:class (stl/css :space)} ","])])
(when (not= last-element penultimate)
[:*
[:span {:class (stl/css :space)} (tr "shortcuts.or")]
(for [char last-element]
[:> converted-chars* {:key (dm/str char "-" (name command))
:char char
:command command}])])]))
(mf/defc shortcut-row*
[{:keys [elements filter-term is-match-section is-match-subsection
editable? custom-shortcuts on-edit on-reset]}]
(let [shortcut-name (keys elements)
shortcut-translations (map #(translation-keyname :sc %) shortcut-name)
match-shortcut? (some #(matches-search % @filter-term) shortcut-translations)
filtered (if (and (or is-match-section is-match-subsection) (not match-shortcut?))
shortcut-translations
(filter #(matches-search % @filter-term) shortcut-translations))
sorted-filtered (sort filtered)]
[:ul {:class (stl/css :sub-menu)}
(for [command-translate sorted-filtered]
(let [sc-by-translate (first (filter #(= (:translation (second %)) command-translate) elements))
[command comand-info] sc-by-translate
content (or (:show-command comand-info) (:command comand-info))
customized? (and editable? (contains? custom-shortcuts command))]
[:li {:class (stl/css-case :shortcuts-name true
:customized customized?)
:key command-translate}
[:span {:class (stl/css :command-name)}
command-translate]
[:div {:class (stl/css :shortcut-actions)}
[:> shortcuts-keys* {:content content
:command command}]
(when editable?
[:div {:class (stl/css :edit-buttons)}
(when customized?
[:> icon-button* {:variant "ghost"
:aria-label (tr "shortcuts.reset")
:on-click (fn [e]
(dom/stop-propagation e)
(on-reset command))
:icon i/reload
:icon-size "s"}])
[:> icon-button* {:variant "ghost"
:aria-label (tr "shortcuts.edit")
:on-click (fn [e]
(dom/stop-propagation e)
(on-edit command))
:icon i/curve
:icon-size "s"}]])]]))]))
(mf/defc section-title*
[{:keys [name is-visible is-sub]}]
[:div {:class (if is-sub
(stl/css :subsection-title)
(stl/css :section-title))}
[:> icon* {:icon-id (if is-visible i/arrow-down i/arrow-right)
:size "s"}]
[:span {:class (if is-sub
(stl/css :subsection-name)
(stl/css :section-name))} name]])
(mf/defc shortcut-subsection*
[{:keys [subsections manage-sections filter-term is-match-section open-sections
editable? custom-shortcuts on-edit on-reset]}]
(let [subsections-names (keys subsections)
subsection-translations (if (= :none (first subsections-names))
(map #(translation-keyname :sc %) subsections-names)
(map #(translation-keyname :sub-sec %) subsections-names))
sorted-translations (sort subsection-translations)]
(if (= :none (first subsections-names))
(let [basic-shortcuts (:none subsections)]
[:> shortcut-row* {:elements (:children basic-shortcuts)
:filter-term filter-term
:is-match-section is-match-section
:is-match-subsection true
:editable? editable?
:custom-shortcuts custom-shortcuts
:on-edit on-edit
:on-reset on-reset}])
[:ul {:class (stl/css :subsection-menu)}
(for [sub-translated sorted-translations]
(let [sub-by-translate (first (filter #(= (:translation (second %)) sub-translated) subsections))
[sub-name sub-info] sub-by-translate
visible? (some #(= % (:id sub-info)) @open-sections)
match-subsection? (matches-search (translation-keyname :sub-sec sub-name) @filter-term)
shortcut-names (map #(translation-keyname :sc %) (keys (:children sub-info)))
match-shortcuts? (some #(matches-search % @filter-term) shortcut-names)]
(when (or match-subsection? match-shortcuts? is-match-section)
[:li {:key sub-translated
:on-click (manage-sections (:id sub-info))}
[:> section-title* {:name sub-translated
:is-visible visible?
:is-sub true}]
[:div {:style {:display (if visible? "initial" "none")}}
[:> shortcut-row* {:elements (:children sub-info)
:filter-term filter-term
:is-match-section is-match-section
:is-match-subsection match-subsection?
:editable? editable?
:custom-shortcuts custom-shortcuts
:on-edit on-edit
:on-reset on-reset}]]])))])))
(mf/defc shortcut-section*
[{:keys [section manage-sections open-sections filter-term
editable? custom-shortcuts on-edit on-reset]}]
(let [[section-key section-info] section
section-id (:id section-info)
section-translation (translation-keyname :sec section-key)
match-section? (matches-search section-translation @filter-term)
subsections (:children section-info)
subs-names (keys subsections)
subs-bodys (reduce #(conj %1 (:children (%2 subsections))) {} subs-names)
sub-trans (map #(if (= "none" (d/name %))
nil
(translation-keyname :sub-sec %)) subs-names)
match-subsection? (some #(matches-search % @filter-term) sub-trans)
translations (map #(translation-keyname :sc %) (keys subs-bodys))
match-shortcut? (some #(matches-search % @filter-term) translations)
visible? (some #(= % section-id) @open-sections)]
(when (or match-section? match-subsection? match-shortcut?)
[:div {:class (stl/css :section)
:on-click (manage-sections section-id)}
[:> section-title* {:name section-translation
:is-visible visible?
:is-sub false}]
[:div {:style {:display (if visible? "initial" "none")}}
[:> shortcut-subsection* {:subsections subsections
:open-sections open-sections
:manage-sections manage-sections
:is-match-section match-section?
:filter-term filter-term
:editable? editable?
:custom-shortcuts custom-shortcuts
:on-edit on-edit
:on-reset on-reset}]]])))

View File

@ -0,0 +1,122 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) KALEIDOS INC Sucursal en España SL
@use "refactor/common-refactor.scss" as deprecated;
.section {
margin: 0;
}
.shortcuts-list {
display: flex;
flex-direction: column;
height: 100%;
padding: deprecated.$s-12;
overflow-y: auto;
font-size: deprecated.$fs-12;
color: var(--title-foreground-color);
}
.section-title,
.subsection-title {
@include deprecated.uppercase-title-typography;
display: flex;
align-items: center;
margin: 0;
padding: deprecated.$s-8 0;
cursor: pointer;
.subsection-name,
.section-name {
padding-left: deprecated.$s-4;
}
&:hover {
color: var(--title-foreground-color-hover);
}
}
.subsection-title {
text-transform: none;
padding-left: deprecated.$s-12;
}
.subsection-menu {
margin-bottom: deprecated.$s-4;
}
.sub-menu {
margin-bottom: deprecated.$s-4;
.shortcuts-name {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
min-height: deprecated.$s-32;
padding: deprecated.$s-6;
margin-bottom: deprecated.$s-4;
border-radius: deprecated.$br-8;
background-color: var(--pill-background-color);
.command-name {
@include deprecated.body-small-typography;
margin-left: deprecated.$s-2;
color: var(--pill-foreground-color);
flex: 1;
min-width: 0;
}
.shortcut-actions {
display: flex;
align-items: center;
gap: deprecated.$s-2;
}
.edit-buttons {
display: flex;
align-items: center;
opacity: 0;
transition: opacity 0.15s ease;
}
&:hover .edit-buttons {
opacity: 1;
}
&.customized {
background-color: var(--pill-background-color-hover, var(--pill-background-color));
.edit-buttons {
opacity: 1;
}
}
.keys {
@include deprecated.flex-center;
gap: deprecated.$s-2;
color: var(--pill-foreground-color);
.key {
@include deprecated.body-small-typography;
@include deprecated.flex-center;
text-transform: capitalize;
height: deprecated.$s-20;
padding: deprecated.$s-2 deprecated.$s-6;
border-radius: deprecated.$s-6;
background-color: var(--menu-shortcut-background-color);
}
.space {
margin: 0 deprecated.$s-2;
}
}
}
}

View File

@ -9,7 +9,6 @@
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.config :as cf]
[app.main.data.dashboard.shortcuts]
[app.main.data.modal :as modal]
[app.main.data.shortcuts :as ds]
@ -22,385 +21,16 @@
[app.main.store :as st]
[app.main.ui.components.search-bar :refer [search-bar*]]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.foundations.assets.icon :as i :refer [icon*]]
[app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.ds.product.panel-title :refer [panel-title*]]
[app.main.ui.shortcuts :as ss]
[app.main.ui.workspace.sidebar.shortcuts.edit-modal]
[app.util.dom :as dom]
[app.util.i18n :refer [tr]]
[app.util.strings :refer [matches-search]]
[clojure.set :as set]
[clojure.string]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(mf/defc converted-chars*
[{:keys [char command]}]
(let [modified-keys {:up ds/up-arrow
:down ds/down-arrow
:left ds/left-arrow
:right ds/right-arrow
:plus "+"}
macos-keys {:command "\u2318"
:option "\u2325"
:alt "\u2325"
:delete "\u232B"
:del "\u232B"
:shift "\u21E7"
:control "\u2303"
:esc "\u238B"
:enter "\u23CE"}
is-macos? (cf/check-platform? :macos)
char (if (contains? modified-keys (keyword char)) ((keyword char) modified-keys) char)
char (if (and is-macos? (contains? macos-keys (keyword char))) ((keyword char) macos-keys) char)
unique-key (str (d/name command) "-" char)]
[:span {:class (stl/css :key)
:key unique-key} char]))
(defn translation-keyname
[type keyname]
;; Execution time translation strings:
(comment
(tr "shortcut-subsection.alignment")
(tr "shortcut-subsection.edit")
(tr "shortcut-subsection.general-dashboard")
(tr "shortcut-subsection.general-viewer")
(tr "shortcut-subsection.main-menu")
(tr "shortcut-subsection.modify-layers")
(tr "shortcut-subsection.navigation-dashboard")
(tr "shortcut-subsection.navigation-viewer")
(tr "shortcut-subsection.navigation-workspace")
(tr "shortcut-subsection.panels")
(tr "shortcut-subsection.path-editor")
(tr "shortcut-subsection.shape")
(tr "shortcut-subsection.text-editor")
(tr "shortcut-subsection.tools")
(tr "shortcut-subsection.zoom-viewer")
(tr "shortcut-subsection.zoom-workspace")
(tr "shortcuts.add-comment")
(tr "shortcuts.add-node")
(tr "shortcuts.align-bottom")
(tr "shortcuts.align-center")
(tr "shortcuts.align-hcenter")
(tr "shortcuts.align-justify")
(tr "shortcuts.align-left")
(tr "shortcuts.align-right")
(tr "shortcuts.align-top")
(tr "shortcuts.align-vcenter")
(tr "shortcuts.artboard-selection")
(tr "shortcuts.bold")
(tr "shortcuts.bool-difference")
(tr "shortcuts.bool-exclude")
(tr "shortcuts.bool-intersection")
(tr "shortcuts.bool-union")
(tr "shortcuts.bring-back")
(tr "shortcuts.bring-backward")
(tr "shortcuts.bring-forward")
(tr "shortcuts.bring-front")
(tr "shortcuts.clear-undo")
(tr "shortcuts.copy")
(tr "shortcuts.copy-link")
(tr "shortcuts.create-component-variant")
(tr "shortcuts.create-new-project")
(tr "shortcuts.cut")
(tr "shortcuts.decrease-zoom")
(tr "shortcuts.delete")
(tr "shortcuts.delete-node")
(tr "shortcuts.detach-component")
(tr "shortcuts.draw-curve")
(tr "shortcuts.draw-ellipse")
(tr "shortcuts.draw-frame")
(tr "shortcuts.draw-nodes")
(tr "shortcuts.draw-path")
(tr "shortcuts.draw-rect")
(tr "shortcuts.draw-text")
(tr "shortcuts.duplicate")
(tr "shortcuts.escape")
(tr "shortcuts.export-shapes")
(tr "shortcuts.find")
(tr "shortcuts.find-and-replace")
(tr "shortcuts.fit-all")
(tr "shortcuts.flip-horizontal")
(tr "shortcuts.flip-vertical")
(tr "shortcuts.font-size-dec")
(tr "shortcuts.font-size-inc")
(tr "shortcuts.go-to-drafts")
(tr "shortcuts.go-to-libs")
(tr "shortcuts.go-to-search")
(tr "shortcuts.group")
(tr "shortcuts.h-distribute")
(tr "shortcuts.hide-ui")
(tr "shortcuts.increase-zoom")
(tr "shortcuts.insert-image")
(tr "shortcuts.italic")
(tr "shortcuts.join-nodes")
(tr "shortcuts.line-through")
(tr "shortcuts.make-corner")
(tr "shortcuts.make-curve")
(tr "shortcuts.mask")
(tr "shortcuts.merge-nodes")
(tr "shortcuts.move")
(tr "shortcuts.move-fast-down")
(tr "shortcuts.move-fast-left")
(tr "shortcuts.move-fast-right")
(tr "shortcuts.move-fast-up")
(tr "shortcuts.move-nodes")
(tr "shortcuts.move-unit-down")
(tr "shortcuts.move-unit-left")
(tr "shortcuts.move-unit-right")
(tr "shortcuts.move-unit-up")
(tr "shortcuts.next-frame")
(tr "shortcuts.opacity-0")
(tr "shortcuts.opacity-1")
(tr "shortcuts.opacity-2")
(tr "shortcuts.opacity-3")
(tr "shortcuts.opacity-4")
(tr "shortcuts.opacity-5")
(tr "shortcuts.opacity-6")
(tr "shortcuts.opacity-7")
(tr "shortcuts.opacity-8")
(tr "shortcuts.opacity-9")
(tr "shortcuts.open-color-picker")
(tr "shortcuts.open-comments")
(tr "shortcuts.open-dashboard")
(tr "shortcuts.open-inspect")
(tr "shortcuts.open-interactions")
(tr "shortcuts.open-viewer")
(tr "shortcuts.open-workspace")
(tr "shortcuts.paste")
(tr "shortcuts.paste-replace")
(tr "shortcuts.prev-frame")
(tr "shortcuts.redo")
(tr "shortcuts.rename")
(tr "shortcuts.reset-zoom")
(tr "shortcuts.scale")
(tr "shortcuts.search-placeholder")
(tr "shortcuts.select-all")
(tr "shortcuts.select-next")
(tr "shortcuts.select-parent-layer")
(tr "shortcuts.select-prev")
(tr "shortcuts.separate-nodes")
(tr "shortcuts.show-pixel-grid")
(tr "shortcuts.show-shortcuts")
(tr "shortcuts.snap-nodes")
(tr "shortcuts.snap-pixel-grid")
(tr "shortcuts.start-editing")
(tr "shortcuts.start-measure")
(tr "shortcuts.stop-measure")
(tr "shortcuts.thumbnail-set")
(tr "shortcuts.toggle-alignment")
(tr "shortcuts.toggle-assets")
(tr "shortcuts.toggle-colorpalette")
(tr "shortcuts.toggle-focus-mode")
(tr "shortcuts.toggle-fullscreen")
(tr "shortcuts.toggle-guides")
(tr "shortcuts.toggle-history")
(tr "shortcuts.toggle-layers")
(tr "shortcuts.toggle-layout-flex")
(tr "shortcuts.toggle-layout-grid")
(tr "shortcuts.toggle-lock")
(tr "shortcuts.toggle-lock-size")
(tr "shortcuts.toggle-rulers")
(tr "shortcuts.toggle-snap-guides")
(tr "shortcuts.toggle-snap-ruler-guide")
(tr "shortcuts.toggle-textpalette")
(tr "shortcuts.toggle-theme")
(tr "shortcuts.toggle-visibility")
(tr "shortcuts.toggle-zoom-style")
(tr "shortcuts.underline")
(tr "shortcuts.undo")
(tr "shortcuts.ungroup")
(tr "shortcuts.unmask")
(tr "shortcuts.v-distribute")
(tr "shortcuts.zoom-lense-decrease")
(tr "shortcuts.zoom-lense-increase")
(tr "shortcuts.zoom-selected"))
(let [translat-pre (case type
:sc "shortcuts."
:sec "shortcut-section."
:sub-sec "shortcut-subsection.")]
(tr (str translat-pre (d/name keyname)))))
(defn add-translation
[type item]
(map (fn [[k v]] [k (assoc v :translation (translation-keyname type k))]) item))
(defn shortcuts->subsections
"A function to obtain the list of subsections and their
associated shortcus from the general map of shortcuts"
[shortcuts]
(let [subsections (into #{} (mapcat :subsections) (vals shortcuts))
get-sc-by-subsection
(fn [subsection [k v]]
(when (some #(= subsection %) (:subsections v)) {k v}))
reduce-sc
(fn [acc subsection]
(let [shortcuts-by-subsection (into {} (keep (partial get-sc-by-subsection subsection) shortcuts))]
(assoc acc subsection {:children shortcuts-by-subsection})))]
(reduce reduce-sc {} subsections)))
(mf/defc shortcuts-keys*
[{:keys [content command]}]
(let [managed-list (if (coll? content)
content
(conj () content))
chars-list (map ds/split-sc managed-list)
last-element (last chars-list)
short-char-list (if (= 1 (count chars-list))
chars-list
(drop-last chars-list))
penultimate (last short-char-list)]
[:span {:class (stl/css :keys)}
(for [chars short-char-list]
[:* {:key (str/join chars)}
(for [char chars]
[:> converted-chars* {:key (dm/str char "-" (name command))
:char char
:command command}])
(when (not= chars penultimate) [:span {:class (stl/css :space)} ","])])
(when (not= last-element penultimate)
[:*
[:span {:class (stl/css :space)} (tr "shortcuts.or")]
(for [char last-element]
[:> converted-chars* {:key (dm/str char "-" (name command))
:char char
:command command}])])]))
(mf/defc shortcut-row*
[{:keys [elements filter-term is-match-section is-match-subsection
editable? custom-shortcuts on-edit on-reset]}]
(let [shortcut-name (keys elements)
shortcut-translations (map #(translation-keyname :sc %) shortcut-name)
match-shortcut? (some #(matches-search % @filter-term) shortcut-translations)
filtered (if (and (or is-match-section is-match-subsection) (not match-shortcut?))
shortcut-translations
(filter #(matches-search % @filter-term) shortcut-translations))
sorted-filtered (sort filtered)]
[:ul {:class (stl/css :sub-menu)}
(for [command-translate sorted-filtered]
(let [sc-by-translate (first (filter #(= (:translation (second %)) command-translate) elements))
[command comand-info] sc-by-translate
content (or (:show-command comand-info) (:command comand-info))
customized? (and editable? (contains? custom-shortcuts command))]
[:li {:class (stl/css-case :shortcuts-name true
:customized customized?)
:key command-translate}
[:span {:class (stl/css :command-name)}
command-translate]
[:div {:class (stl/css :shortcut-actions)}
[:> shortcuts-keys* {:content content
:command command}]
(when editable?
[:div {:class (stl/css :edit-buttons)}
(when customized?
[:> icon-button* {:variant "ghost"
:aria-label (tr "shortcuts.reset")
:on-click (fn [e]
(dom/stop-propagation e)
(on-reset command))
:icon i/reload
:icon-size "s"}])
[:> icon-button* {:variant "ghost"
:aria-label (tr "shortcuts.edit")
:on-click (fn [e]
(dom/stop-propagation e)
(on-edit command))
:icon i/curve
:icon-size "s"}]])]]))]))
(mf/defc section-title*
[{:keys [name is-visible is-sub]}]
[:div {:class (if is-sub
(stl/css :subsection-title)
(stl/css :section-title))}
[:> icon* {:icon-id (if is-visible i/arrow-down i/arrow-right)
:size "s"}]
[:span {:class (if is-sub
(stl/css :subsection-name)
(stl/css :section-name))} name]])
(mf/defc shortcut-subsection*
[{:keys [subsections manage-sections filter-term is-match-section open-sections
editable? custom-shortcuts on-edit on-reset]}]
(let [subsections-names (keys subsections)
subsection-translations (if (= :none (first subsections-names))
(map #(translation-keyname :sc %) subsections-names)
(map #(translation-keyname :sub-sec %) subsections-names))
sorted-translations (sort subsection-translations)]
;; Basics section is treated different because it has no sub sections
(if (= :none (first subsections-names))
(let [basic-shortcuts (:none subsections)]
[:> shortcut-row* {:elements (:children basic-shortcuts)
:filter-term filter-term
:is-match-section is-match-section
:is-match-subsection true
:editable? editable?
:custom-shortcuts custom-shortcuts
:on-edit on-edit
:on-reset on-reset}])
[:ul {:class (stl/css :subsection-menu)}
(for [sub-translated sorted-translations]
(let [sub-by-translate (first (filter #(= (:translation (second %)) sub-translated) subsections))
[sub-name sub-info] sub-by-translate
visible? (some #(= % (:id sub-info)) @open-sections)
match-subsection? (matches-search (translation-keyname :sub-sec sub-name) @filter-term)
shortcut-names (map #(translation-keyname :sc %) (keys (:children sub-info)))
match-shortcuts? (some #(matches-search % @filter-term) shortcut-names)]
(when (or match-subsection? match-shortcuts? is-match-section)
[:li {:key sub-translated
:on-click (manage-sections (:id sub-info))}
[:> section-title* {:name sub-translated
:is-visible visible?
:is-sub true}]
[:div {:style {:display (if visible? "initial" "none")}}
[:> shortcut-row* {:elements (:children sub-info)
:filter-term filter-term
:is-match-section is-match-section
:is-match-subsection match-subsection?
:editable? editable?
:custom-shortcuts custom-shortcuts
:on-edit on-edit
:on-reset on-reset}]]])))])))
(mf/defc shortcut-section*
[{:keys [section manage-sections open-sections filter-term
editable? custom-shortcuts on-edit on-reset]}]
(let [[section-key section-info] section
section-id (:id section-info)
section-translation (translation-keyname :sec section-key)
match-section? (matches-search section-translation @filter-term)
subsections (:children section-info)
subs-names (keys subsections)
subs-bodys (reduce #(conj %1 (:children (%2 subsections))) {} subs-names)
sub-trans (map #(if (= "none" (d/name %))
nil
(translation-keyname :sub-sec %)) subs-names)
match-subsection? (some #(matches-search % @filter-term) sub-trans)
translations (map #(translation-keyname :sc %) (keys subs-bodys))
match-shortcut? (some #(matches-search % @filter-term) translations)
visible? (some #(= % section-id) @open-sections)]
(when (or match-section? match-subsection? match-shortcut?)
[:div {:class (stl/css :section)
:on-click (manage-sections section-id)}
[:> section-title* {:name section-translation
:is-visible visible?
:is-sub false}]
[:div {:style {:display (if visible? "initial" "none")}}
[:> shortcut-subsection* {:subsections subsections
:open-sections open-sections
:manage-sections manage-sections
:is-match-section match-section?
:filter-term filter-term
:editable? editable?
:custom-shortcuts custom-shortcuts
:on-edit on-edit
:on-reset on-reset}]]])))
(def ^:private workspace-shortcuts-raw
(d/deep-merge app.main.data.workspace.path.shortcuts/shortcuts
app.main.data.workspace.shortcuts/shortcuts))
@ -413,71 +43,23 @@
workspace-shortcuts-custom (ds/apply-custom-overrides workspace-shortcuts-raw custom-shortcuts)
all-workspace-shortcuts (->> workspace-shortcuts-custom
(add-translation :sc)
(ss/add-translation :sc)
(into {}))
dashboard-shortcuts (->> app.main.data.dashboard.shortcuts/shortcuts
(add-translation :sc)
(ss/add-translation :sc)
(into {}))
viewer-shortcuts (->> app.main.data.viewer.shortcuts/shortcuts
(add-translation :sc)
(ss/add-translation :sc)
(into {}))
open-sections (mf/use-state [[1]])
filter-term (mf/use-state "")
close-fn #(st/emit! (dw/toggle-layout-flag :shortcuts))
walk (fn walk [element parent-id]
(if (nil? element)
element
(let [rec-fn (fn [index [k item]]
(let [item-id (if (nil? parent-id)
[index]
(conj parent-id index))]
[k (assoc item :id item-id :children (walk (:children item) item-id))]))]
(into {} (map-indexed (partial rec-fn) element)))))
{:keys [all-shortcuts all-sc-names all-sub-names all-section-names]}
(ss/build-all-shortcuts all-workspace-shortcuts dashboard-shortcuts viewer-shortcuts)
workspace-sc-by-subsections (->> (shortcuts->subsections all-workspace-shortcuts)
(add-translation :sub-sec)
(into {}))
dashboard-sc-by-subsections (->> (shortcuts->subsections dashboard-shortcuts)
(add-translation :sub-sec)
(into {}))
viewer-sc-by-subsections (->> (shortcuts->subsections viewer-shortcuts)
(add-translation :sub-sec)
(into {}))
basics-elements (into {} (concat (:children (:basics workspace-sc-by-subsections))
(:children (:basics dashboard-sc-by-subsections))
(:children (:basics viewer-sc-by-subsections))))
workspace-sc-by-subsections (dissoc workspace-sc-by-subsections :basics)
dashboard-sc-by-subsections (dissoc dashboard-sc-by-subsections :basics)
viewer-sc-by-subsections (dissoc viewer-sc-by-subsections :bassics)
all-shortcuts {:basics {:id [1]
:children {:none {:children basics-elements}}
:translation (tr "shortcut-section.basics")}
:workspace {:id [2]
:children workspace-sc-by-subsections
:translation (tr "shortcut-section.workspace")}
:dashboard {:id [3]
:children dashboard-sc-by-subsections
:translation (tr "shortcut-section.dashboard")}
:viewer {:id [4]
:children viewer-sc-by-subsections
:translation (tr "shortcut-section.viewer")}}
all-shortcuts (walk all-shortcuts nil)
all-sc-names (map #(translation-keyname :sc %) (concat
(keys all-workspace-shortcuts)
(keys dashboard-shortcuts)
(keys viewer-shortcuts)))
all-sub-names (map #(translation-keyname :sub-sec %) (concat
(keys workspace-sc-by-subsections)
(keys dashboard-sc-by-subsections)
(keys viewer-sc-by-subsections)))
all-section-names (map #(translation-keyname :sec %) (keys all-shortcuts))
all-item-names (concat all-sc-names all-sub-names all-section-names)
match-any? (some #(matches-search % @filter-term) all-item-names)
@ -541,7 +123,7 @@
current-command (or (get custom-shortcuts shortcut-key) default-command)]
(st/emit! (modal/show :shortcut-edit
{:shortcut-key shortcut-key
:shortcut-name (translation-keyname :sc shortcut-key)
:shortcut-name (ss/translation-keyname :sc shortcut-key)
:current-command current-command
:default-command default-command
:all-shortcuts workspace-shortcuts-custom})))))
@ -580,13 +162,13 @@
(for [section all-shortcuts]
(let [[section-key _] section
ws-editable? (contains? #{:basics :workspace} section-key)]
[:> shortcut-section* {:key (->> section second :id first)
:section section
:manage-sections manage-sections
:open-sections open-sections
:filter-term filter-term
:editable? ws-editable?
:custom-shortcuts custom-shortcuts
:on-edit on-edit-shortcut
:on-reset on-reset-shortcut}]))]
[:> ss/shortcut-section* {:key (name section-key)
:section section
:manage-sections manage-sections
:open-sections open-sections
:filter-term filter-term
:editable? false
:custom-shortcuts custom-shortcuts
:on-edit on-edit-shortcut
:on-reset on-reset-shortcut}]))]
[:div {:class (stl/css :not-found)} (tr "shortcuts.not-found")])]))

View File

@ -32,17 +32,6 @@
margin: var(--sp-s) var(--sp-s) 0 var(--sp-s);
}
.section {
margin: 0;
}
.not-found {
@include deprecated.body-small-typography;
color: var(--empty-message-foreground-color);
margin: deprecated.$s-12;
}
.shortcuts-list {
display: flex;
flex-direction: column;
@ -51,105 +40,11 @@
overflow-y: auto;
font-size: deprecated.$fs-12;
color: var(--title-foreground-color);
.section-title,
.subsection-title {
@include deprecated.uppercase-title-typography;
display: flex;
align-items: center;
margin: 0;
padding: deprecated.$s-8 0;
cursor: pointer;
.subsection-name,
.section-name {
padding-left: deprecated.$s-4;
}
&:hover {
color: var(--title-foreground-color-hover);
}
}
.subsection-title {
text-transform: none;
padding-left: deprecated.$s-12;
}
.subsection-menu {
margin-bottom: deprecated.$s-4;
}
.sub-menu {
margin-bottom: deprecated.$s-4;
.shortcuts-name {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
min-height: deprecated.$s-32;
padding: deprecated.$s-6;
margin-bottom: deprecated.$s-4;
border-radius: deprecated.$br-8;
background-color: var(--pill-background-color);
.command-name {
@include deprecated.body-small-typography;
margin-left: deprecated.$s-2;
color: var(--pill-foreground-color);
flex: 1;
min-width: 0;
}
.shortcut-actions {
display: flex;
align-items: center;
gap: deprecated.$s-2;
}
.edit-buttons {
display: flex;
align-items: center;
opacity: 0;
transition: opacity 0.15s ease;
}
&:hover .edit-buttons {
opacity: 1;
}
&.customized {
background-color: var(--pill-background-color-hover, var(--pill-background-color));
.edit-buttons {
opacity: 1;
}
}
.keys {
@include deprecated.flex-center;
gap: deprecated.$s-2;
color: var(--pill-foreground-color);
.key {
@include deprecated.body-small-typography;
@include deprecated.flex-center;
text-transform: capitalize;
height: deprecated.$s-20;
padding: deprecated.$s-2 deprecated.$s-6;
border-radius: deprecated.$s-6;
background-color: var(--menu-shortcut-background-color);
}
.space {
margin: 0 deprecated.$s-2;
}
}
}
}
}
.not-found {
@include deprecated.body-small-typography;
color: var(--empty-message-foreground-color);
margin: deprecated.$s-12;
}