UNPKG

polyv-live-cli

Version:

CLI tool for managing PolyV live streaming services.

597 lines 19.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.InteractionManager = void 0; const events_1 = require("events"); class InteractionManager extends events_1.EventEmitter { constructor(screen, config) { super(); this.focusableComponents = new Map(); this.focusHistory = []; this.shortcuts = new Map(); this.isDestroyed = false; this.screen = screen; this.config = config; this.initializeInteractionHandlers(); this.registerDefaultShortcuts(); } initializeInteractionHandlers() { if (!this.screen) { throw new Error('Screen instance is required for interaction management'); } if (this.config.keyboardEnabled) { this.setupKeyboardHandling(); } if (this.config.mouseEnabled) { this.setupMouseHandling(); } if (this.config.mouseEnabled && this.screen.program) { this.screen.program.enableMouse(); } } setupKeyboardHandling() { this.screen.on('keypress', (ch, key) => { if (this.isDestroyed || !key) return; const keyboardEvent = { key: key.name || '', shift: key.shift || false, ctrl: key.ctrl || false, alt: key.meta || false, meta: key.meta || false, full: key.full || '', ch: ch || '', }; this.handleKeyboardEvent(keyboardEvent); }); this.screen.key(['tab'], () => { this.focusNext(); }); this.screen.key(['S-tab'], () => { this.focusPrevious(); }); this.screen.key(['escape'], () => { this.handleEscape(); }); this.screen.key(['enter'], () => { this.handleEnter(); }); } setupMouseHandling() { this.screen.on('mouse', (data) => { if (this.isDestroyed) return; const mouseEvent = { action: data.action || 'mousedown', x: data.x || 0, y: data.y || 0, button: data.button || 'left', shift: data.shift || false, ctrl: data.ctrl || false, }; this.handleMouseEvent(mouseEvent); }); } registerDefaultShortcuts() { const defaultShortcuts = [ { key: 'f5', description: 'Refresh all monitoring data', action: 'refresh-all', global: true, }, { key: 'ctrl+r', description: 'Refresh current panel', action: 'refresh-current', global: true, }, { key: 'f1', description: 'Show help and shortcuts', action: 'help', global: true, }, { key: '?', description: 'Show help and shortcuts', action: 'help', global: true, }, { key: 'q', description: 'Exit application', action: 'exit', global: true, }, { key: 'ctrl+c', description: 'Exit application', action: 'exit', global: true, }, { key: 'escape', description: 'Cancel current operation or exit', action: 'cancel', global: true, }, { key: 'f11', description: 'Toggle fullscreen for current panel', action: 'fullscreen', global: true, }, { key: 'ctrl+l', description: 'Clear and redraw screen', action: 'clear-screen', global: true, }, ...Array.from({ length: 9 }, (_, i) => ({ key: `${i + 1}`, description: `Switch to layout ${i + 1}`, action: `layout:${i + 1}`, global: true, })), { key: 'ctrl+f', description: 'Open search', action: 'search', global: true, context: 'list', }, { key: '/', description: 'Quick search', action: 'search', global: true, context: 'list', }, ]; const allShortcuts = [...defaultShortcuts, ...this.config.shortcuts]; allShortcuts.forEach(shortcut => { this.registerShortcut(shortcut); }); } handleKeyboardEvent(event) { this.emit('keyboard:event', event); const shortcutKey = this.buildShortcutKey(event); if (this.shortcuts.has(shortcutKey)) { const shortcut = this.shortcuts.get(shortcutKey); this.activateShortcut(shortcut, event); return; } if (this.currentFocus) { const component = this.focusableComponents.get(this.currentFocus); if (component && component.handleKeyboard(event)) { return; } } this.handleNavigationKeys(event); } handleMouseEvent(event) { this.emit('mouse:event', event); if (event.action === 'wheelup' || event.action === 'wheeldown') { this.handleScrollEvent(event); return; } if (event.action === 'mousedown' && event.button === 'right') { this.handleContextMenu(event); return; } if (event.action === 'mousemove') { this.handleMouseHover(event); } const componentAtPosition = this.findComponentAtPosition(event.x, event.y); if (componentAtPosition) { const { id, component } = componentAtPosition; if (component.handleMouse(event)) { if (event.action === 'mousedown' && event.button === 'left') { this.setFocus(id); } return; } } for (const [id, component] of this.focusableComponents) { if (component.handleMouse(event)) { if (event.action === 'mousedown' && event.button === 'left') { this.setFocus(id); } return; } } } buildShortcutKey(event) { const parts = []; if (event.ctrl) parts.push('C'); if (event.alt) parts.push('A'); if (event.shift) parts.push('S'); if (event.meta) parts.push('M'); parts.push(event.key); return parts.join('-').toLowerCase(); } handleNavigationKeys(event) { switch (event.key) { case 'up': case 'down': case 'left': case 'right': this.handleArrowKeys(event.key); break; case 'home': this.focusFirst(); break; case 'end': this.focusLast(); break; } } handleArrowKeys(direction) { if (!this.currentFocus) { this.focusFirst(); return; } switch (direction) { case 'up': case 'left': this.focusPrevious(); break; case 'down': case 'right': this.focusNext(); break; } } handleEscape() { if (this.currentFocus) { const component = this.focusableComponents.get(this.currentFocus); if (component) { const handled = component.handleKeyboard({ key: 'escape', shift: false, ctrl: false, alt: false, meta: false, }); if (!handled) { this.clearFocus(); } } } } handleEnter() { if (this.currentFocus) { const component = this.focusableComponents.get(this.currentFocus); if (component) { component.handleKeyboard({ key: 'enter', shift: false, ctrl: false, alt: false, meta: false, }); } } } handleScrollEvent(event) { const componentAtPosition = this.findComponentAtPosition(event.x, event.y); if (componentAtPosition) { const { component } = componentAtPosition; if (component.handleMouse(event)) { return; } } if (this.currentFocus) { const component = this.focusableComponents.get(this.currentFocus); if (component) { component.handleMouse(event); } } this.emit('scroll:event', { direction: event.action === 'wheelup' ? 'up' : 'down', x: event.x, y: event.y, }); } handleMouseHover(event) { const componentAtPosition = this.findComponentAtPosition(event.x, event.y); if (componentAtPosition) { const { id, component } = componentAtPosition; this.emit('component:hover', { componentId: id, componentType: component.getType(), x: event.x, y: event.y, }); component.handleMouse(event); } else { this.emit('component:hover:clear'); } } findComponentAtPosition(x, y) { for (const [id, component] of this.focusableComponents) { if (component.canFocus() && this.isPositionInComponent(x, y, component)) { return { id, component }; } } if (this.currentFocus) { const component = this.focusableComponents.get(this.currentFocus); if (component && component.canFocus()) { return { id: this.currentFocus, component }; } } return undefined; } isPositionInComponent(x, y, component) { if (component && typeof component.getComponentBounds === 'function') { const bounds = component.getComponentBounds(); if (bounds) { return x >= bounds.left && x <= bounds.right && y >= bounds.top && y <= bounds.bottom; } } return true; } handleContextMenu(event) { let context = 'global'; let componentId = ''; const componentAtPosition = this.findComponentAtPosition(event.x, event.y); if (componentAtPosition) { context = componentAtPosition.component.getType(); componentId = componentAtPosition.id; } this.emit('contextmenu:requested', { x: event.x, y: event.y, context, componentId, }); } registerComponent(component) { if (this.isDestroyed) return; const id = component.getId(); this.focusableComponents.set(id, component); if (!this.currentFocus && component.canFocus()) { this.setFocus(id); } } unregisterComponent(id) { if (this.isDestroyed) return; if (this.currentFocus === id) { this.focusableComponents.delete(id); const remainingComponents = Array.from(this.focusableComponents.values()) .filter(c => c.canFocus()); if (remainingComponents.length > 0) { const firstComponent = remainingComponents[0]; if (firstComponent) { this.setFocus(firstComponent.getId()); } } else { this.currentFocus = undefined; this.emit('focus:changed', { previous: id, current: undefined, }); } } else { this.focusableComponents.delete(id); } this.focusHistory = this.focusHistory.filter(historyId => historyId !== id); } setFocus(componentId) { if (this.isDestroyed) return false; const component = this.focusableComponents.get(componentId); if (!component || !component.canFocus()) { return false; } const previousFocus = this.currentFocus; if (previousFocus && previousFocus !== componentId) { const prevComponent = this.focusableComponents.get(previousFocus); if (prevComponent) { prevComponent.blur(); } } this.currentFocus = componentId; component.focus(); if (previousFocus && previousFocus !== componentId) { this.focusHistory.push(previousFocus); if (this.focusHistory.length > 10) { this.focusHistory.shift(); } } this.emit('focus:changed', { previous: previousFocus, current: componentId, }); return true; } focusNext() { if (this.isDestroyed) return; const components = Array.from(this.focusableComponents.values()) .filter(c => c.canFocus()) .sort((a, b) => a.getFocusState().tabIndex - b.getFocusState().tabIndex); if (components.length === 0) return; if (!this.currentFocus) { const firstComponent = components[0]; if (firstComponent) { this.setFocus(firstComponent.getId()); } return; } const currentIndex = components.findIndex(c => c.getId() === this.currentFocus); const nextIndex = (currentIndex + 1) % components.length; const nextComponent = components[nextIndex]; if (nextComponent) { this.setFocus(nextComponent.getId()); } } focusPrevious() { if (this.isDestroyed) return; const components = Array.from(this.focusableComponents.values()) .filter(c => c.canFocus()) .sort((a, b) => a.getFocusState().tabIndex - b.getFocusState().tabIndex); if (components.length === 0) return; if (!this.currentFocus) { const lastComponent = components[components.length - 1]; if (lastComponent) { this.setFocus(lastComponent.getId()); } return; } const currentIndex = components.findIndex(c => c.getId() === this.currentFocus); const prevIndex = currentIndex === 0 ? components.length - 1 : currentIndex - 1; const prevComponent = components[prevIndex]; if (prevComponent) { this.setFocus(prevComponent.getId()); } } focusFirst() { if (this.isDestroyed) return; const components = Array.from(this.focusableComponents.values()) .filter(c => c.canFocus()) .sort((a, b) => a.getFocusState().tabIndex - b.getFocusState().tabIndex); if (components.length > 0) { const firstComponent = components[0]; if (firstComponent) { this.setFocus(firstComponent.getId()); } } } focusLast() { if (this.isDestroyed) return; const components = Array.from(this.focusableComponents.values()) .filter(c => c.canFocus()) .sort((a, b) => a.getFocusState().tabIndex - b.getFocusState().tabIndex); if (components.length > 0) { const lastComponent = components[components.length - 1]; if (lastComponent) { this.setFocus(lastComponent.getId()); } } } clearFocus() { if (this.isDestroyed || !this.currentFocus) return; const component = this.focusableComponents.get(this.currentFocus); if (component) { component.blur(); } const previousFocus = this.currentFocus; this.currentFocus = undefined; this.emit('focus:changed', { previous: previousFocus, current: undefined, }); } registerShortcut(shortcut) { if (this.isDestroyed) return; const key = shortcut.key.toLowerCase(); this.shortcuts.set(key, shortcut); } unregisterShortcut(key) { if (this.isDestroyed) return; this.shortcuts.delete(key.toLowerCase()); } activateShortcut(shortcut, _event) { this.emit('shortcut:activated', { key: shortcut.key, action: shortcut.action, }); switch (shortcut.action) { case 'refresh-all': this.emit('action:refresh-all'); break; case 'refresh-current': this.emit('action:refresh-current'); break; case 'help': this.emit('action:help'); break; case 'exit': this.emit('action:exit'); break; case 'cancel': this.emit('action:cancel'); break; case 'fullscreen': this.emit('action:fullscreen'); break; case 'clear-screen': this.emit('action:clear-screen'); break; case 'search': this.emit('action:search'); break; default: if (shortcut.action.startsWith('layout:')) { const layoutNumber = shortcut.action.split(':')[1]; this.emit('action:layout', { layout: layoutNumber }); } else if (shortcut.action.startsWith('panel:')) { const panelNumber = shortcut.action.split(':')[1]; this.emit('action:panel', { panel: panelNumber }); } else { this.emit('action:custom', { action: shortcut.action, key: shortcut.key, description: shortcut.description }); } break; } } getAllShortcuts() { return Array.from(this.shortcuts.values()); } getCurrentFocus() { return this.currentFocus; } getComponents() { return Array.from(this.focusableComponents.values()); } getShortcuts() { return Array.from(this.shortcuts.values()); } getComponent(id) { return this.focusableComponents.get(id); } isInteractionManagerDestroyed() { return this.isDestroyed; } destroy() { if (this.isDestroyed) return; this.isDestroyed = true; this.clearFocus(); this.focusableComponents.clear(); this.currentFocus = undefined; this.shortcuts.clear(); this.focusHistory = []; if (this.config.mouseEnabled && this.screen?.program) { this.screen.program.disableMouse(); } this.removeAllListeners(); this.emit('destroyed'); } } exports.InteractionManager = InteractionManager; //# sourceMappingURL=interaction-manager.js.map