asciitorium
Version:
an ASCII ui framework for web + cli
163 lines (162 loc) • 6.04 kB
JavaScript
import { Component } from './Component';
import { FocusManager } from './FocusManager';
import { DomRenderer } from './renderers/DomRenderer';
import { TerminalRenderer } from './renderers/TerminalRenderer';
import { setRenderCallback } from './RenderScheduler';
import { setupKeyboardHandling, validateWebEnvironment } from './utils';
export class App extends Component {
constructor(props) {
// Set vertical layout as default for Asciitorium, pass through fit option
const asciitoriumProps = {
...props,
layout: props.layout ?? 'vertical',
layoutOptions: { fit: props.fit, ...props.layoutOptions }
};
super(asciitoriumProps);
this.fpsCounter = 0;
this.totalRenderTime = 0;
this.currentFPS = 0;
this.currentCPU = 0;
this.currentMemory = 0;
this.renderer = getDefaultRenderer();
this.focus = new FocusManager();
this.focus.reset(this);
this.render();
// Initialize performance monitoring
this.updatePerformanceMetrics();
// Start FPS and render time reporting
setInterval(() => {
this.currentFPS = this.fpsCounter;
this.fpsCounter = 0;
this.totalRenderTime = 0;
this.updatePerformanceMetrics();
}, 1000);
}
render() {
const start = typeof performance !== 'undefined' && performance.now
? performance.now()
: Date.now();
this.fpsCounter++;
const screenBuffer = Array.from({ length: this.height }, () => Array.from({ length: this.width }, () => ' '));
// Flatten and sort components by z-index
const allComponents = this.getAllDescendants().concat([this]);
allComponents.sort((a, b) => (a.z ?? 0) - (b.z ?? 0));
for (const component of allComponents) {
const buffer = component.draw();
const x = component.x;
const y = component.y;
const transparentChar = component.transparentChar;
for (let row = 0; row < buffer.length; row++) {
const globalY = y + row;
if (globalY < 0 || globalY >= this.height)
continue;
for (let col = 0; col < buffer[row].length; col++) {
const globalX = x + col;
if (globalX < 0 || globalX >= this.width)
continue;
const char = buffer[row][col];
if (char !== transparentChar) {
screenBuffer[globalY][globalX] = char;
}
}
}
}
this.renderer.render(screenBuffer);
const end = typeof performance !== 'undefined' && performance.now
? performance.now()
: Date.now();
this.totalRenderTime += end - start;
}
addChild(component) {
super.addChild(component);
this.focus?.reset(this); // avoid crashing
this.render();
}
removeChild(component) {
super.removeChild(component);
this.focus.reset(this);
this.render();
}
getFPS() {
return this.currentFPS;
}
getRenderTime() {
return this.totalRenderTime;
}
getCPUUsage() {
return this.currentCPU;
}
getMemoryUsage() {
return this.currentMemory;
}
updatePerformanceMetrics() {
// CPU and Memory monitoring - cross-platform
if (typeof process !== 'undefined' && process.cpuUsage && process.memoryUsage) {
// Node.js environment (CLI)
try {
const currentUsage = process.cpuUsage(this.lastCPUUsage);
const totalUsage = currentUsage.user + currentUsage.system;
// Convert microseconds to percentage (approximate)
this.currentCPU = Math.min(100, (totalUsage / 10000)); // Rough estimation
this.lastCPUUsage = process.cpuUsage();
const memUsage = process.memoryUsage();
this.currentMemory = memUsage.heapUsed / (1024 * 1024); // Convert to MB
}
catch (e) {
this.currentCPU = 0;
this.currentMemory = 0;
}
}
else if (typeof performance !== 'undefined' && performance.memory) {
// Browser environment with memory API
try {
const memInfo = performance.memory;
this.currentMemory = memInfo.usedJSHeapSize / (1024 * 1024); // Convert to MB
// No direct CPU access in browser, estimate from render performance
this.currentCPU = Math.min(100, Math.max(0, this.totalRenderTime * 6)); // Rough estimation
}
catch (e) {
this.currentCPU = 0;
this.currentMemory = 0;
}
}
else {
// Fallback - no metrics available
this.currentCPU = 0;
this.currentMemory = 0;
}
}
handleKey(key) {
if (key === 'Tab') {
this.focus.focusNext();
this.render();
event?.preventDefault();
return;
}
if (key === 'Shift') {
this.focus.focusPrevious();
this.render();
event?.preventDefault();
return;
}
if (this.focus.handleKey(key)) {
this.render();
}
}
async start() {
validateWebEnvironment();
await setupKeyboardHandling((key) => this.handleKey(key));
setRenderCallback(() => this.render());
}
}
function getDefaultRenderer() {
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
const screen = document.getElementById('screen');
if (!screen)
throw new Error('No #screen element found for DOM rendering');
return new DomRenderer(screen);
}
else {
return new TerminalRenderer();
}
}