Last active
August 23, 2016 11:06
-
-
Save LunaSquee/0237f0ad0eeda4a452794173c78b8a45 to your computer and use it in GitHub Desktop.
thingy - web shell (terminal emulator) [BASE APPLICATION]
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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'> </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 = ' '; | |
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