Skip to content

Instantly share code, notes, and snippets.

@LunaSquee
Last active August 23, 2016 11:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save LunaSquee/0237f0ad0eeda4a452794173c78b8a45 to your computer and use it in GitHub Desktop.
Save LunaSquee/0237f0ad0eeda4a452794173c78b8a45 to your computer and use it in GitHub Desktop.
thingy - web shell (terminal emulator) [BASE APPLICATION]
<!DOCTYPE html>
<html>
<head>
<style type='text/css'>
body{
margin: 0;
padding: 0;
background-color: #04050a;
color: #fff;
font-size: 120%;
font-family: monospace;
}
.main {
margin: 10px;
}
input#stdin {
background: none;
border: none;
color: #fff;
width: 50vw;
}
.hidden {
display: none;
}
.stdout {
font-size: 90%;
}
.stdin {
font-size: 120%;
position: relative;
}
#infield {
z-index: 5;
}
#inputblock {
background-color: #00ca00;
position: relative;
z-index: 10;
right: -11.4px;
opacity: 0.8;
width: 11px;
display: inline-block;
}
#inputblock.unfocused {
background-color: #04050a;
border: 1px solid #00ca00;
height: 21.82px;
}
.offscreen{
position: absolute;
bottom: 0;
opacity: 0;
}
.stdout .charblock {
display: inline-block;
width: 9px;
}
.stdin .charblock {
display: inline-block;
width: 11px;
}
</style>
<meta charset='utf-8'>
<title>Thingy</title>
</head>
<!-- Codepony LunaSquee -->
<body>
<div class='main'>
<input type='text' name='primary' id='stdin' class='offscreen'>
<div class='letterbox'></div>
<div class='stdin hidden'><span style='color: red;' id='exitcode'></span> <span style='color: limegreen;'>~</span> <span style='color: yellow;' id='directory'>$</span><span id='inputblock' class='unfocused'>&nbsp;</span><span id='infield'></span></div>
</div>
<script type='text/javascript'>
let main;
let inContainer;
let input;
let virtInput;
let exitcode;
let letterbox;
let inputblock;
let ecode = 0;
let canInput = false;
let scrToBottomOnInput = true;
let scrToBottomOnOutput = true;
let commands = {
'help': { response: ['return n - returns status code n', 'exit - exit the shell', 'help - help', 'clear - clear the screen'] },
'return': { response: function(cmd, args) {
if (!args[1]) return 0;
let res = 1;
try {
res = parseInt(args[1]);
} catch(e) {
stdout('stderr', 'return: invalid input');
}
return res;
}},
'exit': { response: function() {
stdout("exit", "Exiting..");
inputOn(false);
window.location.href = '/';
return 0;
}},
'clear': { response: function() {
letterbox.innerHTML = "";
return 0;
}}
};
function scrollBottom() {
document.body.scrollTop = document.body.scrollHeight;
document.documentElement.scrollTop = document.body.scrollHeight;
}
function blockifyStr(str) {
let tt = str.split("");
let nt = "";
for(let v in tt)
nt += "<span class='charblock char-"+tt[v]+"'>"+tt[v]+"</span>";
return nt;
}
let xsc = {
history: [],
historyCaret: 0
}
function clonePrompt() {
let n = inContainer.cloneNode(true);
n.removeChild(n.querySelector('#inputblock'));
let nbsp = document.createElement('span');
nbsp.innerHTML = '&nbsp;';
n.querySelector('#directory').appendChild(nbsp);
letterbox.appendChild(n);
if(scrToBottomOnOutput)
scrollBottom();
return n;
}
function stdout(baseclass, message) {
let d = document.createElement('div');
d.className = baseclass+' stdout';
d.innerHTML = blockifyStr(message);
letterbox.appendChild(d);
if(scrToBottomOnOutput)
scrollBottom();
return d;
}
function updateCaret() {
let offset = 11;
if (virtInput.innerHTML.length != 0)
offset = virtInput.offsetWidth / input.value.length;
inputblock.style.right = ((-(input.selectionStart+1))*offset)+'px';
}
function handleCommand(stdin) {
let args = stdin.split(' ');
if(!commands[args[0]]) {
stdout('stderr', 'thingy: command not found: '+args[0]);
return 127;
}
let command = commands[args[0]];
let returnstatus = 0;
if(command['response']) {
if (command['status'] != null)
returnstatus = command['status'];
if (typeof command['response'] == 'object') {
for(let i in command['response']) {
let ti = command['response'][i];
if (returnstatus > 1)
stdout('stderr', ti);
else
stdout('regular', ti);
}
} else if (typeof command['response'] == 'string') {
if (returnstatus > 1)
stdout('stderr', command['response']);
else
stdout('regular', command['response']);
} else if (typeof command['response'] == 'function') {
returnstatus = command.response(stdin, args, args.length);
}
}
return returnstatus;
}
function inputOn(val) {
canInput = val;
if (val) {
inContainer.className = 'stdin';
input.focus();
} else {
inContainer.className = 'stdin hidden';
}
}
function letterByLetterStdout(message, delayms) {
return new Promise(function(resolve) {
let res = "";
let outLine = stdout("regular", "");
let letters = message.split("");
let t = 0;
let ti = setInterval(function() {
res += letters[t];
outLine.innerHTML = blockifyStr(res);
t++;
if(t >= letters.length) {
resolve(true);
clearInterval(ti);
}
}, delayms);
})
}
(function() {
main = document.querySelector('.main');
inContainer = main.querySelector('.stdin');
input = main.querySelector('#stdin');
virtInput = inContainer.querySelector('#infield');
exitcode = inContainer.querySelector('#exitcode');
letterbox = main.querySelector('.letterbox');
inputblock = inContainer.querySelector('#inputblock');
input.addEventListener('input', function() {
virtInput.innerHTML = blockifyStr(input.value);
if(scrToBottomOnInput)
scrollBottom();
}, false);
input.addEventListener('focus', function() {
inputblock.className = 'focused';
}, false);
input.addEventListener('blur', function() {
inputblock.className = 'unfocused';
}, false);
input.addEventListener('select', function() {
}, false);
input.addEventListener('keypress', function(e) {
if(e.keyCode == 13) {
let stdin = input.value.trim();
clonePrompt();
let ex = 0;
if(stdin) {
if (stdin.indexOf(';') != -1) {
let cmds = stdin.split(';');
for(let i in cmds)
ex = handleCommand(cmds[i].trim());
} else if (stdin.indexOf('&&') != -1) {
let cmds = stdin.split('&&');
let failed = false;
for(let i in cmds) {
if(failed) break;
ex = handleCommand(cmds[i].trim());
if(ex > 0)
failed = true;
}
} else {
ex = handleCommand(stdin);
}
xsc.history.push(stdin);
xsc.historyCaret = xsc.history.length;
} else {
ex = ecode;
}
if(!ex == 0)
exitcode.innerHTML = ex;
else
exitcode.innerHTML = '';
ecode = ex;
input.value = '';
virtInput.innerHTML = '';
return false;
}
}, false);
input.addEventListener('keydown', function(e) {
if(e.keyCode == 38) {
if(xsc.historyCaret <= 0) {
xsc.historyCaret = 0;
} else {
xsc.historyCaret -= 1;
}
let selection = xsc.history[xsc.historyCaret];
if(selection) {
input.value = selection;
input.selectionStart = selection.length;
input.selectionEnd = selection.length;
virtInput.innerHTML = selection;
}
return false;
} else if(e.keyCode == 40) {
if(xsc.historyCaret >= xsc.history.length) {
xsc.historyCaret = xsc.history.length;
} else {
xsc.historyCaret += 1;
}
let selection = xsc.history[xsc.historyCaret]
if(!xsc.history[xsc.historyCaret])
selection = '';
input.value = selection;
input.selectionStart = selection.length;
input.selectionEnd = selection.length;
virtInput.innerHTML = selection;
return false;
}
}, false);
document.addEventListener('click', function() {
if(canInput)
input.focus();
}, false);
setInterval(function() {
updateCaret();
}, 10);
letterByLetterStdout("Welcome to the thingy!", 50).then(inputOn);
})();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment