finished ShortcutManager
This commit is contained in:
@@ -170,9 +170,9 @@
|
||||
|
||||
@if $semitransparent
|
||||
backdrop-filter: blur(2px)
|
||||
background-color: hsla(200, 12%, 9%, 0.98)
|
||||
background-color: color-mix(in srgb, var(--color-panel-background-dark), transparent 1%)
|
||||
@else
|
||||
background-color: hsl(200, 12%, 9%)
|
||||
background-color: var(--color-panel-background-dark)
|
||||
|
||||
@mixin corner-frame($color: rgba(white, 0.06))
|
||||
position: relative
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import {Terminator} from "@opendaw/lib-std"
|
||||
import {Notifier, Terminator} from "@opendaw/lib-std"
|
||||
import {Promises} from "@opendaw/lib-runtime"
|
||||
import {Dialogs} from "@/ui/components/dialogs"
|
||||
import {PreferencePanel} from "@/ui/PreferencePanel"
|
||||
import {ShortcutManagerView} from "@/ui/components/ShortcutManagerView"
|
||||
import {GlobalShortcuts, GlobalShortcutsFactory} from "@/shortcuts/GlobalShortcuts"
|
||||
import {ShortcutDefinitions} from "@/shortcuts/ShortcutDefinitions"
|
||||
import {StudioShortcutManager} from "@/service/StudioShortcutManager"
|
||||
|
||||
export namespace StudioDialogs {
|
||||
export const showPreferences = async () => {
|
||||
@@ -17,11 +20,31 @@ export namespace StudioDialogs {
|
||||
|
||||
export const showShortcutManager = async () => {
|
||||
const lifecycle = new Terminator()
|
||||
const abortController = new AbortController()
|
||||
const updateNotifier = new Notifier<void>()
|
||||
const contexts = {
|
||||
"Global": ShortcutDefinitions.copy(GlobalShortcuts)
|
||||
} satisfies Record<string, ShortcutDefinitions>
|
||||
await Promises.tryCatch(Dialogs.show({
|
||||
headline: "Shortcut Manager",
|
||||
content: ShortcutManagerView({lifecycle}),
|
||||
growWidth: true
|
||||
}))
|
||||
content: ShortcutManagerView({lifecycle, contexts, updateNotifier}),
|
||||
growWidth: true,
|
||||
abortSignal: abortController.signal,
|
||||
buttons: [
|
||||
{
|
||||
text: "Reset", onClick: () => {
|
||||
contexts["Global"] = ShortcutDefinitions.copy(GlobalShortcutsFactory)
|
||||
updateNotifier.notify()
|
||||
}
|
||||
},
|
||||
{text: "Cancel", onClick: () => abortController.abort()}
|
||||
]
|
||||
})).then(() => {
|
||||
if (!abortController.signal.aborted) {
|
||||
ShortcutDefinitions.copyInto(contexts.Global, GlobalShortcuts)
|
||||
StudioShortcutManager.store()
|
||||
}
|
||||
})
|
||||
lifecycle.terminate()
|
||||
}
|
||||
}
|
||||
@@ -65,7 +65,6 @@ import {Surface} from "@/ui/surface/Surface"
|
||||
import {SoftwareMIDIPanel} from "@/ui/software-midi/SoftwareMIDIPanel"
|
||||
import {Mixdowns} from "@/service/Mixdowns"
|
||||
import {ShadertoyState} from "@/ui/shadertoy/ShadertoyState"
|
||||
import {StudioDialogs} from "@/service/StudioDialogs"
|
||||
|
||||
/**
|
||||
* I am just piling stuff after stuff in here to boot the environment.
|
||||
@@ -137,9 +136,6 @@ export class StudioService implements ProjectEnv {
|
||||
this.#configBeforeUnload()
|
||||
this.#checkRecovery()
|
||||
this.#listenPreferences()
|
||||
|
||||
// TODO Remove when done
|
||||
setTimeout(() => StudioDialogs.showShortcutManager().then(), 500)
|
||||
}
|
||||
|
||||
get sampleRate(): number {return this.audioContext.sampleRate}
|
||||
|
||||
@@ -1,53 +1,89 @@
|
||||
import {ShortcutManager, ShortcutOptions} from "@opendaw/lib-dom"
|
||||
import {StudioService} from "@/service/StudioService"
|
||||
import {PanelType} from "@/ui/workspace/PanelType"
|
||||
import {Arrays, asInstanceOf, isNull, RuntimeNotifier, Subscription, Terminable} from "@opendaw/lib-std"
|
||||
import {
|
||||
Arrays,
|
||||
asInstanceOf,
|
||||
isDefined,
|
||||
isNull,
|
||||
RuntimeNotifier,
|
||||
Subscription,
|
||||
Terminable,
|
||||
tryCatch
|
||||
} from "@opendaw/lib-std"
|
||||
import {DefaultWorkspace} from "@/ui/workspace/Default"
|
||||
import {Workspace} from "@/ui/workspace/Workspace"
|
||||
import {AudioUnitBox} from "@opendaw/studio-boxes"
|
||||
import {ProjectUtils} from "@opendaw/studio-adapters"
|
||||
import {StudioDialogs} from "@/service/StudioDialogs"
|
||||
import {GlobalShortcuts} from "@/shortcuts/GlobalShortcuts"
|
||||
import {GlobalShortcuts, GlobalShortcutsFactory} from "@/shortcuts/GlobalShortcuts"
|
||||
import {ShortcutDefinitions} from "@/shortcuts/ShortcutDefinitions"
|
||||
|
||||
export namespace StudioShortcutManager {
|
||||
const localStorageKey = "shortcuts"
|
||||
|
||||
export const reset = (): void => {
|
||||
for (const [key, {keys}] of Object.entries(GlobalShortcutsFactory)) {
|
||||
GlobalShortcuts[key].keys.overrideWith(keys)
|
||||
}
|
||||
}
|
||||
|
||||
export const store = () => {
|
||||
try {
|
||||
const jsonString = JSON.stringify({global: ShortcutDefinitions.toJSON(GlobalShortcuts)})
|
||||
localStorage.setItem(localStorageKey, jsonString)
|
||||
console.debug("Shortcuts saved.")
|
||||
} catch (reason) {
|
||||
console.warn(reason)
|
||||
}
|
||||
}
|
||||
|
||||
export const install = (service: StudioService): Subscription => {
|
||||
const {global: g} = ShortcutManager.get()
|
||||
const {global: gc} = ShortcutManager.get()
|
||||
const {
|
||||
engine: {metronomeEnabled},
|
||||
panelLayout,
|
||||
timeline: {clips: {visible: clipsVisibility}, followCursor, primaryVisibility: {markers, tempo}}
|
||||
} = service
|
||||
const s = GlobalShortcuts
|
||||
const gs = GlobalShortcuts
|
||||
const storedShortcuts = localStorage.getItem(localStorageKey)
|
||||
if (isDefined(storedShortcuts)) {
|
||||
const {status, value} = tryCatch(() => JSON.parse(storedShortcuts))
|
||||
if (status === "success" && "global" in value) {
|
||||
console.debug("Custom shortcuts loaded.")
|
||||
ShortcutDefinitions.fromJSON(gs, value.global)
|
||||
}
|
||||
}
|
||||
const subscriptions = Terminable.many(
|
||||
g.register(s["project-undo"].keys, () => service.runIfProject(project => project.editing.undo())),
|
||||
g.register(s["project-redo"].keys, () => service.runIfProject(project => project.editing.redo())),
|
||||
g.register(s["project-open"].keys, async () => await service.browseLocalProjects()),
|
||||
g.register(s["project-save"].keys, async () => await service.projectProfileService.save(),
|
||||
gc.register(gs["project-undo"].keys, () => service.runIfProject(project => project.editing.undo())),
|
||||
gc.register(gs["project-redo"].keys, () => service.runIfProject(project => project.editing.redo())),
|
||||
gc.register(gs["project-open"].keys, async () => await service.browseLocalProjects()),
|
||||
gc.register(gs["project-save"].keys, async () => await service.projectProfileService.save(),
|
||||
ShortcutOptions.of({activeInTextField: true})),
|
||||
g.register(s["project-save-as"].keys, async () => await service.projectProfileService.saveAs(),
|
||||
gc.register(gs["project-save-as"].keys, async () => await service.projectProfileService.saveAs(),
|
||||
ShortcutOptions.of({activeInTextField: true})),
|
||||
g.register(s["toggle-playback"].keys, () => {
|
||||
gc.register(gs["toggle-playback"].keys, () => {
|
||||
const {engine} = service
|
||||
const isPlaying = engine.isPlaying.getValue()
|
||||
if (isPlaying) {engine.stop()} else {engine.play()}
|
||||
}),
|
||||
g.register(s["toggle-software-keyboard"].keys, () => service.toggleSoftwareKeyboard()),
|
||||
g.register(s["toggle-device-panel"].keys, () => panelLayout.getByType(PanelType.DevicePanel).toggleMinimize()),
|
||||
g.register(s["toggle-content-editor-panel"].keys, () => panelLayout.getByType(PanelType.ContentEditor).toggleMinimize()),
|
||||
g.register(s["toggle-browser-panel"].keys, () => panelLayout.getByType(PanelType.BrowserPanel).toggleMinimize()),
|
||||
g.register(s["toggle-tempo-track"].keys, () => tempo.setValue(!tempo.getValue())),
|
||||
g.register(s["toggle-markers-track"].keys, () => markers.setValue(!markers.getValue())),
|
||||
g.register(s["toggle-clips"].keys, () => clipsVisibility.setValue(!clipsVisibility.getValue())),
|
||||
g.register(s["toggle-follow-cursor"].keys, () => followCursor.setValue(!followCursor.getValue())),
|
||||
g.register(s["toggle-metronome"].keys, () => metronomeEnabled.setValue(!metronomeEnabled.getValue())),
|
||||
g.register(s["copy-device"].keys, () => service.runIfProject(
|
||||
gc.register(gs["toggle-software-keyboard"].keys, () => service.toggleSoftwareKeyboard()),
|
||||
gc.register(gs["toggle-device-panel"].keys, () => panelLayout.getByType(PanelType.DevicePanel).toggleMinimize()),
|
||||
gc.register(gs["toggle-content-editor-panel"].keys, () => panelLayout.getByType(PanelType.ContentEditor).toggleMinimize()),
|
||||
gc.register(gs["toggle-browser-panel"].keys, () => panelLayout.getByType(PanelType.BrowserPanel).toggleMinimize()),
|
||||
gc.register(gs["toggle-tempo-track"].keys, () => tempo.setValue(!tempo.getValue())),
|
||||
gc.register(gs["toggle-markers-track"].keys, () => markers.setValue(!markers.getValue())),
|
||||
gc.register(gs["toggle-clips"].keys, () => clipsVisibility.setValue(!clipsVisibility.getValue())),
|
||||
gc.register(gs["toggle-follow-cursor"].keys, () => followCursor.setValue(!followCursor.getValue())),
|
||||
gc.register(gs["toggle-metronome"].keys, () => metronomeEnabled.setValue(!metronomeEnabled.getValue())),
|
||||
gc.register(gs["copy-device"].keys, () => service.runIfProject(
|
||||
({editing, userEditingManager, skeleton}) => userEditingManager.audioUnit.get().ifSome(({box}) => {
|
||||
const audioUnitBox = asInstanceOf(box, AudioUnitBox)
|
||||
const copies = editing.modify(() => ProjectUtils
|
||||
.extractAudioUnits([audioUnitBox], skeleton), false).unwrap()
|
||||
userEditingManager.audioUnit.edit(copies[0].editing)
|
||||
}))),
|
||||
g.register(s["workspace-next-screen"].keys, () => {
|
||||
gc.register(gs["workspace-next-screen"].keys, () => {
|
||||
const keys = Object.entries(DefaultWorkspace).map(([key]) => key as Workspace.ScreenKeys)
|
||||
const screen = service.layout.screen
|
||||
const current = screen.getValue()
|
||||
@@ -55,7 +91,7 @@ export namespace StudioShortcutManager {
|
||||
screen.setValue(Arrays.getNext(keys, current))
|
||||
}
|
||||
),
|
||||
g.register(s["workspace-prev-screen"].keys, () => {
|
||||
gc.register(gs["workspace-prev-screen"].keys, () => {
|
||||
const keys = Object.entries(DefaultWorkspace).map(([key]) => key as Workspace.ScreenKeys)
|
||||
const screen = service.layout.screen
|
||||
const current = screen.getValue()
|
||||
@@ -63,16 +99,16 @@ export namespace StudioShortcutManager {
|
||||
screen.setValue(Arrays.getPrev(keys, current))
|
||||
}
|
||||
),
|
||||
g.register(s["workspace-screen-dashboard"].keys, async () => await service.closeProject()),
|
||||
g.register(s["workspace-screen-default"].keys, () => service.runIfProject(() => service.switchScreen("default"))),
|
||||
g.register(s["workspace-screen-mixer"].keys, () => service.runIfProject(() => service.switchScreen("mixer"))),
|
||||
g.register(s["workspace-screen-piano"].keys, () => service.runIfProject(() => service.switchScreen("piano"))),
|
||||
g.register(s["workspace-screen-project"].keys, () => service.runIfProject(() => service.switchScreen("project"))),
|
||||
g.register(s["workspace-screen-meter"].keys, () => service.runIfProject(() => service.switchScreen("meter"))),
|
||||
g.register(s["workspace-screen-shadertoy"].keys, () => service.runIfProject(() => service.switchScreen("shadertoy"))),
|
||||
g.register(s["show-preferences"].keys, () => StudioDialogs.showPreferences())
|
||||
gc.register(gs["workspace-screen-dashboard"].keys, async () => await service.closeProject()),
|
||||
gc.register(gs["workspace-screen-default"].keys, () => service.runIfProject(() => service.switchScreen("default"))),
|
||||
gc.register(gs["workspace-screen-mixer"].keys, () => service.runIfProject(() => service.switchScreen("mixer"))),
|
||||
gc.register(gs["workspace-screen-piano"].keys, () => service.runIfProject(() => service.switchScreen("piano"))),
|
||||
gc.register(gs["workspace-screen-project"].keys, () => service.runIfProject(() => service.switchScreen("project"))),
|
||||
gc.register(gs["workspace-screen-meter"].keys, () => service.runIfProject(() => service.switchScreen("meter"))),
|
||||
gc.register(gs["workspace-screen-shadertoy"].keys, () => service.runIfProject(() => service.switchScreen("shadertoy"))),
|
||||
gc.register(gs["show-preferences"].keys, () => StudioDialogs.showPreferences())
|
||||
)
|
||||
const conflicts = g.hasConflicts()
|
||||
const conflicts = gc.hasConflicts()
|
||||
if (conflicts) {
|
||||
RuntimeNotifier.info({
|
||||
headline: "Shortcut Conflict",
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import {Key, ShortcutKeys} from "@opendaw/lib-dom"
|
||||
import {ShortcutValidator} from "@/shortcuts/ShortcutValidator"
|
||||
import {ShortcutDefinitions} from "@/shortcuts/ShortcutDefinitions"
|
||||
|
||||
const shift = true
|
||||
const ctrl = true
|
||||
|
||||
export const GlobalShortcuts = ShortcutValidator.validate({
|
||||
export const GlobalShortcutsFactory = ShortcutValidator.validate({
|
||||
"project-undo": {
|
||||
keys: ShortcutKeys.of(Key.KeyZ, {ctrl}),
|
||||
description: "Undo last action"
|
||||
@@ -110,3 +111,5 @@ export const GlobalShortcuts = ShortcutValidator.validate({
|
||||
description: "Open preferences"
|
||||
}
|
||||
})
|
||||
|
||||
export const GlobalShortcuts = ShortcutDefinitions.copy(GlobalShortcutsFactory)
|
||||
@@ -0,0 +1,3 @@
|
||||
import {ShortcutKeys} from "@opendaw/lib-dom"
|
||||
|
||||
export type ShortcutDefinition = { keys: ShortcutKeys, description: string }
|
||||
@@ -0,0 +1,38 @@
|
||||
import {ShortcutKeys} from "@opendaw/lib-dom"
|
||||
import {ShortcutDefinition} from "@/shortcuts/ShortcutDefinition"
|
||||
import {isAbsent, JSONValue} from "@opendaw/lib-std"
|
||||
|
||||
export type ShortcutDefinitions = Record<string, ShortcutDefinition>
|
||||
|
||||
export namespace ShortcutDefinitions {
|
||||
export const copy = (defs: ShortcutDefinitions): ShortcutDefinitions => {
|
||||
const result: ShortcutDefinitions = {}
|
||||
for (const [key, {keys, description}] of Object.entries(defs)) {
|
||||
result[key] = {keys: keys.copy(), description}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export const copyInto = (source: ShortcutDefinitions, target: ShortcutDefinitions): void => {
|
||||
for (const [key, {keys}] of Object.entries(source)) {
|
||||
target[key].keys.overrideWith(keys.copy())
|
||||
}
|
||||
}
|
||||
|
||||
export const toJSON = (defs: ShortcutDefinitions): JSONValue => {
|
||||
const result: Record<string, JSONValue> = {}
|
||||
for (const [key, {keys}] of Object.entries(defs)) {
|
||||
result[key] = keys.toJSON()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export const fromJSON = (defs: ShortcutDefinitions, values: JSONValue): void => {
|
||||
if (typeof values !== "object" || values === null || Array.isArray(values)) {return}
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,4 @@
|
||||
import {ShortcutKeys} from "@opendaw/lib-dom"
|
||||
|
||||
export type ShortcutDefinition = { keys: ShortcutKeys, description: string }
|
||||
export type ShortcutDefinitions = Record<string, ShortcutDefinition>
|
||||
import {ShortcutDefinitions} from "@/shortcuts/ShortcutDefinitions"
|
||||
|
||||
export namespace ShortcutValidator {
|
||||
export const validate = <T extends ShortcutDefinitions>(actions: T): T => {
|
||||
|
||||
@@ -1,33 +1,49 @@
|
||||
component
|
||||
display: flex
|
||||
flex-direction: column
|
||||
row-gap: 1em
|
||||
outline: none
|
||||
height: 280px
|
||||
overflow: hidden auto
|
||||
|
||||
> div.shortcuts
|
||||
flex: 0 0 320px
|
||||
display: flex
|
||||
flex-direction: column
|
||||
row-gap: 0.25em
|
||||
padding: 0 1em 0 0
|
||||
overflow: hidden auto
|
||||
> details.context
|
||||
outline: none
|
||||
border: none
|
||||
|
||||
> div.shortcut
|
||||
> summary
|
||||
list-style: none
|
||||
color: var(--color-blue)
|
||||
display: flex
|
||||
justify-content: space-between
|
||||
align-items: center
|
||||
cursor: pointer
|
||||
color: var(--color-dark)
|
||||
|
||||
> span
|
||||
white-space: nowrap
|
||||
> h3
|
||||
display: inline-block
|
||||
|
||||
> hr
|
||||
margin: 0.5em
|
||||
height: 1px
|
||||
border: none
|
||||
background-color: var(--color-dark)
|
||||
opacity: 0.1
|
||||
flex: 1
|
||||
> div.shortcuts
|
||||
margin-top: 0.5em
|
||||
display: flex
|
||||
flex-direction: column
|
||||
row-gap: 0.25em
|
||||
padding: 0 1em 0 0
|
||||
outline: none
|
||||
|
||||
&:hover
|
||||
color: var(--color-bright)
|
||||
> div.shortcut
|
||||
display: flex
|
||||
justify-content: space-between
|
||||
cursor: pointer
|
||||
color: var(--color-dark)
|
||||
|
||||
> span
|
||||
white-space: nowrap
|
||||
|
||||
> hr
|
||||
margin: 0.5em
|
||||
height: 1px
|
||||
border: none
|
||||
background-color: var(--color-dark)
|
||||
opacity: 0.1
|
||||
flex: 1
|
||||
|
||||
&:hover
|
||||
color: var(--color-bright)
|
||||
@@ -1,20 +1,23 @@
|
||||
import css from "./ShortcutManagerView.sass?inline"
|
||||
import {Events, Html, ShortcutKeys} from "@opendaw/lib-dom"
|
||||
import {DefaultObservableValue, isAbsent, Lifecycle, Objects, Terminator} from "@opendaw/lib-std"
|
||||
import {DefaultObservableValue, isAbsent, Lifecycle, Notifier, Objects, Terminator} from "@opendaw/lib-std"
|
||||
import {createElement, replaceChildren} from "@opendaw/lib-jsx"
|
||||
import {GlobalShortcuts} from "@/shortcuts/GlobalShortcuts"
|
||||
import {ShortcutDefinition, ShortcutDefinitions} from "@/shortcuts/ShortcutValidator"
|
||||
import {Dialogs} from "@/ui/components/dialogs"
|
||||
import {Surface} from "@/ui/surface/Surface"
|
||||
import {Colors} from "@opendaw/studio-enums"
|
||||
import {ShortcutDefinition} from "@/shortcuts/ShortcutDefinition"
|
||||
import {ShortcutDefinitions} from "@/shortcuts/ShortcutDefinitions"
|
||||
|
||||
const className = Html.adoptStyleSheet(css, "ShortcutManagerView")
|
||||
|
||||
type Construct = {
|
||||
lifecycle: Lifecycle
|
||||
contexts: Record<string, ShortcutDefinitions>
|
||||
updateNotifier: Notifier<void>
|
||||
}
|
||||
|
||||
const editShortcut = async (definitions: ShortcutDefinitions, original: ShortcutDefinition): Promise<ShortcutKeys> => {
|
||||
const editShortcut = async (definitions: ShortcutDefinitions,
|
||||
original: ShortcutDefinition): Promise<ShortcutKeys> => {
|
||||
const lifecycle = new Terminator()
|
||||
const abortController = new AbortController()
|
||||
const shortcut = lifecycle.own(new DefaultObservableValue(original.keys))
|
||||
@@ -56,24 +59,29 @@ const editShortcut = async (definitions: ShortcutDefinitions, original: Shortcut
|
||||
}).then(() => shortcut.getValue(), () => original.keys).finally(() => lifecycle.terminate())
|
||||
}
|
||||
|
||||
export const ShortcutManagerView = ({}: Construct) => {
|
||||
export const ShortcutManagerView = ({lifecycle, contexts, updateNotifier}: Construct) => {
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="shortcuts" onInit={element => {
|
||||
const update = () => replaceChildren(element, (
|
||||
Objects.entries(GlobalShortcuts).map(([key, entry]) => (
|
||||
<div className="shortcut" onclick={async () => {
|
||||
const keys = await editShortcut(GlobalShortcuts, entry)
|
||||
GlobalShortcuts[key].keys.overrideWith(keys)
|
||||
update()
|
||||
}}><span>{entry.description}</span>
|
||||
<hr/>
|
||||
<span>{entry.keys.format()}</span>
|
||||
</div>
|
||||
))
|
||||
))
|
||||
update()
|
||||
}}/>
|
||||
<div className={className} onInit={element => {
|
||||
const update = () => replaceChildren(element, Objects.entries(contexts).map(([name, shortcuts], index) => (
|
||||
<details className="context" open={index === 0}>
|
||||
<summary><h3>{name}</h3></summary>
|
||||
<div className="shortcuts">
|
||||
{Objects.entries(shortcuts).map(([key, entry]) => (
|
||||
<div className="shortcut" onclick={async () => {
|
||||
const keys = await editShortcut(shortcuts, entry)
|
||||
shortcuts[key].keys.overrideWith(keys)
|
||||
update()
|
||||
}}><span>{entry.description}</span>
|
||||
<hr/>
|
||||
<span>{entry.keys.format()}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</details>
|
||||
)))
|
||||
lifecycle.own(updateNotifier.subscribe(update))
|
||||
update()
|
||||
}}>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
BinarySearch,
|
||||
Exec,
|
||||
isAbsent,
|
||||
JSONValue,
|
||||
Lazy,
|
||||
NumberComparator,
|
||||
Option,
|
||||
@@ -111,12 +112,24 @@ export class ShortcutKeys {
|
||||
return new ShortcutKeys(code, modifiers?.ctrl, modifiers?.shift, modifiers?.alt)
|
||||
}
|
||||
|
||||
static fromJSON(value: JSONValue): Option<ShortcutKeys> {
|
||||
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))
|
||||
}
|
||||
|
||||
static fromEvent(event: KeyboardEvent): Option<ShortcutKeys> {
|
||||
const code = event.code
|
||||
if (code.startsWith("Shift")
|
||||
|| code.startsWith("Control")
|
||||
|| code.startsWith("Alt")
|
||||
|| code.startsWith("Meta")) {
|
||||
|| code.startsWith("Meta")
|
||||
|| code === Key.Escape
|
||||
) {
|
||||
return Option.None
|
||||
}
|
||||
return Option.wrap(new ShortcutKeys(code, Keyboard.isControlKey(event), event.shiftKey, event.altKey))
|
||||
@@ -204,6 +217,10 @@ export class ShortcutKeys {
|
||||
this.#alt = keys.#alt
|
||||
}
|
||||
|
||||
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)}
|
||||
|
||||
toString(): string {return `{ShortcutKeys ${this.format()}}`}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user