polyv-live-cli
Version:
CLI tool for managing PolyV live streaming services.
538 lines • 20.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LayoutManager = void 0;
class LayoutManager {
constructor(screen, eventBus) {
this.layouts = new Map();
this.panels = new Map();
this.originalPositions = new Map();
this.screen = screen;
this.eventBus = eventBus;
this.currentState = {
currentLayout: 'default',
panelStates: new Map(),
history: [],
};
this.initializeDefaultLayouts();
this.setupEventHandlers();
}
initializeDefaultLayouts() {
this.layouts.set('compact', {
name: 'compact',
description: 'Compact layout for small terminals',
minSize: { width: 60, height: 20 },
panels: [
{
id: 'main-status',
title: 'Status',
component: null,
position: { top: 0, left: 0, width: '100%', height: '50%' },
border: { type: 'line', fg: 'cyan' },
visible: true,
state: 'normal',
},
{
id: 'system-info',
title: 'System',
component: null,
position: { top: '50%', left: 0, width: '100%', height: '50%' },
border: { type: 'line', fg: 'yellow' },
visible: true,
state: 'normal',
},
],
});
this.layouts.set('standard', {
name: 'standard',
description: 'Standard layout with balanced panels',
minSize: { width: 80, height: 24 },
panels: [
{
id: 'channel-status',
title: 'Channel Status',
component: null,
position: { top: 0, left: 0, width: '60%', height: '60%' },
border: { type: 'line', fg: 'cyan' },
visible: true,
state: 'normal',
},
{
id: 'system-resources',
title: 'System Resources',
component: null,
position: { top: 0, left: '60%', width: '40%', height: '60%' },
border: { type: 'line', fg: 'yellow' },
visible: true,
state: 'normal',
},
{
id: 'stream-metrics',
title: 'Stream Metrics',
component: null,
position: { top: '60%', left: 0, width: '50%', height: '40%' },
border: { type: 'line', fg: 'green' },
visible: true,
state: 'normal',
},
{
id: 'activity-log',
title: 'Activity Log',
component: null,
position: { top: '60%', left: '50%', width: '50%', height: '40%' },
border: { type: 'line', fg: 'magenta' },
visible: true,
state: 'normal',
},
],
});
this.layouts.set('detailed', {
name: 'detailed',
description: 'Detailed layout with all panels visible',
minSize: { width: 120, height: 30 },
panels: [
{
id: 'channel-status',
title: 'Channel Status',
component: null,
position: { top: 0, left: 0, width: '40%', height: '50%' },
border: { type: 'line', fg: 'cyan' },
visible: true,
state: 'normal',
},
{
id: 'stream-metrics',
title: 'Stream Metrics',
component: null,
position: { top: 0, left: '40%', width: '30%', height: '50%' },
border: { type: 'line', fg: 'green' },
visible: true,
state: 'normal',
},
{
id: 'system-resources',
title: 'System Resources',
component: null,
position: { top: 0, left: '70%', width: '30%', height: '50%' },
border: { type: 'line', fg: 'yellow' },
visible: true,
state: 'normal',
},
{
id: 'activity-log',
title: 'Activity Log',
component: null,
position: { top: '50%', left: 0, width: '60%', height: '50%' },
border: { type: 'line', fg: 'magenta' },
visible: true,
state: 'normal',
},
{
id: 'performance-monitor',
title: 'Performance',
component: null,
position: { top: '50%', left: '60%', width: '40%', height: '50%' },
border: { type: 'line', fg: 'red' },
visible: true,
state: 'normal',
},
],
});
this.currentState.currentLayout = 'standard';
}
setupEventHandlers() {
this.screen.on('resize', () => {
this.handleScreenResize();
});
this.eventBus.on('panel:minimize', (data) => {
this.minimizePanel(data.panelId);
});
this.eventBus.on('panel:maximize', (data) => {
this.maximizePanel(data.panelId);
});
this.eventBus.on('panel:restore', (data) => {
this.restorePanel(data.panelId);
});
this.eventBus.on('panel:fullscreen', (data) => {
this.toggleFullscreen(data.panelId);
});
this.eventBus.on('layout:switch', (data) => {
this.switchLayout(data.layoutName);
});
}
registerPanel(panelId, component) {
this.panels.set(panelId, component);
if (component && typeof component.on === 'function') {
component.on('doubleclick', () => {
this.toggleFullscreen(panelId);
});
}
this.eventBus.emit('panel:registered', {
panelId,
timestamp: new Date(),
});
}
unregisterPanel(panelId) {
const component = this.panels.get(panelId);
if (component) {
this.panels.delete(panelId);
this.currentState.panelStates.delete(panelId);
this.eventBus.emit('panel:unregistered', {
panelId,
timestamp: new Date(),
});
}
}
switchLayout(layoutName) {
if (!this.layouts.has(layoutName)) {
throw new Error(`Layout '${layoutName}' not found`);
}
const layout = this.layouts.get(layoutName);
const terminalSize = this.getTerminalSize();
if (terminalSize.width < layout.minSize.width ||
terminalSize.height < layout.minSize.height) {
this.eventBus.emit('layout:size:insufficient', {
required: layout.minSize,
current: terminalSize,
layout: layoutName,
});
return;
}
this.currentState.history.push(this.currentState.currentLayout);
if (this.currentState.history.length > 10) {
this.currentState.history.shift();
}
this.currentState.currentLayout = layoutName;
this.applyLayout(layout);
this.eventBus.emit('layout:switched', {
fromLayout: this.currentState.history[this.currentState.history.length - 1],
toLayout: layoutName,
timestamp: new Date(),
});
}
applyLayout(layout) {
this.storeCurrentStates();
layout.panels.forEach(panelConfig => {
const component = this.panels.get(panelConfig.id);
if (component) {
this.applyPanelConfig(panelConfig, component);
this.currentState.panelStates.set(panelConfig.id, { ...panelConfig });
}
});
this.panels.forEach((_component, panelId) => {
if (!layout.panels.find(p => p.id === panelId)) {
this.hidePanel(panelId);
}
});
this.screen.render();
}
applyPanelConfig(config, component) {
try {
if (component.widget) {
const position = this.calculatePosition(config.position);
component.widget.left = position.left;
component.widget.top = position.top;
component.widget.width = position.width;
component.widget.height = position.height;
if (config.border) {
component.widget.border = {
type: config.border.type || 'line',
};
if (component.widget.style) {
component.widget.style.border = {
fg: config.border.fg || 'white',
bg: config.border.bg || 'black',
};
}
}
if (config.visible !== false) {
component.widget.show();
}
else {
component.widget.hide();
}
if (config.title && component.widget.setLabel) {
component.widget.setLabel(` ${config.title} `);
}
}
}
catch (error) {
console.error(`Failed to apply panel config for ${config.id}:`, error);
}
}
calculatePosition(position) {
const screenSize = this.getTerminalSize();
const calculateValue = (value, dimension) => {
if (typeof value === 'string' && value.endsWith('%')) {
const percentage = parseInt(value) / 100;
return Math.floor(percentage * screenSize[dimension]);
}
return typeof value === 'number' ? value : parseInt(String(value));
};
return {
left: calculateValue(position.left, 'width'),
top: calculateValue(position.top, 'height'),
width: calculateValue(position.width, 'width'),
height: calculateValue(position.height, 'height'),
};
}
toggleFullscreen(panelId) {
const component = this.panels.get(panelId);
if (!component)
return;
if (this.currentState.fullscreenPanel === panelId) {
this.exitFullscreen();
}
else {
this.enterFullscreen(panelId);
}
}
enterFullscreen(panelId) {
const component = this.panels.get(panelId);
if (!component || !component.widget)
return;
this.originalPositions.set(panelId, {
id: panelId,
title: '',
component,
position: {
left: component.widget.left,
top: component.widget.top,
width: component.widget.width,
height: component.widget.height,
},
});
this.panels.forEach((otherComponent, otherId) => {
if (otherId !== panelId && otherComponent.widget) {
otherComponent.widget.hide();
}
});
component.widget.left = 0;
component.widget.top = 0;
component.widget.width = '100%';
component.widget.height = '100%';
component.widget.show();
this.currentState.fullscreenPanel = panelId;
this.screen.render();
this.eventBus.emit('panel:fullscreen:entered', {
panelId,
timestamp: new Date(),
});
}
exitFullscreen() {
if (!this.currentState.fullscreenPanel)
return;
const panelId = this.currentState.fullscreenPanel;
const component = this.panels.get(panelId);
const originalPos = this.originalPositions.get(panelId);
if (component && component.widget && originalPos) {
component.widget.left = originalPos.position.left;
component.widget.top = originalPos.position.top;
component.widget.width = originalPos.position.width;
component.widget.height = originalPos.position.height;
}
this.panels.forEach((otherComponent, otherId) => {
if (otherId !== panelId && otherComponent.widget) {
const panelState = this.currentState.panelStates.get(otherId);
if (panelState && panelState.visible !== false) {
otherComponent.widget.show();
}
}
});
this.currentState.fullscreenPanel = undefined;
this.originalPositions.delete(panelId);
this.screen.render();
this.eventBus.emit('panel:fullscreen:exited', {
panelId,
timestamp: new Date(),
});
}
minimizePanel(panelId) {
const component = this.panels.get(panelId);
if (!component || !component.widget)
return;
const panelState = this.currentState.panelStates.get(panelId);
if (panelState) {
panelState.state = 'minimized';
component.widget.hide();
this.screen.render();
this.eventBus.emit('panel:minimized', {
panelId,
timestamp: new Date(),
});
}
}
maximizePanel(panelId) {
const component = this.panels.get(panelId);
if (!component || !component.widget)
return;
if (!this.originalPositions.has(panelId)) {
this.originalPositions.set(panelId, {
id: panelId,
title: '',
component,
position: {
left: component.widget.left,
top: component.widget.top,
width: component.widget.width,
height: component.widget.height,
},
});
}
const screenSize = this.getTerminalSize();
component.widget.left = Math.floor(screenSize.width * 0.1);
component.widget.top = Math.floor(screenSize.height * 0.1);
component.widget.width = Math.floor(screenSize.width * 0.8);
component.widget.height = Math.floor(screenSize.height * 0.8);
component.widget.show();
const panelState = this.currentState.panelStates.get(panelId);
if (panelState) {
panelState.state = 'maximized';
}
this.screen.render();
this.eventBus.emit('panel:maximized', {
panelId,
timestamp: new Date(),
});
}
restorePanel(panelId) {
const component = this.panels.get(panelId);
if (!component || !component.widget)
return;
const originalPos = this.originalPositions.get(panelId);
if (originalPos) {
component.widget.left = originalPos.position.left;
component.widget.top = originalPos.position.top;
component.widget.width = originalPos.position.width;
component.widget.height = originalPos.position.height;
component.widget.show();
this.originalPositions.delete(panelId);
}
const panelState = this.currentState.panelStates.get(panelId);
if (panelState) {
panelState.state = 'normal';
}
this.screen.render();
this.eventBus.emit('panel:restored', {
panelId,
timestamp: new Date(),
});
}
hidePanel(panelId) {
const component = this.panels.get(panelId);
if (component && component.widget && typeof component.widget.hide === 'function') {
component.widget.hide();
}
}
storeCurrentStates() {
this.panels.forEach((component, panelId) => {
if (component.widget) {
try {
const currentState = {
id: panelId,
title: '',
component,
position: {
left: component.widget.left || 0,
top: component.widget.top || 0,
width: component.widget.width || 0,
height: component.widget.height || 0,
},
visible: !component.widget.hidden,
state: 'normal',
};
this.currentState.panelStates.set(panelId, currentState);
}
catch (error) {
console.error(`Failed to store state for panel ${panelId}:`, error);
}
}
});
}
handleScreenResize() {
const currentLayout = this.layouts.get(this.currentState.currentLayout);
if (currentLayout) {
const terminalSize = this.getTerminalSize();
if (terminalSize.width < currentLayout.minSize.width ||
terminalSize.height < currentLayout.minSize.height) {
this.findAndSwitchToCompatibleLayout(terminalSize);
}
else {
this.applyLayout(currentLayout);
}
}
}
findAndSwitchToCompatibleLayout(terminalSize) {
const compatibleLayouts = Array.from(this.layouts.values())
.filter(layout => terminalSize.width >= layout.minSize.width &&
terminalSize.height >= layout.minSize.height)
.sort((a, b) => (b.minSize.width * b.minSize.height) - (a.minSize.width * a.minSize.height));
if (compatibleLayouts.length > 0 && compatibleLayouts[0]) {
this.switchLayout(compatibleLayouts[0].name);
}
}
getTerminalSize() {
return {
width: this.screen.width || 80,
height: this.screen.height || 24,
};
}
getAvailableLayouts() {
return Array.from(this.layouts.keys());
}
getCurrentLayout() {
return this.currentState.currentLayout;
}
getLayoutInfo(layoutName) {
const name = layoutName || this.currentState.currentLayout;
return this.layouts.get(name) || null;
}
getPanelState(panelId) {
return this.currentState.panelStates.get(panelId) || null;
}
isFullscreen() {
return this.currentState.fullscreenPanel !== undefined;
}
getFullscreenPanel() {
return this.currentState.fullscreenPanel;
}
addLayoutPreset(preset) {
this.layouts.set(preset.name, preset);
this.eventBus.emit('layout:preset:added', {
layoutName: preset.name,
timestamp: new Date(),
});
}
removeLayoutPreset(layoutName) {
if (layoutName === this.currentState.currentLayout) {
throw new Error('Cannot remove currently active layout');
}
this.layouts.delete(layoutName);
this.eventBus.emit('layout:preset:removed', {
layoutName,
timestamp: new Date(),
});
}
saveCurrentState() {
const panels = [];
this.currentState.panelStates.forEach((panelState, _panelId) => {
panels.push({ ...panelState });
});
return {
name: `custom-${Date.now()}`,
description: 'User saved layout',
minSize: this.getTerminalSize(),
panels,
};
}
destroy() {
if (this.currentState.fullscreenPanel) {
this.exitFullscreen();
}
this.currentState.panelStates.clear();
this.originalPositions.clear();
this.panels.clear();
this.layouts.clear();
}
}
exports.LayoutManager = LayoutManager;
//# sourceMappingURL=layout-manager.js.map