mirror of
https://github.com/trailbaseio/trailbase.git
synced 2026-05-19 07:49:57 -05:00
Show skeleton rows in tables while loading data to reduce visual jank.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { For, Match, Show, Switch } from "solid-js";
|
||||
import { Index, For, Match, Show, Switch } from "solid-js";
|
||||
import type { Accessor } from "solid-js";
|
||||
import {
|
||||
flexRender,
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { createIsMobile } from "@/lib/signals";
|
||||
|
||||
type TableOptions<TData, TValue> = {
|
||||
@@ -204,12 +205,13 @@ function omit<T, K extends keyof T>(object: T, key: K): Omit<T, K> {
|
||||
|
||||
export function Table<TData>(props: {
|
||||
table: SolidTable<TData>;
|
||||
loading: boolean;
|
||||
onRowClick?: (idx: number, row: TData) => void;
|
||||
}) {
|
||||
const paginationEnabled = () => props.table.options.manualPagination ?? false;
|
||||
const paginationState = () => props.table.getState().pagination;
|
||||
const columns = () => props.table.options.columns;
|
||||
const numRows = () => props.table.getRowModel().rows?.length ?? 0;
|
||||
const numRows = (): number => props.table.getRowModel().rows.length;
|
||||
const enableSorting = () =>
|
||||
props.table.options.manualSorting || props.table.options.enableSorting;
|
||||
|
||||
@@ -248,6 +250,34 @@ export function Table<TData>(props: {
|
||||
|
||||
<TableBody>
|
||||
<Switch>
|
||||
<Match when={props.loading}>
|
||||
<Index each={Array(paginationState().pageSize)}>
|
||||
{() => (
|
||||
<TableRow>
|
||||
<For each={props.table.getVisibleLeafColumns()}>
|
||||
{(cell) => (
|
||||
<TableCell>
|
||||
<Switch>
|
||||
<Match when={cell.id === "__select__"}>
|
||||
<Checkbox />
|
||||
</Match>
|
||||
|
||||
<Match when={true}>
|
||||
<Skeleton
|
||||
height={16}
|
||||
width={cell.getSize()}
|
||||
radius={10}
|
||||
/>
|
||||
</Match>
|
||||
</Switch>
|
||||
</TableCell>
|
||||
)}
|
||||
</For>
|
||||
</TableRow>
|
||||
)}
|
||||
</Index>
|
||||
</Match>
|
||||
|
||||
<Match when={numRows() > 0}>
|
||||
<For each={props.table.getRowModel().rows}>
|
||||
{(row) => (
|
||||
@@ -256,15 +286,7 @@ export function Table<TData>(props: {
|
||||
</For>
|
||||
</Match>
|
||||
|
||||
<Match when={paginationState().pageIndex > 0}>
|
||||
<TableRow>
|
||||
<TableCell colSpan={columns().length}>
|
||||
<span>Loading...</span>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</Match>
|
||||
|
||||
<Match when={paginationState().pageIndex === 0}>
|
||||
<Match when={true}>
|
||||
<TableRow>
|
||||
<TableCell colSpan={columns().length}>
|
||||
<span>Empty</span>
|
||||
|
||||
@@ -404,65 +404,75 @@ export function AccountsPage() {
|
||||
</Match>
|
||||
|
||||
<Match when={users.isLoading}>
|
||||
<span>Loading</span>
|
||||
<div class="w-full space-y-2.5">
|
||||
<Table
|
||||
table={accountsTable()}
|
||||
loading={true}
|
||||
onRowClick={undefined}
|
||||
/>
|
||||
</div>
|
||||
</Match>
|
||||
|
||||
<Match when={users.data}>
|
||||
<Match when={users.isSuccess}>
|
||||
<div class="w-full space-y-2.5">
|
||||
<Table table={accountsTable()} onRowClick={undefined} />
|
||||
<Table
|
||||
table={accountsTable()}
|
||||
loading={false}
|
||||
onRowClick={undefined}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<SafeSheet
|
||||
children={(sheet) => {
|
||||
return (
|
||||
<>
|
||||
<SheetContent class={sheetMaxWidth}>
|
||||
<AddUser userRefetch={refetch} {...sheet} />
|
||||
</SheetContent>
|
||||
|
||||
<SheetTrigger
|
||||
as={(props: DialogTriggerProps) => (
|
||||
<Button
|
||||
variant="outline"
|
||||
class="flex gap-2"
|
||||
onClick={() => {}}
|
||||
{...props}
|
||||
>
|
||||
Add User
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* WARN: This might open multiple sheets or at least scrims for each row */}
|
||||
<SafeSheet
|
||||
open={[
|
||||
() => editUser() !== undefined,
|
||||
(isOpen: boolean | ((value: boolean) => boolean)) => {
|
||||
if (!isOpen) {
|
||||
setEditUser(undefined);
|
||||
}
|
||||
},
|
||||
]}
|
||||
children={(sheet) => {
|
||||
return (
|
||||
<SheetContent class={sheetMaxWidth}>
|
||||
<Show when={editUser()}>
|
||||
<EditSheetContent
|
||||
user={editUser()!}
|
||||
refetch={refetch}
|
||||
{...sheet}
|
||||
/>
|
||||
</Show>
|
||||
</SheetContent>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Match>
|
||||
</Switch>
|
||||
|
||||
<SafeSheet
|
||||
children={(sheet) => {
|
||||
return (
|
||||
<>
|
||||
<SheetContent class={sheetMaxWidth}>
|
||||
<AddUser userRefetch={refetch} {...sheet} />
|
||||
</SheetContent>
|
||||
|
||||
<SheetTrigger
|
||||
as={(props: DialogTriggerProps) => (
|
||||
<Button
|
||||
variant="outline"
|
||||
class="flex gap-2"
|
||||
onClick={() => {}}
|
||||
{...props}
|
||||
>
|
||||
Add User
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* WARN: This might open multiple sheets or at least scrims for each row */}
|
||||
<SafeSheet
|
||||
open={[
|
||||
() => editUser() !== undefined,
|
||||
(isOpen: boolean | ((value: boolean) => boolean)) => {
|
||||
if (!isOpen) {
|
||||
setEditUser(undefined);
|
||||
}
|
||||
},
|
||||
]}
|
||||
children={(sheet) => {
|
||||
return (
|
||||
<SheetContent class={sheetMaxWidth}>
|
||||
<Show when={editUser()}>
|
||||
<EditSheetContent
|
||||
user={editUser()!}
|
||||
refetch={refetch}
|
||||
{...sheet}
|
||||
/>
|
||||
</Show>
|
||||
</SheetContent>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -195,7 +195,7 @@ function ResultViewImpl(props: {
|
||||
<ExecutionTime timestamp={props.timestamp} />
|
||||
</div>
|
||||
|
||||
<Table table={dataTable()} />
|
||||
<Table table={dataTable()} loading={false} />
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
|
||||
@@ -315,29 +315,27 @@ export function LogsPage() {
|
||||
/>
|
||||
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<Switch fallback={<p>Loading...</p>}>
|
||||
<Switch>
|
||||
<Match when={logsFetch.error}>Error {`${logsFetch.error}`}</Match>
|
||||
|
||||
<Match when={logsFetch.isLoading}>
|
||||
<span>Loading</span>
|
||||
</Match>
|
||||
|
||||
<Match when={logsFetch.data}>
|
||||
{pagination().pageIndex === 0 && logsFetch.data!.stats && (
|
||||
<Match when={true}>
|
||||
<Show when={pagination().pageIndex === 0}>
|
||||
<div class="mb-4 flex w-full flex-col gap-4 md:h-[300px] md:flex-row">
|
||||
<Show when={showMap() && logsFetch.data!.stats!.country_codes}>
|
||||
<Show when={showMap()}>
|
||||
<div class="flex items-center md:w-1/2 md:max-w-[500px]">
|
||||
<WorldMap
|
||||
country_codes={logsFetch.data!.stats!.country_codes!}
|
||||
country_codes={
|
||||
logsFetch.data?.stats?.country_codes ?? null
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<div class={showMap() ? "md:w-1/2" : "w-full"}>
|
||||
<LogsChart stats={logsFetch.data!.stats!} />
|
||||
<LogsChart rates={logsFetch.data?.stats?.rate ?? []} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
|
||||
<FilterBar
|
||||
initial={searchParams.filter}
|
||||
@@ -351,7 +349,7 @@ export function LogsPage() {
|
||||
placeholder={`Filter Query, e.g. '(latency > 2 || status >= 400) && method = "GET"'`}
|
||||
/>
|
||||
|
||||
<Table table={logsTable()} />
|
||||
<Table table={logsTable()} loading={logsFetch.isLoading} />
|
||||
</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
@@ -445,8 +443,8 @@ const Legend = L.Control.extend({
|
||||
},
|
||||
});
|
||||
|
||||
function WorldMap(props: { country_codes: { [key in string]?: number } }) {
|
||||
const codes = () => props.country_codes;
|
||||
function WorldMap(props: { country_codes: Stats["country_codes"] }) {
|
||||
const codes = () => props.country_codes ?? {};
|
||||
|
||||
let ref: HTMLDivElement | undefined;
|
||||
let map: L.Map | undefined;
|
||||
@@ -547,15 +545,10 @@ function WorldMap(props: { country_codes: { [key in string]?: number } }) {
|
||||
);
|
||||
}
|
||||
|
||||
function LogsChart(props: { stats: Stats }) {
|
||||
const stats = props.stats;
|
||||
|
||||
function LogsChart(props: { rates: Stats["rate"] }) {
|
||||
const data = (): ChartData | undefined => {
|
||||
const s = stats;
|
||||
if (!s) return;
|
||||
|
||||
const labels = s.rate.map(([ts, _v]) => Number(ts) * 1000);
|
||||
const data = s.rate.map(([_ts, v]) => v);
|
||||
const labels = props.rates.map(([ts, _v]) => Number(ts) * 1000);
|
||||
const data = props.rates.map(([_ts, v]) => v);
|
||||
|
||||
return {
|
||||
labels,
|
||||
|
||||
@@ -184,7 +184,7 @@ function DatabaseSettingsForm(props: {
|
||||
</p>
|
||||
|
||||
<div class="max-h-[500px] overflow-auto">
|
||||
<Table table={dbTable()} />
|
||||
<Table table={dbTable()} loading={false} />
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { Signal } from "solid-js";
|
||||
import { createWritableMemo } from "@solid-primitives/memo";
|
||||
import { TbRefresh, TbTable, TbTrash, TbColumns } from "solid-icons/tb";
|
||||
import { useSearchParams } from "@solidjs/router";
|
||||
import { useQuery, useQueryClient } from "@tanstack/solid-query";
|
||||
import { useQuery } from "@tanstack/solid-query";
|
||||
import type { QueryObserverResult } from "@tanstack/solid-query";
|
||||
import type {
|
||||
CellContext,
|
||||
@@ -64,7 +64,7 @@ import {
|
||||
UploadedFiles,
|
||||
} from "@/components/tables/Files";
|
||||
|
||||
import { createConfigQuery, invalidateConfig } from "@/lib/api/config";
|
||||
import { createConfigQuery } from "@/lib/api/config";
|
||||
import type { Record, ArrayRecord } from "@/lib/record";
|
||||
import { hashSqlValue } from "@/lib/value";
|
||||
import { urlSafeBase64ToUuid, toHex, safeParseInt } from "@/lib/utils";
|
||||
@@ -255,12 +255,11 @@ function TableHeaderRightHandButtons(props: {
|
||||
const satisfiesRecordApi = createMemo(() =>
|
||||
tableOrViewSatisfiesRecordApiRequirements(props.table, props.allTables),
|
||||
);
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const config = createConfigQuery();
|
||||
const hasRecordApi = () =>
|
||||
hasRecordApis(config?.data?.config, selectedSchema().name);
|
||||
|
||||
const config = createConfigQuery();
|
||||
|
||||
return (
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
{/* Delete table button */}
|
||||
@@ -275,7 +274,7 @@ function TableHeaderRightHandButtons(props: {
|
||||
dry_run: null,
|
||||
});
|
||||
} finally {
|
||||
invalidateConfig(queryClient);
|
||||
await config.refetch();
|
||||
await props.schemaRefetch();
|
||||
}
|
||||
})();
|
||||
@@ -380,10 +379,9 @@ function TableHeader(props: {
|
||||
}) {
|
||||
const allTables = createMemo(() => props.allTables.map(([t, _]) => t));
|
||||
const selectedSchema = () => props.table[0];
|
||||
const type = () => tableType(selectedSchema());
|
||||
|
||||
const headerTitle = () => {
|
||||
switch (type()) {
|
||||
switch (tableType(selectedSchema())) {
|
||||
case "view":
|
||||
return "View";
|
||||
case "virtualTable":
|
||||
@@ -434,34 +432,6 @@ function TableHeader(props: {
|
||||
);
|
||||
}
|
||||
|
||||
type TableState = {
|
||||
selected: Table | View;
|
||||
response: ListRowsResponse;
|
||||
};
|
||||
|
||||
async function buildTableState(
|
||||
selected: Table | View,
|
||||
filter: string | null,
|
||||
pageSize: number,
|
||||
pageIndex: number,
|
||||
cursor: string | null,
|
||||
sorting: SortingState,
|
||||
): Promise<TableState> {
|
||||
const response = await fetchRows(
|
||||
selected.name,
|
||||
filter,
|
||||
pageSize,
|
||||
pageIndex,
|
||||
cursor,
|
||||
formatSortingAsOrder(sorting),
|
||||
);
|
||||
|
||||
return {
|
||||
selected,
|
||||
response,
|
||||
};
|
||||
}
|
||||
|
||||
type CellType = "UUID" | "JSON" | "File" | "File[]" | ColumnDataType;
|
||||
|
||||
function deriveCellType(column: Column): CellType {
|
||||
@@ -483,12 +453,29 @@ function deriveCellType(column: Column): CellType {
|
||||
}
|
||||
|
||||
function buildColumnDefs(
|
||||
tableName: QualifiedName,
|
||||
selectedSchema: Table | View,
|
||||
columns: Column[] | undefined,
|
||||
pkColumnIndex: number,
|
||||
columns: Column[],
|
||||
blobEncoding: BlobEncoding,
|
||||
): ColumnDef<ArrayRecord, SqlValue>[] {
|
||||
return columns.map((col, idx) => {
|
||||
if (columns === undefined) {
|
||||
// Fallback to schema (rather than response) column defintions.
|
||||
if (tableType(selectedSchema) === "table") {
|
||||
return (selectedSchema as Table).columns.map((c) => ({
|
||||
id: c.name,
|
||||
header: c.name,
|
||||
}));
|
||||
}
|
||||
|
||||
// We don't have any schema column defs. Fallback to single col.
|
||||
return [
|
||||
{
|
||||
header: "",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return columns.map((col, idx): ColumnDef<ArrayRecord, SqlValue> => {
|
||||
const fk = getForeignKey(col.options);
|
||||
const notNull = isNotNull(col.options);
|
||||
const type = deriveCellType(col);
|
||||
@@ -505,7 +492,7 @@ function buildColumnDefs(
|
||||
cell: (context) =>
|
||||
renderCell(
|
||||
context,
|
||||
tableName,
|
||||
selectedSchema.name,
|
||||
columns,
|
||||
pkColumnIndex,
|
||||
{
|
||||
@@ -515,12 +502,13 @@ function buildColumnDefs(
|
||||
blobEncoding,
|
||||
),
|
||||
accessorFn: (row: ArrayRecord) => row[idx],
|
||||
} as ColumnDef<ArrayRecord, SqlValue>;
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function ArrayRecordTable(props: {
|
||||
state: TableState;
|
||||
function RecordTable(props: {
|
||||
selectedSchema: Table | View;
|
||||
records: ListRowsResponse | undefined;
|
||||
pagination: SimpleSignal<PaginationState>;
|
||||
filter: SimpleSignal<string | undefined>;
|
||||
columnPinningState: Signal<ColumnPinningState>;
|
||||
@@ -533,31 +521,31 @@ function ArrayRecordTable(props: {
|
||||
new Map<string, SqlValue>(),
|
||||
);
|
||||
|
||||
const selectedSchema = () => props.state.selected;
|
||||
const selectedSchema = () => props.selectedSchema;
|
||||
const mutable = () =>
|
||||
tableType(selectedSchema()) === "table" && !hiddenTable(selectedSchema());
|
||||
const data = () => props.state.response.rows;
|
||||
|
||||
const rowsRefetch = () => props.rowsRefetch();
|
||||
const columns = (): Column[] => props.state.response.columns;
|
||||
const totalRowCount = () => props.state.response.total_row_count;
|
||||
|
||||
const data = () => props.records?.rows;
|
||||
const columns = () => props.records?.columns;
|
||||
const totalRowCount = () => props.records?.total_row_count ?? 0;
|
||||
|
||||
const pkColumnIndex = createMemo(
|
||||
() => findPrimaryKeyColumnIndex(columns()) ?? 0,
|
||||
() => findPrimaryKeyColumnIndex(columns() ?? []) ?? 0,
|
||||
);
|
||||
|
||||
const table = createMemo(() => {
|
||||
const columns = buildColumnDefs(
|
||||
selectedSchema().name,
|
||||
const columnDefs = buildColumnDefs(
|
||||
selectedSchema(),
|
||||
columns(),
|
||||
pkColumnIndex(),
|
||||
props.state.response.columns,
|
||||
blobEncoding(),
|
||||
);
|
||||
|
||||
return buildTable(
|
||||
{
|
||||
// NOTE: The cell rendering is constrolled via the columnsDefs.
|
||||
columns,
|
||||
columns: columnDefs,
|
||||
data: data(),
|
||||
columnPinning: props.columnPinningState[0],
|
||||
onColumnPinningChange: props.columnPinningState[1],
|
||||
@@ -633,10 +621,11 @@ function ArrayRecordTable(props: {
|
||||
<div class="overflow-x-auto pt-4">
|
||||
<TableComponent
|
||||
table={table()}
|
||||
loading={props.records === undefined}
|
||||
onRowClick={
|
||||
mutable()
|
||||
? (_idx: number, row: ArrayRecord) => {
|
||||
setEditRow(rowDataToRow(columns(), row));
|
||||
setEditRow(rowDataToRow(columns() ?? [], row));
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
@@ -690,7 +679,8 @@ function ArrayRecordTable(props: {
|
||||
await deleteRows(
|
||||
prettyFormatQualifiedName(selectedSchema().name),
|
||||
{
|
||||
primary_key_column: columns()[pkColumnIndex()].name,
|
||||
primary_key_column:
|
||||
columns()?.[pkColumnIndex()].name ?? "??",
|
||||
values: ids,
|
||||
},
|
||||
);
|
||||
@@ -739,7 +729,7 @@ function ArrayRecordTable(props: {
|
||||
</Select>
|
||||
|
||||
<Show when={import.meta.env.DEV}>
|
||||
<DebugDialogButton title="Schema" data={data()} />
|
||||
<DebugDialogButton title="Schema" data={data() ?? []} />
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
@@ -751,8 +741,8 @@ function IndexTable(props: {
|
||||
table: Table;
|
||||
schemas: ListSchemasResponse;
|
||||
schemaRefetch: () => Promise<void>;
|
||||
hidden: boolean;
|
||||
}) {
|
||||
const hidden = () => hiddenTable(props.table);
|
||||
const [editIndex, setEditIndex] = createSignal<TableIndex | undefined>();
|
||||
const [selectedIndexes, setSelectedIndexes] = createSignal(new Set<string>());
|
||||
|
||||
@@ -769,7 +759,7 @@ function IndexTable(props: {
|
||||
return buildTable({
|
||||
columns: indexColumns,
|
||||
data: indexes().map(([index, _]) => index),
|
||||
onRowSelection: props.hidden
|
||||
onRowSelection: hidden()
|
||||
? undefined
|
||||
: // eslint-disable-next-line solid/reactivity
|
||||
(rows: Row<TableIndex>[], value: boolean) => {
|
||||
@@ -824,8 +814,9 @@ function IndexTable(props: {
|
||||
<div class="space-y-2.5 overflow-x-auto">
|
||||
<TableComponent
|
||||
table={indexesTable()}
|
||||
loading={false}
|
||||
onRowClick={
|
||||
props.hidden
|
||||
hidden()
|
||||
? undefined
|
||||
: (_idx: number, index: TableIndex) => {
|
||||
setEditIndex(index);
|
||||
@@ -838,7 +829,7 @@ function IndexTable(props: {
|
||||
}}
|
||||
</SafeSheet>
|
||||
|
||||
<Show when={!props.hidden}>
|
||||
<Show when={!hidden()}>
|
||||
<div class="mt-2 flex gap-2">
|
||||
<SafeSheet>
|
||||
{(sheet) => {
|
||||
@@ -938,7 +929,7 @@ function TriggerTable(props: { table: Table; schemas: ListSchemasResponse }) {
|
||||
</p>
|
||||
|
||||
<div class="mt-4">
|
||||
<TableComponent table={triggersTable()} />
|
||||
<TableComponent loading={false} table={triggersTable()} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -950,8 +941,7 @@ export function TablePane(props: {
|
||||
schemaRefetch: () => Promise<void>;
|
||||
}) {
|
||||
const selectedSchema = () => props.selectedTable[0];
|
||||
const type = () => tableType(selectedSchema());
|
||||
const hidden = () => hiddenTable(selectedSchema());
|
||||
const isTable = () => tableType(selectedSchema()) === "table";
|
||||
|
||||
const [searchParams, setSearchParams] = useSearchParams<{
|
||||
filter?: string;
|
||||
@@ -966,64 +956,62 @@ export function TablePane(props: {
|
||||
return [];
|
||||
});
|
||||
|
||||
const filter = () => searchParams.filter;
|
||||
const setFilter = (filter: string | undefined) => {
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
filter,
|
||||
});
|
||||
};
|
||||
const [filter, setFilter] = [
|
||||
() => searchParams.filter,
|
||||
(filter: string | undefined) => {
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
filter,
|
||||
});
|
||||
},
|
||||
];
|
||||
|
||||
const pagination = (): PaginationState => {
|
||||
return {
|
||||
pageSize: safeParseInt(searchParams.pageSize) ?? 20,
|
||||
pageIndex: safeParseInt(searchParams.pageIndex) ?? 0,
|
||||
};
|
||||
};
|
||||
const setPagination = (s: PaginationState) => {
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
pageSize: s.pageSize,
|
||||
pageIndex: s.pageIndex,
|
||||
});
|
||||
};
|
||||
const [pagination, setPagination] = [
|
||||
(): PaginationState => {
|
||||
return {
|
||||
pageSize: safeParseInt(searchParams.pageSize) ?? 20,
|
||||
pageIndex: safeParseInt(searchParams.pageIndex) ?? 0,
|
||||
};
|
||||
},
|
||||
(s: PaginationState) => {
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
pageSize: s.pageSize,
|
||||
pageIndex: s.pageIndex,
|
||||
});
|
||||
},
|
||||
];
|
||||
|
||||
const [sorting, setSorting] = createSignal<SortingState>([]);
|
||||
|
||||
const state: QueryObserverResult<TableState> = useQuery(() => ({
|
||||
const records: QueryObserverResult<ListRowsResponse> = useQuery(() => ({
|
||||
queryKey: [
|
||||
"tableData",
|
||||
prettyFormatQualifiedName(props.selectedTable[0].name),
|
||||
selectedSchema().name,
|
||||
searchParams.filter,
|
||||
pagination().pageIndex,
|
||||
pagination().pageSize,
|
||||
pagination(),
|
||||
sorting(),
|
||||
],
|
||||
] as ReadonlyArray<unknown>,
|
||||
queryFn: async ({ queryKey }) => {
|
||||
const p = pagination();
|
||||
const c = cursors();
|
||||
const s = sorting();
|
||||
|
||||
console.debug(
|
||||
`Fetching data with key: ${queryKey}, index: ${p.pageIndex}, cursors: ${c}, sorting: ${s}`,
|
||||
);
|
||||
console.debug(`Fetching data with key: ${queryKey}`);
|
||||
|
||||
try {
|
||||
const state = await buildTableState(
|
||||
props.selectedTable[0],
|
||||
const { pageSize, pageIndex } = pagination();
|
||||
|
||||
const response = await fetchRows(
|
||||
selectedSchema().name,
|
||||
searchParams.filter ?? null,
|
||||
p.pageSize,
|
||||
p.pageIndex,
|
||||
c[p.pageIndex - 1],
|
||||
s,
|
||||
pageSize,
|
||||
pageIndex,
|
||||
cursors()[pageIndex - 1],
|
||||
formatSortingAsOrder(sorting()),
|
||||
);
|
||||
|
||||
const cursor = state.response.cursor;
|
||||
if (cursor && p.pageIndex >= c.length) {
|
||||
setCursors([...c, cursor]);
|
||||
const newCursor = response.cursor;
|
||||
if (newCursor && pageIndex >= cursors().length) {
|
||||
setCursors([...cursors(), newCursor]);
|
||||
}
|
||||
|
||||
return state;
|
||||
return response;
|
||||
} catch (err) {
|
||||
// Reset.
|
||||
setSearchParams({
|
||||
@@ -1037,13 +1025,7 @@ export function TablePane(props: {
|
||||
},
|
||||
}));
|
||||
|
||||
const client = useQueryClient();
|
||||
const rowsRefetch = () => {
|
||||
// Refetches the actual table contents above.
|
||||
client.invalidateQueries({
|
||||
queryKey: ["tableData"],
|
||||
});
|
||||
};
|
||||
const rowsRefetch = records.refetch;
|
||||
const schemaRefetch = async () => {
|
||||
// First re-fetch the schema then the data rows to trigger a re-render.
|
||||
await props.schemaRefetch();
|
||||
@@ -1063,20 +1045,31 @@ export function TablePane(props: {
|
||||
|
||||
<div class="flex flex-col gap-8 p-4">
|
||||
<Switch>
|
||||
<Match when={state.isError}>
|
||||
<Match when={records.isError}>
|
||||
<div class="my-2 flex flex-col gap-4">
|
||||
Failed to fetch rows: {`${state.error}`}
|
||||
Failed to fetch rows: {`${records.error}`}
|
||||
<div>
|
||||
<Button onClick={() => window.location.reload()}>Reload</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Match>
|
||||
|
||||
<Match when={state.isLoading}>Loading...</Match>
|
||||
<Match when={records.isLoading}>
|
||||
<RecordTable
|
||||
selectedSchema={selectedSchema()}
|
||||
records={undefined}
|
||||
pagination={[pagination, setPagination]}
|
||||
filter={[filter, setFilter]}
|
||||
columnPinningState={[columnPinningState, setColumnPinningState]}
|
||||
sorting={[sorting, setSorting]}
|
||||
rowsRefetch={rowsRefetch}
|
||||
/>
|
||||
</Match>
|
||||
|
||||
<Match when={state.data}>
|
||||
<ArrayRecordTable
|
||||
state={state.data!}
|
||||
<Match when={records.isSuccess}>
|
||||
<RecordTable
|
||||
selectedSchema={selectedSchema()}
|
||||
records={records.data}
|
||||
pagination={[pagination, setPagination]}
|
||||
filter={[filter, setFilter]}
|
||||
columnPinningState={[columnPinningState, setColumnPinningState]}
|
||||
@@ -1086,16 +1079,15 @@ export function TablePane(props: {
|
||||
</Match>
|
||||
</Switch>
|
||||
|
||||
<Show when={type() === "table"}>
|
||||
<Show when={isTable()}>
|
||||
<IndexTable
|
||||
table={selectedSchema() as Table}
|
||||
schemas={props.schemas}
|
||||
schemaRefetch={props.schemaRefetch}
|
||||
hidden={hidden()}
|
||||
/>
|
||||
</Show>
|
||||
|
||||
<Show when={type() === "table"}>
|
||||
<Show when={isTable()}>
|
||||
<TriggerTable
|
||||
table={selectedSchema() as Table}
|
||||
schemas={props.schemas}
|
||||
|
||||
Reference in New Issue
Block a user