Skip to content

Commit 581228b

Browse files
authored
feat(explorer): tables viewer pagination (#3426)
1 parent 31eb0c6 commit 581228b

13 files changed

Lines changed: 278 additions & 96 deletions

File tree

.changeset/metal-years-raise.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@latticexyz/explorer": patch
3+
---
4+
5+
The Explore tab's table viewer now supports pagination through limit/offset clauses in SQL queries.

examples/local-explorer/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,6 @@ node_modules
55
# sqlite indexer data
66
*.db
77
*.db-journal
8+
9+
# worlds configuration
10+
worlds.json

examples/local-explorer/packages/contracts/worlds.json

Lines changed: 0 additions & 5 deletions
This file was deleted.
Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"use client";
22

33
import { useParams } from "next/navigation";
4-
import { parseAsString, useQueryState } from "nuqs";
4+
import { useQueryState } from "nuqs";
55
import { Hex } from "viem";
66
import { useEffect, useState } from "react";
77
import { useChain } from "../../../../hooks/useChain";
@@ -12,39 +12,55 @@ import { indexerForChainId } from "../../../../utils/indexerForChainId";
1212
import { SQLEditor } from "./SQLEditor";
1313
import { TableSelector } from "./TableSelector";
1414
import { TablesViewer } from "./TablesViewer";
15+
import { usePaginationState } from "./hooks/usePaginationState";
16+
import { useSQLQueryState } from "./hooks/useSQLQueryState";
1517

1618
export function Explorer() {
1719
const { worldAddress } = useParams();
1820
const { id: chainId } = useChain();
1921
const indexer = indexerForChainId(chainId);
2022
const [isLiveQuery, setIsLiveQuery] = useState(false);
21-
const [query, setQuery] = useQueryState("query", parseAsString.withDefault(""));
23+
const [{ pageSize }, setPagination] = usePaginationState();
24+
const [query, setQuery] = useSQLQueryState();
2225
const [selectedTableId] = useQueryState("tableId");
2326
const prevSelectedTableId = usePrevious(selectedTableId);
24-
2527
const { data: tables } = useTablesQuery();
2628
const table = tables?.find(({ tableId }) => tableId === selectedTableId);
2729

2830
useEffect(() => {
2931
if (table && (!query || prevSelectedTableId !== selectedTableId)) {
3032
const tableName = constructTableName(table, worldAddress as Hex, chainId);
31-
3233
if (indexer.type === "sqlite") {
3334
setQuery(`SELECT * FROM "${tableName}";`);
3435
} else {
3536
const columns = Object.keys(table.schema).map((column) => `"${column}"`);
36-
setQuery(`SELECT ${columns.join(", ")} FROM "${tableName}";`);
37+
setQuery(`SELECT ${columns.join(", ")} FROM "${tableName}" LIMIT ${pageSize} OFFSET 0;`);
38+
setPagination({
39+
pageIndex: 0,
40+
pageSize,
41+
});
3742
}
3843
}
39-
}, [chainId, setQuery, selectedTableId, table, worldAddress, prevSelectedTableId, query, indexer.type]);
44+
}, [
45+
chainId,
46+
setQuery,
47+
selectedTableId,
48+
table,
49+
worldAddress,
50+
prevSelectedTableId,
51+
query,
52+
indexer.type,
53+
pageSize,
54+
setPagination,
55+
]);
4056

4157
return (
4258
<div className="space-y-4">
4359
<TableSelector tables={tables} />
4460
{indexer.type !== "sqlite" && (
4561
<SQLEditor table={table} isLiveQuery={isLiveQuery} setIsLiveQuery={setIsLiveQuery} />
4662
)}
47-
<TablesViewer table={table} query={query} isLiveQuery={isLiveQuery} />
63+
<TablesViewer table={table} isLiveQuery={isLiveQuery} />
4864
</div>
4965
);
5066
}

packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/SQLEditor.tsx

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import { CommandIcon, CornerDownLeft, LoaderIcon, PauseIcon, PlayIcon } from "lucide-react";
44
import { KeyCode, KeyMod, editor } from "monaco-editor/esm/vs/editor/editor.api";
5-
import { useQueryState } from "nuqs";
65
import { useEffect, useRef, useState } from "react";
76
import { useForm } from "react-hook-form";
87
import { Table } from "@latticexyz/config";
@@ -12,9 +11,12 @@ import { Button } from "../../../../../../components/ui/Button";
1211
import { Form, FormField } from "../../../../../../components/ui/Form";
1312
import { cn } from "../../../../../../utils";
1413
import { useTableDataQuery } from "../../../../queries/useTableDataQuery";
15-
import { monacoOptions } from "./consts";
14+
import { PAGE_SIZE_OPTIONS, monacoOptions } from "./consts";
15+
import { usePaginationState } from "./hooks/usePaginationState";
16+
import { useSQLQueryState } from "./hooks/useSQLQueryState";
1617
import { useMonacoSuggestions } from "./useMonacoSuggestions";
1718
import { useQueryValidator } from "./useQueryValidator";
19+
import { getLimitOffset } from "./utils/getLimitOffset";
1820

1921
type Props = {
2022
table?: Table;
@@ -27,7 +29,9 @@ export function SQLEditor({ table, isLiveQuery, setIsLiveQuery }: Props) {
2729
const containerRef = useRef<HTMLDivElement>(null);
2830
const [isFocused, setIsFocused] = useState(false);
2931
const [isUserTriggeredRefetch, setIsUserTriggeredRefetch] = useState(false);
30-
const [query, setQuery] = useQueryState("query", { defaultValue: "" });
32+
const [pagination, setPagination] = usePaginationState();
33+
const [query, setQuery] = useSQLQueryState();
34+
3135
const validateQuery = useQueryValidator(table);
3236
const {
3337
data: tableData,
@@ -48,9 +52,28 @@ export function SQLEditor({ table, isLiveQuery, setIsLiveQuery }: Props) {
4852
});
4953
const currentQuery = form.watch("query");
5054

51-
const handleSubmit = form.handleSubmit((data) => {
52-
if (validateQuery(data.query)) {
53-
setQuery(data.query);
55+
const handleSubmit = form.handleSubmit(({ query }) => {
56+
if (validateQuery(query)) {
57+
setQuery(query);
58+
59+
// Set the page based on the query
60+
const { limit, offset } = getLimitOffset(query);
61+
if (limit == null || offset == null) {
62+
setPagination({
63+
...pagination,
64+
pageIndex: 0,
65+
});
66+
} else if (PAGE_SIZE_OPTIONS.includes(limit) && (offset === 0 || offset % limit === 0)) {
67+
setPagination({
68+
pageSize: limit,
69+
pageIndex: offset / limit,
70+
});
71+
} else {
72+
setPagination({
73+
...pagination,
74+
pageIndex: 0,
75+
});
76+
}
5477

5578
setIsUserTriggeredRefetch(true);
5679
refetch().finally(() => setIsUserTriggeredRefetch(false));
@@ -100,10 +123,7 @@ export function SQLEditor({ table, isLiveQuery, setIsLiveQuery }: Props) {
100123
id: "executeSQL",
101124
label: "Execute SQL command",
102125
keybindings: [KeyMod.CtrlCmd | KeyCode.Enter],
103-
run: () => {
104-
console.log("EXECUTING SQL COMMAND");
105-
handleSubmit();
106-
},
126+
run: () => handleSubmit(),
107127
});
108128

109129
updateHeight();

0 commit comments

Comments
 (0)