mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-17 21:24:11 +00:00
e9289685fd
Replaces the view hotkey store with a suppressSpaceHotkey prop on ViewNavigationMenu, alongside the suppressSettings prop which already exists for exactly this purpose: a view opting out of a navigation chrome behaviour. The store had one advantage, that it covered the preset route without the router naming the view. That is a single expression in PresetView, and paying for it with a global claim was the wrong trade: the claim was lifecycle state that had to be released on unmount to avoid stranding Space for the whole client, where a prop simply describes the route. Verified both routes: /teleprompter and a locked preset targeting the teleprompter leave Space to the view, while the timer and a preset targeting the timer still open the navigation menu with it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cb8RVPNQ2ETPJxdy4b8CHf
83 lines
2.8 KiB
TypeScript
83 lines
2.8 KiB
TypeScript
import { useEffect, useRef } from 'react';
|
|
|
|
import { useViewParamsEditorStore } from '../../common/components/view-params-editor/viewParamsEditor.store';
|
|
import { resolveTeleprompterAction } from './teleprompter.keymap';
|
|
import type { TeleprompterAction, TeleprompterController } from './teleprompter.types';
|
|
|
|
interface UseTeleprompterControlsArgs {
|
|
controller: TeleprompterController;
|
|
onFlip: (axis: 'h' | 'v') => void;
|
|
onFontSize: (delta: number) => void;
|
|
onResetFontSize: () => void;
|
|
onToggleHelp: () => void;
|
|
}
|
|
|
|
const ignoredTags = new Set(['INPUT', 'TEXTAREA', 'SELECT']);
|
|
|
|
/**
|
|
* Binds the prompter keymap.
|
|
*
|
|
* Space is the universal run/stop key on prompter software and the centre pedal
|
|
* of every three pedal controller, so this view cannot leave it to the
|
|
* navigation menu. The routes which render the teleprompter pass
|
|
* suppressSpaceHotkey to ViewNavigationMenu so the menu stands down.
|
|
*/
|
|
export function useTeleprompterControls(args: UseTeleprompterControlsArgs) {
|
|
const argsRef = useRef(args);
|
|
argsRef.current = args;
|
|
|
|
useEffect(() => {
|
|
function applyAction(action: TeleprompterAction) {
|
|
const { controller, onFlip, onFontSize, onResetFontSize, onToggleHelp } = argsRef.current;
|
|
switch (action.type) {
|
|
case 'togglePlay':
|
|
return controller.togglePlay();
|
|
case 'nudge':
|
|
return controller.nudge(action.lines);
|
|
case 'page':
|
|
return controller.page(action.direction);
|
|
case 'speed':
|
|
return controller.changeSpeed(action.delta);
|
|
case 'rewind':
|
|
return controller.rewind();
|
|
case 'rewindAndPause':
|
|
return controller.rewind(true);
|
|
case 'jumpToEnd':
|
|
return controller.jumpToEnd();
|
|
case 'reengageFollow':
|
|
return controller.reengageFollow();
|
|
case 'flip':
|
|
return onFlip(action.axis);
|
|
case 'fontSize':
|
|
return onFontSize(action.delta);
|
|
case 'resetFontSize':
|
|
return onResetFontSize();
|
|
case 'toggleHelp':
|
|
return onToggleHelp();
|
|
}
|
|
}
|
|
|
|
function handleKeyDown(event: KeyboardEvent) {
|
|
const target = event.target as HTMLElement | null;
|
|
if (target && (ignoredTags.has(target.tagName) || target.isContentEditable)) {
|
|
return;
|
|
}
|
|
// the params editor is a form, it owns the keyboard while it is open
|
|
if (useViewParamsEditorStore.getState().isOpen) {
|
|
return;
|
|
}
|
|
|
|
const action = resolveTeleprompterAction(event);
|
|
if (!action) return;
|
|
|
|
// without this the browser also scrolls the container natively and the
|
|
// movement is applied twice
|
|
event.preventDefault();
|
|
applyAction(action);
|
|
}
|
|
|
|
window.addEventListener('keydown', handleKeyDown);
|
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
}, []);
|
|
}
|