commands api and filesystem for resume app

This commit is contained in:
b1ek 2023-03-15 15:58:14 +10:00
parent 0bfaf89c9b
commit 2e033026f0
Signed by: blek
GPG Key ID: 14546221E3595D0C
11 changed files with 186 additions and 45 deletions

View File

@ -1,2 +1,3 @@
.parcel-cache
parcel-bundle-reports
dist

View File

@ -6,17 +6,29 @@
"author": "blek",
"license": "MIT",
"devDependencies": {
"assert": "^2.0.0",
"events": "^3.1.0",
"parcel": "^2.8.3",
"parcel-namer-without-hash": "^0.0.1",
"process": "^0.11.10"
"path-browserify": "^1.0.0",
"process": "^0.11.10",
"punycode": "^1.4.1",
"querystring-es3": "^0.2.1",
"stream-browserify": "^3.0.0",
"url": "^0.11.0",
"util": "^0.12.3"
},
"dependencies": {
"@parcel/fs": "^2.8.3",
"memfs": "^3.4.13",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"xterm": "^5.1.0",
"xterm-for-react": "^1.0.4",
"xterm-js-shell": "^1.1.3"
},
"scripts": {
"start": "parcel"
"start": "parcel",
"build": "parcel build --no-source-maps"
}
}

View File

@ -22,7 +22,7 @@ export class Console extends React.Component {
}
componentDidMount() {
require('./emulator')(this.terminal.current.terminal);
require('./emulator')(this.terminal.current);
this.terminal.current.terminal.focus();
}
}

View File

@ -0,0 +1,5 @@
const fs = require('fs');
module.exports = (argv, terminal) => {
terminal.writeln('hi')
}

View File

@ -0,0 +1,5 @@
let cmds = {
'cat': require('./cat')
};
module.exports = cmds;

View File

@ -0,0 +1,4 @@
const { fs } = require('memfs');
fs.writeFileSync('README.md', 'uwu');
module.exports = fs;

View File

@ -1,8 +1,9 @@
module.exports = (terminal) => {
module.exports = (dom) => {
const terminal = dom.terminal;
terminal.writeln('Welcome to my online resume!')
terminal.writeln('Type \033[1;32mhelp\033[0m for list of commands')
terminal.writeln('');
require('./zsh')(terminal);
require('./zsh')(terminal, dom);
}

View File

@ -1,15 +1,23 @@
import { Terminal } from 'xterm';
import { XTerm } from 'xterm-for-react';
const fs = require('./fs');
global.fs = fs;
const cmds = require('./commands');
/**
* @type {Terminal}
*/
let terminal;
/**
* @type { XTerm }
*/
let dom;
const prompt = '\033[1;32muser@blek.codes \033[36m~ $ \033[0m';
let cmd = '';
let history = [];
let history_pos = 0;
function text_prompt() {
return prompt.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '');
@ -18,17 +26,51 @@ function text_prompt() {
function pr_char(char) {
cmd += char;
terminal.write(char);
// console.log(char.charCodeAt(0));
if (history_pos != 0) history_pos = 0;
}
function exec_file(f) {
const exists = fs.existsSync(f);
if (!exists) {
terminal.write('zsh: no such file or directory: ' + f);
return;
}
const executable = fs.accessSync(f, fs.constants.X_OK);
if (!executable) {
terminal.writeln('zsh: permission denied: ' + f);
return;
}
terminal.writeln('This is an online resume. It is not big enough to have a script runtime.\n');
return;
}
function exec_cmd() {
let c = cmd;
reset_cmd();
history.unshift(c);
reset_cmd(c);
if (c == '') {
print_prompt();
return;
}
// if path
if (c.match(/^((\.|\.\.)\/|\/).+$/gm)) {
exec_file(c);
terminal.writeln('');
print_prompt();
return;
}
if (cmds[c] != undefined) {
cmds[c](c.split(' '), terminal);
print_prompt();
return;
}
terminal.writeln('zsh: command not found: ' + c);
print_prompt();
history_pos = 0;
}
function print_prompt() {
@ -40,28 +82,25 @@ function reprint_prompt() {
print_prompt();
}
function history_up() {
if (history_pos != history.length) {
reprint_prompt();
terminal.write(history[history_pos]);
history_pos++;
}
}
function reset_cmd() {
cmd = '';
terminal.writeln('');
}
function control_char(char) {
const id = char.codePointAt(0);
/** @param { KeyboardEvent } dom */
function control_char(id, dom) {
const backspace = () => {
if (terminal.buffer.active.cursorX <= text_prompt().length) return;
terminal.write('\b \b');
cmd = cmd.substring(0, cmd.length - 1);
}
switch (id) {
// backspace
case 127:
if (terminal.buffer.active.cursorX <= text_prompt().length) break;
terminal.write('\b \b');
cmd = cmd.substring(0, cmd.length - 1);
case 8:
backspace();
break;
// enter
@ -70,33 +109,43 @@ function control_char(char) {
break;
// Ctrl+c
case 3:
terminal.write('^C');
reset_cmd();
print_prompt();
case 67:
if (dom.ctrlKey) {
terminal.write('^C');
reset_cmd();
print_prompt();
break;
}
case 37:
backspace();
break;
// history up
case 27:
history_up();
break;
default:
console.log('Unknown special char: ' + id);
terminal.write('<');
if (dom.ctrlKey) terminal.write('C');
if (dom.altKey) terminal.write('A');
if (dom.shiftKey) terminal.write('S');
terminal.write(`${id}>`)
break;
}
}
function key(e) {
if (RegExp(/^\p{L}/,'u').test(e.key)) {
pr_char(e.key);
/** @type {KeyboardEvent} */
const dom = e.domEvent;
if (dom.key.length == 1 && !(dom.ctrlKey || dom.altKey || dom.shiftKey)) {
pr_char(e.domEvent.key);
} else {
control_char(e.key);
control_char(e.domEvent.keyCode, dom)
}
}
module.exports = (t) => {
module.exports = (t, d) => {
terminal = t;
dom = d;
terminal.onKey(key);
terminal.write(prompt);
terminal.write(prompt);
}

View File

@ -0,0 +1,52 @@
div#resume_js_app {
width: 800px;
height: 600px;
background: #212121;
border: 1px solid #e1e1e1;
border-radius: 6px;
padding: 2px;
font-family: monospace;
transition: all .15s;
box-shadow: 0 2px 1px #303030a0;
color: #e1e1e1 !important;
}
div#resume_js_app:hover {
box-shadow: 0 2px 3px #303030;
}
div#resume_js_app p.js_loading_indicator {
width: -moz-fit-content;
width: fit-content;
text-align: center;
margin: 0;
padding: 0;
position: relative;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
div#resume_js_app * {
color: #e1e1e1;
font-family: Source Code Pro, monospace !important;
}
div#resume_js_app a, div#resume_js_app a:visited {
text-shadow: 0 0 #24557e30;
font-weight: 500;
text-decoration: none;
transition: all .15s;
color: #24557e !important;
}
div#resume_js_app a:hover {
text-shadow: 0 0 4px #24557e30;
}
div#resume_js_app table * {
text-align: left;
border: 0;
}
/*# sourceMappingURL=resume.css.map */

View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<title>resume.js</title>
<meta charset="utf-8">
<link rel="stylesheet" href="/resume.css">
</head>
<body>
<div id="resume_js_app"></div>
<script src="/resume.js" type="module"></script>
</body>
</html>

View File

@ -1,6 +1,5 @@
import { createRoot } from "react-dom/client";
import { Console } from "./Console";
import { Console } from './Console';
import ReactDOM from 'react-dom/client';
const container = document.getElementById("resume_js_app");
const root = createRoot(container)
root.render(<Console />);
const root = ReactDOM.createRoot(document.getElementById('resume_js_app'));
root.render(<Console />)