mirror of
https://github.com/misode/misode.github.io.git
synced 2026-04-23 07:10:41 +00:00
192 lines
6.0 KiB
TypeScript
192 lines
6.0 KiB
TypeScript
import { Identifier } from 'deepslate'
|
|
import type { ComponentChildren } from 'preact'
|
|
import { createContext } from 'preact'
|
|
import { useCallback, useContext, useState } from 'preact/hooks'
|
|
import type { ProjectData } from '../components/previews/Deepslate.js'
|
|
import config from '../Config.js'
|
|
import { useAsync } from '../hooks/useAsync.js'
|
|
import type { VersionId } from '../services/index.js'
|
|
import { DEFAULT_VERSION } from '../services/index.js'
|
|
import { DRAFTS_URI, PROJECTS_URI, SpyglassClient } from '../services/Spyglass.js'
|
|
import { Store } from '../Store.js'
|
|
import { genPath, hexId, message, safeJsonParse } from '../Utils.js'
|
|
|
|
export type ProjectMeta = {
|
|
name: string,
|
|
namespace?: string,
|
|
version?: VersionId,
|
|
storage?: ProjectStorage,
|
|
/** @deprecated */
|
|
files?: ProjectFile[],
|
|
/** @deprecated */
|
|
unknownFiles?: UnknownFile[],
|
|
}
|
|
|
|
export type ProjectStorage = {
|
|
type: 'indexeddb',
|
|
rootUri: string,
|
|
}
|
|
|
|
type ProjectFile = {
|
|
type: string,
|
|
id: string,
|
|
data: any,
|
|
}
|
|
|
|
type UnknownFile = {
|
|
path: string,
|
|
data: string,
|
|
}
|
|
|
|
export const DRAFT_PROJECT: ProjectMeta = {
|
|
name: 'Drafts',
|
|
namespace: 'draft',
|
|
storage: {
|
|
type: 'indexeddb',
|
|
rootUri: DRAFTS_URI,
|
|
},
|
|
}
|
|
|
|
interface ProjectContext {
|
|
projects: ProjectMeta[],
|
|
project: ProjectMeta,
|
|
projectUri: string | undefined,
|
|
setProjectUri: (uri: string | undefined) => void,
|
|
createProject: (project: ProjectMeta) => void,
|
|
deleteProject: (name: string) => void,
|
|
changeProject: (name: string) => void,
|
|
updateProject: (project: Partial<ProjectMeta>) => void,
|
|
}
|
|
const Project = createContext<ProjectContext | undefined>(undefined)
|
|
|
|
export function useProject() {
|
|
const context = useContext(Project)
|
|
if (context === undefined) {
|
|
throw new Error('Cannot use project outside of provider')
|
|
}
|
|
return context
|
|
}
|
|
|
|
export function ProjectProvider({ children }: { children: ComponentChildren }) {
|
|
const [projects, setProjects] = useState<ProjectMeta[]>(Store.getProjects())
|
|
const [openProject, setOpenProject] = useState<string>(Store.getOpenProject())
|
|
|
|
const { value: project} = useAsync(async () => {
|
|
const project = projects.find(p => p.name === openProject)
|
|
if (!project) {
|
|
if (openProject !== undefined && openProject !== DRAFT_PROJECT.name) {
|
|
console.warn(`Cannot find project ${openProject} to open`)
|
|
}
|
|
return DRAFT_PROJECT
|
|
}
|
|
if (project.storage === undefined) {
|
|
try {
|
|
const projectRoot = `${PROJECTS_URI}${hexId()}/`
|
|
console.log(`Upgrading project ${openProject} to IndexedDB at ${projectRoot}`)
|
|
await SpyglassClient.FS.mkdir(projectRoot)
|
|
if (project.files) {
|
|
await Promise.all(project.files.map(async file => {
|
|
const gen = config.generators.find(g => g.id === file.type)
|
|
if (!gen) {
|
|
console.warn(`Could not upgrade file ${file.id} of type ${file.type}, no generator found!`)
|
|
return
|
|
}
|
|
const type = genPath(gen, project.version ?? DEFAULT_VERSION)
|
|
const { namespace, path } = Identifier.parse(file.id)
|
|
const uri = type === 'pack_mcmeta'
|
|
? `${projectRoot}data/pack.mcmeta`
|
|
: `${projectRoot}data/${namespace}/${type}/${path}.json`
|
|
return SpyglassClient.FS.writeFile(uri, JSON.stringify(file.data, null, 2))
|
|
}))
|
|
}
|
|
if (project.unknownFiles) {
|
|
await Promise.all(project.unknownFiles.map(async file => {
|
|
const uri = projectRoot + file.path
|
|
return SpyglassClient.FS.writeFile(uri, file.data)
|
|
}))
|
|
}
|
|
changeProjects(projects.map(p => p === project ? { ...p, storage: { type: 'indexeddb', rootUri: projectRoot } } : p))
|
|
} catch (e) {
|
|
console.error(`Something went wrong upgrading project ${openProject}: ${message(e)}`)
|
|
return DRAFT_PROJECT
|
|
}
|
|
}
|
|
return project
|
|
}, [projects, openProject])
|
|
|
|
const [projectUri, setProjectUri] = useState<string>()
|
|
|
|
const changeProjects = useCallback((projects: ProjectMeta[]) => {
|
|
Store.setProjects(projects)
|
|
setProjects(projects)
|
|
}, [])
|
|
|
|
const createProject = useCallback((project: ProjectMeta) => {
|
|
changeProjects([...projects, project])
|
|
}, [projects])
|
|
|
|
const deleteProject = useCallback(async (name: string) => {
|
|
if (name === DRAFT_PROJECT.name) return
|
|
const project = projects.find(p => p.name === name)
|
|
if (project) {
|
|
const projectRoot = getProjectRoot(project)
|
|
const entries = await SpyglassClient.FS.readdir(projectRoot)
|
|
await Promise.all(entries.map(async e => SpyglassClient.FS.unlink(e.name)))
|
|
}
|
|
changeProjects(projects.filter(p => p.name !== name))
|
|
setOpenProject(DRAFT_PROJECT.name)
|
|
}, [projects])
|
|
|
|
const changeProject = useCallback((name: string) => {
|
|
Store.setOpenProject(name)
|
|
setOpenProject(name)
|
|
}, [])
|
|
|
|
const updateProject = useCallback((edits: Partial<ProjectMeta>) => {
|
|
changeProjects(projects.map(p => p.name === openProject ? { ...p, ...edits } : p))
|
|
}, [projects, openProject])
|
|
|
|
const value: ProjectContext = {
|
|
projects,
|
|
project: project ?? DRAFT_PROJECT,
|
|
projectUri,
|
|
setProjectUri,
|
|
createProject,
|
|
changeProject,
|
|
deleteProject,
|
|
updateProject,
|
|
}
|
|
|
|
return <Project.Provider value={value}>
|
|
{children}
|
|
</Project.Provider>
|
|
}
|
|
|
|
export function getProjectRoot(project: ProjectMeta) {
|
|
if (project.storage?.type === 'indexeddb') {
|
|
return project.storage.rootUri
|
|
}
|
|
throw new Error(`Unsupported project storage ${project.storage?.type}`)
|
|
}
|
|
|
|
export async function getWorldgenProjectData(project: ProjectMeta): Promise<ProjectData> {
|
|
const projectRoot = getProjectRoot(project)
|
|
const categories = ['worldgen/noise_settings', 'worldgen/noise', 'worldgen/density_function']
|
|
const result: ProjectData = Object.fromEntries(categories.map(c => [c, {}]))
|
|
const entries = await SpyglassClient.FS.readdir(projectRoot)
|
|
for (const entry of entries) {
|
|
for (const category of categories) {
|
|
if (entry.name.includes(category)) {
|
|
const pattern = RegExp(`data/([a-z0-9_.-]+)/${category}/([a-z0-9_./-]+).json$`)
|
|
const match = entry.name.match(pattern)
|
|
if (match) {
|
|
const data = await SpyglassClient.FS.readFile(entry.name)
|
|
const text = new TextDecoder().decode(data)
|
|
result[category][`${match[1]}:${match[2]}`] = safeJsonParse(text)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|