-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Rename AudioRouter to AudioProvider * add Keyboard context * bind some keyboard actions * select tracks on 0-9 * update keyboard binding display * Fix Track controls * update roadmnap * make KeyboardBindings data driven
- Loading branch information
Showing
9 changed files
with
338 additions
and
63 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
/** | ||
* Exposes a context that can be used to bind keyboard events throughout the app. | ||
* Keydown/Keyup listeners are easy to add, but the syntax is kind of annoying | ||
* because the callback has to check for the right key. | ||
* This is a more convenient wrapper for `window.addEventListener('keydown', callback). | ||
* Perhaps this doesn't need to be a context at all, and can just be an exported function | ||
* that wraps `window.addEventListener` -- we'll see! | ||
* | ||
* (intended) Usage: | ||
* | ||
* function MyComponent() { | ||
* const keyboard = useKeyboard() | ||
* | ||
* function myFunction() { | ||
* // do something | ||
* } | ||
* | ||
* keyboard.on('a', 'id', myFunction) | ||
* } | ||
*/ | ||
import React, { createContext, useContext, useEffect, useMemo } from 'react' | ||
import { logger } from '../util/logger' | ||
|
||
type KeyboardEventHandler = (event: KeyboardEvent) => void | ||
|
||
type KeyboardController = { | ||
on(key: string, id: string, callback: KeyboardEventHandler): void | ||
off(key: string, id: string): void | ||
} | ||
|
||
type EventHandler = { | ||
id?: string | ||
callback: KeyboardEventHandler | ||
} | ||
type CallbackMap = Record<string, EventHandler[]> | ||
|
||
const KeyboardContext = createContext<KeyboardController | null>(null) | ||
|
||
type Props = { | ||
children: React.ReactNode | ||
} | ||
|
||
export const KeyboardProvider: React.FC<Props> = ({ children }) => { | ||
// callbackMap is a map of keys to EventHandlers. | ||
// EventHandlers contain an (optional) ID and a callback. | ||
// The ID allows deduplication, so that multiple event registrations | ||
// do not result in multiple callback calls. | ||
// The ID also allows us to register multiple EventHandlers for a single key; | ||
// this is primarily useful for the event registrations on Tracks, | ||
// since they are added and removed depending on whether the track is selected. | ||
const callbackMap: CallbackMap = useMemo( | ||
() => ({ | ||
Escape: [ | ||
{ | ||
callback: () => { | ||
// @ts-expect-error this is totally valid, not sure why TS doesn't think so | ||
const maybeFn = document.activeElement?.blur?.bind( | ||
document.activeElement | ||
) | ||
if (typeof maybeFn === 'function') { | ||
maybeFn() | ||
} | ||
}, | ||
}, | ||
], | ||
}), | ||
[] | ||
) | ||
|
||
useEffect(() => { | ||
const keydownCallback = (e: KeyboardEvent) => { | ||
logger.debug({ key: e.key, meta: e.metaKey, shift: e.shiftKey }) | ||
callbackMap[e.key]?.map((item) => item.callback(e)) | ||
} | ||
window.addEventListener('keydown', keydownCallback) | ||
return () => { | ||
window.removeEventListener('keydown', keydownCallback) | ||
} | ||
}, [callbackMap]) | ||
|
||
const controller = { | ||
on(key: string, id: string, callback: KeyboardEventHandler) { | ||
if (Array.isArray(callbackMap[key])) { | ||
const index = callbackMap[key].findIndex((item) => item.id === id) | ||
if (index < 0) { | ||
callbackMap[key].push({ id, callback }) | ||
} else { | ||
callbackMap[key][index] = { id, callback } | ||
} | ||
} else { | ||
callbackMap[key] = [{ id, callback }] | ||
} | ||
}, | ||
off(key: string, id: string) { | ||
if (!Array.isArray(callbackMap[key])) { | ||
return // nothing to do | ||
} | ||
const index = callbackMap[key].findIndex((item) => item.id === id) | ||
if (index >= 0) { | ||
callbackMap[key].splice(index, 1) | ||
} | ||
}, | ||
} | ||
|
||
return ( | ||
<KeyboardContext.Provider value={controller}> | ||
{children} | ||
</KeyboardContext.Provider> | ||
) | ||
} | ||
|
||
export function useKeyboard() { | ||
const keyboard = useContext(KeyboardContext) | ||
|
||
if (keyboard === null) { | ||
throw new Error('useKeyboard cannot be used outside of KeyboardProvider') | ||
} | ||
|
||
return keyboard | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
/** | ||
* This is a simple display input, | ||
* to inform users how to use keyboard bindings. | ||
*/ | ||
|
||
const globalKeyBindings = { | ||
space: 'Play / pause', | ||
c: 'Mute click track', | ||
'0-9': 'Select track', | ||
} | ||
|
||
const trackKeyBindings = { | ||
r: 'Arm for recording', | ||
m: 'Mute track', | ||
i: 'Monitor input', | ||
} | ||
|
||
export default function KeyboardBindings() { | ||
return ( | ||
<div> | ||
<h2 className="text-xl mb-8 mt-16">Keyboard controls</h2> | ||
<table className=""> | ||
<thead> | ||
<tr> | ||
<th className="text-left">Key</th> | ||
<th className="text-left">Binding</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{Object.entries(globalKeyBindings).map(([key, action]) => ( | ||
<tr> | ||
<td className="w-32">{key}</td> | ||
<td>{action}</td> | ||
</tr> | ||
))} | ||
|
||
<tr> | ||
<td colSpan={2}> | ||
<em>After selecting track</em> | ||
</td> | ||
</tr> | ||
|
||
{Object.entries(trackKeyBindings).map(([key, action]) => ( | ||
<tr> | ||
<td className="w-32">{key}</td> | ||
<td>{action}</td> | ||
</tr> | ||
))} | ||
</tbody> | ||
</table> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.