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">
|
<script setup lang="ts">
|
||||||
import '../misc/konsole';
|
import '../modules/konsole';
|
||||||
import {onMounted, ref} from 'vue';
|
import {onMounted, ref} from 'vue';
|
||||||
|
|
||||||
const animate = ref(true);
|
const animate = ref(true);
|
||||||
@ -28,48 +28,6 @@ onMounted(async () => {
|
|||||||
});
|
});
|
||||||
</script>
|
</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>
|
<template>
|
||||||
<div id="konsole" @click="animate = false"></div>
|
<div id="konsole" @click="animate = false"></div>
|
||||||
</template>
|
</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