diff --git a/src/app/components/BtnMenu.tsx b/src/app/components/BtnMenu.tsx index 0cc1eee3..7e7aaf07 100644 --- a/src/app/components/BtnMenu.tsx +++ b/src/app/components/BtnMenu.tsx @@ -9,15 +9,16 @@ interface BtnMenuProps extends JSX.HTMLAttributes { relative?: boolean, tooltip?: string, tooltipLoc?: 'se' | 'sw' | 'nw', + menuDir?: 'left' | 'right' children: ComponentChildren, } export function BtnMenu(props: BtnMenuProps) { - const { icon, label, relative, tooltip, tooltipLoc, children } = props + const { icon, label, relative, tooltip, tooltipLoc, menuDir, children } = props const [active, setActive] = useFocus() - return
+ return
setActive()} /> - {active &&
+ {active &&
{children}
}
diff --git a/src/app/components/Octicon.tsx b/src/app/components/Octicon.tsx index c2754857..c57dc887 100644 --- a/src/app/components/Octicon.tsx +++ b/src/app/components/Octicon.tsx @@ -71,4 +71,5 @@ export const Octicon = { upload: , x: , x_circle: , + wrap_inside: } diff --git a/src/app/components/generator/JsonFileView.tsx b/src/app/components/generator/JsonFileView.tsx index 734c63c0..440ff3ff 100644 --- a/src/app/components/generator/JsonFileView.tsx +++ b/src/app/components/generator/JsonFileView.tsx @@ -62,11 +62,11 @@ export function JsonFileView({ docAndNode, node }: JsonFileViewProps) { } const rootType = getRootType(resourceType) const type = simplifyType(rootType, ctx) - return type + return {type, rootType} }, [resourceType, ctx]) return
- {(ctx && mcdocType) && } + {(ctx && mcdocType) && }
} diff --git a/src/app/components/generator/McdocHelpers.ts b/src/app/components/generator/McdocHelpers.ts index 9928e15c..7b133808 100644 --- a/src/app/components/generator/McdocHelpers.ts +++ b/src/app/components/generator/McdocHelpers.ts @@ -2,8 +2,9 @@ import * as core from '@spyglassmc/core' import type { JsonNode, JsonPairNode } from '@spyglassmc/json' import { JsonArrayNode, JsonObjectNode, JsonStringNode } from '@spyglassmc/json' import { JsonStringOptions } from '@spyglassmc/json/lib/parser/string.js' -import type { Attributes, AttributeValue, ListType, McdocType, NumericType, PrimitiveArrayType, TupleType, UnionType } from '@spyglassmc/mcdoc' +import type { Attributes, AttributeValue, DispatcherType, ListType, McdocType, NumericType, PrimitiveArrayType, ReferenceType, TupleType, UnionType } from '@spyglassmc/mcdoc' import { NumericRange, RangeKind } from '@spyglassmc/mcdoc' +import { TypeDefSymbolData } from '@spyglassmc/mcdoc/lib/binder/index.js' import type { McdocCheckerContext, SimplifiedMcdocType, SimplifiedMcdocTypeNoUnion, SimplifyValueNode } from '@spyglassmc/mcdoc/lib/runtime/checker/index.js' import { simplify } from '@spyglassmc/mcdoc/lib/runtime/checker/index.js' import config from '../../Config.js' @@ -42,7 +43,7 @@ export function getRootDefault(id: string, ctx: core.CheckerContext) { return getDefault(type, core.Range.create(0), ctx) } -export function getDefault(type: SimplifiedMcdocType, range: core.Range, ctx: core.CheckerContext): JsonNode { +export function getDefault(type: McdocType, range: core.Range, ctx: core.CheckerContext): JsonNode { if (type.kind === 'string') { return JsonStringNode.mock(range) } @@ -391,6 +392,7 @@ export function isSelectRegistry(registry: string) { const defaultCollapsedTypes = new Set([ '::java::data::worldgen::surface_rule::SurfaceRule', + '::java::data::worldgen::density_function::DensityFunctionRef' ]) export function isDefaultCollapsedType(type: McdocType) { @@ -399,6 +401,10 @@ export function isDefaultCollapsedType(type: McdocType) { } return false } +export function canCollapse(node:JsonNode | undefined) +{ + return node !== undefined && (node.type === 'json:array' || node.type == 'json:object'); +} interface SimplifyNodeContext { key?: JsonStringNode @@ -472,6 +478,126 @@ function inferType(node: JsonNode): Exclude { } } +export type RecursiveSlot = { + identifier: string + dispatcher: DispatcherType + fieldKey: string + isArrayField?: boolean + fieldCount: number +} +function simplifyReference(type: ReferenceType, ctx:core.CheckerContext) +{ + let finalRefType = type; + while(true) + { + const newTypeDef = (ctx.symbols.query(ctx.doc, 'mcdoc', finalRefType.path!).symbol?.data as TypeDefSymbolData | undefined)?.typeDef + if(newTypeDef?.kind === 'reference') + finalRefType = newTypeDef + else break; + } + return finalRefType; +} +export function collectRecursiveDefinitions(ctx:core.CheckerContext, type: McdocType){ + let recursiveSlots:RecursiveSlot[] = [] + if(type.kind === 'reference') + { + const targetType = simplifyReference(type, ctx); + addRecursiveDefinitions(ctx, targetType, targetType, recursiveSlots); + } + else if(type.kind === 'dispatcher') + { + for(const index of type.parallelIndices) + { + if(index.kind === 'static') + { + const querySymbol = ctx.symbols.query(ctx.doc, 'mcdoc/dispatcher', type.registry, index.value).symbol + const dispatchedType = (querySymbol?.data as TypeDefSymbolData | undefined)?.typeDef + if(dispatchedType?.kind === 'reference') + { + const targetType = simplifyReference(dispatchedType, ctx); + addRecursiveDefinitions(ctx, targetType, targetType, recursiveSlots); + } + } + } + } + + return recursiveSlots +} +function addRecursiveDefinitions(ctx:core.CheckerContext, targetType: ReferenceType, referencedType:McdocType | undefined, recursiveSlots:RecursiveSlot[]) { + if(referencedType == undefined) return; + if(referencedType.kind === 'union') { + for(const member of referencedType.members){ + addRecursiveDefinitions(ctx, targetType, member, recursiveSlots) + } + } else if(referencedType.kind === 'reference' && referencedType.path != undefined) { + + if(referencedType.path == undefined) return; + const docValue = ctx.symbols.query(ctx.doc, 'mcdoc', referencedType.path).symbol; + if(!docValue?.data) return; + addRecursiveDefinitions(ctx, targetType, (docValue.data as TypeDefSymbolData | undefined)?.typeDef , recursiveSlots); + } else if(referencedType.kind === 'dispatcher') + { + for(const index of referencedType.parallelIndices) + { + if(index.kind === 'static') + { + const docValue = ctx.symbols.query(ctx.doc, 'mcdoc/dispatcher', referencedType.registry, index.value).symbol + if(docValue?.data) + addRecursiveDefinitions(ctx, targetType, (docValue.data as TypeDefSymbolData).typeDef, recursiveSlots); + } + } + } + else if(referencedType.kind === 'struct') { + for(const field of referencedType.fields) { + if(field.kind === 'spread') + { + if(field.type.kind === 'dispatcher'){ + const symbols = ctx.symbols.global['mcdoc/dispatcher']?.[field.type.registry] + for(const key in symbols?.members) + { + const item = symbols.members[key] as core.Symbol + if(item == undefined || item.data == undefined) continue + const itemData = item.data as TypeDefSymbolData; + if(itemData.typeDef.kind != 'reference' || itemData.typeDef.path == undefined) continue + const simplified = simplifyType(itemData.typeDef, ctx); + if(simplified.kind === 'struct') + { + for(const spreadField of simplified.fields){ + if(spreadField.key.kind !== 'literal') continue; + if(spreadField.type.kind === 'reference') + { + if(simplifyReference(spreadField.type, ctx).path === targetType.path) + { + recursiveSlots.push({identifier: 'minecraft:' + key, dispatcher: field.type, fieldKey: spreadField.key.value.value.toString(), fieldCount: simplified.fields.length}) + } + } + else if(spreadField.type.kind === 'list' && spreadField.type.item.kind === 'reference'){ + if(simplifyReference(spreadField.type.item, ctx).path === targetType.path) + { + recursiveSlots.push({identifier: 'minecraft:' + key, dispatcher: field.type, fieldKey: spreadField.key.value.value.toString(), isArrayField: true, fieldCount: simplified.fields.length}) + } + } + // else if(spreadField.type.kind === 'struct') { + // for(const childField of spreadField.type.fields) + // { + // console.log('todo', childField); + // } + // } + // else if(spreadField.type.kind === 'union') { + // for(const childMember of spreadField.type.members) + // { + // console.log('todo', childMember); + // } + // } + } + } + } + } + } + } + } +} + export function quickEqualTypes(a: SimplifiedMcdocTypeNoUnion, b: SimplifiedMcdocTypeNoUnion): boolean { if (a === b) { return true diff --git a/src/app/components/generator/McdocRenderer.tsx b/src/app/components/generator/McdocRenderer.tsx index eeb5035e..7a31fe7b 100644 --- a/src/app/components/generator/McdocRenderer.tsx +++ b/src/app/components/generator/McdocRenderer.tsx @@ -20,10 +20,11 @@ import { useFocus } from '../../hooks/useFocus.js' import { checkVersion } from '../../services/Versions.js' import { generateColor, hexId, intToHexRgb, randomInt, randomSeed } from '../../Utils.js' import { Btn } from '../Btn.jsx' +import { BtnMenu } from '../BtnMenu.jsx' import { ItemDisplay } from '../ItemDisplay.jsx' import { ItemDisplay1204 } from '../ItemDisplay1204.jsx' import { Octicon } from '../Octicon.jsx' -import { formatIdentifier, getCategory, getChange, getDefault, getItemType, isDefaultCollapsedType, isFixedList, isInlineTuple, isListOrArray, isNumericType, isSelectRegistry, quickEqualTypes, simplifyType } from './McdocHelpers.js' +import { canCollapse, collectRecursiveDefinitions, formatIdentifier, getCategory, getChange, getDefault, getItemType, isDefaultCollapsedType, isFixedList, isInlineTuple, isListOrArray, isNumericType, isSelectRegistry, quickEqualTypes, RecursiveSlot, simplifyType } from './McdocHelpers.js' export interface McdocContext extends core.CheckerContext { makeEdit: MakeEdit @@ -38,7 +39,10 @@ interface Props { node: JsonNode | undefined ctx: McdocContext } -export function McdocRoot({ type, node, ctx } : Props) { +interface McdocRootProps extends Props{ + rootType: McdocType +} +export function McdocRoot({ type, rootType, node, ctx } : McdocRootProps) { const { locale } = useLocale() if (type.kind === 'struct' && type.fields.length > 0 && JsonObjectNode.is(node)) { @@ -50,6 +54,7 @@ export function McdocRoot({ type, node, ctx } : Props) { + {node != undefined && }
@@ -590,7 +595,7 @@ function StaticField({ pair, index, field, fieldKey, staticFields, isToggled, ex const child = pair?.value const childType = simplifyType(field.type, ctx, { key: pair?.key, parent: node }) - const canToggle = isDefaultCollapsedType(field.type) + const canToggle = isDefaultCollapsedType(field.type) && canCollapse(child) const isCollapsed = canToggle && isToggled !== true const makeFieldEdit = useCallback((edit) => { @@ -660,11 +665,99 @@ function StaticField({ pair, index, field, fieldKey, staticFields, isToggled, ex )} {!isCollapsed && } + {child != undefined && }
{!isCollapsed && } } +interface RecursiveContextMenuProps{ + type: McdocType + node: JsonNode + ctx: McdocContext +} +function RecursiveContextMenu({type, node, ctx} : RecursiveContextMenuProps){ + const wrapTargets = useMemo(() => { + return collectRecursiveDefinitions(ctx, type) + }, [type, ctx.doc, ctx.symbols]); + + const onclick = useCallback((wrappable: RecursiveSlot) => { + ctx.makeEdit(range => { + //const keyNode = JsonStringNode.mock(core.Range.create(0)) + //keyNode.value = 'type' + const objectNode = JsonObjectNode.mock(range) + for(const index of wrappable.dispatcher.parallelIndices) + { + let valueNode = JsonStringNode.mock(core.Range.create(0)); + valueNode.value = wrappable.identifier + + if(index.kind == 'static') + { + let keyNode = JsonStringNode.mock(core.Range.create(0)); + keyNode.value = index.value + + objectNode.children.push({ + type: 'pair', + range: core.Range.create(0), + key: keyNode, + value: valueNode, + }); + } else if(index.kind == 'dynamic') + { + for(const accessor of index.accessor) + { + let keyNode = JsonStringNode.mock(core.Range.create(0)); + keyNode.value = accessor.toString() + + objectNode.children.push({ + type: 'pair', + range: core.Range.create(0), + key: keyNode, + value: valueNode, + }); + } + } + } + + let assignedNode = node; + + if(wrappable.isArrayField) + { + const arrayNode = JsonArrayNode.mock(core.Range.create(0)); + arrayNode.children.push( + { + type: 'item', + range: core.Range.create(0), + value: assignedNode + }); + assignedNode = arrayNode + } + let childKeyNode = JsonStringNode.mock(core.Range.create(0)); + childKeyNode.value = wrappable.fieldKey + const newPair: core.PairNode = { + type: 'pair', + range: core.Range.create(0), + key: childKeyNode, + value: assignedNode, + } + objectNode.children.push(newPair) + + return objectNode + }) + }, [node, ctx]); + if(wrapTargets.length == 0) return <>; + return + {wrapTargets.map(v => { + let label = formatIdentifier(v.identifier) + if(v.fieldCount > 1) + { + label += '/' + formatIdentifier(v.fieldKey) + } + return onclick(v)}/> + } + )} + +} interface DynamicKeyProps { keyType: SimplifiedMcdocType valueType: McdocType diff --git a/src/styles/nodes.css b/src/styles/nodes.css index 5336c764..f2f707a4 100644 --- a/src/styles/nodes.css +++ b/src/styles/nodes.css @@ -124,6 +124,9 @@ padding-left: 9px; background-color: var(--node-background-input); } +.node-header > .btn-menu > .btn{ + border-radius: 0; +} .node-header > input[type="color"] { padding: 0 2px;