- Updated the `isNavigating` derived variable to use a boolean conversion instead of a strict null check, preventing the navigation indicator from hanging when `navigating` is undefined. - Added a comment to clarify the reason for this change, enhancing code readability and maintainability.
74 lines
2.3 KiB
Svelte
74 lines
2.3 KiB
Svelte
<script lang="ts">
|
|
import './layout.css';
|
|
import { page, navigating } from '$app/state';
|
|
import { onMount } from 'svelte';
|
|
import { ModeWatcher } from 'mode-watcher';
|
|
import favicon from '$lib/assets/favicon.svg';
|
|
import AppSidebar from '$lib/components/app-sidebar.svelte';
|
|
import { fetchAggSummary } from '$lib/api/client.js';
|
|
import * as Sidebar from '$lib/components/ui/sidebar/index.js';
|
|
import * as Tooltip from '$lib/components/ui/tooltip/index.js';
|
|
import { Toaster } from '$lib/components/ui/sonner/index.js';
|
|
import SiteHeader from '$lib/components/site-header.svelte';
|
|
import { panelTitleFromPath } from '$lib/page-title.js';
|
|
|
|
let { children } = $props();
|
|
|
|
const docTitle = $derived(`${panelTitleFromPath(page.url.pathname)} · Telemt Panel`);
|
|
/** Нельзя использовать `!== null`: при `navigating === undefined` выражение истинно и полоска «зависает». */
|
|
const isNavigating = $derived(!!navigating);
|
|
|
|
let serverAliases = $state<string[]>([]);
|
|
|
|
const serverMatch = $derived.by(() => {
|
|
const m = page.url.pathname.match(/^\/servers\/([^/]+)/);
|
|
return m ? m[1] : '';
|
|
});
|
|
const serverPath = $derived(serverMatch ? `/servers/${serverMatch}` : '');
|
|
|
|
onMount(() => {
|
|
let cancelled = false;
|
|
fetchAggSummary({ top_n: 10 })
|
|
.then((env) => {
|
|
if (cancelled) return;
|
|
const set = new Set<string>();
|
|
for (const s of env.data.servers ?? []) {
|
|
if (s.alias) set.add(s.alias);
|
|
}
|
|
serverAliases = [...set].sort();
|
|
})
|
|
.catch(() => {
|
|
/* sidebar без списка — страницы сами покажут ошибку */
|
|
});
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
});
|
|
</script>
|
|
|
|
<svelte:head>
|
|
<link rel="icon" href={favicon} />
|
|
<title>{docTitle}</title>
|
|
</svelte:head>
|
|
|
|
<ModeWatcher />
|
|
<Tooltip.Provider>
|
|
<Sidebar.Provider>
|
|
<AppSidebar {serverAliases} {serverPath} />
|
|
<Sidebar.Inset>
|
|
{#if isNavigating}
|
|
<div class="h-0.5 w-full overflow-hidden bg-transparent">
|
|
<div class="h-full w-1/3 animate-pulse rounded-full bg-primary"></div>
|
|
</div>
|
|
{/if}
|
|
<header class="flex h-14 w-full shrink-0 items-center gap-2 border-b border-border px-4">
|
|
<SiteHeader />
|
|
</header>
|
|
<div class="flex flex-1 flex-col p-4">
|
|
{@render children()}
|
|
</div>
|
|
</Sidebar.Inset>
|
|
</Sidebar.Provider>
|
|
<Toaster richColors closeButton position="top-right" />
|
|
</Tooltip.Provider>
|