Files
meridian-os/js/apps/vscode.js

215 lines
8.0 KiB
JavaScript

// VS Code spoof — three-pane layout with file explorer and syntax-highlighted editor.
const FILES = [
{ path: 'src/index.ts', content: `import { Engine } from './engine';
const engine = new Engine({
title: 'Meridian OS',
version: '0.1.0',
antialias: true,
vsync: true,
});
// Entry point
engine.start();
console.log('Meridian OS initialized');
export default engine;` },
{ path: 'src/engine.ts', content: `export interface EngineConfig {
title: string;
version: string;
antialias: boolean;
vsync: boolean;
}
export class Engine {
private config: EngineConfig;
private running: boolean = false;
constructor(config: EngineConfig) {
this.config = config;
this.running = false;
}
start(): void {
if (this.running) return;
this.running = true;
console.log(\`Starting \${this.config.title} v\${this.config.version}\`);
this.renderLoop();
}
private renderLoop(): void {
while (this.running) {
this.update();
this.draw();
}
}
private update(): void {
// Game loop update
}
private draw(): void {
// Game loop render
}
stop(): void {
this.running = false;
}
}` },
{ path: 'README.md', content: `# Meridian OS
## Overview
A browser-based desktop environment for a video game.
Elegant, restrained, gamer-literate without being loud.
## Features
- Window manager with drag, focus, minimize, close
- Multiple apps: Notepad, Music, Terminal
- Spoof apps: Steam, VS Code, Postman, Chrome
- Achievement system
- localStorage persistence
## Tech Stack
- Vanilla JS
- CSS custom properties
- No frameworks. No excuses.` },
];
function highlightTS(code) {
// Very basic "syntax highlighting" — just colour types, strings, keywords
return code
.replace(/(import|export|from|const|let|var|function|class|private|public|return|if|while|interface|type|new|void|false|true|this|typeof|instanceof|extends)/g, '<span style="color:#c678dd">$1</span>')
.replace(/(\/\/.*)/g, '<span style="color:#6a9955">$1</span>')
.replace(/('.*?'|".*?"|`.*?`)/g, '<span style="color:#ce9178">$1</span>')
.replace(/\b(\d+)\b/g, '<span style="color:#b5cea8">$1</span>')
.replace(/(\w+)(?=\s*\()/g, '<span style="color:#61afef">$1</span>')
.replace(/(:\s*)(\w+)/g, '$1<span style="color:#e5c07b">$2</span>');
}
export default {
id: 'vscode',
title: 'VS Code',
render(container, api) {
let activeFile = 0;
const root = document.createElement('div');
root.style.cssText = 'height:100%;display:flex;flex-direction:column;background:#1e1e1e;font-family:var(--font-mono,monospace);font-size:13px;color:#d4d4d4;';
// Activity bar (leftmost, 40px wide)
const actBar = document.createElement('div');
actBar.style.cssText = 'width:40px;background:#333333;display:flex;flex-direction:column;align-items:center;padding-top:8px;border-right:1px solid #444444;flex-shrink:0;';
// 5 geometric icons as divs
const icons = [
{ shape: 'square', id: 'explorer' },
{ shape: 'hline', id: 'search' },
{ shape: 'dots', id: 'git' },
{ shape: 'circle', id: 'debug' },
{ shape: 'grid', id: 'extensions' },
];
const iconStyle = 'width:24px;height:24px;margin-bottom:16px;cursor:pointer;opacity:0.5;transition:opacity 150ms;';
icons.forEach((icon) => {
const el = document.createElement('div');
el.style.cssText = iconStyle;
el.id = `vscode-icon-${icon.id}`;
if (icon.shape === 'square') {
el.innerHTML = '<div style="width:18px;height:18px;border:2px solid #d4d4d4;"></div>';
} else if (icon.shape === 'hline') {
el.innerHTML = '<div style="width:18px;height:2px;background:#d4d4d4;margin-top:8px;"></div><div style="width:12px;height:2px;background:#d4d4d4;margin-top:4px;"></div><div style="width:16px;height:2px;background:#d4d4d4;margin-top:4px;"></div>';
} else if (icon.shape === 'dots') {
el.innerHTML = '<div style="display:flex;justify-content:space-around;align-items:center;height:100%;">' +
'<div style="width:5px;height:5px;border-radius:50%;background:#d4d4d4;"></div>' +
'<div style="width:5px;height:5px;border-radius:50%;background:#d4d4d4;"></div>' +
'<div style="width:5px;height:5px;border-radius:50%;background:#d4d4d4;"></div></div>';
} else if (icon.shape === 'circle') {
el.innerHTML = '<div style="width:18px;height:18px;border-radius:50%;border:2px solid #d4d4d4;"></div>';
} else if (icon.shape === 'grid') {
el.innerHTML = '<div style="display:grid;grid-template-columns:1fr 1fr;gap:2px;width:18px;height:18px;">' +
'<div style="background:#d4d4d4;"></div><div style="background:#d4d4d4;"></div>' +
'<div style="background:#d4d4d4;"></div><div style="background:#d4d4d4;"></div></div>';
}
if (icon.id === 'explorer') {
el.style.opacity = '1';
el.style.borderLeft = '2px solid var(--accent)';
el.style.marginLeft = '-2px';
}
el.addEventListener('click', () => {
icons.forEach((ic) => {
const e = document.getElementById(`vscode-icon-${ic.id}`);
if (e) { e.style.opacity = '0.5'; e.style.borderLeft = 'none'; }
});
el.style.opacity = '1';
el.style.borderLeft = '2px solid var(--accent)';
el.style.marginLeft = '-2px';
});
actBar.appendChild(el);
});
// File explorer (200px wide)
const explorer = document.createElement('div');
explorer.style.cssText = 'width:200px;background:#252526;border-right:1px solid #444444;display:flex;flex-direction:column;flex-shrink:0;';
const explLabel = document.createElement('div');
explLabel.style.cssText = 'padding:8px 12px;font-size:11px;font-weight:600;color:#bbbbbb;text-transform:uppercase;letter-spacing:0.05em;';
explLabel.textContent = 'Explorer';
explorer.appendChild(explLabel);
const folderLabel = document.createElement('div');
folderLabel.style.cssText = 'padding:4px 12px;font-size:12px;color:#9cdcfe;font-weight:600;cursor:pointer;';
folderLabel.textContent = 'MERIDIAN-OS';
explorer.appendChild(folderLabel);
FILES.forEach((file, i) => {
const row = document.createElement('div');
row.style.cssText = 'padding:4px 12px 4px 24px;font-size:13px;color:#cccccc;cursor:pointer;display:flex;align-items:center;transition:background 100ms;';
const ext = file.path.split('.').pop();
const tsColor = ext === 'ts' ? '#519aba' : (ext === 'md' ? '#82aaff' : '#cccccc');
row.innerHTML = `<span style="color:${tsColor};margin-right:6px;font-weight:600;">${ext === 'md' ? 'M' : '{ }'}</span>${file.path}`;
row.addEventListener('click', () => {
activeFile = i;
updateEditor();
});
explorer.appendChild(row);
});
// Editor area
const editor = document.createElement('div');
editor.style.cssText = 'flex:1;overflow:auto;padding:16px 20px;background:#1e1e1e;';
const editorContent = document.createElement('pre');
editorContent.style.cssText = 'margin:0;font-family:Consolas,Menlo,Monaco,monospace;font-size:13px;line-height:1.6;white-space:pre;tab-size:2;';
function updateEditor() {
editorContent.innerHTML = '<span style="color:#858585;">' +
Array.from({ length: FILES[activeFile].content.split('\n').length }, (_, i) => i + 1).join('\n') +
'</span> ' + highlightTS(FILES[activeFile].content);
}
updateEditor();
editor.appendChild(editorContent);
// Body container
const body = document.createElement('div');
body.style.cssText = 'flex:1;display:flex;overflow:hidden;';
body.appendChild(explorer);
body.appendChild(editor);
// Status bar
const statusBar = document.createElement('div');
statusBar.style.cssText = 'height:22px;background:#007acc;display:flex;align-items:center;padding:0 12px;font-size:12px;color:#ffffff;flex-shrink:0;gap:16px;';
statusBar.innerHTML = '<span>main</span><span>\u{2713} 0 problems</span><span>UTF-8</span><span>TypeScript</span>';
root.appendChild(actBar);
root.appendChild(body);
root.appendChild(statusBar);
container.appendChild(root);
},
};