feat: add terminal and simple shortcut system (#16)

This commit is contained in:
Dominic Elm
2024-07-29 14:37:23 +02:00
committed by GitHub
parent d35f64eb1d
commit 8486d85f64
24 changed files with 696 additions and 111 deletions

View File

@@ -0,0 +1,39 @@
import { map } from 'nanostores';
import { workbenchStore } from './workbench';
export interface Shortcut {
key: string;
ctrlKey?: boolean;
shiftKey?: boolean;
altKey?: boolean;
metaKey?: boolean;
ctrlOrMetaKey?: boolean;
action: () => void;
}
export interface Shortcuts {
toggleTerminal: Shortcut;
}
export interface Settings {
shortcuts: Shortcuts;
}
export const shortcutsStore = map<Shortcuts>({
toggleTerminal: {
key: 'j',
ctrlOrMetaKey: true,
action: () => workbenchStore.toggleTerminal(),
},
});
export const settingsStore = map<Settings>({
shortcuts: shortcutsStore.get(),
});
shortcutsStore.subscribe((shortcuts) => {
settingsStore.set({
...settingsStore.get(),
shortcuts,
});
});

View File

@@ -0,0 +1,40 @@
import type { WebContainer, WebContainerProcess } from '@webcontainer/api';
import { atom, type WritableAtom } from 'nanostores';
import type { ITerminal } from '~/types/terminal';
import { newShellProcess } from '~/utils/shell';
import { coloredText } from '~/utils/terminal';
export class TerminalStore {
#webcontainer: Promise<WebContainer>;
#terminals: Array<{ terminal: ITerminal; process: WebContainerProcess }> = [];
showTerminal: WritableAtom<boolean> = import.meta.hot?.data.showTerminal ?? atom(false);
constructor(webcontainerPromise: Promise<WebContainer>) {
this.#webcontainer = webcontainerPromise;
if (import.meta.hot) {
import.meta.hot.data.showTerminal = this.showTerminal;
}
}
toggleTerminal(value?: boolean) {
this.showTerminal.set(value !== undefined ? value : !this.showTerminal.get());
}
async attachTerminal(terminal: ITerminal) {
try {
const shellProcess = await newShellProcess(await this.#webcontainer, terminal);
this.#terminals.push({ terminal, process: shellProcess });
} catch (error: any) {
terminal.write(coloredText.red('Failed to spawn shell\n\n') + error.message);
return;
}
}
onTerminalResize(cols: number, rows: number) {
for (const { process } of this.#terminals) {
process.resize({ cols, rows });
}
}
}

View File

@@ -8,6 +8,8 @@ export function themeIsDark() {
return themeStore.get() === 'dark';
}
export const DEFAULT_THEME = 'light';
export const themeStore = atom<Theme>(initStore());
function initStore() {
@@ -15,10 +17,10 @@ function initStore() {
const persistedTheme = localStorage.getItem(kTheme) as Theme | undefined;
const themeAttribute = document.querySelector('html')?.getAttribute('data-theme');
return persistedTheme ?? (themeAttribute as Theme) ?? 'light';
return persistedTheme ?? (themeAttribute as Theme) ?? DEFAULT_THEME;
}
return 'light';
return DEFAULT_THEME;
}
export function toggleTheme() {

View File

@@ -3,10 +3,12 @@ import type { EditorDocument, ScrollPosition } from '~/components/editor/codemir
import { ActionRunner } from '~/lib/runtime/action-runner';
import type { ActionCallbackData, ArtifactCallbackData } from '~/lib/runtime/message-parser';
import { webcontainer } from '~/lib/webcontainer';
import type { ITerminal } from '~/types/terminal';
import { unreachable } from '~/utils/unreachable';
import { EditorStore } from './editor';
import { FilesStore, type FileMap } from './files';
import { PreviewsStore } from './previews';
import { TerminalStore } from './terminal';
export interface ArtifactState {
title: string;
@@ -22,6 +24,7 @@ export class WorkbenchStore {
#previewsStore = new PreviewsStore(webcontainer);
#filesStore = new FilesStore(webcontainer);
#editorStore = new EditorStore(this.#filesStore);
#terminalStore = new TerminalStore(webcontainer);
artifacts: Artifacts = import.meta.hot?.data.artifacts ?? map({});
showWorkbench: WritableAtom<boolean> = import.meta.hot?.data.showWorkbench ?? atom(false);
@@ -53,6 +56,22 @@ export class WorkbenchStore {
return this.#editorStore.selectedFile;
}
get showTerminal() {
return this.#terminalStore.showTerminal;
}
toggleTerminal(value?: boolean) {
this.#terminalStore.toggleTerminal(value);
}
attachTerminal(terminal: ITerminal) {
this.#terminalStore.attachTerminal(terminal);
}
onTerminalResize(cols: number, rows: number) {
this.#terminalStore.onTerminalResize(cols, rows);
}
setDocuments(files: FileMap) {
this.#editorStore.setDocuments(files);