diff --git a/src/app/components/generator/ProjectPanel.tsx b/src/app/components/generator/ProjectPanel.tsx index 219e1559..fa4d0dbb 100644 --- a/src/app/components/generator/ProjectPanel.tsx +++ b/src/app/components/generator/ProjectPanel.tsx @@ -1,11 +1,10 @@ import { Identifier } from 'deepslate' import { route } from 'preact-router' -import { useCallback, useMemo, useRef } from 'preact/hooks' +import { useCallback, useEffect, useMemo, useRef, useState } from 'preact/hooks' import config from '../../Config.js' import { DRAFT_PROJECT, getProjectRoot, useLocale, useProject } from '../../contexts/index.js' import { useModal } from '../../contexts/Modal.jsx' import { useSpyglass } from '../../contexts/Spyglass.jsx' -import { useAsync } from '../../hooks/useAsync.js' import { useFocus } from '../../hooks/useFocus.js' import { cleanUrl, writeZip } from '../../Utils.js' import { Btn } from '../Btn.js' @@ -25,12 +24,17 @@ export function ProjectPanel() { const projectRoot = getProjectRoot(project) - const { value: entries } = useAsync(async () => { - const entries = await client.fs.readdir(projectRoot) - return entries.flatMap(e => { - return e.name.startsWith(projectRoot) ? [e.name.slice(projectRoot.length)] : [] + const [entries, setEntries] = useState() + useEffect(() => { + if (!service) { + return + } + service.watchTree(projectRoot, setEntries) + client.fs.readdir(projectRoot).then(entries => { + setEntries(entries.flatMap(e => e.name.startsWith(projectRoot) ? [e.name.slice(projectRoot.length)] : [])) }) - }, [project]) + return () => service.unwatchTree(projectRoot, setEntries) + }, [service, projectRoot]) const download = useRef(null) diff --git a/src/app/services/Spyglass.ts b/src/app/services/Spyglass.ts index 95940cdd..a36362a4 100644 --- a/src/app/services/Spyglass.ts +++ b/src/app/services/Spyglass.ts @@ -68,7 +68,8 @@ export class SpyglassClient { } export class SpyglassService { - private readonly watchers = new Map void)[]>() + private readonly fileWatchers = new Map void)[]>() + private readonly treeWatchers: { prefix: string, handler: (uris: string[]) => void }[] = [] private constructor ( public readonly version: VersionId, @@ -76,11 +77,30 @@ export class SpyglassService { private readonly client: SpyglassClient, ) { service.project.on('documentUpdated', (e) => { - const uriWatchers = this.watchers.get(e.doc.uri) ?? [] + const uriWatchers = this.fileWatchers.get(e.doc.uri) ?? [] for (const handler of uriWatchers) { handler(e) } }) + let treeWatcherTask = Promise.resolve() + let hasPendingTask = false + const treeWatcher = () => { + hasPendingTask = true + // Wait for previous task to finish, then re-run once after 5 ms + treeWatcherTask = treeWatcherTask.finally(async () => { + if (!hasPendingTask) { + return + } + hasPendingTask = false + await new Promise((res) => setTimeout(res, 5)) + await Promise.all(this.treeWatchers.map(async ({ prefix, handler }) => { + const entries = await client.fs.readdir(prefix) + handler(entries.flatMap(e => e.name.startsWith(prefix) ? [e.name.slice(prefix.length)] : [])) + })) + }) + } + service.project.on('fileCreated', treeWatcher) + service.project.on('fileDeleted', treeWatcher) } public getCheckerContext(doc?: TextDocument, errors?: core.LanguageError[]) { @@ -150,6 +170,8 @@ export class SpyglassService { } await this.service.project.externals.fs.writeFile(newUri, content) await this.service.project.externals.fs.unlink(oldUri) + // await this.service.project.externals.fs.writeFile(oldUri, content) + // await this.service.project.externals.fs.unlink(oldUri) const d = this.client.documents.get(oldUri) if (d) { const doc = TextDocument.create(newUri, d.doc.languageId, d.doc.version, d.doc.getText()) @@ -220,16 +242,25 @@ export class SpyglassService { } public watchFile(uri: string, handler: (docAndNode: core.DocAndNode) => void) { - const uriWatchers = computeIfAbsent(this.watchers, uri, () => []) + const uriWatchers = computeIfAbsent(this.fileWatchers, uri, () => []) uriWatchers.push(handler) } public unwatchFile(uri: string, handler: (docAndNode: core.DocAndNode) => void) { - const uriWatchers = computeIfAbsent(this.watchers, uri, () => []) + const uriWatchers = computeIfAbsent(this.fileWatchers, uri, () => []) const index = uriWatchers.findIndex(w => w === handler) uriWatchers.splice(index, 1) } + public watchTree(prefix: string, handler: (uris: string[]) => void) { + this.treeWatchers.push({ prefix, handler }) + } + + public unwatchTree(prefix: string, handler: (uris: string[]) => void) { + const index = this.treeWatchers.findIndex(w => w.prefix === prefix && w.handler === handler) + this.treeWatchers.splice(index, 1) + } + public static async create(versionId: VersionId, client: SpyglassClient) { const version = siteConfig.versions.find(v => v.id === versionId)! const logger = console