Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/app/components/BtnMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,16 @@ interface BtnMenuProps extends JSX.HTMLAttributes<HTMLDivElement> {
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 <div {...props} class={`btn-menu${relative === false ? ' no-relative' : ''} ${props.class}`}>
return <div {...props} class={`btn-menu${relative === false ? ' no-relative' : ''} ${props.class}`} >
<Btn {...{icon, label, tooltip, tooltipLoc}} onClick={() => setActive()} />
{active && <div class="btn-group">
{active && <div class="btn-group" style={menuDir === 'right' ? 'left:0;right:unset' : 'right:0;left:unset'}>
{children}
</div>}
</div>
Expand Down
1 change: 1 addition & 0 deletions src/app/components/Octicon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,5 @@ export const Octicon = {
upload: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8.53 1.22a.75.75 0 00-1.06 0L3.72 4.97a.75.75 0 001.06 1.06l2.47-2.47v6.69a.75.75 0 001.5 0V3.56l2.47 2.47a.75.75 0 101.06-1.06L8.53 1.22zM3.75 13a.75.75 0 000 1.5h8.5a.75.75 0 000-1.5h-8.5z"></path></svg>,
x: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M3.72 3.72a.75.75 0 011.06 0L8 6.94l3.22-3.22a.75.75 0 111.06 1.06L9.06 8l3.22 3.22a.75.75 0 11-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 01-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 010-1.06z"></path></svg>,
x_circle: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M3.404 12.596a6.5 6.5 0 119.192-9.192 6.5 6.5 0 01-9.192 9.192zM2.344 2.343a8 8 0 1011.313 11.314A8 8 0 002.343 2.343zM6.03 4.97a.75.75 0 00-1.06 1.06L6.94 8 4.97 9.97a.75.75 0 101.06 1.06L8 9.06l1.97 1.97a.75.75 0 101.06-1.06L9.06 8l1.97-1.97a.75.75 0 10-1.06-1.06L8 6.94 6.03 4.97z"></path></svg>,
wrap_inside: <svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><rect x="4" y="4" width="8" height="8" rx="1" stroke="currentColor" fill="none" stroke-width="1"/><path d="M3 6 C3 4.5 4.2 3.5 5.7 3.5H10 M10 3.5 L8.8 2.3 M10 3.5 L8.8 4.7" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"/><path d="M13 10 C13 11.5 11.8 12.5 10.3 12.5H6 M6 12.5 L7.2 13.7 M6 12.5 L7.2 11.3" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"/></svg>
}
4 changes: 2 additions & 2 deletions src/app/components/generator/JsonFileView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 <div class="file-view node-root" data-category={getCategory(resourceType)}>
{(ctx && mcdocType) && <McdocRoot type={mcdocType} node={node} ctx={ctx} />}
{(ctx && mcdocType) && <McdocRoot type={mcdocType.type} node={node} ctx={ctx} rootType={mcdocType.rootType} />}
</div>
}

Expand Down
130 changes: 128 additions & 2 deletions src/app/components/generator/McdocHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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) {
Expand All @@ -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
Expand Down Expand Up @@ -472,6 +478,126 @@ function inferType(node: JsonNode): Exclude<McdocType, UnionType> {
}
}

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
Expand Down
99 changes: 96 additions & 3 deletions src/app/components/generator/McdocRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -38,7 +39,10 @@ interface Props<Type extends SimplifiedMcdocType = SimplifiedMcdocType> {
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)) {
Expand All @@ -50,6 +54,7 @@ export function McdocRoot({ type, node, ctx } : Props) {
<Errors type={type} node={node} ctx={ctx} />
<Key label={locale('root')} />
<Head type={type} node={node} ctx={ctx} />
{node != undefined && <RecursiveContextMenu type={rootType} node={node} ctx={ctx}/>}
</div>
<Body type={type} node={node} ctx={ctx} />
</>
Expand Down Expand Up @@ -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<MakeEdit>((edit) => {
Expand Down Expand Up @@ -660,11 +665,99 @@ function StaticField({ pair, index, field, fieldKey, staticFields, isToggled, ex
)}
<Key label={fieldKey} doc={field.desc} />
{!isCollapsed && <Head type={childType} node={child} optional={field.optional} ctx={fieldCtx} />}
{child != undefined && <RecursiveContextMenu type={field.type} node={child} ctx={fieldCtx}/>}
</div>
{!isCollapsed && <Body type={childType} node={child} optional={field.optional} ctx={fieldCtx} />}
</div>
}
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<JsonStringNode, JsonNode> = {
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 <BtnMenu class='recursive-wrapper' tooltipLoc='se' menuDir='right' icon='wrap_inside'>
{wrapTargets.map(v => {
let label = formatIdentifier(v.identifier)
if(v.fieldCount > 1)
{
label += '/' + formatIdentifier(v.fieldKey)
}
return <Btn label={label} onClick={() => onclick(v)}/>
}
)}
</BtnMenu>
}
interface DynamicKeyProps {
keyType: SimplifiedMcdocType
valueType: McdocType
Expand Down
3 changes: 3 additions & 0 deletions src/styles/nodes.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down