commit 9d414158e7306a4813fce7aa0c29f14ba4f85113 Author: b1ek Date: Sun Mar 19 12:30:29 2023 +1000 Move resume to external repo diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f21686d --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +node_modules +yarn.lock +package-lock.json +dist + +*.log +*.tmp diff --git a/package.json b/package.json new file mode 100644 index 0000000..9367b3f --- /dev/null +++ b/package.json @@ -0,0 +1,37 @@ +{ + "name": "resume", + "version": "1.0.0", + "main": "src/resume.js", + "source": "src/index.html", + "author": "blek", + "license": "MIT", + "devDependencies": { + "assert": "^2.0.0", + "events": "^3.1.0", + "parcel": "^2.8.3", + "parcel-namer-without-hash": "^0.0.1", + "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", + "copy-to-clipboard": "^3.3.3", + "file-saver": "^2.0.5", + "memfs": "^3.4.13", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "unionfs": "^4.4.0", + "xterm": "^5.1.0", + "xterm-for-react": "^1.0.4", + "xterm-js-shell": "^1.1.3" + }, + "scripts": { + "start": "parcel", + "build": "parcel build --no-source-maps" + } +} diff --git a/src/Console.js b/src/Console.js new file mode 100644 index 0000000..4dd40c8 --- /dev/null +++ b/src/Console.js @@ -0,0 +1,29 @@ +import React from 'react'; +import { XTerm } from 'xterm-for-react' + +export class Console extends React.Component { + constructor(props) { + super(props); + this.terminal = React.createRef(null); + } + + render() { + return
+ +
; + } + + componentDidMount() { + require('./emulator')(this.terminal.current); + this.terminal.current.terminal.focus(); + } +} \ No newline at end of file diff --git a/src/emulator/commands/cat.js b/src/emulator/commands/cat.js new file mode 100644 index 0000000..5301052 --- /dev/null +++ b/src/emulator/commands/cat.js @@ -0,0 +1,36 @@ +import { Terminal } from 'xterm'; +const fs = require('../fs'); + +/** + * + * @param { string[] } argv + * @param { Terminal } terminal + */ +module.exports = (argv, terminal) => { + if (argv.indexOf('--help') != -1) { + terminal.writeln(`Usage: ${argv[0]} [files] [-n]`); + terminal.writeln(' -n --number show lines numbers'); + terminal.writeln(' --help show this help'); + terminal.writeln('Read files into stdout'); + return; + } + const numbers = (argv.indexOf('-n') != -1) || (argv.indexOf('--number') != -1); + + let files = argv.filter(x => { return !x.startsWith('-') }); + files.shift(); + files.forEach(file => { + if (!fs.existsSync(file)) { + terminal.writeln(`${argv[0]}: ${file}: no such file or directory`); + return; + } + const lines = fs.readFileSync(file).toString().split('\n'); + + if (numbers) { + lines.forEach((line, i) => { + terminal.write('\033[35m' + i + ' |\033[0m '); + }) + } else { + terminal.write(lines.join('\n')); + } + }) +} \ No newline at end of file diff --git a/src/emulator/commands/cmds.js b/src/emulator/commands/cmds.js new file mode 100644 index 0000000..7fbbd63 --- /dev/null +++ b/src/emulator/commands/cmds.js @@ -0,0 +1,25 @@ +import { Terminal } from 'xterm'; + +/** + * + * @param {string[]} argv + * @param {Terminal} terminal + */ +module.exports = (argv, terminal) => { + if (argv.indexOf('--help') != -1) { + terminal.writeln(`Usage: ${argv[0]} [--help]`); + terminal.writeln('Lists all available commands.'); + return; + } + const cmds = Object.keys(require('../commands')); + let i = 0; + cmds.forEach(x => { + if (x == argv[0]) return; + if (i == 4) { + terminal.writeln(''); + i = 0; + } else i++; + terminal.write('\033[1;32m' + x + '\033[0m '); + }); + terminal.writeln(''); +} \ No newline at end of file diff --git a/src/emulator/commands/export_file.js b/src/emulator/commands/export_file.js new file mode 100644 index 0000000..2216896 --- /dev/null +++ b/src/emulator/commands/export_file.js @@ -0,0 +1,43 @@ +import { Terminal } from 'xterm'; +import { saveAs } from 'file-saver'; +const fs = require('../fs'); + +/** + * + * @param { string[] } argv + * @param { Terminal } terminal + */ +module.exports = (argv, terminal) => { + if (argv.indexOf('--help') != -1 || argv.length == 1) { + terminal.write( +`Usage: ${argv[0]} [FILES] [-z] +Export file from filesystem to your device. + -z Compress in zip format (not supported)`); + return; + } + + let files = [...argv]; + files.shift(); + + if (argv.indexOf('-z') != -1) { + terminal.write(`${argv[0]}: cannot zip '${files.join(' ').replace('\'', "\\'")}': not supported\n`); + return; + } + + try { + var isFileSaverSupported = !!new Blob; + } catch (e) {} + + if (!isFileSaverSupported) { + terminal.write(`${argv[0]}: Your browser does not support this feature.`); + return; + } + + if (!fs.existsSync(argv[1])) { + terminal.write(`${argv[0]}: cannot open ${argv[1]}: no such file or directory\n`); + return; + } + saveAs(new Blob([fs.readFileSync(argv[1])]), argv[1]); + + +} \ No newline at end of file diff --git a/src/emulator/commands/import_file.js b/src/emulator/commands/import_file.js new file mode 100644 index 0000000..6d80827 --- /dev/null +++ b/src/emulator/commands/import_file.js @@ -0,0 +1,37 @@ +import { Terminal } from 'xterm'; +import { saveAs } from 'file-saver'; +const fs = require('../fs'); + +/** + * + * @param { string[] } argv + * @param { Terminal } terminal + */ +module.exports = (argv, terminal) => { + + if (argv.indexOf('--help') != -1) { + terminal.write( +`Usage: ${argv[0]} [DESTANATION]\n +Import files from your system to this filesystem. +` + ); + return; + } + + let el = document.getElementById('upload_file_btn'); + if (el == null) { + el = document.createElement('input'); + el.style.display = 'none'; + el.type = 'file'; + el.id = 'upload_file_btn'; + el.setAttribute('multiple', 'multiple'); + document.body.appendChild(el); + } + + const dir = argv[1] || '.'; + + el.click(); + + let files = el.files; + global.f = files +} \ No newline at end of file diff --git a/src/emulator/commands/index.js b/src/emulator/commands/index.js new file mode 100644 index 0000000..4ab817b --- /dev/null +++ b/src/emulator/commands/index.js @@ -0,0 +1,18 @@ +let cmds = { + 'cat': require('./cat'), + 'cmds': require('./cmds'), + 'cmdls': require('./cmds'), + 'help': require('./cmds'), + 'ls': require('./ls'), + 'skills': require('./skills'), + 'mkdir': require('./mkdir'), + 'wget': require('./wget'), + 'export_file': require('./export_file'), + 'import_file': require('./import_file'), + 'zsh': require('./zsh'), + + // alias l='ls -l' + 'l': (a,t) => {require('./ls')([...a, '-l'], t)}, +}; + +module.exports = cmds; \ No newline at end of file diff --git a/src/emulator/commands/ls.js b/src/emulator/commands/ls.js new file mode 100644 index 0000000..be24307 --- /dev/null +++ b/src/emulator/commands/ls.js @@ -0,0 +1,67 @@ +const { Terminal } = require('xterm'); +const fs = require('../fs'); + +/** + * + * @param {string[]} argv + * @param {Terminal} terminal + */ +module.exports = (argv, terminal) => { + if (argv.indexOf('--help') != -1) { + terminal.writeln(`Usage: ${argv[0]} [dirs] [-a|--all] [-l]`); + terminal.writeln('Lists files in directories'); + terminal.writeln(' -a --all List all files (including those who start with .)'); + terminal.writeln(' -l Use long listing format'); + return; + } + + const has_arg = (arg) => {return argv.indexOf(arg) != -1}; + + let directories = [...argv]; + + const all = (has_arg('-a') || has_arg('--all')); + const long_format = has_arg('-l'); + + directories.shift(); + + // remove .* files if -a not specified + if (!all) + directories = directories.filter(x => !x.startsWith('.')); + + // remove arguments + directories = directories.filter(x => !x.startsWith('-')); + + if (directories.length == 0) directories = ['.']; + + // remove dublicates + directories = [...new Set(directories)]; + + directories.forEach((dir, i) => { + + if (!fs.lstatSync(dir).isDirectory()) { + terminal.write(dir); + return; + } + + if (directories.length != 1) { + terminal.writeln(dir + ':'); + terminal.writeln(''); + } + + if (!fs.existsSync(dir)) { + terminal.writeln(`${argv[0]}: cannot access '${dir}': No such file or directory`); + return; + } + let files = fs.readdirSync(dir); + files.forEach((file, i) => { + + if (!long_format) + terminal.write(file + '\033[0m '); + else + terminal.writeln('drwx-xr-x 1 nobody nobody 4.0K Jan 1 13 01:00 ' + file); + if ((i+1) % 5 == 0) + terminal.writeln(''); + }); + }) + terminal.writeln(''); +} \ No newline at end of file diff --git a/src/emulator/commands/mkdir.js b/src/emulator/commands/mkdir.js new file mode 100644 index 0000000..20d6873 --- /dev/null +++ b/src/emulator/commands/mkdir.js @@ -0,0 +1,26 @@ +import { Terminal } from 'xterm'; +const fs = require('../fs'); + +/** + * @param {string[]} argv + * @param {Terminal} terminal + */ +module.exports = (argv, terminal) => { + if (argv.indexOf('--help') != -1) { + terminal.write(` +Usage: ${argv[0]} [DIRECTORY] [-p] +Create a directory + -p Create parent directories +`); + return; + } + + const parents = argv.indexOf('-p') != -1; + const dir = argv[1]; + + try { + fs.mkdirSync(dir, {recursive: parents}); + } catch (err) { + terminal.write(`${argv[0]}: can't create ${dir}: ${err}`); + } +} \ No newline at end of file diff --git a/src/emulator/commands/skills.js b/src/emulator/commands/skills.js new file mode 100644 index 0000000..ba583d3 --- /dev/null +++ b/src/emulator/commands/skills.js @@ -0,0 +1,34 @@ +const fs = require('../fs'); + +module.exports = (argv, terminal) => { + + if (argv.indexOf('--help') != -1) { + terminal.writeln(`Usage: ${argv[0]}\nShows my skills info`); + return; + } + + if (argv[1] != undefined) { + const file = 'skills/' + argv[1]; + if (!fs.existsSync(file)) + return; + terminal.write(fs.readFileSync(file)); + return; + } + + terminal.write(` +My skills + +\x1b[1;32m[#####] 100% Web dev (fullstack)\x1b[0m +I like doing backend mostly, but sometimes i do +frontend projects like this one. +Learn more: \x1b[4;32mskills web\x1b[0m + +\x1b[1;33m[## ] 40% Native C/C++/Rust\x1b[0m +Not really my zone of work but its really fun to +make something with these in spare time. +Learn more: \x1b[4;32mskills nt\x1b[0m + +\x1b[1;38:5:244m[ ] 0% Being cis\x1b[0m +Never liked it +`); +} \ No newline at end of file diff --git a/src/emulator/commands/wget.js b/src/emulator/commands/wget.js new file mode 100644 index 0000000..0139d3b --- /dev/null +++ b/src/emulator/commands/wget.js @@ -0,0 +1,66 @@ +import { Terminal } from 'xterm'; +const fs = require('../fs'); + +/** + * @param {string[]} argv + * @param {Terminal} terminal + */ +module.exports = async (argv, terminal) => { + if (argv.indexOf('--help') != -1) { + terminal.write(` +Usage: ${argv[0]} [URL] [-O out.file] [-V] [-Q] [-s] + --help Show this help + -O [FILE] Specify output file + -V Verbose mode + -Q Quiet mode (default) +`); + return; + } + + const url = argv[1]; + + let filepath = url.split('/')[url.split('/').length - 1]; + + if (argv.indexOf('-O') != -1) { + if (argv.indexOf('-O') + 1 == argv.length) { + terminal.writeln(`${argv[0]}: missing output file`); + return; + } + filepath = argv[argv.indexOf('-O') + 1]; + } + + function progress(p) { + let total = p.total; + + if (total == 0) + total = '?' + // terminal.write(`\rDownloading... ${p.loaded}/${total}`); + } + + function write(file) { + if (filepath == '-') { + terminal.write(file); + } else { + fs.writeFileSync(filepath, new Buffer(file)); + } + } + + let file; + + let req = new XMLHttpRequest(); + req.open('GET', 'http://cors.blek.codes/' + url, true); + req.responseType = 'arraybuffer'; + req.onprogress = progress; + req.onload = (e) => { + if (e.type == 'load') { + write(req.response); + } + }; + req.send(); + + const delay = t => new Promise(resolve => setTimeout(resolve, t)); + while(req.readyState == 1) await delay(250); + + + return; +} \ No newline at end of file diff --git a/src/emulator/commands/zsh.js b/src/emulator/commands/zsh.js new file mode 100644 index 0000000..4f91eb0 --- /dev/null +++ b/src/emulator/commands/zsh.js @@ -0,0 +1,8 @@ +const { Terminal } = require("xterm"); + +/** + * @param {Terminal} terminal + */ +module.exports = (argv, terminal) => { + window.location.href = 'https://youtu.be/dQw4w9WgXcQ?t=3' +} \ No newline at end of file diff --git a/src/emulator/files.js b/src/emulator/files.js new file mode 100644 index 0000000..b3d510b --- /dev/null +++ b/src/emulator/files.js @@ -0,0 +1,22 @@ +module.exports = { + '/skills/web': ` +\x1b[1mMy web experience\x1b[0m +I don't really know anything about web development, +but I have some projects which are presented below: + +blek! Site: Second rewrite of my website. https://github.com/b1ek/blekSite +blek! ID: An auth server. https://github.com/b1ek/blekID +blek! Bin: A pastebin alternative. https://git.blek.codes/blek/bin +homepage.js: Third rewrite of my website. https://git.blek.codes/blek/homepage.js +`, + '/skills/nt': ` +Me native dev experience +It isn't much but i do have a couple of projects +(most of my native work is toy projects) + +CuteSchedule A school project that is + supposed to be a interactive https://github.com/b1ek/CuteSchedule + schedule menu hanging on + a TV +f2bin Convert files into C code https://github.com/b1ek/f2bin` +} \ No newline at end of file diff --git a/src/emulator/fs.js b/src/emulator/fs.js new file mode 100644 index 0000000..740489d --- /dev/null +++ b/src/emulator/fs.js @@ -0,0 +1,8 @@ +const { fs, vol } = require('memfs'); +fs.writeFileSync('README.md', 'uwu'); +const { ufs } = require('unionfs'); + +ufs.use(fs).use(vol.fromJSON(require('./files'))); +ufs.constants = fs.constants; + +module.exports = ufs; \ No newline at end of file diff --git a/src/emulator/index.js b/src/emulator/index.js new file mode 100644 index 0000000..c63a4fd --- /dev/null +++ b/src/emulator/index.js @@ -0,0 +1,11 @@ +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('Use \033[1;33mAlt+C/V\033[0m to copy or paste'); + terminal.writeln(''); + + require('./zsh')(terminal, dom); + require('./pastebuffer')(terminal, dom); +} \ No newline at end of file diff --git a/src/emulator/pastebuffer.js b/src/emulator/pastebuffer.js new file mode 100644 index 0000000..bb4d033 --- /dev/null +++ b/src/emulator/pastebuffer.js @@ -0,0 +1,37 @@ +import { Terminal } from "xterm" +import copy from 'copy-to-clipboard'; + +/** @type { Terminal } */ +let terminal; +const zsh = require('./zsh'); + +/** + * + * @param {{ key: string, domEvent: KeyboardEvent }} e + */ +async function keyHandler(e) { + const dom = e.domEvent; + + if (!dom.altKey) + return; + + if (dom.key.length != 1) + return; + + switch (dom.key.toLowerCase()) { + case 'c': + if (!terminal.hasSelection()) break; + + copy(terminal.getSelection()) + break; + case 'v': + zsh.pr_char(prompt("Paste your text:"), {key: 'a'}) + + } +} + +module.exports = (t, d) => { + terminal = t; + terminal.onKey(keyHandler); + +} \ No newline at end of file diff --git a/src/emulator/zsh.js b/src/emulator/zsh.js new file mode 100644 index 0000000..9c03b7a --- /dev/null +++ b/src/emulator/zsh.js @@ -0,0 +1,230 @@ + +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 lastcmd = window.sessionStorage.getItem('last_cmd') || ''; + +function text_prompt() { + return prompt.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, ''); +} + +/** + * + * @param { string } char + * @param { KeyboardEvent } dom + */ +function pr_char(char, dom) { + if (dom.key.length != 1) return; + + cmd += char; + terminal.write(char); +} + +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; +} + +async function exec_cmd() { + let c = cmd; + const command = c.split(' ')[0]; + reset_cmd(c); + lastcmd = c; + window.sessionStorage.setItem('last_cmd', c); + + if (command == '') { + print_prompt(); + return; + } + + // if path + if (command.match(/^((\.|\.\.)\/|\/).+$/gm)) { + exec_file(command); + terminal.writeln(''); + print_prompt(); + return; + } + + if (cmds[command] != undefined) { + const startY = terminal.buffer.normal.cursorY + + await cmds[command](c.split(' '), terminal); + + await (new Promise(resolve => setTimeout(resolve, 10))); + + if (terminal.buffer.active.cursorX != 0 && startY != terminal.buffer.active.cursorY) { + terminal.write('\033[30;47m%\033[0m\n'); + } + print_prompt(); + return; + } + + terminal.writeln('zsh: command not found: ' + command); + print_prompt(); +} + +function print_prompt() { + terminal.write(prompt); +} + +function reprint_prompt() { + terminal.write('\033[2K\r'); + print_prompt(); +} + +function reset_cmd() { + cmd = ''; + terminal.writeln(''); +} + +function cbackspace() { + let exploded = cmd.substring(0, cmd.length - 2).split(' '); + + if (exploded.length == 1) { + reprint_prompt(); + cmd = ''; + return; + } + exploded.pop(); + + cmd = exploded.join(' ') + ' '; + reprint_prompt(); + terminal.write(cmd); + return; + +} + +function backspace(isCtrl) { + if (terminal.buffer.active.cursorX <= text_prompt().length) return; + + if (isCtrl) { + return cbackspace(); + } + + terminal.write('\b \b'); + cmd = cmd.substring(0, cmd.length - 1); +} + +/** @param { KeyboardEvent } dom */ +async function control_char(id, dom) { + + switch (id) { + + // backspace + case 8: + backspace(dom.ctrlKey); + break; + + // enter + case 13: + exec_cmd(); + break; + + case 38: + if (lastcmd == '') break; + cmd = lastcmd; + reprint_prompt(); + terminal.write(lastcmd); + break; + + // Ctrl+c + case 67: + if (dom.ctrlKey) { + terminal.write('^C'); + reset_cmd(); + print_prompt(); + break; + } + + case 86: + if (dom.altKey) break; + + case 82: + // Why it checks if letter is 'r': + // For some reason, ctrl+(v|r|d) executed this code. + // This is a simple fix + // (same works for any other code like this in this function) + if (dom.ctrlKey && dom.key.toLowerCase() == 'r') { + if (Math.random() < 0.1) terminal.write('uwu'); + window.location.reload(); + break; + } + + case 68: + if (dom.ctrlKey && dom.key.toLowerCase() == 'd') { + terminal.writeln(''); + pr_char = () => {}; + exec_cmd = pr_char; + control_char = () => {}; + break; + } + + default: + const wr = (t) => { + terminal.write(t); + cmd += t; + } + if (dom.ctrlKey && (dom.key.length == 1)) { + wr('^' + dom.key.toUpperCase()); + break; + } + + wr('<'); + if (dom.ctrlKey) wr('C'); + if (dom.altKey) wr('A'); + if (dom.shiftKey) wr('S'); + wr(`${id}>`) + break; + } +} + +function key(e) { + /** @type {KeyboardEvent} */ + const dom = e.domEvent; + if (dom.key.length == 1 && !(dom.ctrlKey || dom.altKey)) { + pr_char(e.domEvent.key, dom); + } else { + control_char(e.domEvent.keyCode, dom) + } +} + +function register(t, d) { + terminal = t; + dom = d; + + terminal.onKey(key); + terminal.write(prompt); +} + +register.pr_char = pr_char; + +module.exports = register; \ No newline at end of file diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..3f231d9 --- /dev/null +++ b/src/index.html @@ -0,0 +1,13 @@ + + + + resume.js + + + + + +
+ + + \ No newline at end of file diff --git a/src/resume.css b/src/resume.css new file mode 100644 index 0000000..02ba993 --- /dev/null +++ b/src/resume.css @@ -0,0 +1,43 @@ +div#resume_js_app { + background: #212121; + width:1000px; + height:800px; + border: 1px solid #e1e1e1; + border-radius: 6px; + font-family: monospace; + box-shadow: 0 2px 1px #303030A0; + color: #e1e1e1 !important; + padding: 8px 2px; + transition: 150ms ease; +} +div#resume_js_app:hover { + box-shadow: 0 2px 3px #303030; +} +div#resume_js_app p.js_loading_indicator { + padding: 0; margin: 0; + position: relative; + top: 50%; left: 50%; + transform: translate(-50%, -50%); + width: fit-content; + text-align: center; +} + + +div#resume_js_app * { + color: #e1e1e1; + font-family: 'Source Code Pro', monospace !important; +} +div#resume_js_app a, div#resume_js_app a:visited { + color: rgb(36, 85, 126) !important; + text-decoration: none; + font-weight: 500; + transition: 150ms ease; + text-shadow: 0 0 0px #24557e30; +} +div#resume_js_app a:hover { + text-shadow: 0 0 4px #24557e30; +} +div#resume_js_app table * { + border:0; + text-align: left; +} \ No newline at end of file diff --git a/src/resume.html b/src/resume.html new file mode 100644 index 0000000..8d9138c --- /dev/null +++ b/src/resume.html @@ -0,0 +1,13 @@ + + + + resume.js + + + + + +
+ + + \ No newline at end of file diff --git a/src/resume.js b/src/resume.js new file mode 100644 index 0000000..d648c03 --- /dev/null +++ b/src/resume.js @@ -0,0 +1,5 @@ +import { Console } from './Console'; +import ReactDOM from 'react-dom/client'; + +const root = ReactDOM.createRoot(document.getElementById('resume_js_app')); +root.render() \ No newline at end of file diff --git a/src/rootstyle.css b/src/rootstyle.css new file mode 120000 index 0000000..283be1b --- /dev/null +++ b/src/rootstyle.css @@ -0,0 +1 @@ +../../../public/static/ui/resume.css \ No newline at end of file