Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: table filters #2227

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15,873 changes: 233 additions & 15,640 deletions app/src/generated/graphql-env.d.ts

Large diffs are not rendered by default.

37 changes: 37 additions & 0 deletions app/src/lib/graphql/documents/transaction-receipt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { graphql } from "gql.tada"

export const transactionReceiptQueryDocument = graphql(/* GraphQL */ `
query TransactionQuery($hash: String!) {
data: v0_transfers(
where: {
_or: [
{source_transaction_hash:{_eq: $hash}},
{destination_transaction_hash:{_eq: $hash}},
]
}) {
sender
normalized_sender
source_chain_id
source_timestamp
source_transaction_hash

receiver
normalized_receiver
destination_chain_id
destination_timestamp
destination_transaction_hash

source_chain { display_name }
destination_chain { display_name }

forwards {
port
channel
receiver
chain {
chain_id
}
}
}
}
`)
4 changes: 3 additions & 1 deletion app/src/lib/graphql/documents/transfers.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { graphql } from "gql.tada"

export const allTransfersQueryDocument = graphql(/* GraphQL */ `
query AllTransfersQuery @cached(ttl: 1) {
query AllTransfersQuery @cached(ttl: 1) {
v0_transfers(limit: 100, order_by: {source_timestamp: desc}) {
sender
source_chain_id
Expand All @@ -10,6 +10,8 @@ export const allTransfersQueryDocument = graphql(/* GraphQL */ `
destination_chain_id
assets
source_timestamp
source_chain { display_name }
destination_chain { display_name }
forwards {
chain {
chain_id
Expand Down
10 changes: 9 additions & 1 deletion app/src/lib/query-client.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import { toast } from "svelte-sonner"
import { browser } from "$app/environment"
import { MutationCache, QueryClient } from "@tanstack/svelte-query"
import { MutationCache, QueryCache, QueryClient } from "@tanstack/svelte-query"
import { createSyncStoragePersister } from "@tanstack/query-sync-storage-persister"

export function createQueryClient() {
const queryClient: QueryClient = new QueryClient({
queryCache: new QueryCache({
onError: (error, query) => {
if (query.state.data !== undefined) {
toast.error(`tanstack query error: ${error.message}`)
}
}
}),
defaultOptions: {
queries: {
enabled: browser,
Expand Down
6 changes: 6 additions & 0 deletions app/src/lib/utilities/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
export type DeepNonNullable<T> = T extends object
? {
[P in keyof T]: DeepNonNullable<NonNullable<T[P]>>
}
: NonNullable<T>

export type LooseAutocomplete<T> = {
[K in keyof T]: T[K]
} & {
Expand Down
95 changes: 67 additions & 28 deletions app/src/routes/explorer/(components)/table.svelte
Original file line number Diff line number Diff line change
@@ -1,47 +1,61 @@
<script lang="ts" generics="T extends object" >
import { derived, get } from "svelte/store"
<script lang="ts">
import {
flexRender,
type FilterFn,
type ColumnDef,
getCoreRowModel,
type TableOptions,
createSvelteTable,
getFilteredRowModel,
getPaginationRowModel
} from "@tanstack/svelte-table"
import { writable, type Readable } from "svelte/store"
import { derived } from "svelte/store"
import type { MaybePromise } from "valibot"
import { cn } from "$lib/utilities/shadcn.ts"
import Search from "virtual:icons/lucide/search"
import * as Table from "$lib/components/ui/table"
import { createVirtualizer } from "@tanstack/svelte-virtual"
import { writable, type Readable } from "svelte/store"
import * as Card from "$lib/components/ui/card/index.ts"
import Input from "$lib/components/ui/input/input.svelte"
import type { FormInputEvent } from "$lib/components/ui/input"
import { createVirtualizer, debounce } from "@tanstack/svelte-virtual"

type DataRow = $$Generic

export let columns: Array<ColumnDef<any>>
// https://github.com/TanStack/table/issues/4241
// @ts-ignore
export let dataStore: Readable<Array<any>>
export let onClick: ((tr: unknown) => void) | undefined = undefined
export let tableName: string | undefined = undefined
export let globalFilter: string | undefined = undefined
export let fuzzyFilter: FilterFn<DataRow> | undefined = undefined
export let columns: Array<ColumnDef<DataRow>>
export let dataStore: Readable<Array<DataRow>>
export let rowsLength: number | undefined = undefined
export let onClick: undefined | ((row: DataRow) => MaybePromise<void>) = undefined

const options = writable<TableOptions<any>>({
const options = writable<TableOptions<DataRow>>({
columns,
data: $dataStore,
enableHiding: true,
enableFilters: true,
// https://github.com/TanStack/table/issues/4241
// @ts-ignore
columns,
autoResetPageIndex: true,
enableGlobalFilter: true,
enableColumnFilters: true,
enableColumnResizing: true,
enableMultiRowSelection: true,
globalFilterFn: fuzzyFilter,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel()
getPaginationRowModel: getPaginationRowModel(),
filterFns: fuzzyFilter ? { fuzzy: fuzzyFilter } : undefined
})

let virtualListElement: HTMLDivElement

const table = createSvelteTable(options)
const rows = derived(table, $t => $t.getRowModel().rows)

$: rowsLength = $rows.length

$: $table.setGlobalFilter(globalFilter)

$: virtualizer = createVirtualizer<HTMLDivElement, HTMLTableRowElement>({
overscan: 20,
count: $rows.length,
Expand All @@ -52,43 +66,68 @@ $: virtualizer = createVirtualizer<HTMLDivElement, HTMLTableRowElement>({
$: dataStore.subscribe(() => {
if (!$dataStore) return
$table.setPageSize($dataStore.length)
options.update(options => ({ ...options, data: $dataStore as unknown as Array<T> }))
options.update(options => ({ ...options, data: $dataStore }))
})
</script>

<!-- <div class="relative w-full">
<Search class="absolute left-2.5 top-3 size-4 text-muted-foreground" />
<Input
type="text"
autocorrect="off"
autocomplete="off"
spellcheck="false"
autocapitalize="off"
on:keyup={handleKeyUp}
bind:value={globalFilter}
placeholder={`Search ${tableName || 'for any column'}`}
class={cn(
'bg-white/35',
'pl-8 pr-2.5 border border-b-transparent w-full py-1 outline-none ring-0 ring-offset-0 ring-offset-transparent',
'focus:outline-none focus:ring-0 focus-visible:ring-0 focus-visible:outline-none focus:ring-offset-0 focus-visible:ring-offset-0',
)}
/>
</div> -->
<Card.Root>
<div bind:this={virtualListElement} >
<div bind:this={virtualListElement}>
<Table.Root>
<Table.Header>
{#each $table.getHeaderGroups() as headerGroup (headerGroup.id)}
<Table.Row>
{#each headerGroup.headers as header (header.id)}
<Table.Head
colspan={header.colSpan}
class={cn(`w-[${header.getSize()}px] whitespace-nowrap`)}
>
{#if !header.id.endsWith('hidden')}
<Table.Head
colspan={header.colSpan}
class={cn(`w-[${header.getSize()}px] whitespace-nowrap`)}
>
<svelte:component
this={flexRender(header.column.columnDef.header, header.getContext())}
/>
</Table.Head>
</Table.Head>
{/if}
{/each}
</Table.Row>
{/each}
</Table.Header>
<Table.Body class={cn(`h-[${$virtualizer.getTotalSize()}px]] whitespace-nowrap`)}>
{#each $virtualizer.getVirtualItems() as row, index (row.index)}
<Table.Row
class={cn(onClick !== undefined ? 'cursor-pointer' : '',
class={cn(
onClick !== undefined ? 'cursor-pointer' : '',
index % 2 === 0 ? 'bg-secondary/10' : 'bg-transparent',
)}
on:click={onClick !== undefined ? (() => onClick($rows[row.index].original)) : undefined}
on:click|once={onClick !== undefined
? () => onClick($rows[row.index].original)
: undefined}
>
{#each $rows[row.index].getVisibleCells() as cell, index (cell.id)}
<Table.Cell>
<svelte:component
this={flexRender(cell.column.columnDef.cell, cell.getContext())}
/>
</Table.Cell>
{#if !cell.id.endsWith('hidden')}
<Table.Cell>
<svelte:component
this={flexRender(cell.column.columnDef.cell, cell.getContext())}
/>
</Table.Cell>
{/if}
{/each}
</Table.Row>
{/each}
Expand Down
57 changes: 2 additions & 55 deletions app/src/routes/explorer/+layout.svelte
Original file line number Diff line number Diff line change
@@ -1,61 +1,13 @@
<script lang="ts">
import { onMount } from "svelte"
import { page } from "$app/stores"
import { onNavigate } from "$app/navigation"
import { cn } from "$lib/utilities/shadcn.ts"
import type { LayoutData } from "./$types.ts"
import Menu from "./(components)/menu.svelte"
import * as Resizable from "$lib/components/ui/resizable"
import ArrowLeftIcon from "virtual:icons/lucide/arrow-left"
import GripVerticalIcon from "virtual:icons/tabler/grip-vertical"
import { ScrollArea } from "$lib/components/ui/scroll-area/index.ts"

export let data: LayoutData

let windowSize = { width: window.innerWidth, height: window.innerHeight }

const handleResize = () => {
requestAnimationFrame(() => {
windowSize = { width: window.innerWidth, height: window.innerHeight }
})
}

let isCollapsed = false
let leftPane: Resizable.PaneAPI
$: [leftSize, rightSize] = [14, 88]

onMount(() => {
window.addEventListener("resize", handleResize)
return () => {
window.removeEventListener("resize", handleResize)
}
})

$: {
try {
if (windowSize?.width < 900) {
isCollapsed = true
} else {
isCollapsed = false
}
// biome-ignore lint/suspicious/noEmptyBlockStatements: <explanation>
} catch {}
}

const onLayoutChange: Resizable.PaneGroupProps["onLayoutChange"] = sizes => {
document.cookie = `PaneForge:layout=${JSON.stringify(sizes)}`
}

const onCollapse: Resizable.PaneProps["onExpand"] = () => {
isCollapsed = true
document.cookie = `PaneForge:collapsed=${true}`
}

const onExpand: Resizable.PaneProps["onExpand"] = () => {
isCollapsed = false
document.cookie = `PaneForge:collapsed=${false}`
}

let explorerRoute = $page.route.id?.split("/").at(2) ?? null
$: explorerPageDescription =
data.tables.filter(t => t.route === explorerRoute).at(0)?.description ?? null
Expand All @@ -65,17 +17,12 @@ onNavigate(navigation => {
explorerRoute = navigation.to?.route.id?.split("/").at(2) ?? null
}
})

// @ts-expect-error
$: mainExplorerPage = $page.route.id?.split("/").length <= 3
</script>

<svelte:head>
<title>Union - Explorer</title>
</svelte:head>



<!-- mobile layout !-->
<div class="flex flex-row sm:divide-x overflow-x-none max-w-full w-full">
<nav class={cn("sm:bg-muted h-full overflow-y-auto", explorerRoute === null ? "flex-1 sm:flex-none" : "hidden sm:block")}>
Expand All @@ -96,12 +43,12 @@ $: mainExplorerPage = $page.route.id?.split("/").length <= 3
<span class="uppercase">{$page.route.id?.split('/').at(-2)}</span>
</a>

<div class="p-2 pt-0 sm:p-6 flex flex-col flex-1">
<div class="p-2 pt-0 sm:px-6 sm:py-4 flex flex-col flex-1">
<div class={cn($page.route.id?.split('/').length === 3 ? "" : "hidden")}>
<h2 class="text-4xl font-extrabold font-expanded sm:!font-extra-expanded uppercase font-supermolot">
{explorerRoute?.replaceAll('-', ' ')}
</h2>
<p class="pb-4 -mt-1 text-muted-foreground">{'>'} {explorerPageDescription}</p>
<p class="pb-2.5 -mt-1 text-muted-foreground">{'>'} {explorerPageDescription}</p>
</div>
<slot />
</div>
Expand Down
Loading