Andrey Antukh 4b978767ea
🌐 Clean up en translations (#11853)
Drop 277 keys nothing references from en.po (verified against
frontend/src and common/src) and let sync propagate the
deletions to every locale. Clear all 10 fuzzy entries: fill
the 5 empty translations, keep the 4 valid ones, and drop the
duplicated max-quote-reached in favor of max-quota-reached
(the backend code stays, the UI maps it to the quota text).

Recover 22 used-but-missing keys with translations: the 19
shortcuts section/subsection labels plus connected-to,
pixel-grid-color and tokens.add-set. Make the rest
statically visible to rehash instead: :label fns on shortcut
commands, sections and subsections (one debug-only and one
colorpicker-local id exempt); case branches in place of
dm/str-built keys (export modal, text decoration and
transform, undo history with raw-key fallback); hoist
conditionals out of tr calls; pre-translate modal props and
role labels; replace the lone (i18n/tr ...) site with tr.
Turn static :error/code data into eager :error/fn calls in
the common schemas and the auth/password forms. Rename the
two keys containing spaces and point team leave at
max-quota-reached. Backend-driven keys stay dynamic by
design, declared with (tr ...) comments: the five
weak-password details, team and organization notifications.

Tooling: rehash also scans common/src and no longer treats
a missing -l as no locale; new clj-kondo tr-dynamic warning
flags non-literal tr args (lint scripts use --fail-level
error so it never fails CI); tr docstring states the
literal-only rule. Tests cover the shortcut label wiring,
the undo-history fallback and the :error/fn schemas.
Translations memory rewritten to match; es check word list
gains three entries.

Rebased onto develop: adopt the register field-error UX
(the weak-password declarations move onto the :options
code), keep develop's newer keys (connection-error,
account-locked, save-retrying, tokens-source strings) with
fresh references, and reword the shortcuts.cljs prose
comment so rehash does not invent a "literal" key.

AI-assisted-by: muse-spark-1.3-contributor
2026-09-25 11:11:44 +02:00

935 lines
44 KiB
Clojure

;; 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 SUBSIDIARY SL
(ns app.main.data.workspace.shortcuts
(:require
[app.config :as cf]
[app.main.data.common :as dcm]
[app.main.data.event :as ev]
[app.main.data.exports.assets :as de]
[app.main.data.modal :as modal]
[app.main.data.preview :as dp]
[app.main.data.profile :as du]
[app.main.data.shortcuts :as ds]
[app.main.data.workspace :as dw]
[app.main.data.workspace.colors :as mdc]
[app.main.data.workspace.comments :as dwcm]
[app.main.data.workspace.drawing :as dwd]
[app.main.data.workspace.drawing.common :as dwdc]
[app.main.data.workspace.layers :as dwly]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.shape-layout :as dwsl]
[app.main.data.workspace.shapes :as dws]
[app.main.data.workspace.text.shortcuts :as dwtxts]
[app.main.data.workspace.texts :as dwtxt]
[app.main.data.workspace.transforms :as dwt]
[app.main.data.workspace.undo :as dwu]
[app.main.data.workspace.variants :as dwv]
[app.main.features :as features]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.hooks.resize :as r]
[app.util.dom :as dom]
[app.util.i18n :refer [tr]]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Shortcuts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- toggle-layout-flag
[flag]
(-> (dw/toggle-layout-flag flag)
(vary-meta assoc ::ev/origin "workspace-shortcuts")))
(defn on-display-guides-keydown
[^js event]
(let [mod? (if (cf/check-platform? :macos)
(.-metaKey event)
(.-ctrlKey event))
shift? (.-shiftKey event)
code (.-code event)]
(when (and mod?
(or (and (not shift?) (= "Quote" code))
(and shift? (= "Backslash" code))))
(.preventDefault event)
(st/emit! (toggle-layout-flag :display-guides)))))
(defn- emit-when-no-readonly
[& events]
(let [can-edit? (:can-edit (deref refs/permissions))
read-only? (deref refs/workspace-read-only?)]
(when (and can-edit? (not read-only?))
(run! st/emit! events))))
;; Shortcuts format https://github.com/ccampbell/mousetrap
(def base-shortcuts
{;; EDIT
:undo {:tooltip (ds/meta "Z")
:label (fn [] (tr "shortcuts.undo"))
:command (ds/c-mod "z")
:section [:workspace]
:subsections [:edit]
:fn #(emit-when-no-readonly dwu/undo)}
:redo {:tooltip (ds/meta "Y")
:label (fn [] (tr "shortcuts.redo"))
:command [(ds/c-mod "shift+z") (ds/c-mod "y")]
:section [:workspace]
:subsections [:edit]
:fn #(emit-when-no-readonly dwu/redo)}
:clear-undo {:tooltip (ds/alt "Q")
:label (fn [] (tr "shortcuts.clear-undo"))
:command "alt+q"
:subsections [:edit]
:section [:workspace]
:fn #(emit-when-no-readonly dwu/reinitialize-undo)}
:copy {:tooltip (ds/meta "C")
:label (fn [] (tr "shortcuts.copy"))
:command (ds/c-mod "c")
:subsections [:edit]
:section [:workspace]
:fn #(st/emit! (dw/copy-selected))}
:copy-link {:tooltip (ds/shift (ds/alt "C"))
:label (fn [] (tr "shortcuts.copy-link"))
:command "shift+alt+c"
:subsections [:edit]
:section [:workspace]
:fn #(st/emit! (dw/copy-link-to-clipboard))}
:cut {:tooltip (ds/meta "X")
:label (fn [] (tr "shortcuts.cut"))
:command (ds/c-mod "x")
:subsections [:edit]
:section [:workspace]
:fn #(emit-when-no-readonly
(dw/copy-selected)
(dw/delete-selected))}
:paste {:tooltip (ds/meta "V")
:label (fn [] (tr "shortcuts.paste"))
:disabled true
:command (ds/c-mod "v")
:subsections [:edit]
:section [:workspace]
:customizable false
:fn (constantly nil)}
:paste-replace {:tooltip (ds/meta (ds/shift "V"))
:label (fn [] (tr "shortcuts.paste-replace"))
:command (ds/c-mod "shift+v")
:subsections [:edit]
:section [:workspace]
:fn #(emit-when-no-readonly (dw/paste-from-clipboard {:replace? true}))}
:copy-props {:tooltip (ds/meta (ds/alt "c"))
:label (fn [] (tr "shortcuts.copy-props"))
:command (ds/c-mod "alt+c")
:subsections [:edit]
:section [:workspace]
:fn #(st/emit! (dw/copy-selected-props))}
:paste-props {:tooltip (ds/meta (ds/alt "v"))
:label (fn [] (tr "shortcuts.paste-props"))
:command (ds/c-mod "alt+v")
:subsections [:edit]
:section [:workspace]
:fn #(st/emit! (dw/paste-selected-props))}
:delete {:tooltip (ds/supr)
:label (fn [] (tr "shortcuts.delete"))
:command ["del" "backspace"]
:subsections [:edit]
:section [:workspace]
:fn #(emit-when-no-readonly (dw/delete-selected))}
:duplicate {:tooltip (ds/meta "D")
:label (fn [] (tr "shortcuts.duplicate"))
:command (ds/c-mod "d")
:subsections [:edit]
:section [:workspace]
:fn #(emit-when-no-readonly (dwv/duplicate-or-add-variant))}
:start-editing {:tooltip (ds/enter)
:label (fn [] (tr "shortcuts.start-editing"))
:command "enter"
:subsections [:edit]
:section [:workspace]
:fn #(emit-when-no-readonly (dw/start-editing-selected))}
:show-measure {:tooltip (ds/alt "")
:label (fn [] (tr "shortcuts.show-measure"))
:command ["alt" "."]
:type ["keydown" "keyup"]
:subsections [:tools]
:section [:workspace]
:fn #(emit-when-no-readonly
(let [type (.-type %)]
(dw/toggle-distances-display (if (= type "keydown") true false))))}
:escape {:tooltip (ds/esc)
:label (fn [] (tr "shortcuts.escape"))
:command "escape"
:subsections [:edit]
:section [:workspace]
:fn #(st/emit! :interrupt (dwdc/clear-drawing) (dw/deselect-all true))}
:find {:tooltip (ds/meta "F") :command (ds/c-mod "f") :subsections [:edit]
:label (fn [] (tr "shortcuts.find"))
:section [:workspace]
:fn #(st/emit! (dw/open-layers-search :find))}
:find-and-replace {:tooltip (ds/meta "H") :command (ds/c-mod "h") :subsections [:edit]
:label (fn [] (tr "shortcuts.find-and-replace"))
:section [:workspace]
:fn #(st/emit! (dw/open-layers-search :find-and-replace))}
;; MODIFY LAYERS
:rename {:tooltip (ds/alt "N")
:label (fn [] (tr "shortcuts.rename"))
:command "alt+n"
:subsections [:edit]
:section [:workspace]
:fn #(emit-when-no-readonly (dw/start-rename-selected))}
:group {:tooltip (ds/meta "G")
:label (fn [] (tr "shortcuts.group"))
:command (ds/c-mod "g")
:subsections [:modify-layers]
:section [:workspace]
:fn #(emit-when-no-readonly (dw/group-selected))}
:ungroup {:tooltip (ds/shift "G")
:label (fn [] (tr "shortcuts.ungroup"))
:command "shift+g"
:subsections [:modify-layers]
:section [:workspace]
:fn #(emit-when-no-readonly (dw/ungroup-selected))}
:mask {:tooltip (ds/meta "M")
:label (fn [] (tr "shortcuts.mask"))
:command (ds/c-mod "m")
:subsections [:modify-layers]
:section [:workspace]
:fn #(emit-when-no-readonly (dw/mask-group))}
:unmask {:tooltip (ds/meta-shift "M")
:label (fn [] (tr "shortcuts.unmask"))
:command (ds/c-mod "shift+m")
:subsections [:modify-layers]
:section [:workspace]
:fn #(emit-when-no-readonly (dw/unmask-group))}
:create-component-variant {:tooltip (ds/meta "K")
:label (fn [] (tr "shortcuts.create-component-variant"))
:command (ds/c-mod "k")
:subsections [:modify-layers]
:section [:workspace]
:fn #(emit-when-no-readonly (dwv/add-component-or-variant))}
:detach-component {:tooltip (ds/meta-shift "K")
:label (fn [] (tr "shortcuts.detach-component"))
:command (ds/c-mod "shift+k")
:subsections [:modify-layers]
:section [:workspace]
:fn #(emit-when-no-readonly dwl/detach-selected-components)}
:flip-vertical {:tooltip (ds/shift "V")
:label (fn [] (tr "shortcuts.flip-vertical"))
:command "shift+v"
:subsections [:modify-layers]
:section [:workspace]
:fn #(emit-when-no-readonly (dw/flip-vertical-selected))}
:flip-horizontal {:tooltip (ds/shift "H")
:label (fn [] (tr "shortcuts.flip-horizontal"))
:command "shift+h"
:subsections [:modify-layers]
:section [:workspace]
:fn #(emit-when-no-readonly (dw/flip-horizontal-selected))}
:bring-forward {:tooltip (ds/meta ds/up-arrow)
:label (fn [] (tr "shortcuts.bring-forward"))
:command (ds/c-mod "up")
:subsections [:modify-layers]
:section [:workspace]
:fn #(emit-when-no-readonly (dw/vertical-order-selected :up))}
:bring-backward {:tooltip (ds/meta ds/down-arrow)
:label (fn [] (tr "shortcuts.bring-backward"))
:command (ds/c-mod "down")
:subsections [:modify-layers]
:section [:workspace]
:fn #(emit-when-no-readonly (dw/vertical-order-selected :down))}
:bring-front {:tooltip (ds/meta-shift ds/up-arrow)
:label (fn [] (tr "shortcuts.bring-front"))
:command (ds/c-mod "shift+up")
:subsections [:modify-layers]
:section [:workspace]
:fn #(emit-when-no-readonly (dw/vertical-order-selected :top))}
:bring-back {:tooltip (ds/meta-shift ds/down-arrow)
:label (fn [] (tr "shortcuts.bring-back"))
:command (ds/c-mod "shift+down")
:subsections [:modify-layers]
:section [:workspace]
:fn #(emit-when-no-readonly (dw/vertical-order-selected :bottom))}
:move-fast-up {:tooltip (ds/shift ds/up-arrow)
:label (fn [] (tr "shortcuts.move-fast-up"))
:command ["shift+up" "shift+alt+up"]
:subsections [:modify-layers]
:section [:workspace]
:fn #(emit-when-no-readonly (dwt/move-selected :up true))}
:move-fast-down {:tooltip (ds/shift ds/down-arrow)
:label (fn [] (tr "shortcuts.move-fast-down"))
:command ["shift+down" "shift+alt+down"]
:subsections [:modify-layers]
:section [:workspace]
:fn #(emit-when-no-readonly (dwt/move-selected :down true))}
:move-fast-right {:tooltip (ds/shift ds/right-arrow)
:label (fn [] (tr "shortcuts.move-fast-right"))
:command ["shift+right" "shift+alt+right"]
:subsections [:modify-layers]
:section [:workspace]
:fn #(emit-when-no-readonly (dwt/move-selected :right true))}
:move-fast-left {:tooltip (ds/shift ds/left-arrow)
:label (fn [] (tr "shortcuts.move-fast-left"))
:command ["shift+left" "shift+alt+left"]
:subsections [:modify-layers]
:section [:workspace]
:fn #(emit-when-no-readonly (dwt/move-selected :left true))}
:move-unit-up {:tooltip ds/up-arrow
:label (fn [] (tr "shortcuts.move-unit-up"))
:command ["up" "alt+up"]
:subsections [:modify-layers]
:section [:workspace]
:fn #(emit-when-no-readonly (dwt/move-selected :up false))}
:move-unit-down {:tooltip ds/down-arrow
:label (fn [] (tr "shortcuts.move-unit-down"))
:command ["down" "alt+down"]
:section [:workspace]
:subsections [:modify-layers]
:fn #(emit-when-no-readonly (dwt/move-selected :down false))}
:move-unit-left {:tooltip ds/right-arrow
:label (fn [] (tr "shortcuts.move-unit-left"))
:command ["right" "alt+right"]
:subsections [:modify-layers]
:section [:workspace]
:fn #(emit-when-no-readonly (dwt/move-selected :right false))}
:move-unit-right {:tooltip ds/left-arrow
:label (fn [] (tr "shortcuts.move-unit-right"))
:command ["left" "alt+left"]
:section [:workspace]
:subsections [:modify-layers]
:fn #(emit-when-no-readonly (dwt/move-selected :left false))}
:artboard-selection {:tooltip (ds/meta (ds/alt "G"))
:label (fn [] (tr "shortcuts.artboard-selection"))
:command (ds/c-mod "alt+g")
:section [:workspace]
:subsections [:modify-layers]
:fn #(emit-when-no-readonly (dws/create-artboard-from-selection))}
:toggle-layout-flex {:tooltip (ds/shift "A")
:label (fn [] (tr "shortcuts.toggle-layout-flex"))
:command "shift+a"
:section [:workspace]
:subsections [:modify-layers]
:fn #(emit-when-no-readonly
(with-meta (dwsl/toggle-layout :flex)
{::ev/origin "workspace:shortcuts"}))}
:toggle-layout-grid {:tooltip (ds/meta-shift "A")
:label (fn [] (tr "shortcuts.toggle-layout-grid"))
:command (ds/c-mod "shift+a")
:section [:workspace]
:subsections [:modify-layers]
:fn #(emit-when-no-readonly
(with-meta (dwsl/toggle-layout :grid)
{::ev/origin "workspace:shortcuts"}))}
;; TOOLS
:draw-frame {:tooltip "B"
:label (fn [] (tr "shortcuts.draw-frame"))
:command ["b" "a"]
:section [:workspace :basics]
:subsections [:tools :basics]
:fn #(emit-when-no-readonly (dwd/select-for-drawing :frame))}
:move {:tooltip "V"
:label (fn [] (tr "shortcuts.move"))
:command "v"
:section [:workspace]
:subsections [:tools]
:fn #(emit-when-no-readonly :interrupt)}
:draw-rect {:tooltip "R"
:label (fn [] (tr "shortcuts.draw-rect"))
:command "r"
:section [:workspace]
:subsections [:tools]
:fn #(emit-when-no-readonly (dwd/select-for-drawing :rect))}
:draw-ellipse {:tooltip "E"
:label (fn [] (tr "shortcuts.draw-ellipse"))
:command "e"
:section [:workspace]
:subsections [:tools]
:fn #(emit-when-no-readonly (dwd/select-for-drawing :circle))}
:draw-text {:tooltip "T"
:label (fn [] (tr "shortcuts.draw-text"))
:command "t"
:section [:workspace]
:subsections [:tools]
:fn #(emit-when-no-readonly dwtxt/start-edit-if-selected
(dwd/select-for-drawing :text))}
:draw-path {:tooltip "P"
:label (fn [] (tr "shortcuts.draw-path"))
:command "p"
:section [:workspace]
:subsections [:tools]
:fn #(emit-when-no-readonly (dwd/select-for-drawing :path))}
:draw-line {:tooltip "L"
:label (fn [] (tr "shortcuts.draw-line"))
:command "l"
:subsections [:tools]
:section [:workspace]
:fn #(emit-when-no-readonly (dwd/select-for-drawing :line))}
:draw-arrow {:tooltip (ds/shift "L")
:label (fn [] (tr "shortcuts.draw-arrow"))
:command "shift+l"
:subsections [:tools]
:section [:workspace]
:fn #(emit-when-no-readonly (dwd/select-for-drawing :arrow))}
:draw-curve {:tooltip (ds/shift "C")
:label (fn [] (tr "shortcuts.draw-curve"))
:command "shift+c"
:section [:workspace]
:subsections [:tools]
:fn #(emit-when-no-readonly (dwd/select-for-drawing :curve))}
:add-comment {:tooltip "C"
:label (fn [] (tr "shortcuts.add-comment"))
:command "c"
:section [:workspace]
:subsections [:tools]
:fn #(st/emit! (dwd/select-for-drawing :comments))}
:toggle-comments-visibility
{:tooltip (ds/meta-shift "C")
:label (fn [] (tr "shortcuts.toggle-comments-visibility"))
:command (ds/c-mod "shift+c")
:section [:workspace]
:subsections [:main-menu]
:fn #(st/emit! (dwcm/toggle-comments-visibility {:origin "workspace-shortcuts"}))}
:insert-image {:tooltip (ds/shift "K")
:label (fn [] (tr "shortcuts.insert-image"))
:command "shift+k"
:section [:workspace]
:subsections [:tools]
:fn #(-> "image-upload" dom/get-element dom/click)}
:toggle-visibility {:tooltip (ds/meta-shift "H")
:label (fn [] (tr "shortcuts.toggle-visibility"))
:command (ds/c-mod "shift+h")
:section [:workspace]
:subsections [:tools]
:fn #(emit-when-no-readonly (dw/toggle-visibility-selected))}
:toggle-lock {:tooltip (ds/meta-shift "L")
:label (fn [] (tr "shortcuts.toggle-lock"))
:command (ds/c-mod "shift+l")
:section [:workspace]
:subsections [:tools]
:fn #(emit-when-no-readonly (dw/toggle-lock-selected))}
:toggle-lock-size {:tooltip (ds/shift "L")
:label (fn [] (tr "shortcuts.toggle-lock-size"))
:command "shift+l"
:section [:workspace]
:subsections [:tools]
:fn #(emit-when-no-readonly (dw/toggle-proportion-lock))}
:scale {:tooltip "K"
:label (fn [] (tr "shortcuts.scale"))
:command "k"
:section [:workspace]
:subsections [:tools]
:fn #(emit-when-no-readonly (toggle-layout-flag :scale-text))}
:open-color-picker {:tooltip "I"
:label (fn [] (tr "shortcuts.open-color-picker"))
:command "i"
:section [:workspace]
:subsections [:tools]
:fn #(emit-when-no-readonly (mdc/picker-for-selected-shape))}
:toggle-focus-mode {:command "f"
:label (fn [] (tr "shortcuts.toggle-focus-mode"))
:tooltip "F"
:section [:workspace :basics]
:subsections [:basics :tools]
:fn #(st/emit! (dw/toggle-focus-mode))}
;; ITEM ALIGNMENT
:align-left {:tooltip (ds/alt "A")
:label (fn [] (tr "shortcuts.align-left"))
:command "alt+a"
:section [:workspace]
:subsections [:alignment]
:fn #(emit-when-no-readonly (dw/align-objects :hleft))}
:align-right {:tooltip (ds/alt "D")
:label (fn [] (tr "shortcuts.align-right"))
:command "alt+d"
:section [:workspace]
:subsections [:alignment]
:fn #(emit-when-no-readonly (dw/align-objects :hright))}
:align-top {:tooltip (ds/alt "W")
:label (fn [] (tr "shortcuts.align-top"))
:command "alt+w"
:section [:workspace]
:subsections [:alignment]
:fn #(emit-when-no-readonly (dw/align-objects :vtop))}
:align-hcenter {:tooltip (ds/alt "H")
:label (fn [] (tr "shortcuts.align-hcenter"))
:command "alt+h"
:section [:workspace]
:subsections [:alignment]
:fn #(emit-when-no-readonly (dw/align-objects :hcenter))}
:align-vcenter {:tooltip (ds/alt "V")
:label (fn [] (tr "shortcuts.align-vcenter"))
:command "alt+v"
:section [:workspace]
:subsections [:alignment]
:fn #(emit-when-no-readonly (dw/align-objects :vcenter))}
:align-bottom {:tooltip (ds/alt "S")
:label (fn [] (tr "shortcuts.align-bottom"))
:command "alt+s"
:section [:workspace]
:subsections [:alignment]
:fn #(emit-when-no-readonly (dw/align-objects :vbottom))}
:h-distribute {:tooltip (ds/meta-shift (ds/alt "H"))
:label (fn [] (tr "shortcuts.h-distribute"))
:command (ds/c-mod "shift+alt+h")
:section [:workspace]
:subsections [:alignment]
:fn #(emit-when-no-readonly (dw/distribute-objects :horizontal))}
:v-distribute {:tooltip (ds/meta-shift (ds/alt "V"))
:label (fn [] (tr "shortcuts.v-distribute"))
:command (ds/c-mod "shift+alt+v")
:section [:workspace]
:subsections [:alignment]
:fn #(emit-when-no-readonly (dw/distribute-objects :vertical))}
;; MAIN MENU
:toggle-rulers {:tooltip (ds/meta-shift "R")
:label (fn [] (tr "shortcuts.toggle-rulers"))
:command (ds/c-mod "shift+r")
:section [:workspace]
:subsections [:main-menu]
:fn #(st/emit! (toggle-layout-flag :rulers))}
:select-all {:tooltip (ds/meta "A")
:label (fn [] (tr "shortcuts.select-all"))
:command (ds/c-mod "a")
:section [:workspace]
:subsections [:main-menu]
:fn #(st/emit! (dw/select-all))}
:toggle-guides {:tooltip (ds/meta "'")
:label (fn [] (tr "shortcuts.toggle-guides"))
;;https://github.com/ccampbell/mousetrap/issues/85
:command [(ds/c-mod "'") (ds/c-mod "219")]
:show-command (ds/c-mod "'")
:subsections [:main-menu]
:section [:workspace]
:fn #(st/emit! (toggle-layout-flag :display-guides))}
:toggle-alignment {:tooltip (ds/meta "\\")
:label (fn [] (tr "shortcuts.toggle-alignment"))
:command (ds/c-mod "\\")
:subsections [:main-menu]
:section [:workspace]
:fn #(st/emit! (toggle-layout-flag :dynamic-alignment))}
:thumbnail-set {:tooltip (ds/shift "T")
:label (fn [] (tr "shortcuts.thumbnail-set"))
:command "shift+t"
:subsections [:main-menu]
:section [:workspace]
:fn #(st/emit! (dw/toggle-file-thumbnail-selected))}
:show-pixel-grid {:tooltip (ds/shift ",")
:label (fn [] (tr "shortcuts.show-pixel-grid"))
:command "shift+,"
:subsections [:main-menu]
:section [:workspace]
:fn #(st/emit! (toggle-layout-flag :show-pixel-grid))}
:snap-pixel-grid {:command ","
:label (fn [] (tr "shortcuts.snap-pixel-grid"))
:tooltip ","
:subsections [:main-menu]
:section [:workspace]
:fn #(st/emit! (toggle-layout-flag :snap-pixel-grid))}
:export-shapes {:tooltip (ds/meta-shift "E")
:label (fn [] (tr "shortcuts.export-shapes"))
:command (ds/c-mod "shift+e")
:subsections [:basics :main-menu]
:section [:workspace :basics]
:fn #(st/emit!
(de/show-workspace-export-dialog {:origin "workspace:shortcuts"}))}
:toggle-snap-ruler-guide {:tooltip (ds/meta-shift "G")
:label (fn [] (tr "shortcuts.toggle-snap-ruler-guide"))
:command (ds/c-mod "shift+g")
:subsections [:main-menu]
:section [:workspace]
:fn #(st/emit! (toggle-layout-flag :snap-ruler-guides))}
:toggle-snap-guides {:tooltip (ds/meta-shift "'")
:label (fn [] (tr "shortcuts.toggle-snap-guides"))
;;https://github.com/ccampbell/mousetrap/issues/85
:command [(ds/c-mod "shift+'") (ds/c-mod "shift+219")]
:show-command (ds/c-mod "shift+'")
:subsections [:main-menu]
:section [:workspace]
:fn #(st/emit! (toggle-layout-flag :snap-guides))}
:show-shortcuts {:tooltip "?"
:label (fn [] (tr "shortcuts.show-shortcuts"))
:command "?"
:subsections [:main-menu]
:section [:workspace]
:fn #(st/emit! (toggle-layout-flag :shortcuts))}
;; PANELS
:toggle-layers {:tooltip (ds/alt "L")
:label (fn [] (tr "shortcuts.toggle-layers"))
:command (ds/a-mod "l")
:subsections [:panels]
:section [:workspace]
:fn #(st/emit! (dcm/go-to-workspace :layout :layers))}
:toggle-assets {:tooltip (ds/alt "I")
:label (fn [] (tr "shortcuts.toggle-assets"))
:command (ds/a-mod "i")
:subsections [:panels]
:section [:workspace]
:fn #(st/emit! (dcm/go-to-workspace :layout :assets))}
:toggle-history {:tooltip (ds/meta-alt "H")
:label (fn [] (tr "shortcuts.toggle-history"))
:command (ds/ca-mod "h")
:subsections [:panels]
:section [:workspace]
:fn #(emit-when-no-readonly
(dw/toggle-layout-flag :document-history))}
:toggle-colorpalette {:tooltip (ds/alt "P")
:label (fn [] (tr "shortcuts.toggle-colorpalette"))
:command (ds/a-mod "p")
:subsections [:panels]
:section [:workspace]
:fn #(do (r/set-resize-type! :bottom)
(emit-when-no-readonly (dw/remove-layout-flag :hide-palettes)
(dw/remove-layout-flag :textpalette)
(toggle-layout-flag :colorpalette)))}
:toggle-textpalette {:tooltip (ds/alt "T")
:label (fn [] (tr "shortcuts.toggle-textpalette"))
:command (ds/a-mod "t")
:subsections [:panels]
:section [:workspace]
:fn #(do (r/set-resize-type! :bottom)
(emit-when-no-readonly (dw/remove-layout-flag :hide-palettes)
(dw/remove-layout-flag :colorpalette)
(toggle-layout-flag :textpalette)))}
:hide-ui {:tooltip "\\"
:label (fn [] (tr "shortcuts.hide-ui"))
:command "\\"
:subsections [:panels :basics]
:section [:workspace :basics]
:fn #(st/emit! (toggle-layout-flag :hide-ui))}
;; ZOOM-WORKSPACE
:increase-zoom {:tooltip "+"
:label (fn [] (tr "shortcuts.increase-zoom"))
:command ["+" "="]
:subsections [:zoom-workspace]
:section [:workspace]
:fn #(st/emit! (dw/increase-zoom))}
:decrease-zoom {:tooltip "-"
:label (fn [] (tr "shortcuts.decrease-zoom"))
:command ["-" "_"]
:subsections [:zoom-workspace]
:section [:workspace]
:fn #(st/emit! (dw/decrease-zoom))}
:reset-zoom {:tooltip (ds/shift "0")
:label (fn [] (tr "shortcuts.reset-zoom"))
:command ["shift+0" "shift+num0"]
:subsections [:zoom-workspace]
:section [:workspace]
:fn #(st/emit! dw/reset-zoom)}
:fit-all {:tooltip (ds/shift "1")
:label (fn [] (tr "shortcuts.fit-all"))
:command ["shift+1" "shift+num1"]
:subsections [:zoom-workspace]
:section [:workspace]
:fn #(st/emit! dw/zoom-to-fit-all)}
:zoom-selected {:tooltip (ds/shift "2")
:label (fn [] (tr "shortcuts.zoom-selected"))
:command ["shift+2" "shift+num2" "@" "\""]
:subsections [:zoom-workspace]
:section [:workspace]
:fn #(st/emit! dw/zoom-to-selected-shape)}
:zoom-lense-increase {:tooltip "Z"
:label (fn [] (tr "shortcuts.zoom-lense-increase"))
:command "z"
:subsections [:zoom-workspace]
:section [:workspace]
:fn identity}
:zoom-lense-decrease {:tooltip (ds/alt "Z")
:label (fn [] (tr "shortcuts.zoom-lense-decrease"))
:command "alt+z"
:subsections [:zoom-workspace]
:section [:workspace]
:fn identity}
;; NAVIGATION
:open-viewer {:tooltip "G V"
:label (fn [] (tr "shortcuts.open-viewer"))
:command "g v"
:subsections [:navigation-workspace]
:section [:workspace]
:fn #(st/emit! (dcm/go-to-viewer))}
:open-inspect {:tooltip "G I"
:label (fn [] (tr "shortcuts.open-inspect"))
:command "g i"
:subsections [:navigation-workspace]
:section [:workspace]
:fn #(st/emit! (dcm/go-to-viewer :section :inspect))}
:open-comments {:tooltip "G C"
:label (fn [] (tr "shortcuts.open-comments"))
:command "g c"
:subsections [:navigation-workspace]
:section [:workspace]
:fn #(st/emit! (dcm/go-to-viewer :section :comments))}
:open-dashboard {:tooltip "G D"
:label (fn [] (tr "shortcuts.open-dashboard"))
:command "g d"
:subsections [:navigation-workspace]
:section [:workspace]
:fn #(st/emit! (dcm/go-to-dashboard-recent))}
:select-prev {:tooltip (ds/shift "tab")
:label (fn [] (tr "shortcuts.select-prev"))
:command "shift+tab"
:subsections [:navigation-workspace]
:section [:workspace]
:fn #(st/emit! (dw/select-prev-shape))}
:select-next {:tooltip ds/tab
:label (fn [] (tr "shortcuts.select-next"))
:command "tab"
:subsections [:navigation-workspace]
:section [:workspace]
:fn #(st/emit! (dw/select-next-shape))}
:select-parent-layer {:tooltip (ds/shift ds/enter)
:label (fn [] (tr "shortcuts.select-parent-layer"))
:command "shift+enter"
:subsections [:navigation-workspace]
:section [:workspace]
:fn #(emit-when-no-readonly (dw/select-parent-layer))}
;; SHAPE
:bool-union {:tooltip (ds/meta (ds/alt "U"))
:label (fn [] (tr "shortcuts.bool-union"))
:command (ds/c-mod "alt+u")
:subsections [:shape]
:section [:workspace]
:fn #(emit-when-no-readonly (dw/create-bool :union))}
:bool-difference {:tooltip (ds/meta (ds/alt "D"))
:label (fn [] (tr "shortcuts.bool-difference"))
:command (ds/c-mod "alt+d")
:subsections [:shape]
:section [:workspace]
:fn #(emit-when-no-readonly (dw/create-bool :difference))}
:bool-intersection {:tooltip (ds/meta (ds/alt "I"))
:label (fn [] (tr "shortcuts.bool-intersection"))
:command (ds/c-mod "alt+i")
:subsections [:shape]
:section [:workspace]
:fn #(emit-when-no-readonly (dw/create-bool :intersection))}
:bool-exclude {:tooltip (ds/meta (ds/alt "E"))
:label (fn [] (tr "shortcuts.bool-exclude"))
:command (ds/c-mod "alt+e")
:subsections [:shape]
:section [:workspace]
:fn #(emit-when-no-readonly (dw/create-bool :exclude))}
;; THEME
:toggle-theme {:tooltip (ds/alt "M")
:label (fn [] (tr "shortcuts.toggle-theme"))
:command (ds/a-mod "m")
:subsections [:generic :basics]
:section [:workspace :basics]
:fn #(st/emit! (with-meta (du/toggle-theme)
{::ev/origin "workspace:shortcut"}))}
;; PLUGINS
:plugins {:tooltip (ds/meta (ds/alt "P"))
:label (fn [] (tr "shortcuts.plugins"))
:command (ds/c-mod "alt+p")
:subsections [:generic :basics]
:section [:workspace :basics]
:fn #(when (features/active-feature? @st/state "plugins/runtime")
(st/emit!
(ev/event {::ev/name "open-plugins-manager" ::ev/origin "workspace:shortcuts"})
(modal/show :plugin-management {})))}})
(def debug-shortcuts
;; PREVIEW
{:preview-frame {:tooltip (ds/meta (ds/alt ds/enter))
:command (ds/c-mod "alt+enter")
:fn #(emit-when-no-readonly (dp/open-preview-selected))}})
(def opacity-shortcuts
{:opacity-0 {:label (fn [] (tr "shortcuts.opacity-0"))
:tooltip "0"
:command ["0" "num0"]
:section [:workspace]
:subsections [:modify-layers]
:fn #(emit-when-no-readonly (dwly/pressed-opacity 0))}
:opacity-1 {:label (fn [] (tr "shortcuts.opacity-1"))
:tooltip "1"
:command ["1" "num1"]
:section [:workspace]
:subsections [:modify-layers]
:fn #(emit-when-no-readonly (dwly/pressed-opacity 1))}
:opacity-2 {:label (fn [] (tr "shortcuts.opacity-2"))
:tooltip "2"
:command ["2" "num2"]
:section [:workspace]
:subsections [:modify-layers]
:fn #(emit-when-no-readonly (dwly/pressed-opacity 2))}
:opacity-3 {:label (fn [] (tr "shortcuts.opacity-3"))
:tooltip "3"
:command ["3" "num3"]
:section [:workspace]
:subsections [:modify-layers]
:fn #(emit-when-no-readonly (dwly/pressed-opacity 3))}
:opacity-4 {:label (fn [] (tr "shortcuts.opacity-4"))
:tooltip "4"
:command ["4" "num4"]
:section [:workspace]
:subsections [:modify-layers]
:fn #(emit-when-no-readonly (dwly/pressed-opacity 4))}
:opacity-5 {:label (fn [] (tr "shortcuts.opacity-5"))
:tooltip "5"
:command ["5" "num5"]
:section [:workspace]
:subsections [:modify-layers]
:fn #(emit-when-no-readonly (dwly/pressed-opacity 5))}
:opacity-6 {:label (fn [] (tr "shortcuts.opacity-6"))
:tooltip "6"
:command ["6" "num6"]
:section [:workspace]
:subsections [:modify-layers]
:fn #(emit-when-no-readonly (dwly/pressed-opacity 6))}
:opacity-7 {:label (fn [] (tr "shortcuts.opacity-7"))
:tooltip "7"
:command ["7" "num7"]
:section [:workspace]
:subsections [:modify-layers]
:fn #(emit-when-no-readonly (dwly/pressed-opacity 7))}
:opacity-8 {:label (fn [] (tr "shortcuts.opacity-8"))
:tooltip "8"
:command ["8" "num8"]
:section [:workspace]
:subsections [:modify-layers]
:fn #(emit-when-no-readonly (dwly/pressed-opacity 8))}
:opacity-9 {:label (fn [] (tr "shortcuts.opacity-9"))
:tooltip "9"
:command ["9" "num9"]
:section [:workspace]
:subsections [:modify-layers]
:fn #(emit-when-no-readonly (dwly/pressed-opacity 9))}})
(def shortcuts
(cond-> (merge base-shortcuts opacity-shortcuts dwtxts/shortcuts)
*assert*
(merge debug-shortcuts)))
(defn get-tooltip
"Returns the tooltip string for a shortcut, using any custom binding
from the user's profile props if one exists, falling back to the
default :tooltip field."
[shortcut]
(assert (contains? shortcuts shortcut) (str shortcut))
(let [custom-shortcuts (deref refs/custom-shortcuts)
custom-command (get-in custom-shortcuts [:workspace shortcut])]
(if (and custom-command (not= custom-command ""))
(ds/command->tooltip custom-command)
(get-in shortcuts [shortcut :tooltip]))))
(defn get-effective-tooltip
"Returns the tooltip string for a shortcut given an already-resolved
custom-shortcuts map. Use this when you already have the custom
shortcuts derefed for the current render cycle."
[shortcut custom-shortcuts]
(let [custom-command (get-in custom-shortcuts [:workspace shortcut])]
(if (and custom-command (not= custom-command ""))
(ds/command->tooltip custom-command)
(get-in shortcuts [shortcut :tooltip]))))