diff --git a/src/lib/state/breadcrumbs-state.svelte.ts b/src/lib/state/breadcrumbs-state.svelte.ts index a40241c0..40492c1e 100644 --- a/src/lib/state/breadcrumbs-state.svelte.ts +++ b/src/lib/state/breadcrumbs-state.svelte.ts @@ -1,25 +1,39 @@ import type { Pathname } from '$app/types'; -import { onMount } from 'svelte'; +import { onDestroy } from 'svelte'; -export interface BreadCrumbEntry { - text: string; +export interface BreadcrumbEntry { + label: string; href: Pathname | null; } -let _state = $state([]); +interface BreadcrumbSlot { + id: symbol; + entries: BreadcrumbEntry[]; +} + +const _slots = $state([]); export const breadcrumbs = { - get state() { - return _state; + get state(): BreadcrumbEntry[] { + return _slots.flatMap((s) => s.entries); }, - push: (text: string, href: Pathname | null = null) => { - onMount(() => { - const entry = { text, href }; - _state = [..._state, entry]; - return () => { - _state = _state.filter((e) => e.text !== entry.text || e.href !== entry.href); - }; - }); - }, - clear: () => (_state = []), }; + +export function registerBreadcrumbs( + entriesFn: () => Array<{ label: string; href?: Pathname | null }> +): void { + const id = Symbol(); + _slots.push({ id, entries: [] }); + + $effect(() => { + const slot = _slots.find((s) => s.id === id); + if (slot) { + slot.entries = entriesFn().map((e) => ({ label: e.label, href: e.href ?? null })); + } + }); + + onDestroy(() => { + const idx = _slots.findIndex((s) => s.id === id); + if (idx !== -1) _slots.splice(idx, 1); + }); +} diff --git a/src/routes/(app)/admin/+layout.svelte b/src/routes/(app)/admin/+layout.svelte index 802a6fdc..67dbd242 100644 --- a/src/routes/(app)/admin/+layout.svelte +++ b/src/routes/(app)/admin/+layout.svelte @@ -7,9 +7,12 @@ diff --git a/src/routes/(app)/hubs/[hubId=guid]/update/+page.svelte b/src/routes/(app)/hubs/[hubId=guid]/update/+page.svelte index 9ebff57a..505658d3 100644 --- a/src/routes/(app)/hubs/[hubId=guid]/update/+page.svelte +++ b/src/routes/(app)/hubs/[hubId=guid]/update/+page.svelte @@ -72,7 +72,7 @@ import { handleApiError } from '$lib/errorhandling/apiErrorHandling'; import { getConnection } from '$lib/signalr/user.svelte'; import { serializeOtaInstallMessage } from '$lib/signalr/serializers/OtaInstall'; - import { breadcrumbs } from '$lib/state/breadcrumbs-state.svelte'; + import { registerBreadcrumbs } from '$lib/state/breadcrumbs-state.svelte'; import { HubOnlineState, onlineHubs, @@ -197,8 +197,7 @@ $effect(() => fetchOtaLogs(page.params.hubId)); - breadcrumbs.push('Hubs', '/hubs'); - breadcrumbs.push('Update'); + registerBreadcrumbs(() => [{ label: 'Hubs', href: '/hubs' }, { label: hubName ?? 'Update' }]); onMount(refreshOwnHubs); diff --git a/src/routes/(app)/profile/+page.svelte b/src/routes/(app)/profile/+page.svelte index e69de29b..56e96ece 100644 --- a/src/routes/(app)/profile/+page.svelte +++ b/src/routes/(app)/profile/+page.svelte @@ -0,0 +1,10 @@ + + +
+

Profile

+

Your profile page is coming soon.

+
diff --git a/src/routes/(app)/settings/account/+page.svelte b/src/routes/(app)/settings/account/+page.svelte index dd7a9798..6aeb7b62 100644 --- a/src/routes/(app)/settings/account/+page.svelte +++ b/src/routes/(app)/settings/account/+page.svelte @@ -4,8 +4,14 @@ import ChangeEmail from './ChangeEmail.svelte'; import ChangePassword from './ChangePassword.svelte'; import ChangeUsername from './ChangeUsername.svelte'; + import { registerBreadcrumbs } from '$lib/state/breadcrumbs-state.svelte'; import DangerZone from './DangerZone.svelte'; + registerBreadcrumbs(() => [ + { label: 'Settings', href: '/settings/account' }, + { label: 'Account' }, + ]); + let account = $derived(userState.self); diff --git a/src/routes/(app)/settings/api-tokens/+page.svelte b/src/routes/(app)/settings/api-tokens/+page.svelte index f4c93a54..753c6bf5 100644 --- a/src/routes/(app)/settings/api-tokens/+page.svelte +++ b/src/routes/(app)/settings/api-tokens/+page.svelte @@ -22,8 +22,13 @@ import { toast } from 'svelte-sonner'; import DataTableActions from './data-table-actions.svelte'; import TokenCreateDialog from './dialog-token-create.svelte'; + import { registerBreadcrumbs } from '$lib/state/breadcrumbs-state.svelte'; import TokenCreatedDialog from './dialog-token-created.svelte'; + registerBreadcrumbs(() => [ + { label: 'Settings', href: '/settings/account' }, + { label: 'API Tokens' }, + ]); let data = $state([]); let sorting = $state([]); diff --git a/src/routes/(app)/settings/api-tokens/new/+page.svelte b/src/routes/(app)/settings/api-tokens/new/+page.svelte index 02d3fea4..df8ac148 100644 --- a/src/routes/(app)/settings/api-tokens/new/+page.svelte +++ b/src/routes/(app)/settings/api-tokens/new/+page.svelte @@ -10,6 +10,13 @@ import type { QueryParamsType } from './queryParamsType'; import { page } from '$app/state'; import { browser } from '$app/environment'; + import { registerBreadcrumbs } from '$lib/state/breadcrumbs-state.svelte'; + + registerBreadcrumbs(() => [ + { label: 'Settings', href: '/settings/account' }, + { label: 'API Tokens', href: '/settings/api-tokens' }, + { label: 'New' }, + ]); let windowQueryParams = $state< | QueryParamsType diff --git a/src/routes/(app)/settings/connections/+page.svelte b/src/routes/(app)/settings/connections/+page.svelte index 857fa92c..36ca6945 100644 --- a/src/routes/(app)/settings/connections/+page.svelte +++ b/src/routes/(app)/settings/connections/+page.svelte @@ -16,8 +16,14 @@ import { onMount } from 'svelte'; import { toast } from 'svelte-sonner'; import DisconnectDialog from './dialog-oauth-disconnect.svelte'; + import { registerBreadcrumbs } from '$lib/state/breadcrumbs-state.svelte'; import { backendMetadata } from '$lib/state/backend-metadata-state.svelte'; + registerBreadcrumbs(() => [ + { label: 'Settings', href: '/settings/account' }, + { label: 'Connections' }, + ]); + // ---------- state let loading = $state(false); // overall refresh button state let loadingProviders = $state(true); diff --git a/src/routes/(app)/settings/sessions/+page.svelte b/src/routes/(app)/settings/sessions/+page.svelte index 340da8a8..9a5b2ca6 100644 --- a/src/routes/(app)/settings/sessions/+page.svelte +++ b/src/routes/(app)/settings/sessions/+page.svelte @@ -19,8 +19,14 @@ import { handleApiError } from '$lib/errorhandling/apiErrorHandling'; import { onMount } from 'svelte'; import { toast } from 'svelte-sonner'; + import { registerBreadcrumbs } from '$lib/state/breadcrumbs-state.svelte'; import DataTableActions from './data-table-actions.svelte'; + registerBreadcrumbs(() => [ + { label: 'Settings', href: '/settings/account' }, + { label: 'Sessions' }, + ]); + let data = $state([]); let sorting = $state([]); diff --git a/src/routes/(app)/shares/public/+page.svelte b/src/routes/(app)/shares/public/+page.svelte index 10b3755d..4c102983 100644 --- a/src/routes/(app)/shares/public/+page.svelte +++ b/src/routes/(app)/shares/public/+page.svelte @@ -21,8 +21,11 @@ import { handleApiError } from '$lib/errorhandling/apiErrorHandling'; import { onMount } from 'svelte'; import DataTableActions from './data-table-actions.svelte'; + import { registerBreadcrumbs } from '$lib/state/breadcrumbs-state.svelte'; import CreatePublicShareDialog from './dialog-publicshare-create.svelte'; + registerBreadcrumbs(() => [{ label: 'Public Shares' }]); + const columns: ColumnDef[] = [ CreateSortableColumnDef('name', 'Name', RenderCell), CreateSortableColumnDef('createdOn', 'Created at', LocaleDateTimeRenderer), diff --git a/src/routes/(app)/shares/public/[shareId=guid]/edit/+page.svelte b/src/routes/(app)/shares/public/[shareId=guid]/edit/+page.svelte index 92a7af5f..6f76925e 100644 --- a/src/routes/(app)/shares/public/[shareId=guid]/edit/+page.svelte +++ b/src/routes/(app)/shares/public/[shareId=guid]/edit/+page.svelte @@ -1,5 +1,6 @@ {#if breadcrumbs.state.length > 0} - + - {#each breadcrumbs.state as crumb, index (crumb.href)} + {#each breadcrumbs.state as crumb, i (crumb.href || crumb.label)} + {#if i > 0} + + {/if} - {#if index < breadcrumbs.state.length - 1} - - {crumb.text} - - + {#if crumb.href && i < breadcrumbs.state.length - 1} + {crumb.label} {:else} - - {crumb.text} - + {crumb.label} {/if} {/each} diff --git a/src/routes/Header.svelte b/src/routes/Header.svelte index efb10880..efa0b0ad 100644 --- a/src/routes/Header.svelte +++ b/src/routes/Header.svelte @@ -10,6 +10,7 @@ import GithubIcon from '$lib/components/svg/GithubIcon.svelte'; import { Button } from '$lib/components/ui/button'; import * as DropdownMenu from '$lib/components/ui/dropdown-menu'; + import { Separator } from '$lib/components/ui/separator'; import { useSidebar } from '$lib/components/ui/sidebar'; import { userState } from '$lib/state/user-state.svelte'; import { cn } from '$lib/utils'; @@ -28,54 +29,55 @@ {/snippet} -
- - -
-
+
+
+ + + +
+
- + - {#if userState.self} - - - {userState.self.name}'s avatar - - - - - {@render dropdownItem('Profile', '/profile')} - {@render dropdownItem('Settings', '/settings/account')} - {@render dropdownItem('Logout', '/logout')} - - - - {:else} - - - - {/if} + {#if userState.self} + + + {userState.self.name}'s avatar + + + + + {@render dropdownItem('Profile', '/profile')} + {@render dropdownItem('Settings', '/settings/account')} + {@render dropdownItem('Logout', '/logout')} + + + + {:else} + + + + {/if} +
diff --git a/src/routes/shares/public/[shareId=guid]/+page.svelte b/src/routes/shares/public/[shareId=guid]/+page.svelte index 9fcff814..51a12fff 100644 --- a/src/routes/shares/public/[shareId=guid]/+page.svelte +++ b/src/routes/shares/public/[shareId=guid]/+page.svelte @@ -8,6 +8,7 @@ import * as Card from '$lib/components/ui/card/index.js'; import Input from '$lib/components/ui/input/input.svelte'; import { handleApiError } from '$lib/errorhandling/apiErrorHandling'; + import { registerBreadcrumbs } from '$lib/state/breadcrumbs-state.svelte'; import { userState } from '$lib/state/user-state.svelte'; import { onMount } from 'svelte'; import ControlView from './ControlView.svelte'; @@ -18,10 +19,16 @@ ); let details = $state>(getShareDetails()); + let shareData = $state(null); let enterAsGuestClicked = $state(false); let guestName = $state(null); let entered = $state(false); + registerBreadcrumbs(() => [ + { label: 'Public Shares', href: '/shares/public' }, + { label: shareData?.name ?? 'Public Share' }, + ]); + // Fetch share details async function getShareDetails(): Promise { const shareId = page.params.shareId; @@ -30,7 +37,9 @@ } try { - return (await publicShockerSharesApi.publicGetPublicShare(shareId)).data; + const response = (await publicShockerSharesApi.publicGetPublicShare(shareId)).data; + shareData = response; + return response; } catch (error) { handleApiError(error); throw error; diff --git a/src/routes/shares/public/[shareId=guid]/ControlView.svelte b/src/routes/shares/public/[shareId=guid]/ControlView.svelte index 77da0d15..afc77c75 100644 --- a/src/routes/shares/public/[shareId=guid]/ControlView.svelte +++ b/src/routes/shares/public/[shareId=guid]/ControlView.svelte @@ -60,7 +60,7 @@
- {#if userState.self} + {#if userState.self?.id === shareLinkRoot.author.id} {/if}