first commit

This commit is contained in:
JasterV 2020-10-02 14:36:04 +02:00
commit 04b4c365a1
7 changed files with 1095 additions and 0 deletions

120
README.md Normal file
View file

@ -0,0 +1,120 @@
# JS Terminal
This project is based on 2 main objects which allow me to work in a very easy way with the terminal and all the file structure.
## CLI
As we are working on the implementation of a terminal which allows to execute commands, I thought of creating an object which would provide me with all the basic functionalities expected from such a terminal like executing a command, checking if a command exists, printing results in the terminal etc.
For the implementation of each command, I created an object which contains as keys the names of each command and as value all the information about that command including a *run* function to execute it.
This way, to execute any command entered by the user I only need to:
```javascript
cli.run(command, params);
```
And the object *cli* will be in charge of executing:
```javascript
commands[command].run(params);
```
The commands accepted by this terminal are: ```**pwd**, **ls**, **cd**, **mkdir**, **echo**, **cat**, **rm**, **mv**, **clear**, **help**, **man**, **urbandict**, **js**```,
To work with paths, files and directories I implemented an object called *filesTree* which provides me with all the functionalities I need to work with a file hierarchy.
## Files Tree
To develop a terminal that works with files and directories, first I proposed a tree type object that could perform operations such as adding nodes, removing nodes, moving nodes, searching nodes, etc.
This structure is based on a set of nodes, where each node contains a reference to the parent node and, in the case of a directory, references to the children.
![Tree Diagram](assets/tree_diagram.png)
Then I can implement in a very fast and simple way any command that is requested as well as *ls* (list child nodes), *mkdir* (create a new directory type node), *mv* (move a node) etc.
In addition, working with a tree structure allows me to navigate between the nodes using very simple recursive functions.
## Local Storage
In the *local storage* both the file tree and the history of entered commands are stored.
To be able to save a circular tree structure like the one we have created (Children nodes save references from the parents and vice versa), we cannot convert to *string* the object directly.
To do that, I have implemented 2 intermediate steps, *serialize* and *deserialize*.
To serialize the tree, I go through it recursively exchanging all the references to an object for an id that represents that object, and I store this id-object pair in a new object.
This way I can save this new object I create in the *local storage*.
To deserialize the tree we do the opposite process. We go through this new object and transform each id to the object it identifies, creating again a tree structure.
## Keyboard shortcuts
+ Up Arrow: See the previous command entered
+ Down Arrow: See the following entered command
+ Ctrl + L: Clear Terminal
+ Tab: Autocomplete with the following available file/directory
## History
To save each entered command we use an array which we save in the *local storage* every time we enter a new command.
## Urban Dictionary API
To call this api from the terminal I have created a command called *urbandict* which receives a word as a parameter.
This [API](https://rapidapi.com/community/api/urban-dictionary) receives the word that the user has written and returns a list with all the existing definitions in the *Urban Dictionary* web of that word.
ENDPOINT: ```https://mashape-community-urban-dictionary.p.rapidapi.com/define```
To ask for the definitions of a word we add that word in the *term* parameter:
```https://mashape-community-urban-dictionary.p.rapidapi.com/define?term=hair```
### API Response
```json
{
"list": [
{
"definition": "A separate creature that happens to live on your head, hard to [tame]. Ferociously attacking it with scissors, dye or hairproducts may [euthanize] said beast for a short while but beware of [angering] it.",
"permalink": "http://hair.urbanup.com/4950762",
"thumbs_up": 186,
"sound_urls": [],
"author": "nofu",
"word": "hair",
"defid": 4950762,
"current_vote": "",
"written_on": "2010-05-09T00:00:00.000Z",
"example": "[Lizzie] tried hot-ironing her hair to [submission] but oh [woes], it was raining and the beast came out on top in the end anyway.",
"thumbs_down": 52
},
{
"definition": "A growing substance found mostly [on the head]. Hair is largely [amino] [acid] based and can come in a veriety of colours e.g. brown, ginger, black or blonde.",
"permalink": "http://hair.urbanup.com/109686",
"thumbs_up": 527,
"sound_urls": [],
"author": "Jim Hodgson",
"word": "hair",
"defid": 109686,
"current_vote": "",
"written_on": "2003-04-28T00:00:00.000Z",
"example": "[Tomorrow] I will [comb] [my hair]",
"thumbs_down": 236
},
...
},
}
```
## Incidents Record
+ Dividing a *path* into *tail* and *name* has given many problems as there are many *corner cases* that are hard to find
+ Auto complete with *tab* has given some strange behavior but is not a problem for the execution
+ Moving a directory gave problems if the current directory was underneath the moving node. This is now fixed
+ Saving the tree in the local storage gave an error. I have solved it through a serialization process
## Lessons Learnt Record
+ Creation of regex for the split of a valid linux path
+ Serializing and deserializing a tree in a recursive way
+ I have been able to implement one of my favorite data structures since its operations are recursive by nature and I have had a great time :)

BIN
assets/tree_diagram.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

44
index.html Normal file
View file

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JS CLI</title>
<!-- import the webpage's stylesheet -->
<link rel="stylesheet" href="src/style.css" />
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"
integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<script src="src/scripts/filesTree.js"></script>
<script src="src/scripts/cli.js"></script>
<script src="src/scripts/script.js" defer></script>
</head>
<body>
<main>
<h1>JS CLI</h1>
<div class="cli">
<div class="cli-hd">
<div class="circle"></div>
<div class="circle"></div>
<div class="circle"></div>
</div>
<div class="cli-out">
<div class="cli-inp" id="main-inp">
<p class="route"><span>/</span> ></p>
<input type="text" spellcheck="false" autofocus>
</div>
</div>
</div>
</main>
</body>
</html>

500
src/scripts/cli.js Normal file
View file

@ -0,0 +1,500 @@
function createCli() {
/** PRIVATE ATTRIBUTES */
var tree = getFilesFromStorage();
var history = getHistory();
var historyIndex = -1;
var tabs = [];
var tabsIndex = 0;
var commands = {
ls: {
run: ls,
name: "ls - list directory contents",
synopsis: "ls [OPTION] [FILE]",
description: "List information about the FILEs (the current directory by default)" +
"<br>" + "-R, list subdirectories recursively" +
"<br>" + "-S, sort by file size, largest first" +
"<br>" + "-t, sort by modification time, newest first",
},
pwd: {
run: pwd,
name: "pwd - print name of current/working directory",
synopsis: "pwd",
description: "Print the full filename of the current working directory."
},
cd: {
run: cd,
name: "cd - move to another directory",
synopsis: "cd [PATH]",
description: "Move to the specified path (the root directory by default)"
},
mkdir: {
run: function (params) {
mkdir(params);
saveFiles();
},
name: "mkdir - make directories",
synopsis: "mkdir DIRECTORY",
description: "Create the DIRECTORY(ies), if they do not already exist."
},
echo: {
run: function (params) {
echo(params);
saveFiles();
},
name: "echo - display a line of text",
synopsis: "echo [STRING] | echo [STRING] > [FILE]",
description: "Echo the STRING(s) to standard output. Can be used with the '>' and '>>' operators to create a new file."
},
cat: {
run: cat,
name: "cat - print file contents on the standard output",
synopsis: "cat [FILE]",
description: "Echo a FILE to standard output"
},
rm: {
run: function (params) {
rm(params);
saveFiles();
},
name: "rm - remove files or directories",
synopsis: "rm [PATH]",
description: "rm removes the specified file/directory. It can't remove parent directories or the current directory."
},
mv: {
run: function (params) {
mv(params);
saveFiles();
},
name: "mv - move (rename) files",
synopsis: "mv SOURCE DEST | mv SOURCE DIRECTORY",
description: "Rename SOURCE to DEST, or move SOURCE to DIRECTORY."
},
clear: {
run: clear,
name: "clear - clear the terminal screen",
synopsis: "clear",
description: "clear clears your screen if this is possible."
},
help: {
run: help,
name: "help - display a brief explanation of each command",
synopsis: "help",
description: "Display a shorthand manual with all the commands available"
},
man: {
run: man,
name: "man - an interface to the system reference manuals",
synopsis: "man [COMMAND]",
description: "man is the system's manual pager. Each page argument given to man is normally the name of a program, utility or function. The manual page associated with each of these arguments is then found and displayed",
},
urbandict: {
run: function (params) {
urbandict(params);
},
name: "urbandict - search definitions of the given word",
synopsis: "urbandict [STRING]",
description: "Make a GET request to the Urban Dictionary API (https://www.urbandictionary.com/) and echo the results to standard output.",
},
js: {
run: function (params) {
js(params);
},
name: "js - evaluate the given file",
synopsis: "js [FILE]",
description: "evaluates the content of a given js file and shows the result of the evaluation",
}
}
/** PUBLIC METHODS */
return {
has(command) {
return command in commands;
},
run(command, params) {
if (params == undefined) params = [];
commands[command].run(params);
},
newLine: newLine,
getInputValue: getInputValue,
historyUp() {
if (historyIndex < history.length - 1) {
historyIndex += 1;
setInputValue(history[historyIndex]);
}
},
historyDown() {
if (historyIndex >= 0) {
historyIndex -= 1;
setInputValue(history[historyIndex]);
}
},
addToHistory(value) {
if (value != "") {
history.unshift(value);
saveHistory();
}
historyIndex = -1;
},
autocomplete() {
var value = getInputValue();
var tokens = value.split(' ');
var parent = tree.currentNode();
var path = splitPath(tree.currentPath());
if (value.length > 0) {
if (tokens.length > 1) {
path = splitPath(tokens[tokens.length - 1])
parent = tree.findNode(path[0]);
}
try {
var child = getNextAvailableChild(parent, path[1]);
var newPath = autocompleteTail(path[0], child.name);
if (tokens.length > 1) tokens[tokens.length - 1] = newPath;
else tokens.push(child.name);
setInputValue(tokens.join(' '));
} catch (error) {}
}
},
resetTab() {
tabs = [];
tabsIndex = 0;
},
}
/** PRIVATE METHODS */
function js(params) {
if (params.length != 1) {
newLine("USAGE: js [FILE]");
return;
}
var path = params[0]
var file = tree.findNode(path)
try {
var result = tree.evaluateFile(file);
newLine(result);
} catch (error) {
newLine(error);
}
}
function urbandict(params) {
if (params.length != 1) {
newLine("USAGE: urbandict [STRING]");
return;
}
var word = params[0];
getMeanings(word).then(function (response) {
if (response.length == 0) {
newLine("No definitions were found for " + word);
} else {
newLine()
response.forEach(function (item) {
cliLog("<strong>DEFINITION</strong>")
cliLog(item.definition.replace(/[\[\]]/g, ''));
cliLog("<strong>EXAMPLE</strong>");
cliLog(item.example.replace(/[\[\]]/g, ''));
cliLog("<strong>REFERENCE</strong>")
cliLog(item.permalink)
cliLog("<strong>- - - - - - - - - -</strong>")
})
}
}).catch(function (error) {
newLine(error);
})
}
function help(params) {
if (params.length > 0) {
newLine("Usage: help");
return;
}
newLine();
for (var key of Object.keys(commands))
cliLog(commands[key].name);
}
function man(params) {
if (params.length != 1) {
newLine("Usage: man [COMMAND]");
return;
}
var command = params[0];
if (!(command in commands)) {
newLine("Error: " + command + " is not a valid command");
return;
}
command = commands[command];
newLine();
cliLog("<strong>NAME</strong>")
cliLog(command.name);
cliLog("<strong>SYNOPSIS</strong>");
cliLog(command.synopsis);
cliLog("<strong>DESCRIPTION</strong>");
cliLog(command.description);
}
function ls(params) {
if (params.length > 2) {
newLine("Usage: ls [option] [path]");
return;
}
var flag = undefined;
var dir = tree.currentNode();
if (params.length == 1) {
if (params[0].startsWith("-")) flag = params[0]
else dir = tree.findNode(params[0])
} else if (params.length == 2) {
flag = params[0]
dir = tree.findNode(params[1])
}
try {
var result = tree.listNode(dir, flag);
newLine()
if (flag == "-R")
for (var elem of result) cliLog(elem[1] + elem[0])
else
for (var elem of result) cliLog(elem.name)
} catch (error) {
newLine(error);
}
}
function rm(params) {
if (params.length != 1) {
newLine("Usage: rm [path]");
return;
}
var dir = tree.findNode(params[0]);
try {
tree.removeNode(dir);
newLine();
} catch (error) {
newLine(error)
}
}
function mv(params) {
if (params.length != 2) {
newLine("Usage: mv [from] [to]");
return;
}
var from = tree.findNode(params[0])
var to = tree.findNode(params[1])
try {
if (to == undefined) {
var path = splitPath(params[1])
var tail = path[0],
fromName = path[1]
to = tree.findNode(tail)
tree.moveNode(from, to, fromName);
} else tree.moveNode(from, to);
newLine()
} catch (error) {
newLine(error)
}
}
function clear(params) {
if (params.length > 0) {
newLine("Usage: clear");
return;
}
var clone = $("#main-inp").clone(true);
$(".cli-out").empty();
$(".cli-out").append(clone);
clearInput();
}
function echo(params) {
if (params.length != 1 && params.length != 3) {
newLine("Usage: echo [str] [>|>>] [path]")
return;
}
if (params.length == 1) {
newLine(params[0])
return;
}
if (params[1] != '>' && params[1] != '>>') {
newLine("Usage: echo [str] [>|>>] [path]")
return;
}
var path = splitPath(params[2])
var name = path[1],
tail = path[0]
var dir = tree.findNode(tail);
try {
if (params[1] == ">>") tree.createFile(dir, name, params[0], true)
else tree.createFile(dir, name, params[0])
newLine();
} catch (error) {
newLine(error);
}
}
function pwd(params) {
if (params.length > 0) {
newLine("Usage: pwd");
return;
}
newLine(tree.currentPath())
}
function mkdir(params) {
if (params.length != 1) {
newLine("Usage: mkdir [path]");
return;
}
var path = splitPath(params[0])
var name = path[1],
tail = path[0]
var dir = tree.findNode(tail);
try {
tree.createDir(dir, name)
newLine();
} catch (error) {
newLine(error);
}
}
function cat(params) {
if (params.length != 1) {
newLine("Usage: cat [path]");
return;
}
var file = tree.findNode(params[0]);
try {
var content = tree.getContent(file);
newLine(content);
} catch (error) {
newLine(error);
}
}
function cd(params) {
if (params.length > 1) {
newLine("Usage: cd [path]");
return;
}
var dir = tree.root();
if (params.length == 1) dir = tree.findNode(params[0]);
try {
tree.moveTo(dir);
newLine();
} catch (error) {
newLine(error);
}
}
/** LOCAL STORAGE METHODS */
function getFilesFromStorage() {
var item = localStorage.getItem("filetree");
if (item != undefined)
return deserializeTree(JSON.parse(item))
return filesTree();
}
function saveFiles() {
var item = tree.serialize();
localStorage.setItem("filetree", JSON.stringify(item));
}
function getHistory() {
var item = localStorage.getItem("history");
if (item != undefined) return JSON.parse(item);
return [];
}
function saveHistory() {
localStorage.setItem("history", JSON.stringify(history));
}
/** DOM INTERACTION METHODS */
function newLine(result) {
var clone = cloneMainInp();
$("#main-inp").before(clone);
if (result != undefined)
cliLog(result)
$("#main-inp span").text(tree.currentPath());
clearInput();
}
function cliLog(result) {
$("#main-inp").before($('<p class="cmd-result">' + result + '</p>'))
}
function cloneMainInp() {
var clone = $('<div class="cli-inp"></div>');
var route = $('<p class="route">' + '<span>' + $("#main-inp span").text() + '</span> > ' + $("#main-inp input").val() + ' </p>');
clone.append(route);
return clone;
}
function clearInput() {
$("#main-inp input").focus();
$("#main-inp input").val("");
}
function setInputValue(value) {
$("#main-inp input").val(value);
}
function getInputValue() {
return $("#main-inp input").val().trim();
}
/** UTIL METHODS */
function getMeanings(word) {
return axios({
"url": "https://mashape-community-urban-dictionary.p.rapidapi.com/define?term=" + word,
"method": "get",
"timeout": 0,
"headers": {
"x-rapidapi-key": "a3a58aad81mshba110cbc0274d35p1d8b24jsn848d26bf6933"
},
}).then(function (response) {
return response.data.list;
})
}
function splitPath(path) {
var tailRegex = /^\S*\/[\.]*|^\.+$/gm;
var baseRegex = /[\w\-\_]+$|([\w\-\_]*\.+\w+)$|([\w\-\_]+\.+\w*)$/gm;
var tail = tailRegex.exec(path)
var base = baseRegex.exec(path)
tail = tail == null ? "." : tail[0]
base = base == null ? "" : base[0]
return [tail, base]
}
function getNextAvailableChild(parent, name) {
if (tabs.length > 0) {
var child = tabs[tabsIndex];
tabsIndex = (tabsIndex + 1) % tabs.length;
return child;
}
tabs = tree.listNode(parent);
for (var i = 0; i < tabs.length; i++) {
if (tabs[i].name.startsWith(name)) {
var child = tabs[i];
tabsIndex = (i + 1) % tabs.length;
return child
}
}
var child = tabs[tabsIndex];
tabsIndex = (tabsIndex + 1) % tabs.length;
return child;
}
function autocompleteTail(tail, newName) {
return tail.endsWith("/") ? tail + newName :
tail.startsWith(".") ? newName :
tail + "/" + newName;
}
}

258
src/scripts/filesTree.js Normal file
View file

@ -0,0 +1,258 @@
function filesTree(root) {
/** PRIVATE ATTRIBUTES */
if (root == undefined) root = {
parent: null,
childs: {},
name: "/",
time: Date.now(),
type: "d",
}
var currentDir = root;
/** PUBLIC METHODS */
return {
currentPath() {
return getPath(currentDir);
},
currentNode() {
return currentDir;
},
root() {
return root;
},
findNode(path) {
var node = currentDir;
if (path == "/") return root;
if (path == ".") return currentDir;
if (path.startsWith("/")) node = root;
tokens = path.replace(/\//g, " ").trim().split(" ")
for (var i = 0; i < tokens.length; i++) {
if (tokens[i] == '.') continue;
if (tokens[i] == ".." && node.parent != null)
node = node.parent;
else if (tokens[i] == ".." && node.parent == null) continue;
else {
if (tokens[i] in node.childs) {
if (isdir(node.childs[tokens[i]]))
node = node.childs[tokens[i]];
else return node.childs[tokens[i]]
} else return undefined;
}
}
return node;
},
listNode(node, flag) {
var validFlags = ["-S", "-R", "-t"];
var result = []
if (node == undefined) throw Error("Path not found");
if (isfile(node)) throw Error("Can't list a file");
if (flag != undefined && !validFlags.includes(flag)) throw Error("Invalid option");
if (flag == "-R") result = listRecursive(node)
else {
for (var key of Object.keys(node.childs)) result.push(node.childs[key])
if (flag == "-S") result.sort(compareSize);
else if (flag == "-t") result.sort(compareTime);
}
return result
},
removeNode(node) {
if (node == undefined) throw Error("Path not found");
if (isChild(currentDir, node)) throw Error("Can't remove a parent directory");
if (node == currentDir) throw Error("Can't remove the current directory");
delete node.parent.childs[node.name];
},
moveNode(from, to, newName) {
if (from == undefined || to == undefined) throw Error("Path not found");
if (from == root) throw Error("Can't move the root directory");
if (from == currentDir) throw Error("Can't move the current directory");
if (from == to) throw Error("File already exists");
if (isdir(from) && isChild(currentDir, from)) throw Error("Can't move a parent directory");
if (isChild(to, from)) throw Error("Can't move a file to a child");
if (isdir(from) && isfile(to)) throw Error("cannot overwrite non-directory with directory");
this.removeNode(from);
from.time = Date.now()
if (isdir(to)) {
from.parent = to;
if (newName) from.name = newName;
} else {
from.name = to.name;
from.parent = to.parent;
}
from.parent.childs[from.name] = from;
},
createDir(parent, name) {
if (parent == undefined) throw Error("Path not found");
if (isfile(parent)) throw Error(getPath(parent) + " is a file")
if (name in parent.childs) throw Error("Folder already exists");
if (name == "") throw Error(getPath(parent) + " already exists");
parent.childs[name] = {
name: name,
parent: parent,
childs: {},
time: Date.now(),
type: "d",
}
},
createFile(parent, name, content, append) {
if (parent == undefined) throw Error("Path not found");
if (isfile(parent)) throw Error(getPath(parent) + " is a file")
if (name == "") throw Error("Name undefined");
if (name in parent.childs && isdir(parent.childs[name])) throw Error(name + " is a directory");
parent.childs[name] = {
name: name,
parent: parent,
content: append && name in parent.childs ? parent.childs[name].content + '\n' + content : content,
time: Date.now(),
type: "f",
}
},
evaluateFile(file) {
if(file == undefined) throw Error("Path not found");
if(!isfile(file)) throw Error("Can't evaluate a directory");
return eval(file.content);
},
moveTo(node) {
if(node == undefined) throw Error("Path not found")
if (node != undefined && isfile(node)) throw Error("Cannot move to a file");
currentDir = node ? node : root;
},
getContent(file) {
if (file == undefined) throw Error("Path not found");
if (isdir(file)) throw Error("Cannot read a directory");
return file.content;
},
serialize() {
return serializeNode(root);
},
}
/** PRIVATE METHODS */
function compareSize(a, b) {
var size1 = getNodeSize(a)
var size2 = getNodeSize(b)
if (size1 < size2) return 1
if (size1 > size2) return -1
return 0
}
function getNodeSize(node) {
if (isfile(node)) return 2 * node.content.length
var size = 0;
for (var key of Object.keys(node.childs)) size += getNodeSize(node.childs[key])
return 4096 + size;
}
function compareTime(a, b) {
if (a.time < b.time) return 1
if (a.time > b.time) return -1
return 0
}
function listRecursive(node, level, result) {
if (level == undefined) level = 0;
if (result == undefined) result = [];
var tabs = '&nbsp&nbsp&nbsp'.repeat(level);
result.push([node.name, tabs]);
if (isdir(node)) {
for (var key of Object.keys(node.childs))
listRecursive(node.childs[key], level + 1, result)
}
return result;
}
function isdir(node) {
return node.type == "d";
}
function isfile(node) {
return node.type == "f";
}
function isChild(child, parent) {
if (child == null || parent == null) return false
if (child.parent == parent) return true;
return isChild(child.parent, parent)
}
function getPath(node, path) {
if (path == undefined) path = "";
if (node == root) return "/" + path;
var next = path == "" ? node.name : node.name + "/" + path;
return getPath(node.parent, next);
}
function serializeNode(node, result, parentId) {
if (result == undefined) result = {}
var id = generateUniqueId(result);
if (parentId != undefined) result[parentId].childs.push(id);
result[id] = Object.assign({}, node);
result[id].parent = parentId;
if (isdir(node)) {
var childs = node.childs;
result[id].childs = []
for (var key of Object.keys(childs))
serializeNode(childs[key], result, id)
}
return result
}
function generateUniqueId(obj) {
var id = s4() + '-' + s4();
while (id in obj) {
id = s4() + '-' + s4();
}
return id;
}
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
}
/** FILES TREE CONSTRUCTOR FROM SERIALIZED OBJECT */
function deserializeTree(obj) {
var rootNode = {}
for (var key of Object.keys(obj)) {
if (obj[key].name == "/") {
rootNode = obj[key];
break;
}
}
deserializeNode(rootNode, obj);
return filesTree(rootNode);
function deserializeNode(node, storage) {
if (node.parent != null) node.parent = storage[node.parent]
if (node.type == "d") {
var childs = node.childs;
node.childs = {}
for (var id of childs) {
var child = storage[id];
node.childs[child.name] = child;
deserializeNode(child, storage);
}
}
}
}

53
src/scripts/script.js Normal file
View file

@ -0,0 +1,53 @@
$(function () {
var cli = createCli();
var ctrl_down = false;
/** EVENT LISTENERS */
$(".cli-out").click(function () {
$("#main-inp input").focus();
});
$(".cli-out").keydown(function (e) {
// on ENTER
if (e.which == 13) {
var value = cli.getInputValue();
cli.addToHistory(value);
cmd(value);
}
// on CTRL
if (e.which == 17) ctrl_down = true;
// on L
if (e.which == 76 && ctrl_down) {
e.preventDefault();
cli.run("clear");
ctrl_down = false;
}
// on ARROW UP
if (e.which == 38) cli.historyUp();
// on ARROW DOWN
if (e.which == 40) cli.historyDown();
// on TAB
if(e.which == 9) {
e.preventDefault();
cli.autocomplete();
} else cli.resetTab();
});
$(".cli-out").keyup(function (e) {
// on CTRL UP
if (e.which == 17) ctrl_down = false;
});
/** FUNCTIONS */
function cmd(value) {
var seq = value.split(' ');
var command = seq[0];
var params = seq.splice(1);
if (cli.has(command))
cli.run(command, params);
else if (command == "")
cli.newLine();
else
cli.newLine("Command not found: " + command);
}
});

120
src/style.css Normal file
View file

@ -0,0 +1,120 @@
@import url('https://fonts.googleapis.com/css2?family=VT323&display=swap');
:root {
--bg-color: #171622;
--text-color: white;
--cmd-color: #1D1C29;
--cmd-hd-color: #343148;
--gray-color: #5D5B6D;
}
html, body {
padding: 0;
margin: 0;
height: 100vh;
}
body {
background-color: var(--bg-color);
color: var(--text-color);
font-family: 'VT323', monospace;
}
h1, h2, h3, p {
margin: 0;
word-break: break-all;
}
main {
height: 100%;
display: flex;
flex-direction: column;
}
main h1 {
text-align: center;
font-size: 5rem;
padding: 2rem 0rem;
}
.cli {
flex: 1;
display: flex;
flex-direction: column;
width: min(90%, 800px);
margin: 0rem auto 5rem;
border-radius: 15px;
background-color: gray;
background-color: var(--cmd-color);
overflow: hidden;
}
.cli-hd {
height: 30px;
background-color: var(--cmd-hd-color);
display: flex;
align-items: center;
padding-left: .5rem;
}
.circle {
background-color: var(--gray-color);
width: 10px;
height: 10px;
border-radius: 15px;
margin-left: .5rem;
}
.cli-out {
flex: 1;
overflow-y: auto;
overflow-anchor: auto;
}
.cli-out:hover {
cursor: text;
}
.cli-out > * {
margin: .5rem 0rem;
padding: 1rem;
}
.cli-inp {
display: flex;
padding: 0rem 1rem;
font-size: 1.5rem;
}
.cli-inp span {
color: greenyellow;
}
.cli-inp .route {
padding-right: .5rem;
}
.cli-inp input {
flex: 1;
background-color: transparent;
border: none;
color: white;
font-family: 'VT323', monospace;
font-size: 1.5rem;
}
input:focus {
outline: none;
}
.cmd-result {
font-size: 1.5rem;
padding: .5rem 2rem;
}
@media(max-width: 450px) {
main h1 {
font-size: 4rem;
padding: 1rem 0rem;
}
}