chore: make webfs usable
This commit is contained in:
@@ -4,11 +4,6 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
|
||||||
<!-- google fonts -->
|
|
||||||
<!--<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&display=swap" rel="stylesheet" />-->
|
|
||||||
|
|
||||||
<!-- import main stylesheet -->
|
<!-- import main stylesheet -->
|
||||||
<link rel="stylesheet" href="/src/styles/app.scss" />
|
<link rel="stylesheet" href="/src/styles/app.scss" />
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ request.onerror = _ => {
|
|||||||
"webfs error",
|
"webfs error",
|
||||||
"Failed to initialize the webfs database using IndexedDB. Make sure webshell does not have any IndexedDB related permissions disabled and try again.",
|
"Failed to initialize the webfs database using IndexedDB. Make sure webshell does not have any IndexedDB related permissions disabled and try again.",
|
||||||
'critical',
|
'critical',
|
||||||
).Show(-1)
|
).Show()
|
||||||
}
|
}
|
||||||
|
|
||||||
request.onsuccess = event => {
|
request.onsuccess = event => {
|
||||||
|
|||||||
@@ -212,8 +212,23 @@ export class Item {
|
|||||||
|
|
||||||
private normalizePath(path: string): string {
|
private normalizePath(path: string): string {
|
||||||
if (!path.startsWith('/')) path = '/' + path
|
if (!path.startsWith('/')) path = '/' + path
|
||||||
if (path.length > 1 && path.endsWith('/')) path = path.slice(0, -1)
|
|
||||||
return path
|
// Resolve . and .. segments.
|
||||||
|
// Split on '/' and walk each part: skip empty strings (from leading
|
||||||
|
// slash or consecutive slashes) and '.', pop on '..', push otherwise.
|
||||||
|
const parts = path.split('/')
|
||||||
|
const resolved: string[] = []
|
||||||
|
for (const part of parts) {
|
||||||
|
if (part === '' || part === '.') {
|
||||||
|
continue
|
||||||
|
} else if (part === '..') {
|
||||||
|
if (resolved.length > 0) resolved.pop() // never climbs above root
|
||||||
|
} else {
|
||||||
|
resolved.push(part)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '/' + resolved.join('/')
|
||||||
}
|
}
|
||||||
|
|
||||||
private getParentPath(): string {
|
private getParentPath(): string {
|
||||||
|
|||||||
@@ -3,10 +3,6 @@ import type { SimpleStream } from '../utils/SimpleStream'
|
|||||||
import { Program } from './Program'
|
import { Program } from './Program'
|
||||||
|
|
||||||
export class Cat extends Program {
|
export class Cat extends Program {
|
||||||
constructor() {
|
|
||||||
super()
|
|
||||||
}
|
|
||||||
|
|
||||||
async Exec(_: SimpleStream<string>, stdout: SimpleStream<string>, __: Item, args: string[]): Promise<number> {
|
async Exec(_: SimpleStream<string>, stdout: SimpleStream<string>, __: Item, args: string[]): Promise<number> {
|
||||||
let item: Item = await Item.open(args[1])
|
let item: Item = await Item.open(args[1])
|
||||||
|
|
||||||
|
|||||||
@@ -7,17 +7,24 @@ export class Echo extends Program {
|
|||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
|
||||||
async Exec(_: SimpleStream<string>, stdout: SimpleStream<string>, ___: Item, args: string[]): Promise<number> {
|
async Exec(_: SimpleStream<string>, stdout: SimpleStream<string>, workdir: Item, args: string[]): Promise<number> {
|
||||||
let item: Item = await Item.open(args[1])
|
if (args.length < 2) {
|
||||||
|
stdout.emit("echo: error: missing path argument\n")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = await Item.open(args[1].startsWith('/')
|
||||||
|
? args[1]
|
||||||
|
: `${workdir.GetPath()}${workdir.GetPath().endsWith('/') ? '' : '/'}${args[1]}`)
|
||||||
|
|
||||||
if (!(await item.Exists())) {
|
if (!(await item.Exists())) {
|
||||||
stdout.emit(`echo: error: item ${item.GetPath()} doesn't exist.\n`)
|
stdout.emit(`echo: error: item ${item.GetPath()} doesn't exist.\n`)
|
||||||
return 1
|
return 2
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.IsDirectory()) {
|
if (item.IsDirectory()) {
|
||||||
stdout.emit(`echo: error: can't write data to a directory; item ${item.GetPath()} is a directory.\n`)
|
stdout.emit(`echo: error: can't write data to a directory; item ${item.GetPath()} is a directory.\n`)
|
||||||
return 2
|
return 3
|
||||||
}
|
}
|
||||||
|
|
||||||
await item.Write(args.slice(2).join(' '))
|
await item.Write(args.slice(2).join(' '))
|
||||||
|
|||||||
@@ -8,7 +8,12 @@ export class Ls extends Program {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async Exec(_: SimpleStream<string>, stdout: SimpleStream<string>, workdir: Item, args: string[]): Promise<number> {
|
async Exec(_: SimpleStream<string>, stdout: SimpleStream<string>, workdir: Item, args: string[]): Promise<number> {
|
||||||
let item: Item = args[1] ? await Item.openDir(args[1]) : workdir
|
const item = args[1]
|
||||||
|
? await Item.openDir(args[1].startsWith('/')
|
||||||
|
? args[1]
|
||||||
|
: `${workdir.GetPath()}${workdir.GetPath().endsWith('/') ? '' : '/'}${args[1]}`)
|
||||||
|
: workdir
|
||||||
|
|
||||||
if (args[1] && !item.IsDirectory()) {
|
if (args[1] && !item.IsDirectory()) {
|
||||||
stdout.emit("ls: error: the provided path is not a directory\n")
|
stdout.emit("ls: error: the provided path is not a directory\n")
|
||||||
return 1
|
return 1
|
||||||
@@ -22,10 +27,11 @@ export class Ls extends Program {
|
|||||||
console.log(item)
|
console.log(item)
|
||||||
|
|
||||||
stdout.emit(`-> Listing contents of item: '${item.GetPath()}'\n`)
|
stdout.emit(`-> Listing contents of item: '${item.GetPath()}'\n`)
|
||||||
stdout.emit(` [ attr name ]\n`)
|
stdout.emit(` [ drwx name ]\n`)
|
||||||
const items = await item.List()
|
const items = await item.List()
|
||||||
items.forEach((entry, i) => {
|
items.forEach(entry => {
|
||||||
stdout.emit(` | ${''.padEnd(4, '-').padEnd(8, ' ')} ${entry.GetName()}\n`)
|
stdout.emit(item.IsDirectory().toString())
|
||||||
|
stdout.emit(` | ${(item.IsDirectory() ? 'd' : '').padEnd(4, '-').padEnd(8, ' ')} ${entry.GetName()}\n`)
|
||||||
})
|
})
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@@ -8,12 +8,20 @@ export class Mkdir extends Program {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async Exec(_: SimpleStream<string>, stdout: SimpleStream<string>, workdir: Item, args: string[]): Promise<number> {
|
async Exec(_: SimpleStream<string>, stdout: SimpleStream<string>, workdir: Item, args: string[]): Promise<number> {
|
||||||
const item = await Item.openDir(args[1])
|
if (args.length < 2) {
|
||||||
|
stdout.emit("mkdir: error: missing path argument\n")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = await Item.openDir(args[1].startsWith('/')
|
||||||
|
? args[1]
|
||||||
|
: `${workdir.GetPath()}${workdir.GetPath().endsWith('/') ? '' : '/'}${args[1]}`)
|
||||||
|
|
||||||
stdout.emit(`does ${args[1]} exist? ${await item.Exists()}\n`)
|
stdout.emit(`does ${args[1]} exist? ${await item.Exists()}\n`)
|
||||||
|
|
||||||
if (await item.Exists()) {
|
if (await item.Exists()) {
|
||||||
stdout.emit(`mkdir: directory ${item.GetPath()} already exists.\n`)
|
stdout.emit(`mkdir: directory ${item.GetPath()} already exists.\n`)
|
||||||
return 1
|
return 2
|
||||||
}
|
}
|
||||||
|
|
||||||
item.Create()
|
item.Create()
|
||||||
|
|||||||
@@ -16,9 +16,13 @@ export class Rm extends Program {
|
|||||||
let item: Item
|
let item: Item
|
||||||
|
|
||||||
try {
|
try {
|
||||||
item = await Item.openDir(args[1])
|
item = await Item.openDir(args[1].startsWith('/')
|
||||||
|
? args[1]
|
||||||
|
: `${workdir.GetPath()}${workdir.GetPath().endsWith('/') ? '' : '/'}${args[1]}`)
|
||||||
} catch {
|
} catch {
|
||||||
item = await Item.open(args[1])
|
item = await Item.open(args[1].startsWith('/')
|
||||||
|
? args[1]
|
||||||
|
: `${workdir.GetPath()}${workdir.GetPath().endsWith('/') ? '' : '/'}${args[1]}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(await item.Exists())) {
|
if (!(await item.Exists())) {
|
||||||
|
|||||||
@@ -10,8 +10,9 @@ export class Touch extends Program {
|
|||||||
async Exec(_: SimpleStream<string>, stdout: SimpleStream<string>, workdir: Item, args: string[]): Promise<number> {
|
async Exec(_: SimpleStream<string>, stdout: SimpleStream<string>, workdir: Item, args: string[]): Promise<number> {
|
||||||
stdout.emit(`touching children, please wait...\n`)
|
stdout.emit(`touching children, please wait...\n`)
|
||||||
|
|
||||||
const item = await Item.open(args[1])
|
const path = args.slice(1).join()
|
||||||
stdout.emit(`does ${args[1]} exist? ${await item.Exists()}\n`)
|
const item = await Item.open(path.includes('/') ? path : `${workdir.GetPath()}${workdir.GetPath().endsWith('/') ? '' : '/'}${path}`)
|
||||||
|
stdout.emit(`does ${item.GetPath()} exist? ${await item.Exists()}\n`)
|
||||||
|
|
||||||
if (await item.Exists()) {
|
if (await item.Exists()) {
|
||||||
stdout.emit("touch: the file already exists.\n")
|
stdout.emit("touch: the file already exists.\n")
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { Item } from '../fs/Item'
|
||||||
import type { Program } from '../program/Program'
|
import type { Program } from '../program/Program'
|
||||||
import type { Terminal } from '../terminal/Terminal'
|
import type { Terminal } from '../terminal/Terminal'
|
||||||
import type { EventBroadcaster } from '../utils/EventBroadcaster'
|
import type { EventBroadcaster } from '../utils/EventBroadcaster'
|
||||||
@@ -17,4 +18,5 @@ export abstract class Shell {
|
|||||||
abstract ExecuteProgram(name: string, args: string[]): void
|
abstract ExecuteProgram(name: string, args: string[]): void
|
||||||
abstract Init(): Promise<void>
|
abstract Init(): Promise<void>
|
||||||
abstract HandleKeyInput(key: string, isCharacter: boolean): void
|
abstract HandleKeyInput(key: string, isCharacter: boolean): void
|
||||||
|
abstract SetWorkingDirectory(directory: Item): Promise<void>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import { ResetIndexedDb } from '../program/ResetIndexedDb'
|
|||||||
import { Cat } from '../program/Cat'
|
import { Cat } from '../program/Cat'
|
||||||
import { Echo } from '../program/Echo'
|
import { Echo } from '../program/Echo'
|
||||||
import { Mkdir } from '../program/Mkdir'
|
import { Mkdir } from '../program/Mkdir'
|
||||||
|
import { Pwd } from '../program/Pwd'
|
||||||
|
import { Cd } from '../program/Cd'
|
||||||
|
|
||||||
export class Wush extends Shell {
|
export class Wush extends Shell {
|
||||||
public readonly Version = "0.1.0"
|
public readonly Version = "0.1.0"
|
||||||
@@ -47,7 +49,7 @@ export class Wush extends Shell {
|
|||||||
readonly stdout: SimpleStream<string>
|
readonly stdout: SimpleStream<string>
|
||||||
|
|
||||||
private programs: { [name: string]: Program } = {}
|
private programs: { [name: string]: Program } = {}
|
||||||
private workingDirectory: Item = null as unknown as Item // workdir is initialized in the Init so this should be safe
|
private workingDirectory: Item = null as unknown as Item // workdir is initialized in Init so this should be safe
|
||||||
|
|
||||||
constructor(broadcaster: EventBroadcaster, terminal: Terminal) {
|
constructor(broadcaster: EventBroadcaster, terminal: Terminal) {
|
||||||
super(broadcaster, terminal)
|
super(broadcaster, terminal)
|
||||||
@@ -65,9 +67,6 @@ export class Wush extends Shell {
|
|||||||
// load workdir
|
// load workdir
|
||||||
this.workingDirectory = await Item.Root()
|
this.workingDirectory = await Item.Root()
|
||||||
|
|
||||||
if (!this.workingDirectory.Exists())
|
|
||||||
this.workingDirectory.Create()
|
|
||||||
|
|
||||||
// load core programs
|
// load core programs
|
||||||
this.programs['clear'] = new Clear()
|
this.programs['clear'] = new Clear()
|
||||||
this.programs['eval'] = new Eval()
|
this.programs['eval'] = new Eval()
|
||||||
@@ -83,6 +82,8 @@ export class Wush extends Shell {
|
|||||||
this.programs['cat'] = new Cat()
|
this.programs['cat'] = new Cat()
|
||||||
this.programs['echo'] = new Echo()
|
this.programs['echo'] = new Echo()
|
||||||
this.programs['mkdir'] = new Mkdir()
|
this.programs['mkdir'] = new Mkdir()
|
||||||
|
this.programs['pwd'] = new Pwd()
|
||||||
|
this.programs['cd'] = new Cd(this)
|
||||||
|
|
||||||
this.stdout.on(data => this.WriteEscapedString(data))
|
this.stdout.on(data => this.WriteEscapedString(data))
|
||||||
this.Prompt()
|
this.Prompt()
|
||||||
@@ -121,7 +122,19 @@ export class Wush extends Shell {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Prompt() {
|
Prompt() {
|
||||||
this.terminal.Write(`hi [${this.execExitCode}] -> `)
|
this.terminal.Write(`boykisser in ${this.workingDirectory.GetPath()} -> `)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the working directory
|
||||||
|
* @param directory the directory to enter into
|
||||||
|
* @throws an error if the directory cannot be opened
|
||||||
|
*/
|
||||||
|
async SetWorkingDirectory(directory: Item) {
|
||||||
|
if (!(await directory.Exists()) || !directory.IsDirectory())
|
||||||
|
throw new Error(`Directory ${directory.GetPath()} doesn't exist`)
|
||||||
|
|
||||||
|
this.workingDirectory = directory
|
||||||
}
|
}
|
||||||
|
|
||||||
WriteStdin(data: string) {
|
WriteStdin(data: string) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
@use "./colors.scss" as colors;
|
@use "./colors.scss" as colors;
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 10px;
|
||||||
background: colors.$terminal-background;
|
background: colors.$terminal-background;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -155,8 +155,8 @@ export class Terminal {
|
|||||||
UpdateCursor() {
|
UpdateCursor() {
|
||||||
this.SetCursorStyle(this.cursorStyle)
|
this.SetCursorStyle(this.cursorStyle)
|
||||||
|
|
||||||
this.cursor.style.left = `${this.cursorPosition.col * this.cellWidth}px`
|
this.cursor.style.left = `${this.cursorPosition.col * this.cellWidth + this.terminal.offsetLeft}px `
|
||||||
this.cursor.style.top = `${this.cursorPosition.row * this.cellHeight}px`
|
this.cursor.style.top = `${this.cursorPosition.row * this.cellHeight + this.terminal.offsetTop}px`
|
||||||
}
|
}
|
||||||
|
|
||||||
GetCursorPosition(): CursorPosition {
|
GetCursorPosition(): CursorPosition {
|
||||||
|
|||||||
Reference in New Issue
Block a user