Bash and IO working, basic error setup. Changelog:

Commands:
    - ls (only with -l)
    - cd (basic, probably unfinished)
This commit is contained in:
2025-11-20 14:10:20 +01:00
committed by Kamil Olszewski
parent 4428cc7e8a
commit e853268e52
15 changed files with 775 additions and 441 deletions

View File

@@ -1,65 +1,46 @@
<script lang="ts">
import type { User } from '$lib/stores/bash/bash';
import type { TreeNode } from '$lib/stores/bash/fs';
import { Terminal, type TermInitArgs } from '$lib/stores/terminal';
import {
onDestroy,
onMount,
type Snippet,
type Component,
type ComponentProps,
mount
} from 'svelte';
import { Terminal, type PrintData } from '$lib/stores/terminal/terminal';
import { onMount } from 'svelte';
import Input from './terminal/Input.svelte';
import { initTerminal, isInitializing } from '$lib/stores/terminal/init.svelte';
import { clear, print } from '$lib/stores/terminal/stdio';
let { children, username, cwd }: { children: Snippet; username: string; cwd: string } = $props();
const clearTerminal = (): void => clear();
/* function jsonToTreeNode(data: any): TreeNode {
return {
name: data.Name,
type: data.Type,
readonly: data.ReadOnly,
interactible: data.Interactible,
func: data.Func,
children: data.Children ? data.Children.map((child: any) => jsonToTreeNode(child)) : [],
content: data.Content,
link: data.Link || [],
permission: {
user: {
r: data.Permission[0]?.Read,
w: data.Permission[0]?.Write,
x: data.Permission[0]?.Exec
},
group: {
r: data.Permission[1]?.Read,
w: data.Permission[1]?.Write,
x: data.Permission[1]?.Exec
},
other: {
r: data.Permission[2]?.Read,
w: data.Permission[2]?.Write,
x: data.Permission[2]?.Exec
}
},
owner: data.Owner,
group: data.Group,
modtime: new Date(data.Mtime)
};
const printOutput = (e: HTMLElement, d: PrintData): void => print(e, d);
function updateTerminal() {
username = terminal!.getUser().username;
cwd = terminal!.getCwd();
}
async function fetchFileSystem(path: string): Promise<any> {
const response = await fetch(path);
if (!response.ok) throw new Error('Failed to fetch the file system json');
const data = await response.json();
const node: TreeNode = jsonToTreeNode(data);
return node;
function handleInput(e: KeyboardEvent) {
switch (e.key) {
case 'Enter': {
terminal.executeCommand(inputValue);
break;
}
case 'ArrowRight': {
//TODO: Move cursor visually
}
case 'ArrowLeft': {
//TODO: Move cursor visually
}
case 'ArrowUp': {
//TODO: Make a traverse history function with up/down args
}
case 'ArrowDown': {
//TODO: Make a traverse history function with up/down args
}
}
}
let callbackInit = {
print: (data: any) => {
console.log('print callback executed');
//print(data);
const e = document.getElementById('outputWrapper');
if (!e) return;
printOutput(e, data);
}
};
@@ -76,110 +57,44 @@
let username: string = $state(testUser.username);
let cwd: string = $state(testUser.home);
let isInitializing = $state(true);
let inputValue = $state<string>('');
onMount(async () => {
try {
let fsJson = await fetchFileSystem('/src/lib/assets/fs/fs.json');
let args: TermInitArgs = {
bash: {
user: testUser,
fs: fsJson
}
};
terminal = new Terminal(args);
terminal.registerCallbacks(callbackInit);
username = terminal.getUser().username;
cwd = terminal.getCwd();
} catch (error) {
console.error('Failed to initialize terminal:', error);
} finally {
terminal = await initTerminal(testUser, callbackInit);
updateTerminal();
isInitializing = false;
}
});
function updateTerminal() {
username = terminal!.getUser().username;
cwd = terminal!.getCwd();
} */
let outputContainer = $state<HTMLElement>();
let instances = $state<Set<ReturnType<typeof mount>>>(new Set());
onMount(() => {
const scrollable = document.getElementById('cout');
const config = { childList: true };
const callback = function (mutationList: any, observer: any) {
for (let mutation of mutationList) {
if (mutation.type === 'childList') {
scrollable?.scrollTo(0, scrollable.scrollHeight);
}
}
};
const observer = new MutationObserver(callback);
observer.observe(scrollable!, config);
});
export function addComponent<T extends Component>(
component: T,
props: ComponentProps<T> = {} as ComponentProps<T>
): ReturnType<typeof mount> | undefined {
if (!outputContainer) return;
const instance = mount(component, {
target: outputContainer,
props
});
instances.add(instance);
return instance;
}
export function clearComponents(): void {
for (const instance of instances) {
instance.$destroy();
}
instances.clear();
}
onDestroy(() => {
for (const instance of instances) {
instance.$destroy();
} catch (error) {
console.error('onMount trycatch failed');
}
});
</script>
<div id="terminal" class="terminal-window shadow-() size-full rounded-md shadow-bg">
<div
class="terminal-bar flex h-9 w-full flex-row items-center rounded-t-md bg-bg-dark text-center font-terminal text-sm font-bold text-primary-dark light:bg-bg-dark-light light:text-primary-light"
>
<div class="dots-wrapper mx-2.5 flex h-full flex-row items-center justify-center gap-2.5">
<button class="size-2.5 cursor-pointer rounded-full p-0" title=""></button>
<button class="size-2.5 cursor-pointer rounded-full p-0" title=""></button>
<button class="size-2.5 cursor-pointer rounded-full p-0" title=""></button>
<label for="input" onkeydowncapture={(e) => handleInput(e)}>
<div id="terminal" class="terminal-window shadow-() size-full resize rounded-md shadow-bg">
<div
class="terminal-bar flex h-9 w-full flex-row items-center rounded-t-md bg-bg-dark text-center font-terminal text-sm font-bold text-primary-dark light:bg-bg-dark-light light:text-primary-light"
>
<div class="dots-wrapper mx-2.5 flex h-full flex-row items-center justify-center gap-2.5">
<button class="size-2.5 cursor-pointer rounded-full p-0" title=""></button>
<button class="size-2.5 cursor-pointer rounded-full p-0" title=""></button>
<button class="size-2.5 cursor-pointer rounded-full p-0" title=""></button>
</div>
<div class=" flex">
<h5>{username}</h5>
<!-- prettier-ignore -->
<h5 class=" mr-2">@terminal: </h5>
<h5>{cwd}</h5>
</div>
</div>
<div class=" flex">
<h5>{username}</h5>
<!-- prettier-ignore -->
<h5 class=" mr-2">@terminal: </h5>
<h5>{cwd}</h5>
<div
class="inner-content scroll-hidden h-[860px] origin-top overflow-y-auto rounded-b-md bg-bg-light-dark p-4 text-text-dark shadow-subtle light:bg-bg-lighter-light light:text-text-light"
id="cout"
>
<div id="outputWrapper"></div>
<Input {cwd} bind:inputValue />
</div>
</div>
<div
class="inner-content scroll-hidden h-[860px] origin-top overflow-y-auto rounded-b-md bg-bg-light-dark p-4 text-text-dark shadow-subtle light:bg-bg-lighter-light light:text-text-light"
id="cout"
>
<div bind:this={outputContainer}></div>
{@render children()}
</div>
</div>
</label>
<style>
* {