Add generator quick switcher

This commit is contained in:
Misode
2021-07-02 04:38:02 +02:00
parent 985057ab4e
commit 7c266ca945
9 changed files with 56 additions and 39 deletions
+1 -1
View File
@@ -62,7 +62,7 @@ function Main() {
}
return <>
<Header {...{lang, title, theme, language: lang, changeLanguage, changeTheme}} />
<Header {...{lang, title, version, theme, language: lang, changeLanguage, changeTheme}} />
<Router onChange={changeRoute}>
<Home path="/" {...{lang, changeTitle}} />
<FieldSettings path="/settings/fields" {...{lang, changeTitle}} />
+2 -2
View File
@@ -11,10 +11,10 @@ export type VersionId = typeof VersionIds[number]
export type BlockStateRegistry = {
[block: string]: {
properties: {
properties?: {
[key: string]: string[],
},
default: {
default?: {
[key: string]: string,
},
},
+15 -7
View File
@@ -1,7 +1,9 @@
import { getCurrentUrl, Link } from 'preact-router'
import { getCurrentUrl, Link, route } from 'preact-router'
import { Btn, BtnMenu, Icons, Octicon } from '.'
import config from '../../config.json'
import { locale } from '../Locales'
import type { VersionId } from '../Schemas'
import { checkVersion } from '../Schemas'
import { cleanUrl, getGenerator } from '../Utils'
const Themes: Record<string, keyof typeof Octicon> = {
@@ -13,21 +15,27 @@ const Themes: Record<string, keyof typeof Octicon> = {
type HeaderProps = {
lang: string,
title: string,
version: VersionId,
theme: string,
changeTheme: (theme: string) => unknown,
language: string,
changeLanguage: (language: string) => unknown,
}
export function Header({ lang, title, theme, changeTheme, language, changeLanguage }: HeaderProps) {
export function Header({ lang, title, version, theme, changeTheme, language, changeLanguage }: HeaderProps) {
const loc = locale.bind(null, lang)
const category = getGenerator(getCurrentUrl())?.category
const gen = getGenerator(getCurrentUrl())
return <header>
<div class="header-title">
<Link class="home-link" href={typeof category === 'string' ? cleanUrl(category) : '/'}>
{Icons.home}
</Link>
<div class="title">
<Link class="home-link" href="/">{Icons.home}</Link>
<h2>{title}</h2>
{gen && <BtnMenu icon="chevron_down">
{config.generators
.filter(g => g.category === gen?.category && checkVersion(version, g.minVersion))
.map(g =>
<Btn label={loc(g.id)} active={g.id === gen.id} onClick={() => route(cleanUrl(g.url))} />
)}
</BtnMenu>}
</div>
<nav>
<ul>
+1
View File
@@ -2,6 +2,7 @@ export const Octicon = {
archive: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.75 2.5a.25.25 0 00-.25.25v1.5c0 .138.112.25.25.25h12.5a.25.25 0 00.25-.25v-1.5a.25.25 0 00-.25-.25H1.75zM0 2.75C0 1.784.784 1 1.75 1h12.5c.966 0 1.75.784 1.75 1.75v1.5A1.75 1.75 0 0114.25 6H1.75A1.75 1.75 0 010 4.25v-1.5zM1.75 7a.75.75 0 01.75.75v5.5c0 .138.112.25.25.25h10.5a.25.25 0 00.25-.25v-5.5a.75.75 0 111.5 0v5.5A1.75 1.75 0 0113.25 15H2.75A1.75 1.75 0 011 13.25v-5.5A.75.75 0 011.75 7zm4.5 1a.75.75 0 000 1.5h3.5a.75.75 0 100-1.5h-3.5z"></path></svg>,
arrow_left: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.78 12.53a.75.75 0 01-1.06 0L2.47 8.28a.75.75 0 010-1.06l4.25-4.25a.75.75 0 011.06 1.06L4.81 7h7.44a.75.75 0 010 1.5H4.81l2.97 2.97a.75.75 0 010 1.06z"></path></svg>,
arrow_right: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8.22 2.97a.75.75 0 011.06 0l4.25 4.25a.75.75 0 010 1.06l-4.25 4.25a.75.75 0 01-1.06-1.06l2.97-2.97H3.75a.75.75 0 010-1.5h7.44L8.22 4.03a.75.75 0 010-1.06z"></path></svg>,
chevron_down: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M12.78 6.22a.75.75 0 010 1.06l-4.25 4.25a.75.75 0 01-1.06 0L3.22 7.28a.75.75 0 011.06-1.06L8 9.94l3.72-3.72a.75.75 0 011.06 0z"></path></svg>,
chevron_right: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M6.22 3.22a.75.75 0 011.06 0l4.25 4.25a.75.75 0 010 1.06l-4.25 4.25a.75.75 0 01-1.06-1.06L9.94 8 6.22 4.28a.75.75 0 010-1.06z"></path></svg>,
clippy: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M5.75 1a.75.75 0 00-.75.75v3c0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75v-3a.75.75 0 00-.75-.75h-4.5zm.75 3V2.5h3V4h-3zm-2.874-.467a.75.75 0 00-.752-1.298A1.75 1.75 0 002 3.75v9.5c0 .966.784 1.75 1.75 1.75h8.5A1.75 1.75 0 0014 13.25v-9.5a1.75 1.75 0 00-.874-1.515.75.75 0 10-.752 1.298.25.25 0 01.126.217v9.5a.25.25 0 01-.25.25h-8.5a.25.25 0 01-.25-.25v-9.5a.25.25 0 01.126-.217z"></path></svg>,
code: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M4.72 3.22a.75.75 0 011.06 1.06L2.06 8l3.72 3.72a.75.75 0 11-1.06 1.06L.47 8.53a.75.75 0 010-1.06l4.25-4.25zm6.56 0a.75.75 0 10-1.06 1.06L13.94 8l-3.72 3.72a.75.75 0 101.06 1.06l4.25-4.25a.75.75 0 000-1.06l-4.25-4.25z"></path></svg>,
+15 -16
View File
@@ -26,22 +26,21 @@ export function Generator({ lang, changeTitle, version, onChangeVersion }: Gener
const dismissError = (error: string) => {
setErrors(errors.filter(e => e !== error))
}
const [errorBoundary] = useErrorBoundary()
const [errorBoundary, errorRetry] = useErrorBoundary()
if (errorBoundary) {
return <main><ErrorPanel error={`Something went wrong rendering the generator: ${errorBoundary.message}`}/></main>
return <main><ErrorPanel error={`Something went wrong rendering the generator: ${errorBoundary.message}`} onDismiss={errorRetry} /></main>
}
const generator = getGenerator(getCurrentUrl())
if (!generator) {
return <main><ErrorPanel error="Cannot find generator" /></main>
const gen = getGenerator(getCurrentUrl())
if (!gen) {
return <main><ErrorPanel error={`Cannot find generator "${getCurrentUrl()}"`} /></main>
}
const minVersion = generator.minVersion ?? '1.15'
const allowedVersions = config.versions
.filter(v => checkVersion(v.id, minVersion))
.filter(v => checkVersion(v.id, gen.minVersion))
.map(v => v.id as VersionId)
changeTitle(loc('title.generator', loc(generator.id)), allowedVersions)
changeTitle(loc('title.generator', loc(gen.id)), allowedVersions)
const [model, setModel] = useState<DataModel | null>(null)
const [blockStates, setBlockStates] = useState<BlockStateRegistry | null>(null)
@@ -49,10 +48,10 @@ export function Generator({ lang, changeTitle, version, onChangeVersion }: Gener
setModel(null)
getBlockStates(version)
.then(b => setBlockStates(b))
getModel(version, generator.id)
getModel(version, gen.id)
.then(m => setModel(m))
.catch(e => { console.error(e); addError(e.message) })
}, [version, generator.id])
}, [version, gen.id])
const reset = () => {
Analytics.generatorEvent('reset')
@@ -91,7 +90,7 @@ export function Generator({ lang, changeTitle, version, onChangeVersion }: Gener
getCollections(version)
.then(collections => {
const terms = (presetFilter ?? '').trim().split(' ')
const presets = collections.get(generator.id)
const presets = collections.get(gen.id)
.map(p => p.slice(10))
.filter(p => terms.every(t => p.includes(t)))
if (presets) {
@@ -99,11 +98,11 @@ export function Generator({ lang, changeTitle, version, onChangeVersion }: Gener
}
})
.catch(e => { console.error(e); addError(e.message) })
}, [version, generator.id, presetFilter])
}, [version, gen.id, presetFilter])
const loadPreset = (id: string) => {
Analytics.generatorEvent('load-preset', id)
fetchPreset(version, generator.path ?? generator.id, id).then(preset => {
fetchPreset(version, gen.path ?? gen.id, id).then(preset => {
model?.reset(preset, false)
})
}
@@ -136,7 +135,7 @@ export function Generator({ lang, changeTitle, version, onChangeVersion }: Gener
const [previewShown, setPreviewShown] = useState(false)
const hasPreview = HasPreview.includes(generator.id)
const hasPreview = HasPreview.includes(gen.id)
let actionsShown = 1
if (hasPreview) actionsShown += 1
if (sourceShown) actionsShown += 2
@@ -187,10 +186,10 @@ export function Generator({ lang, changeTitle, version, onChangeVersion }: Gener
</div>
</div>
<div class={`popup-preview${previewShown ? ' shown' : ''}`}>
<PreviewPanel {...{lang, model, version, id: generator.id}} shown={previewShown} onError={addError} />
<PreviewPanel {...{lang, model, version, id: gen.id}} shown={previewShown} onError={addError} />
</div>
<div class={`popup-source${sourceShown ? ' shown' : ''}`}>
<SourcePanel {...{lang, model, blockStates, doCopy, doDownload, doImport}} name={generator.schema ?? 'data'} onError={addError} />
<SourcePanel {...{lang, model, blockStates, doCopy, doDownload, doImport}} name={gen.schema ?? 'data'} onError={addError} />
</div>
</>
}
+5 -5
View File
@@ -16,7 +16,7 @@ export type TreeProps = {
const selectRegistries = ['loot_table.type', 'loot_entry.type', 'function.function', 'condition.condition', 'criterion.trigger', 'dimension.generator.type', 'dimension.generator.biome_source.type', 'carver.type', 'feature.type', 'decorator.type', 'feature.tree.minimum_size.type', 'block_state_provider.type', 'trunk_placer.type', 'foliage_placer.type', 'tree_decorator.type', 'int_provider.type', 'float_provider.type', 'height_provider.type', 'structure_feature.type', 'surface_builder.type', 'processor.processor_type', 'rule_test.predicate_type', 'pos_rule_test.predicate_type', 'template_element.element_type', 'block_placer.type']
const hiddenFields = ['number_provider.type', 'score_provider.type', 'nbt_provider.type', 'int_provider.type', 'float_provider.type', 'height_provider.type']
const flattenedFields = ['feature.config', 'decorator.config', 'int_provider.value', 'float_provider.value', 'block_state_provider.simple_state_provider.state', 'block_state_provider.rotated_block_provider.state', 'block_state_provider.weighted_state_provider.entries.entry.data', 'rule_test.block_state', 'structure_feature.config', 'surface_builder.config', 'template_pool.elements.entry.element']
const inlineFields = ['loot_entry.type', 'function.function', 'condition.condition', 'criterion.trigger', 'dimension.generator.type', 'dimension.generator.biome_source.type', 'feature.type', 'decorator.type', 'block_state_provider.type', 'feature.tree.minimum_size.type', 'trunk_placer.type', 'foliage_placer.type', 'tree_decorator.type', 'block_placer.type', 'rule_test.predicate_type', 'processor.processor_type', 'template_element.element_type']
const inlineFields = ['loot_entry.type', 'function.function', 'condition.condition', 'criterion.trigger', 'dimension.generator.type', 'dimension.generator.biome_source.type', 'feature.type', 'decorator.type', 'block_state_provider.type', 'feature.tree.minimum_size.type', 'trunk_placer.type', 'foliage_placer.type', 'tree_decorator.type', 'block_placer.type', 'rule_test.predicate_type', 'processor.processor_type', 'template_element.element_type', 'nbt_operation.op', 'number_provider.value', 'score_provider.name', 'score_provider.target', 'nbt_provider.source', 'nbt_provider.target']
/**
* Secondary model used to remember the keys of a map
@@ -132,9 +132,9 @@ export const renderHtml: Hook<[any, TreeProps], [string, string, string]> = {
const suffix = keyRendered[1] + `<button class="add" data-id="${onAdd}" aria-label="${props.loc('button.add')}">${Octicon.plus_circle}</button>`
if (blockState && path.last() === 'Properties') {
if (typeof value !== 'object') value = {}
const properties = Object.entries(blockState.properties)
const properties = Object.entries(blockState.properties ?? {})
.map(([key, values]) => [key, StringNode(null!, { enum: values })])
Object.entries(blockState.properties).forEach(([key, values]) => {
Object.entries(blockState.properties ?? {}).forEach(([key, values]) => {
if (typeof value[key] !== 'string') {
path.model.errors.add(path.push(key), 'error.expected_string')
} else if (!values.includes(value[key])) {
@@ -151,9 +151,9 @@ export const renderHtml: Hook<[any, TreeProps], [string, string, string]> = {
const childPath = path.modelPush(key)
const category = children.category(childPath)
const childrenSchema = blockState
? StringNode(null!, { enum: blockState.properties[key] ?? [] })
? StringNode(null!, { enum: blockState.properties?.[key] ?? [] })
: children
if (blockState?.properties[key] && !blockState.properties[key].includes(value[key])) {
if (blockState?.properties?.[key] && !blockState.properties?.[key].includes(value[key])) {
path.model.errors.add(childPath, 'error.invalid_enum_option', value[key])
}
const [cPrefix, cSuffix, cBody] = childrenSchema.hook(this, childPath, value[key], props)
+1 -1
View File
@@ -27,7 +27,7 @@ export const transformOutput: Hook<[any, OutputProps], any> = {
const res: any = {}
Object.keys(value).forEach(f => {
if (blockState) {
if (!Object.keys(blockState.properties).includes(f)) return
if (!Object.keys(blockState.properties ?? {}).includes(f)) return
}
res[f] = children.hook(this, path.push(f), value[f], props)
})
+1 -1
View File
@@ -10,7 +10,7 @@
"preview": "Visualisieren",
"reset": "Zurücksetzen",
"share": "Teilen",
"title.generator": "%0%_Generator",
"title.generator": "%0%-Generator",
"title.home": "Datenpaketgeneratoren",
"worldgen/biome": "Biom",
"worldgen/configured_carver": "Borer",
+15 -6
View File
@@ -99,12 +99,12 @@ body[data-panel="settings"] header {
position: fixed;
}
.header-title {
.title {
display: flex;
align-items: center;
}
.header-title h2 {
.title h2 {
color: var(--nav);
}
@@ -170,14 +170,23 @@ nav li svg {
height: 24px;
}
nav li .btn-menu > .btn {
.title .btn-menu {
margin: 0 8px;
}
.title .btn-menu > .btn svg {
width: 24px;
height: 24px;
}
header .btn-menu > .btn {
background: none !important;
padding: 0;
box-shadow: none;
fill: var(--nav);
}
nav li .btn-menu > .btn:hover {
header .btn-menu > .btn:hover {
fill: var(--nav-hover);
}
@@ -683,8 +692,8 @@ hr {
/* SMALL */
@media screen and (max-width: 580px) {
.header-title h2 {
font-size: 22px;
.title h2 {
font-size: 18px;
}
body nav li {