Initial commit — Meridian OS browser desktop demo
This commit is contained in:
316
js/apps/terminal.js
Normal file
316
js/apps/terminal.js
Normal file
@@ -0,0 +1,316 @@
|
||||
export default {
|
||||
id: 'terminal',
|
||||
title: 'Terminal',
|
||||
render: function (container, api) {
|
||||
var commandHistory = [];
|
||||
var historyIndex = -1;
|
||||
var outputLines = [];
|
||||
var bus = api.bus;
|
||||
|
||||
// ── DOM Structure ──────────────────────────────────────
|
||||
container.style.cssText = 'height:100%;width:100%;background:#0a0b0d;color:#cfd3d8;display:flex;flex-direction:column;font-family:ui-monospace,"JetBrains Mono",Menlo,monospace;overflow:hidden;cursor:text;';
|
||||
|
||||
var outputEl = document.createElement('div');
|
||||
outputEl.style.cssText = 'flex:1;overflow-y:auto;padding:12px;font-size:14px;line-height:1.5;white-space:pre-wrap;word-break:break-word;';
|
||||
|
||||
var inputRow = document.createElement('div');
|
||||
inputRow.style.cssText = 'display:flex;align-items:center;padding:4px 12px 12px;';
|
||||
|
||||
var promptEl = document.createElement('span');
|
||||
promptEl.style.cssText = 'color:#d4a24c;font-size:14px;line-height:1.5;white-space:pre;user-select:none;';
|
||||
promptEl.textContent = 'tarnished@meridian:~$ ';
|
||||
|
||||
var inputEl = document.createElement('input');
|
||||
inputEl.style.cssText = 'flex:1;background:transparent;border:none;color:inherit;outline:none;font-family:inherit;font-size:14px;line-height:1.5;cursor:text;';
|
||||
inputEl.setAttribute('type', 'text');
|
||||
inputEl.setAttribute('autocomplete', 'off');
|
||||
inputEl.setAttribute('autocorrect', 'off');
|
||||
inputEl.setAttribute('autocapitalize', 'off');
|
||||
inputEl.setAttribute('spellcheck', 'false');
|
||||
|
||||
inputRow.appendChild(promptEl);
|
||||
inputRow.appendChild(inputEl);
|
||||
container.appendChild(outputEl);
|
||||
container.appendChild(inputRow);
|
||||
|
||||
// ── Helpers ────────────────────────────────────────────
|
||||
function appendLine(text, color) {
|
||||
var line = document.createElement('div');
|
||||
line.style.cssText = 'font-size:14px;line-height:1.5;';
|
||||
line.style.color = color || '#cfd3d8';
|
||||
line.textContent = text;
|
||||
outputEl.appendChild(line);
|
||||
outputLines.push(line);
|
||||
outputEl.scrollTop = outputEl.scrollHeight;
|
||||
}
|
||||
|
||||
function printBanner() {
|
||||
appendLine('Meridian Shell 1.0 (kernel 4.19-browser)');
|
||||
appendLine("Type 'help' to begin. Try 'praise_the_sun' if you know what you're doing.");
|
||||
appendLine('');
|
||||
}
|
||||
|
||||
// ── Real Commands ──────────────────────────────────────
|
||||
function cmdHelp() {
|
||||
appendLine('Available commands:');
|
||||
appendLine(' help Show this help message');
|
||||
appendLine(' ls List directory contents');
|
||||
appendLine(' whoami Show current user');
|
||||
appendLine(' date Show current date');
|
||||
appendLine(' uname Show system info');
|
||||
appendLine(' echo <text> Print text');
|
||||
appendLine(' clear Clear terminal output');
|
||||
appendLine(' cat <file> Read a file');
|
||||
appendLine(' history Show command history');
|
||||
appendLine(' achievements Show unlocked achievements');
|
||||
appendLine(' exit Close terminal');
|
||||
appendLine('');
|
||||
appendLine("Try 'praise_the_sun' if you know what you're doing.");
|
||||
}
|
||||
|
||||
function cmdLs() {
|
||||
appendLine('Documents Downloads Music Pictures save_files.dat');
|
||||
}
|
||||
|
||||
function cmdWhoami() {
|
||||
var user = api.state.get('user');
|
||||
appendLine(user ? user.name : 'tarnished');
|
||||
}
|
||||
|
||||
function cmdDate() {
|
||||
appendLine(new Date().toString());
|
||||
}
|
||||
|
||||
function cmdUname() {
|
||||
appendLine('Meridian 1.0 (browser-kernel)');
|
||||
}
|
||||
|
||||
function cmdEcho(args) {
|
||||
if (args.length === 0) return;
|
||||
appendLine(args.join(' '));
|
||||
}
|
||||
|
||||
function cmdClear() {
|
||||
outputEl.innerHTML = '';
|
||||
outputLines = [];
|
||||
}
|
||||
|
||||
function cmdCat(file) {
|
||||
if (!file) {
|
||||
appendLine('cat: missing operand', '#c85d5d');
|
||||
return;
|
||||
}
|
||||
if (file === 'save_files.dat') {
|
||||
appendLine("Ah, you want to read your save file? Not today. Some mysteries are better left unsolved.", '#d4a24c');
|
||||
return;
|
||||
}
|
||||
appendLine('cat: ' + file + ': No such file or directory', '#c85d5d');
|
||||
}
|
||||
|
||||
function cmdHistory() {
|
||||
if (commandHistory.length === 0) {
|
||||
appendLine('No commands in history.');
|
||||
return;
|
||||
}
|
||||
commandHistory.forEach(function (cmd, i) {
|
||||
appendLine(' ' + String(i + 1).padStart(4) + ' ' + cmd);
|
||||
});
|
||||
}
|
||||
|
||||
function cmdAchievements() {
|
||||
var achievements = api.state.get('achievements');
|
||||
if (!achievements || achievements.length === 0) {
|
||||
appendLine('No achievements unlocked yet.');
|
||||
return;
|
||||
}
|
||||
appendLine('Unlocked achievements (' + achievements.length + '):');
|
||||
achievements.forEach(function (a) {
|
||||
appendLine(' [x] ' + a);
|
||||
});
|
||||
}
|
||||
|
||||
function cmdExit() {
|
||||
api.close();
|
||||
}
|
||||
|
||||
// ── Easter Eggs ────────────────────────────────────────
|
||||
var easterEggs = {
|
||||
'praise_the_sun': {
|
||||
msg: 'A grace of Gold is bestowed upon you.\nMay it guide you in the dark times ahead.',
|
||||
slug: 'er_grace'
|
||||
},
|
||||
'tarnished': {
|
||||
msg: 'Well then, tarnished. Seek strength. For that is your duty.',
|
||||
slug: 'er_tarnished'
|
||||
},
|
||||
'try finger, but hole': {
|
||||
msg: 'Message left by a fellow traveler. Rating: brilliant. Rating: inappropriate.',
|
||||
slug: 'er_bong_hits'
|
||||
},
|
||||
'dropshot': {
|
||||
msg: 'Hitmarker. Clean drop.',
|
||||
slug: 'er_dropshot'
|
||||
},
|
||||
'360_noscope': {
|
||||
msg: ' |\n /|\\\n-------\n |/\nmontage material acquired.',
|
||||
slug: 'er_360noscope'
|
||||
},
|
||||
'mw2': {
|
||||
msg: 'A true classic. Soap would be proud.',
|
||||
slug: 'er_mw2'
|
||||
},
|
||||
'siuuu': {
|
||||
msg: 'SIUUUUU! The keeper had no chance.',
|
||||
slug: 'er_siuuu'
|
||||
},
|
||||
'golazo': {
|
||||
msg: 'Top bins. What a strike. What a moment.',
|
||||
slug: 'er_golazo'
|
||||
},
|
||||
'wonderkid': {
|
||||
msg: 'Regen detected. Potential: 5 stars.',
|
||||
slug: 'er_wonderkid'
|
||||
},
|
||||
'konami': {
|
||||
msg: 'Cheat mode enabled. (it does nothing. you are already a legend.)',
|
||||
slug: 'er_konami'
|
||||
},
|
||||
'matrix': {
|
||||
msg: null,
|
||||
slug: 'er_matrix'
|
||||
},
|
||||
'sudo rm -rf /': {
|
||||
msg: 'nice try, tarnished. root is a state of mind.',
|
||||
slug: 'er_sudo_rm_rf'
|
||||
}
|
||||
};
|
||||
|
||||
function triggerEasterEgg(cmdName, egg) {
|
||||
bus.emit('term:cmd', { cmd: cmdName });
|
||||
bus.emit('easter:found', { id: egg.slug });
|
||||
|
||||
if (cmdName === 'matrix') {
|
||||
appendLine('Entering the matrix...', '#d4a24c');
|
||||
var mlines = [];
|
||||
for (var mi = 0; mi < 8; mi++) {
|
||||
var row = '';
|
||||
for (var mj = 0; mj < 40; mj++) {
|
||||
row += Math.random() < 0.5 ? '0' : '1';
|
||||
}
|
||||
mlines.push(row);
|
||||
}
|
||||
var mlIdx = 0;
|
||||
function typeNext() {
|
||||
if (mlIdx < mlines.length) {
|
||||
appendLine(mlines[mlIdx], '#41c35a');
|
||||
mlIdx++;
|
||||
setTimeout(typeNext, 30);
|
||||
}
|
||||
}
|
||||
setTimeout(typeNext, 30);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmdName === 'sudo rm -rf /') {
|
||||
appendLine('...', '#d4a24c');
|
||||
setTimeout(function () {
|
||||
outputEl.removeChild(outputEl.lastChild);
|
||||
appendLine('nice try, tarnished. root is a state of mind.', '#d4a24c');
|
||||
}, 900);
|
||||
return;
|
||||
}
|
||||
|
||||
if (egg.msg && egg.msg.indexOf('\n') !== -1) {
|
||||
egg.msg.split('\n').forEach(function (ln) {
|
||||
appendLine(ln, '#d4a24c');
|
||||
});
|
||||
} else if (egg.msg) {
|
||||
appendLine(egg.msg, '#d4a24c');
|
||||
}
|
||||
}
|
||||
|
||||
// ── Command Processing ─────────────────────────────────
|
||||
function emitRealCmd(cmd) {
|
||||
bus.emit('term:cmd', { cmd: cmd });
|
||||
}
|
||||
|
||||
function processInput(raw) {
|
||||
var trimmed = raw.trim();
|
||||
if (!trimmed) return;
|
||||
|
||||
commandHistory.push(trimmed);
|
||||
if (commandHistory.length > 50) {
|
||||
commandHistory.shift();
|
||||
}
|
||||
historyIndex = commandHistory.length;
|
||||
|
||||
appendLine(promptEl.textContent + trimmed, '#d4a24c');
|
||||
|
||||
if (easterEggs[trimmed]) {
|
||||
triggerEasterEgg(trimmed, easterEggs[trimmed]);
|
||||
return;
|
||||
}
|
||||
|
||||
var parts = trimmed.split(/\s+/);
|
||||
var cmd = parts[0].toLowerCase();
|
||||
|
||||
if (easterEggs[cmd]) {
|
||||
triggerEasterEgg(cmd, easterEggs[cmd]);
|
||||
return;
|
||||
}
|
||||
|
||||
emitRealCmd(cmd);
|
||||
switch (cmd) {
|
||||
case 'help': cmdHelp(); break;
|
||||
case 'ls': cmdLs(); break;
|
||||
case 'whoami': cmdWhoami(); break;
|
||||
case 'date': cmdDate(); break;
|
||||
case 'uname': cmdUname(); break;
|
||||
case 'echo': cmdEcho(parts.slice(1)); break;
|
||||
case 'clear': cmdClear(); break;
|
||||
case 'cat': cmdCat(parts[1]); break;
|
||||
case 'history': cmdHistory(); break;
|
||||
case 'achievements': cmdAchievements(); break;
|
||||
case 'exit': cmdExit(); break;
|
||||
default:
|
||||
appendLine('Command not found: ' + cmd, '#c85d5d');
|
||||
}
|
||||
}
|
||||
|
||||
// ── Keyboard Handling ──────────────────────────────────
|
||||
inputEl.addEventListener('keydown', function (e) {
|
||||
if (e.key === 'Enter') {
|
||||
processInput(inputEl.value);
|
||||
inputEl.value = '';
|
||||
historyIndex = commandHistory.length;
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
e.preventDefault();
|
||||
if (historyIndex > 0) {
|
||||
historyIndex--;
|
||||
inputEl.value = commandHistory[historyIndex];
|
||||
}
|
||||
} else if (e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
if (historyIndex < commandHistory.length - 1) {
|
||||
historyIndex++;
|
||||
inputEl.value = commandHistory[historyIndex];
|
||||
} else {
|
||||
historyIndex = commandHistory.length;
|
||||
inputEl.value = '';
|
||||
}
|
||||
} else if (e.ctrlKey && e.key.toLowerCase() === 'l') {
|
||||
e.preventDefault();
|
||||
cmdClear();
|
||||
}
|
||||
});
|
||||
|
||||
// ── Click to Focus ─────────────────────────────────────
|
||||
container.addEventListener('click', function () {
|
||||
inputEl.focus();
|
||||
});
|
||||
|
||||
// ── Initial Banner ─────────────────────────────────────
|
||||
printBanner();
|
||||
inputEl.focus();
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user