mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-19 14:14:17 +00:00
8ec12ae4d2
Speed, font size and the flips could each be set from two places: the view params editor, which writes the query, and the prompter's own keys and buttons, which wrote React state and nothing else. The URL therefore described how the view was opened rather than how it had been tuned, so a link copied after setting the prompter up handed the next person a different prompter, and a reload threw the setup away. The live controls now mirror into the same query the params editor writes, so the existing features built on it — sharing a link, saving a URL preset, redirecting a client — describe what is actually on screen without changes of their own. Settings left at their default stay out of the query, so a shared link carries only what was deliberately changed. The write is debounced and replaces rather than pushes: the arrow keys repeat while held, and neither a held key nor a foot pedal should fill the address bar or the back button with one entry per frame. Font size was a hidden multiplier on top of the option, which had no way to be expressed as a param. It is now the size itself, stepped by a ratio so a press is the same visual change at any size, sharing the option's own range. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cb8RVPNQ2ETPJxdy4b8CHf
99 lines
4.4 KiB
TypeScript
99 lines
4.4 KiB
TypeScript
import { resolveTeleprompterAction, type TeleprompterKeyEvent } from '../teleprompter.keymap';
|
|
import { SPEED_STEP, SPEED_STEP_COARSE } from '../teleprompter.scroll';
|
|
|
|
function makeEvent(overrides: Partial<TeleprompterKeyEvent>): TeleprompterKeyEvent {
|
|
return {
|
|
code: '',
|
|
key: '',
|
|
shiftKey: false,
|
|
ctrlKey: false,
|
|
metaKey: false,
|
|
altKey: false,
|
|
repeat: false,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe('resolveTeleprompterAction()', () => {
|
|
test('space toggles playback', () => {
|
|
expect(resolveTeleprompterAction(makeEvent({ code: 'Space' }))).toEqual({ type: 'togglePlay' });
|
|
});
|
|
|
|
test('ignores a repeating space', () => {
|
|
// a held key, or a pressed foot pedal, otherwise toggles playback dozens of times
|
|
expect(resolveTeleprompterAction(makeEvent({ code: 'Space', repeat: true }))).toBeNull();
|
|
});
|
|
|
|
test('vertical arrows nudge by a line', () => {
|
|
expect(resolveTeleprompterAction(makeEvent({ code: 'ArrowDown' }))).toEqual({ type: 'nudge', lines: 1 });
|
|
expect(resolveTeleprompterAction(makeEvent({ code: 'ArrowUp' }))).toEqual({ type: 'nudge', lines: -1 });
|
|
});
|
|
|
|
test('a repeating arrow still nudges, so the key can be held', () => {
|
|
expect(resolveTeleprompterAction(makeEvent({ code: 'ArrowDown', repeat: true }))).toEqual({
|
|
type: 'nudge',
|
|
lines: 1,
|
|
});
|
|
});
|
|
|
|
test('page keys jump a screen', () => {
|
|
expect(resolveTeleprompterAction(makeEvent({ code: 'PageDown' }))).toEqual({ type: 'page', direction: 1 });
|
|
expect(resolveTeleprompterAction(makeEvent({ code: 'PageUp' }))).toEqual({ type: 'page', direction: -1 });
|
|
});
|
|
|
|
test('horizontal arrows change speed', () => {
|
|
expect(resolveTeleprompterAction(makeEvent({ code: 'ArrowRight' }))).toEqual({ type: 'speed', delta: SPEED_STEP });
|
|
expect(resolveTeleprompterAction(makeEvent({ code: 'ArrowLeft' }))).toEqual({ type: 'speed', delta: -SPEED_STEP });
|
|
});
|
|
|
|
test('shift makes the speed step coarse', () => {
|
|
expect(resolveTeleprompterAction(makeEvent({ code: 'ArrowRight', shiftKey: true }))).toEqual({
|
|
type: 'speed',
|
|
delta: SPEED_STEP_COARSE,
|
|
});
|
|
expect(resolveTeleprompterAction(makeEvent({ code: 'ArrowLeft', shiftKey: true }))).toEqual({
|
|
type: 'speed',
|
|
delta: -SPEED_STEP_COARSE,
|
|
});
|
|
});
|
|
|
|
test('home rewinds and escape rewinds and stops', () => {
|
|
expect(resolveTeleprompterAction(makeEvent({ code: 'Home' }))).toEqual({ type: 'rewind' });
|
|
expect(resolveTeleprompterAction(makeEvent({ code: 'Escape' }))).toEqual({ type: 'rewindAndPause' });
|
|
expect(resolveTeleprompterAction(makeEvent({ code: 'End' }))).toEqual({ type: 'jumpToEnd' });
|
|
});
|
|
|
|
test('f flips, shift+f flips the other axis', () => {
|
|
expect(resolveTeleprompterAction(makeEvent({ key: 'f' }))).toEqual({ type: 'flip', axis: 'h' });
|
|
expect(resolveTeleprompterAction(makeEvent({ key: 'F', shiftKey: true }))).toEqual({ type: 'flip', axis: 'v' });
|
|
});
|
|
|
|
test('font size is resolved by key so it survives other layouts', () => {
|
|
expect(resolveTeleprompterAction(makeEvent({ key: '+' }))).toMatchObject({ type: 'fontSize' });
|
|
expect(resolveTeleprompterAction(makeEvent({ key: '=' }))).toMatchObject({ type: 'fontSize' });
|
|
expect(resolveTeleprompterAction(makeEvent({ key: '-' }))?.type).toBe('fontSize');
|
|
expect(resolveTeleprompterAction(makeEvent({ key: '0' }))).toEqual({ type: 'resetFontSize' });
|
|
});
|
|
|
|
test('plus increases and minus decreases', () => {
|
|
expect(resolveTeleprompterAction(makeEvent({ key: '+' }))).toEqual({ type: 'fontSize', steps: 1 });
|
|
expect(resolveTeleprompterAction(makeEvent({ key: '-' }))).toEqual({ type: 'fontSize', steps: -1 });
|
|
});
|
|
|
|
test('l re-engages the follow and ? shows the help', () => {
|
|
expect(resolveTeleprompterAction(makeEvent({ key: 'l' }))).toEqual({ type: 'reengageFollow' });
|
|
expect(resolveTeleprompterAction(makeEvent({ key: '?', shiftKey: true }))).toEqual({ type: 'toggleHelp' });
|
|
});
|
|
|
|
test('never shadows a shortcut which carries a modifier', () => {
|
|
// mod+, opens the view params and must keep working
|
|
expect(resolveTeleprompterAction(makeEvent({ key: ',', metaKey: true }))).toBeNull();
|
|
expect(resolveTeleprompterAction(makeEvent({ code: 'Space', ctrlKey: true }))).toBeNull();
|
|
expect(resolveTeleprompterAction(makeEvent({ code: 'ArrowRight', altKey: true }))).toBeNull();
|
|
});
|
|
|
|
test('ignores keys it does not bind', () => {
|
|
expect(resolveTeleprompterAction(makeEvent({ code: 'KeyQ', key: 'q' }))).toBeNull();
|
|
});
|
|
});
|