Skip to content

Commit a5fdfa8

Browse files
karooolisfrolic
andauthored
fix(explorer): display remotely updated table values (#3737)
Co-authored-by: Kevin Ingersoll <kingersoll@gmail.com>
1 parent 0fc224f commit a5fdfa8

15 files changed

Lines changed: 367 additions & 200 deletions

File tree

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+
Table values updated remotely are now also reflected in the table viewer.

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

Lines changed: 0 additions & 138 deletions
This file was deleted.

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

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,7 @@ export function SQLEditor({ table, isLiveQuery, setIsLiveQuery }: Props) {
3333
const [query, setQuery] = useSQLQueryState();
3434

3535
const validateQuery = useQueryValidator(table);
36-
const {
37-
data: tableData,
38-
refetch,
39-
isRefetching: isTableDataRefetching,
40-
} = useTableDataQuery({
41-
table,
42-
query,
43-
isLiveQuery,
44-
});
36+
const { data: tableData, refetch, isRefetching: isTableDataRefetching } = useTableDataQuery({ table, isLiveQuery });
4537
const isRefetching = isTableDataRefetching && isUserTriggeredRefetch;
4638
useMonacoSuggestions(table);
4739

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { Hex } from "viem";
2+
import { useEffect } from "react";
3+
import { Table } from "@latticexyz/config";
4+
import { Checkbox } from "../../../../../../../components/ui/Checkbox";
5+
import { cn } from "../../../../../../../utils";
6+
import { useSetField } from "./useSetField";
7+
8+
type EditableProps = {
9+
name: string;
10+
value: boolean;
11+
table: Table;
12+
keyTuple: readonly Hex[];
13+
blockHeight: number;
14+
disabled?: boolean;
15+
readOnly?: false;
16+
};
17+
18+
type ReadOnlyProps = {
19+
value: boolean;
20+
readOnly: true;
21+
};
22+
23+
type Props = EditableProps | ReadOnlyProps;
24+
25+
function ReadonlyBooleanField(props: ReadOnlyProps) {
26+
return <Checkbox className="ml-2" checked={props.value} disabled />;
27+
}
28+
29+
function EditableBooleanField({ name, value, table, keyTuple, blockHeight, disabled }: EditableProps) {
30+
const write = useSetField<Table, never, boolean>({ table, keyTuple, fieldName: name as never });
31+
32+
useEffect(() => {
33+
if (write.status === "success" && BigInt(blockHeight) >= write.data.receipt.blockNumber) {
34+
write.reset();
35+
}
36+
}, [write, blockHeight]);
37+
38+
return (
39+
<Checkbox
40+
className={cn("ml-2", write.isPending ? "cursor-wait" : "")}
41+
checked={write.status === "success" || write.status === "pending" ? write.variables.value : value}
42+
onCheckedChange={(checked) => {
43+
if (checked === "indeterminate") return;
44+
write.mutate({ value: checked });
45+
}}
46+
disabled={disabled || write.isPending}
47+
/>
48+
);
49+
}
50+
51+
export function BooleanField(props: Props) {
52+
if (props.readOnly) {
53+
return <ReadonlyBooleanField {...props} />;
54+
}
55+
return <EditableBooleanField {...props} />;
56+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { Hex } from "viem";
2+
import { useAccount } from "wagmi";
3+
import { AbiType, Table } from "@latticexyz/config";
4+
import { TDataRow } from "../../../../../queries/useTableDataQuery";
5+
import { BooleanField } from "./BooleanField";
6+
import { TextField } from "./TextField";
7+
8+
type Props = {
9+
name: string;
10+
value: TDataRow[string];
11+
tableConfig: Table;
12+
keyTuple: readonly Hex[];
13+
blockHeight?: number;
14+
fieldType: AbiType;
15+
};
16+
17+
export function EditableTableColumn({ name, value, fieldType, tableConfig, keyTuple, blockHeight = 0 }: Props) {
18+
const { isConnected } = useAccount();
19+
20+
if (fieldType === "bool") {
21+
return (
22+
<BooleanField
23+
name={name}
24+
value={Boolean(value)}
25+
table={tableConfig}
26+
keyTuple={keyTuple}
27+
blockHeight={blockHeight}
28+
readOnly={!isConnected}
29+
/>
30+
);
31+
}
32+
33+
return (
34+
<TextField
35+
name={name}
36+
value={String(value)}
37+
table={tableConfig}
38+
keyTuple={keyTuple}
39+
blockHeight={blockHeight}
40+
readOnly={!isConnected}
41+
/>
42+
);
43+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { AbiType } from "@latticexyz/config";
2+
import { BooleanField } from "./BooleanField";
3+
import { TextField } from "./TextField";
4+
5+
type Props = {
6+
type?: AbiType;
7+
value: unknown;
8+
};
9+
10+
export function TableColumn({ type = "string", value }: Props) {
11+
if (type === "bool") {
12+
return <BooleanField value={value as boolean} readOnly />;
13+
}
14+
return <TextField value={value as string} readOnly />;
15+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { Hex } from "viem";
2+
import { useEffect, useState } from "react";
3+
import { Table } from "@latticexyz/config";
4+
import { cn } from "../../../../../../../utils";
5+
import { useSetField } from "./useSetField";
6+
7+
type EditableProps = {
8+
name: string;
9+
value: string;
10+
table: Table;
11+
keyTuple: readonly Hex[];
12+
blockHeight: number;
13+
readOnly?: false;
14+
disabled?: boolean;
15+
};
16+
17+
type ReadOnlyProps = {
18+
value: string;
19+
readOnly: true;
20+
};
21+
22+
type Props = EditableProps | ReadOnlyProps;
23+
24+
function ReadonlyTextField(props: ReadOnlyProps) {
25+
return <div className="px-2 py-4">{props.value}</div>;
26+
}
27+
28+
function EditableTextField({ name, value, table, keyTuple, blockHeight, disabled }: EditableProps) {
29+
const write = useSetField<Table, never, string>({ table, keyTuple, fieldName: name as never });
30+
useEffect(() => {
31+
if (write.status === "success" && BigInt(blockHeight) >= write.data.receipt.blockNumber) {
32+
write.reset();
33+
}
34+
}, [write, blockHeight]);
35+
36+
const [edit, setEdit] = useState<{ value: string; initialValue: string } | null>(null);
37+
return (
38+
<form
39+
onSubmit={(event) => {
40+
event.preventDefault();
41+
42+
if (!edit) return;
43+
44+
// Skip if our input hasn't changed from the indexer value
45+
if (edit.value === value) {
46+
setEdit(null);
47+
return;
48+
}
49+
50+
// Indexer value changed while we were editing, so we might
51+
// be at risk of overwriting a change from somewhere else.
52+
if (edit.initialValue !== value) {
53+
if (
54+
!window.confirm("The value of this field changed while editing. Are you sure you want to overwrite it?")
55+
) {
56+
setEdit(null);
57+
return;
58+
}
59+
}
60+
61+
write.mutate({ value: edit.value });
62+
setEdit(null);
63+
}}
64+
>
65+
<input
66+
className={cn("bg-transparent px-2 py-4", write.isPending && "cursor-wait")}
67+
name={name}
68+
value={edit ? edit.value : write.status !== "idle" ? String(write.variables.value) : value}
69+
onFocus={(event) => setEdit({ value: event.currentTarget.value, initialValue: value })}
70+
onChange={(event) => {
71+
const nextValue = event.currentTarget.value;
72+
setEdit((state) => ({
73+
value: nextValue,
74+
initialValue: state?.initialValue ?? value,
75+
}));
76+
}}
77+
onKeyDown={(event) => {
78+
if (event.key === "Enter") {
79+
event.currentTarget.blur();
80+
} else if (event.key === "Escape") {
81+
setEdit(null);
82+
}
83+
}}
84+
onBlur={(event) => event.currentTarget.form?.requestSubmit()}
85+
disabled={disabled || write.isPending}
86+
/>
87+
</form>
88+
);
89+
}
90+
91+
export function TextField(props: Props) {
92+
if (props.readOnly) {
93+
return <ReadonlyTextField {...props} />;
94+
}
95+
return <EditableTextField {...props} />;
96+
}

0 commit comments

Comments
 (0)