polyv-live-cli
Version:
CLI tool for managing PolyV live streaming services.
597 lines • 19.9 kB
JavaScript
"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