import Split from 'split.js' import { DataModel, TreeView, SourceView, ConditionSchema, LootTableSchema, AdvancementSchema, DimensionSchema, DimensionTypeSchema, LOCALES, locale, COLLECTIONS } from 'minecraft-schemas' import { RegistryFetcher } from './RegistryFetcher' import { SandboxSchema } from './Sandbox' import { ErrorsView } from './ErrorsView' const LOCAL_STORAGE_THEME = 'theme' const publicPath = process.env.NODE_ENV === 'production' ? '/dev/' : '/'; const modelFromPath = (p: string) => p.split('/').filter(e => e.length !== 0).pop() ?? '' const addChecked = (el: HTMLElement) => { el.classList.add('check') setTimeout(() => { el.classList.remove('check') }, 2000) } const languages: { [key: string]: string } = { 'en': 'English', 'pt': 'Português', 'ru': 'Русский', 'zh-CN': '简体中文' } const models: { [key: string]: DataModel } = { 'loot-table': new DataModel(LootTableSchema), 'predicate': new DataModel(ConditionSchema), 'advancement': new DataModel(AdvancementSchema), 'dimension': new DataModel(DimensionSchema), 'dimension-type': new DataModel(DimensionTypeSchema), 'sandbox': new DataModel(SandboxSchema) } const registries = [ 'attribute', 'biome', 'biome_source', 'block', 'enchantment', 'entity_type', 'fluid', 'item', 'loot_condition_type', 'loot_function_type', 'loot_pool_entry_type', 'mob_effect', 'stat_type', 'structure_feature' ] const treeViewObserver = (el: HTMLElement) => { el.querySelectorAll('.node-header[data-error]').forEach(e => { e.insertAdjacentHTML('beforeend', ``) }) el.querySelectorAll('.collapse.closed, button.add').forEach(e => { e.insertAdjacentHTML('afterbegin', ``) }) el.querySelectorAll('.collapse.open, button.remove').forEach(e => { e.insertAdjacentHTML('afterbegin', ``) }) } Promise.all([ fetch(publicPath + 'locales/schema/en.json').then(r => r.json()), fetch(publicPath + 'locales/app/en.json').then(r => r.json()), RegistryFetcher(COLLECTIONS, registries) ]).then(responses => { LOCALES.register('en', {...responses[0], ...responses[1]}) const homeLink = document.getElementById('home-link')! const homeGenerators = document.getElementById('home-generators')! const selectedModel = document.getElementById('selected-model')! const modelSelector = document.getElementById('model-selector')! const modelSelectorMenu = document.getElementById('model-selector-menu')! const languageSelector = document.getElementById('language-selector')! const languageSelectorMenu = document.getElementById('language-selector-menu')! const themeSelector = document.getElementById('theme-selector')! const treeViewEl = document.getElementById('tree-view')! const sourceViewEl = document.getElementById('source-view')! const errorsViewEl = document.getElementById('errors-view')! const homeViewEl = document.getElementById('home-view')! const errorsToggle = document.getElementById('errors-toggle')! const sourceViewOutput = (document.getElementById('source-view-output') as HTMLTextAreaElement) const treeViewOutput = document.getElementById('tree-view-output')! const sourceControlsToggle = document.getElementById('source-controls-toggle')! const sourceControlsMenu = document.getElementById('source-controls-menu')! const sourceControlsCopy = document.getElementById('source-controls-copy')! const sourceControlsDownload = document.getElementById('source-controls-download')! const sourceToggle = document.getElementById('source-toggle')! const treeControlsToggle = document.getElementById('tree-controls-toggle')! const treeControlsMenu = document.getElementById('tree-controls-menu')! const treeControlsVersionToggle = document.getElementById('tree-controls-version-toggle')! const treeControlsVersionMenu = document.getElementById('tree-controls-version-menu')! const treeControlsReset = document.getElementById('tree-controls-reset')! const treeControlsUndo = document.getElementById('tree-controls-undo')! const treeControlsRedo = document.getElementById('tree-controls-redo')! let selected = '' const updateModel = () => { if (models[selected] === undefined) { selectedModel.textContent = locale(`title.home`) } else { selectedModel.textContent = locale(`title.${selected}`) new TreeView(models[selected], treeViewOutput, { showErrors: true, observer: treeViewObserver }), new SourceView(models[selected], sourceViewOutput, { indentation: 2 }), new ErrorsView(models[selected], errorsViewEl) models[selected].invalidate() } modelSelectorMenu.innerHTML = '' Object.keys(models).forEach(m => { modelSelectorMenu.insertAdjacentHTML('beforeend', `
${locale(m)}
`) modelSelectorMenu.lastChild?.addEventListener('click', evt => { reload(publicPath + m) }) }) } const updateLanguage = (key: string) => { LOCALES.language = key document.querySelectorAll('[data-i18n]').forEach(el => { el.textContent = locale(el.attributes.getNamedItem('data-i18n')!.value) }) languageSelectorMenu.innerHTML = '' Object.keys(languages).forEach(key => { languageSelectorMenu.insertAdjacentHTML('beforeend', `
${languages[key]}
`) languageSelectorMenu.lastChild?.addEventListener('click', evt => { updateLanguage(key) languageSelectorMenu.style.visibility = 'hidden' }) }) updateModel() } Split([treeViewEl, sourceViewEl], { sizes: [66, 34] }) homeLink.addEventListener('click', evt => { reload(publicPath) }) modelSelector.addEventListener('click', evt => { modelSelectorMenu.style.visibility = 'visible' document.body.addEventListener('click', evt => { modelSelectorMenu.style.visibility = 'hidden' }, { capture: true, once: true }) }) window.onpopstate = (evt: PopStateEvent) => { reload(location.pathname) } sourceToggle.addEventListener('click', evt => { if (sourceViewEl.classList.contains('active')) { sourceViewEl.classList.remove('active') sourceToggle.classList.remove('toggled') } else { sourceViewEl.classList.add('active') sourceToggle.classList.add('toggled') } }) languageSelector.addEventListener('click', evt => { languageSelectorMenu.style.visibility = 'visible' document.body.addEventListener('click', evt => { languageSelectorMenu.style.visibility = 'hidden' }, { capture: true, once: true }) }) const updateTheme = (theme: string | null) => { if (theme === null) return if (theme === 'dark') { document.documentElement.setAttribute('data-theme', 'dark') themeSelector.classList.add('toggled') localStorage.setItem(LOCAL_STORAGE_THEME, 'dark') } else { document.documentElement.setAttribute('data-theme', 'light') themeSelector.classList.remove('toggled') localStorage.setItem(LOCAL_STORAGE_THEME, 'light') } } updateTheme(localStorage.getItem(LOCAL_STORAGE_THEME)) themeSelector.addEventListener('click', evt => { if (document.documentElement.getAttribute('data-theme') === 'dark') { updateTheme('light') } else { updateTheme('dark') } }) sourceControlsToggle.addEventListener('click', evt => { sourceControlsMenu.style.visibility = 'visible' document.body.addEventListener('click', evt => { sourceControlsMenu.style.visibility = 'hidden' }, { capture: true, once: true }) }) sourceControlsCopy.addEventListener('click', evt => { sourceViewOutput.select() document.execCommand('copy'); addChecked(sourceControlsCopy) }) sourceControlsDownload.addEventListener('click', evt => { const fileContents = encodeURIComponent(JSON.stringify(models[selected].data, null, 2) + "\n") const dataString = "data:text/json;charset=utf-8," + fileContents const downloadAnchor = document.getElementById('source-controls-download-anchor')! downloadAnchor.setAttribute("href", dataString) downloadAnchor.setAttribute("download", "data.json") downloadAnchor.click() }) treeControlsToggle.addEventListener('click', evt => { treeControlsMenu.style.visibility = 'visible' document.body.addEventListener('click', evt => { treeControlsMenu.style.visibility = 'hidden' }, { capture: true, once: true }) }) treeControlsVersionToggle.addEventListener('click', evt => { treeControlsVersionMenu.style.visibility = 'visible' document.body.addEventListener('click', evt => { treeControlsVersionMenu.style.visibility = 'hidden' }, { capture: true, once: true }) }) treeControlsReset.addEventListener('click', evt => { models[selected].reset(models[selected].schema.default(), true) addChecked(treeControlsReset) }) treeControlsUndo.addEventListener('click', evt => { models[selected].undo() }) treeControlsRedo.addEventListener('click', evt => { models[selected].redo() }) document.addEventListener('keyup', evt => { if (evt.ctrlKey && evt.key === 'z') { models[selected].undo() } else if (evt.ctrlKey && evt.key === 'y') { models[selected].redo() } }) errorsToggle.addEventListener('click', evt => { if (errorsViewEl.classList.contains('hidden')) { errorsViewEl.classList.remove('hidden') errorsToggle.classList.remove('toggled') } else { errorsViewEl.classList.add('hidden') errorsToggle.classList.add('toggled') } }) const reload = (target: string) => { selected = modelFromPath(target) ?? '' if (target) { history.pushState(target, 'Change Page', target) } const panels = [treeViewEl, sourceViewEl, errorsViewEl] if (models[selected] === undefined) { homeViewEl.style.display = ''; (document.querySelector('.gutter') as HTMLElement).style.display = 'none' modelSelector.style.display = 'none' panels.forEach(v => v.style.display = 'none') homeGenerators.innerHTML = '' Object.keys(models).forEach(m => { homeGenerators.insertAdjacentHTML('beforeend', `
${locale(m)}
`) homeGenerators.lastChild?.addEventListener('click', evt => { reload(publicPath + m) }) }) } else { homeViewEl.style.display = 'none'; (document.querySelector('.gutter') as HTMLElement).style.display = '' modelSelector.style.display = '' panels.forEach(v => v.style.display = '') } updateModel() updateLanguage('en') } reload(location.pathname) document.body.style.visibility = 'initial' })