Pulled konsole out into it's own module
This commit is contained in:
parent
42d26503bc
commit
0046dc1cb4
@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import '../misc/konsole';
|
||||
import '../modules/konsole';
|
||||
import {onMounted, ref} from 'vue';
|
||||
|
||||
const animate = ref(true);
|
||||
@ -28,48 +28,6 @@ onMounted(async () => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.cli-container {
|
||||
padding: 1rem;
|
||||
background: #333;
|
||||
font-family: monospace !important;
|
||||
overflow-y: auto;
|
||||
min-height: 150px;
|
||||
max-height: 300px;
|
||||
|
||||
.cli-stdout {
|
||||
flex-grow: 1;
|
||||
color: #0f0;
|
||||
|
||||
.cli-stdout-line {
|
||||
padding: 0;
|
||||
min-height: 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.cli-stdin {
|
||||
display: flex;
|
||||
margin: 0;
|
||||
|
||||
.cli-stdin-prompt {
|
||||
padding-right: 0.55em;
|
||||
text-wrap: nowrap;
|
||||
color: #0f0;
|
||||
}
|
||||
|
||||
.cli-stdin-input {
|
||||
border: none;
|
||||
outline: none;
|
||||
font-size: 1rem;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
color: #0f0;
|
||||
flex-grow: 1;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div id="konsole" @click="animate = false"></div>
|
||||
</template>
|
||||
|
@ -1,344 +0,0 @@
|
||||
window.cli = {
|
||||
// Element references
|
||||
_input: null,
|
||||
_parent: null,
|
||||
_prompt: null,
|
||||
_output: null,
|
||||
|
||||
// CLI State
|
||||
_history: [],
|
||||
_index: 0,
|
||||
pwd: '/',
|
||||
hostname: 'virtual',
|
||||
env: {},
|
||||
exec: {},
|
||||
filesystem: {},
|
||||
user: 'root',
|
||||
version: '0.3.0',
|
||||
|
||||
build: (elementId) => {
|
||||
window.cli._parent = document.querySelector(elementId);
|
||||
if(!window.cli._parent)
|
||||
throw new Error(`Could not create konsole, element "${elementId}" does not exist`);
|
||||
|
||||
window.cli._parent.innerHTML = `
|
||||
<div class="cli-container">
|
||||
<div class="cli-stdout"></div>
|
||||
<div class="cli-stdin" onclick="window.cli._input.focus()">
|
||||
<label for="${elementId}-cli-stdin-input" class="cli-stdin-prompt">${window.cli.prompt()}</label>
|
||||
<input id="${elementId}-cli-stdin-input" class="cli-stdin-input" type="text" />
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
window.cli._input = document.querySelector(elementId + ' .cli-stdin-input');
|
||||
window.cli._prompt = document.querySelector(elementId + ' .cli-stdin-prompt');
|
||||
window.cli._output = document.querySelector(elementId + ' .cli-stdout');
|
||||
window.cli._input.addEventListener('keyup', (e) => {
|
||||
if(e.key == "Enter") {
|
||||
window.cli.disable();
|
||||
if(!!window.cli._input.value) {
|
||||
window.cli._history.push(window.cli._input.value);
|
||||
window.cli._index = window.cli._history.length;
|
||||
window.cli.stdIn(window.cli._input.value)
|
||||
}
|
||||
window.cli._input.value = '';
|
||||
window.cli.enable();
|
||||
} else if(e.key == 'Up' || e.key == 'ArrowUp') {
|
||||
if(window.cli._index > 0) window.cli._index--;
|
||||
window.cli._input.value = window.cli._index == window.cli._history.length ? '' : window.cli._history[window.cli._index];
|
||||
setTimeout(() => {
|
||||
const end = window.cli._input.value.length;
|
||||
window.cli._input.setSelectionRange(end, end);
|
||||
window.cli._input.focus();
|
||||
}, 1)
|
||||
} else if(e.key == 'Down' || e.key == 'ArrowDown') {
|
||||
if(window.cli._index < window.cli._history.length) window.cli._index++;
|
||||
window.cli._input.value = window.cli._index == window.cli._history.length ? '' : window.cli._history[window.cli._index];
|
||||
setTimeout(() => {
|
||||
const end = window.cli._input.value.length;
|
||||
window.cli._input.setSelectionRange(end, end);
|
||||
window.cli._input.focus();
|
||||
}, 1)
|
||||
}
|
||||
});
|
||||
setTimeout(() => window.cli.exec['banner'].run(), 1);
|
||||
},
|
||||
disable: () => {
|
||||
window.cli._input.disabled = true;
|
||||
window.cli._prompt.style.visibility = 'hidden';
|
||||
},
|
||||
enable: () => {
|
||||
window.cli._input.disabled = false;
|
||||
window.cli._input.focus();
|
||||
window.cli._prompt.style.visibility = 'visible';
|
||||
},
|
||||
|
||||
path: (path=window.cli.pwd) => {
|
||||
let p = path[0] == '/'? path : (window.cli.pwd + (window.cli.pwd.endsWith('/') ? '' : '/') + path.replace('./', ''))
|
||||
.replaceAll('//', '/');
|
||||
const parts = p.split('/').filter(p => !!p);
|
||||
for(let i = 0; i < parts.length; i++) {
|
||||
if(parts[i] == '..') {
|
||||
i--;
|
||||
parts.splice(i, 2);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
return '/' + (parts.length ? parts.join('/') : '');
|
||||
},
|
||||
prompt: () => `${window.cli.user}@${window.cli.hostname}:${window.cli.pwd}${window.cli.user == 'root' ? '#' : '$'}`,
|
||||
fs: (path, set) => {
|
||||
return window.cli.path(path).split('/').filter(p => !!p).reduce((t, p, i, arr) => {
|
||||
if(!t?.hasOwnProperty(p)) {
|
||||
if(set == undefined) return undefined;
|
||||
t[p] = {};
|
||||
}
|
||||
if(set !== undefined && i == arr.length - 1) {
|
||||
if(set == null) delete t[p];
|
||||
else t[p] = set;
|
||||
}
|
||||
return t[p];
|
||||
}, window.cli.filesystem);
|
||||
},
|
||||
stdErr: (text) => {
|
||||
const p = document.createElement('p');
|
||||
p.classList.add('cli-stdout-line');
|
||||
p.classList.add('cli-stdout-error');
|
||||
p.innerText = text;
|
||||
window.cli._output.appendChild(p);
|
||||
},
|
||||
stdIn:(command, silent=false) => {
|
||||
(Array.isArray(command) ? command.join(' ') : command).split(';').filter(c => !!c).forEach(c => {
|
||||
const parts = c.match(/(?:[^\s"]+|"[^"]*")+/g);
|
||||
if(!parts) return;
|
||||
|
||||
const exec = window.cli.exec[parts[0]];
|
||||
if(!exec?.run) {
|
||||
window.cli.stdErr(`${window.cli.prompt()} ${command}\n${parts[0]}: command not found`);
|
||||
} else {
|
||||
try {
|
||||
const args = parts.slice(1).map(a => (a[0] == '"' || a[0] == "'") ? a.slice(1, -1) : a);
|
||||
const out = exec.run(args);
|
||||
if(!silent) window.cli.stdOut(`${window.cli.prompt()} ${command}${out ? '\n' + out : ''}`);
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
window.cli.stdErr(`${window.cli.prompt()} ${command}\n${err.message || `${parts[0]}: exited with a non-zero status`}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
stdOut: (text, html=false) => {
|
||||
const p = document.createElement('p');
|
||||
p.classList.add('cli-stdout-line');
|
||||
p[html ? 'innerHTML' : 'innerText'] = text;
|
||||
window.cli._output.appendChild(p);
|
||||
},
|
||||
type: (text, speed=150) => {
|
||||
let counter = 0;
|
||||
return new Promise(res => {
|
||||
let typing = setInterval(() => {
|
||||
if(counter < text.length) {
|
||||
window.cli._input.value += text[counter];
|
||||
} else {
|
||||
clearInterval(typing);
|
||||
setTimeout(() => {
|
||||
window.cli.stdIn(text);
|
||||
window.cli._input.value = '';
|
||||
res();
|
||||
}, 750);
|
||||
}
|
||||
counter++;
|
||||
}, speed);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
window.cli.exec['banner'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Display login banner';
|
||||
},
|
||||
run: args => {
|
||||
window.cli.stdOut(`Konsole ${window.cli.version} LTS ${window.cli.hostname} tty1\n\n${window.cli.hostname} login: ${window.cli.user}\npassword:\n\n`);
|
||||
}
|
||||
}
|
||||
window.cli.exec['cd'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Clear console output';
|
||||
},
|
||||
run: args => {
|
||||
const path = window.cli.fs(args[0]);
|
||||
if(!path) throw new Error(`cd: \'${args[0]}\': No such file or directory`)
|
||||
|
||||
window.cli.pwd = window.cli.path(args[0]);
|
||||
window.cli._prompt.innerText = window.cli.prompt();
|
||||
}
|
||||
}
|
||||
window.cli.exec['clear'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Clear console output';
|
||||
},
|
||||
run: args => {
|
||||
setTimeout(() => window.cli._output.innerHTML = '', 1);
|
||||
}
|
||||
}
|
||||
window.cli.exec['date'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Get current date & time';
|
||||
},
|
||||
run: args => {
|
||||
return (new Date()).toLocaleString();
|
||||
}
|
||||
}
|
||||
window.cli.exec['echo'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Output text to console';
|
||||
},
|
||||
run: args => {
|
||||
return args.join(' ');
|
||||
}
|
||||
}
|
||||
window.cli.exec['exit'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'End session';
|
||||
},
|
||||
run: args => {
|
||||
window.cli.stdIn('clear');
|
||||
window.cli._history = [];
|
||||
window.cli._index = 0;
|
||||
window.cli.stdIn('banner');
|
||||
}
|
||||
}
|
||||
window.cli.exec['help'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Display all commands';
|
||||
},
|
||||
run: args => {
|
||||
return `Konsole v${window.cli.version} - A prototype bash emulator written by Zakary Timson\n\n` +
|
||||
Object.keys(window.cli.exec).map(command => `${command} - ${window.cli.exec[command].help()}`).join('\n');
|
||||
}
|
||||
}
|
||||
window.cli.exec['hostname'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Get computer hostname';
|
||||
},
|
||||
run: args => {
|
||||
return window.cli.hostname;
|
||||
}
|
||||
}
|
||||
window.cli.exec['ls'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Display directory contents';
|
||||
},
|
||||
run: args => {
|
||||
const target = window.cli.fs(args[0]);
|
||||
if(!target) throw new Error(`ls: cannot access \'${args[0]}\': No such file or directory`)
|
||||
return Object.keys(target).reduce((acc, p) => acc + `${p}\n`, '');
|
||||
}
|
||||
}
|
||||
window.cli.exec['man'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'View command\'s manual';
|
||||
},
|
||||
run: args => {
|
||||
return window.cli.exec[args[0]].help();
|
||||
}
|
||||
}
|
||||
window.cli.exec['mkdir'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Create new directory';
|
||||
},
|
||||
run: args => {
|
||||
if(!args[0]) throw new Error('mkdir: missing operand');
|
||||
window.cli.fs(args[0], {});
|
||||
}
|
||||
}
|
||||
window.cli.exec['pwd'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Get present working directory';
|
||||
},
|
||||
run: args => {
|
||||
return window.cli.pwd;
|
||||
}
|
||||
}
|
||||
window.cli.exec['rm'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Delete file or directory';
|
||||
},
|
||||
run: args => {
|
||||
if(!args[0]) throw new Error('rm: missing operand');
|
||||
window.cli.fs(args[0], null);
|
||||
}
|
||||
}
|
||||
window.cli.exec['shower-thought'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Random shower thought';
|
||||
},
|
||||
run: args => {
|
||||
const motd = [
|
||||
'Why do kamikaze pilots wear helmets?',
|
||||
'How are giraffes real, but unicorns made up',
|
||||
'When you are a kid you don\'t realize you are also watching your parents grow up',
|
||||
'Some one at Google was like "Yea, just have someone drive down every road on earth!"',
|
||||
'The number of people older than you never goes up',
|
||||
'When you brush your teeth you are cleaning your skeleton',
|
||||
'Pregnancy is like a group project where one person get\'s stuck with all the work',
|
||||
'If the universe wasn\'t infinite it would be even scarier',
|
||||
'Either we are alone in the universe or we are not. both are terrifying',
|
||||
'The object of golf is to play the least amount of golf.'
|
||||
];
|
||||
return motd[~~(Math.random() * motd.length)];
|
||||
}
|
||||
}
|
||||
window.cli.exec['whoami'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Get current user account';
|
||||
},
|
||||
run: args => {
|
||||
return window.cli.user;
|
||||
}
|
||||
}
|
11
src/modules/konsole/commands/banner.js
Normal file
11
src/modules/konsole/commands/banner.js
Normal file
@ -0,0 +1,11 @@
|
||||
window.cli.exec['banner'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Display login banner';
|
||||
},
|
||||
run: args => {
|
||||
return `Konsole ${window.cli.version} LTS ${window.cli.hostname} tty1\n\n${window.cli.hostname} login: ${window.cli.user}\npassword:\n\n`;
|
||||
}
|
||||
}
|
15
src/modules/konsole/commands/cd.js
Normal file
15
src/modules/konsole/commands/cd.js
Normal file
@ -0,0 +1,15 @@
|
||||
window.cli.exec['cd'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Clear console output';
|
||||
},
|
||||
run: args => {
|
||||
const path = window.cli.fs(args[0]);
|
||||
if(!path) throw new Error(`cd: \'${args[0]}\': No such file or directory`)
|
||||
|
||||
window.cli.pwd = window.cli.path(args[0]);
|
||||
window.cli._prompt.innerText = window.cli._buildPrompt();
|
||||
}
|
||||
}
|
11
src/modules/konsole/commands/clear.js
Normal file
11
src/modules/konsole/commands/clear.js
Normal file
@ -0,0 +1,11 @@
|
||||
window.cli.exec['clear'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Clear console output';
|
||||
},
|
||||
run: args => {
|
||||
setTimeout(() => window.cli._output.innerHTML = '', 1);
|
||||
}
|
||||
}
|
11
src/modules/konsole/commands/date.js
Normal file
11
src/modules/konsole/commands/date.js
Normal file
@ -0,0 +1,11 @@
|
||||
window.cli.exec['date'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Get current date & time';
|
||||
},
|
||||
run: args => {
|
||||
return new Date().toLocaleString();
|
||||
}
|
||||
}
|
11
src/modules/konsole/commands/echo.js
Normal file
11
src/modules/konsole/commands/echo.js
Normal file
@ -0,0 +1,11 @@
|
||||
window.cli.exec['echo'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Output text to console';
|
||||
},
|
||||
run: args => {
|
||||
return args.join(' ');
|
||||
}
|
||||
}
|
16
src/modules/konsole/commands/exit.js
Normal file
16
src/modules/konsole/commands/exit.js
Normal file
@ -0,0 +1,16 @@
|
||||
window.cli.exec['exit'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'End session';
|
||||
},
|
||||
run: args => {
|
||||
setTimeout(() => {
|
||||
window.cli._history = [];
|
||||
window.cli._index = 0;
|
||||
window.cli._output.innerHTML = ''
|
||||
window.cli.stdOut(window.cli.exec['banner'].run());
|
||||
}, 1);
|
||||
}
|
||||
}
|
12
src/modules/konsole/commands/help.js
Normal file
12
src/modules/konsole/commands/help.js
Normal file
@ -0,0 +1,12 @@
|
||||
window.cli.exec['help'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Display all commands';
|
||||
},
|
||||
run: args => {
|
||||
return `Konsole v${window.cli.version} - an experimental bash emulator written in JavaScript\nCreated By: Zakary Timson\n\n` +
|
||||
Object.keys(window.cli.exec).map(command => `${command} - ${window.cli.exec[command].help()}`).join('\n');
|
||||
}
|
||||
}
|
11
src/modules/konsole/commands/hostname.js
Normal file
11
src/modules/konsole/commands/hostname.js
Normal file
@ -0,0 +1,11 @@
|
||||
window.cli.exec['hostname'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Get computer hostname';
|
||||
},
|
||||
run: args => {
|
||||
return window.cli.hostname;
|
||||
}
|
||||
}
|
13
src/modules/konsole/commands/ls.js
Normal file
13
src/modules/konsole/commands/ls.js
Normal file
@ -0,0 +1,13 @@
|
||||
window.cli.exec['ls'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Display directory contents';
|
||||
},
|
||||
run: args => {
|
||||
const target = window.cli.fs(args[0]);
|
||||
if(!target) throw new Error(`ls: cannot access \'${args[0]}\': No such file or directory`)
|
||||
return Object.keys(target).reduce((acc, p) => acc + `${p}\n`, '');
|
||||
}
|
||||
}
|
11
src/modules/konsole/commands/man.js
Normal file
11
src/modules/konsole/commands/man.js
Normal file
@ -0,0 +1,11 @@
|
||||
window.cli.exec['man'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'View command\'s manual';
|
||||
},
|
||||
run: args => {
|
||||
return window.cli.exec[args[0]].help();
|
||||
}
|
||||
}
|
12
src/modules/konsole/commands/mkdir.js
Normal file
12
src/modules/konsole/commands/mkdir.js
Normal file
@ -0,0 +1,12 @@
|
||||
window.cli.exec['mkdir'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Create new directory';
|
||||
},
|
||||
run: args => {
|
||||
if(!args[0]) throw new Error('mkdir: missing operand');
|
||||
window.cli.fs(args[0], {});
|
||||
}
|
||||
}
|
11
src/modules/konsole/commands/pwd.js
Normal file
11
src/modules/konsole/commands/pwd.js
Normal file
@ -0,0 +1,11 @@
|
||||
window.cli.exec['pwd'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Get present working directory';
|
||||
},
|
||||
run: args => {
|
||||
return window.cli.pwd;
|
||||
}
|
||||
}
|
12
src/modules/konsole/commands/rm.js
Normal file
12
src/modules/konsole/commands/rm.js
Normal file
@ -0,0 +1,12 @@
|
||||
window.cli.exec['rm'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Delete file or directory';
|
||||
},
|
||||
run: args => {
|
||||
if(!args[0]) throw new Error('rm: missing operand');
|
||||
window.cli.fs(args[0], null);
|
||||
}
|
||||
}
|
23
src/modules/konsole/commands/shower-thoughts.js
Normal file
23
src/modules/konsole/commands/shower-thoughts.js
Normal file
@ -0,0 +1,23 @@
|
||||
window.cli.exec['shower-thought'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Random shower thought';
|
||||
},
|
||||
run: args => {
|
||||
const motd = [
|
||||
'Why do kamikaze pilots wear helmets?',
|
||||
'How are giraffes real, but unicorns made up',
|
||||
'When you are a kid you don\'t realize you are also watching your parents grow up',
|
||||
'Some one at Google was like "Yea, just have someone drive down every road on earth!"',
|
||||
'The number of people older than you never goes up',
|
||||
'When you brush your teeth you are cleaning your skeleton',
|
||||
'Pregnancy is like a group project where one person get\'s stuck with all the work',
|
||||
'If the universe wasn\'t infinite it would be even scarier',
|
||||
'Either we are alone in the universe or we are not. both are terrifying',
|
||||
'The object of golf is to play the least amount of golf.'
|
||||
];
|
||||
return motd[~~(Math.random() * motd.length)];
|
||||
}
|
||||
}
|
11
src/modules/konsole/commands/whoami.js
Normal file
11
src/modules/konsole/commands/whoami.js
Normal file
@ -0,0 +1,11 @@
|
||||
window.cli.exec['whoami'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Get current user account';
|
||||
},
|
||||
run: args => {
|
||||
return window.cli.user;
|
||||
}
|
||||
}
|
19
src/modules/konsole/index.js
Normal file
19
src/modules/konsole/index.js
Normal file
@ -0,0 +1,19 @@
|
||||
import './konsole.js';
|
||||
import './konsole.css';
|
||||
|
||||
// CLI Commands
|
||||
import './commands/banner.js';
|
||||
import './commands/cd.js'
|
||||
import './commands/clear.js';
|
||||
import './commands/date.js';
|
||||
import './commands/echo.js';
|
||||
import './commands/exit.js';
|
||||
import './commands/help.js';
|
||||
import './commands/hostname.js';
|
||||
import './commands/ls.js';
|
||||
import './commands/man.js';
|
||||
import './commands/mkdir.js';
|
||||
import './commands/pwd.js';
|
||||
import './commands/rm.js';
|
||||
import './commands/shower-thoughts.js';
|
||||
import './commands/whoami.js';
|
39
src/modules/konsole/konsole.css
Normal file
39
src/modules/konsole/konsole.css
Normal file
@ -0,0 +1,39 @@
|
||||
.cli-container {
|
||||
padding: 1em;
|
||||
background: #333;
|
||||
font-family: monospace !important;
|
||||
overflow-y: auto;
|
||||
min-height: 150px;
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
.cli-stdout {
|
||||
flex-grow: 1;
|
||||
color: #0f0;
|
||||
}
|
||||
|
||||
.cli-stdout-line {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
min-height: 1em;
|
||||
}
|
||||
|
||||
.cli-stdin {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.cli-stdin-prompt {
|
||||
padding-right: 0.5em;
|
||||
text-wrap: nowrap;
|
||||
color: #0f0;
|
||||
}
|
||||
|
||||
.cli-stdin-input {
|
||||
border: none;
|
||||
outline: none;
|
||||
font-size: 1em;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
color: #0f0;
|
||||
flex-grow: 1;
|
||||
padding: 0;
|
||||
}
|
157
src/modules/konsole/konsole.js
Normal file
157
src/modules/konsole/konsole.js
Normal file
@ -0,0 +1,157 @@
|
||||
window.cli = {
|
||||
// Element references
|
||||
_input: null,
|
||||
_parent: null,
|
||||
_prompt: null,
|
||||
_output: null,
|
||||
|
||||
// CLI State
|
||||
_history: [],
|
||||
_index: 0,
|
||||
pwd: '/',
|
||||
hostname: 'virtual',
|
||||
env: {},
|
||||
exec: {},
|
||||
filesystem: {},
|
||||
user: 'root',
|
||||
version: '0.3.0',
|
||||
|
||||
_buildPrompt: () => `${window.cli.user}@${window.cli.hostname}:${window.cli.pwd}${window.cli.user == 'root' ? '#' : '$'}`,
|
||||
|
||||
build: (elementId) => {
|
||||
window.cli._parent = document.querySelector(elementId);
|
||||
if(!window.cli._parent)
|
||||
throw new Error(`Could not create konsole, element "${elementId}" does not exist`);
|
||||
|
||||
window.cli._parent.innerHTML = `
|
||||
<div class="cli-container">
|
||||
<div class="cli-stdout"></div>
|
||||
<div class="cli-stdin" onclick="window.cli._input.focus()">
|
||||
<label for="${elementId}-cli-stdin-input" class="cli-stdin-prompt">${window.cli._buildPrompt()}</label>
|
||||
<input id="${elementId}-cli-stdin-input" class="cli-stdin-input" type="text" />
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
window.cli._input = document.querySelector(elementId + ' .cli-stdin-input');
|
||||
window.cli._prompt = document.querySelector(elementId + ' .cli-stdin-prompt');
|
||||
window.cli._output = document.querySelector(elementId + ' .cli-stdout');
|
||||
window.cli._input.addEventListener('keyup', (e) => {
|
||||
if(e.key == "Enter") {
|
||||
window.cli.disable();
|
||||
if(!!window.cli._input.value) {
|
||||
window.cli._history.push(window.cli._input.value);
|
||||
window.cli._index = window.cli._history.length;
|
||||
window.cli.stdIn(window.cli._input.value)
|
||||
}
|
||||
window.cli._input.value = '';
|
||||
window.cli.enable();
|
||||
} else if(e.key == 'Up' || e.key == 'ArrowUp') {
|
||||
if(window.cli._index > 0) window.cli._index--;
|
||||
window.cli._input.value = window.cli._index == window.cli._history.length ? '' : window.cli._history[window.cli._index];
|
||||
setTimeout(() => {
|
||||
const end = window.cli._input.value.length;
|
||||
window.cli._input.setSelectionRange(end, end);
|
||||
window.cli._input.focus();
|
||||
}, 1)
|
||||
} else if(e.key == 'Down' || e.key == 'ArrowDown') {
|
||||
if(window.cli._index < window.cli._history.length) window.cli._index++;
|
||||
window.cli._input.value = window.cli._index == window.cli._history.length ? '' : window.cli._history[window.cli._index];
|
||||
setTimeout(() => {
|
||||
const end = window.cli._input.value.length;
|
||||
window.cli._input.setSelectionRange(end, end);
|
||||
window.cli._input.focus();
|
||||
}, 1)
|
||||
}
|
||||
});
|
||||
setTimeout(() => window.cli.stdOut(window.cli.exec['banner'].run()), 1);
|
||||
},
|
||||
disable: () => {
|
||||
window.cli._input.disabled = true;
|
||||
window.cli._prompt.style.visibility = 'hidden';
|
||||
},
|
||||
enable: () => {
|
||||
window.cli._input.disabled = false;
|
||||
window.cli._input.focus();
|
||||
window.cli._prompt.style.visibility = 'visible';
|
||||
},
|
||||
path: (path=window.cli.pwd) => {
|
||||
let p = path[0] == '/'? path : (window.cli.pwd + (window.cli.pwd.endsWith('/') ? '' : '/') + path.replace('./', ''))
|
||||
.replaceAll('//', '/');
|
||||
const parts = p.split('/').filter(p => !!p);
|
||||
for(let i = 0; i < parts.length; i++) {
|
||||
if(parts[i] == '..') {
|
||||
i--;
|
||||
parts.splice(i, 2);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
return '/' + (parts.length ? parts.join('/') : '');
|
||||
},
|
||||
fs: (path, set) => {
|
||||
return window.cli.path(path).split('/').filter(p => !!p).reduce((t, p, i, arr) => {
|
||||
if(!t?.hasOwnProperty(p)) {
|
||||
if(set == undefined) return undefined;
|
||||
t[p] = {};
|
||||
}
|
||||
if(set !== undefined && i == arr.length - 1) {
|
||||
if(set == null) delete t[p];
|
||||
else t[p] = set;
|
||||
}
|
||||
return t[p];
|
||||
}, window.cli.filesystem);
|
||||
},
|
||||
stdErr: (text) => {
|
||||
const p = document.createElement('p');
|
||||
p.classList.add('cli-stdout-line');
|
||||
p.classList.add('cli-stdout-error');
|
||||
p.innerText = text;
|
||||
window.cli._output.appendChild(p);
|
||||
},
|
||||
stdIn:(command, suppress=false) => {
|
||||
(Array.isArray(command) ? command.join(' ') : command).split(';').filter(c => !!c).forEach(c => {
|
||||
const parts = c.match(/(?:[^\s"]+|"[^"]*")+/g);
|
||||
if(!parts) return;
|
||||
|
||||
const exec = window.cli.exec[parts[0]];
|
||||
if(!exec?.run) {
|
||||
if(!suppress) window.cli.stdErr(`${window.cli._buildPrompt()} ${command}\n${parts[0]}: command not found`);
|
||||
} else {
|
||||
try {
|
||||
const args = parts.slice(1).map(a => (a[0] == '"' || a[0] == "'") ? a.slice(1, -1) : a);
|
||||
const out = exec.run(args);
|
||||
if(!suppress) window.cli.stdOut(`${window.cli._buildPrompt()} ${command}${out ? '\n' + out : ''}`);
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
if(!suppress) {
|
||||
window.cli._output.removeChild(window.cli._output.children[window.cli._output.children.length - 1]);
|
||||
window.cli.stdErr(`${window.cli._buildPrompt()} ${command}\n${err.message || `${parts[0]}: exited with a non-zero status`}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
stdOut: (text='', html=false) => {
|
||||
const p = document.createElement('p');
|
||||
p.classList.add('cli-stdout-line');
|
||||
p[html ? 'innerHTML' : 'innerText'] = text;
|
||||
window.cli._output.appendChild(p);
|
||||
},
|
||||
type: (text, speed=150) => {
|
||||
let counter = 0;
|
||||
return new Promise(res => {
|
||||
let typing = setInterval(() => {
|
||||
if(counter < text.length) {
|
||||
window.cli._input.value += text[counter];
|
||||
} else {
|
||||
clearInterval(typing);
|
||||
setTimeout(() => {
|
||||
window.cli.stdIn(text);
|
||||
window.cli._input.value = '';
|
||||
res();
|
||||
}, 750);
|
||||
}
|
||||
counter++;
|
||||
}, speed);
|
||||
});
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue
Block a user