diff --git a/src/app/components/ErrorPanel.tsx b/src/app/components/ErrorPanel.tsx index c0582e3a..3c75328a 100644 --- a/src/app/components/ErrorPanel.tsx +++ b/src/app/components/ErrorPanel.tsx @@ -22,7 +22,7 @@ export function ErrorPanel({ error, prefix, reportable, onDismiss, body: body_, const [stack, setStack] = useState(undefined) const gen = getGenerator(getCurrentUrl()) - const source = gen ? spyglass?.getFile(spyglass.getUnsavedFileUri(gen)).doc?.getText() : undefined + const source = gen ? spyglass?.getFileContents(spyglass.getUnsavedFileUri(version, gen)) : undefined const name = (prefix ?? '') + (error instanceof Error ? error.message : error) useEffect(() => { diff --git a/src/app/components/generator/SchemaGenerator.tsx b/src/app/components/generator/SchemaGenerator.tsx index c6893751..2c538470 100644 --- a/src/app/components/generator/SchemaGenerator.tsx +++ b/src/app/components/generator/SchemaGenerator.tsx @@ -21,7 +21,7 @@ interface Props { export function SchemaGenerator({ gen, allowedVersions }: Props) { const { locale } = useLocale() const { version, changeVersion, changeTargetVersion } = useVersion() - const { spyglass, spyglassLoading } = useSpyglass() + const { spyglass } = useSpyglass() const { projects, project, file, updateProject, updateFile, closeFile } = useProject() const [error, setError] = useState(null) const [errorBoundary, errorRetry] = useErrorBoundary() @@ -34,17 +34,14 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) { const uri = useMemo(() => { // TODO: return different uri when project file is open - return spyglass?.getUnsavedFileUri(gen) - }, [spyglass, gen.id]) + return spyglass?.getUnsavedFileUri(version, gen) + }, [spyglass, version, gen]) const [currentPreset, setCurrentPreset] = useSearchParam('preset') const [sharedSnippetId, setSharedSnippetId] = useSearchParam(SHARE_KEY) const ignoreChange = useRef(false) const { value: docAndNode } = useAsync(async () => { - if (spyglassLoading || !spyglass || !uri) { - return AsyncCancel - } let data: unknown = undefined if (currentPreset && sharedSnippetId) { setSharedSnippetId(undefined) @@ -86,10 +83,10 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) { data = file.data } // TODO: if data is undefined, set to generator's default - const docAndNode = await spyglass.setFileContents(uri, data ? JSON.stringify(data) : undefined) + const docAndNode = await spyglass.setFileContents(version, uri, data ? JSON.stringify(data) : undefined) Analytics.setGenerator(gen.id) return docAndNode - }, [gen.id, version, sharedSnippetId, currentPreset, project.name, file?.id, spyglass, spyglassLoading]) + }, [gen.id, version, sharedSnippetId, currentPreset, project.name, file?.id, spyglass]) const { doc } = docAndNode ?? {} diff --git a/src/app/components/generator/SourcePanel.tsx b/src/app/components/generator/SourcePanel.tsx index 27b4065b..7310c9f7 100644 --- a/src/app/components/generator/SourcePanel.tsx +++ b/src/app/components/generator/SourcePanel.tsx @@ -1,7 +1,7 @@ import type { DocAndNode } from '@spyglassmc/core' import { fileUtil } from '@spyglassmc/core' import { useCallback, useEffect, useRef, useState } from 'preact/hooks' -import { useLocale } from '../../contexts/index.js' +import { useLocale, useVersion } from '../../contexts/index.js' import { useDocAndNode } from '../../contexts/Spyglass.jsx' import { useLocalStorage } from '../../hooks/index.js' import { getSourceFormats, getSourceIndent, getSourceIndents, parseSource, sortData, stringifySource } from '../../services/index.js' @@ -28,6 +28,7 @@ type SourcePanelProps = { } export function SourcePanel({ spyglass, docAndNode, doCopy, doDownload, doImport, copySuccess, onError }: SourcePanelProps) { const { locale } = useLocale() + const { version } = useVersion() const [indent, setIndent] = useState(Store.getIndent()) const [format, setFormat] = useState(Store.getFormat()) const [sort, setSort] = useLocalStorage('misode_output_sort', 'schema') @@ -77,7 +78,7 @@ export function SourcePanel({ spyglass, docAndNode, doCopy, doDownload, doImport if (!spyglass || !docAndNode) return try { const data = await parseSource(value, format) - await spyglass.setFileContents(docAndNode.doc.uri, JSON.stringify(data)) + await spyglass.setFileContents(version, docAndNode.doc.uri, JSON.stringify(data)) } catch (e) { if (e instanceof Error) { e.message = `Error importing: ${e.message}` @@ -88,7 +89,7 @@ export function SourcePanel({ spyglass, docAndNode, doCopy, doDownload, doImport console.error(e) } } - }, [spyglass, docAndNode, text, indent, format, sort, highlighting]) + }, [spyglass, version, docAndNode, text, indent, format, sort, highlighting]) useEffect(() => { if (highlighting) { diff --git a/src/app/contexts/Spyglass.tsx b/src/app/contexts/Spyglass.tsx index 9bc46cb5..3a8d2e66 100644 --- a/src/app/contexts/Spyglass.tsx +++ b/src/app/contexts/Spyglass.tsx @@ -3,19 +3,20 @@ import type { ComponentChildren } from 'preact' import { createContext } from 'preact' import type { Inputs } from 'preact/hooks' import { useContext, useEffect, useState } from 'preact/hooks' -import { useAsync } from '../hooks/useAsync.js' import { Spyglass } from '../services/Spyglass.js' -import { useVersion } from './Version.jsx' interface SpyglassContext { - spyglass?: Spyglass, - spyglassLoading: boolean, + spyglass: Spyglass, } const SpyglassContext = createContext(undefined) export function useSpyglass(): SpyglassContext { - return useContext(SpyglassContext) ?? { spyglassLoading: true } + const ctx = useContext(SpyglassContext) + if (ctx === undefined) { + throw new Error('Cannot use Spyglass context') + } + return ctx } export function watchSpyglassUri( @@ -23,10 +24,10 @@ export function watchSpyglassUri( handler: (docAndNode: DocAndNode) => void, inputs: Inputs = [], ) { - const { spyglass, spyglassLoading } = useSpyglass() + const { spyglass } = useSpyglass() useEffect(() => { - if (!uri || !spyglass || spyglassLoading) { + if (!uri || !spyglass) { return } spyglass.watchFile(uri, handler) @@ -51,15 +52,10 @@ export function useDocAndNode(original: DocAndNode | undefined, inputs: Inputs = } export function SpyglassProvider({ children }: { children: ComponentChildren }) { - const { version } = useVersion() - - const { value: spyglass, loading: spyglassLoading } = useAsync(() => { - return Spyglass.initialize(version) - }, [version]) + const [spyglass] = useState(new Spyglass()) const value: SpyglassContext = { spyglass, - spyglassLoading, } return diff --git a/src/app/services/Spyglass.ts b/src/app/services/Spyglass.ts index 48fb6363..296c0911 100644 --- a/src/app/services/Spyglass.ts +++ b/src/app/services/Spyglass.ts @@ -16,50 +16,38 @@ import { fetchBlockStates, fetchRegistries, fetchVanillaMcdoc, getVersionChecksu import type { VersionId } from './Versions.js' export class Spyglass { - private static readonly INSTANCES = new Map>() - + private readonly instances = new Map>() private readonly watchers = new Map void)[]>() - private constructor( - private readonly service: core.Service, - private readonly version: ConfigVersion, - ) { - this.service.project.on('documentUpdated', (e) => { - const uriWatchers = this.watchers.get(e.doc.uri) ?? [] - for (const handler of uriWatchers) { - handler(e) - } - }) - } - - public async setFileContents(uri: string, contents?: string) { + public async setFileContents(versionId: VersionId, uri: string, contents?: string) { + const service = await this.getService(versionId) if (contents !== undefined) { - await this.service.project.externals.fs.writeFile(uri, contents) + await service.project.externals.fs.writeFile(uri, contents) } else { try { - const buffer = await this.service.project.externals.fs.readFile(uri) + const buffer = await service.project.externals.fs.readFile(uri) contents = new TextDecoder().decode(buffer) } catch (e) { contents = '{}' } } - await this.service.project.onDidOpen(uri, 'json', 1, contents) - const docAndNode = await this.service.project.ensureClientManagedChecked(uri) + await service.project.onDidOpen(uri, 'json', 1, contents) + const docAndNode = await service.project.ensureClientManagedChecked(uri) if (!docAndNode) { throw new Error('[Spyglass setFileContents] Cannot get doc and node') } return docAndNode } - public getFile(uri: string): Partial { - return this.service.project.getClientManaged(uri) ?? {} + public getFileContents(_uri: string): string | undefined { + return undefined // TODO } - public getUnsavedFileUri(gen: ConfigGenerator) { + public getUnsavedFileUri(versionId: VersionId, gen: ConfigGenerator) { if (gen.id === 'pack_mcmeta') { return 'file:///project/pack.mcmeta' } - return `file:///project/data/draft/${genPath(gen, this.version.id)}/unsaved.json` + return `file:///project/data/draft/${genPath(gen, versionId)}/unsaved.json` } public watchFile(uri: string, handler: (docAndNode: core.DocAndNode) => void) { @@ -73,8 +61,8 @@ export class Spyglass { uriWatchers.splice(index, 1) } - public static async initialize(versionId: VersionId) { - const instance = this.INSTANCES.get(versionId) + private async getService(versionId: VersionId) { + const instance = this.instances.get(versionId) if (instance) { return instance } @@ -107,9 +95,15 @@ export class Spyglass { }) await service.project.ready() await service.project.cacheService.save() - return new Spyglass(service, version) + service.project.on('documentUpdated', (e) => { + const uriWatchers = this.watchers.get(e.doc.uri) ?? [] + for (const handler of uriWatchers) { + handler(e) + } + }) + return service })() - this.INSTANCES.set(versionId, promise) + this.instances.set(versionId, promise) return promise } }