From 241f1213962c8bad29fbfe63ba5d89c9569ead31 Mon Sep 17 00:00:00 2001 From: karooolis Date: Mon, 23 Sep 2024 22:37:34 +0300 Subject: [PATCH 1/7] use nuqs for search params handling --- packages/explorer/package.json | 1 + .../[worldAddress]/explore/DataExplorer.tsx | 8 +--- .../[worldAddress]/explore/TableSelector.tsx | 42 +++++++++++------- .../[worldAddress]/explore/TablesViewer.tsx | 44 +++++++------------ pnpm-lock.yaml | 18 ++++++++ 5 files changed, 63 insertions(+), 50 deletions(-) diff --git a/packages/explorer/package.json b/packages/explorer/package.json index 186764a1c6..9582c238d3 100644 --- a/packages/explorer/package.json +++ b/packages/explorer/package.json @@ -61,6 +61,7 @@ "debug": "^4.3.4", "lucide-react": "^0.408.0", "next": "14.2.5", + "nuqs": "^1.19.2", "query-string": "^9.1.0", "react": "^18", "react-dom": "^18", diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/DataExplorer.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/DataExplorer.tsx index 012ef410b5..f2082085c8 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/DataExplorer.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/DataExplorer.tsx @@ -1,13 +1,11 @@ "use client"; import { Loader } from "lucide-react"; -import { useSearchParams } from "next/navigation"; import { useQuery } from "@tanstack/react-query"; import { TableSelector } from "./TableSelector"; import { TablesViewer } from "./TablesViewer"; export function DataExplorer() { - const searchParams = useSearchParams(); const { data: tables, isLoading } = useQuery({ queryKey: ["tables"], queryFn: async () => { @@ -16,7 +14,6 @@ export function DataExplorer() { if (!response.ok) { throw new Error(json.error); } - return json; }, select: (data) => data.tables.map((table: { name: string }) => table.name), @@ -24,7 +21,6 @@ export function DataExplorer() { throwOnError: true, retry: false, }); - const selectedTable = searchParams.get("tableId") || (tables?.length > 0 ? tables[0] : null); if (isLoading) { return ; @@ -32,8 +28,8 @@ export function DataExplorer() { return ( <> - - + + ); } diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TableSelector.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TableSelector.tsx index c5390670c4..c3f0582dd4 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TableSelector.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TableSelector.tsx @@ -1,6 +1,8 @@ import { Check, ChevronsUpDown, Lock } from "lucide-react"; import { useParams } from "next/navigation"; -import { useState } from "react"; +import { useQueryState } from "nuqs"; +import { Hex } from "viem"; +import { useEffect, useState } from "react"; import { internalTableNames } from "@latticexyz/store-sync/sqlite"; import { Button } from "../../../../../../components/ui/Button"; import { @@ -15,20 +17,28 @@ import { Popover, PopoverContent, PopoverTrigger } from "../../../../../../compo import { cn } from "../../../../../../lib/utils"; type Props = { - value: string | undefined; - options: string[]; + tables: string[]; }; -export function TableSelector({ value, options }: Props) { +export function TableSelector({ tables }: Props) { + const [selectedTableId, setTableId] = useQueryState("tableId"); const [open, setOpen] = useState(false); const { worldAddress } = useParams(); + useEffect(() => { + if (!selectedTableId && tables.length > 0) { + setTableId(tables[0] as Hex); + } + }, [selectedTableId, setTableId, tables]); + return (
@@ -39,26 +49,24 @@ export function TableSelector({ value, options }: Props) { No framework found. - {options.map((option) => { + {tables.map((tableId) => { return ( { - const url = new URL(window.location.href); - const searchParams = new URLSearchParams(url.search); - searchParams.set("tableId", currentValue); - window.history.pushState({}, "", `${window.location.pathname}?${searchParams}`); - + key={tableId} + value={tableId} + onSelect={(newTableId) => { + setTableId(newTableId as Hex); setOpen(false); }} className="font-mono" > - - {(internalTableNames as string[]).includes(option) && ( + + {(internalTableNames as string[]).includes(tableId) && ( )} - {option.replace(`${worldAddress}__`, "")} + {tableId.replace(`${worldAddress}__`, "")} ); })} diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TablesViewer.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TablesViewer.tsx index 3d992be635..ada5fb9b4c 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TablesViewer.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TablesViewer.tsx @@ -1,12 +1,11 @@ import { ArrowUpDown, Loader } from "lucide-react"; +import { parseAsBoolean, useQueryState } from "nuqs"; import { useState } from "react"; import { internalTableNames } from "@latticexyz/store-sync/sqlite"; import { useQuery } from "@tanstack/react-query"; import { ColumnDef, - ColumnFiltersState, SortingState, - VisibilityState, flexRender, getCoreRowModel, getFilteredRowModel, @@ -21,22 +20,16 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from ". import { bufferToBigInt } from "../utils/bufferToBigInt"; import { EditableTableCell } from "./EditableTableCell"; -type Props = { - table: string | undefined; -}; - -export function TablesViewer({ table: selectedTable }: Props) { +export function TablesViewer() { + const [selectedTableId] = useQueryState("tableId"); + const [globalFilter, setGlobalFilter] = useQueryState("globalFilter"); + const [showAllColumns, setShowAllColumns] = useQueryState("showAllColumns", parseAsBoolean); const [sorting, setSorting] = useState([]); - const [columnFilters, setColumnFilters] = useState([]); - const [columnVisibility, setColumnVisibility] = useState({}); - const [rowSelection, setRowSelection] = useState({}); - const [globalFilter, setGlobalFilter] = useState(""); - const [showAllColumns, setShowAllColumns] = useState(false); const { data: schema } = useQuery({ - queryKey: ["schema", { table: selectedTable }], + queryKey: ["schema", { table: selectedTableId }], queryFn: async () => { - const response = await fetch(`/api/schema?table=${selectedTable}`); + const response = await fetch(`/api/schema?table=${selectedTableId}`); return response.json(); }, select: (data) => { @@ -50,9 +43,9 @@ export function TablesViewer({ table: selectedTable }: Props) { }); const { data: rows } = useQuery({ - queryKey: ["rows", { table: selectedTable }], + queryKey: ["rows", { table: selectedTableId }], queryFn: async () => { - const response = await fetch(`/api/rows?table=${selectedTable}`); + const response = await fetch(`/api/rows?table=${selectedTableId}`); return response.json(); }, select: (data) => { @@ -67,14 +60,14 @@ export function TablesViewer({ table: selectedTable }: Props) { ); }); }, - enabled: Boolean(selectedTable), + enabled: Boolean(selectedTableId), refetchInterval: 1000, }); const { data: mudTableConfig } = useQuery({ - queryKey: ["table", { selectedTable }], + queryKey: ["table", { selectedTableId }], queryFn: async () => { - const response = await fetch(`/api/table?table=${selectedTable}`); + const response = await fetch(`/api/table?table=${selectedTableId}`); return response.json(); }, select: (data) => { @@ -84,7 +77,7 @@ export function TablesViewer({ table: selectedTable }: Props) { value_schema: JSON.parse(data.table.value_schema).json, }; }, - enabled: Boolean(selectedTable), + enabled: Boolean(selectedTableId), }); const columns: ColumnDef<{}>[] = schema?.map(({ name, type }: { name: string; type: string }) => { @@ -122,7 +115,10 @@ export function TablesViewer({ table: selectedTable }: Props) { const keysSchema = Object.keys(mudTableConfig?.key_schema || {}); const keyTuple = keysSchema.map((key) => row.getValue(key)); const value = row.getValue(name); - if ((selectedTable && (internalTableNames as string[]).includes(selectedTable)) || keysSchema.includes(name)) { + if ( + (selectedTableId && (internalTableNames as string[]).includes(selectedTableId)) || + keysSchema.includes(name) + ) { return value?.toString(); } @@ -140,20 +136,14 @@ export function TablesViewer({ table: selectedTable }: Props) { }, }, onSortingChange: setSorting, - onColumnFiltersChange: setColumnFilters, getCoreRowModel: getCoreRowModel(), getPaginationRowModel: getPaginationRowModel(), getSortedRowModel: getSortedRowModel(), getFilteredRowModel: getFilteredRowModel(), - onColumnVisibilityChange: setColumnVisibility, - onRowSelectionChange: setRowSelection, onGlobalFilterChange: setGlobalFilter, globalFilterFn: "includesString", state: { sorting, - columnFilters, - columnVisibility, - rowSelection, globalFilter, }, }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4ad28c4a1d..83790d4fb0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -537,6 +537,9 @@ importers: next: specifier: 14.2.5 version: 14.2.5(@babel/core@7.25.2)(@opentelemetry/api@1.8.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + nuqs: + specifier: ^1.19.2 + version: 1.19.2(next@14.2.5(@babel/core@7.25.2)(@opentelemetry/api@1.8.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)) query-string: specifier: ^9.1.0 version: 9.1.0 @@ -8596,6 +8599,9 @@ packages: typescript: optional: true + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} @@ -8798,6 +8804,11 @@ packages: nullthrows@1.1.1: resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==} + nuqs@1.19.2: + resolution: {integrity: sha512-r6Hw44yMg8tuxr+zS4AmeB+Z++j1pT99DuCfy+XmouCBaXHHhBPOoWZQQuSUI0ja2s00v6o96HdSnvq2/5qOyw==} + peerDependencies: + next: '>=13.4 <14.0.2 || ^14.0.3' + nwsapi@2.2.7: resolution: {integrity: sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==} @@ -20875,6 +20886,8 @@ snapshots: optionalDependencies: typescript: 5.4.2 + mitt@3.0.1: {} + mkdirp-classic@0.5.3: {} mkdirp@0.5.6: @@ -21061,6 +21074,11 @@ snapshots: nullthrows@1.1.1: {} + nuqs@1.19.2(next@14.2.5(@babel/core@7.25.2)(@opentelemetry/api@1.8.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)): + dependencies: + mitt: 3.0.1 + next: 14.2.5(@babel/core@7.25.2)(@opentelemetry/api@1.8.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + nwsapi@2.2.7: {} ob1@0.80.12: From ec786536f145f933384896676b0a5db20e31552a Mon Sep 17 00:00:00 2001 From: karooolis Date: Tue, 24 Sep 2024 09:16:26 +0300 Subject: [PATCH 2/7] add sorting to query state --- .../worlds/[worldAddress]/explore/TablesViewer.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TablesViewer.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TablesViewer.tsx index ada5fb9b4c..9c14f765a0 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TablesViewer.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TablesViewer.tsx @@ -1,6 +1,5 @@ import { ArrowUpDown, Loader } from "lucide-react"; -import { parseAsBoolean, useQueryState } from "nuqs"; -import { useState } from "react"; +import { parseAsBoolean, parseAsJson, useQueryState } from "nuqs"; import { internalTableNames } from "@latticexyz/store-sync/sqlite"; import { useQuery } from "@tanstack/react-query"; import { @@ -23,8 +22,8 @@ import { EditableTableCell } from "./EditableTableCell"; export function TablesViewer() { const [selectedTableId] = useQueryState("tableId"); const [globalFilter, setGlobalFilter] = useQueryState("globalFilter"); - const [showAllColumns, setShowAllColumns] = useQueryState("showAllColumns", parseAsBoolean); - const [sorting, setSorting] = useState([]); + const [showAllColumns, setShowAllColumns] = useQueryState("showAllColumns", parseAsBoolean.withDefault(false)); + const [sorting, setSorting] = useQueryState("sorting", parseAsJson().withDefault([])); const { data: schema } = useQuery({ queryKey: ["schema", { table: selectedTableId }], From 2e98f8b7f6a19af6927f30d1b4616ab422420de7 Mon Sep 17 00:00:00 2001 From: karooolis Date: Tue, 24 Sep 2024 10:21:23 +0300 Subject: [PATCH 3/7] add sorting to query state --- .../worlds/[worldAddress]/explore/TablesViewer.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TablesViewer.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TablesViewer.tsx index 9c14f765a0..0598786012 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TablesViewer.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TablesViewer.tsx @@ -1,10 +1,11 @@ import { ArrowUpDown, Loader } from "lucide-react"; -import { parseAsBoolean, parseAsJson, useQueryState } from "nuqs"; +import { parseAsArrayOf, parseAsBoolean, parseAsJson, parseAsString, useQueryState } from "nuqs"; +import { useRef } from "react"; import { internalTableNames } from "@latticexyz/store-sync/sqlite"; import { useQuery } from "@tanstack/react-query"; import { ColumnDef, - SortingState, + ColumnSort, flexRender, getCoreRowModel, getFilteredRowModel, @@ -21,9 +22,12 @@ import { EditableTableCell } from "./EditableTableCell"; export function TablesViewer() { const [selectedTableId] = useQueryState("tableId"); - const [globalFilter, setGlobalFilter] = useQueryState("globalFilter"); + const [globalFilter, setGlobalFilter] = useQueryState("filter", parseAsString.withDefault("")); const [showAllColumns, setShowAllColumns] = useQueryState("showAllColumns", parseAsBoolean.withDefault(false)); - const [sorting, setSorting] = useQueryState("sorting", parseAsJson().withDefault([])); + const [sorting, setSorting] = useQueryState( + "sort", + parseAsArrayOf(parseAsJson()).withDefault(useRef([]).current), + ); const { data: schema } = useQuery({ queryKey: ["schema", { table: selectedTableId }], @@ -160,7 +164,7 @@ export function TablesViewer() {
table.setGlobalFilter(event.target.value)} className="max-w-sm rounded border px-2 py-1" /> From 2da16f80dfc3cba0e887c19cff09d19dc97a81c2 Mon Sep 17 00:00:00 2001 From: Karolis Ramanauskas Date: Tue, 24 Sep 2024 10:28:52 +0300 Subject: [PATCH 4/7] Create sixty-comics-love.md --- .changeset/sixty-comics-love.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/sixty-comics-love.md diff --git a/.changeset/sixty-comics-love.md b/.changeset/sixty-comics-love.md new file mode 100644 index 0000000000..ac10d295f9 --- /dev/null +++ b/.changeset/sixty-comics-love.md @@ -0,0 +1,5 @@ +--- +"@latticexyz/explorer": patch +--- + +Added support for tracking table filtering, sorting, and additional parameters like selected tableId using type-safe URL search params. This ensures that state changes persist across sessions and can be shared with a URL. From 524f6a8a8a1cd5c83d61b1f53d17073be0757675 Mon Sep 17 00:00:00 2001 From: karooolis Date: Tue, 24 Sep 2024 11:30:05 +0300 Subject: [PATCH 5/7] update typing --- .../worlds/[worldAddress]/explore/TablesViewer.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TablesViewer.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TablesViewer.tsx index 0598786012..bca6a96bab 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TablesViewer.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TablesViewer.tsx @@ -1,11 +1,10 @@ import { ArrowUpDown, Loader } from "lucide-react"; -import { parseAsArrayOf, parseAsBoolean, parseAsJson, parseAsString, useQueryState } from "nuqs"; -import { useRef } from "react"; +import { parseAsBoolean, parseAsJson, parseAsString, useQueryState } from "nuqs"; import { internalTableNames } from "@latticexyz/store-sync/sqlite"; import { useQuery } from "@tanstack/react-query"; import { ColumnDef, - ColumnSort, + SortingState, flexRender, getCoreRowModel, getFilteredRowModel, @@ -20,14 +19,13 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from ". import { bufferToBigInt } from "../utils/bufferToBigInt"; import { EditableTableCell } from "./EditableTableCell"; +const emptyColumnSort: SortingState = []; + export function TablesViewer() { const [selectedTableId] = useQueryState("tableId"); const [globalFilter, setGlobalFilter] = useQueryState("filter", parseAsString.withDefault("")); const [showAllColumns, setShowAllColumns] = useQueryState("showAllColumns", parseAsBoolean.withDefault(false)); - const [sorting, setSorting] = useQueryState( - "sort", - parseAsArrayOf(parseAsJson()).withDefault(useRef([]).current), - ); + const [sorting, setSorting] = useQueryState("sort", parseAsJson().withDefault(emptyColumnSort)); const { data: schema } = useQuery({ queryKey: ["schema", { table: selectedTableId }], From 070937c8094fa1d3a8b56c056af9981fbe12e45d Mon Sep 17 00:00:00 2001 From: karooolis Date: Tue, 24 Sep 2024 11:51:40 +0300 Subject: [PATCH 6/7] rename initial sort data, use search params --- .../worlds/[worldAddress]/explore/TablesViewer.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TablesViewer.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TablesViewer.tsx index bca6a96bab..bba5a1d79f 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TablesViewer.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TablesViewer.tsx @@ -19,18 +19,18 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from ". import { bufferToBigInt } from "../utils/bufferToBigInt"; import { EditableTableCell } from "./EditableTableCell"; -const emptyColumnSort: SortingState = []; +const initialSortingState: SortingState = []; export function TablesViewer() { - const [selectedTableId] = useQueryState("tableId"); + const [selectedTableId] = useQueryState("tableId", parseAsString.withDefault("")); const [globalFilter, setGlobalFilter] = useQueryState("filter", parseAsString.withDefault("")); const [showAllColumns, setShowAllColumns] = useQueryState("showAllColumns", parseAsBoolean.withDefault(false)); - const [sorting, setSorting] = useQueryState("sort", parseAsJson().withDefault(emptyColumnSort)); + const [sorting, setSorting] = useQueryState("sort", parseAsJson().withDefault(initialSortingState)); const { data: schema } = useQuery({ queryKey: ["schema", { table: selectedTableId }], queryFn: async () => { - const response = await fetch(`/api/schema?table=${selectedTableId}`); + const response = await fetch(`/api/schema?${new URLSearchParams({ table: selectedTableId })}`); return response.json(); }, select: (data) => { @@ -41,12 +41,13 @@ export function TablesViewer() { return !column.name.startsWith("__"); }); }, + enabled: Boolean(selectedTableId), }); const { data: rows } = useQuery({ queryKey: ["rows", { table: selectedTableId }], queryFn: async () => { - const response = await fetch(`/api/rows?table=${selectedTableId}`); + const response = await fetch(`/api/rows?${new URLSearchParams({ table: selectedTableId })}`); return response.json(); }, select: (data) => { @@ -68,7 +69,7 @@ export function TablesViewer() { const { data: mudTableConfig } = useQuery({ queryKey: ["table", { selectedTableId }], queryFn: async () => { - const response = await fetch(`/api/table?table=${selectedTableId}`); + const response = await fetch(`/api/table?${new URLSearchParams({ table: selectedTableId })}`); return response.json(); }, select: (data) => { From f56622507d2471e6851fa59a65149644910fdb8f Mon Sep 17 00:00:00 2001 From: Karolis Ramanauskas Date: Tue, 24 Sep 2024 11:52:16 +0300 Subject: [PATCH 7/7] Update .changeset/sixty-comics-love.md Co-authored-by: Kevin Ingersoll --- .changeset/sixty-comics-love.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/sixty-comics-love.md b/.changeset/sixty-comics-love.md index ac10d295f9..ab04a02f26 100644 --- a/.changeset/sixty-comics-love.md +++ b/.changeset/sixty-comics-love.md @@ -2,4 +2,4 @@ "@latticexyz/explorer": patch --- -Added support for tracking table filtering, sorting, and additional parameters like selected tableId using type-safe URL search params. This ensures that state changes persist across sessions and can be shared with a URL. +Table filters are now included as part of the URL. This enables deep links and improves navigating between pages without losing search state.