import type { DataModel } from '@mcschema/core' import { useEffect, useErrorBoundary, useState } from 'preact/hooks' import config from '../../config.json' import { Analytics } from '../Analytics' import { Ad, Btn, BtnInput, BtnMenu, ErrorPanel, HasPreview, Octicon, PreviewPanel, SourcePanel, Tree } from '../components' import { fetchPreset } from '../DataFetcher' import { locale } from '../Locales' import type { BlockStateRegistry, VersionId } from '../Schemas' import { checkVersion, getBlockStates, getCollections, getModel } from '../Schemas' type GeneratorProps = { lang: string, changeTitle: (title: string, versions?: string[]) => unknown, version: VersionId, onChangeVersion: (version: VersionId) => unknown, generator?: string, path?: string, category?: string, } export function Generator({ lang, changeTitle, version, onChangeVersion, category, generator }: GeneratorProps) { const loc = locale.bind(null, lang) const [error, setError] = useState('') const [errorBoundary] = useErrorBoundary() if (errorBoundary) { return } const id = category ? `${category}/${generator}` : generator ?? '' const modelConfig = config.models.find(m => m.id === id) if (!modelConfig) { return } const minVersion = config.models.find(m => m.id === id)?.minVersion ?? '1.15' const allowedVersions = config.versions .filter(v => checkVersion(v.id, minVersion)) .map(v => v.id as VersionId) changeTitle(loc('title.generator', loc(id)), allowedVersions) const [model, setModel] = useState(null) const [blockStates, setBlockStates] = useState(null) useEffect(() => { setModel(null) getBlockStates(version) .then(b => setBlockStates(b)) getModel(version, id) .then(m => setModel(m)) .catch(e => setError(e.message)) }, [version, category, generator]) const reset = () => { Analytics.generatorEvent('reset') model?.reset(model.schema.default(), true) } const undo = (e: MouseEvent) => { e.stopPropagation() Analytics.generatorEvent('undo', 'Menu') model?.undo() } const redo = (e: MouseEvent) => { e.stopPropagation() Analytics.generatorEvent('redo', 'Menu') model?.redo() } const onKeyUp = (e: KeyboardEvent) => { if (e.ctrlKey && e.key === 'z') { Analytics.generatorEvent('undo', 'Hotkey') model?.undo() } else if (e.ctrlKey && e.key === 'y') { Analytics.generatorEvent('redo', 'Hotkey') model?.redo() } } useEffect(() => { document.addEventListener('keyup', onKeyUp) return () => { document.removeEventListener('keyup', onKeyUp) } }, [model]) const [presetFilter, setPresetFilter] = useState('') const [presetResults, setPresetResults] = useState([]) const registry = (modelConfig.category ? modelConfig.category + '/' : '') + modelConfig.schema useEffect(() => { if (!modelConfig.path) return getCollections(version) .then(collections => { const terms = (presetFilter ?? '').trim().split(' ') const presets = collections.get(registry) .map(p => p.slice(10)) .filter(p => terms.every(t => p.includes(t))) if (presets) { setPresetResults(presets) } }) .catch(e => setError(e.message)) }, [version, category, generator, presetFilter]) const loadPreset = (id: string) => { Analytics.generatorEvent('load-preset', id) fetchPreset(version, modelConfig.path!, id).then(preset => { model?.reset(preset, false) }) } const [sourceShown, setSourceShown] = useState(window.innerWidth > 820) const [doCopy, setCopy] = useState(0) const [doDownload, setDownload] = useState(0) const [doImport, setImport] = useState(0) const copySource = () => { Analytics.generatorEvent('copy') setCopy(doCopy + 1) } const downloadSource = () => { Analytics.generatorEvent('download') setDownload(doDownload + 1) } const importSource = () => { Analytics.generatorEvent('import') setSourceShown(true) setImport(doImport + 1) } const toggleSource = () => { Analytics.generatorEvent('toggle-output', !sourceShown ? 'visible' : 'hidden') setSourceShown(!sourceShown) setCopy(0) setDownload(0) setImport(0) } const [previewShown, setPreviewShown] = useState(false) const hasPreview = HasPreview.includes(id) let actionsShown = 1 if (hasPreview) actionsShown += 1 if (sourceShown) actionsShown += 2 const togglePreview = () => { Analytics.generatorEvent('toggle-preview', !previewShown ? 'visible' : 'hidden') setPreviewShown(!previewShown) } return <> {modelConfig.path && {presetResults.map(preset => loadPreset(preset)} />)} {presetResults.length === 0 && } } {allowedVersions.reverse().map(v => onChangeVersion(v)} /> )} {error && } {previewShown ? Octicon.x_circle : Octicon.play} {Octicon.download} {Octicon.clippy} {sourceShown ? Octicon.chevron_right : Octicon.code} > }