🔥 Nuke all mcschema related code

This commit is contained in:
Misode
2024-10-15 05:14:02 +02:00
parent b9a23d0f47
commit ccdcf9e7e3
39 changed files with 199 additions and 3220 deletions

View File

@@ -3,10 +3,9 @@ import { Identifier } from 'deepslate/core'
import { useCallback, useEffect, useMemo, useRef, useState } from 'preact/hooks'
import { useVersion } from '../contexts/Version.jsx'
import { useAsync } from '../hooks/useAsync.js'
import { fetchItemComponents } from '../services/index.js'
import { fetchItemComponents, fetchRegistries } from '../services/index.js'
import { ResolvedItem } from '../services/ResolvedItem.js'
import { renderItem } from '../services/Resources.js'
import { getCollections } from '../services/Schemas.js'
import { jsonToNbt } from '../Utils.js'
import { ItemTooltip } from './ItemTooltip.jsx'
import { Octicon } from './Octicon.jsx'
@@ -83,14 +82,17 @@ function ItemItself({ item }: ResolvedProps) {
return Octicon.package
}
const { value: collections } = useAsync(() => getCollections(version), [])
const { value: allModels, loading: loadingModels } = useAsync(async () => {
const registries = await fetchRegistries(version)
return registries.get('model')
}, [version])
if (collections === undefined) {
if (loadingModels || allModels === undefined) {
return null
}
const modelPath = `item/${item.id.path}`
if (collections.get('model').includes('minecraft:' + modelPath)) {
if (allModels && allModels.includes('minecraft:' + modelPath)) {
return <RenderedItem item={item} />
}

View File

@@ -3,8 +3,8 @@ import { Identifier } from 'deepslate-1.20.4/core'
import { useEffect, useRef, useState } from 'preact/hooks'
import { useVersion } from '../contexts/Version.jsx'
import { useAsync } from '../hooks/useAsync.js'
import { fetchRegistries } from '../services/index.js'
import { renderItem } from '../services/Resources1204.js'
import { getCollections } from '../services/Schemas.js'
import { ItemTooltip1204 } from './ItemTooltip1204.jsx'
import { Octicon } from './Octicon.jsx'
import { itemHasGlint } from './previews/LootTable1204.js'
@@ -69,14 +69,17 @@ function ItemItself({ item }: Props) {
return Octicon.package
}
const { value: collections } = useAsync(() => getCollections(version), [])
const { value: allModels, loading: loadingModels } = useAsync(async () => {
const registries = await fetchRegistries(version)
return registries.get('model')
}, [version])
if (collections === undefined) {
if (loadingModels || allModels === undefined) {
return null
}
const modelPath = `item/${item.id.path}`
if (collections.get('model').includes('minecraft:' + modelPath)) {
if (allModels && allModels.includes('minecraft:' + modelPath)) {
return <RenderedItem item={item} hasGlint={hasGlint} />
}

View File

@@ -1,4 +1,3 @@
import type { NodeChildren } from '@mcschema/core'
import { NumberInput, RangeInput } from '../index.js'
import { CustomizedInput } from './CustomizedInput.jsx'
@@ -12,7 +11,6 @@ interface Props {
initial?: number,
error?: string,
onChange: (value: number) => void,
children?: NodeChildren,
}
export function CustomizedSlider(props: Props) {
const isInteger = (props.step ?? 1) >= 1

View File

@@ -1,13 +1,13 @@
import { DataModel } from '@mcschema/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: DataModel,
model: FileModel,
id: string,
method: string,
onClose: () => void,
@@ -29,7 +29,7 @@ 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: DataModel.unwrapLists(model.data) })
updateFile(id, undefined, { type: id, id: fileId, data: model.data })
onClose()
}

View File

@@ -1,69 +1,59 @@
import type { DataModel } from '@mcschema/core'
import { Path } from '@mcschema/core'
import { useState } from 'preact/hooks'
import { useModel } from '../../hooks/index.js'
import type { VersionId } from '../../services/index.js'
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']
type PreviewPanelProps = {
model: DataModel | undefined,
version: VersionId,
model: FileModel | undefined,
id: string,
shown: boolean,
onError: (message: string) => unknown,
}
export function PreviewPanel({ model, version, id, shown }: PreviewPanelProps) {
const [, setCount] = useState(0)
useModel(model, () => {
setCount(count => count + 1)
})
export function PreviewPanel({ model, id, shown }: PreviewPanelProps) {
const { version } = useVersion()
if (!model) return <></>
const data = model.get(new Path([]))
if (!data) return <></>
if (id === 'loot_table') {
return <LootTablePreview {...{ model, version, shown, data }} />
return <LootTablePreview {...{ model, shown }} />
}
if (id === 'recipe') {
return <RecipePreview {...{ model, version, shown, data }} />
return <RecipePreview {...{ model, shown }} />
}
if (id === 'dimension' && model.get(new Path(['generator', 'type']))?.endsWith('noise')) {
return <BiomeSourcePreview {...{ model, version, shown, data }} />
if (id === 'dimension' && model.data.generator?.type?.endsWith('noise')) {
return <BiomeSourcePreview {...{ model, shown }} />
}
if (id === 'worldgen/density_function') {
return <DensityFunctionPreview {...{ model, version, shown, data }} />
return <DensityFunctionPreview {...{ model, shown }} />
}
if (id === 'worldgen/noise') {
return <NoisePreview {...{ model, version, shown, data }} />
return <NoisePreview {...{ model, shown }} />
}
if (id === 'worldgen/noise_settings' && checkVersion(version, '1.18')) {
return <NoiseSettingsPreview {...{ model, version, shown, data }} />
return <NoiseSettingsPreview {...{ model, shown }} />
}
if ((id === 'worldgen/placed_feature' || (id === 'worldgen/configured_feature' && checkVersion(version, '1.16', '1.17')))) {
return <DecoratorPreview {...{ model, version, shown, data }} />
return <DecoratorPreview {...{ model, shown }} />
}
if (id === 'worldgen/structure_set' && checkVersion(version, '1.19')) {
return <StructureSetPreview {...{ model, version, shown, data }} />
return <StructureSetPreview {...{ model, shown }} />
}
if (id === 'block_definition') {
return <BlockStatePreview {...{ model, version, shown, data }} />
return <BlockStatePreview {...{ model, shown }} />
}
if (id === 'model') {
return <ModelPreview {...{ model, version, shown, data }} />
return <ModelPreview {...{ model, shown }} />
}
return <></>

View File

@@ -1,10 +1,8 @@
import type { DataModel } from '@mcschema/core'
import { useCallback, useMemo, useRef, useState } from 'preact/hooks'
import { Analytics } from '../../Analytics.js'
import config from '../../Config.js'
import { disectFilePath, DRAFT_PROJECT, getFilePath, useLocale, useProject, useVersion } from '../../contexts/index.js'
import { useFocus } from '../../hooks/useFocus.js'
import type { VersionId } from '../../services/index.js'
import { stringifySource } from '../../services/index.js'
import { Store } from '../../Store.js'
import { writeZip } from '../../Utils.js'
@@ -15,9 +13,6 @@ import type { TreeViewGroupRenderer, TreeViewLeafRenderer } from '../TreeView.js
import { TreeView } from '../TreeView.js'
interface Props {
model: DataModel | undefined,
version: VersionId,
id: string,
onError: (message: string) => unknown,
onRename: (file: { type: string, id: string }) => unknown,
onCreate: () => unknown,

View File

@@ -1,14 +1,12 @@
import { DataModel, Path } from '@mcschema/core'
import { route } from 'preact-router'
import { useCallback, useEffect, useErrorBoundary, useMemo, useRef, useState } from 'preact/hooks'
import { useCallback, useEffect, useErrorBoundary, useMemo, useState } from 'preact/hooks'
import { Analytics } from '../../Analytics.js'
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, useModel, useSearchParam } from '../../hooks/index.js'
import { getOutput } from '../../schema/transformOutput.js'
import type { VersionId } from '../../services/index.js'
import { checkVersion, fetchPreset, getBlockStates, getCollections, getModel, getSnippet, shareSnippet } from '../../services/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 { Store } from '../../Store.js'
import { cleanUrl, deepEqual, 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'
@@ -22,7 +20,7 @@ interface Props {
export function SchemaGenerator({ gen, allowedVersions }: Props) {
const { locale } = useLocale()
const { version, changeVersion, changeTargetVersion } = useVersion()
const { projects, project, file, updateProject, updateFile, closeFile } = useProject()
const { projects, project, file, updateProject, closeFile } = useProject()
const [error, setError] = useState<Error | string | null>(null)
const [errorBoundary, errorRetry] = useErrorBoundary()
if (errorBoundary) {
@@ -34,16 +32,15 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
const [currentPreset, setCurrentPreset] = useSearchParam('preset')
const [sharedSnippetId, setSharedSnippetId] = useSearchParam(SHARE_KEY)
const ignoreChange = useRef(false)
const backup = useMemo(() => Store.getBackup(gen.id), [gen.id])
const loadBackup = () => {
if (backup !== undefined) {
model?.reset(DataModel.wrapLists(backup), false)
// TODO: implement
}
}
const { value } = useAsync(async () => {
const {} = useAsync(async () => {
let data: unknown = undefined
if (currentPreset && sharedSnippetId) {
setSharedSnippetId(undefined)
@@ -81,81 +78,65 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
}
data = file.data
}
const [model, blockStates] = await Promise.all([
getModel(version, gen.id),
getBlockStates(version),
])
if (data) {
ignoreChange.current = true
model.reset(DataModel.wrapLists(data), false)
// TODO: set file contents to data
}
Analytics.setGenerator(gen.id)
return { model, blockStates }
return {}
}, [gen.id, version, sharedSnippetId, currentPreset, project.name, file?.id])
const model = value?.model
const blockStates = value?.blockStates
const model: FileModel = createMockFileModel()
useModel(model, model => {
if (!ignoreChange.current) {
setCurrentPreset(undefined, true)
setSharedSnippetId(undefined, true)
}
if (file && model && blockStates) {
const data = getOutput(model, blockStates)
updateFile(gen.id, file.id, { id: file.id, data })
}
ignoreChange.current = false
Store.setBackup(gen.id, DataModel.unwrapLists(model.data))
setError(null)
}, [gen.id, setCurrentPreset, setSharedSnippetId, blockStates, file?.id])
// TODO: when contents of file change:
// - remove preset and share id from url
// - update project
// - store backup
const reset = () => {
Analytics.resetGenerator(gen.id, model?.historyIndex ?? 1, 'menu')
model?.reset(DataModel.wrapLists(model.schema.default()), true)
Analytics.resetGenerator(gen.id, 1, 'menu')
// TODO
}
const undo = (e: MouseEvent) => {
e.stopPropagation()
Analytics.undoGenerator(gen.id, model?.historyIndex ?? 1, 'menu')
model?.undo()
Analytics.undoGenerator(gen.id, 1, 'menu')
// TODO
}
const redo = (e: MouseEvent) => {
e.stopPropagation()
Analytics.redoGenerator(gen.id, model?.historyIndex ?? 1, 'menu')
model?.redo()
Analytics.redoGenerator(gen.id, 1, 'menu')
// TODO
}
const onKeyUp = (e: KeyboardEvent) => {
if (e.ctrlKey && e.key === 'z') {
Analytics.undoGenerator(gen.id, model?.historyIndex ?? 1, 'hotkey')
model?.undo()
} else if (e.ctrlKey && e.key === 'y') {
Analytics.redoGenerator(gen.id, model?.historyIndex ?? 1, 'hotkey')
model?.redo()
}
}
const onKeyDown = (e: KeyboardEvent) => {
if (e.ctrlKey && e.key === 's') {
setFileSaving('hotkey')
e.preventDefault()
e.stopPropagation()
}
}
useEffect(() => {
const onKeyUp = (e: KeyboardEvent) => {
if (e.ctrlKey && e.key === 'z') {
Analytics.undoGenerator(gen.id, 1, 'hotkey')
// TODO
} else if (e.ctrlKey && e.key === 'y') {
Analytics.redoGenerator(gen.id, 1, 'hotkey')
// TODO
}
}
const onKeyDown = (e: KeyboardEvent) => {
if (e.ctrlKey && e.key === 's') {
setFileSaving('hotkey')
e.preventDefault()
e.stopPropagation()
}
}
document.addEventListener('keyup', onKeyUp)
document.addEventListener('keydown', onKeyDown)
return () => {
document.removeEventListener('keyup', onKeyUp)
document.removeEventListener('keydown', onKeyDown)
}
}, [model, blockStates, file])
}, [gen.id])
const [presets, setPresets] = useState<string[]>([])
useEffect(() => {
getCollections(version).then(collections => {
setPresets(collections.get(gen.id).map(p => p.startsWith('minecraft:') ? p.slice(10) : p))
})
.catch(e => { console.error(e); setError(e) })
const { value: presets } = useAsync(async () => {
const registries = await fetchRegistries(version)
const entries = registries.get(gen.id) ?? []
return entries.map(e => e.startsWith('minecraft:') ? e.slice(10) : e)
}, [version, gen.id])
const selectPreset = (id: string) => {
@@ -168,13 +149,7 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
const loadPreset = async (id: string) => {
try {
const preset = await fetchPreset(version, genPath(gen, version), id)
const seed = model?.get(new Path(['generator', 'seed']))
if (preset?.generator?.seed !== undefined && seed !== undefined) {
preset.generator.seed = seed
if (preset.generator.biome_source?.seed !== undefined) {
preset.generator.biome_source.seed = seed
}
}
// TODO: sync random seed
return preset
} catch (e) {
setError(`Cannot load preset ${id} in ${version}`)
@@ -203,14 +178,14 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
setShareUrl(`${location.origin}/${gen.url}/?version=${version}&preset=${currentPreset}`)
setShareShown(true)
copySharedId()
} else if (model && blockStates) {
const output = getOutput(model, blockStates)
if (deepEqual(output, model.schema.default())) {
} else {
// TODO: get contents from file, and compare to default of type
if (deepEqual(model.data, {})) {
setShareUrl(`${location.origin}/${gen.url}/?version=${version}`)
setShareShown(true)
} else {
setShareLoading(true)
shareSnippet(gen.id, version, output, previewShown)
shareSnippet(gen.id, version, model.data, previewShown)
.then(({ id, length, compressed, rate }) => {
Analytics.createSnippet(gen.id, id, version, length, compressed, rate)
const url = `${location.origin}/${gen.url}/?${SHARE_KEY}=${id}`
@@ -298,21 +273,12 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
const [projectDeleting, setprojectDeleting] = useState(false)
const [fileSaving, setFileSaving] = useState<string | undefined>(undefined)
const [fileRenaming, setFileRenaming] = useState<{ type: string, id: string } | undefined>(undefined)
const [newFileQueued, setNewFileQueued] = useState(false)
const onNewFile = useCallback(() => {
closeFile()
// Need to queue reset because otherwise the useModel hook will update the old file
setNewFileQueued(true)
// TODO: create new file with default contents
}, [closeFile])
useEffect(() => {
if (file === undefined && newFileQueued) {
model?.reset(DataModel.wrapLists(model.schema.default()), true)
setNewFileQueued(false)
}
}, [model, newFileQueued, file])
return <>
<main class={`generator${previewShown ? ' has-preview' : ''}${projectShown ? ' has-project' : ''}`}>
{!gen.tags?.includes('partners') && <Ad id="data-pack-generator" type="text" />}
@@ -335,7 +301,7 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
</BtnMenu>
</div>
{error && <ErrorPanel error={error} onDismiss={() => setError(null)} />}
<Tree {...{model, version, blockStates}} onError={setError} />
<Tree model={model} onError={setError} />
<Footer donate={!gen.tags?.includes('partners')} />
</main>
<div class="popup-actions right-actions" style={`--offset: -${8 + actionsShown * 50}px;`}>
@@ -356,10 +322,10 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
</div>
</div>
<div class={`popup-preview${previewShown ? ' shown' : ''}`}>
<PreviewPanel {...{model, version, id: gen.id}} shown={previewShown} onError={setError} />
<PreviewPanel model={model} id={gen.id} shown={previewShown} onError={setError} />
</div>
<div class={`popup-source${sourceShown ? ' shown' : ''}`}>
<SourcePanel {...{model, blockStates, doCopy, doDownload, doImport}} name={gen.schema ?? 'data'} copySuccess={copySuccess} onError={setError} />
<SourcePanel {...{model, doCopy, doDownload, doImport}} name={gen.schema ?? 'data'} copySuccess={copySuccess} onError={setError} />
</div>
<div class={`popup-share${shareShown ? ' shown' : ''}`}>
<TextInput value={shareUrl} readonly />
@@ -371,7 +337,7 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
</div>
</div>
<div class={`popup-project${projectShown ? ' shown' : ''}`}>
<ProjectPanel {...{model, version, id: gen.id}} onError={setError} onDeleteProject={() => setprojectDeleting(true)} onRename={setFileRenaming} onCreate={() => setProjectCreating(true)} />
<ProjectPanel onError={setError} onDeleteProject={() => setprojectDeleting(true)} onRename={setFileRenaming} onCreate={() => setProjectCreating(true)} />
</div>
{projectCreating && <ProjectCreation onClose={() => setProjectCreating(false)} />}
{projectDeleting && <ProjectDeletion onClose={() => setprojectDeleting(false)} />}

View File

@@ -1,9 +1,7 @@
import { DataModel } from '@mcschema/core'
import { useCallback, useEffect, useRef, useState } from 'preact/hooks'
import { useLocale } from '../../contexts/index.js'
import { useLocalStorage, useModel } from '../../hooks/index.js'
import { getOutput } from '../../schema/transformOutput.js'
import type { BlockStateRegistry } from '../../services/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 { Store } from '../../Store.js'
import { message } from '../../Utils.js'
@@ -18,15 +16,14 @@ interface Editor {
type SourcePanelProps = {
name: string,
model: DataModel | undefined,
blockStates: BlockStateRegistry | undefined,
model: FileModel | undefined,
doCopy?: number,
doDownload?: number,
doImport?: number,
copySuccess: () => unknown,
onError: (message: string | Error) => unknown,
}
export function SourcePanel({ name, model, blockStates, doCopy, doDownload, doImport, copySuccess, onError }: SourcePanelProps) {
export function SourcePanel({ name, model, doCopy, doDownload, doImport, copySuccess, onError }: SourcePanelProps) {
const { locale } = useLocale()
const [indent, setIndent] = useState(Store.getIndent())
const [format, setFormat] = useState(Store.getFormat())
@@ -40,8 +37,8 @@ export function SourcePanel({ name, model, blockStates, doCopy, doDownload, doIm
const textarea = useRef<HTMLTextAreaElement>(null)
const editor = useRef<Editor>()
const getSerializedOutput = useCallback((model: DataModel, blockStates: BlockStateRegistry) => {
let data = getOutput(model, blockStates)
const getSerializedOutput = useCallback((model: FileModel) => {
let data = model.data
if (sort === 'alphabetically') {
data = sortData(data)
}
@@ -51,9 +48,9 @@ export function SourcePanel({ name, model, blockStates, doCopy, doDownload, doIm
useEffect(() => {
retransform.current = () => {
if (!editor.current) return
if (!model || !blockStates) return
if (!model) return
try {
const output = getSerializedOutput(model, blockStates)
const output = getSerializedOutput(model)
editor.current.setValue(output)
} catch (e) {
if (e instanceof Error) {
@@ -72,8 +69,8 @@ export function SourcePanel({ name, model, blockStates, doCopy, doDownload, doIm
const value = editor.current.getValue()
if (value.length === 0) return
try {
const data = await parseSource(value, format)
model?.reset(DataModel.wrapLists(data), false)
await parseSource(value, format)
// TODO: import
} catch (e) {
if (e instanceof Error) {
e.message = `Error importing: ${e.message}`
@@ -84,7 +81,7 @@ export function SourcePanel({ name, model, blockStates, doCopy, doDownload, doIm
console.error(e)
}
}
}, [model, blockStates, indent, format, sort, highlighting])
}, [model, indent, format, sort, highlighting])
useEffect(() => {
if (highlighting) {
@@ -145,10 +142,7 @@ export function SourcePanel({ name, model, blockStates, doCopy, doDownload, doIm
}
}, [highlighting])
useModel(model, () => {
if (!retransform.current) return
retransform.current()
})
// TODO: when file contents change, retransform
useEffect(() => {
if (!retransform.current) return
if (model) retransform.current()
@@ -163,16 +157,16 @@ export function SourcePanel({ name, model, blockStates, doCopy, doDownload, doIm
}, [indent, format, sort, highlighting, braceLoaded])
useEffect(() => {
if (doCopy && model && blockStates) {
navigator.clipboard.writeText(getSerializedOutput(model, blockStates)).then(() => {
if (doCopy && model) {
navigator.clipboard.writeText(getSerializedOutput(model)).then(() => {
copySuccess()
})
}
}, [doCopy])
useEffect(() => {
if (doDownload && model && blockStates && download.current) {
const content = encodeURIComponent(getSerializedOutput(model, blockStates))
if (doDownload && model && download.current) {
const content = encodeURIComponent(getSerializedOutput(model))
download.current.setAttribute('href', `data:text/json;charset=utf-8,${content}`)
const fileName = name === 'pack_mcmeta' ? 'pack.mcmeta' : `${name}.${format}`
download.current.setAttribute('download', fileName)

View File

@@ -1,19 +1,14 @@
import type { DataModel } from '@mcschema/core'
import { useErrorBoundary, useState } from 'preact/hooks'
import { useErrorBoundary } from 'preact/hooks'
import { useLocale } from '../../contexts/index.js'
import { useModel } from '../../hooks/index.js'
import { FullNode } from '../../schema/renderHtml.js'
import type { BlockStateRegistry, VersionId } from '../../services/index.js'
import type { FileModel } from '../../services/index.js'
type TreePanelProps = {
version: VersionId,
model: DataModel | undefined,
blockStates: BlockStateRegistry | undefined,
model: FileModel | undefined,
onError: (message: string) => unknown,
}
export function Tree({ version, model, blockStates, onError }: TreePanelProps) {
export function Tree({ model, onError }: TreePanelProps) {
const { lang } = useLocale()
if (!model || !blockStates || lang === 'none') return <></>
if (!model || lang === 'none') return <></>
const [error] = useErrorBoundary(e => {
onError(`Error rendering the tree: ${e.message}`)
@@ -21,12 +16,7 @@ export function Tree({ version, model, blockStates, onError }: TreePanelProps) {
})
if (error) return <></>
const [, setState] = useState(0)
useModel(model, () => {
setState(state => state + 1)
})
return <div class="tree" data-cy="tree">
<FullNode {...{model, lang, version, blockStates}}/>
{/* TODO: render tree */}
</div>
}

View File

@@ -1,8 +1,7 @@
import { DataModel } from '@mcschema/core'
import { clampedMap } from 'deepslate'
import { mat3 } from 'gl-matrix'
import { useCallback, useRef, useState } from 'preact/hooks'
import { getProjectData, useLocale, useProject, useStore } from '../../contexts/index.js'
import { getProjectData, useLocale, useProject, useStore, useVersion } from '../../contexts/index.js'
import { useAsync } from '../../hooks/index.js'
import { checkVersion } from '../../services/Schemas.js'
import { Store } from '../../Store.js'
@@ -21,8 +20,9 @@ type Layer = typeof LAYERS[number]
const DETAIL_DELAY = 300
const DETAIL_SCALE = 2
export const BiomeSourcePreview = ({ data, shown, version }: PreviewProps) => {
export const BiomeSourcePreview = ({ model, shown }: PreviewProps) => {
const { locale } = useLocale()
const { version } = useVersion()
const { project } = useProject()
const { biomeColors } = useStore()
const [seed, setSeed] = useState(randomSeed())
@@ -31,13 +31,13 @@ export const BiomeSourcePreview = ({ data, shown, version }: PreviewProps) => {
const [focused, setFocused] = useState<string[]>([])
const [focused2, setFocused2] = useState<string[]>([])
const state = JSON.stringify(data)
const type: string = data?.generator?.biome_source?.type?.replace(/^minecraft:/, '') ?? ''
const state = JSON.stringify(model.data)
const type: string = model.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(DataModel.unwrapLists(data?.generator?.settings), DataModel.unwrapLists(data?.generator?.biome_source), seed)
await DEEPSLATE.loadChunkGenerator(model.data?.generator?.settings, model.data?.generator?.biome_source, seed)
return {
biomeSource: { loaded: true },
noiseRouter: checkVersion(version, '1.19') ? DEEPSLATE.getNoiseRouter() : undefined,

View File

@@ -1,4 +1,3 @@
import { DataModel } from '@mcschema/core'
import { BlockDefinition, Identifier, Structure, StructureRenderer } from 'deepslate/render'
import type { mat4 } from 'gl-matrix'
import { useCallback, useRef } from 'preact/hooks'
@@ -11,14 +10,14 @@ import { InteractiveCanvas3D } from './InteractiveCanvas3D.jsx'
const PREVIEW_ID = Identifier.parse('misode:preview')
export const BlockStatePreview = ({ data, shown }: PreviewProps) => {
export const BlockStatePreview = ({ model, shown }: PreviewProps) => {
const { version } = useVersion()
const serializedData = JSON.stringify(data)
const serializedData = JSON.stringify(model.data)
const { value: resources } = useAsync(async () => {
if (!shown) return AsyncCancel
const resources = await getResources(version)
const definition = BlockDefinition.fromJson(DataModel.unwrapLists(data))
const definition = BlockDefinition.fromJson(model.data)
const wrapper = new ResourceWrapper(resources, {
getBlockDefinition(id) {
if (id.equals(PREVIEW_ID)) return definition

View File

@@ -1,9 +1,8 @@
import { DataModel } from '@mcschema/core'
import type { BlockPos, ChunkPos, PerlinNoise, Random } from 'deepslate/worldgen'
import type { Color } from '../../Utils.js'
import { clamp, isObject, stringToColor } from '../../Utils.js'
import type { VersionId } from '../../services/index.js'
import { checkVersion } from '../../services/index.js'
import type { Color } from '../../Utils.js'
import { clamp, isObject, stringToColor } from '../../Utils.js'
export type Placement = [BlockPos, number]
@@ -38,9 +37,9 @@ export const featureColors: Color[] = [
export function decorateChunk(pos: ChunkPos, state: any, ctx: PlacementContext): PlacedFeature[] {
if (checkVersion(ctx.version, undefined, '1.17')) {
getPlacements([pos[0] * 16, 0, pos[1] * 16], DataModel.unwrapLists(state), ctx)
getPlacements([pos[0] * 16, 0, pos[1] * 16], state, ctx)
} else {
modifyPlacement([pos[0] * 16, 0, pos[1] * 16], DataModel.unwrapLists(state.placement), ctx)
modifyPlacement([pos[0] * 16, 0, pos[1] * 16], state.placement, ctx)
}
return ctx.placements.map(([pos, i]) => {

View File

@@ -1,7 +1,7 @@
import { BlockPos, ChunkPos, LegacyRandom, PerlinNoise } from 'deepslate'
import type { mat3 } from 'gl-matrix'
import { useCallback, useMemo, useRef, useState } from 'preact/hooks'
import { useLocale } from '../../contexts/index.js'
import { useLocale, useVersion } from '../../contexts/index.js'
import { computeIfAbsent, iterateWorld2D, randomSeed } from '../../Utils.js'
import { Btn } from '../index.js'
import type { PlacedFeature, PlacementContext } from './Decorator.js'
@@ -9,10 +9,11 @@ import { decorateChunk } from './Decorator.js'
import type { PreviewProps } from './index.js'
import { InteractiveCanvas2D } from './InteractiveCanvas2D.jsx'
export const DecoratorPreview = ({ data, version, shown }: PreviewProps) => {
export const DecoratorPreview = ({ model, shown }: PreviewProps) => {
const { locale } = useLocale()
const { version } = useVersion()
const [seed, setSeed] = useState(randomSeed())
const state = JSON.stringify(data)
const state = JSON.stringify(model.data)
const { context, chunkFeatures } = useMemo(() => {
const random = new LegacyRandom(seed)
@@ -51,7 +52,7 @@ export const DecoratorPreview = ({ data, version, shown }: PreviewProps) => {
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, data, context))
const features = computeIfAbsent(chunkFeatures, `${pos[0]} ${pos[1]}`, () => decorateChunk(pos, model.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) {

View File

@@ -1,4 +1,3 @@
import { DataModel } from '@mcschema/core'
import type { Voxel } from 'deepslate/render'
import { clampedMap, VoxelRenderer } from 'deepslate/render'
import type { mat3, mat4 } from 'gl-matrix'
@@ -19,7 +18,7 @@ import { InteractiveCanvas3D } from './InteractiveCanvas3D.jsx'
const MODES = ['side', 'top', '3d'] as const
export const DensityFunctionPreview = ({ data, shown }: PreviewProps) => {
export const DensityFunctionPreview = ({ model, shown }: PreviewProps) => {
const { locale } = useLocale()
const { project } = useProject()
const { version } = useVersion()
@@ -29,11 +28,11 @@ export const DensityFunctionPreview = ({ data, shown }: PreviewProps) => {
const [seed, setSeed] = useState(randomSeed())
const [minY] = useState(0)
const [height] = useState(256)
const serializedData = JSON.stringify(data)
const serializedData = JSON.stringify(model.data)
const { value: df } = useAsync(async () => {
await DEEPSLATE.loadVersion(version, getProjectData(project))
const df = DEEPSLATE.loadDensityFunction(DataModel.unwrapLists(data), minY, height, seed)
const df = DEEPSLATE.loadDensityFunction(model.data, minY, height, seed)
return df
}, [version, project, minY, height, seed, serializedData])

View File

@@ -1,4 +1,3 @@
import { DataModel } from '@mcschema/core'
import { Identifier } from 'deepslate'
import { useMemo, useRef, useState } from 'preact/hooks'
import { useLocale, useVersion } from '../../contexts/index.js'
@@ -12,7 +11,7 @@ import type { PreviewProps } from './index.js'
import { generateLootTable } from './LootTable.js'
import { generateLootTable as generateLootTable1204 } from './LootTable1204.js'
export const LootTablePreview = ({ data }: PreviewProps) => {
export const LootTablePreview = ({ model }: PreviewProps) => {
const { locale } = useLocale()
const { version } = useVersion()
const use1204 = !checkVersion(version, '1.20.5')
@@ -35,7 +34,7 @@ export const LootTablePreview = ({ data }: PreviewProps) => {
])
}, [version])
const table = DataModel.unwrapLists(data)
const table = model.data
const state = JSON.stringify(table)
const items = useMemo(() => {
if (dependencies === undefined || loading) {

View File

@@ -1,4 +1,3 @@
import { DataModel } from '@mcschema/core'
import { BlockDefinition, BlockModel, Identifier, Structure, StructureRenderer } from 'deepslate/render'
import type { mat4 } from 'gl-matrix'
import { useCallback, useRef } from 'preact/hooks'
@@ -12,22 +11,22 @@ 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 = ({ data, shown }: PreviewProps) => {
export const ModelPreview = ({ model, shown }: PreviewProps) => {
const { version } = useVersion()
const serializedData = JSON.stringify(data)
const serializedData = JSON.stringify(model.data)
const { value: resources } = useAsync(async () => {
if (!shown) return AsyncCancel
const resources = await getResources(version)
const model = BlockModel.fromJson(DataModel.unwrapLists(data))
model.flatten(resources)
const blockModel = BlockModel.fromJson(model.data)
blockModel.flatten(resources)
const wrapper = new ResourceWrapper(resources, {
getBlockDefinition(id) {
if (id.equals(PREVIEW_ID)) return PREVIEW_DEFINITION
return null
},
getBlockModel(id) {
if (id.equals(PREVIEW_ID)) return model
if (id.equals(PREVIEW_ID)) return blockModel
return null
},
})

View File

@@ -1,4 +1,3 @@
import { DataModel } from '@mcschema/core'
import { clampedMap, NoiseParameters, NormalNoise, XoroshiroRandom } from 'deepslate'
import type { mat3 } from 'gl-matrix'
import { useCallback, useMemo, useRef, useState } from 'preact/hooks'
@@ -12,14 +11,14 @@ import { ColormapSelector } from './ColormapSelector.jsx'
import type { PreviewProps } from './index.js'
import { InteractiveCanvas2D } from './InteractiveCanvas2D.jsx'
export const NoisePreview = ({ data, shown }: PreviewProps) => {
export const NoisePreview = ({ model, shown }: PreviewProps) => {
const { locale } = useLocale()
const [seed, setSeed] = useState(randomSeed())
const state = JSON.stringify(data)
const state = JSON.stringify(model.data)
const noise = useMemo(() => {
const random = XoroshiroRandom.create(seed)
const params = NoiseParameters.fromJson(DataModel.unwrapLists(data))
const params = NoiseParameters.fromJson(model.data)
return new NormalNoise(random, params)
}, [state, seed])

View File

@@ -1,31 +1,31 @@
import { DataModel } from '@mcschema/core'
import { clampedMap } from 'deepslate'
import type { mat3 } from 'gl-matrix'
import { vec2 } from 'gl-matrix'
import { useCallback, useMemo, useRef, useState } from 'preact/hooks'
import { useCallback, useRef, useState } from 'preact/hooks'
import { getProjectData, useLocale, useProject, useVersion } from '../../contexts/index.js'
import { useAsync } from '../../hooks/index.js'
import { fetchRegistries } from '../../services/index.js'
import { Store } from '../../Store.js'
import { iterateWorld2D, randomSeed } from '../../Utils.js'
import { getProjectData, useLocale, useProject } from '../../contexts/index.js'
import { useAsync } from '../../hooks/index.js'
import { CachedCollections } from '../../services/index.js'
import { Btn, BtnInput, BtnMenu, ErrorPanel } from '../index.js'
import type { ColormapType } from './Colormap.js'
import { getColormap } from './Colormap.js'
import { ColormapSelector } from './ColormapSelector.jsx'
import { DEEPSLATE } from './Deepslate.js'
import { InteractiveCanvas2D } from './InteractiveCanvas2D.jsx'
import type { PreviewProps } from './index.js'
import { InteractiveCanvas2D } from './InteractiveCanvas2D.jsx'
export const NoiseSettingsPreview = ({ data, shown, version }: PreviewProps) => {
export const NoiseSettingsPreview = ({ model, 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(data)
const state = JSON.stringify(model.data)
const { value, error } = useAsync(async () => {
const unwrapped = DataModel.unwrapLists(data)
const unwrapped = model.data
await DEEPSLATE.loadVersion(version, getProjectData(project))
const biomeSource = { type: 'fixed', biome }
await DEEPSLATE.loadChunkGenerator(unwrapped, biomeSource, seed)
@@ -86,7 +86,10 @@ export const NoiseSettingsPreview = ({ data, shown, version }: PreviewProps) =>
}
}, [noiseSettings, finalDensity])
const allBiomes = useMemo(() => CachedCollections?.get('worldgen/biome') ?? [], [version])
const { value: allBiomes } = useAsync(async () => {
const registries = await fetchRegistries(version)
return registries.get('worldgen/biome')
}, [version])
if (error) {
return <ErrorPanel error={error} prefix="Failed to initialize preview: " />

View File

@@ -1,7 +1,6 @@
import { DataModel } from '@mcschema/core'
import { Identifier, ItemStack } from 'deepslate'
import { useEffect, useMemo, useRef, useState } from 'preact/hooks'
import { useLocale } from '../../contexts/index.js'
import { useLocale, useVersion } from '../../contexts/index.js'
import { useAsync } from '../../hooks/useAsync.js'
import type { VersionId } from '../../services/index.js'
import { checkVersion, fetchAllPresets } from '../../services/index.js'
@@ -12,8 +11,9 @@ import type { PreviewProps } from './index.js'
const ANIMATION_TIME = 1000
export const RecipePreview = ({ data, version }: PreviewProps) => {
export const RecipePreview = ({ model }: PreviewProps) => {
const { locale } = useLocale()
const { version } = useVersion()
const [advancedTooltips, setAdvancedTooltips] = useState(true)
const [animation, setAnimation] = useState(0)
const overlay = useRef<HTMLDivElement>(null)
@@ -29,7 +29,7 @@ export const RecipePreview = ({ data, version }: PreviewProps) => {
return () => clearInterval(interval)
}, [])
const recipe = DataModel.unwrapLists(data)
const recipe = model.data
const state = JSON.stringify(recipe)
const items = useMemo<Map<Slot, ItemStack>>(() => {
return placeItems(version, recipe, animation, itemTags ?? new Map())

View File

@@ -1,26 +1,26 @@
import { DataModel } from '@mcschema/core'
import type { Identifier } from 'deepslate'
import { ChunkPos } from 'deepslate'
import type { mat3 } from 'gl-matrix'
import { useCallback, useMemo, useRef, useState } from 'preact/hooks'
import { useLocale, useVersion } from '../../contexts/index.js'
import { useAsync } from '../../hooks/useAsync.js'
import type { Color } from '../../Utils.js'
import { computeIfAbsent, iterateWorld2D, randomSeed, stringToColor } from '../../Utils.js'
import { useLocale } from '../../contexts/index.js'
import { useAsync } from '../../hooks/useAsync.js'
import { Btn } from '../index.js'
import { featureColors } from './Decorator.js'
import { DEEPSLATE } from './Deepslate.js'
import { InteractiveCanvas2D } from './InteractiveCanvas2D.jsx'
import type { PreviewProps } from './index.js'
import { InteractiveCanvas2D } from './InteractiveCanvas2D.jsx'
export const StructureSetPreview = ({ data, version, shown }: PreviewProps) => {
export const StructureSetPreview = ({ model, shown }: PreviewProps) => {
const { locale } = useLocale()
const { version } = useVersion()
const [seed, setSeed] = useState(randomSeed())
const state = JSON.stringify(data)
const state = JSON.stringify(model.data)
const { value: structureSet } = useAsync(async () => {
await DEEPSLATE.loadVersion(version)
const structureSet = DEEPSLATE.loadStructureSet(DataModel.unwrapLists(data), seed)
const structureSet = DEEPSLATE.loadStructureSet(model.data, seed)
return structureSet
}, [state, version, seed])

View File

@@ -1,5 +1,4 @@
import type { DataModel } from '@mcschema/core'
import type { VersionId } from '../../services/index.js'
import type { FileModel } from '../../services/index.js'
export * from './BiomeSourcePreview.js'
export * from './BlockStatePreview.jsx'
@@ -12,9 +11,7 @@ export * from './NoiseSettingsPreview.js'
export * from './RecipePreview.jsx'
export * from './StructureSetPreview.jsx'
export type PreviewProps = {
model: DataModel,
data: any,
shown: boolean,
version: VersionId,
export interface PreviewProps {
model: FileModel
shown: boolean
}