Skip to content

Commit c670c8f

Browse files
authored
Packet explorer, URL-based pagination (#4025)
- **feat(app2): store page in URL** - **fix(app2): cleanup** - **feat(app2): store transfer fibers in store** - **fix(app2): dry** - **fix(app2): explain sortorder types** - **chore(app2): update graphql schema** - **feat(app2): WIP packet explorer** - **fix(app2): chain fetching** - **fix(app2): wip universal_chain_id fixes** - **feat(app2): nicer packet details page** - **fix(app2): refine packet hash page** - **feat(app2): better packet page** - **fix(app2): decode ports** - **feat(app2): colors** - **chore: fmt**
2 parents 3bb746c + 4f6eef6 commit c670c8f

31 files changed

+43235
-582
lines changed

app2/CONVENTIONS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,19 @@
33
## Type Safety
44

55
- Use Effect's type-safe alternatives where possible:
6+
67
```typescript
78
// Instead of:
89
let value: string | null | undefined
910

1011
// Use:
1112
let value: Option<string>
1213
```
14+
1315
- Never use `try {} catch {}` blocks. always use Effect. When dealing with unsafe functions from libraries, use `Effect.tryPromie(() => somePromiseFn())`
1416
17+
- Do NOT do `Option.isSome(Option.fromNullable(T))` if `T` is already an `Option` type
18+
1519
## Styling
1620
1721
- Use Tailwind's zinc color palette instead of gray for neutral colors

app2/src/app.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
@import "tailwindcss";
22

33
@custom-variant dark (&:where(.dark, .dark *));
4+
5+
@theme {
6+
--color-zinc-925: oklch(0.1755 0.0055 285.854);
7+
}

app2/src/generated/graphql-env.d.ts

Lines changed: 42150 additions & 524 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app2/src/generated/schema.graphql

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app2/src/lib/components/layout/Sidebar/index.svelte

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,46 @@ import ConnectWalletButton from "$lib/components/ui/ConnectWalletButton.svelte"
33
import { uiStore } from "$lib/stores/ui.svelte"
44
import Button from "$lib/components/ui/Button.svelte"
55
import SharpSettingsIcon from "$lib/components/icons/SharpSettingsIcon.svelte"
6-
import { page } from "$app/stores"
6+
import { page } from "$app/state"
77
import { cn } from "$lib/utils"
88
import { onMount } from "svelte"
99
import { navigation } from "./navigation.js"
1010
11-
const isCurrentPath = (path: string) => $page.url.pathname === path
11+
const isCurrentPath = (path: string) => {
12+
// Exact match
13+
if (page.url.pathname === path) return true
14+
15+
// Check if current path is a subroute of the navigation item
16+
// For example, /explorer/packets/123 should highlight /explorer/packets
17+
if (path !== "/" && page.url.pathname.startsWith(`${path}/`)) return true
18+
19+
return false
20+
}
1221
1322
let highlightElement: HTMLElement
1423
1524
const updateHighlightPosition = () => {
16-
if ($page.url.pathname && highlightElement) {
17-
const newActive = document.querySelector(`[data-path="${$page.url.pathname}"]`) as HTMLElement
18-
if (newActive) {
19-
const rect = newActive.getBoundingClientRect()
25+
if (page.url.pathname && highlightElement) {
26+
// Find the best matching navigation item
27+
let bestMatch: HTMLElement | null = null
28+
let bestMatchLength = 0
29+
30+
// Check all navigation items to find the best match
31+
const allNavItems = document.querySelectorAll("[data-path]")
32+
allNavItems.forEach(item => {
33+
const itemPath = item.getAttribute("data-path")
34+
if (
35+
itemPath &&
36+
(page.url.pathname === itemPath ||
37+
(page.url.pathname.startsWith(`${itemPath}/`) && itemPath.length > bestMatchLength))
38+
) {
39+
bestMatch = item as HTMLElement
40+
bestMatchLength = itemPath.length
41+
}
42+
})
43+
44+
if (bestMatch) {
45+
const rect = bestMatch.getBoundingClientRect()
2046
highlightElement.style.top = `${rect.top}px`
2147
highlightElement.style.left = `${rect.left}px`
2248
highlightElement.style.width = `${rect.width}px`
@@ -29,7 +55,7 @@ const updateHighlightPosition = () => {
2955
}
3056
3157
$effect(() => {
32-
if ($page.url.pathname) {
58+
if (page.url.pathname) {
3359
updateHighlightPosition()
3460
}
3561
})
@@ -73,8 +99,7 @@ onMount(() => {
7399
isCurrentPath(item.path) ? "" : "dark:hover:bg-zinc-900"
74100
)}
75101
>
76-
<svelte:component
77-
this={item.icon}
102+
<item.icon
78103
class="size-5 text-zinc-500"
79104
/>
80105
{item.title}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<script lang="ts">
2+
import type { PacketListItem } from "$lib/schema/packet"
3+
import { chains } from "$lib/stores/chains.svelte"
4+
import { getChain } from "$lib/schema/chain"
5+
import { Option } from "effect"
6+
import ChainComponent from "./ChainComponent.svelte"
7+
import DateTimeComponent from "$lib/components/ui/DateTimeComponent.svelte"
8+
import { goto } from "$app/navigation"
9+
import SharpRightArrowIcon from "../icons/SharpRightArrowIcon.svelte"
10+
11+
type Props = {
12+
packet: PacketListItem
13+
}
14+
15+
const { packet }: Props = $props()
16+
17+
const sourceChain = $derived(
18+
Option.flatMap(chains.data, chainsData => getChain(chainsData, packet.source_universal_chain_id))
19+
)
20+
21+
const destinationChain = $derived(
22+
Option.flatMap(chains.data, chainsData =>
23+
getChain(chainsData, packet.destination_universal_chain_id)
24+
)
25+
)
26+
27+
const handleClick = () => {
28+
goto(`/explorer/packets/${packet.packet_hash}`)
29+
}
30+
</script>
31+
32+
<!-- svelte-ignore a11y_click_events_have_key_events -->
33+
<!-- svelte-ignore a11y_no_static_element_interactions -->
34+
<div
35+
class="flex justify-between gap-8 px-4 py-3 h-16 cursor-pointer hover:bg-zinc-50 dark:hover:bg-zinc-900 transition-colors duration-75 items-center"
36+
onclick={handleClick}
37+
>
38+
<div>
39+
<div class="font-mono">
40+
{packet.packet_hash}
41+
</div>
42+
<div class="flex items-center gap-1 text-zinc-400 text-sm">
43+
{#if Option.isSome(sourceChain)}
44+
<ChainComponent chain={sourceChain.value} />
45+
{:else}
46+
<div class="text-zinc-500">{packet.source_universal_chain_id}</div>
47+
{/if}
48+
<SharpRightArrowIcon class="size-5" />
49+
{#if Option.isSome(destinationChain)}
50+
<ChainComponent chain={destinationChain.value} />
51+
{:else}
52+
<div class="text-zinc-500">{packet.destination_universal_chain_id}</div>
53+
{/if}
54+
</div>
55+
</div>
56+
<div class="text-sm">{packet.channel_version}</div>
57+
<DateTimeComponent value={packet.packet_send_timestamp} />
58+
</div>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<script lang="ts">
2+
import Skeleton from "$lib/components/ui/Skeleton.svelte"
3+
</script>
4+
5+
<div class="flex justify-between gap-8 px-4 py-3 h-16 items-center border-b border-zinc-800">
6+
<div>
7+
<div>
8+
<Skeleton class="h-5 w-64" />
9+
</div>
10+
<div class="flex items-center gap-1 mt-1">
11+
<Skeleton class="h-4 w-24" />
12+
<Skeleton class="h-4 w-5" />
13+
<Skeleton class="h-4 w-24" />
14+
</div>
15+
</div>
16+
<Skeleton class="h-5 w-24" />
17+
<Skeleton class="h-5 w-32" />
18+
</div>

app2/src/lib/components/model/TransferListItemComponent.svelte

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script lang="ts">
22
import type { TransferListItem } from "$lib/schema/transfer-list"
33
import { Option } from "effect"
4-
import { getChain } from "$lib/schema/chain"
4+
import { getChain, UniversalChainId } from "$lib/schema/chain"
55
import ChainComponent from "./ChainComponent.svelte"
66
import TokenComponent from "$lib/components/model/TokenComponent.svelte"
77
import Label from "../ui/Label.svelte"
@@ -25,15 +25,15 @@ const handleClick = () => {
2525

2626
{#if Option.isSome(chains.data)}
2727
{@const chainss = chains.data.value}
28-
{@const sourceChain = getChain(chainss, transfer.source_chain.chain_id)}
28+
{@const sourceChain = getChain(chainss, transfer.source_chain.universal_chain_id)}
2929
{@const destinationChain = getChain(
3030
chainss,
31-
transfer.destination_chain.chain_id,
31+
transfer.destination_chain.universal_chain_id,
3232
)}
3333
<!-- svelte-ignore a11y_click_events_have_key_events -->
3434
<!-- svelte-ignore a11y_no_static_element_interactions -->
3535
<div
36-
class="flex justify-between gap-8 px-4 py-3 h-16 cursor-pointer hover:bg-zinc-50 dark:hover:bg-zinc-800 transition-colors duration-75 items-center"
36+
class="flex justify-between gap-8 px-4 py-3 h-16 cursor-pointer hover:bg-zinc-50 dark:hover:bg-zinc-900 transition-colors duration-75 items-center"
3737
onclick={handleClick}
3838
>
3939
<div>

app2/src/lib/components/ui/Card.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ const { children, class: className = "", divided = false, ...rest }: Props = $pr
1414
const classes = cn(
1515
// Base styles
1616
"rounded border shadow-sm",
17-
"dark:border-zinc-700 dark:bg-zinc-900",
17+
"dark:border-zinc-800 bg-zinc-925",
1818
// Conditional padding and dividers
19-
divided ? "p-0 divide-y divide-zinc-800" : "p-4",
19+
divided ? "p-0 divide-y divide-zinc-900" : "p-4",
2020
// Additional classes passed as props
2121
className
2222
)
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<script lang="ts">
2+
import { Option } from "effect"
3+
import type { PacketList } from "$lib/schema/packet"
4+
import Button from "./Button.svelte"
5+
import DateTimeComponent from "./DateTimeComponent.svelte"
6+
7+
type Props = {
8+
data: Option.Option<typeof PacketList.Type>
9+
onLive: () => void
10+
onPrevPage: () => void
11+
onNextPage: () => void
12+
}
13+
14+
const { data, onLive, onPrevPage, onNextPage }: Props = $props()
15+
</script>
16+
17+
<div class="flex gap-6">
18+
<Button onclick={onLive}>LIVE</Button>
19+
<div class="rounded shadow flex">
20+
<button
21+
onclick={onPrevPage}
22+
class="cursor-pointer border-l border-t border-b bg-zinc-700 border-zinc-600 h-10 w-10 rounded-tl rounded-bl"
23+
>
24+
25+
</button>
26+
<div
27+
class="bg-zinc-900 border-t border-b border-zinc-800 flex items-center justify-center px-4 min-w-[250px]"
28+
>
29+
{#if Option.isSome(data) && data.value.length > 0}
30+
<DateTimeComponent value={data.value[0].packet_send_timestamp} />
31+
{/if}
32+
</div>
33+
<button
34+
onclick={onNextPage}
35+
class="cursor-pointer border-r border-t border-b bg-zinc-700 border-zinc-600 h-10 w-10 rounded-tr rounded-br"
36+
>
37+
38+
</button>
39+
</div>
40+
</div>

0 commit comments

Comments
 (0)