feat(workbench): add file tree and hook up editor
This commit is contained in:
94
packages/bolt/app/lib/stores/files.ts
Normal file
94
packages/bolt/app/lib/stores/files.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import type { PathWatcherEvent, WebContainer } from '@webcontainer/api';
|
||||
import { map } from 'nanostores';
|
||||
import { bufferWatchEvents } from '../../utils/buffer';
|
||||
import { WORK_DIR } from '../../utils/constants';
|
||||
|
||||
const textDecoder = new TextDecoder('utf8', { fatal: true });
|
||||
|
||||
interface File {
|
||||
type: 'file';
|
||||
content: string;
|
||||
}
|
||||
|
||||
interface Folder {
|
||||
type: 'folder';
|
||||
}
|
||||
|
||||
type Dirent = File | Folder;
|
||||
|
||||
export type FileMap = Record<string, Dirent | undefined>;
|
||||
|
||||
export class FilesStore {
|
||||
#webcontainer: Promise<WebContainer>;
|
||||
|
||||
files = map<FileMap>({});
|
||||
|
||||
constructor(webcontainerPromise: Promise<WebContainer>) {
|
||||
this.#webcontainer = webcontainerPromise;
|
||||
|
||||
this.#init();
|
||||
}
|
||||
|
||||
async #init() {
|
||||
const webcontainer = await this.#webcontainer;
|
||||
|
||||
webcontainer.watchPaths(
|
||||
{ include: [`${WORK_DIR}/**`], exclude: ['**/node_modules', '.git'], includeContent: true },
|
||||
bufferWatchEvents(100, this.#processEventBuffer.bind(this)),
|
||||
);
|
||||
}
|
||||
|
||||
#processEventBuffer(events: Array<[events: PathWatcherEvent[]]>) {
|
||||
const watchEvents = events.flat(2);
|
||||
|
||||
for (const { type, path, buffer } of watchEvents) {
|
||||
// remove any trailing slashes
|
||||
const sanitizedPath = path.replace(/\/+$/g, '');
|
||||
|
||||
switch (type) {
|
||||
case 'add_dir': {
|
||||
// we intentionally add a trailing slash so we can distinguish files from folders in the file tree
|
||||
this.files.setKey(sanitizedPath, { type: 'folder' });
|
||||
break;
|
||||
}
|
||||
case 'remove_dir': {
|
||||
this.files.setKey(sanitizedPath, undefined);
|
||||
|
||||
for (const [direntPath] of Object.entries(this.files)) {
|
||||
if (direntPath.startsWith(sanitizedPath)) {
|
||||
this.files.setKey(direntPath, undefined);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 'add_file':
|
||||
case 'change': {
|
||||
this.files.setKey(sanitizedPath, { type: 'file', content: this.#decodeFileContent(buffer) });
|
||||
break;
|
||||
}
|
||||
case 'remove_file': {
|
||||
this.files.setKey(sanitizedPath, undefined);
|
||||
break;
|
||||
}
|
||||
case 'update_directory': {
|
||||
// we don't care about these events
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#decodeFileContent(buffer?: Uint8Array) {
|
||||
if (!buffer || buffer.byteLength === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
return textDecoder.decode(buffer);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user