Skip to content

Commit c596154

Browse files
Add blinking badges for unseen releases, move comp
1 parent 60205b5 commit c596154

File tree

3 files changed

+117
-14
lines changed

3 files changed

+117
-14
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<script lang="ts">
2+
import { onMount } from "svelte";
3+
import { get } from "svelte/store";
4+
import { localStorageStore } from "$lib/localStorageStore";
5+
6+
/**
7+
* The name of the localStorage item to get the date from.
8+
*/
9+
export let storedDateItem: string;
10+
11+
let showPulse = false;
12+
13+
onMount(() => {
14+
if (storedDateItem) {
15+
const storedDate = localStorageStore(storedDateItem, "");
16+
const lastVisitItem = localStorage.getItem("lastVisit");
17+
if (storedDate && lastVisitItem) {
18+
showPulse = new Date(get(storedDate)) > new Date(lastVisitItem);
19+
}
20+
}
21+
});
22+
</script>
23+
24+
{#if showPulse}
25+
<div class="relative inline-flex">
26+
<slot />
27+
<span class="absolute right-0 top-0 -mr-0.5 -mt-0.5 flex size-2.5">
28+
<span
29+
class="absolute inline-flex h-full w-full animate-ping rounded-full bg-primary opacity-75"
30+
/>
31+
<span class="inline-flex size-2.5 rounded-full bg-primary" />
32+
</span>
33+
</div>
34+
{:else}
35+
<slot />
36+
{/if}

src/routes/+page.svelte

Lines changed: 81 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
import * as Accordion from "$lib/components/ui/accordion";
2020
import * as Tabs from "$lib/components/ui/tabs";
2121
import * as Tooltip from "$lib/components/ui/tooltip";
22-
import ListElementRenderer from "./renderers/ListElementRenderer.svelte";
22+
import BlinkingBadge from "$lib/components/BlinkingBadge.svelte";
23+
import ListElementRenderer from "$lib/renderers/ListElementRenderer.svelte";
2324
2425
// Repositories to fetch releases from
2526
type Repo = {
@@ -42,8 +43,8 @@
4243
*/
4344
versionFromTag: (tag: string) => string;
4445
};
45-
type Tabs = "svelte" | "kit" | "others";
46-
const repos: Record<Tabs, { name: string; repos: Repo[] }> = {
46+
type Tab = "svelte" | "kit" | "others";
47+
const repos: Record<Tab, { name: string; repos: Repo[] }> = {
4748
svelte: {
4849
name: "Svelte",
4950
repos: [
@@ -120,6 +121,12 @@
120121
).then(responses => responses.flat());
121122
}
122123
124+
// Badges
125+
let previousTab: string = currentRepo;
126+
let visitedTabs: string[] = [];
127+
const lastVisitKey = "lastVisit";
128+
let lastVisitDateString = "";
129+
123130
// Settings
124131
let displaySvelteBetaReleases = localStorageStore("displaySvelteBetaReleases", true);
125132
let displayKitBetaReleases = localStorageStore("displayKitBetaReleases", true);
@@ -161,7 +168,7 @@
161168
}).format(-Math.ceil(dateDiff), relevantUnit);
162169
}
163170
164-
// Misc
171+
// Types
165172
type Entries<T> = {
166173
[K in keyof T]: [K, T[K]];
167174
}[keyof T][];
@@ -170,10 +177,15 @@
170177
return Object.entries(obj) as Entries<T>;
171178
}
172179
173-
// Remove previous settings (will be removed in a future update)
174180
onMount(() => {
181+
// Remove previous settings (will be removed in a future update)
175182
localStorage.removeItem("displayBetaReleases");
176183
localStorage.removeItem("nonKitReleasesDisplay");
184+
185+
const localItem = localStorage.getItem(lastVisitKey);
186+
const nowDate = new Date().toISOString();
187+
lastVisitDateString = localItem ?? nowDate;
188+
localStorage.setItem(lastVisitKey, nowDate);
177189
});
178190
</script>
179191

@@ -211,18 +223,42 @@
211223
<span class="text-primary">{repos[currentRepo].name}</span>
212224
Releases
213225
</h2>
214-
<Tabs.Root bind:value={currentRepo} class="mt-8">
226+
<Tabs.Root
227+
bind:value={currentRepo}
228+
class="mt-8"
229+
onValueChange={newValue => {
230+
const toSet = new Set(visitedTabs);
231+
toSet.add(previousTab);
232+
visitedTabs = [...toSet];
233+
234+
// I have no clue how this can be undefined
235+
if (newValue) {
236+
previousTab = newValue;
237+
}
238+
}}
239+
>
215240
<div
216241
class="flex flex-col items-start gap-4 xs:flex-row xs:items-center xs:justify-between xs:gap-0"
217242
>
218243
<Tabs.List class="bg-input dark:bg-muted">
219244
{#each typedEntries(repos) as [id, { name }]}
220-
<Tabs.Trigger
221-
class="data-[state=inactive]:text-foreground/60 data-[state=inactive]:hover:bg-background/50 data-[state=active]:hover:text-foreground/75 data-[state=inactive]:hover:text-foreground dark:data-[state=inactive]:hover:bg-background/25"
222-
value={id}
223-
>
224-
{name}
225-
</Tabs.Trigger>
245+
{#if !visitedTabs.includes(id) && id !== currentRepo}
246+
<BlinkingBadge storedDateItem="{id.toLowerCase()}MostRecentUpdate">
247+
<Tabs.Trigger
248+
class="data-[state=inactive]:text-foreground/60 data-[state=inactive]:hover:bg-background/50 data-[state=active]:hover:text-foreground/75 data-[state=inactive]:hover:text-foreground dark:data-[state=inactive]:hover:bg-background/25"
249+
value={id}
250+
>
251+
{name}
252+
</Tabs.Trigger>
253+
</BlinkingBadge>
254+
{:else}
255+
<Tabs.Trigger
256+
class="data-[state=inactive]:text-foreground/60 data-[state=inactive]:hover:bg-background/50 data-[state=active]:hover:text-foreground/75 data-[state=inactive]:hover:text-foreground dark:data-[state=inactive]:hover:bg-background/25"
257+
value={id}
258+
>
259+
{name}
260+
</Tabs.Trigger>
261+
{/if}
226262
{/each}
227263
</Tabs.List>
228264
<div class="ml-auto flex items-center space-x-2 xs:ml-0">
@@ -273,6 +309,30 @@
273309
<Skeleton class="h-80 w-full" />
274310
</div>
275311
{:then releases}
312+
<!-- eslint-disable-next-line @typescript-eslint/no-unused-vars -->
313+
{@const _ = (() => {
314+
// Update the most recent date of a release of the list
315+
const latestRelease = releases.sort(
316+
(a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
317+
)[0];
318+
if (!latestRelease) return false; // boolean because cannot store void in a const
319+
const storedDate = localStorage.getItem(`${id.toLowerCase()}MostRecentUpdate`);
320+
const latestReleaseDate = new Date(latestRelease.created_at);
321+
if (storedDate) {
322+
const storedDateObj = new Date(storedDate);
323+
if (latestReleaseDate > storedDateObj) {
324+
localStorage.setItem(
325+
`${id.toLowerCase()}MostRecentUpdate`,
326+
`"${latestReleaseDate.toISOString()}"`
327+
);
328+
}
329+
} else {
330+
localStorage.setItem(
331+
`${id.toLowerCase()}MostRecentUpdate`,
332+
`"${latestReleaseDate.toISOString()}"`
333+
);
334+
}
335+
})()}
276336
<!-- The latest releases for each package of the repoList -->
277337
{@const latestReleases = (
278338
id === "others"
@@ -442,8 +502,7 @@
442502
? !isMajorRelease &&
443503
semver.major(releaseRepo.versionFromTag(release.tag_name)) <
444504
semver.major(releaseRepo.versionFromTag(matchingLatestRelease.tag_name)) &&
445-
releaseDate.getTime() >
446-
new Date(matchingEarliestOfLatestMajor.created_at).getTime()
505+
releaseDate > new Date(matchingEarliestOfLatestMajor.created_at)
447506
: false}
448507
{@const releaseBody = (() => {
449508
const body = release.body ?? "";
@@ -461,6 +520,14 @@
461520
<!-- Trigger with release name, date and optional prerelease badge -->
462521
<Accordion.Trigger class="group hover:no-underline">
463522
<div class="flex w-full items-center gap-2 xs:items-baseline xs:gap-1">
523+
{#if new Date(release.created_at) > new Date(lastVisitDateString) && !visitedTabs.includes(id)}
524+
<div class="relative ml-1 mr-2 inline-flex">
525+
<span
526+
class="absolute inline-flex h-full w-full animate-ping rounded-full bg-primary opacity-75"
527+
/>
528+
<span class="inline-flex size-2.5 rounded-full bg-primary" />
529+
</div>
530+
{/if}
464531
<div class="flex flex-col items-start gap-1">
465532
<span class="text-left text-lg group-hover:underline">
466533
{release.name}

0 commit comments

Comments
 (0)