diff --git a/.changeset/smooth-pots-nail.md b/.changeset/smooth-pots-nail.md new file mode 100644 index 0000000000..54a707713d --- /dev/null +++ b/.changeset/smooth-pots-nail.md @@ -0,0 +1,6 @@ +--- +"@latticexyz/dev-tools": patch +"@latticexyz/store-sync": patch +--- + +Improves support for internal/client-only RECS components diff --git a/packages/dev-tools/src/recs/ComponentData.tsx b/packages/dev-tools/src/recs/ComponentData.tsx index 8066d8c5bc..36d0678285 100644 --- a/packages/dev-tools/src/recs/ComponentData.tsx +++ b/packages/dev-tools/src/recs/ComponentData.tsx @@ -2,6 +2,7 @@ import { useParams } from "react-router-dom"; import { useDevToolsContext } from "../DevToolsContext"; import { ComponentDataTable } from "./ComponentDataTable"; import { isStoreComponent } from "@latticexyz/store-sync/recs"; +import { StoreComponentDataTable } from "./StoreComponentDataTable"; // TODO: use react-table or similar for better perf with lots of logs @@ -13,9 +14,13 @@ export function ComponentData() { const component = world.components.find((component) => component.id === idParam); // TODO: error message or redirect? - if (!component || !isStoreComponent(component)) return null; + if (!component) return null; // key here is useful to force a re-render on component changes, // otherwise state hangs around from previous render during navigation (entities) - return ; + return isStoreComponent(component) ? ( + + ) : ( + + ); } diff --git a/packages/dev-tools/src/recs/ComponentDataTable.tsx b/packages/dev-tools/src/recs/ComponentDataTable.tsx index 97662a8069..5323d9af3f 100644 --- a/packages/dev-tools/src/recs/ComponentDataTable.tsx +++ b/packages/dev-tools/src/recs/ComponentDataTable.tsx @@ -1,11 +1,12 @@ import { useEntityQuery } from "@latticexyz/react"; -import { Component, Has, Schema, getComponentValueStrict } from "@latticexyz/recs"; -import { StoreComponentMetadata, decodeEntity } from "@latticexyz/store-sync/recs"; +import { Component, Has, getComponentValueStrict, Type } from "@latticexyz/recs"; +import { decodeEntity } from "@latticexyz/store-sync/recs"; +import { serialize } from "../serialize"; // TODO: use react-table or similar for better perf with lots of logs type Props = { - component: Component; + component: Component; }; export function ComponentDataTable({ component }: Props) { @@ -16,12 +17,8 @@ export function ComponentDataTable({ component }: Props) { - {Object.keys(component.metadata.keySchema).map((name) => ( - - ))} - {Object.keys(component.metadata.valueSchema).map((name) => ( + + {Object.keys(component.schema).map((name) => ( @@ -30,20 +27,19 @@ export function ComponentDataTable({ component }: Props) { {entities.map((entity) => { - const key = decodeEntity(component.metadata.keySchema, entity); const value = getComponentValueStrict(component, entity); return ( - {Object.keys(component.metadata.keySchema).map((name) => ( - - ))} - {Object.keys(component.metadata.valueSchema).map((name) => { + + {Object.keys(component.schema).map((name) => { const fieldValue = value[name]; return ( ); })} diff --git a/packages/dev-tools/src/recs/ComponentsPage.tsx b/packages/dev-tools/src/recs/ComponentsPage.tsx index d4f53bb62e..2cd10b8e02 100644 --- a/packages/dev-tools/src/recs/ComponentsPage.tsx +++ b/packages/dev-tools/src/recs/ComponentsPage.tsx @@ -3,25 +3,26 @@ import { NavButton } from "../NavButton"; import { useEffect, useRef } from "react"; import { twMerge } from "tailwind-merge"; import { useDevToolsContext } from "../DevToolsContext"; -import { isStoreComponent } from "@latticexyz/store-sync/recs"; +import { getComponentName } from "./getComponentName"; export function ComponentsPage() { const { recsWorld: world } = useDevToolsContext(); if (!world) throw new Error("Missing recsWorld"); - const components = world.components.filter(isStoreComponent); + const components = [...world.components].sort((a, b) => getComponentName(a).localeCompare(getComponentName(b))); + // TODO: lift up selected component so we can remember previous selection between tab nav const { id: idParam } = useParams(); - const selectedComponent = components.find((component) => component.id === idParam); + const selectedComponent = components.find((component) => component.id === idParam) ?? components[0]; const detailsRef = useRef(null); const navigate = useNavigate(); useEffect(() => { - if (components.length && !selectedComponent) { - navigate(components[0].id); + if (idParam !== selectedComponent.id) { + navigate(selectedComponent.id); } - }, [components, selectedComponent]); + }, [idParam, selectedComponent.id]); useEffect(() => { const listener = (event: MouseEvent) => { @@ -45,7 +46,7 @@ export function ComponentsPage() { {selectedComponent ? ( - {selectedComponent.metadata.componentName} + {getComponentName(selectedComponent)} ) : ( Pick a component… )} @@ -68,7 +69,7 @@ export function ComponentsPage() { } }} > - {component.metadata.componentName} + {getComponentName(component)} ))} diff --git a/packages/dev-tools/src/recs/StoreComponentDataTable.tsx b/packages/dev-tools/src/recs/StoreComponentDataTable.tsx new file mode 100644 index 0000000000..6e012dc312 --- /dev/null +++ b/packages/dev-tools/src/recs/StoreComponentDataTable.tsx @@ -0,0 +1,56 @@ +import { useEntityQuery } from "@latticexyz/react"; +import { Component, Has, Schema, getComponentValueStrict } from "@latticexyz/recs"; +import { StoreComponentMetadata, decodeEntity } from "@latticexyz/store-sync/recs"; + +// TODO: use react-table or similar for better perf with lots of logs + +type Props = { + component: Component; +}; + +export function StoreComponentDataTable({ component }: Props) { + // TODO: this breaks when navigating because its state still has entity IDs from prev page + const entities = useEntityQuery([Has(component)]); + + return ( +
- {name} - entity {name}
- {String(key[name])} - {entity} - {Array.isArray(fieldValue) ? fieldValue.map(String).join(", ") : String(fieldValue)} + {component.schema[name] === Type.T + ? serialize(fieldValue) + : Array.isArray(fieldValue) + ? fieldValue.map(String).join(", ") + : String(fieldValue)}
+ + + {Object.keys(component.metadata.keySchema).map((name) => ( + + ))} + {Object.keys(component.metadata.valueSchema).map((name) => ( + + ))} + + + + {entities.map((entity) => { + const key = decodeEntity(component.metadata.keySchema, entity); + const value = getComponentValueStrict(component, entity); + return ( + + {Object.keys(component.metadata.keySchema).map((name) => ( + + ))} + {Object.keys(component.metadata.valueSchema).map((name) => { + const fieldValue = value[name]; + return ( + + ); + })} + + ); + })} + +
+ {name} + + {name} +
+ {String(key[name])} + + {Array.isArray(fieldValue) ? fieldValue.map(String).join(", ") : String(fieldValue)} +
+ ); +} diff --git a/packages/dev-tools/src/recs/getComponentName.ts b/packages/dev-tools/src/recs/getComponentName.ts new file mode 100644 index 0000000000..3b37186ed8 --- /dev/null +++ b/packages/dev-tools/src/recs/getComponentName.ts @@ -0,0 +1,5 @@ +import { Component } from "@latticexyz/recs"; + +export function getComponentName(component: Component): string { + return String(component.metadata?.componentName ?? component.id); +} diff --git a/packages/dev-tools/src/summary/ComponentsSummary.tsx b/packages/dev-tools/src/summary/ComponentsSummary.tsx index 102f441f66..2c3d60964d 100644 --- a/packages/dev-tools/src/summary/ComponentsSummary.tsx +++ b/packages/dev-tools/src/summary/ComponentsSummary.tsx @@ -1,25 +1,25 @@ import { World } from "@latticexyz/recs"; import { NavButton } from "../NavButton"; -import { isStoreComponent } from "@latticexyz/store-sync/recs"; +import { getComponentName } from "../recs/getComponentName"; type Props = { world: World; }; export function ComponentsSummary({ world }: Props) { - const componentsWithName = world.components.filter(isStoreComponent); + const components = [...world.components].sort((a, b) => getComponentName(a).localeCompare(getComponentName(b))); return ( <> - {componentsWithName.length ? ( + {components.length ? ( <>
- {componentsWithName.map((component) => ( + {components.map((component) => ( - {String(component.metadata.componentName)} + {getComponentName(component)} ))}
diff --git a/packages/store-sync/src/recs/defineInternalComponents.ts b/packages/store-sync/src/recs/defineInternalComponents.ts index e73627471a..5f4a461549 100644 --- a/packages/store-sync/src/recs/defineInternalComponents.ts +++ b/packages/store-sync/src/recs/defineInternalComponents.ts @@ -1,21 +1,13 @@ -import { World, defineComponent, Type, Component, Schema } from "@latticexyz/recs"; +import { World, defineComponent, Type, Component, Schema, Metadata } from "@latticexyz/recs"; import { Table } from "../common"; -import { StoreComponentMetadata } from "./common"; // eslint-disable-next-line @typescript-eslint/explicit-function-return-type export function defineInternalComponents(world: World) { return { - TableMetadata: defineComponent<{ table: Type.T }, StoreComponentMetadata, Table>( + TableMetadata: defineComponent<{ table: Type.T }, Metadata, Table>( world, { table: Type.T }, - { - metadata: { - componentName: "TableMetadata", - tableName: "recs:TableMetadata", - keySchema: {}, - valueSchema: {}, - }, - } + { metadata: { componentName: "TableMetadata" } } ), SyncProgress: defineComponent( world, @@ -26,14 +18,7 @@ export function defineInternalComponents(world: World) { latestBlockNumber: Type.BigInt, lastBlockNumberProcessed: Type.BigInt, }, - { - metadata: { - componentName: "SyncProgress", - tableName: "recs:SyncProgress", - keySchema: {}, - valueSchema: {}, - }, - } + { metadata: { componentName: "SyncProgress" } } ), - } as const satisfies Record>; + } as const satisfies Record>; }