zakscode/src/components/konsole.vue
2024-01-03 23:49:40 -05:00

252 lines
5.3 KiB
Vue

<script setup>
import {onMounted} from 'vue';
const hostname = 'virtual';
let history = [];
let historyIndex = 0;
let prompt;
let input;
let output;
function focus() {
input.focus();
}
function disable() {
input.disabled = true;
prompt.style.visibility = 'hidden';
}
function enable() {
input.disabled = false;
input.focus();
prompt.style.visibility = 'visible';
}
function banner() {
stdOut(`Konsole 0.2.0 LTS virtual tty1<br><br>${hostname} login: root<br>password:<br><br>`);
}
function process(command) {
(Array.isArray(command) ? command.join(' ') : command).split(';').filter(c => !!c).forEach(c => {
const parts = c.split(' ').filter(c => !!c);
if(window.cli[parts[0]] == undefined || window.cli[parts[0]].run == undefined) {
stdErr(`${parts[0]}: command not found`);
} else {
try {
const out = window.cli[parts[0]].run(parts.slice(1));
if(!!out) stdOut(out);
} catch(err) {
console.error(err)
stdErr(`${parts[0]}: exited with a non-zero status`);
}
}
});
}
function stdErr(text) {
const p = document.createElement('p');
p.classList.add('console-output-line');
p.classList.add('console-output-error');
p.innerText = text;
output.appendChild(p);
}
function stdOut(text, html=true) {
const p = document.createElement('p');
p.classList.add('console-output-line');
p[html ? 'innerHTML' : 'innerText'] = text;
output.appendChild(p);
}
function stdIn(event) {
if(event.key == "Enter") {
disable();
let inputValue = input.value;
input.value = '';
stdOut(`root@localhost:~ # ${inputValue}`, false);
if(!!inputValue) {
history.push(inputValue);
historyIndex = history.length;
process(inputValue)
}
enable();
} else if(event.key == 'Up' || event.key == 'ArrowUp') {
if(historyIndex > 0) historyIndex--;
input.value = historyIndex == history.length ? '' : history[historyIndex];
setTimeout(() => {
const end = input.value.length;
input.setSelectionRange(end, end);
input.focus();
}, 1)
} else if(event.key == 'Down' || event.key == 'ArrowDown') {
if(historyIndex < history.length) historyIndex++;
input.value = historyIndex == history.length ? '' : history[historyIndex];
setTimeout(() => {
const end = input.value.length;
input.setSelectionRange(end, end);
input.focus();
}, 1)
}
}
onMounted(() => {
prompt = document.getElementsByClassName('console-input-prompt')[0];
input = document.getElementsByClassName('console-input-field')[0];
output = document.getElementsByClassName('console-output')[0];
banner();
});
window.cli = {};
window.cli['clear'] = {
autocomplete: () => {
return [];
},
help: () => {
return 'Clear console output';
},
run: args => {
output.innerHTML = '';
}
}
window.cli['echo'] = {
autocomplete: () => {
return [];
},
help: () => {
return 'Output text to console';
},
run: args => {
return args.join(' ');
}
}
window.cli['exit'] = {
autocomplete: () => {
return [];
},
help: () => {
return 'End session';
},
run: args => {
process('clear');
history = [];
historyIndex = 0;
banner();
}
}
window.cli['help'] = {
autocomplete: () => {
return [];
},
help: () => {
return 'Display all commands';
},
run: args => {
return Object.keys(window.cli).map(command => `${command} - ${window.cli[command].help()}`).join('<br>') + '<br><br>';
}
}
window.cli['hostname'] = {
autocomplete: () => {
return [];
},
help: () => {
return 'Get computer hostname';
},
run: args => {
return 'localhost'
}
}
window.cli['man'] = {
autocomplete: () => {
return [];
},
help: () => {
return 'Command manual';
},
run: args => {
return window.cli[args[0]].help();
}
}
window.cli['whoami'] = {
autocomplete: () => {
return [];
},
help: () => {
return 'Get username';
},
run: args => {
return 'root'
}
}
</script>
<style>
.console {
display: flex;
flex-direction: column;
padding: 1rem;
background: #333;
font-family: monospace !important;
overflow-y: auto;
.console-output {
flex-grow: 1;
color: #0f0;
}
.console-output-line {
margin: 0;
padding: 0;
min-height: 1.25rem;
}
.console-input {
display: flex;
margin: 0;
.console-input-prompt {
padding-right: 0.55em;
text-wrap: nowrap;
color: #0f0;
}
.console-input-field {
border: none;
outline: none;
font-size: 1rem;
background-color: rgba(0, 0, 0, 0);
color: #0f0;
flex-grow: 1;
padding: 0;
animation: blink-empty 1s infinite linear;
background-image: linear-gradient(#0f0, #0f0);
background-position: 1px center;
background-repeat: no-repeat;
background-size: 1px 1.1em;
&:focus {
background-image: none;
}
}
}
}
@keyframes blink-empty {
0% {background-size: 1px 1.1em;}
50% {background-size: 1px 1.1em;}
51% {background-size: 0 1.1em;}
100% {background-size: 0 1.1em;}
}
</style>
<template>
<div class="console">
<div class="console-output"></div>
<div class="console-input" @click=" focus()">
<div class="console-input-prompt">root@{{hostname}}:~ #</div>
<input class="console-input-field" type="text" @keydown="stdIn($event)">
</div>
</div>
</template>