Skip to content

Commit fe2ed01

Browse files
authored
Upgrade queries/schemas, smoother barchart (#4046)
- **feat(app2): sexier sidebar** - **fix(app2): tweak number animation** - **fix(app2): packet-trace height type** - **feat(app2): improve transfer chart hover** - **fix(barchart): use option** - **fix(app2): update stats queries to v2** - **fix(app2): remove total transfers for user** - **fix(app2): update graphql schema**
2 parents 6a5eb08 + 983ea6e commit fe2ed01

File tree

11 files changed

+44734
-739
lines changed

11 files changed

+44734
-739
lines changed

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

Lines changed: 43920 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: 706 additions & 152 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/navigation.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import SharpTransferIcon from "$lib/components/icons/SharpTransferIcon.svelte"
2-
import SharpListIcon from "$lib/components/icons/SharpListIcon.svelte"
32
import SharpDashboardIcon from "$lib/components/icons/SharpDashboardIcon.svelte"
43
import SharpStakeIcon from "$lib/components/icons/SharpStakeIcon.svelte"
54
import SharpChannelsIcon from "$lib/components/icons/SharpChannelsIcon.svelte"
@@ -29,12 +28,17 @@ export const navigation: Array<NavSection> = [
2928
{
3029
path: "/transfer",
3130
title: "Transfer",
32-
icon: SharpTransferIcon
33-
},
34-
{
35-
path: "/transfers",
36-
title: "Your Transfers",
37-
icon: SharpListIcon
31+
icon: OutlineControlPointDuplicate,
32+
subroutes: [
33+
{
34+
path: "/transfers",
35+
title: "History"
36+
},
37+
{
38+
path: "/faucet",
39+
title: "Faucet"
40+
}
41+
]
3842
}
3943
]
4044
},

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

Lines changed: 43 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@ import type { FetchDecodeGraphqlError } from "$lib/utils/queries"
77
import { onMount } from "svelte"
88
99
type Props = {
10-
data: Option.Option<Array<DailyTransfer>>
10+
data: Option.Option<ReadonlyArray<DailyTransfer>>
1111
error: Option.Option<FetchDecodeGraphqlError>
1212
class?: string
13+
onHoverChange?: (day: Option.Option<DailyTransfer>) => void
1314
}
1415
15-
const { data, error, class: className = "" } = $props()
16+
const { data, error, class: className = "", onHoverChange = () => {} }: Props = $props()
1617
1718
// Format large numbers with commas (used for chart tooltips)
1819
function formatNumber(num: string | number): string {
@@ -24,38 +25,40 @@ const reversedDailyTransfers = $derived(Option.isSome(data) ? [...data.value].re
2425
2526
const maxCount = $derived(Option.isSome(data) ? Math.max(...data.value.map(d => d.count)) : 0)
2627
27-
// Calculate nice round numbers for y-axis labels
28-
const yLabels = $derived(() => {
29-
if (maxCount <= 0) return [0, 0, 0, 0, 0]
28+
// Track the currently hovered day for display
29+
let hoveredDay = $state<Option.Option<DailyTransfer>>(Option.none())
3030
31-
// Find a nice round maximum that's at least as large as maxCount
32-
const magnitude = 10 ** Math.floor(Math.log10(maxCount))
33-
const roundedMax = Math.ceil(maxCount / magnitude) * magnitude
34-
35-
// Create evenly spaced labels
36-
return [
37-
0,
38-
Math.round(roundedMax / 4),
39-
Math.round(roundedMax / 2),
40-
Math.round((roundedMax * 3) / 4),
41-
roundedMax
42-
]
31+
// Find the day with the highest count
32+
const highestDay = $derived.by(() => {
33+
if (!Option.isSome(data) || data.value.length === 0) return Option.none()
34+
return Option.some(
35+
data.value.reduce((max, current) => (current.count > max.count ? current : max), data.value[0])
36+
)
4337
})
4438
39+
// The count to display (either hovered day or highest day)
40+
const displayCount = $derived(() =>
41+
Option.isSome(hoveredDay)
42+
? hoveredDay.value.count
43+
: Option.isSome(highestDay)
44+
? highestDay.value.count
45+
: 0
46+
)
47+
const displayDate = $derived(() =>
48+
Option.isSome(hoveredDay)
49+
? hoveredDay.value.day_date
50+
: Option.isSome(highestDay)
51+
? highestDay.value.day_date
52+
: ""
53+
)
54+
4555
// Calculate bar heights as percentages
4656
const barHeights = $derived(
4757
reversedDailyTransfers.map(day => ({
4858
...day,
4959
heightPercent: maxCount > 0 ? Math.max((day.count / maxCount) * 100, 1) : 1
5060
}))
5161
)
52-
53-
// Get labels for x-axis (first, middle, last)
54-
const xAxisLabels = $derived(
55-
reversedDailyTransfers.filter(
56-
(_, i, arr) => i === 0 || i === Math.floor(arr.length / 2) || i === arr.length - 1
57-
)
58-
)
5962
</script>
6063

6164
{#if Option.isSome(data) && maxCount > 0}
@@ -70,17 +73,30 @@ const xAxisLabels = $derived(
7073

7174
<!-- Bars -->
7275
<div class="absolute left-0 right-0 top-0 bottom-0 pt-1 px-4 pt-4">
73-
<div class="flex h-full gap-[1px] sm:gap-[2px] md:gap-1 items-end">
76+
<div class="flex h-full items-end">
7477
{#each barHeights as day, i}
75-
<div class="flex flex-col flex-1 group size-full justify-end hover:opacity-100">
78+
<!-- svelte-ignore a11y_no_static_element_interactions -->
79+
<div
80+
class="flex pr-1 flex-col flex-1 group size-full justify-end hover:opacity-100"
81+
onmouseenter={() => {
82+
hoveredDay = Option.some(day);
83+
onHoverChange(Option.some(day));
84+
}}
85+
onmouseleave={() => {
86+
hoveredDay = Option.none();
87+
onHoverChange(Option.none());
88+
}}
89+
>
7690
<div class="w-full size-full flex items-end">
7791
<div
78-
class="relative w-full bg-white rounded-t bar animate-bar"
92+
class="relative w-full bg-white bar animate-bar"
7993
style="--final-height: {day.heightPercent}%; --delay: {i * 50}ms; min-height: 1px;"
8094
>
95+
<!-- uncomment for tooltip
8196
<div class="absolute pointer-events-none bottom-full mb-2 left-1/2 transform -translate-x-1/2 bg-zinc-950 border-zinc-900 border text-white dark:text-white px-2 py-1 rounded text-xs opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap z-10">
8297
<div>{formatNumber(day.count)}</div> <DateTimeComponent value={day.day} showTime={false} />
8398
</div>
99+
!-->
84100
</div>
85101
</div>
86102
</div>

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,14 @@ type Props = {
1111
}
1212
1313
const { statistic, class: className = "" }: Props = $props()
14-
let displayValue = $state(0)
14+
let displayValue = $state(1000000)
1515
let isFirstLoad = $state(true)
1616
1717
// Update displayValue whenever statistic.value changes
1818
$effect(() => {
1919
if (isFirstLoad) {
2020
// On first load, animate from 0 to the value
2121
onMount(() => {
22-
displayValue = 0
2322
setTimeout(() => {
2423
displayValue = statistic.value
2524
isFirstLoad = false

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const classes = cn(
2626
<div
2727
class={classes}
2828
{...rest}
29-
in:fade={{delay:200}}
29+
in:fade={{delay:100}}
3030
>
3131
{@render children()}
3232
</div>

app2/src/lib/queries/statistics.svelte.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import { graphql } from "gql.tada"
55
import { statistics, dailyTransfers } from "$lib/stores/statistics.svelte"
66

77
export const statisticsQuery = createQueryGraphql({
8-
schema: Schema.Struct({ v1_ibc_union_statistics: Statistics }),
8+
schema: Schema.Struct({ v2_stats_count: Statistics }),
99
document: graphql(`
1010
query StatsQuery @cached(ttl: 1) {
11-
v1_ibc_union_statistics {
11+
v2_stats_count {
1212
name
1313
value
1414
}
@@ -17,7 +17,7 @@ export const statisticsQuery = createQueryGraphql({
1717
variables: {},
1818
refetchInterval: "1 second",
1919
writeData: data => {
20-
statistics.data = data.pipe(Option.map(d => d.v1_ibc_union_statistics))
20+
statistics.data = data.pipe(Option.map(d => d.v2_stats_count))
2121
},
2222
writeError: error => {
2323
statistics.error = error
@@ -26,19 +26,19 @@ export const statisticsQuery = createQueryGraphql({
2626

2727
export const dailyTransfersQuery = (limit = 30) =>
2828
createQueryGraphql({
29-
schema: Schema.Struct({ v1_ibc_union_daily_fungible_asset_orders: DailyTransfers }),
29+
schema: Schema.Struct({ v2_stats_transfers_daily_count: DailyTransfers }),
3030
document: graphql(`
3131
query TransfersPerDay($limit: Int!) @cached(ttl: 60) {
32-
v1_ibc_union_daily_fungible_asset_orders(limit: $limit, order_by: {day: desc}) {
32+
v2_stats_transfers_daily_count(args: { p_days_back: $limit }) {
3333
count
34-
day
34+
day_date
3535
}
3636
}
3737
`),
3838
variables: { limit },
3939
refetchInterval: "60 seconds",
4040
writeData: data => {
41-
dailyTransfers.data = data.pipe(Option.map(d => d.v1_ibc_union_daily_fungible_asset_orders))
41+
dailyTransfers.data = data.pipe(Option.map(d => d.v2_stats_transfers_daily_count))
4242
},
4343
writeError: error => {
4444
dailyTransfers.error = error

app2/src/lib/schema/packet-trace.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import { Schema } from "effect"
22
import { UniversalChainId } from "$lib/schema/chain"
33
import { Hex } from "$lib/schema/hex"
4+
import { Height } from "./height.ts"
45

56
export class PacketTrace extends Schema.Class<PacketTrace>("PacketTrace")({
67
type: Schema.String,
78
chain: Schema.Struct({
89
universal_chain_id: UniversalChainId
910
}),
10-
height: Schema.OptionFromNullOr(Schema.Number),
11+
height: Schema.OptionFromNullOr(Height),
1112
block_hash: Schema.OptionFromNullOr(Hex),
1213
timestamp: Schema.OptionFromNullOr(Schema.DateTimeUtc),
1314
transaction_hash: Schema.OptionFromNullOr(Hex)

app2/src/lib/schema/statistics.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Schema } from "effect"
22

33
export const StatisticItem = Schema.Struct({
44
name: Schema.String,
5-
value: Schema.Int
5+
value: Schema.NumberFromString
66
})
77

88
export type StatisticItem = Schema.Schema.Type<typeof StatisticItem>
@@ -11,8 +11,8 @@ export const Statistics = Schema.Array(StatisticItem)
1111
export type Statistics = Schema.Schema.Type<typeof Statistics>
1212

1313
export const DailyTransfer = Schema.Struct({
14-
day: Schema.DateTimeUtc,
15-
count: Schema.Int
14+
day_date: Schema.DateTimeUtc,
15+
count: Schema.NumberFromString
1616
})
1717

1818
export type DailyTransfer = Schema.Schema.Type<typeof DailyTransfer>

app2/src/routes/explorer/+page.svelte

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,32 @@ import ErrorComponent from "$lib/components/model/ErrorComponent.svelte"
99
import Sections from "$lib/components/ui/Sections.svelte"
1010
import StatisticComponent from "$lib/components/model/StatisticComponent.svelte"
1111
import BarChart from "$lib/components/model/BarChart.svelte"
12+
import DateTimeComponent from "$lib/components/ui/DateTimeComponent.svelte"
13+
import type { DailyTransfer } from "$lib/schema/statistics"
14+
15+
// State for tracking the currently hovered day
16+
let hoveredDay = $state<Option.Option<DailyTransfer>>(Option.none())
17+
18+
// Find the day with the highest count
19+
const highestDay = $derived.by(() => {
20+
if (!Option.isSome(dailyTransfers.data) || dailyTransfers.data.value.length === 0)
21+
return Option.none()
22+
return Option.some(
23+
dailyTransfers.data.value.reduce(
24+
(max, current) => (current.count > max.count ? current : max),
25+
dailyTransfers.data.value[0]
26+
)
27+
)
28+
})
29+
30+
// The count to display (either hovered day or highest day)
31+
const displayDay = $derived(
32+
Option.isSome(hoveredDay)
33+
? hoveredDay.value
34+
: Option.isSome(highestDay)
35+
? highestDay.value
36+
: undefined
37+
)
1238
1339
onMount(() => {
1440
statistics.runEffect(statisticsQuery)
@@ -43,12 +69,22 @@ onMount(() => {
4369

4470
<!-- Daily Transfers Chart -->
4571
<Card class="h-80 relative" divided>
46-
<div class="p-4 gap-4 absolute top-0 left-0 border-b-0">
47-
<h2 class="text-2xl font-bold mb-1">Daily Transfers</h2>
48-
<Label>Last 30 days of transfer activity</Label>
72+
<div class="p-4 gap-4 absolute top-0 left-0 border-b-0 w-full z-10">
73+
<div class="flex justify-between items-center">
74+
{#if displayDay !== undefined}
75+
<div>
76+
<Label>{#if Option.isSome(hoveredDay)}<DateTimeComponent class="text-zinc-500" value={hoveredDay.value.day_date} showTime={false} />{:else}Daily Transfers{/if}</Label>
77+
<div class="text-2xl font-bold mt-1">{displayDay.count.toLocaleString()}</div>
78+
</div>
79+
{/if}
80+
</div>
4981
</div>
5082

51-
<BarChart data={dailyTransfers.data} error={dailyTransfers.error} />
83+
<BarChart
84+
data={dailyTransfers.data}
85+
error={dailyTransfers.error}
86+
onHoverChange={(day) => hoveredDay = day}
87+
/>
5288
</Card>
5389

5490
</Sections>

0 commit comments

Comments
 (0)