|
1 | 1 | <script lang="ts"> |
2 | 2 | import { onMount } from "svelte"; |
3 | 3 | import { get } from "svelte/store"; |
| 4 | + import { pushState } from "$app/navigation"; |
| 5 | + import { page } from "$app/stores"; |
| 6 | + import type { TabsProps } from "bits-ui"; |
4 | 7 | import type { Octokit } from "octokit"; |
5 | 8 | import { ArrowUpRight, LoaderCircle } from "lucide-svelte"; |
6 | 9 | import { MetaTags } from "svelte-meta-tags"; |
7 | 10 | import { persisted } from "svelte-persisted-store"; |
8 | 11 | import semver from "semver"; |
9 | 12 | import type { Snapshot } from "./$types"; |
10 | | - import type { Tab } from "$lib/types"; |
| 13 | + import { availableTabs, type Tab } from "$lib/types"; |
11 | 14 | import { FAVICON_PNG_URL, PROD_URL } from "$lib/config"; |
12 | 15 | import { getOctokit } from "$lib/octokit"; |
13 | 16 | import { getTabState } from "$lib/stores"; |
|
29 | 32 |
|
30 | 33 | const octokit = getOctokit(); |
31 | 34 |
|
32 | | - // Repositories to fetch releases from |
33 | | - let currentRepo: Tab = "svelte"; |
| 35 | + let currentTab: Tab = "svelte"; |
34 | 36 |
|
35 | | - // Tab change |
| 37 | + function onTabChange(newTab: TabsProps["value"]) { |
| 38 | + const toSet = new Set(visitedTabs); |
| 39 | + toSet.add(previousTab); |
| 40 | + visitedTabs = [...toSet]; |
| 41 | +
|
| 42 | + // I have no clue how this can be undefined |
| 43 | + if (newTab) { |
| 44 | + // @ts-expect-error Svelte 5, please |
| 45 | + previousTab = newTab; |
| 46 | + pushState(`?${queryParam}=${newTab}`, {}); |
| 47 | + } |
| 48 | + } |
| 49 | +
|
| 50 | + // Tab change from the store (layout) |
36 | 51 | let tabChangeAsked = false; |
37 | 52 | const tabState = getTabState(); |
38 | | - tabState.subscribe(value => { |
39 | | - if (value === currentRepo) return; |
| 53 | + tabState.subscribe(newTab => { |
| 54 | + if (newTab === currentTab) return; |
40 | 55 | tabChangeAsked = true; |
41 | 56 | window.scrollTo({ top: 0, behavior: "smooth" }); |
42 | 57 | }); |
43 | 58 |
|
44 | 59 | let scrollY = 0; |
45 | 60 | $: if (tabChangeAsked && scrollY === 0) { |
46 | | - currentRepo = get(tabState); |
| 61 | + currentTab = get(tabState); |
| 62 | + onTabChange(currentTab); |
47 | 63 | tabChangeAsked = false; |
48 | 64 | } |
49 | 65 |
|
| 66 | + // Tab change from the URL |
| 67 | + const queryParam = "tab"; |
| 68 | + let shouldUnsubscribe = false; |
| 69 | + const unsubscribe = page.subscribe(({ url }) => { |
| 70 | + const tab = url.searchParams.get(queryParam) as Tab | null; |
| 71 | + if (!tab) { |
| 72 | + shouldUnsubscribe = true; |
| 73 | + return; |
| 74 | + } |
| 75 | + if (availableTabs.includes(tab)) { |
| 76 | + tabState.set(tab); |
| 77 | + currentTab = tab; |
| 78 | + shouldUnsubscribe = true; |
| 79 | + } |
| 80 | + }); |
| 81 | + $: if (shouldUnsubscribe) unsubscribe(); |
| 82 | +
|
50 | 83 | /** |
51 | 84 | * Fetches releases from GitHub for the given category, for |
52 | 85 | * all the repositories in that category. |
|
100 | 133 | }; |
101 | 134 |
|
102 | 135 | // Badges |
103 | | - let previousTab: Tab = currentRepo; |
| 136 | + let previousTab: Tab = currentTab; |
104 | 137 | let visitedTabs: Tab[] = []; |
105 | 138 | let loadedTabs: Tab[] = []; |
106 | 139 | let isLoadingDone = false; |
|
172 | 205 | <svelte:window bind:scrollY /> |
173 | 206 |
|
174 | 207 | <MetaTags |
175 | | - title={repos[currentRepo].name} |
| 208 | + title={repos[currentTab].name} |
176 | 209 | titleTemplate="%s | Svelte Changelog" |
177 | 210 | description="A nice UI to stay up-to-date with Svelte releases" |
178 | 211 | canonical={PROD_URL} |
|
202 | 235 |
|
203 | 236 | <div class="container py-8"> |
204 | 237 | <h2 class="text-3xl font-bold"> |
205 | | - <span class="text-primary">{repos[currentRepo].name}</span> |
| 238 | + <span class="text-primary">{repos[currentTab].name}</span> |
206 | 239 | Releases |
207 | 240 | </h2> |
208 | | - <Tabs.Root |
209 | | - bind:value={currentRepo} |
210 | | - class="mt-8" |
211 | | - onValueChange={newValue => { |
212 | | - const toSet = new Set(visitedTabs); |
213 | | - toSet.add(previousTab); |
214 | | - visitedTabs = [...toSet]; |
215 | | - |
216 | | - // I have no clue how this can be undefined |
217 | | - if (newValue) { |
218 | | - // @ts-expect-error Svelte 5, please |
219 | | - previousTab = newValue; |
220 | | - } |
221 | | - }} |
222 | | - > |
| 241 | + <Tabs.Root bind:value={currentTab} class="mt-8" onValueChange={onTabChange}> |
223 | 242 | <div |
224 | 243 | class="flex flex-col items-start gap-4 xs:flex-row xs:items-center xs:justify-between xs:gap-0" |
225 | 244 | > |
226 | 245 | <Tabs.List class="bg-input dark:bg-muted"> |
227 | 246 | {#each typedEntries(repos) as [id, { name }]} |
228 | 247 | <BlinkingBadge |
229 | 248 | storedDateItem="{id}MostRecentUpdate" |
230 | | - show={!visitedTabs.includes(id) && id !== currentRepo} |
| 249 | + show={!visitedTabs.includes(id) && id !== currentTab} |
231 | 250 | > |
232 | 251 | <Tabs.Trigger |
233 | 252 | 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" |
|
240 | 259 | </Tabs.List> |
241 | 260 | <div class="ml-auto flex items-center space-x-2 xs:ml-0"> |
242 | 261 | <!-- Tab-specific settings --> |
243 | | - {#if currentRepo === "svelte"} |
| 262 | + {#if currentTab === "svelte"} |
244 | 263 | <Checkbox |
245 | | - id="beta-releases-{currentRepo}" |
| 264 | + id="beta-releases-{currentTab}" |
246 | 265 | bind:checked={$displaySvelteBetaReleases} |
247 | | - aria-labelledby="beta-releases-label-{currentRepo}" |
| 266 | + aria-labelledby="beta-releases-label-{currentTab}" |
248 | 267 | /> |
249 | | - {:else if currentRepo === "kit"} |
| 268 | + {:else if currentTab === "kit"} |
250 | 269 | <Checkbox |
251 | | - id="beta-releases-{currentRepo}" |
| 270 | + id="beta-releases-{currentTab}" |
252 | 271 | bind:checked={$displayKitBetaReleases} |
253 | | - aria-labelledby="beta-releases-label-{currentRepo}" |
| 272 | + aria-labelledby="beta-releases-label-{currentTab}" |
254 | 273 | /> |
255 | 274 | {:else} |
256 | 275 | <Checkbox |
257 | | - id="beta-releases-{currentRepo}" |
| 276 | + id="beta-releases-{currentTab}" |
258 | 277 | bind:checked={$displayOtherBetaReleases} |
259 | | - aria-labelledby="beta-releases-label-{currentRepo}" |
| 278 | + aria-labelledby="beta-releases-label-{currentTab}" |
260 | 279 | /> |
261 | 280 | {/if} |
262 | 281 | <Label |
263 | | - id="beta-releases-label-{currentRepo}" |
264 | | - for="beta-releases-{currentRepo}" |
| 282 | + id="beta-releases-label-{currentTab}" |
| 283 | + for="beta-releases-{currentTab}" |
265 | 284 | class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" |
266 | 285 | > |
267 | | - Show {repos[currentRepo].name} prereleases |
| 286 | + Show {repos[currentTab].name} prereleases |
268 | 287 | </Label> |
269 | 288 | </div> |
270 | 289 | </div> |
|
0 commit comments