diff --git a/examples/sds-demo/src/lib/components/History.svelte b/examples/sds-demo/src/lib/components/History.svelte index a519d30..bb99e89 100644 --- a/examples/sds-demo/src/lib/components/History.svelte +++ b/examples/sds-demo/src/lib/components/History.svelte @@ -206,7 +206,10 @@ display: flex; flex-direction: column; height: 100%; - overflow: hidden; + overflow-y: scroll; + overflow-x: hidden; + min-width: 400px; + scrollbar-width: none; } .virtualizer-container { diff --git a/examples/sds-demo/src/lib/components/HistoryItem.svelte b/examples/sds-demo/src/lib/components/HistoryItem.svelte index 57995d5..5906f11 100644 --- a/examples/sds-demo/src/lib/components/HistoryItem.svelte +++ b/examples/sds-demo/src/lib/components/HistoryItem.svelte @@ -3,11 +3,15 @@ import type { MessageChannelEventObject } from '$lib/sds/stream'; import { getMessageId } from '$lib/sds/message'; - export let event: MessageChannelEventObject; - export let identicon: string; + export let event: MessageChannelEventObject | undefined = undefined; + export let identicon: string = ''; export let currentIdFilter: string | null = null; - export let onEventClick: (id: string | null) => void; - export let onDependencyClick: (messageId: string, event: Event) => void; + export let onEventClick: (id: string | null) => void = () => {}; + export let onDependencyClick: (messageId: string, event: Event) => void = () => {}; + export let width: number = 340; + export let height: number = 178; + + export let overflow: boolean = true; // Map event types to colors using index signature const eventColors: { [key in string]: string } = { @@ -29,13 +33,15 @@ [MessageChannelEvent.MissedMessages]: 'Missed' }; - $: id = getMessageId(event); - $: color = eventColors[event.type] || '#888'; - $: name = eventNames[event.type] || event.type; + $: id = event ? getMessageId(event) : null; + $: color = event ? (eventColors[event.type] || '#888') : '#f0f0f0'; + $: name = event ? (eventNames[event.type] || event.type) : ''; $: matchesFilter = currentIdFilter && id === currentIdFilter; function handleEventClick() { - onEventClick(id); + if (event && id) { + onEventClick(id); + } } function handleDependencyClick(messageId: string, e: Event) { @@ -43,65 +49,75 @@ } -
-
-
-
- Identicon +
+ {#if event} +
+
+
+ Identicon +
+
+
+ {name} +
+
+ {id} +
+
+ {#if event.type === MessageChannelEvent.MessageDelivered} +
+ {event.payload.sentOrReceived} +
+ {/if} + {#if event.type === MessageChannelEvent.MessageSent || event.type === MessageChannelEvent.MessageReceived} +
+ {event.payload.lamportTimestamp} +
+ {/if}
-
-
- {name} -
-
- {id} -
-
- {#if event.type === MessageChannelEvent.MessageDelivered} -
- {event.payload.sentOrReceived} -
- {/if} {#if event.type === MessageChannelEvent.MessageSent || event.type === MessageChannelEvent.MessageReceived} -
- {event.payload.lamportTimestamp} -
+ {#each event.payload.causalHistory as dependency} + {@const dependencyMatchesFilter = + currentIdFilter && dependency.messageId === currentIdFilter} +
handleDependencyClick(dependency.messageId, e)} + > + {dependency.messageId} +
+ {/each} {/if}
- {#if event.type === MessageChannelEvent.MessageSent || event.type === MessageChannelEvent.MessageReceived} - - {#each event.payload.causalHistory as dependency} - {@const dependencyMatchesFilter = - currentIdFilter && dependency.messageId === currentIdFilter} -
handleDependencyClick(dependency.messageId, e)} - > - {dependency.messageId} -
- {/each} - - {/if} -
+ {/if}
diff --git a/examples/sds-demo/src/lib/components/StateGraphDashboard.svelte b/examples/sds-demo/src/lib/components/StateGraphDashboard.svelte new file mode 100644 index 0000000..eafe0b1 --- /dev/null +++ b/examples/sds-demo/src/lib/components/StateGraphDashboard.svelte @@ -0,0 +1,143 @@ + + +
+ {#if grid} +
+
+ {#each grid as row} +
+ {#each row as cell} + + {#if cell} + {@const id = getMessageId(cell)} + {#if id && identicons[id]} + + {/if} + {/if} + {/each} +
+ {/each} +
+
+ {/if} +
+ + diff --git a/examples/sds-demo/src/lib/components/StateGraphSummary.svelte b/examples/sds-demo/src/lib/components/StateGraphSummary.svelte new file mode 100644 index 0000000..61a1659 --- /dev/null +++ b/examples/sds-demo/src/lib/components/StateGraphSummary.svelte @@ -0,0 +1,84 @@ + + +
+ {#each actual_grid as row} +
+

{row.lamportTimestamp}

+ {#each row.columns as cell} + {#if cell?.type} +
+

{eventNames[cell.type]}

+
+ {/if} + {/each} +
+ {/each} +
+ + diff --git a/examples/sds-demo/src/lib/sds.svelte.ts b/examples/sds-demo/src/lib/sds.svelte.ts index f6d0c73..e930d5a 100644 --- a/examples/sds-demo/src/lib/sds.svelte.ts +++ b/examples/sds-demo/src/lib/sds.svelte.ts @@ -73,7 +73,7 @@ async function send(payload: Uint8Array): Promise { console.error('error sending message', result.failures); } return { - success: result.successes.length > 0, + success: true, retrievalHint: hash }; }); diff --git a/examples/sds-demo/src/lib/utils/event.ts b/examples/sds-demo/src/lib/utils/event.ts new file mode 100644 index 0000000..ecf39a4 --- /dev/null +++ b/examples/sds-demo/src/lib/utils/event.ts @@ -0,0 +1,20 @@ +import { MessageChannelEvent } from '@waku/sds'; + +export const eventColors: { [key in string]: string } = { + [MessageChannelEvent.MessageSent]: '#3B82F6', // blue + [MessageChannelEvent.MessageDelivered]: '#10B981', // green + [MessageChannelEvent.MessageReceived]: '#8B5CF6', // purple + [MessageChannelEvent.MessageAcknowledged]: '#059669', // dark green + [MessageChannelEvent.PartialAcknowledgement]: '#6D28D9', // dark purple + [MessageChannelEvent.MissedMessages]: '#EF4444' // red + }; + + // Event type to display name using index signature +export const eventNames: { [key in string]: string } = { + [MessageChannelEvent.MessageSent]: 'Sent', + [MessageChannelEvent.MessageDelivered]: 'Delivered', + [MessageChannelEvent.MessageReceived]: 'Received', + [MessageChannelEvent.MessageAcknowledged]: 'Acknowledged', + [MessageChannelEvent.PartialAcknowledgement]: 'Partially Acknowledged', + [MessageChannelEvent.MissedMessages]: 'Missed' + }; \ No newline at end of file diff --git a/examples/sds-demo/src/lib/utils/stateGraph.svelte.ts b/examples/sds-demo/src/lib/utils/stateGraph.svelte.ts new file mode 100644 index 0000000..7ebab5e --- /dev/null +++ b/examples/sds-demo/src/lib/utils/stateGraph.svelte.ts @@ -0,0 +1,123 @@ +import { type MessageChannelEventObject } from '$lib/sds/stream'; +import { MessageChannelEvent } from '@waku/sds'; + +const lamportTimestamp = $state(0); +let maxLamportTimestamp = $state(0); + +export const initializeGrid = (_maxLamportTimestamp: number) => { + maxLamportTimestamp = _maxLamportTimestamp; + const rows = maxLamportTimestamp; + const columns = maxLamportTimestamp; + return createGrid(rows, columns); +}; + +export const addItems = (items: Array, _lamportTimestamp?: number) => { + if (!_lamportTimestamp) { + _lamportTimestamp = lamportTimestamp; + } + grid[_lamportTimestamp] = items; +}; + +export const createGrid = ( + rows: number, + columns: number +): Array> => { + return Array(rows) + .fill(null) + .map(() => Array(columns).fill(null)); +}; + +type GridItem = { + lamportTimestamp: number; + events: Array; +}; + +const x_start = $state(0); +const x_window = 100; +const x_threshold = 0; +const virtual_grid: Map = $state(new Map()); +export const actual_grid = $state(createGrid(x_window, 10).map((row, index) => ({lamportTimestamp: index, columns: row}))); +export const update_virtual_grid = (event: MessageChannelEventObject) => { + const lamportTimestamp = getLamportTimestamp(event); + if (!lamportTimestamp) { + return; + } + const events = virtual_grid.get(lamportTimestamp)?.events || []; + events.push(event); + virtual_grid.set(lamportTimestamp, { lamportTimestamp, events }); + if(lamportTimestamp > x_start + x_window - x_threshold) { + shift_actual_grid(0 ,lamportTimestamp - x_window - x_threshold); + } else if (x_start <= lamportTimestamp && lamportTimestamp <= x_start + x_window) { + actual_grid[lamportTimestamp % x_window].columns.push(event); + } +} +export const shift_actual_grid = (amount: number = 1, _x_start?: number) => { + if (!_x_start) { + _x_start = x_start; + } + for(let i = _x_start + amount; i < _x_start + x_window + amount; i++) { + const events = virtual_grid.get(i)?.events || []; + actual_grid[i % x_window] = {lamportTimestamp: i, columns: events}; + } +} + +export const grid = $state(createGrid(50, 10)); + +const getLamportTimestamp = (event: MessageChannelEventObject) => { + let lamportTimestamp = null; + if ( + event.type === MessageChannelEvent.MessageSent || + event.type === MessageChannelEvent.MessageReceived || + event.type === MessageChannelEvent.SyncSent || + event.type === MessageChannelEvent.SyncReceived + ) { + lamportTimestamp = event.payload.lamportTimestamp; + if (!lamportTimestamp) { + return; + } + } else { + lamportTimestamp = longestTimestamp; + } + return lamportTimestamp; +} +const messagesPerLamportTimestamp = $state(new Map>()); +let longestTimestamp = $state(0); +export const recordMessage = ( + message: MessageChannelEventObject, + _grid?: Array> +) => { + if (!_grid) { + _grid = grid; + } + let lamportTimestamp = null; + if ( + message.type === MessageChannelEvent.MessageSent || + message.type === MessageChannelEvent.MessageReceived || + message.type === MessageChannelEvent.SyncSent || + message.type === MessageChannelEvent.SyncReceived + ) { + lamportTimestamp = message.payload.lamportTimestamp; + if (!lamportTimestamp) { + return; + } + } else { + lamportTimestamp = longestTimestamp; + } + const messages = messagesPerLamportTimestamp.get(lamportTimestamp) || []; + messages.push(message); + messagesPerLamportTimestamp.set(lamportTimestamp, messages); + if (lamportTimestamp > longestTimestamp) { + longestTimestamp = lamportTimestamp; + } + const firstFill = _grid[lamportTimestamp].findIndex((item) => item !== null); + if (firstFill === -1) { + _grid[lamportTimestamp][Math.floor(_grid[lamportTimestamp].length / 2)] = message; + } else { + const lastFill = _grid[lamportTimestamp].findLastIndex((item) => item !== null); + if (firstFill > _grid[lamportTimestamp].length - lastFill) { + _grid[lamportTimestamp][firstFill - 1] = message; + } else { + _grid[lamportTimestamp][lastFill + 1] = message; + } + } +}; diff --git a/examples/sds-demo/src/lib/waku/waku.svelte.ts b/examples/sds-demo/src/lib/waku/waku.svelte.ts index 145f445..55f5fcd 100644 --- a/examples/sds-demo/src/lib/waku/waku.svelte.ts +++ b/examples/sds-demo/src/lib/waku/waku.svelte.ts @@ -121,9 +121,8 @@ export async function startWaku(): Promise { // Connect to peers await node.dial( - "/ip4/127.0.0.1/tcp/8000/ws/p2p/16Uiu2HAm6LgMnvadFttVeFsW5WHuoefsviCRbfo4AvnjySp4rnNt" - // "/dns4/node-01.do-ams3.waku.sandbox.status.im/tcp/8095/wss/p2p/16Uiu2HAmNaeL4p3WEYzC9mgXBmBWSgWjPHRvatZTXnp8Jgv3iKsb" // '/dns4/waku-test.bloxy.one/tcp/8095/wss/p2p/16Uiu2HAmSZbDB7CusdRhgkD81VssRjQV5ZH13FbzCGcdnbbh6VwZ' + "/ip4/127.0.0.1/tcp/8000/ws/p2p/16Uiu2HAm3TLea2NVs4dAqYM2gAgoV9CMKGeD1BkP3RAvmk7HBAbU" ); // eslint-disable-next-line @typescript-eslint/no-explicit-any (window as any).waku = node; diff --git a/examples/sds-demo/src/routes/+layout.svelte b/examples/sds-demo/src/routes/+layout.svelte index 84ff1a8..3871b42 100644 --- a/examples/sds-demo/src/routes/+layout.svelte +++ b/examples/sds-demo/src/routes/+layout.svelte @@ -18,7 +18,7 @@ -
+
diff --git a/examples/sds-demo/src/routes/+page.svelte b/examples/sds-demo/src/routes/+page.svelte index 854f804..b2f9a6e 100644 --- a/examples/sds-demo/src/routes/+page.svelte +++ b/examples/sds-demo/src/routes/+page.svelte @@ -7,7 +7,7 @@ // Redirect to history page when connected $effect(() => { if ($connectionState.status === "connected") { - goto('/history'); + goto('/state-graph'); } }); diff --git a/examples/sds-demo/src/routes/state-graph/+page.svelte b/examples/sds-demo/src/routes/state-graph/+page.svelte new file mode 100644 index 0000000..beda3b7 --- /dev/null +++ b/examples/sds-demo/src/routes/state-graph/+page.svelte @@ -0,0 +1,42 @@ + + +
+ +
+ +
+ +
+ +
+ +
+ +
+