mirror of
https://github.com/misode/misode.github.io.git
synced 2026-04-30 17:49:34 +00:00
Use spyglass DocAndNode to store current file data
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import config from '../config.json'
|
||||
import type { VersionId } from './services/Schemas.js'
|
||||
import type { VersionId } from './services/Versions.js'
|
||||
|
||||
export interface ConfigLanguage {
|
||||
code: string,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Identifier } from 'deepslate'
|
||||
import { deepClone, deepEqual } from '../../Utils.js'
|
||||
import { fetchAllPresets, fetchBlockStates } from '../../services/DataFetcher.js'
|
||||
import type { VersionId } from '../../services/Schemas.js'
|
||||
import type { VersionId } from '../../services/Versions.js'
|
||||
import { deepClone, deepEqual } from '../../Utils.js'
|
||||
import type { CustomizedOreModel } from './CustomizedModel.js'
|
||||
import { CustomizedModel } from './CustomizedModel.js'
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { VersionId } from '../../services/Schemas.js'
|
||||
import type { VersionId } from '../../services/Versions.js'
|
||||
|
||||
export interface CustomizedOreModel {
|
||||
size: number,
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import type { DocAndNode } from '@spyglassmc/core'
|
||||
import { useState } from 'preact/hooks'
|
||||
import { Analytics } from '../../Analytics.js'
|
||||
import { useLocale, useProject } from '../../contexts/index.js'
|
||||
import type { FileModel } from '../../services/index.js'
|
||||
import { Btn } from '../Btn.js'
|
||||
import { TextInput } from '../forms/index.js'
|
||||
import { Modal } from '../Modal.js'
|
||||
|
||||
interface Props {
|
||||
model: FileModel,
|
||||
docAndNode: DocAndNode,
|
||||
id: string,
|
||||
method: string,
|
||||
onClose: () => void,
|
||||
}
|
||||
export function FileCreation({ model, id, method, onClose }: Props) {
|
||||
export function FileCreation({ docAndNode, id, method, onClose }: Props) {
|
||||
const { locale } = useLocale()
|
||||
const { projects, project, updateFile } = useProject()
|
||||
const [fileId, setFileId] = useState(id === 'pack_mcmeta' ? 'pack' : '')
|
||||
@@ -29,7 +29,8 @@ export function FileCreation({ model, id, method, onClose }: Props) {
|
||||
return
|
||||
}
|
||||
Analytics.saveProjectFile(id, projects.length, project.files.length, method as any)
|
||||
updateFile(id, undefined, { type: id, id: fileId, data: model.data })
|
||||
const data = JSON.parse(docAndNode.doc.getText())
|
||||
updateFile(id, undefined, { type: id, id: fileId, data })
|
||||
onClose()
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ import { useMemo } from 'preact/hooks'
|
||||
import type { ConfigGenerator } from '../../Config.js'
|
||||
import config from '../../Config.js'
|
||||
import { useLocale } from '../../contexts/Locale.jsx'
|
||||
import type { VersionId } from '../../services/Schemas.js'
|
||||
import { checkVersion } from '../../services/Schemas.js'
|
||||
import type { VersionId } from '../../services/Versions.js'
|
||||
import { checkVersion } from '../../services/Versions.js'
|
||||
import { cleanUrl } from '../../Utils.js'
|
||||
import { Badge, Card, Icons, ToolCard } from '../index.js'
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useMemo, useState } from 'preact/hooks'
|
||||
import type { ConfigGenerator } from '../../Config.js'
|
||||
import config from '../../Config.js'
|
||||
import { useLocale, useVersion } from '../../contexts/index.js'
|
||||
import { checkVersion } from '../../services/Schemas.js'
|
||||
import { checkVersion } from '../../services/Versions.js'
|
||||
import { GeneratorCard, TextInput, VersionSwitcher } from '../index.js'
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -1,59 +1,59 @@
|
||||
import type { DocAndNode } from '@spyglassmc/core'
|
||||
import { useVersion } from '../../contexts/Version.jsx'
|
||||
import type { FileModel } from '../../services/index.js'
|
||||
import { checkVersion } from '../../services/index.js'
|
||||
import { BiomeSourcePreview, BlockStatePreview, DecoratorPreview, DensityFunctionPreview, LootTablePreview, ModelPreview, NoisePreview, NoiseSettingsPreview, RecipePreview, StructureSetPreview } from '../previews/index.js'
|
||||
|
||||
export const HasPreview = ['loot_table', 'recipe', 'dimension', 'worldgen/density_function', 'worldgen/noise', 'worldgen/noise_settings', 'worldgen/configured_feature', 'worldgen/placed_feature', 'worldgen/structure_set', 'block_definition', 'model']
|
||||
export const HasPreview = ['loot_table', 'recipe', 'dimension', 'worldgen/density_function', 'worldgen/noise', 'worldgen/noise_settings', 'worldgen/configured_feature', 'worldgen/placed_feature', 'worldgen/structure_set', 'block_definition', 'docAndNode']
|
||||
|
||||
type PreviewPanelProps = {
|
||||
model: FileModel | undefined,
|
||||
docAndNode: DocAndNode | undefined,
|
||||
id: string,
|
||||
shown: boolean,
|
||||
onError: (message: string) => unknown,
|
||||
}
|
||||
export function PreviewPanel({ model, id, shown }: PreviewPanelProps) {
|
||||
export function PreviewPanel({ docAndNode, id, shown }: PreviewPanelProps) {
|
||||
const { version } = useVersion()
|
||||
|
||||
if (!model) return <></>
|
||||
if (!docAndNode) return <></>
|
||||
|
||||
if (id === 'loot_table') {
|
||||
return <LootTablePreview {...{ model, shown }} />
|
||||
return <LootTablePreview {...{ docAndNode, shown }} />
|
||||
}
|
||||
|
||||
if (id === 'recipe') {
|
||||
return <RecipePreview {...{ model, shown }} />
|
||||
return <RecipePreview {...{ docAndNode, shown }} />
|
||||
}
|
||||
|
||||
if (id === 'dimension' && model.data.generator?.type?.endsWith('noise')) {
|
||||
return <BiomeSourcePreview {...{ model, shown }} />
|
||||
if (id === 'dimension' && JSON.parse(docAndNode.doc.getText()).generator?.type?.endsWith('noise')) {
|
||||
return <BiomeSourcePreview {...{ docAndNode, shown }} />
|
||||
}
|
||||
|
||||
if (id === 'worldgen/density_function') {
|
||||
return <DensityFunctionPreview {...{ model, shown }} />
|
||||
return <DensityFunctionPreview {...{ docAndNode, shown }} />
|
||||
}
|
||||
|
||||
if (id === 'worldgen/noise') {
|
||||
return <NoisePreview {...{ model, shown }} />
|
||||
return <NoisePreview {...{ docAndNode, shown }} />
|
||||
}
|
||||
|
||||
if (id === 'worldgen/noise_settings' && checkVersion(version, '1.18')) {
|
||||
return <NoiseSettingsPreview {...{ model, shown }} />
|
||||
return <NoiseSettingsPreview {...{ docAndNode, shown }} />
|
||||
}
|
||||
|
||||
if ((id === 'worldgen/placed_feature' || (id === 'worldgen/configured_feature' && checkVersion(version, '1.16', '1.17')))) {
|
||||
return <DecoratorPreview {...{ model, shown }} />
|
||||
return <DecoratorPreview {...{ docAndNode, shown }} />
|
||||
}
|
||||
|
||||
if (id === 'worldgen/structure_set' && checkVersion(version, '1.19')) {
|
||||
return <StructureSetPreview {...{ model, shown }} />
|
||||
return <StructureSetPreview {...{ docAndNode, shown }} />
|
||||
}
|
||||
|
||||
if (id === 'block_definition') {
|
||||
return <BlockStatePreview {...{ model, shown }} />
|
||||
return <BlockStatePreview {...{ docAndNode, shown }} />
|
||||
}
|
||||
|
||||
if (id === 'model') {
|
||||
return <ModelPreview {...{ model, shown }} />
|
||||
return <ModelPreview {...{ docAndNode, shown }} />
|
||||
}
|
||||
|
||||
return <></>
|
||||
|
||||
@@ -5,11 +5,11 @@ import type { ConfigGenerator } from '../../Config.js'
|
||||
import config from '../../Config.js'
|
||||
import { DRAFT_PROJECT, useLocale, useProject, useVersion } from '../../contexts/index.js'
|
||||
import { AsyncCancel, useActiveTimeout, useAsync, useSearchParam } from '../../hooks/index.js'
|
||||
import type { FileModel, VersionId } from '../../services/index.js'
|
||||
import { checkVersion, createMockFileModel, fetchPreset, fetchRegistries, getSnippet, shareSnippet } from '../../services/index.js'
|
||||
import { setupSpyglass } from '../../services/Spyglass.js'
|
||||
import type { VersionId } from '../../services/index.js'
|
||||
import { checkVersion, fetchPreset, fetchRegistries, getSnippet, shareSnippet } from '../../services/index.js'
|
||||
import { Spyglass } from '../../services/Spyglass.js'
|
||||
import { Store } from '../../Store.js'
|
||||
import { cleanUrl, deepEqual, genPath } 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'
|
||||
|
||||
export const SHARE_KEY = 'share'
|
||||
@@ -31,10 +31,15 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
|
||||
|
||||
useEffect(() => Store.visitGenerator(gen.id), [gen.id])
|
||||
|
||||
useEffect(() => {
|
||||
setupSpyglass(version)
|
||||
const { value: spyglass, loading: spyglassLoading } = useAsync(() => {
|
||||
return Spyglass.initialize(version)
|
||||
}, [version])
|
||||
|
||||
const uri = useMemo(() => {
|
||||
// TODO: return different uri when project file is open
|
||||
return spyglass?.getUnsavedFileUri(gen)
|
||||
}, [spyglass, gen.id])
|
||||
|
||||
const [currentPreset, setCurrentPreset] = useSearchParam('preset')
|
||||
const [sharedSnippetId, setSharedSnippetId] = useSearchParam(SHARE_KEY)
|
||||
const backup = useMemo(() => Store.getBackup(gen.id), [gen.id])
|
||||
@@ -45,7 +50,10 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
|
||||
}
|
||||
}
|
||||
|
||||
const {} = useAsync(async () => {
|
||||
const { value: docAndNode } = useAsync(async () => {
|
||||
if (spyglassLoading || !spyglass || !uri) {
|
||||
return AsyncCancel
|
||||
}
|
||||
let data: unknown = undefined
|
||||
if (currentPreset && sharedSnippetId) {
|
||||
setSharedSnippetId(undefined)
|
||||
@@ -83,14 +91,12 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
|
||||
}
|
||||
data = file.data
|
||||
}
|
||||
if (data) {
|
||||
// TODO: set file contents to data
|
||||
}
|
||||
const docAndNode = await spyglass.setFileContents(uri, JSON.stringify(data ?? {}))
|
||||
Analytics.setGenerator(gen.id)
|
||||
return {}
|
||||
}, [gen.id, version, sharedSnippetId, currentPreset, project.name, file?.id])
|
||||
return docAndNode
|
||||
}, [gen.id, version, sharedSnippetId, currentPreset, project.name, file?.id, spyglass, spyglassLoading])
|
||||
|
||||
const model: FileModel = createMockFileModel()
|
||||
const { doc } = docAndNode ?? {}
|
||||
|
||||
// TODO: when contents of file change:
|
||||
// - remove preset and share id from url
|
||||
@@ -184,13 +190,13 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
|
||||
setShareShown(true)
|
||||
copySharedId()
|
||||
} else {
|
||||
// TODO: get contents from file, and compare to default of type
|
||||
if (deepEqual(model.data, {})) {
|
||||
// TODO: check if files hasn't been modified compared to the default
|
||||
if (false) {
|
||||
setShareUrl(`${location.origin}/${gen.url}/?version=${version}`)
|
||||
setShareShown(true)
|
||||
} else {
|
||||
} else if (doc) {
|
||||
setShareLoading(true)
|
||||
shareSnippet(gen.id, version, model.data, previewShown)
|
||||
shareSnippet(gen.id, version, JSON.parse(doc.getText()), previewShown)
|
||||
.then(({ id, length, compressed, rate }) => {
|
||||
Analytics.createSnippet(gen.id, id, version, length, compressed, rate)
|
||||
const url = `${location.origin}/${gen.url}/?${SHARE_KEY}=${id}`
|
||||
@@ -306,7 +312,7 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
|
||||
</BtnMenu>
|
||||
</div>
|
||||
{error && <ErrorPanel error={error} onDismiss={() => setError(null)} />}
|
||||
<Tree model={model} onError={setError} />
|
||||
{docAndNode && <Tree docAndNode={docAndNode} onError={setError} />}
|
||||
<Footer donate={!gen.tags?.includes('partners')} />
|
||||
</main>
|
||||
<div class="popup-actions right-actions" style={`--offset: -${8 + actionsShown * 50}px;`}>
|
||||
@@ -327,10 +333,10 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
|
||||
</div>
|
||||
</div>
|
||||
<div class={`popup-preview${previewShown ? ' shown' : ''}`}>
|
||||
<PreviewPanel model={model} id={gen.id} shown={previewShown} onError={setError} />
|
||||
<PreviewPanel docAndNode={docAndNode} id={gen.id} shown={previewShown} onError={setError} />
|
||||
</div>
|
||||
<div class={`popup-source${sourceShown ? ' shown' : ''}`}>
|
||||
<SourcePanel {...{model, doCopy, doDownload, doImport}} name={gen.schema ?? 'data'} copySuccess={copySuccess} onError={setError} />
|
||||
<SourcePanel spyglass={spyglass} docAndNode={docAndNode} {...{doCopy, doDownload, doImport}} copySuccess={copySuccess} onError={setError} />
|
||||
</div>
|
||||
<div class={`popup-share${shareShown ? ' shown' : ''}`}>
|
||||
<TextInput value={shareUrl} readonly />
|
||||
@@ -346,7 +352,7 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
|
||||
</div>
|
||||
{projectCreating && <ProjectCreation onClose={() => setProjectCreating(false)} />}
|
||||
{projectDeleting && <ProjectDeletion onClose={() => setprojectDeleting(false)} />}
|
||||
{model && fileSaving && <FileCreation id={gen.id} model={model} method={fileSaving} onClose={() => setFileSaving(undefined)} />}
|
||||
{docAndNode && fileSaving && <FileCreation id={gen.id} docAndNode={docAndNode} method={fileSaving} onClose={() => setFileSaving(undefined)} />}
|
||||
{fileRenaming && <FileRenaming id={fileRenaming.type } name={fileRenaming.id} onClose={() => setFileRenaming(undefined)} />}
|
||||
</>
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
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 { useLocalStorage } from '../../hooks/index.js'
|
||||
import type { FileModel } from '../../services/index.js'
|
||||
import { getSourceFormats, getSourceIndent, getSourceIndents, parseSource, sortData, stringifySource } from '../../services/index.js'
|
||||
import type { Spyglass } from '../../services/Spyglass.js'
|
||||
import { Store } from '../../Store.js'
|
||||
import { message } from '../../Utils.js'
|
||||
import { Btn, BtnMenu } from '../index.js'
|
||||
@@ -15,15 +17,15 @@ interface Editor {
|
||||
}
|
||||
|
||||
type SourcePanelProps = {
|
||||
name: string,
|
||||
model: FileModel | undefined,
|
||||
spyglass: Spyglass | undefined,
|
||||
docAndNode: DocAndNode | undefined,
|
||||
doCopy?: number,
|
||||
doDownload?: number,
|
||||
doImport?: number,
|
||||
copySuccess: () => unknown,
|
||||
onError: (message: string | Error) => unknown,
|
||||
}
|
||||
export function SourcePanel({ name, model, doCopy, doDownload, doImport, copySuccess, onError }: SourcePanelProps) {
|
||||
export function SourcePanel({ spyglass, docAndNode, doCopy, doDownload, doImport, copySuccess, onError }: SourcePanelProps) {
|
||||
const { locale } = useLocale()
|
||||
const [indent, setIndent] = useState(Store.getIndent())
|
||||
const [format, setFormat] = useState(Store.getFormat())
|
||||
@@ -37,20 +39,23 @@ export function SourcePanel({ name, model, doCopy, doDownload, doImport, copySuc
|
||||
const textarea = useRef<HTMLTextAreaElement>(null)
|
||||
const editor = useRef<Editor>()
|
||||
|
||||
const getSerializedOutput = useCallback((model: FileModel) => {
|
||||
let data = model.data
|
||||
const getSerializedOutput = useCallback((text: string) => {
|
||||
let data = JSON.parse(text)
|
||||
if (sort === 'alphabetically') {
|
||||
data = sortData(data)
|
||||
}
|
||||
return stringifySource(data, format, indent)
|
||||
}, [indent, format, sort])
|
||||
|
||||
const text = docAndNode?.doc.getText()
|
||||
|
||||
useEffect(() => {
|
||||
retransform.current = () => {
|
||||
if (!editor.current) return
|
||||
if (!model) return
|
||||
if (!editor.current || text === undefined) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
const output = getSerializedOutput(model)
|
||||
const output = getSerializedOutput(text)
|
||||
editor.current.setValue(output)
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
@@ -68,9 +73,10 @@ export function SourcePanel({ name, model, doCopy, doDownload, doImport, copySuc
|
||||
if (!editor.current) return
|
||||
const value = editor.current.getValue()
|
||||
if (value.length === 0) return
|
||||
if (!spyglass || !docAndNode) return
|
||||
try {
|
||||
await parseSource(value, format)
|
||||
// TODO: import
|
||||
const data = await parseSource(value, format)
|
||||
await spyglass.setFileContents(docAndNode.doc.uri, JSON.stringify(data))
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
e.message = `Error importing: ${e.message}`
|
||||
@@ -81,7 +87,7 @@ export function SourcePanel({ name, model, doCopy, doDownload, doImport, copySuc
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
}, [model, indent, format, sort, highlighting])
|
||||
}, [spyglass, docAndNode, text, indent, format, sort, highlighting])
|
||||
|
||||
useEffect(() => {
|
||||
if (highlighting) {
|
||||
@@ -144,9 +150,10 @@ export function SourcePanel({ name, model, doCopy, doDownload, doImport, copySuc
|
||||
|
||||
// TODO: when file contents change, retransform
|
||||
useEffect(() => {
|
||||
if (!retransform.current) return
|
||||
if (model) retransform.current()
|
||||
}, [model])
|
||||
if (retransform.current && text !== undefined) {
|
||||
retransform.current()
|
||||
}
|
||||
}, [text])
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor.current || !retransform.current) return
|
||||
@@ -157,18 +164,18 @@ export function SourcePanel({ name, model, doCopy, doDownload, doImport, copySuc
|
||||
}, [indent, format, sort, highlighting, braceLoaded])
|
||||
|
||||
useEffect(() => {
|
||||
if (doCopy && model) {
|
||||
navigator.clipboard.writeText(getSerializedOutput(model)).then(() => {
|
||||
if (doCopy && text !== undefined) {
|
||||
navigator.clipboard.writeText(getSerializedOutput(text)).then(() => {
|
||||
copySuccess()
|
||||
})
|
||||
}
|
||||
}, [doCopy])
|
||||
}, [doCopy, text])
|
||||
|
||||
useEffect(() => {
|
||||
if (doDownload && model && download.current) {
|
||||
const content = encodeURIComponent(getSerializedOutput(model))
|
||||
if (doDownload && docAndNode && text !== undefined && download.current) {
|
||||
const content = encodeURIComponent(getSerializedOutput(text))
|
||||
download.current.setAttribute('href', `data:text/json;charset=utf-8,${content}`)
|
||||
const fileName = name === 'pack_mcmeta' ? 'pack.mcmeta' : `${name}.${format}`
|
||||
const fileName = fileUtil.basename(docAndNode.doc.uri)
|
||||
download.current.setAttribute('download', fileName)
|
||||
download.current.click()
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import type { DocAndNode } from '@spyglassmc/core'
|
||||
import { useErrorBoundary } from 'preact/hooks'
|
||||
import { useLocale } from '../../contexts/index.js'
|
||||
import type { FileModel } from '../../services/index.js'
|
||||
|
||||
type TreePanelProps = {
|
||||
model: FileModel | undefined,
|
||||
docAndNode: DocAndNode,
|
||||
onError: (message: string) => unknown,
|
||||
}
|
||||
export function Tree({ model, onError }: TreePanelProps) {
|
||||
export function Tree({ onError }: TreePanelProps) {
|
||||
const { lang } = useLocale()
|
||||
if (!model || lang === 'none') return <></>
|
||||
if (lang === 'none') return <></>
|
||||
|
||||
const [error] = useErrorBoundary(e => {
|
||||
onError(`Error rendering the tree: ${e.message}`)
|
||||
|
||||
@@ -3,7 +3,7 @@ import { mat3 } from 'gl-matrix'
|
||||
import { useCallback, useRef, useState } from 'preact/hooks'
|
||||
import { getProjectData, useLocale, useProject, useStore, useVersion } from '../../contexts/index.js'
|
||||
import { useAsync } from '../../hooks/index.js'
|
||||
import { checkVersion } from '../../services/Schemas.js'
|
||||
import { checkVersion } from '../../services/Versions.js'
|
||||
import { Store } from '../../Store.js'
|
||||
import { iterateWorld2D, randomSeed, stringToColor } from '../../Utils.js'
|
||||
import { Btn, BtnMenu, NumberInput } from '../index.js'
|
||||
@@ -20,7 +20,7 @@ type Layer = typeof LAYERS[number]
|
||||
const DETAIL_DELAY = 300
|
||||
const DETAIL_SCALE = 2
|
||||
|
||||
export const BiomeSourcePreview = ({ model, shown }: PreviewProps) => {
|
||||
export const BiomeSourcePreview = ({ docAndNode, shown }: PreviewProps) => {
|
||||
const { locale } = useLocale()
|
||||
const { version } = useVersion()
|
||||
const { project } = useProject()
|
||||
@@ -31,18 +31,19 @@ export const BiomeSourcePreview = ({ model, shown }: PreviewProps) => {
|
||||
const [focused, setFocused] = useState<string[]>([])
|
||||
const [focused2, setFocused2] = useState<string[]>([])
|
||||
|
||||
const state = JSON.stringify(model.data)
|
||||
const type: string = model.data?.generator?.biome_source?.type?.replace(/^minecraft:/, '') ?? ''
|
||||
const text = docAndNode.doc.getText()
|
||||
const data = JSON.parse(text)
|
||||
const type: string = data?.generator?.biome_source?.type?.replace(/^minecraft:/, '') ?? ''
|
||||
const hasRandomness = type === 'multi_noise' || type === 'the_end'
|
||||
|
||||
const { value } = useAsync(async function loadBiomeSource() {
|
||||
await DEEPSLATE.loadVersion(version, getProjectData(project))
|
||||
await DEEPSLATE.loadChunkGenerator(model.data?.generator?.settings, model.data?.generator?.biome_source, seed)
|
||||
await DEEPSLATE.loadChunkGenerator(data?.generator?.settings, data?.generator?.biome_source, seed)
|
||||
return {
|
||||
biomeSource: { loaded: true },
|
||||
noiseRouter: checkVersion(version, '1.19') ? DEEPSLATE.getNoiseRouter() : undefined,
|
||||
}
|
||||
}, [state, seed, project, version])
|
||||
}, [text, seed, project, version])
|
||||
const { biomeSource, noiseRouter } = value ?? {}
|
||||
|
||||
const actualLayer = noiseRouter ? layer : 'biomes'
|
||||
|
||||
@@ -10,14 +10,15 @@ import { InteractiveCanvas3D } from './InteractiveCanvas3D.jsx'
|
||||
|
||||
const PREVIEW_ID = Identifier.parse('misode:preview')
|
||||
|
||||
export const BlockStatePreview = ({ model, shown }: PreviewProps) => {
|
||||
export const BlockStatePreview = ({ docAndNode, shown }: PreviewProps) => {
|
||||
const { version } = useVersion()
|
||||
const serializedData = JSON.stringify(model.data)
|
||||
|
||||
const text = docAndNode.doc.getText()
|
||||
|
||||
const { value: resources } = useAsync(async () => {
|
||||
if (!shown) return AsyncCancel
|
||||
const resources = await getResources(version)
|
||||
const definition = BlockDefinition.fromJson(model.data)
|
||||
const definition = BlockDefinition.fromJson(JSON.parse(text))
|
||||
const wrapper = new ResourceWrapper(resources, {
|
||||
getBlockDefinition(id) {
|
||||
if (id.equals(PREVIEW_ID)) return definition
|
||||
@@ -25,7 +26,7 @@ export const BlockStatePreview = ({ model, shown }: PreviewProps) => {
|
||||
},
|
||||
})
|
||||
return wrapper
|
||||
}, [shown, version, serializedData])
|
||||
}, [shown, version, text])
|
||||
|
||||
const renderer = useRef<StructureRenderer | undefined>(undefined)
|
||||
|
||||
|
||||
@@ -9,11 +9,12 @@ import { decorateChunk } from './Decorator.js'
|
||||
import type { PreviewProps } from './index.js'
|
||||
import { InteractiveCanvas2D } from './InteractiveCanvas2D.jsx'
|
||||
|
||||
export const DecoratorPreview = ({ model, shown }: PreviewProps) => {
|
||||
export const DecoratorPreview = ({ docAndNode, shown }: PreviewProps) => {
|
||||
const { locale } = useLocale()
|
||||
const { version } = useVersion()
|
||||
const [seed, setSeed] = useState(randomSeed())
|
||||
const state = JSON.stringify(model.data)
|
||||
|
||||
const text = docAndNode.doc.getText()
|
||||
|
||||
const { context, chunkFeatures } = useMemo(() => {
|
||||
const random = new LegacyRandom(seed)
|
||||
@@ -32,7 +33,7 @@ export const DecoratorPreview = ({ model, shown }: PreviewProps) => {
|
||||
context,
|
||||
chunkFeatures: new Map<string, PlacedFeature[]>(),
|
||||
}
|
||||
}, [state, version, seed])
|
||||
}, [text, version, seed])
|
||||
|
||||
const ctx = useRef<CanvasRenderingContext2D>()
|
||||
const imageData = useRef<ImageData>()
|
||||
@@ -49,10 +50,10 @@ export const DecoratorPreview = ({ model, shown }: PreviewProps) => {
|
||||
}, [])
|
||||
const onDraw = useCallback(function onDraw(transform: mat3) {
|
||||
if (!ctx.current || !imageData.current || !shown) return
|
||||
|
||||
const data = JSON.parse(text)
|
||||
iterateWorld2D(imageData.current, transform, (x, y) => {
|
||||
const pos = ChunkPos.create(Math.floor(x / 16), Math.floor(-y / 16))
|
||||
const features = computeIfAbsent(chunkFeatures, `${pos[0]} ${pos[1]}`, () => decorateChunk(pos, model.data, context))
|
||||
const features = computeIfAbsent(chunkFeatures, `${pos[0]} ${pos[1]}`, () => decorateChunk(pos, data, context))
|
||||
return features.find(f => f.pos[0] === x && f.pos[2] == -y) ?? { pos: BlockPos.create(x, 0, -y) }
|
||||
}, (feature) => {
|
||||
if ('color' in feature) {
|
||||
|
||||
@@ -18,7 +18,7 @@ import { InteractiveCanvas3D } from './InteractiveCanvas3D.jsx'
|
||||
|
||||
const MODES = ['side', 'top', '3d'] as const
|
||||
|
||||
export const DensityFunctionPreview = ({ model, shown }: PreviewProps) => {
|
||||
export const DensityFunctionPreview = ({ docAndNode, shown }: PreviewProps) => {
|
||||
const { locale } = useLocale()
|
||||
const { project } = useProject()
|
||||
const { version } = useVersion()
|
||||
@@ -28,13 +28,14 @@ export const DensityFunctionPreview = ({ model, shown }: PreviewProps) => {
|
||||
const [seed, setSeed] = useState(randomSeed())
|
||||
const [minY] = useState(0)
|
||||
const [height] = useState(256)
|
||||
const serializedData = JSON.stringify(model.data)
|
||||
|
||||
const text = docAndNode.doc.getText()
|
||||
|
||||
const { value: df } = useAsync(async () => {
|
||||
await DEEPSLATE.loadVersion(version, getProjectData(project))
|
||||
const df = DEEPSLATE.loadDensityFunction(model.data, minY, height, seed)
|
||||
const df = DEEPSLATE.loadDensityFunction(JSON.parse(text), minY, height, seed)
|
||||
return df
|
||||
}, [version, project, minY, height, seed, serializedData])
|
||||
}, [version, project, minY, height, seed, text])
|
||||
|
||||
// === 2D ===
|
||||
const imageData = useRef<ImageData>()
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { Random } from 'deepslate/core'
|
||||
import { Identifier, ItemStack, LegacyRandom } from 'deepslate/core'
|
||||
import { NbtCompound, NbtInt, NbtList, NbtString, NbtTag } from 'deepslate/nbt'
|
||||
import { ResolvedItem } from '../../services/ResolvedItem.js'
|
||||
import type { VersionId } from '../../services/Schemas.js'
|
||||
import type { VersionId } from '../../services/Versions.js'
|
||||
import { clamp, getWeightedRandom, isObject, jsonToNbt } from '../../Utils.js'
|
||||
|
||||
export interface SlottedItem {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Random } from 'deepslate-1.20.4/core'
|
||||
import { Enchantment, Identifier, ItemStack, LegacyRandom } from 'deepslate-1.20.4/core'
|
||||
import { NbtCompound, NbtInt, NbtList, NbtShort, NbtString, NbtTag, NbtType } from 'deepslate-1.20.4/nbt'
|
||||
import type { VersionId } from '../../services/Schemas.js'
|
||||
import type { VersionId } from '../../services/Versions.js'
|
||||
import { clamp, getWeightedRandom, isObject } from '../../Utils.js'
|
||||
|
||||
export interface SlottedItem {
|
||||
|
||||
@@ -11,7 +11,7 @@ import type { PreviewProps } from './index.js'
|
||||
import { generateLootTable } from './LootTable.js'
|
||||
import { generateLootTable as generateLootTable1204 } from './LootTable1204.js'
|
||||
|
||||
export const LootTablePreview = ({ model }: PreviewProps) => {
|
||||
export const LootTablePreview = ({ docAndNode }: PreviewProps) => {
|
||||
const { locale } = useLocale()
|
||||
const { version } = useVersion()
|
||||
const use1204 = !checkVersion(version, '1.20.5')
|
||||
@@ -34,13 +34,13 @@ export const LootTablePreview = ({ model }: PreviewProps) => {
|
||||
])
|
||||
}, [version])
|
||||
|
||||
const table = model.data
|
||||
const state = JSON.stringify(table)
|
||||
const text = docAndNode.doc.getText()
|
||||
const items = useMemo(() => {
|
||||
if (dependencies === undefined || loading) {
|
||||
return []
|
||||
}
|
||||
const [itemTags, lootTables, itemComponents, enchantments, enchantmentTags] = dependencies
|
||||
const table = JSON.parse(text)
|
||||
if (use1204) {
|
||||
return generateLootTable1204(table, {
|
||||
version, seed, luck, daytime, weather,
|
||||
@@ -60,7 +60,7 @@ export const LootTablePreview = ({ model }: PreviewProps) => {
|
||||
getEnchantmentTag: (id) => (enchantmentTags?.get(id.replace(/^minecraft:/, '')) as any)?.values ?? [],
|
||||
getBaseComponents: (id) => new Map([...(itemComponents?.get(Identifier.parse(id).toString()) ?? new Map()).entries()].map(([k, v]) => [k, jsonToNbt(v)])),
|
||||
})
|
||||
}, [version, seed, luck, daytime, weather, mixItems, state, dependencies, loading])
|
||||
}, [version, seed, luck, daytime, weather, mixItems, text, dependencies, loading])
|
||||
|
||||
return <>
|
||||
<div ref={overlay} class="preview-overlay">
|
||||
|
||||
@@ -11,14 +11,15 @@ import { InteractiveCanvas3D } from './InteractiveCanvas3D.jsx'
|
||||
const PREVIEW_ID = Identifier.parse('misode:preview')
|
||||
const PREVIEW_DEFINITION = new BlockDefinition({ '': { model: PREVIEW_ID.toString() }}, undefined)
|
||||
|
||||
export const ModelPreview = ({ model, shown }: PreviewProps) => {
|
||||
export const ModelPreview = ({ docAndNode, shown }: PreviewProps) => {
|
||||
const { version } = useVersion()
|
||||
const serializedData = JSON.stringify(model.data)
|
||||
|
||||
const text = docAndNode.doc.getText()
|
||||
|
||||
const { value: resources } = useAsync(async () => {
|
||||
if (!shown) return AsyncCancel
|
||||
const resources = await getResources(version)
|
||||
const blockModel = BlockModel.fromJson(model.data)
|
||||
const blockModel = BlockModel.fromJson(JSON.parse(text))
|
||||
blockModel.flatten(resources)
|
||||
const wrapper = new ResourceWrapper(resources, {
|
||||
getBlockDefinition(id) {
|
||||
@@ -31,7 +32,7 @@ export const ModelPreview = ({ model, shown }: PreviewProps) => {
|
||||
},
|
||||
})
|
||||
return wrapper
|
||||
}, [shown, version, serializedData])
|
||||
}, [shown, version, text])
|
||||
|
||||
const renderer = useRef<StructureRenderer | undefined>(undefined)
|
||||
|
||||
|
||||
@@ -11,16 +11,17 @@ import { ColormapSelector } from './ColormapSelector.jsx'
|
||||
import type { PreviewProps } from './index.js'
|
||||
import { InteractiveCanvas2D } from './InteractiveCanvas2D.jsx'
|
||||
|
||||
export const NoisePreview = ({ model, shown }: PreviewProps) => {
|
||||
export const NoisePreview = ({ docAndNode, shown }: PreviewProps) => {
|
||||
const { locale } = useLocale()
|
||||
const [seed, setSeed] = useState(randomSeed())
|
||||
const state = JSON.stringify(model.data)
|
||||
|
||||
const text = docAndNode.doc.getText()
|
||||
|
||||
const noise = useMemo(() => {
|
||||
const random = XoroshiroRandom.create(seed)
|
||||
const params = NoiseParameters.fromJson(model.data)
|
||||
const params = NoiseParameters.fromJson(JSON.parse(text))
|
||||
return new NormalNoise(random, params)
|
||||
}, [state, seed])
|
||||
}, [text, seed])
|
||||
|
||||
const imageData = useRef<ImageData>()
|
||||
const ctx = useRef<CanvasRenderingContext2D>()
|
||||
|
||||
@@ -15,24 +15,25 @@ import { DEEPSLATE } from './Deepslate.js'
|
||||
import type { PreviewProps } from './index.js'
|
||||
import { InteractiveCanvas2D } from './InteractiveCanvas2D.jsx'
|
||||
|
||||
export const NoiseSettingsPreview = ({ model, shown }: PreviewProps) => {
|
||||
export const NoiseSettingsPreview = ({ docAndNode, shown }: PreviewProps) => {
|
||||
const { locale } = useLocale()
|
||||
const { version } = useVersion()
|
||||
const { project } = useProject()
|
||||
const [seed, setSeed] = useState(randomSeed())
|
||||
const [biome, setBiome] = useState('minecraft:plains')
|
||||
const [layer, setLayer] = useState('terrain')
|
||||
const state = JSON.stringify(model.data)
|
||||
|
||||
const text = docAndNode.doc.getText()
|
||||
|
||||
const { value, error } = useAsync(async () => {
|
||||
const unwrapped = model.data
|
||||
const data = JSON.parse(text)
|
||||
await DEEPSLATE.loadVersion(version, getProjectData(project))
|
||||
const biomeSource = { type: 'fixed', biome }
|
||||
await DEEPSLATE.loadChunkGenerator(unwrapped, biomeSource, seed)
|
||||
await DEEPSLATE.loadChunkGenerator(data, biomeSource, seed)
|
||||
const noiseSettings = DEEPSLATE.getNoiseSettings()
|
||||
const finalDensity = DEEPSLATE.loadDensityFunction(unwrapped?.noise_router?.final_density, noiseSettings.minY, noiseSettings.height, seed)
|
||||
const finalDensity = DEEPSLATE.loadDensityFunction(data?.noise_router?.final_density, noiseSettings.minY, noiseSettings.height, seed)
|
||||
return { noiseSettings, finalDensity }
|
||||
}, [state, seed, version, project, biome])
|
||||
}, [text, seed, version, project, biome])
|
||||
const { noiseSettings, finalDensity } = value ?? {}
|
||||
|
||||
const imageData = useRef<ImageData>()
|
||||
|
||||
@@ -11,7 +11,7 @@ import type { PreviewProps } from './index.js'
|
||||
|
||||
const ANIMATION_TIME = 1000
|
||||
|
||||
export const RecipePreview = ({ model }: PreviewProps) => {
|
||||
export const RecipePreview = ({ docAndNode }: PreviewProps) => {
|
||||
const { locale } = useLocale()
|
||||
const { version } = useVersion()
|
||||
const [advancedTooltips, setAdvancedTooltips] = useState(true)
|
||||
@@ -29,11 +29,11 @@ export const RecipePreview = ({ model }: PreviewProps) => {
|
||||
return () => clearInterval(interval)
|
||||
}, [])
|
||||
|
||||
const recipe = model.data
|
||||
const state = JSON.stringify(recipe)
|
||||
const text = docAndNode.doc.getText()
|
||||
const recipe = JSON.parse(text)
|
||||
const items = useMemo<Map<Slot, ItemStack>>(() => {
|
||||
return placeItems(version, recipe, animation, itemTags ?? new Map())
|
||||
}, [state, animation, itemTags])
|
||||
}, [text, animation, itemTags])
|
||||
|
||||
const gui = useMemo(() => {
|
||||
const type = recipe.type?.replace(/^minecraft:/, '')
|
||||
@@ -46,7 +46,7 @@ export const RecipePreview = ({ model }: PreviewProps) => {
|
||||
} else {
|
||||
return '/images/crafting_table.png'
|
||||
}
|
||||
}, [state])
|
||||
}, [text])
|
||||
|
||||
return <>
|
||||
<div ref={overlay} class="preview-overlay">
|
||||
|
||||
@@ -12,17 +12,18 @@ import { DEEPSLATE } from './Deepslate.js'
|
||||
import type { PreviewProps } from './index.js'
|
||||
import { InteractiveCanvas2D } from './InteractiveCanvas2D.jsx'
|
||||
|
||||
export const StructureSetPreview = ({ model, shown }: PreviewProps) => {
|
||||
export const StructureSetPreview = ({ docAndNode, shown }: PreviewProps) => {
|
||||
const { locale } = useLocale()
|
||||
const { version } = useVersion()
|
||||
const [seed, setSeed] = useState(randomSeed())
|
||||
const state = JSON.stringify(model.data)
|
||||
|
||||
const text = docAndNode.doc.getText()
|
||||
|
||||
const { value: structureSet } = useAsync(async () => {
|
||||
await DEEPSLATE.loadVersion(version)
|
||||
const structureSet = DEEPSLATE.loadStructureSet(model.data, seed)
|
||||
const structureSet = DEEPSLATE.loadStructureSet(JSON.parse(text), seed)
|
||||
return structureSet
|
||||
}, [state, version, seed])
|
||||
}, [text, version, seed])
|
||||
|
||||
const { chunkStructures, structureColors } = useMemo(() => {
|
||||
return {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { FileModel } from '../../services/index.js'
|
||||
import type { DocAndNode } from '@spyglassmc/core'
|
||||
|
||||
export * from './BiomeSourcePreview.js'
|
||||
export * from './BlockStatePreview.jsx'
|
||||
@@ -12,6 +12,6 @@ export * from './RecipePreview.jsx'
|
||||
export * from './StructureSetPreview.jsx'
|
||||
|
||||
export interface PreviewProps {
|
||||
model: FileModel
|
||||
docAndNode: DocAndNode
|
||||
shown: boolean
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { useEffect, useErrorBoundary, useMemo } from 'preact/hooks'
|
||||
import config from '../Config.js'
|
||||
import { CustomizedPanel } from '../components/customized/CustomizedPanel.jsx'
|
||||
import { ErrorPanel, Footer, Octicon, VersionSwitcher } from '../components/index.js'
|
||||
import config from '../Config.js'
|
||||
import { useLocale, useTitle, useVersion } from '../contexts/index.js'
|
||||
import { useSearchParam } from '../hooks/index.js'
|
||||
import type { VersionId } from '../services/Schemas.js'
|
||||
import { checkVersion } from '../services/Schemas.js'
|
||||
import type { VersionId } from '../services/Versions.js'
|
||||
import { checkVersion } from '../services/Versions.js'
|
||||
|
||||
const MIN_VERSION = '1.20'
|
||||
const Tabs = ['basic', 'biomes', 'structures', 'ores']
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import config from '../Config.js'
|
||||
import { Store } from '../Store.js'
|
||||
import { message } from '../Utils.js'
|
||||
import type { VersionId } from './Schemas.js'
|
||||
import { checkVersion } from './Schemas.js'
|
||||
import type { VersionId } from './Versions.js'
|
||||
import { checkVersion } from './Versions.js'
|
||||
|
||||
const CACHE_NAME = 'misode-v2'
|
||||
const CACHE_LATEST_VERSION = 'cached_latest_version'
|
||||
|
||||
@@ -3,7 +3,7 @@ import { BlockDefinition, BlockModel, Identifier, ItemRenderer, TextureAtlas, up
|
||||
import config from '../Config.js'
|
||||
import { message } from '../Utils.js'
|
||||
import { fetchLanguage, fetchResources } from './DataFetcher.js'
|
||||
import type { VersionId } from './Schemas.js'
|
||||
import type { VersionId } from './Versions.js'
|
||||
|
||||
const Resources: Record<string, ResourceManager | Promise<ResourceManager>> = {}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { BlockDefinition, BlockModel, Identifier, ItemRenderer, TextureAtlas, up
|
||||
import config from '../Config.js'
|
||||
import { message } from '../Utils.js'
|
||||
import { fetchLanguage, fetchResources } from './DataFetcher.js'
|
||||
import type { VersionId } from './Schemas.js'
|
||||
import type { VersionId } from './Versions.js'
|
||||
|
||||
const Resources: Record<string, ResourceManager | Promise<ResourceManager>> = {}
|
||||
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
import config from '../Config.js'
|
||||
import { message } from '../Utils.js'
|
||||
import type { BlockStateData } from './DataFetcher.js'
|
||||
import { fetchBlockStates, fetchRegistries } from './DataFetcher.js'
|
||||
|
||||
export const VersionIds = ['1.15', '1.16', '1.17', '1.18', '1.18.2', '1.19', '1.19.3', '1.19.4', '1.20', '1.20.2', '1.20.3', '1.20.5', '1.21', '1.21.2'] as const
|
||||
export type VersionId = typeof VersionIds[number]
|
||||
|
||||
export const DEFAULT_VERSION: VersionId = '1.21'
|
||||
|
||||
interface VersionData {
|
||||
registries: Map<string, string[]>
|
||||
blockStates: Map<string, BlockStateData>
|
||||
}
|
||||
|
||||
const Versions: Record<string, VersionData | Promise<VersionData>> = {}
|
||||
|
||||
async function getVersion(id: VersionId): Promise<VersionData> {
|
||||
if (!Versions[id]) {
|
||||
Versions[id] = (async () => {
|
||||
try {
|
||||
const registries = await fetchRegistries(id)
|
||||
const blockStates= await fetchBlockStates(id)
|
||||
Versions[id] = { registries, blockStates }
|
||||
return Versions[id]
|
||||
} catch (e) {
|
||||
throw new Error(`Cannot get version "${id}": ${message(e)}`)
|
||||
}
|
||||
})()
|
||||
return Versions[id]
|
||||
}
|
||||
return Versions[id]
|
||||
}
|
||||
|
||||
export async function getBlockStates(version: VersionId): Promise<Map<string, BlockStateData>> {
|
||||
const versionData = await getVersion(version)
|
||||
return versionData.blockStates
|
||||
}
|
||||
|
||||
export function checkVersion(versionId: string, minVersionId: string | undefined, maxVersionId?: string) {
|
||||
const version = config.versions.findIndex(v => v.id === versionId)
|
||||
const minVersion = minVersionId ? config.versions.findIndex(v => v.id === minVersionId) : 0
|
||||
const maxVersion = maxVersionId ? config.versions.findIndex(v => v.id === maxVersionId) : config.versions.length - 1
|
||||
return minVersion <= version && version <= maxVersion
|
||||
}
|
||||
|
||||
export interface FileModel {
|
||||
get text(): string
|
||||
get data(): any
|
||||
}
|
||||
|
||||
export function createMockFileModel(): FileModel {
|
||||
return {
|
||||
text: '{}',
|
||||
data: {},
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import lz from 'lz-string'
|
||||
import type { VersionId } from './Schemas.js'
|
||||
import type { VersionId } from './Versions.js'
|
||||
|
||||
const API_PREFIX = 'https://snippets.misode.workers.dev'
|
||||
|
||||
|
||||
@@ -9,61 +9,87 @@ import { localize } from '@spyglassmc/locales'
|
||||
import * as mcdoc from '@spyglassmc/mcdoc'
|
||||
import * as nbt from '@spyglassmc/nbt'
|
||||
import * as zip from '@zip.js/zip.js'
|
||||
import type { ConfigVersion } from '../Config.js'
|
||||
import type { ConfigGenerator, ConfigVersion } from '../Config.js'
|
||||
import siteConfig from '../Config.js'
|
||||
import type { VersionId } from './index.js'
|
||||
import { fetchBlockStates, fetchRegistries, fetchVanillaMcdoc, getVersionChecksum } from './index.js'
|
||||
import { genPath } from '../Utils.js'
|
||||
import { fetchBlockStates, fetchRegistries, fetchVanillaMcdoc, getVersionChecksum } from './DataFetcher.js'
|
||||
import type { VersionId } from './Versions.js'
|
||||
|
||||
const externals: core.Externals = {
|
||||
...BrowserExternals,
|
||||
archive: {
|
||||
...BrowserExternals.archive,
|
||||
async decompressBall(buffer, { stripLevel } = {}) {
|
||||
const reader = new zip.ZipReader(new zip.BlobReader(new Blob([buffer])))
|
||||
const entries = await reader.getEntries()
|
||||
return await Promise.all(entries.map(async e => {
|
||||
const data = await e.getData?.(new zip.Uint8ArrayWriter())
|
||||
const path = stripLevel === 1 ? e.filename.substring(e.filename.indexOf('/') + 1) : e.filename
|
||||
const type = e.directory ? 'dir' : 'file'
|
||||
return { data, path, mtime: '', type, mode: 0 }
|
||||
}))
|
||||
},
|
||||
},
|
||||
export class Spyglass {
|
||||
private static readonly INSTANCES = new Map<VersionId, Promise<Spyglass>>()
|
||||
|
||||
private constructor(
|
||||
private readonly service: core.Service,
|
||||
private readonly version: ConfigVersion,
|
||||
) {}
|
||||
|
||||
public async setFileContents(uri: string, contents: string) {
|
||||
await this.service.project.onDidOpen(uri, 'json', 1, contents)
|
||||
const docAndNode = await this.service.project.ensureClientManagedChecked(uri)
|
||||
if (!docAndNode) {
|
||||
throw new Error('[Spyglass setFileContents] Cannot get doc and node')
|
||||
}
|
||||
return docAndNode
|
||||
}
|
||||
|
||||
public getFile(uri: string): Partial<core.DocAndNode> {
|
||||
return this.service.project.getClientManaged(uri) ?? {}
|
||||
}
|
||||
|
||||
public getUnsavedFileUri(gen: ConfigGenerator) {
|
||||
return `file:project/data/draft/${genPath(gen, this.version.id)}/unsaved.json`
|
||||
}
|
||||
|
||||
public static async initialize(versionId: VersionId) {
|
||||
const instance = this.INSTANCES.get(versionId)
|
||||
if (instance) {
|
||||
return instance
|
||||
}
|
||||
const promise = (async () => {
|
||||
const version = siteConfig.versions.find(v => v.id === versionId)!
|
||||
const service = new core.Service({
|
||||
logger: console,
|
||||
profilers: new core.ProfilerFactory(console, [
|
||||
'project#init',
|
||||
'project#ready',
|
||||
]),
|
||||
project: {
|
||||
cacheRoot: 'file:cache/',
|
||||
projectRoots: ['file:project/'],
|
||||
externals: {
|
||||
...BrowserExternals,
|
||||
archive: {
|
||||
...BrowserExternals.archive,
|
||||
decompressBall,
|
||||
},
|
||||
},
|
||||
defaultConfig: core.ConfigService.merge(core.VanillaConfig, {
|
||||
env: {
|
||||
gameVersion: version.ref ?? version.id,
|
||||
dependencies: ['@vanilla-mcdoc'],
|
||||
},
|
||||
}),
|
||||
initializers: [mcdoc.initialize, initialize],
|
||||
},
|
||||
})
|
||||
await service.project.ready()
|
||||
await service.project.cacheService.save()
|
||||
return new Spyglass(service, version)
|
||||
})()
|
||||
this.INSTANCES.set(versionId, promise)
|
||||
return promise
|
||||
}
|
||||
}
|
||||
|
||||
export async function setupSpyglass(versionId: VersionId) {
|
||||
const version = siteConfig.versions.find(v => v.id === versionId)!
|
||||
const gameVersion = version.ref ?? version.id
|
||||
const logger: core.Logger = console
|
||||
const profilers = new core.ProfilerFactory(logger, [
|
||||
'project#init',
|
||||
'project#ready',
|
||||
'misode#setup',
|
||||
])
|
||||
const profiler = profilers.get('misode#setup')
|
||||
const service = new core.Service({
|
||||
logger,
|
||||
profilers,
|
||||
project: {
|
||||
cacheRoot: 'file:cache/',
|
||||
projectRoots: ['file:project/'],
|
||||
externals: externals,
|
||||
defaultConfig: core.ConfigService.merge(core.VanillaConfig, {
|
||||
env: {
|
||||
gameVersion: gameVersion,
|
||||
dependencies: ['@vanilla-mcdoc'],
|
||||
},
|
||||
}),
|
||||
initializers: [mcdoc.initialize, initialize],
|
||||
},
|
||||
})
|
||||
await service.project.ready()
|
||||
profiler.task('Project ready')
|
||||
await service.project.cacheService.save()
|
||||
profiler.task('Save cache')
|
||||
profiler.finalize()
|
||||
|
||||
service.logger.info(service.project.symbols.global)
|
||||
const decompressBall: core.Externals['archive']['decompressBall'] = async (buffer, options) => {
|
||||
const reader = new zip.ZipReader(new zip.BlobReader(new Blob([buffer])))
|
||||
const entries = await reader.getEntries()
|
||||
return await Promise.all(entries.map(async e => {
|
||||
const data = await e.getData?.(new zip.Uint8ArrayWriter())
|
||||
const path = options?.stripLevel === 1 ? e.filename.substring(e.filename.indexOf('/') + 1) : e.filename
|
||||
const type = e.directory ? 'dir' : 'file'
|
||||
return { data, path, mtime: '', type, mode: 0 }
|
||||
}))
|
||||
}
|
||||
|
||||
const initialize: core.ProjectInitializer = async (ctx) => {
|
||||
|
||||
13
src/app/services/Versions.ts
Normal file
13
src/app/services/Versions.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import config from '../Config.js'
|
||||
|
||||
export const VersionIds = ['1.15', '1.16', '1.17', '1.18', '1.18.2', '1.19', '1.19.3', '1.19.4', '1.20', '1.20.2', '1.20.3', '1.20.5', '1.21', '1.21.2'] as const
|
||||
export type VersionId = typeof VersionIds[number]
|
||||
|
||||
export const DEFAULT_VERSION: VersionId = '1.21'
|
||||
|
||||
export function checkVersion(versionId: string, minVersionId: string | undefined, maxVersionId?: string) {
|
||||
const version = config.versions.findIndex(v => v.id === versionId)
|
||||
const minVersion = minVersionId ? config.versions.findIndex(v => v.id === minVersionId) : 0
|
||||
const maxVersion = maxVersionId ? config.versions.findIndex(v => v.id === maxVersionId) : config.versions.length - 1
|
||||
return minVersion <= version && version <= maxVersion
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
export * from './Article.js'
|
||||
export * from './DataFetcher.js'
|
||||
export * from './Schemas.js'
|
||||
export * from './Sharing.js'
|
||||
export * from './Source.js'
|
||||
export * from './Versions.js'
|
||||
|
||||
Reference in New Issue
Block a user