Refactor projects to use indexeddb

This commit is contained in:
Misode
2024-11-13 05:29:06 +01:00
parent 26079d1188
commit 2366716cae
17 changed files with 265 additions and 501 deletions

View File

@@ -1,5 +1,6 @@
import { route } from 'preact-router'
import { useCallback, useEffect, useErrorBoundary, useMemo, useRef, useState } from 'preact/hooks'
import type { Method } from '../../Analytics.js'
import { Analytics } from '../../Analytics.js'
import type { ConfigGenerator } from '../../Config.js'
import config from '../../Config.js'
@@ -8,8 +9,9 @@ import { useSpyglass, watchSpyglassUri } from '../../contexts/Spyglass.jsx'
import { AsyncCancel, useActiveTimeout, useAsync, useSearchParam } from '../../hooks/index.js'
import type { VersionId } from '../../services/index.js'
import { checkVersion, fetchDependencyMcdoc, fetchPreset, fetchRegistries, getSnippet, shareSnippet } from '../../services/index.js'
import { DEPENDENCY_URI } from '../../services/Spyglass.js'
import { Store } from '../../Store.js'
import { cleanUrl, genPath, safeJsonParse } from '../../Utils.js'
import { cleanUrl, genPath } from '../../Utils.js'
import { Ad, Btn, BtnMenu, ErrorPanel, FileCreation, FileRenaming, Footer, HasPreview, Octicon, PreviewPanel, ProjectCreation, ProjectDeletion, ProjectPanel, SearchList, SourcePanel, TextInput, Tree, VersionSwitcher } from '../index.js'
import { getRootDefault } from './McdocHelpers.js'
@@ -23,7 +25,7 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
const { locale } = useLocale()
const { version, changeVersion, changeTargetVersion } = useVersion()
const { service } = useSpyglass()
const { projects, project, file, updateProject, updateFile, closeFile } = useProject()
const { project, projectUri, setProjectUri, updateProject } = useProject()
const [error, setError] = useState<Error | string | null>(null)
const [errorBoundary, errorRetry] = useErrorBoundary()
if (errorBoundary) {
@@ -34,9 +36,21 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
useEffect(() => Store.visitGenerator(gen.id), [gen.id])
const uri = useMemo(() => {
// TODO: return different uri when project file is open
return service?.getUnsavedFileUri(gen)
}, [service, version, gen])
if (!service) {
return undefined
}
if (projectUri) {
const category = projectUri.endsWith('/pack.mcmeta')
? 'pack_mcmeta'
: service.dissectUri(projectUri)?.category
if (category === gen.id) {
return projectUri
} else {
setProjectUri(undefined)
}
}
return service.getUnsavedFileUri(gen)
}, [service, version, gen, projectUri])
const [currentPreset, setCurrentPreset] = useSearchParam('preset')
const [sharedSnippetId, setSharedSnippetId] = useSearchParam(SHARE_KEY)
@@ -75,20 +89,13 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
Analytics.openSnippet(gen.id, sharedSnippetId, version)
ignoreChange.current = true
text = snippet.text
} else if (file) {
if (project.version && project.version !== version) {
changeVersion(project.version, false)
return AsyncCancel
}
ignoreChange.current = true
text = JSON.stringify(file.data, null, 2)
}
if (!service || !uri) {
return AsyncCancel
}
if (gen.dependency) {
const dependency = await fetchDependencyMcdoc(gen.dependency)
const dependencyUri = `file:///project/mcdoc/${gen.dependency}.mcdoc`
const dependencyUri = `${DEPENDENCY_URI}${gen.dependency}.mcdoc`
await service.writeFile(dependencyUri, dependency)
}
if (text !== undefined) {
@@ -104,24 +111,18 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
const docAndNode = await service.openFile(uri)
Analytics.setGenerator(gen.id)
return docAndNode
}, [gen.id, version, sharedSnippetId, currentPreset, project.name, file?.id, service])
}, [gen.id, version, sharedSnippetId, currentPreset, project.name, service, uri])
const { doc } = docAndNode ?? {}
watchSpyglassUri(uri, ({ doc }) => {
watchSpyglassUri(uri, () => {
if (!ignoreChange.current) {
setCurrentPreset(undefined, true)
setSharedSnippetId(undefined, true)
}
if (file) {
const data = safeJsonParse(doc.getText())
if (data !== undefined) {
updateFile(gen.id, file.id, { id: file.id, data })
}
}
ignoreChange.current = false
setError(null)
}, [updateFile])
}, [])
const reset = async () => {
if (!service || !uri) {
@@ -304,9 +305,9 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
const [projectShown, setProjectShown] = useState(Store.getProjectPanelOpen() ?? window.innerWidth > 1000)
const toggleProjectShown = useCallback(() => {
if (projectShown) {
Analytics.hideProject(gen.id, projects.length, project.files.length, 'menu')
Analytics.hideProject('menu')
} else {
Analytics.showProject(gen.id, projects.length, project.files.length, 'menu')
Analytics.showProject('menu')
}
Store.setProjectPanelOpen(!projectShown)
setProjectShown(!projectShown)
@@ -314,13 +315,13 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
const [projectCreating, setProjectCreating] = useState(false)
const [projectDeleting, setprojectDeleting] = useState(false)
const [fileSaving, setFileSaving] = useState<string | undefined>(undefined)
const [fileRenaming, setFileRenaming] = useState<{ type: string, id: string } | undefined>(undefined)
const [fileSaving, setFileSaving] = useState<Method | undefined>(undefined)
const [fileRenaming, setFileRenaming] = useState<string | undefined>(undefined)
const onNewFile = useCallback(() => {
closeFile()
setProjectUri(undefined)
// TODO: create new file with default contents
}, [closeFile])
}, [setProjectUri])
return <>
<main class={`${previewShown ? 'has-preview' : ''} ${projectShown ? 'has-project' : ''}`}>
@@ -379,11 +380,11 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
</div>
</div>
<div class={`popup-project${projectShown ? ' shown' : ''}`}>
<ProjectPanel onError={setError} onDeleteProject={() => setprojectDeleting(true)} onRename={setFileRenaming} onCreate={() => setProjectCreating(true)} />
<ProjectPanel onError={setError} onRename={setFileRenaming} onDeleteProject={() => setprojectDeleting(true)} onCreateProject={() => setProjectCreating(true)} />
</div>
{projectCreating && <ProjectCreation onClose={() => setProjectCreating(false)} />}
{projectDeleting && <ProjectDeletion onClose={() => setprojectDeleting(false)} />}
{docAndNode && fileSaving && <FileCreation id={gen.id} docAndNode={docAndNode} method={fileSaving} onClose={() => setFileSaving(undefined)} />}
{fileRenaming && <FileRenaming id={fileRenaming.type } name={fileRenaming.id} onClose={() => setFileRenaming(undefined)} />}
{docAndNode && fileSaving && <FileCreation gen={gen} docAndNode={docAndNode} method={fileSaving} onCreate={(uri) => {setFileSaving(undefined); setProjectUri(uri)}} onClose={() => setFileSaving(undefined)} />}
{fileRenaming && <FileRenaming uri={fileRenaming} onClose={() => setFileRenaming(undefined)} />}
</>
}