cleaning ShortcutManager

This commit is contained in:
André Michelle
2025-12-23 16:51:41 +01:00
parent a5a3e8f108
commit ef91d79761
8 changed files with 66 additions and 60 deletions
+6 -6
View File
@@ -5778,9 +5778,9 @@
"license": "MIT"
},
"node_modules/fastq": {
"version": "1.19.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
"integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
"version": "1.20.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
"integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
"license": "ISC",
"dependencies": {
"reusify": "^1.0.4"
@@ -7893,9 +7893,9 @@
}
},
"node_modules/lib0": {
"version": "0.2.115",
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.115.tgz",
"integrity": "sha512-noaW4yNp6hCjOgDnWWxW0vGXE3kZQI5Kqiwz+jIWXavI9J9WyfJ9zjsbQlQlgjIbHBrvlA/x3TSIXBUJj+0L6g==",
"version": "0.2.116",
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.116.tgz",
"integrity": "sha512-4zsosjzmt33rx5XjmFVYUAeLNh+BTeDTiwGdLt4muxiir2btsc60Nal0EvkvDRizg+pnlK1q+BtYi7M+d4eStw==",
"license": "MIT",
"dependencies": {
"isomorphic.js": "^0.2.4"
@@ -1,4 +1,4 @@
import {Key, ShortcutKeys} from "@opendaw/lib-dom"
import {Key, Shortcut} from "@opendaw/lib-dom"
import {ShortcutValidator} from "@/shortcuts/ShortcutValidator"
import {ShortcutDefinitions} from "@/shortcuts/ShortcutDefinitions"
@@ -7,107 +7,107 @@ const ctrl = true
export const GlobalShortcutsFactory = ShortcutValidator.validate({
"project-undo": {
keys: ShortcutKeys.of(Key.KeyZ, {ctrl}),
keys: Shortcut.of(Key.KeyZ, {ctrl}),
description: "Undo last action"
},
"project-redo": {
keys: ShortcutKeys.of(Key.KeyZ, {ctrl, shift}),
keys: Shortcut.of(Key.KeyZ, {ctrl, shift}),
description: "Redo last action"
},
"project-open": {
keys: ShortcutKeys.of(Key.KeyO, {ctrl}),
keys: Shortcut.of(Key.KeyO, {ctrl}),
description: "Open project from local storage"
},
"project-save": {
keys: ShortcutKeys.of(Key.KeyS, {ctrl}),
keys: Shortcut.of(Key.KeyS, {ctrl}),
description: "Save project to local storage"
},
"project-save-as": {
keys: ShortcutKeys.of(Key.KeyS, {ctrl, shift}),
keys: Shortcut.of(Key.KeyS, {ctrl, shift}),
description: "Save project as new file"
},
"toggle-playback": {
keys: ShortcutKeys.of(Key.Space),
keys: Shortcut.of(Key.Space),
description: "Start or pause playback"
},
"toggle-software-keyboard": {
keys: ShortcutKeys.of(Key.KeyK, {ctrl}),
keys: Shortcut.of(Key.KeyK, {ctrl}),
description: "Show or hide software keyboard"
},
"toggle-device-panel": {
keys: ShortcutKeys.of(Key.KeyD, {shift}),
keys: Shortcut.of(Key.KeyD, {shift}),
description: "Show or hide device panel"
},
"toggle-content-editor-panel": {
keys: ShortcutKeys.of(Key.KeyE, {shift}),
keys: Shortcut.of(Key.KeyE, {shift}),
description: "Show or hide content editor"
},
"toggle-browser-panel": {
keys: ShortcutKeys.of(Key.KeyB, {shift}),
keys: Shortcut.of(Key.KeyB, {shift}),
description: "Show or hide browser panel"
},
"toggle-tempo-track": {
keys: ShortcutKeys.of(Key.KeyT, {shift}),
keys: Shortcut.of(Key.KeyT, {shift}),
description: "Show or hide tempo track"
},
"toggle-markers-track": {
keys: ShortcutKeys.of(Key.KeyM, {shift}),
keys: Shortcut.of(Key.KeyM, {shift}),
description: "Show or hide markers track"
},
"toggle-clips": {
keys: ShortcutKeys.of(Key.KeyC, {shift}),
keys: Shortcut.of(Key.KeyC, {shift}),
description: "Show or hide clips"
},
"toggle-follow-cursor": {
keys: ShortcutKeys.of(Key.KeyF, {shift}),
keys: Shortcut.of(Key.KeyF, {shift}),
description: "Toggle follow playhead"
},
"toggle-metronome": {
keys: ShortcutKeys.of(Key.KeyM, {ctrl}),
keys: Shortcut.of(Key.KeyM, {ctrl}),
description: "Enable or disable metronome"
},
"copy-device": {
keys: ShortcutKeys.of(Key.KeyD, {ctrl}),
keys: Shortcut.of(Key.KeyD, {ctrl}),
description: "Duplicate selected device"
},
"workspace-next-screen": {
keys: ShortcutKeys.of(Key.Tab),
keys: Shortcut.of(Key.Tab),
description: "Switch to next screen"
},
"workspace-prev-screen": {
keys: ShortcutKeys.of(Key.Tab, {shift}),
keys: Shortcut.of(Key.Tab, {shift}),
description: "Switch to previous screen"
},
"workspace-screen-dashboard": {
keys: ShortcutKeys.of(Key.Digit0, {shift}),
keys: Shortcut.of(Key.Digit0, {shift}),
description: "Go to dashboard"
},
"workspace-screen-default": {
keys: ShortcutKeys.of(Key.Digit1, {shift}),
keys: Shortcut.of(Key.Digit1, {shift}),
description: "Go to arrangement view"
},
"workspace-screen-mixer": {
keys: ShortcutKeys.of(Key.Digit2, {shift}),
keys: Shortcut.of(Key.Digit2, {shift}),
description: "Go to mixer view"
},
"workspace-screen-piano": {
keys: ShortcutKeys.of(Key.Digit3, {shift}),
keys: Shortcut.of(Key.Digit3, {shift}),
description: "Go to piano roll"
},
"workspace-screen-project": {
keys: ShortcutKeys.of(Key.Digit4, {shift}),
keys: Shortcut.of(Key.Digit4, {shift}),
description: "Go to project settings"
},
"workspace-screen-shadertoy": {
keys: ShortcutKeys.of(Key.Digit5, {shift}),
keys: Shortcut.of(Key.Digit5, {shift}),
description: "Go to shader visualizer"
},
"workspace-screen-meter": {
keys: ShortcutKeys.of(Key.Digit6, {shift}),
keys: Shortcut.of(Key.Digit6, {shift}),
description: "Go to meter view"
},
"show-preferences": {
keys: ShortcutKeys.of(Key.Comma, {ctrl}),
keys: Shortcut.of(Key.Comma, {ctrl}),
description: "Open preferences"
}
})
@@ -1,3 +1,3 @@
import {ShortcutKeys} from "@opendaw/lib-dom"
import {Shortcut} from "@opendaw/lib-dom"
export type ShortcutDefinition = { keys: ShortcutKeys, description: string }
export type ShortcutDefinition = { keys: Shortcut, description: string }
@@ -1,4 +1,4 @@
import {ShortcutKeys} from "@opendaw/lib-dom"
import {Shortcut} from "@opendaw/lib-dom"
import {ShortcutDefinition} from "@/shortcuts/ShortcutDefinition"
import {isAbsent, JSONValue} from "@opendaw/lib-std"
@@ -32,7 +32,7 @@ export namespace ShortcutDefinitions {
for (const [key, value] of Object.entries(values) as Array<[string, JSONValue]>) {
const def = defs[key]
if (isAbsent(def)) {continue}
ShortcutKeys.fromJSON(value).ifSome(keys => def.keys.overrideWith(keys))
Shortcut.fromJSON(value).ifSome(keys => def.keys.overrideWith(keys))
}
}
}
@@ -1,7 +1,7 @@
component
display: flex
flex-direction: column
row-gap: 1em
row-gap: 0.5em
outline: none
height: 280px
overflow: hidden auto
@@ -1,5 +1,5 @@
import css from "./ShortcutManagerView.sass?inline"
import {Events, Html, ShortcutKeys} from "@opendaw/lib-dom"
import {Events, Html, Shortcut} from "@opendaw/lib-dom"
import {DefaultObservableValue, isAbsent, Lifecycle, Notifier, Objects, Terminator} from "@opendaw/lib-std"
import {createElement, replaceChildren} from "@opendaw/lib-jsx"
import {Dialogs} from "@/ui/components/dialogs"
@@ -17,7 +17,7 @@ type Construct = {
}
const editShortcut = async (definitions: ShortcutDefinitions,
original: ShortcutDefinition): Promise<ShortcutKeys> => {
original: ShortcutDefinition): Promise<Shortcut> => {
const lifecycle = new Terminator()
const abortController = new AbortController()
const shortcut = lifecycle.own(new DefaultObservableValue(original.keys))
@@ -28,7 +28,7 @@ const editShortcut = async (definitions: ShortcutDefinitions,
<h3 style={{color: Colors.orange.toString()}}>Shortcut for "{original.description}"</h3>
<div style={{color: Colors.blue.toString(), height: "1.25em"}} onConnect={element => {
lifecycle.own(Events.subscribe(Surface.get(element).owner, "keydown", event => {
ShortcutKeys.fromEvent(event).ifSome(newShortcut => {
Shortcut.fromEvent(event).ifSome(newShortcut => {
shortcut.setValue(newShortcut)
element.textContent = newShortcut.format()
})
@@ -59,11 +59,17 @@ const editShortcut = async (definitions: ShortcutDefinitions,
}).then(() => shortcut.getValue(), () => original.keys).finally(() => lifecycle.terminate())
}
let lastOpenIndex = 0
export const ShortcutManagerView = ({lifecycle, contexts, updateNotifier}: Construct) => {
return (
<div className={className} onInit={element => {
const update = () => replaceChildren(element, Objects.entries(contexts).map(([name, shortcuts], index) => (
<details className="context" open={index === 0}>
<details className="context"
open={lastOpenIndex === index}
onInit={element => element.ontoggle = () => {
if (element.open) {lastOpenIndex = index}
}}>
<summary><h3>{name}</h3></summary>
<div className="shortcuts">
{Objects.entries(shortcuts).map(([key, entry]) => (
+17 -17
View File
@@ -40,7 +40,7 @@ export class ShortcutManager {
}
}
hasConflict(keys: ShortcutKeys): boolean {
hasConflict(keys: Shortcut): boolean {
if (this.global.hasConflict(keys)) {return true}
return this.#contexts.some(ctx => ctx.hasConflict(keys))
}
@@ -77,7 +77,7 @@ export class ShortcutContext {
get active(): boolean {return this.#isActive()}
get shortcuts(): ReadonlyArray<ShortcutEntry> {return this.#shortcuts}
register(keys: ShortcutKeys, action: Exec, options?: ShortcutOptions): Subscription {
register(keys: Shortcut, action: Exec, options?: ShortcutOptions): Subscription {
const entry: ShortcutEntry = {keys: keys, action, options: options ?? ShortcutOptions.Default}
const index = BinarySearch.leftMostMapped(
this.#shortcuts, entry.options.priority, NumberComparator, ({options: {priority}}) => priority)
@@ -85,12 +85,12 @@ export class ShortcutContext {
return this.#terminator.own({terminate: () => this.#shortcuts.splice(this.#shortcuts.indexOf(entry), 1)})
}
hasConflict(keys: ShortcutKeys): boolean {
hasConflict(keys: Shortcut): boolean {
return this.#shortcuts.some(entry => entry.keys.equals(keys))
}
hasConflicts(): ReadonlyArray<ShortcutKeys> {
const conflicts: Array<ShortcutKeys> = []
hasConflicts(): ReadonlyArray<Shortcut> {
const conflicts: Array<Shortcut> = []
for (let i = 0; i < this.#shortcuts.length; i++) {
const keys = this.#shortcuts[i].keys
if (conflicts.some(other => other.equals(keys))) {continue}
@@ -107,22 +107,22 @@ export class ShortcutContext {
terminate(): void {this.#terminator.terminate()}
}
export class ShortcutKeys {
static of(code: string, modifiers?: { ctrl?: boolean, shift?: boolean, alt?: boolean }): ShortcutKeys {
return new ShortcutKeys(code, modifiers?.ctrl, modifiers?.shift, modifiers?.alt)
export class Shortcut {
static of(code: string, modifiers?: { ctrl?: boolean, shift?: boolean, alt?: boolean }): Shortcut {
return new Shortcut(code, modifiers?.ctrl, modifiers?.shift, modifiers?.alt)
}
static fromJSON(value: JSONValue): Option<ShortcutKeys> {
static fromJSON(value: JSONValue): Option<Shortcut> {
if (typeof value !== "object" || value === null || Array.isArray(value)) {return Option.None}
const {code, ctrl, shift, alt} = value as Record<string, unknown>
if (typeof code !== "string") {return Option.None}
if (typeof ctrl !== "boolean") {return Option.None}
if (typeof shift !== "boolean") {return Option.None}
if (typeof alt !== "boolean") {return Option.None}
return Option.wrap(new ShortcutKeys(code, ctrl, shift, alt))
return Option.wrap(new Shortcut(code, ctrl, shift, alt))
}
static fromEvent(event: KeyboardEvent): Option<ShortcutKeys> {
static fromEvent(event: KeyboardEvent): Option<Shortcut> {
const code = event.code
if (code.startsWith("Shift")
|| code.startsWith("Control")
@@ -132,7 +132,7 @@ export class ShortcutKeys {
) {
return Option.None
}
return Option.wrap(new ShortcutKeys(code, Keyboard.isControlKey(event), event.shiftKey, event.altKey))
return Option.wrap(new Shortcut(code, Keyboard.isControlKey(event), event.shiftKey, event.altKey))
}
static readonly #keyNames: Record<string, string | [mac: string, other: string]> = {
@@ -187,7 +187,7 @@ export class ShortcutKeys {
get shift(): boolean {return this.#shift}
get alt(): boolean {return this.#alt}
equals(other: ShortcutKeys): boolean {
equals(other: Shortcut): boolean {
return this.#code === other.#code
&& this.#ctrl === other.#ctrl
&& this.#shift === other.#shift
@@ -206,11 +206,11 @@ export class ShortcutKeys {
if (this.#shift) {parts.push(Browser.isMacOS() ? "⇧" : "Shift")}
if (this.#alt) {parts.push(Browser.isMacOS() ? "⌥" : "Alt")}
if (this.#ctrl) {parts.push(Browser.isMacOS() ? "⌘" : "Ctrl")}
parts.push(ShortcutKeys.#formatKey(this.#code))
parts.push(Shortcut.#formatKey(this.#code))
return parts.join(Browser.isMacOS() ? "" : "+")
}
overrideWith(keys: ShortcutKeys): void {
overrideWith(keys: Shortcut): void {
this.#code = keys.#code
this.#ctrl = keys.#ctrl
this.#shift = keys.#shift
@@ -219,7 +219,7 @@ export class ShortcutKeys {
toJSON(): JSONValue {return {code: this.#code, ctrl: this.#ctrl, shift: this.#shift, alt: this.#alt}}
copy(): ShortcutKeys {return new ShortcutKeys(this.#code, this.#ctrl, this.#shift, this.#alt)}
copy(): Shortcut {return new Shortcut(this.#code, this.#ctrl, this.#shift, this.#alt)}
toString(): string {return `{ShortcutKeys ${this.format()}}`}
}
@@ -247,7 +247,7 @@ export class ShortcutOptions {
}
type ShortcutEntry = {
readonly keys: ShortcutKeys
readonly keys: Shortcut
readonly action: Exec
readonly options: ShortcutOptions
}
+1 -1
View File
@@ -11,6 +11,6 @@ echo "Removing package-lock.json..."
find . -name package-lock.json -type f -delete 2>/dev/null || true
echo "Removing .turbo..."
find . -name .turbo -type d -prune -exec rm -rf {} + 2>/dev/null || true
rm -rf .turbo packages/*/.turbo 2>/dev/null || true
rm -rf packages/studio/boxes/src/* 2>/dev/null || true