polyv-live-cli
Version:
CLI tool for managing PolyV live streaming services.
235 lines • 8.15 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ContextMenu = void 0;
const blessed_1 = __importDefault(require("blessed"));
class ContextMenu {
constructor(screen, eventBus) {
this.isVisible = false;
this.selectedIndex = 0;
this.menuItems = [];
this.screen = screen;
this.eventBus = eventBus;
}
show(config) {
if (this.isVisible) {
this.hide();
}
this.currentConfig = config;
this.menuItems = config.items.filter(item => item.enabled !== false);
this.selectedIndex = 0;
if (this.menuItems.length === 0) {
return;
}
this.createMenuBox(config);
if (this.menuBox) {
this.isVisible = true;
this.eventBus.emit('contextmenu:shown', {
x: config.x,
y: config.y,
context: config.context,
componentId: config.componentId,
itemCount: this.menuItems.length,
timestamp: new Date(),
});
}
}
hide() {
if (!this.isVisible || !this.menuBox)
return;
try {
this.screen.remove(this.menuBox);
this.menuBox = null;
this.isVisible = false;
this.selectedIndex = 0;
this.menuItems = [];
this.screen.render();
this.eventBus.emit('contextmenu:hidden', {
timestamp: new Date(),
});
}
catch (error) {
console.error('Failed to hide context menu:', error);
this.menuBox = null;
this.isVisible = false;
}
}
navigateUp() {
if (!this.isVisible || this.menuItems.length === 0)
return;
this.selectedIndex = this.selectedIndex === 0
? this.menuItems.length - 1
: this.selectedIndex - 1;
this.updateMenuDisplay();
}
navigateDown() {
if (!this.isVisible || this.menuItems.length === 0)
return;
this.selectedIndex = (this.selectedIndex + 1) % this.menuItems.length;
this.updateMenuDisplay();
}
selectItem() {
if (!this.isVisible || this.menuItems.length === 0)
return;
const selectedItem = this.menuItems[this.selectedIndex];
if (selectedItem) {
this.executeAction(selectedItem);
this.hide();
}
}
isContextMenuVisible() {
return this.isVisible;
}
getSelectedIndex() {
return this.selectedIndex;
}
createMenuBox(config) {
try {
const content = this.generateMenuContent();
const { adjustedX, adjustedY } = this.calculatePosition(config.x, config.y);
const menuWidth = this.calculateMenuWidth();
const menuHeight = this.menuItems.length + 2;
this.menuBox = blessed_1.default.box({
left: adjustedX,
top: adjustedY,
width: menuWidth,
height: menuHeight,
content,
tags: true,
border: {
type: 'line',
},
style: {
fg: config.style?.fg || 'white',
bg: config.style?.bg || 'black',
border: {
fg: config.style?.border?.fg || 'gray',
bg: config.style?.border?.bg || 'black',
},
},
keys: true,
mouse: true,
});
this.setupMenuEvents();
this.screen.append(this.menuBox);
this.menuBox.focus();
this.screen.render();
}
catch (error) {
console.error('Failed to create context menu:', error);
this.isVisible = false;
this.menuBox = null;
}
}
setupMenuEvents() {
if (!this.menuBox)
return;
this.menuBox.key(['up', 'k'], () => {
this.navigateUp();
});
this.menuBox.key(['down', 'j'], () => {
this.navigateDown();
});
this.menuBox.key(['enter', 'space'], () => {
this.selectItem();
});
this.menuBox.key(['escape', 'q'], () => {
this.hide();
});
this.menuBox.on('click', (data) => {
if (data && typeof data.y === 'number') {
const clickedIndex = data.y - 1;
if (clickedIndex >= 0 && clickedIndex < this.menuItems.length) {
this.selectedIndex = clickedIndex;
this.selectItem();
}
}
});
if (this.currentConfig?.autoHide !== false) {
this.screen.on('click', () => {
setTimeout(() => {
if (this.isVisible) {
this.hide();
}
}, 50);
});
}
}
generateMenuContent() {
const lines = [];
this.menuItems.forEach((item, index) => {
const isSelected = index === this.selectedIndex;
const icon = item.icon ? `${item.icon} ` : '';
const shortcut = item.shortcut ? ` {gray-fg}${item.shortcut}{/gray-fg}` : '';
let line = `${icon}${item.label}${shortcut}`;
if (isSelected) {
const selectedFg = this.currentConfig?.style?.selectedFg || 'black';
const selectedBg = this.currentConfig?.style?.selectedBg || 'white';
line = `{${selectedBg}-bg}{${selectedFg}-fg}${line}{/${selectedFg}-fg}{/${selectedBg}-bg}`;
}
lines.push(line);
if (item.separator && index < this.menuItems.length - 1) {
lines.push('{gray-fg}─'.repeat(this.calculateMenuWidth() - 4) + '{/gray-fg}');
}
});
return lines.join('\n');
}
updateMenuDisplay() {
if (!this.menuBox)
return;
try {
const content = this.generateMenuContent();
this.menuBox.setContent(content);
this.screen.render();
}
catch (error) {
console.error('Failed to update menu display:', error);
}
}
calculateMenuWidth() {
let maxWidth = 20;
this.menuItems.forEach(item => {
const iconLength = item.icon ? item.icon.length + 1 : 0;
const shortcutLength = item.shortcut ? item.shortcut.length + 1 : 0;
const itemWidth = iconLength + item.label.length + shortcutLength + 4;
maxWidth = Math.max(maxWidth, itemWidth);
});
return Math.min(maxWidth, 60);
}
calculatePosition(x, y) {
const screenWidth = this.screen.width || 80;
const screenHeight = this.screen.height || 24;
const menuWidth = this.calculateMenuWidth();
const menuHeight = this.menuItems.length + 2;
let adjustedX = x;
let adjustedY = y;
if (adjustedX + menuWidth > screenWidth) {
adjustedX = Math.max(0, x - menuWidth);
}
if (adjustedY + menuHeight > screenHeight) {
adjustedY = Math.max(0, y - menuHeight);
}
return { adjustedX, adjustedY };
}
executeAction(item) {
this.eventBus.emit('contextmenu:action', {
action: item.action,
label: item.label,
context: this.currentConfig?.context,
componentId: this.currentConfig?.componentId,
timestamp: new Date(),
});
this.eventBus.emit(`action:${item.action}`, {
context: this.currentConfig?.context,
componentId: this.currentConfig?.componentId,
source: 'contextmenu',
});
}
destroy() {
this.hide();
}
}
exports.ContextMenu = ContextMenu;
//# sourceMappingURL=context-menu.js.map