diff --git a/public/assets/fonts/JetBrainsMonoNerdFontPropo-Regular.ttf b/public/assets/fonts/JetBrainsMonoNerdFontPropo-Regular.ttf new file mode 100644 index 00000000..eb0dc2e3 Binary files /dev/null and b/public/assets/fonts/JetBrainsMonoNerdFontPropo-Regular.ttf differ diff --git a/public/builtin/apps/scripts/terminal.js b/public/builtin/apps/scripts/terminal.js index f9baf7dc..7454ac0e 100644 --- a/public/builtin/apps/scripts/terminal.js +++ b/public/builtin/apps/scripts/terminal.js @@ -5,6 +5,11 @@ import 'https://cdn.jsdelivr.net/npm/xterm@5.2.1/lib/xterm.min.js'; import FitAddon from 'https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.7.0/+esm'; import WebLinksAddon from 'https://cdn.jsdelivr.net/npm/xterm-addon-web-links@0.8.0/+esm'; +import CanvasAddon from 'https://cdn.jsdelivr.net/npm/xterm-addon-canvas@0.4.0/+esm'; +import FontFaceObserver from 'https://cdn.jsdelivr.net/npm/fontfaceobserver@2.3.0/+esm'; + +import { loadCSS } from '/scripts/utilities.js'; +import { config } from '/scripts/managers.js'; import { CommandsAddon } from './terminal/handler.js'; @@ -12,38 +17,74 @@ import { _auth } from '../../../scripts/firebase.js'; import * as auth from 'https://www.gstatic.com/firebasejs/10.0.0/firebase-auth.js'; const commands = { - 'cd': './terminal/commands/cd.js' + cd: './terminal/commands/cd.js', }; -const term = new Terminal({ - cursorBlink: true, - fontFamily: 'JetBrains Mono Nerd Font, monospace', - letterSpacing: '1', - allowTransparency: true -}); - -auth.onAuthStateChanged(_auth, (user) => { - if (user) { - let dir = { - path: '/', - set: (str) => { - dir.path = str; - } - }; +window.loadCSS = (FILE_URL, callback) => { + const styleEle = document.createElement('link'); - let username; - if (_auth.currentUser.displayName !== null) username = _auth.currentUser.displayName.toLowerCase().replaceAll(' ', '-'); - else username = 'guest'; - - const usr = { username }; + styleEle.setAttribute('rel', 'stylesheet'); + styleEle.setAttribute('type', 'text/css'); + styleEle.setAttribute('href', FILE_URL); + + document.head.appendChild(styleEle); - const fitAddon = new FitAddon.FitAddon(); + styleEle.addEventListener('load', () => { + if (typeof callback == 'function') + callback(); - term.loadAddon(fitAddon); - term.loadAddon(new WebLinksAddon.WebLinksAddon()); - term.loadAddon(new CommandsAddon({ commands }, usr, dir)); + }); +}; + +window.addEventListener('load', () => { + window.loadCSS(config.settings.get('theme').url, () => { + const term = new Terminal({ + cursorBlink: true, + fontFamily: 'JetBrains Mono Nerd Font, monospace', + fontSize: '15', + lineHeight: '1.1', + letterSpacing: '1.4', + allowTransparency: true, + theme: { + background: getComputedStyle(document.querySelector(':root')).getPropertyValue('--desktop-bg'), + foreground: getComputedStyle(document.querySelector(':root')).getPropertyValue('--text-color'), + } + }); - term.open(document.getElementById('terminal')); - fitAddon.fit(); - } + auth.onAuthStateChanged(_auth, (user) => { + if (user) { + let dir = { + path: '/', + set: (str) => { + dir.path = str; + }, + }; + + let username; + if (_auth.currentUser.displayName !== null) + username = _auth.currentUser.displayName + .toLowerCase() + .replaceAll(' ', '-'); + else username = 'guest'; + + const usr = { username }; + + const fitAddon = new FitAddon.FitAddon(); + + term.loadAddon(fitAddon); + term.loadAddon(new WebLinksAddon.WebLinksAddon()); + term.loadAddon(new CanvasAddon.CanvasAddon()); + term.loadAddon(new CommandsAddon({ commands }, usr, dir)); + + const font = new FontFaceObserver('JetBrains Mono Nerd Font'); + font.load().then(() => { + term.open(document.getElementById('terminal')); + }, () => { + window.location.reload(); + }); + + fitAddon.fit(); + } + }); + }); }); \ No newline at end of file diff --git a/public/builtin/apps/scripts/terminal/commands/git.js b/public/builtin/apps/scripts/terminal/commands/git.js index a187aecb..68dfa1e0 100644 --- a/public/builtin/apps/scripts/terminal/commands/git.js +++ b/public/builtin/apps/scripts/terminal/commands/git.js @@ -9,20 +9,104 @@ export const metadata = { }; export const exec = async (fs, term, usr, dir, args) => { + const options = args.map(arg => { + if (arg.startsWith('--') && arg.includes('=') == false) return arg; + if (arg.startsWith('-') && arg.includes('=') == false) return arg; + }).filter((element) => element !== undefined); + + const vars = {}; + + args.forEach((arg) => { + if (arg.startsWith('--') && arg.includes('=')) { + vars[arg.split('=')[0].replace('--', '')] = arg.split('=')[1]; + }; + }); + + args.shift(); + const values = args.map(arg => { + if (!arg.startsWith('-')) return arg; + }).filter((element) => element !== undefined); + + console.log(options, values, vars); + return new Promise(async (resolve) => { - if (args[1] == 'clone') { - term.writeln(`Cloning into '${args[2].split(/(\\|\/)/g).pop()}'...`); + if (values[0] == 'clone') { + let cfg = { + quiet: false, + depth: 1, + noCheckout: false, + noTags: false, + singleBranch: true, + }; + + if (options.includes('-q') || options.includes('--quiet')) cfg.quiet = true; + if (options.includes('-n') || options.includes('--no-checkout')) cfg.noCheckout = true; + if (options.includes('--no-tags')) cfg.noTags = true; + if (options.includes('--single-branch')) cfg.singleBranch = true; + if (options.includes('--no-single-branch')) cfg.singleBranch = false; + + if (vars['depth']) cfg.depth = vars['depth']; + if (vars['shallow-since']) cfg.since = new Date(vars['shallow-since']); + if (vars['shallow-exclude']) cfg.exclude = vars['shallow-exclude']; + + if (cfg.quiet !== true) term.writeln(`Cloning into '${values[1].split(/(\\|\/)/g).pop()}'...`); + await git.clone({ fs, http, - dir: dir.path + '/' + args[2].split(/(\\|\/)/g).pop(), + dir: values[2] ?? dir.path + '/' + values[1].split(/(\\|\/)/g).pop(), corsProxy: 'https://cors.isomorphic-git.org', - url: args[2], - singleBranch: true, - depth: 1, - onMessage: (e) => term.writeln(e), + url: values[1], + noCheckout: cfg.noCheckout, + singleBranch: cfg.singleBranch, + depth: cfg.depth, + noTags: cfg.noTags, + since: cfg.since, + exclude: cfg.exclude, + onMessage: (e) => { + if (cfg.quiet !== true) term.writeln(e); + }, }); resolve(''); + } else if (values[0] == 'init') { + let cfg = { + quiet: false, + bare: false, + defaultBranch: 'master', + }; + + if (options.includes('-q') || options.includes('--quiet')) cfg.quiet = true; + if (options.includes('--bare')) cfg.bare = true; + + if (vars['initial-branch']) cfg.defaultBranch = vars['initial-branch']; + if (vars['separate-git-dir']) cfg.gitdir = vars['separate-git-dir']; + + await git.init({ + fs, + http, + dir: values[1] ?? dir.path, + bare: cfg.bare, + defaultBranch: cfg.defaultBranch, + gitdir: cfg.gitdir + }); + + if (cfg.quiet !== true) term.writeln(`Initialized empty Git repository in ${fs.realpathSync(values[1]) ?? dir.path}/.git/`); + + resolve(''); + } else { + if (options[0] == '--version') { + resolve('git version ' + git.version()); + } else if (options[0] == '--help') { + resolve([ + `usage: git [--version] [--help] []`, + ``, + `These are common Git commands used in various situations:`, + ``, + `start a working area`, + ` clone Clone a repository into a new directory`, + ` init Create an empty Git repository or reinitialize an existing one` + ]); + } } }); }; \ No newline at end of file diff --git a/public/builtin/apps/scripts/terminal/handler.js b/public/builtin/apps/scripts/terminal/handler.js index 9643214f..34704ca7 100644 --- a/public/builtin/apps/scripts/terminal/handler.js +++ b/public/builtin/apps/scripts/terminal/handler.js @@ -7,7 +7,13 @@ import sh from 'https://cdn.jsdelivr.net/npm/shell-quote@1.8.1/+esm'; export class CommandsAddon { _disposables = []; - promptMSG = async () => `${c.blue('')}${c.bgBlue(` flow@${this.user.username}`)}${c.bgCyan(c.blue('') + ` ${this.dir.path}`)}${c.cyan('')} `; + promptMSG = async () => { + if (await this.fs.readdirSync(this.dir.path).includes('.git')) { + return `${c.green('')}${c.bgGreen(` flow@${this.user.username}`)}${c.bgCyan(c.green('') + ` ${this.fs.realpathSync(this.dir.path)}`)}${c.bgBlue(c.cyan(' '))}${c.bgBlue(`󰓁 ${this.fs.readFileSync(this.dir.path + '/.git/HEAD').toString().replaceAll('\n', '').split('ref: refs/heads/')[1]}`)}${c.blue('')} `; + } else { + return `${c.green('')}${c.bgGreen(` flow@${this.user.username}`)}${c.bgCyan(c.green('') + ` ${this.fs.realpathSync(this.dir.path)}`)}${c.cyan('')} `; + } + }; constructor(options, usr, dir) { this.commandsMap = options.commands; @@ -18,6 +24,11 @@ export class CommandsAddon { this.current = ''; BrowserFS.install(window); + } + + activate(term) { + this.terminal = term; + BrowserFS.configure({ fs: 'AsyncMirror', options: { @@ -32,20 +43,15 @@ export class CommandsAddon { if (e) console.error(e); this.fs = require('fs'); - this.promptMSG = async () => `${c.blue('')}${c.bgBlue(` flow@${this.user.username}`)}${c.bgCyan(c.blue('') + ` ${this.fs.realpathSync(this.dir.path)}`)}${c.cyan('')} `; + + this.terminal.prompt = async () => { + this.terminal.write('\r' + await this.promptMSG()); + }; + + this.terminal.writeln('Welcome to FluSH!'); + this.terminal.writeln(''); + this.terminal.prompt(); }); - } - - activate(term) { - this.terminal = term; - - this.terminal.prompt = async () => { - this.terminal.write('\r' + await this.promptMSG()); - }; - - this.terminal.writeln('Welcome to FluSH!'); - this.terminal.writeln(''); - this.terminal.prompt(); this.terminal.attachCustomKeyEventHandler(async (key) => { if (key.code === 'KeyV' && key.ctrlKey && key.type === 'keydown') { diff --git a/public/builtin/apps/terminal.html b/public/builtin/apps/terminal.html index 97d8be14..0ada6d27 100644 --- a/public/builtin/apps/terminal.html +++ b/public/builtin/apps/terminal.html @@ -9,7 +9,7 @@
- +