-
Notifications
You must be signed in to change notification settings - Fork 196
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(dev-tools): show zustand tables (#1891)
- Loading branch information
Showing
23 changed files
with
317 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@latticexyz/store-sync": major | ||
--- | ||
|
||
`syncToZustand` now uses `tables` argument to populate the Zustand store's `tables` key, rather than the on-chain table registration events. This means we'll no longer store data into Zustand you haven't opted into receiving (e.g. other namespaces). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
--- | ||
"@latticexyz/dev-tools": minor | ||
"create-mud": minor | ||
--- | ||
|
||
Added Zustand support to Dev Tools: | ||
|
||
```ts | ||
const { syncToZustand } from "@latticexyz/store-sync"; | ||
const { mount as mountDevTools } from "@latticexyz/dev-tools"; | ||
|
||
const { useStore } = syncToZustand({ ... }); | ||
|
||
mountDevTools({ | ||
... | ||
useStore, | ||
}); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { Hex } from "viem"; | ||
|
||
type Props = { | ||
hex: Hex; | ||
}; | ||
|
||
export function TruncatedHex({ hex }: Props) { | ||
if (hex.length <= 10) { | ||
return <span>{hex}</span>; | ||
} | ||
|
||
return ( | ||
<span> | ||
<span className="after:content-['…'] after:select-none">{hex.slice(0, 6)}</span> | ||
<span className="tracking-[-1ch] text-transparent">{hex.slice(6, -4)}</span> | ||
{hex.slice(-4)} | ||
</span> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
import { Component } from "@latticexyz/recs"; | ||
|
||
export function getComponentName(component: Component): string { | ||
return String(component.metadata?.componentName ?? component.id); | ||
return String(component.metadata?.tableName ?? component.metadata?.componentName ?? component.id); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { NavButton } from "../NavButton"; | ||
import { useTables } from "../zustand/useTables"; | ||
|
||
export function TablesSummary() { | ||
const tables = useTables(); | ||
return ( | ||
<div className="flex flex-col gap-1 items-start"> | ||
{tables.map((table) => ( | ||
<NavButton key={table.tableId} to={`/tables/${table.tableId}`} className="font-mono text-xs hover:text-white"> | ||
{table.namespace}:{table.name} | ||
</NavButton> | ||
))} | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import React from "react"; | ||
import { SchemaAbiType, SchemaAbiTypeToPrimitiveType } from "@latticexyz/schema-type"; | ||
import { isHex } from "viem"; | ||
import { TruncatedHex } from "../TruncatedHex"; | ||
|
||
type Props = { | ||
value: SchemaAbiTypeToPrimitiveType<SchemaAbiType>; | ||
}; | ||
|
||
export function FieldValue({ value }: Props) { | ||
return Array.isArray(value) ? ( | ||
value.map((item, i) => ( | ||
<React.Fragment key={JSON.stringify({ i, value })}> | ||
{i > 0 ? ", " : null} | ||
<FieldValue value={item} /> | ||
</React.Fragment> | ||
)) | ||
) : isHex(value) ? ( | ||
<TruncatedHex hex={value} /> | ||
) : ( | ||
<>{String(value)}</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { useParams } from "react-router-dom"; | ||
import { TableDataTable } from "./TableDataTable"; | ||
import { useTables } from "./useTables"; | ||
|
||
// TODO: use react-table or similar for better perf with lots of logs | ||
|
||
export function TableData() { | ||
const tables = useTables(); | ||
|
||
const { id: idParam } = useParams(); | ||
const table = tables.find((t) => t.tableId === idParam); | ||
|
||
// TODO: error message or redirect? | ||
if (!table) 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 <TableDataTable key={table.tableId} table={table} />; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import { Table } from "@latticexyz/store"; | ||
import { useRecords } from "./useRecords"; | ||
import { isHex } from "viem"; | ||
import { TruncatedHex } from "../TruncatedHex"; | ||
import { FieldValue } from "./FieldValue"; | ||
|
||
// TODO: use react-table or similar for better perf with lots of logs | ||
|
||
type Props = { | ||
table: Table; | ||
}; | ||
|
||
export function TableDataTable({ table }: Props) { | ||
const records = useRecords(table); | ||
|
||
return ( | ||
<table className="w-full -mx-1"> | ||
<thead className="sticky top-0 z-10 bg-slate-800 text-left"> | ||
<tr className="text-amber-200/80 font-mono"> | ||
{Object.keys(table.keySchema).map((name) => ( | ||
<th key={name} className="px-1.5 pt-1.5 font-normal"> | ||
{name} | ||
</th> | ||
))} | ||
{Object.keys(table.valueSchema).map((name) => ( | ||
<th key={name} className="px-1.5 pt-1.5 font-normal"> | ||
{name} | ||
</th> | ||
))} | ||
</tr> | ||
</thead> | ||
<tbody className="font-mono text-xs"> | ||
{records.map((record) => { | ||
return ( | ||
<tr key={record.id}> | ||
{Object.keys(table.keySchema).map((name) => ( | ||
<td key={name} className="px-1.5 whitespace-nowrap overflow-hidden text-ellipsis"> | ||
<FieldValue value={record.key[name]} /> | ||
</td> | ||
))} | ||
{Object.keys(table.valueSchema).map((name) => ( | ||
<td key={name} className="px-1.5 whitespace-nowrap overflow-hidden text-ellipsis"> | ||
<FieldValue value={record.value[name]} /> | ||
</td> | ||
))} | ||
</tr> | ||
); | ||
})} | ||
</tbody> | ||
</table> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import { Outlet, useNavigate, useParams } from "react-router-dom"; | ||
import { NavButton } from "../NavButton"; | ||
import { useEffect, useRef } from "react"; | ||
import { twMerge } from "tailwind-merge"; | ||
import { useTables } from "./useTables"; | ||
|
||
export function TablesPage() { | ||
const tables = useTables(); | ||
|
||
// TODO: lift up selected component so we can remember previous selection between tab nav | ||
const { id: idParam } = useParams(); | ||
const selectedTable = tables.find((table) => table.tableId === idParam) ?? tables[0]; | ||
|
||
const detailsRef = useRef<HTMLDetailsElement>(null); | ||
const navigate = useNavigate(); | ||
|
||
useEffect(() => { | ||
if (idParam !== selectedTable.tableId) { | ||
navigate(selectedTable.tableId); | ||
} | ||
}, [idParam, selectedTable.tableId]); | ||
|
||
useEffect(() => { | ||
const listener = (event: MouseEvent) => { | ||
if (!detailsRef.current) return; | ||
if (event.target instanceof Node && detailsRef.current.contains(event.target)) return; | ||
detailsRef.current.open = false; | ||
}; | ||
window.addEventListener("click", listener); | ||
return () => window.removeEventListener("click", listener); | ||
}); | ||
|
||
return ( | ||
<div className="p-6 space-y-4"> | ||
<div className="space-y-2"> | ||
<h1 className="font-bold text-white/40 uppercase text-xs">Table</h1> | ||
|
||
<details ref={detailsRef} className="pointer-events-none select-none"> | ||
<summary className="group pointer-events-auto cursor-pointer inline-flex"> | ||
<span | ||
className={ | ||
"inline-flex gap-2 px-3 py-2 items-center border-2 border-white/10 rounded group-hover:border-blue-700 group-hover:bg-blue-700 group-hover:text-white" | ||
} | ||
> | ||
{selectedTable ? ( | ||
<span className="font-mono"> | ||
{selectedTable.namespace}:{selectedTable.name} | ||
</span> | ||
) : ( | ||
<span>Pick a table…</span> | ||
)} | ||
<span className="text-white/40 text-xs">▼</span> | ||
</span> | ||
</summary> | ||
<div className="relative"> | ||
<div className="pointer-events-auto absolute top-1 left-0 z-20 bg-slate-700 rounded shadow-lg flex flex-col py-1.5 font-mono text-xs leading-none"> | ||
{tables.map((table) => ( | ||
<NavButton | ||
className={twMerge( | ||
"px-2 py-1.5 text-left hover:bg-blue-700 hover:text-white", | ||
table === selectedTable ? "bg-slate-600" : null | ||
)} | ||
key={table.tableId} | ||
to={table.tableId} | ||
onClick={() => { | ||
if (detailsRef.current) { | ||
detailsRef.current.open = false; | ||
} | ||
}} | ||
> | ||
{table.namespace}:{table.name} | ||
</NavButton> | ||
))} | ||
</div> | ||
</div> | ||
</details> | ||
</div> | ||
<Outlet /> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { Table } from "@latticexyz/store"; | ||
import { useDevToolsContext } from "../DevToolsContext"; | ||
import { useEffect, useState } from "react"; | ||
import { TableRecord } from "@latticexyz/store-sync/zustand"; | ||
|
||
export function useRecords<table extends Table>(table: table): TableRecord<table>[] { | ||
const { useStore } = useDevToolsContext(); | ||
if (!useStore) throw new Error("Missing useStore"); | ||
|
||
// React doesn't like using hooks from another copy of React libs, so we have to use the non-React API to get data out of Zustand | ||
const [records, setRecords] = useState<{ readonly [k: string]: TableRecord<table> }>( | ||
useStore.getState().getRecords(table) | ||
); | ||
useEffect(() => { | ||
return useStore.subscribe((state) => { | ||
const nextRecords = useStore.getState().getRecords(table); | ||
if (nextRecords !== records) { | ||
setRecords(nextRecords); | ||
} | ||
}); | ||
}, [useStore, records]); | ||
|
||
return Object.values(records); | ||
} |
Oops, something went wrong.