From e5d2c02fb3add341430c32334719344e3f980b54 Mon Sep 17 00:00:00 2001 From: Misode Date: Tue, 28 Sep 2021 23:46:21 +0200 Subject: [PATCH] Show nested feature decorators in a list (#173) * Show nested feature decorators in a list * Fix swapping decorator indices * Move wrapper model creation to separate function * Add setting string node on enter --- package-lock.json | 14 ++-- package.json | 2 +- src/app/Schemas.ts | 20 ++++- src/app/Utils.ts | 4 + src/app/hooks/useCanvas.ts | 6 +- src/app/schema/ModelWrapper.ts | 25 ++++++ src/app/schema/renderHtml.tsx | 147 ++++++++++++++++++++++++++------- src/locales/en.json | 2 + src/styles/nodes.css | 8 +- 9 files changed, 180 insertions(+), 48 deletions(-) create mode 100644 src/app/schema/ModelWrapper.ts diff --git a/package-lock.json b/package-lock.json index c77680a6..b22e58e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "@mcschema/core": "^0.12.8", + "@mcschema/core": "^0.12.9", "@mcschema/java-1.15": "^0.2.0", "@mcschema/java-1.16": "^0.6.3", "@mcschema/java-1.17": "^0.2.23", @@ -315,9 +315,9 @@ } }, "node_modules/@mcschema/core": { - "version": "0.12.8", - "resolved": "https://registry.npmjs.org/@mcschema/core/-/core-0.12.8.tgz", - "integrity": "sha512-O7NdYlxBFgK+iH9IE4/A8JX9ESPlxZsGke66NBQ/lFIdMYMOKpTeJBk6SKgEHSneqinf9w3IXGaE3aqDeJMo6A==" + "version": "0.12.9", + "resolved": "https://registry.npmjs.org/@mcschema/core/-/core-0.12.9.tgz", + "integrity": "sha512-oPmfg2NRSzy4+xIbERPPYQ8yfuQVlmznGbqnob0Cww6AeNzmEZm5c9+wVIqHSRlFS1ZsvCpBtUfXL1t83AUDgQ==" }, "node_modules/@mcschema/java-1.15": { "version": "0.2.1", @@ -2790,9 +2790,9 @@ } }, "@mcschema/core": { - "version": "0.12.8", - "resolved": "https://registry.npmjs.org/@mcschema/core/-/core-0.12.8.tgz", - "integrity": "sha512-O7NdYlxBFgK+iH9IE4/A8JX9ESPlxZsGke66NBQ/lFIdMYMOKpTeJBk6SKgEHSneqinf9w3IXGaE3aqDeJMo6A==" + "version": "0.12.9", + "resolved": "https://registry.npmjs.org/@mcschema/core/-/core-0.12.9.tgz", + "integrity": "sha512-oPmfg2NRSzy4+xIbERPPYQ8yfuQVlmznGbqnob0Cww6AeNzmEZm5c9+wVIqHSRlFS1ZsvCpBtUfXL1t83AUDgQ==" }, "@mcschema/java-1.15": { "version": "0.2.1", diff --git a/package.json b/package.json index a655499a..786f7218 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "author": "Misode", "license": "MIT", "dependencies": { - "@mcschema/core": "^0.12.8", + "@mcschema/core": "^0.12.9", "@mcschema/java-1.15": "^0.2.0", "@mcschema/java-1.16": "^0.6.3", "@mcschema/java-1.17": "^0.2.23", diff --git a/src/app/Schemas.ts b/src/app/Schemas.ts index 4a7332d6..340c5fb5 100644 --- a/src/app/Schemas.ts +++ b/src/app/Schemas.ts @@ -1,5 +1,5 @@ -import type { CollectionRegistry, SchemaRegistry } from '@mcschema/core' -import { DataModel } from '@mcschema/core' +import type { CollectionRegistry, INode, SchemaRegistry } from '@mcschema/core' +import { ChoiceNode, DataModel, Reference, StringNode } from '@mcschema/core' import * as java15 from '@mcschema/java-1.15' import * as java16 from '@mcschema/java-1.16' import * as java17 from '@mcschema/java-1.17' @@ -47,6 +47,9 @@ const versionGetter: { 1.18: java18, } +export let CachedDecorator: INode +export let CachedFeature: INode + async function getVersion(id: VersionId): Promise { if (!Versions[id]) { Versions[id] = (async () => { @@ -69,6 +72,19 @@ async function getVersion(id: VersionId): Promise { export async function getModel(version: VersionId, id: string): Promise { if (!Models[id] || Models[id].version !== version) { const versionData = await getVersion(version) + + CachedDecorator = Reference(versionData.schemas, 'configured_decorator') + CachedFeature = ChoiceNode([ + { + type: 'string', + node: StringNode(versionData.collections, { validator: 'resource', params: { pool: '$worldgen/configured_feature' } }), + }, + { + type: 'object', + node: Reference(versionData.schemas, 'configured_feature'), + }, + ], { choiceContext: 'feature' }) + const schemaName = config.generators.find(g => g.id === id)?.schema if (!schemaName) { throw new Error(`Cannot find model ${id}`) diff --git a/src/app/Utils.ts b/src/app/Utils.ts index aec45f3e..c901b540 100644 --- a/src/app/Utils.ts +++ b/src/app/Utils.ts @@ -7,6 +7,10 @@ export function isPromise(obj: any): obj is Promise { return typeof (obj as any)?.then === 'function' } +export function isObject(obj: any) { + return typeof obj === 'object' && obj !== null +} + const dec2hex = (dec: number) => ('0' + dec.toString(16)).substr(-2) export function hexId(length = 12) { diff --git a/src/app/hooks/useCanvas.ts b/src/app/hooks/useCanvas.ts index f44965dc..9b1f411a 100644 --- a/src/app/hooks/useCanvas.ts +++ b/src/app/hooks/useCanvas.ts @@ -60,9 +60,9 @@ export function useCanvas({ size, draw, onDrag, onHover, onLeave }: { document.body.addEventListener('mouseup', onMouseUp) return () => { - canvas.current.removeEventListener('mousedown', onMouseDown) - canvas.current.removeEventListener('mousemove', onMouseMove) - canvas.current.removeEventListener('mouseleave', onMouseLeave) + canvas.current?.removeEventListener('mousedown', onMouseDown) + canvas.current?.removeEventListener('mousemove', onMouseMove) + canvas.current?.removeEventListener('mouseleave', onMouseLeave) document.body.removeEventListener('mouseup', onMouseUp) } }, [...inputs ?? [], canvas.current]) diff --git a/src/app/schema/ModelWrapper.ts b/src/app/schema/ModelWrapper.ts new file mode 100644 index 00000000..ffe811d6 --- /dev/null +++ b/src/app/schema/ModelWrapper.ts @@ -0,0 +1,25 @@ +import type { INode, Path } from '@mcschema/core' +import { DataModel } from '@mcschema/core' + +export class ModelWrapper extends DataModel { + constructor( + schema: INode, + private readonly mapper: (path: Path) => Path, + private readonly getter: (path: Path) => any, + private readonly setter: (path: Path, value: any, silent?: boolean) => any, + ) { + super(schema) + } + + map(path: Path) { + return this.mapper(path) + } + + get(path: Path) { + return this.getter(path) + } + + set(path: Path, value: any, silent?: boolean) { + return this.setter(path, value, silent) + } +} diff --git a/src/app/schema/renderHtml.tsx b/src/app/schema/renderHtml.tsx index 35df7513..c96a643c 100644 --- a/src/app/schema/renderHtml.tsx +++ b/src/app/schema/renderHtml.tsx @@ -1,14 +1,16 @@ -import type { BooleanHookParams, EnumOption, Hook, INode, NumberHookParams, StringHookParams, ValidationOption } from '@mcschema/core' -import { DataModel, MapNode, ModelPath, ObjectNode, Path, relativePath, StringNode } from '@mcschema/core' +import type { BooleanHookParams, EnumOption, Hook, INode, NodeChildren, NumberHookParams, StringHookParams, ValidationOption } from '@mcschema/core' +import { DataModel, ListNode, MapNode, ModelPath, ObjectNode, Path, relativePath, StringNode } from '@mcschema/core' import type { ComponentChildren, JSX } from 'preact' import { memo } from 'preact/compat' -import { useRef, useState } from 'preact/hooks' +import { useState } from 'preact/hooks' import { Btn } from '../components' import { Octicon } from '../components/Octicon' import { useFocus } from '../hooks' import { locale } from '../Locales' import type { BlockStateRegistry } from '../Schemas' -import { deepClone, deepEqual, hexId, newSeed } from '../Utils' +import { CachedDecorator, CachedFeature } from '../Schemas' +import { deepClone, deepEqual, hexId, isObject, newSeed } from '../Utils' +import { ModelWrapper } from './ModelWrapper' const selectRegistries = ['loot_table.type', 'loot_entry.type', 'function.function', 'condition.condition', 'criterion.trigger', 'dimension.generator.type', 'dimension.generator.biome_source.type', 'dimension.generator.biome_source.preset', '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'] @@ -220,7 +222,14 @@ const renderHtml: RenderHook = { return [null, , null] }, - object({ node, getActiveFields, getChildModelPath }, path, value, lang, states, ctx) { + object({ node, config, getActiveFields, getChildModelPath }, path, value, lang, states, ctx) { + if (path.getArray().length == 0 && isDecorated(config.context, value)) { + const { wrapper, fields } = createDecoratorsWrapper(getActiveFields(path), path, value) + value = wrapper.data + getActiveFields = () => fields + getChildModelPath = (path, key) => new ModelPath(wrapper, new Path(path.getArray(), ['feature'])).push(key) + } + let prefix: JSX.Element | null = null let suffix: JSX.Element | null = null if (node.optional()) { @@ -319,40 +328,18 @@ function BooleanSuffix({ path, node, value, lang }: NodeProps } function NumberSuffix({ path, config, integer, value, lang }: NodeProps) { - const [text, setText] = useState(value ?? '') - const [editing, setEditing] = useState(false) - const commitTimeout = useRef() - const commitValue = useRef() - const scheduleCommit = (newValue: number) => { - if (commitTimeout.current) clearTimeout(commitTimeout.current) - commitValue.current = newValue - commitTimeout.current = setTimeout(() => { - path.model.set(path, commitValue.current) - commitValue.current = undefined - setEditing(false) - }, 500) - } const onChange = (evt: Event) => { const value = (evt.target as HTMLInputElement).value const parsed = integer ? parseInt(value) : parseFloat(value) - setText(value) - scheduleCommit(parsed) - } - const onFocus = () => { - setEditing(true) - } - const onBlur = () => { - if (commitValue === undefined) setEditing(false) - setText(commitValue.current ?? value ?? '') + path.model.set(path, parsed) } const onColor = (evt: Event) => { const value = (evt.target as HTMLInputElement).value const parsed = parseInt(value.slice(1), 16) - setText(parsed) - scheduleCommit(parsed) + path.model.set(path, parsed) } return <> - + {if (evt.key === 'Enter') onChange(evt)}} /> {config?.color && } {['dimension.generator.seed', 'dimension.generator.biome_source.seed', 'world_settings.seed'].includes(path.getContext().join('.')) && } @@ -391,7 +378,7 @@ function StringSuffix({ path, getValues, config, node, value, lang, states }: No } else { const datalistId = hexId() return <> - {if (evt.key === 'Enter') onChange(evt)}} list={values.length > 0 ? datalistId : ''} /> {values.length > 0 && {values.map(v =>