import { atom, map, type MapStore, type ReadableAtom, type WritableAtom } from 'nanostores'; import type { EditorDocument, ScrollPosition } from '../../components/editor/codemirror/CodeMirrorEditor'; import { unreachable } from '../../utils/unreachable'; import { ActionRunner } from '../runtime/action-runner'; import type { ActionCallbackData, ArtifactCallbackData } from '../runtime/message-parser'; import { webcontainer } from '../webcontainer'; import { EditorStore } from './editor'; import { FilesStore, type FileMap } from './files'; import { PreviewsStore } from './previews'; export interface ArtifactState { title: string; closed: boolean; runner: ActionRunner; } export type ArtifactUpdateState = Pick; type Artifacts = MapStore>; export class WorkbenchStore { #previewsStore = new PreviewsStore(webcontainer); #filesStore = new FilesStore(webcontainer); #editorStore = new EditorStore(webcontainer); artifacts: Artifacts = import.meta.hot?.data.artifacts ?? map({}); showWorkbench: WritableAtom = import.meta.hot?.data.showWorkbench ?? atom(false); get previews() { return this.#previewsStore.previews; } get files() { return this.#filesStore.files; } get currentDocument(): ReadableAtom { return this.#editorStore.currentDocument; } get selectedFile(): ReadableAtom { return this.#editorStore.selectedFile; } setDocuments(files: FileMap) { this.#editorStore.setDocuments(files); } setShowWorkbench(show: boolean) { this.showWorkbench.set(show); } setCurrentDocumentContent(newContent: string) { const filePath = this.currentDocument.get()?.filePath; if (!filePath) { return; } this.#editorStore.updateFile(filePath, newContent); } setCurrentDocumentScrollPosition(position: ScrollPosition) { const editorDocument = this.currentDocument.get(); if (!editorDocument) { return; } const { filePath } = editorDocument; this.#editorStore.updateScrollPosition(filePath, position); } setSelectedFile(filePath: string | undefined) { this.#editorStore.setSelectedFile(filePath); } abortAllActions() { // TODO: what do we wanna do and how do we wanna recover from this? } addArtifact({ messageId, title }: ArtifactCallbackData) { const artifact = this.#getArtifact(messageId); if (artifact) { return; } this.artifacts.setKey(messageId, { title, closed: false, runner: new ActionRunner(webcontainer), }); } updateArtifact({ messageId }: ArtifactCallbackData, state: Partial) { const artifact = this.#getArtifact(messageId); if (!artifact) { return; } this.artifacts.setKey(messageId, { ...artifact, ...state }); } async addAction(data: ActionCallbackData) { const { messageId } = data; const artifact = this.#getArtifact(messageId); if (!artifact) { unreachable('Artifact not found'); } artifact.runner.addAction(data); } async runAction(data: ActionCallbackData) { const { messageId } = data; const artifact = this.#getArtifact(messageId); if (!artifact) { unreachable('Artifact not found'); } artifact.runner.runAction(data); } #getArtifact(id: string) { const artifacts = this.artifacts.get(); return artifacts[id]; } } export const workbenchStore = new WorkbenchStore(); if (import.meta.hot) { import.meta.hot.data.artifacts = workbenchStore.artifacts; import.meta.hot.data.showWorkbench = workbenchStore.showWorkbench; }