tracks lexicons and how many times they appeared on the jetstream

feat(client): add toggling between showing since stream start vs server init

ptr.pet e8cda421 a4dd7621

verified
Changed files
+99 -22
client
+1 -1
client/src/lib/components/BskyToggle.svelte
···
<!-- svelte-ignore a11y_no_static_element_interactions -->
<button
onclick={onBskyToggle}
-
class="wsbadge !mt-0 !font-normal bg-yellow-100 hover:bg-yellow-200 border-yellow-300"
+
class="wsbadge !mt-0 !font-normal bg-blue-100 hover:bg-blue-200 border-blue-300"
>
<input checked={dontShowBsky} type="checkbox" />
<span class="ml-0.5"> hide app.bsky.* </span>
+4 -4
client/src/lib/components/RefreshControl.svelte
···
</script>
<div
-
class="wsbadge !pl-2 !px-1 !mt-0 !font-normal bg-green-100 hover:bg-green-200 border-green-300"
+
class="wsbadge !pl-2 !px-1 !mt-0 !font-normal bg-lime-100 hover:bg-lime-200 border-lime-300"
>
-
<label for="refresh-rate" class="text-green-800 mr-1">refresh:</label>
+
<label for="refresh-rate" class="text-lime-800 mr-1">refresh:</label>
<input
id="refresh-rate"
value={refreshRate}
···
pattern="[0-9]*"
min="0"
placeholder="real-time"
-
class="bg-green-50 text-green-900 placeholder-green-400 border border-green-200 rounded-full px-1 outline-none focus:bg-white focus:border-green-400 min-w-0 w-20"
+
class="bg-green-50 text-lime-900 placeholder-lime-600 border border-lime-200 rounded-full px-1 outline-none focus:bg-white focus:border-lime-400 min-w-0 w-20"
/>
-
<span class="text-green-700">s</span>
+
<span class="text-lime-700">s</span>
</div>
+29
client/src/lib/components/ShowControls.svelte
···
+
<script lang="ts">
+
import type { ShowOption } from "$lib/types";
+
+
interface Props {
+
show: ShowOption;
+
onShowChange: (value: ShowOption) => void;
+
}
+
+
let { show, onShowChange }: Props = $props();
+
+
const showOptions: ShowOption[] = ["server init", "stream start"];
+
</script>
+
+
<div
+
class="wsbadge !pl-2 !px-1 !mt-0 !font-normal bg-pink-100 hover:bg-pink-200 border-pink-300"
+
>
+
<label for="show" class="text-pink-800 mr-1"> show since: </label>
+
<select
+
id="show"
+
value={show}
+
onchange={(e) =>
+
onShowChange((e.target as HTMLSelectElement).value as ShowOption)}
+
class="bg-pink-50 text-pink-900 border border-pink-200 rounded-full px-1 outline-none focus:bg-white focus:border-pink-400 min-w-0"
+
>
+
{#each showOptions as option}
+
<option value={option}>{option}</option>
+
{/each}
+
</select>
+
</div>
+1
client/src/lib/types.ts
···
};
export type SortOption = "total" | "created" | "deleted" | "date";
+
export type ShowOption = "server init" | "stream start";
+64 -17
client/src/routes/+page.svelte
···
EventRecord,
Events,
NsidCount,
+
ShowOption,
Since,
SortOption,
} from "$lib/types";
import { onMount, onDestroy } from "svelte";
-
import { writable } from "svelte/store";
+
import { get, writable } from "svelte/store";
import { PUBLIC_API_URL } from "$env/static/public";
import { fetchEvents, fetchTrackingSince } from "$lib/api";
import { createRegexFilter } from "$lib/filter";
···
import BskyToggle from "$lib/components/BskyToggle.svelte";
import RefreshControl from "$lib/components/RefreshControl.svelte";
import { formatTimestamp } from "$lib/format";
+
import ShowControls from "$lib/components/ShowControls.svelte";
type Props = {
data: { events: Events; trackingSince: Since };
···
const events = writable(
new Map<string, EventRecord>(Object.entries(data.events.events)),
);
+
const eventsStart = new Map<string, EventRecord>(
+
Object.entries(data.events.events),
+
);
const pendingUpdates = new Map<string, EventRecord>();
-
let eventsList: NsidCount[] = $state([]);
+
let updateTimer: NodeJS.Timeout | null = null;
-
events.subscribe((value) => {
-
eventsList = value
-
.entries()
-
.map(([nsid, event]) => ({
-
nsid,
-
...event,
-
}))
-
.toArray();
-
});
let per_second = $state(data.events.per_second);
let tracking_since = $state(data.trackingSince.since);
+
const diffEvents = (
+
oldEvents: Map<string, EventRecord>,
+
newEvents: Map<string, EventRecord>,
+
): NsidCount[] => {
+
const nsidCounts: NsidCount[] = [];
+
for (const [nsid, event] of newEvents.entries()) {
+
const oldEvent = oldEvents.get(nsid);
+
if (oldEvent) {
+
const counts = {
+
nsid,
+
count: event.count - oldEvent.count,
+
deleted_count: event.deleted_count - oldEvent.deleted_count,
+
last_seen: event.last_seen,
+
};
+
if (counts.count > 0 || counts.deleted_count > 0)
+
nsidCounts.push(counts);
+
} else {
+
nsidCounts.push({
+
nsid,
+
...event,
+
});
+
}
+
}
+
return nsidCounts;
+
};
const applyEvents = (newEvents: Record<string, EventRecord>) => {
events.update((map) => {
for (const [nsid, event] of Object.entries(newEvents)) {
···
});
};
+
let error: string | null = $state(null);
+
let filterRegex = $state("");
+
let dontShowBsky = $state(false);
+
let sortBy: SortOption = $state("total");
+
let refreshRate = $state("");
+
let changedByUser = $state(false);
+
let show: ShowOption = $state("server init");
+
let eventsList: NsidCount[] = $state([]);
+
let updateEventsList = $derived((value: Map<string, EventRecord>) => {
+
switch (show) {
+
case "server init":
+
eventsList = value
+
.entries()
+
.map(([nsid, event]) => ({
+
nsid,
+
...event,
+
}))
+
.toArray();
+
break;
+
case "stream start":
+
eventsList = diffEvents(eventsStart, value);
+
break;
+
}
+
});
+
events.subscribe((value) => updateEventsList(value));
let all: EventRecord = $derived(
eventsList.reduce(
(acc, event) => {
···
},
),
);
-
let error: string | null = $state(null);
-
let filterRegex = $state("");
-
let dontShowBsky = $state(false);
-
let sortBy: SortOption = $state("total");
-
let refreshRate = $state("");
-
let changedByUser = $state(false);
let websocket: WebSocket | null = null;
let isStreamOpen = $state(false);
···
refreshRate = "2";
else if (refreshRate === "2" && changedByUser === false)
refreshRate = "";
+
}}
+
/>
+
<ShowControls
+
{show}
+
onShowChange={(value: ShowOption) => {
+
show = value;
+
updateEventsList(get(events));
}}
/>
<RefreshControl