@riducms/admin 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +31 -0
- package/THIRD_PARTY_NOTICES.md +217 -0
- package/package.json +62 -0
- package/src/app/admin-core-view-route.svelte +89 -0
- package/src/app/admin-layout.svelte +457 -0
- package/src/app/admin-navigation-progress.svelte +204 -0
- package/src/app/admin-page-title.svelte +94 -0
- package/src/app/admin-provider-layer.svelte +26 -0
- package/src/app/admin-router-root.svelte +9 -0
- package/src/app/admin-routes.svelte +116 -0
- package/src/app/deferred-redirect.svelte +23 -0
- package/src/app/loading-boundary.svelte +70 -0
- package/src/app/not-found-route.svelte +29 -0
- package/src/app-meta.ts +2 -0
- package/src/app.css +467 -0
- package/src/app.svelte +88 -0
- package/src/assets/ridu-logo-arch.svg +3 -0
- package/src/assets/ridu-word-logo.svg +7 -0
- package/src/components/brand/ridu-logo.svelte +24 -0
- package/src/components/code-block/code-block.svelte +107 -0
- package/src/components/json-tree/json-tree-node.svelte +77 -0
- package/src/components/json-tree/json-viewer.svelte +113 -0
- package/src/components/ui/banner/banner.svelte +41 -0
- package/src/components/ui/banner/index.ts +5 -0
- package/src/components/ui/bulk-action-bar/bulk-action-bar.svelte +50 -0
- package/src/components/ui/bulk-action-bar/index.ts +1 -0
- package/src/components/ui/button/index.ts +8 -0
- package/src/components/ui/calendar/calendar-caption.svelte +72 -0
- package/src/components/ui/calendar/calendar-cell.svelte +20 -0
- package/src/components/ui/calendar/calendar-day.svelte +27 -0
- package/src/components/ui/calendar/calendar-grid-row.svelte +13 -0
- package/src/components/ui/calendar/calendar-grid.svelte +17 -0
- package/src/components/ui/calendar/calendar-head-cell.svelte +20 -0
- package/src/components/ui/calendar/calendar-header.svelte +20 -0
- package/src/components/ui/calendar/calendar-heading.svelte +17 -0
- package/src/components/ui/calendar/calendar-month-select.svelte +45 -0
- package/src/components/ui/calendar/calendar-month.svelte +16 -0
- package/src/components/ui/calendar/calendar-months.svelte +20 -0
- package/src/components/ui/calendar/calendar-nav.svelte +20 -0
- package/src/components/ui/calendar/calendar-next-button.svelte +30 -0
- package/src/components/ui/calendar/calendar-prev-button.svelte +30 -0
- package/src/components/ui/calendar/calendar-year-select.svelte +44 -0
- package/src/components/ui/calendar/calendar.svelte +118 -0
- package/src/components/ui/calendar/index.ts +1 -0
- package/src/components/ui/checkbox/checkbox.svelte +35 -0
- package/src/components/ui/checkbox/index.ts +1 -0
- package/src/components/ui/confirmation-dialog/confirmation-dialog.svelte +37 -0
- package/src/components/ui/confirmation-dialog/index.ts +1 -0
- package/src/components/ui/date-value-control/date-value-calendar.svelte +37 -0
- package/src/components/ui/date-value-control/date-value-control-loader.svelte +36 -0
- package/src/components/ui/date-value-control/date-value-control.svelte +177 -0
- package/src/components/ui/date-value-control/index.ts +3 -0
- package/src/components/ui/dialog/dialog-close.svelte +11 -0
- package/src/components/ui/dialog/dialog-content.svelte +52 -0
- package/src/components/ui/dialog/dialog-description.svelte +20 -0
- package/src/components/ui/dialog/dialog-footer.svelte +20 -0
- package/src/components/ui/dialog/dialog-header.svelte +20 -0
- package/src/components/ui/dialog/dialog-overlay.svelte +20 -0
- package/src/components/ui/dialog/dialog-title.svelte +17 -0
- package/src/components/ui/dialog/dialog-trigger.svelte +11 -0
- package/src/components/ui/dialog/dialog.svelte +7 -0
- package/src/components/ui/dialog/index.ts +8 -0
- package/src/components/ui/dropdown-menu/dropdown-menu-content.svelte +27 -0
- package/src/components/ui/dropdown-menu/dropdown-menu-item.svelte +20 -0
- package/src/components/ui/dropdown-menu/dropdown-menu-separator.svelte +17 -0
- package/src/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +16 -0
- package/src/components/ui/dropdown-menu/dropdown-menu.svelte +7 -0
- package/src/components/ui/dropdown-menu/index.ts +5 -0
- package/src/components/ui/empty-state/empty-state.svelte +34 -0
- package/src/components/ui/empty-state/index.ts +1 -0
- package/src/components/ui/input/index.ts +1 -0
- package/src/components/ui/kbd/index.ts +1 -0
- package/src/components/ui/kbd/kbd.svelte +24 -0
- package/src/components/ui/pagination/index.ts +1 -0
- package/src/components/ui/pagination/pagination.svelte +88 -0
- package/src/components/ui/popover/index.ts +3 -0
- package/src/components/ui/popover/popover-content.svelte +29 -0
- package/src/components/ui/popover/popover-trigger.svelte +11 -0
- package/src/components/ui/popover/popover.svelte +7 -0
- package/src/components/ui/radio-group/index.ts +3 -0
- package/src/components/ui/radio-group/radio-card-item.svelte +38 -0
- package/src/components/ui/radio-group/radio-group-item.svelte +17 -0
- package/src/components/ui/radio-group/radio-group.svelte +19 -0
- package/src/components/ui/select/index.ts +4 -0
- package/src/components/ui/select/select-content.svelte +38 -0
- package/src/components/ui/select/select-item.svelte +40 -0
- package/src/components/ui/select/select-scroll-down-button.svelte +23 -0
- package/src/components/ui/select/select-scroll-up-button.svelte +23 -0
- package/src/components/ui/select/select-trigger.svelte +29 -0
- package/src/components/ui/select/select.svelte +11 -0
- package/src/components/ui/sheet/index.ts +6 -0
- package/src/components/ui/sheet/sheet-close.svelte +7 -0
- package/src/components/ui/sheet/sheet-content.svelte +77 -0
- package/src/components/ui/sheet/sheet-description.svelte +17 -0
- package/src/components/ui/sheet/sheet-overlay.svelte +36 -0
- package/src/components/ui/sheet/sheet-title.svelte +17 -0
- package/src/components/ui/sheet/sheet-trigger.svelte +7 -0
- package/src/components/ui/sheet/sheet.svelte +7 -0
- package/src/components/ui/skeleton/index.ts +1 -0
- package/src/components/ui/skeleton/skeleton.svelte +31 -0
- package/src/components/ui/slider/index.ts +3 -0
- package/src/components/ui/slider/slider-content.svelte +59 -0
- package/src/components/ui/slider/slider.svelte +35 -0
- package/src/components/ui/sonner/index.ts +1 -0
- package/src/components/ui/sonner/sonner.svelte +55 -0
- package/src/components/ui/status-indicator/index.ts +5 -0
- package/src/components/ui/status-indicator/status-indicator.svelte +54 -0
- package/src/components/ui/table/index.ts +6 -0
- package/src/components/ui/table/table-body.svelte +20 -0
- package/src/components/ui/table/table-cell.svelte +23 -0
- package/src/components/ui/table/table-head.svelte +23 -0
- package/src/components/ui/table/table-header.svelte +20 -0
- package/src/components/ui/table/table-row.svelte +23 -0
- package/src/components/ui/table/table.svelte +22 -0
- package/src/components/ui/tabs/index.ts +8 -0
- package/src/components/ui/tabs/tabs-content.svelte +17 -0
- package/src/components/ui/tabs/tabs-list.svelte +40 -0
- package/src/components/ui/tabs/tabs-trigger.svelte +23 -0
- package/src/components/ui/tabs/tabs.svelte +19 -0
- package/src/components/ui/textarea/index.ts +1 -0
- package/src/components/ui/tooltip/index.ts +4 -0
- package/src/components/ui/tooltip/tooltip-content.svelte +50 -0
- package/src/components/ui/tooltip/tooltip-provider.svelte +7 -0
- package/src/components/ui/tooltip/tooltip-trigger.svelte +7 -0
- package/src/components/ui/tooltip/tooltip.svelte +7 -0
- package/src/core/api/admin-client.ts +45 -0
- package/src/core/forms/field-condition.ts +63 -0
- package/src/core/forms/form-controller.svelte.ts +369 -0
- package/src/core/forms/form-draft-recovery.ts +80 -0
- package/src/core/forms/form-schema.ts +609 -0
- package/src/core/forms/form-validation.ts +750 -0
- package/src/core/i18n/admin-i18n.svelte.ts +364 -0
- package/src/core/i18n/localized-schema.ts +111 -0
- package/src/core/notifications/notification-center.svelte.ts +69 -0
- package/src/core/notifications/validation-toast.svelte +18 -0
- package/src/core/plugins/field-registry.ts +60 -0
- package/src/core/plugins/row-label-registry.ts +62 -0
- package/src/core/preferences/preference-write-queue.ts +107 -0
- package/src/core/routing/admin-paths.ts +137 -0
- package/src/core/runtime/admin-runtime.svelte.ts +625 -0
- package/src/features/auth/account-menu.svelte +159 -0
- package/src/features/auth/account-navigation.svelte +44 -0
- package/src/features/auth/account-route.svelte +395 -0
- package/src/features/auth/account-security-controller.svelte.ts +119 -0
- package/src/features/auth/account-security-route.svelte +460 -0
- package/src/features/auth/auth-brand.svelte +17 -0
- package/src/features/auth/auth-frame.svelte +18 -0
- package/src/features/auth/create-first-user-controller.svelte.ts +220 -0
- package/src/features/auth/create-first-user-route.svelte +174 -0
- package/src/features/auth/forgot-password-route.svelte +78 -0
- package/src/features/auth/login-route.svelte +221 -0
- package/src/features/auth/request-verification-route.svelte +73 -0
- package/src/features/auth/reset-password-route.svelte +107 -0
- package/src/features/auth/verify-email-route.svelte +60 -0
- package/src/features/collections/collection-list-bulk-actions.svelte +143 -0
- package/src/features/collections/collection-list-bulk-editor.svelte +157 -0
- package/src/features/collections/collection-list-controller.svelte.ts +717 -0
- package/src/features/collections/collection-list-filter-popover.svelte +238 -0
- package/src/features/collections/collection-list-preferences-controller.svelte.ts +393 -0
- package/src/features/collections/collection-list-results.svelte +410 -0
- package/src/features/collections/collection-list-route.svelte +951 -0
- package/src/features/collections/collection-list-workspace-toolbar.svelte +409 -0
- package/src/features/collections/collection-table-skeleton.svelte +44 -0
- package/src/features/collections/list-workspace.ts +265 -0
- package/src/features/dashboard/dashboard-route.svelte +141 -0
- package/src/features/development/development-tools.svelte +217 -0
- package/src/features/documents/document-api-view-loader.svelte +31 -0
- package/src/features/documents/document-api-view.svelte +547 -0
- package/src/features/documents/document-controller.svelte.ts +1252 -0
- package/src/features/documents/document-field-partition.ts +18 -0
- package/src/features/documents/document-field-sections.svelte +46 -0
- package/src/features/documents/document-heading.ts +20 -0
- package/src/features/documents/document-route-dirty.ts +43 -0
- package/src/features/documents/document-route-loader.svelte +22 -0
- package/src/features/documents/document-route.svelte +1124 -0
- package/src/features/documents/live-preview-panel.svelte +390 -0
- package/src/features/documents/live-preview.ts +61 -0
- package/src/features/documents/upload-document-preview-loader.svelte +40 -0
- package/src/features/documents/upload-document-preview.svelte +525 -0
- package/src/features/localization/content-locale-switcher.svelte +93 -0
- package/src/features/navigation/admin-breadcrumbs.svelte +124 -0
- package/src/features/navigation/admin-command-menu.svelte +245 -0
- package/src/features/navigation/admin-resource-groups.ts +72 -0
- package/src/features/reference-browser/reference-browser-loader.svelte +15 -0
- package/src/features/reference-browser/reference-browser-workflow.svelte.ts +691 -0
- package/src/features/reference-browser/reference-browser.svelte +844 -0
- package/src/features/uploads/bulk-upload-route.svelte +624 -0
- package/src/features/uploads/bulk-upload.ts +39 -0
- package/src/features/uploads/upload-metadata-fields.svelte +112 -0
- package/src/features/versions/version-diff.ts +72 -0
- package/src/features/versions/version-history-route.svelte +581 -0
- package/src/fields/field-clipboard.ts +132 -0
- package/src/fields/field-layout.svelte +60 -0
- package/src/fields/field-layout.ts +57 -0
- package/src/fields/field-messages.svelte +29 -0
- package/src/fields/field-renderer.svelte +152 -0
- package/src/fields/field-shell.svelte +30 -0
- package/src/fields/field-tab-layout.svelte +99 -0
- package/src/fields/join/join-field.svelte +358 -0
- package/src/fields/localized-field-presentation.ts +17 -0
- package/src/fields/nested/nested-field.svelte +602 -0
- package/src/fields/nested/scoped-field.ts +78 -0
- package/src/fields/nested/sortable-row.svelte +32 -0
- package/src/fields/point/point-field.svelte +52 -0
- package/src/fields/radio/radio-field.svelte +34 -0
- package/src/fields/relationship/relationship-field-controller.svelte.ts +245 -0
- package/src/fields/relationship/relationship-field.svelte +258 -0
- package/src/fields/relationship/relationship-lookup-controller.svelte.ts +129 -0
- package/src/fields/relationship/relationship-query.ts +88 -0
- package/src/fields/relationship/relationship-quick-picker-controller.svelte.ts +198 -0
- package/src/fields/relationship/relationship-quick-picker.svelte +327 -0
- package/src/fields/relationship/relationship-value.ts +47 -0
- package/src/fields/scalar/date-control-value.ts +60 -0
- package/src/fields/scalar/date-value.ts +26 -0
- package/src/fields/scalar/scalar-field.svelte +137 -0
- package/src/fields/select/select-field.svelte +132 -0
- package/src/fields/select/select-value.ts +14 -0
- package/src/fields/text/slug-field.svelte +87 -0
- package/src/fields/text/slug.ts +38 -0
- package/src/fields/text/text-field.svelte +41 -0
- package/src/fields/ui/ui-field.svelte +12 -0
- package/src/fields/virtual/virtual-field.svelte +28 -0
- package/src/index.ts +49 -0
- package/src/main.ts +12 -0
- package/src/vite-env.d.ts +9 -0
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Link, NavLink, Outlet, useLocation } from "@hvniel/svelte-router";
|
|
3
|
+
import type { AdminNavigationComponent } from "@riducms/plugin";
|
|
4
|
+
import { tick } from "svelte";
|
|
5
|
+
import { MediaQuery } from "svelte/reactivity";
|
|
6
|
+
import ChevronDownIcon from "~icons/lucide/chevron-down";
|
|
7
|
+
import ChevronLeftIcon from "~icons/lucide/chevron-left";
|
|
8
|
+
import MenuIcon from "~icons/lucide/menu";
|
|
9
|
+
import SearchIcon from "~icons/lucide/search";
|
|
10
|
+
|
|
11
|
+
import { Button, buttonVariants } from "@admin/components/ui/button";
|
|
12
|
+
import {
|
|
13
|
+
Sheet,
|
|
14
|
+
SheetClose,
|
|
15
|
+
SheetContent,
|
|
16
|
+
SheetDescription,
|
|
17
|
+
SheetTitle,
|
|
18
|
+
SheetTrigger,
|
|
19
|
+
} from "@admin/components/ui/sheet";
|
|
20
|
+
import {
|
|
21
|
+
getPreferenceWriteQueue,
|
|
22
|
+
preferenceOwnerID,
|
|
23
|
+
} from "@admin/core/preferences/preference-write-queue";
|
|
24
|
+
import { collectionPath, globalPath } from "@admin/core/routing/admin-paths";
|
|
25
|
+
import { getAdminRuntime } from "@admin/core/runtime/admin-runtime.svelte";
|
|
26
|
+
import AccountMenu from "@admin/features/auth/account-menu.svelte";
|
|
27
|
+
import AdminBreadcrumbs from "@admin/features/navigation/admin-breadcrumbs.svelte";
|
|
28
|
+
import { groupAdminResources } from "@admin/features/navigation/admin-resource-groups";
|
|
29
|
+
import AdminCommandMenu from "@admin/features/navigation/admin-command-menu.svelte";
|
|
30
|
+
import ContentLocaleSwitcher from "@admin/features/localization/content-locale-switcher.svelte";
|
|
31
|
+
import { cn } from "@riducms/ui";
|
|
32
|
+
|
|
33
|
+
const navigationPreferenceKey = "navigation";
|
|
34
|
+
const runtime = getAdminRuntime();
|
|
35
|
+
const location = $derived(useLocation());
|
|
36
|
+
const routeSegments = $derived(location.pathname.split("/").filter(Boolean));
|
|
37
|
+
const routeOwnsViewport = $derived(
|
|
38
|
+
(routeSegments[0] === "collections" &&
|
|
39
|
+
routeSegments.length > 2 &&
|
|
40
|
+
routeSegments[2] !== "trash" &&
|
|
41
|
+
routeSegments[2] !== "upload" &&
|
|
42
|
+
routeSegments[3] !== "versions") ||
|
|
43
|
+
(routeSegments[0] === "globals" &&
|
|
44
|
+
routeSegments.length > 1 &&
|
|
45
|
+
routeSegments[2] !== "versions")
|
|
46
|
+
);
|
|
47
|
+
const narrowNavigation = new MediaQuery("(max-width: 1023px)");
|
|
48
|
+
const applicationName = $derived(
|
|
49
|
+
runtime.manifest === undefined
|
|
50
|
+
? runtime.i18n.t("general:riduApplication")
|
|
51
|
+
: runtime.i18n.text(
|
|
52
|
+
runtime.manifest.application.name,
|
|
53
|
+
runtime.manifest.application.nameTranslations
|
|
54
|
+
)
|
|
55
|
+
);
|
|
56
|
+
const resourceGroups = $derived(
|
|
57
|
+
groupAdminResources(runtime.visibleCollections, runtime.visibleGlobals, {
|
|
58
|
+
collections: runtime.i18n.t("navigation:collections"),
|
|
59
|
+
globals: runtime.i18n.t("navigation:globals"),
|
|
60
|
+
})
|
|
61
|
+
);
|
|
62
|
+
const navigationPluginRoutes = $derived(
|
|
63
|
+
runtime.pluginRoutes.filter((route) => route.navigation !== undefined)
|
|
64
|
+
);
|
|
65
|
+
let commandOpen = $state(false);
|
|
66
|
+
let desktopNavigationOpen = $state(true);
|
|
67
|
+
let mobileNavigationOpen = $state(false);
|
|
68
|
+
let desktopToggle = $state<HTMLButtonElement | null>(null);
|
|
69
|
+
let preferenceGeneration = 0;
|
|
70
|
+
const navigationReplacement = $derived(
|
|
71
|
+
runtime.navigationComponents.find((component) => component.position === "replace")
|
|
72
|
+
);
|
|
73
|
+
const navigationBefore = $derived(
|
|
74
|
+
runtime.navigationComponents.filter((component) => component.position === "before")
|
|
75
|
+
);
|
|
76
|
+
const navigationBeforeLinks = $derived(
|
|
77
|
+
runtime.navigationComponents.filter((component) => component.position === "beforeLinks")
|
|
78
|
+
);
|
|
79
|
+
const navigationAfterLinks = $derived(
|
|
80
|
+
runtime.navigationComponents.filter((component) => component.position === "afterLinks")
|
|
81
|
+
);
|
|
82
|
+
const navigationAfter = $derived(
|
|
83
|
+
runtime.navigationComponents.filter((component) => component.position === "after")
|
|
84
|
+
);
|
|
85
|
+
const navigationLogo = $derived(
|
|
86
|
+
runtime.brandComponents.find((component) => component.surface === "navigationLogo")
|
|
87
|
+
);
|
|
88
|
+
const shellHeaders = $derived(
|
|
89
|
+
runtime.shellComponents.filter((component) => component.position === "header")
|
|
90
|
+
);
|
|
91
|
+
const shellActions = $derived(
|
|
92
|
+
runtime.shellComponents.filter((component) => component.position === "actions")
|
|
93
|
+
);
|
|
94
|
+
const hasShellHeader = $derived(runtime.manifest !== undefined && shellHeaders.length > 0);
|
|
95
|
+
|
|
96
|
+
$effect(() => {
|
|
97
|
+
const owner = preferenceOwnerID(runtime.session);
|
|
98
|
+
if (owner === "") return;
|
|
99
|
+
const generation = ++preferenceGeneration;
|
|
100
|
+
const request = new AbortController();
|
|
101
|
+
void runtime.client
|
|
102
|
+
.preference<unknown>(navigationPreferenceKey, { signal: request.signal })
|
|
103
|
+
.then((preference) => {
|
|
104
|
+
if (
|
|
105
|
+
request.signal.aborted ||
|
|
106
|
+
generation !== preferenceGeneration ||
|
|
107
|
+
typeof preference !== "object" ||
|
|
108
|
+
preference === null ||
|
|
109
|
+
typeof (preference as { open?: unknown }).open !== "boolean"
|
|
110
|
+
) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
desktopNavigationOpen = (preference as { open: boolean }).open;
|
|
114
|
+
})
|
|
115
|
+
.catch(() => undefined);
|
|
116
|
+
return () => request.abort();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
async function toggleDesktopNavigation() {
|
|
120
|
+
preferenceGeneration += 1;
|
|
121
|
+
const next = !desktopNavigationOpen;
|
|
122
|
+
desktopNavigationOpen = next;
|
|
123
|
+
const owner = preferenceOwnerID(runtime.session);
|
|
124
|
+
if (owner !== "") {
|
|
125
|
+
void getPreferenceWriteQueue()
|
|
126
|
+
.enqueue(
|
|
127
|
+
navigationPreferenceKey,
|
|
128
|
+
owner,
|
|
129
|
+
() => preferenceOwnerID(runtime.session) === owner,
|
|
130
|
+
() => runtime.client.setPreference(navigationPreferenceKey, { open: next })
|
|
131
|
+
)
|
|
132
|
+
.catch(() => undefined);
|
|
133
|
+
}
|
|
134
|
+
await tick();
|
|
135
|
+
desktopToggle?.focus();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function closeMobileNavigation() {
|
|
139
|
+
if (narrowNavigation.current) mobileNavigationOpen = false;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function openCommandMenu() {
|
|
143
|
+
if (narrowNavigation.current) {
|
|
144
|
+
mobileNavigationOpen = false;
|
|
145
|
+
await tick();
|
|
146
|
+
}
|
|
147
|
+
commandOpen = true;
|
|
148
|
+
}
|
|
149
|
+
</script>
|
|
150
|
+
|
|
151
|
+
{#snippet navigationContributions(components: readonly AdminNavigationComponent[])}
|
|
152
|
+
{#if runtime.manifest !== undefined}
|
|
153
|
+
{#each components as component (component.key)}
|
|
154
|
+
<component.component
|
|
155
|
+
manifest={runtime.manifest}
|
|
156
|
+
user={runtime.session?.user}
|
|
157
|
+
defaultView={defaultNavigation}
|
|
158
|
+
i18n={runtime.i18n}
|
|
159
|
+
/>
|
|
160
|
+
{/each}
|
|
161
|
+
{/if}
|
|
162
|
+
{/snippet}
|
|
163
|
+
|
|
164
|
+
{#snippet defaultNavigation()}
|
|
165
|
+
{@render navigationContributions(navigationBefore)}
|
|
166
|
+
|
|
167
|
+
<nav
|
|
168
|
+
aria-label={runtime.i18n.t("navigation:adminNavigation")}
|
|
169
|
+
class="grid flex-1 content-start overflow-y-auto px-5 pb-5"
|
|
170
|
+
>
|
|
171
|
+
{@render navigationContributions(navigationBeforeLinks)}
|
|
172
|
+
|
|
173
|
+
{#each resourceGroups as group (group.key)}
|
|
174
|
+
<details open class="group/resource mb-3">
|
|
175
|
+
<summary
|
|
176
|
+
class="flex cursor-pointer list-none items-center justify-between gap-3 py-1 text-[12.5px] text-foreground-faint transition-colors hover:text-foreground-strong [&::-webkit-details-marker]:hidden"
|
|
177
|
+
>
|
|
178
|
+
<span>{runtime.i18n.text(group.label, group.translations)}</span>
|
|
179
|
+
<ChevronDownIcon
|
|
180
|
+
class="size-2.5 rotate-[-90deg] text-foreground-soft transition-transform group-open/resource:rotate-0"
|
|
181
|
+
aria-hidden="true"
|
|
182
|
+
/>
|
|
183
|
+
</summary>
|
|
184
|
+
<div class="grid">
|
|
185
|
+
{#each group.collections as collection (collection.id)}
|
|
186
|
+
<NavLink
|
|
187
|
+
to={collectionPath(collection.slug)}
|
|
188
|
+
class={({ isActive, isPending }) =>
|
|
189
|
+
cn(
|
|
190
|
+
"relative flex min-h-7 items-center text-[13px] transition-colors after:absolute after:top-1 after:bottom-1 after:-start-5 after:w-0.5",
|
|
191
|
+
isActive
|
|
192
|
+
? "font-medium text-foreground-strong after:bg-foreground-strong"
|
|
193
|
+
: "text-sidebar-foreground hover:text-foreground",
|
|
194
|
+
isPending && "opacity-60"
|
|
195
|
+
)}
|
|
196
|
+
onclick={closeMobileNavigation}
|
|
197
|
+
>
|
|
198
|
+
<span class="truncate">
|
|
199
|
+
{runtime.i18n.text(collection.labels.plural, collection.labels.pluralTranslations)}
|
|
200
|
+
</span>
|
|
201
|
+
</NavLink>
|
|
202
|
+
{/each}
|
|
203
|
+
{#each group.globals as global (global.id)}
|
|
204
|
+
<NavLink
|
|
205
|
+
to={globalPath(global.slug)}
|
|
206
|
+
class={({ isActive, isPending }) =>
|
|
207
|
+
cn(
|
|
208
|
+
"relative flex min-h-7 items-center text-[13px] transition-colors after:absolute after:top-1 after:bottom-1 after:-start-5 after:w-0.5",
|
|
209
|
+
isActive
|
|
210
|
+
? "font-medium text-foreground-strong after:bg-foreground-strong"
|
|
211
|
+
: "text-sidebar-foreground hover:text-foreground",
|
|
212
|
+
isPending && "opacity-60"
|
|
213
|
+
)}
|
|
214
|
+
onclick={closeMobileNavigation}
|
|
215
|
+
>
|
|
216
|
+
<span class="truncate">
|
|
217
|
+
{runtime.i18n.text(global.labels.singular, global.labels.singularTranslations)}
|
|
218
|
+
</span>
|
|
219
|
+
</NavLink>
|
|
220
|
+
{/each}
|
|
221
|
+
</div>
|
|
222
|
+
</details>
|
|
223
|
+
{/each}
|
|
224
|
+
|
|
225
|
+
{#if navigationPluginRoutes.length > 0}
|
|
226
|
+
<details open class="group/resource mb-3">
|
|
227
|
+
<summary
|
|
228
|
+
class="flex cursor-pointer list-none items-center justify-between gap-3 py-1 text-[12.5px] text-foreground-faint transition-colors hover:text-foreground-strong [&::-webkit-details-marker]:hidden"
|
|
229
|
+
>
|
|
230
|
+
<span>{runtime.i18n.t("navigation:plugins")}</span>
|
|
231
|
+
<ChevronDownIcon
|
|
232
|
+
class="size-2.5 rotate-[-90deg] text-foreground-soft transition-transform group-open/resource:rotate-0"
|
|
233
|
+
aria-hidden="true"
|
|
234
|
+
/>
|
|
235
|
+
</summary>
|
|
236
|
+
<div class="grid">
|
|
237
|
+
{#each navigationPluginRoutes as pluginRoute (pluginRoute.path)}
|
|
238
|
+
<NavLink
|
|
239
|
+
to={`/${pluginRoute.path}`}
|
|
240
|
+
class={({ isActive }) =>
|
|
241
|
+
cn(
|
|
242
|
+
"relative flex min-h-7 items-center text-[13px] transition-colors after:absolute after:top-1 after:bottom-1 after:-start-5 after:w-0.5",
|
|
243
|
+
isActive
|
|
244
|
+
? "font-medium text-foreground-strong after:bg-foreground-strong"
|
|
245
|
+
: "text-sidebar-foreground hover:text-foreground"
|
|
246
|
+
)}
|
|
247
|
+
onclick={closeMobileNavigation}
|
|
248
|
+
>
|
|
249
|
+
{pluginRoute.navigation?.labelKey === undefined
|
|
250
|
+
? pluginRoute.navigation?.label
|
|
251
|
+
: runtime.i18n.t(pluginRoute.navigation.labelKey)}
|
|
252
|
+
</NavLink>
|
|
253
|
+
{/each}
|
|
254
|
+
</div>
|
|
255
|
+
</details>
|
|
256
|
+
{/if}
|
|
257
|
+
|
|
258
|
+
{@render navigationContributions(navigationAfterLinks)}
|
|
259
|
+
</nav>
|
|
260
|
+
|
|
261
|
+
{@render navigationContributions(navigationAfter)}
|
|
262
|
+
|
|
263
|
+
{#if runtime.session !== undefined}
|
|
264
|
+
<div class="mt-auto px-5 py-4">
|
|
265
|
+
<AccountMenu compact />
|
|
266
|
+
</div>
|
|
267
|
+
{/if}
|
|
268
|
+
{/snippet}
|
|
269
|
+
|
|
270
|
+
{#snippet navigationSurface(mobile = false)}
|
|
271
|
+
<div class={["flex h-14 shrink-0 items-center gap-2 pe-4", mobile ? "ps-4" : "ps-16"]}>
|
|
272
|
+
{#if mobile}
|
|
273
|
+
<SheetClose
|
|
274
|
+
class={buttonVariants({ variant: "outline", size: "icon-sm" })}
|
|
275
|
+
aria-label={runtime.i18n.t("navigation:closeMenu")}
|
|
276
|
+
>
|
|
277
|
+
<ChevronLeftIcon class="size-4 rtl:rotate-180" aria-hidden="true" />
|
|
278
|
+
</SheetClose>
|
|
279
|
+
{/if}
|
|
280
|
+
|
|
281
|
+
<Link
|
|
282
|
+
class="ms-1 min-w-0 flex-1 truncate text-[13px] font-medium text-foreground"
|
|
283
|
+
to="/"
|
|
284
|
+
title={applicationName}
|
|
285
|
+
aria-label={applicationName}
|
|
286
|
+
onclick={closeMobileNavigation}
|
|
287
|
+
>
|
|
288
|
+
{#if navigationLogo !== undefined && runtime.manifest !== undefined}
|
|
289
|
+
<navigationLogo.component
|
|
290
|
+
manifest={runtime.manifest}
|
|
291
|
+
user={runtime.session?.user}
|
|
292
|
+
surface="navigationLogo"
|
|
293
|
+
i18n={runtime.i18n}
|
|
294
|
+
/>
|
|
295
|
+
{/if}
|
|
296
|
+
</Link>
|
|
297
|
+
|
|
298
|
+
<Button
|
|
299
|
+
variant="ghost"
|
|
300
|
+
size="icon-sm"
|
|
301
|
+
onclick={openCommandMenu}
|
|
302
|
+
aria-label={runtime.i18n.t("navigation:searchAndNavigate")}
|
|
303
|
+
tooltip={runtime.i18n.t("navigation:searchAndNavigateShortcut")}
|
|
304
|
+
>
|
|
305
|
+
<SearchIcon class="size-3.5" aria-hidden="true" />
|
|
306
|
+
</Button>
|
|
307
|
+
</div>
|
|
308
|
+
|
|
309
|
+
{#if runtime.manifest !== undefined && navigationReplacement !== undefined}
|
|
310
|
+
<navigationReplacement.component
|
|
311
|
+
manifest={runtime.manifest}
|
|
312
|
+
user={runtime.session?.user}
|
|
313
|
+
defaultView={defaultNavigation}
|
|
314
|
+
i18n={runtime.i18n}
|
|
315
|
+
/>
|
|
316
|
+
{:else}
|
|
317
|
+
{@render defaultNavigation()}
|
|
318
|
+
{/if}
|
|
319
|
+
{/snippet}
|
|
320
|
+
|
|
321
|
+
{#snippet adminHeaderActions()}
|
|
322
|
+
{#if runtime.manifest !== undefined}
|
|
323
|
+
{#each shellActions as component (component.key)}
|
|
324
|
+
<component.component
|
|
325
|
+
manifest={runtime.manifest}
|
|
326
|
+
user={runtime.session?.user}
|
|
327
|
+
position="actions"
|
|
328
|
+
i18n={runtime.i18n}
|
|
329
|
+
/>
|
|
330
|
+
{/each}
|
|
331
|
+
{/if}
|
|
332
|
+
<ContentLocaleSwitcher />
|
|
333
|
+
{#if runtime.session !== undefined}
|
|
334
|
+
<div class="lg:hidden">
|
|
335
|
+
<AccountMenu compact />
|
|
336
|
+
</div>
|
|
337
|
+
{/if}
|
|
338
|
+
{/snippet}
|
|
339
|
+
|
|
340
|
+
{#if hasShellHeader && runtime.manifest !== undefined}
|
|
341
|
+
<header
|
|
342
|
+
class="flex min-h-5 items-center gap-3 bg-background-surface px-5 text-[12px] sm:px-8 lg:px-15"
|
|
343
|
+
aria-label={runtime.i18n.t("navigation:pluginShellHeader")}
|
|
344
|
+
>
|
|
345
|
+
{#each shellHeaders as component (component.key)}
|
|
346
|
+
<component.component
|
|
347
|
+
manifest={runtime.manifest}
|
|
348
|
+
user={runtime.session?.user}
|
|
349
|
+
position="header"
|
|
350
|
+
i18n={runtime.i18n}
|
|
351
|
+
/>
|
|
352
|
+
{/each}
|
|
353
|
+
</header>
|
|
354
|
+
{/if}
|
|
355
|
+
|
|
356
|
+
<div
|
|
357
|
+
class={[
|
|
358
|
+
"transition-[grid-template-columns] duration-300 ease-out motion-reduce:transition-none md:grid md:overflow-hidden",
|
|
359
|
+
hasShellHeader
|
|
360
|
+
? "min-h-[calc(100vh-1.25rem)] md:h-[calc(100vh-1.25rem)]"
|
|
361
|
+
: "min-h-screen md:h-screen",
|
|
362
|
+
desktopNavigationOpen ? "lg:grid-cols-[275px_minmax(0,1fr)]" : "lg:grid-cols-[0_minmax(0,1fr)]",
|
|
363
|
+
]}
|
|
364
|
+
>
|
|
365
|
+
{#if narrowNavigation.current}
|
|
366
|
+
<Sheet bind:open={mobileNavigationOpen}>
|
|
367
|
+
<SheetTrigger
|
|
368
|
+
class={buttonVariants({
|
|
369
|
+
variant: "outline",
|
|
370
|
+
size: "icon-sm",
|
|
371
|
+
class: hasShellHeader ? "fixed top-7 start-2 z-30" : "fixed top-2.5 start-2 z-30",
|
|
372
|
+
})}
|
|
373
|
+
aria-label={runtime.i18n.t("navigation:openMenu")}
|
|
374
|
+
title={runtime.i18n.t("navigation:openMenu")}
|
|
375
|
+
>
|
|
376
|
+
<MenuIcon class="size-4" aria-hidden="true" />
|
|
377
|
+
</SheetTrigger>
|
|
378
|
+
<SheetContent
|
|
379
|
+
side={runtime.i18n.direction === "rtl" ? "right" : "left"}
|
|
380
|
+
showCloseButton={false}
|
|
381
|
+
class="w-[min(275px,calc(100vw-40px))] border-sidebar-border bg-sidebar p-0 text-sidebar-foreground sm:max-w-none"
|
|
382
|
+
>
|
|
383
|
+
<SheetTitle class="sr-only">
|
|
384
|
+
{runtime.i18n.t("navigation:adminNavigation")}
|
|
385
|
+
</SheetTitle>
|
|
386
|
+
<SheetDescription class="sr-only">
|
|
387
|
+
{runtime.i18n.t("navigation:adminNavigationDescription")}
|
|
388
|
+
</SheetDescription>
|
|
389
|
+
<aside class="flex h-full min-h-0 flex-col">
|
|
390
|
+
{@render navigationSurface(true)}
|
|
391
|
+
</aside>
|
|
392
|
+
</SheetContent>
|
|
393
|
+
</Sheet>
|
|
394
|
+
{:else}
|
|
395
|
+
<div class="relative h-full min-h-0 bg-sidebar text-sidebar-foreground">
|
|
396
|
+
<aside
|
|
397
|
+
id="ridu-admin-navigation"
|
|
398
|
+
inert={!desktopNavigationOpen}
|
|
399
|
+
aria-hidden={!desktopNavigationOpen}
|
|
400
|
+
class={[
|
|
401
|
+
"absolute inset-y-0 start-0 flex w-[275px] min-h-0 flex-col border-e border-sidebar-border bg-sidebar transition-[transform,opacity] duration-300 ease-out motion-reduce:transition-none",
|
|
402
|
+
desktopNavigationOpen
|
|
403
|
+
? "translate-x-0 opacity-100"
|
|
404
|
+
: runtime.i18n.direction === "rtl"
|
|
405
|
+
? "pointer-events-none translate-x-full opacity-0"
|
|
406
|
+
: "pointer-events-none -translate-x-full opacity-0",
|
|
407
|
+
]}
|
|
408
|
+
>
|
|
409
|
+
{@render navigationSurface()}
|
|
410
|
+
</aside>
|
|
411
|
+
</div>
|
|
412
|
+
|
|
413
|
+
<Button
|
|
414
|
+
bind:ref={desktopToggle}
|
|
415
|
+
variant="outline"
|
|
416
|
+
size="icon-sm"
|
|
417
|
+
class={cn(
|
|
418
|
+
"fixed z-40",
|
|
419
|
+
hasShellHeader ? "top-[34px]" : "top-3.5",
|
|
420
|
+
desktopNavigationOpen ? "start-4" : "start-2"
|
|
421
|
+
)}
|
|
422
|
+
onclick={toggleDesktopNavigation}
|
|
423
|
+
aria-label={runtime.i18n.t(
|
|
424
|
+
desktopNavigationOpen ? "navigation:closeMenu" : "navigation:openMenu"
|
|
425
|
+
)}
|
|
426
|
+
aria-controls="ridu-admin-navigation"
|
|
427
|
+
aria-expanded={desktopNavigationOpen}
|
|
428
|
+
title={runtime.i18n.t(desktopNavigationOpen ? "navigation:closeMenu" : "navigation:openMenu")}
|
|
429
|
+
>
|
|
430
|
+
{#if desktopNavigationOpen}
|
|
431
|
+
<ChevronLeftIcon class="size-4 rtl:rotate-180" aria-hidden="true" />
|
|
432
|
+
{:else}
|
|
433
|
+
<MenuIcon class="size-4" aria-hidden="true" />
|
|
434
|
+
{/if}
|
|
435
|
+
</Button>
|
|
436
|
+
{/if}
|
|
437
|
+
|
|
438
|
+
<main
|
|
439
|
+
class={[
|
|
440
|
+
"relative min-w-0 bg-background max-lg:ps-11 md:flex md:h-full md:flex-col",
|
|
441
|
+
routeOwnsViewport ? "md:overflow-hidden" : "md:overflow-y-auto",
|
|
442
|
+
]}
|
|
443
|
+
>
|
|
444
|
+
<header
|
|
445
|
+
class="flex h-14 shrink-0 items-center justify-between gap-4 bg-background px-5 sm:px-8 lg:px-15"
|
|
446
|
+
aria-label={runtime.i18n.t("navigation:adminHeader")}
|
|
447
|
+
>
|
|
448
|
+
<AdminBreadcrumbs />
|
|
449
|
+
<div class="flex shrink-0 items-center gap-2">
|
|
450
|
+
{@render adminHeaderActions()}
|
|
451
|
+
</div>
|
|
452
|
+
</header>
|
|
453
|
+
<Outlet />
|
|
454
|
+
</main>
|
|
455
|
+
</div>
|
|
456
|
+
|
|
457
|
+
<AdminCommandMenu bind:open={commandOpen} collections={runtime.visibleCollections} />
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { useLocation } from "@hvniel/svelte-router";
|
|
3
|
+
import { onMount, tick } from "svelte";
|
|
4
|
+
|
|
5
|
+
import { getAdminRuntime } from "@admin/core/runtime/admin-runtime.svelte";
|
|
6
|
+
|
|
7
|
+
const revealDelayMilliseconds = 160;
|
|
8
|
+
const minimumVisibleMilliseconds = 180;
|
|
9
|
+
const completionMilliseconds = 150;
|
|
10
|
+
const fallbackIntentMilliseconds = 140;
|
|
11
|
+
const runtime = getAdminRuntime();
|
|
12
|
+
const routeLocation = $derived(useLocation());
|
|
13
|
+
const routeKey = $derived(
|
|
14
|
+
`${routeLocation.pathname}${routeLocation.search}${routeLocation.hash}`
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
type ProgressPhase = "idle" | "pending" | "running" | "finishing";
|
|
18
|
+
|
|
19
|
+
let phase = $state<ProgressPhase>("idle");
|
|
20
|
+
let progress = $state(0);
|
|
21
|
+
let observedRouteKey = "";
|
|
22
|
+
let startedAt = 0;
|
|
23
|
+
let generation = 0;
|
|
24
|
+
let revealTimer: number | undefined;
|
|
25
|
+
let trickleTimer: number | undefined;
|
|
26
|
+
let finishTimer: number | undefined;
|
|
27
|
+
let hideTimer: number | undefined;
|
|
28
|
+
let resetTimer: number | undefined;
|
|
29
|
+
let intentTimer: number | undefined;
|
|
30
|
+
|
|
31
|
+
function clearTimer(timer: number | undefined) {
|
|
32
|
+
if (timer !== undefined) window.clearTimeout(timer);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function clearCompletionTimers() {
|
|
36
|
+
clearTimer(finishTimer);
|
|
37
|
+
clearTimer(hideTimer);
|
|
38
|
+
clearTimer(resetTimer);
|
|
39
|
+
finishTimer = undefined;
|
|
40
|
+
hideTimer = undefined;
|
|
41
|
+
resetTimer = undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function scheduleTrickle() {
|
|
45
|
+
clearTimer(trickleTimer);
|
|
46
|
+
trickleTimer = window.setTimeout(() => {
|
|
47
|
+
if (phase !== "running") return;
|
|
48
|
+
progress += (0.92 - progress) * 0.16;
|
|
49
|
+
scheduleTrickle();
|
|
50
|
+
}, 150);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function start() {
|
|
54
|
+
clearCompletionTimers();
|
|
55
|
+
clearTimer(intentTimer);
|
|
56
|
+
intentTimer = undefined;
|
|
57
|
+
|
|
58
|
+
if (phase === "pending") return;
|
|
59
|
+
if (phase === "running") {
|
|
60
|
+
generation += 1;
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
generation += 1;
|
|
65
|
+
const activeGeneration = generation;
|
|
66
|
+
phase = "pending";
|
|
67
|
+
progress = 0;
|
|
68
|
+
clearTimer(revealTimer);
|
|
69
|
+
revealTimer = window.setTimeout(() => {
|
|
70
|
+
if (activeGeneration !== generation || phase !== "pending") return;
|
|
71
|
+
revealTimer = undefined;
|
|
72
|
+
startedAt = performance.now();
|
|
73
|
+
progress = 0.08;
|
|
74
|
+
phase = "running";
|
|
75
|
+
scheduleTrickle();
|
|
76
|
+
}, revealDelayMilliseconds);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function afterDestinationPaint() {
|
|
80
|
+
return new Promise<void>((resolve) => {
|
|
81
|
+
requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function finish() {
|
|
86
|
+
const activeGeneration = generation;
|
|
87
|
+
await tick();
|
|
88
|
+
await afterDestinationPaint();
|
|
89
|
+
if (activeGeneration !== generation || phase === "idle" || phase === "finishing") return;
|
|
90
|
+
if (phase === "pending") {
|
|
91
|
+
clearTimer(revealTimer);
|
|
92
|
+
revealTimer = undefined;
|
|
93
|
+
phase = "idle";
|
|
94
|
+
progress = 0;
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const remaining = Math.max(0, minimumVisibleMilliseconds - (performance.now() - startedAt));
|
|
99
|
+
finishTimer = window.setTimeout(() => {
|
|
100
|
+
if (activeGeneration !== generation || phase !== "running") return;
|
|
101
|
+
clearTimer(trickleTimer);
|
|
102
|
+
trickleTimer = undefined;
|
|
103
|
+
progress = 1;
|
|
104
|
+
phase = "finishing";
|
|
105
|
+
hideTimer = window.setTimeout(() => {
|
|
106
|
+
if (activeGeneration !== generation) return;
|
|
107
|
+
phase = "idle";
|
|
108
|
+
resetTimer = window.setTimeout(() => {
|
|
109
|
+
if (activeGeneration === generation) progress = 0;
|
|
110
|
+
}, completionMilliseconds);
|
|
111
|
+
}, completionMilliseconds);
|
|
112
|
+
}, remaining);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function startIntent() {
|
|
116
|
+
const routeBeforeIntent = routeKey;
|
|
117
|
+
start();
|
|
118
|
+
intentTimer = window.setTimeout(() => {
|
|
119
|
+
intentTimer = undefined;
|
|
120
|
+
if (routeKey === routeBeforeIntent) void finish();
|
|
121
|
+
}, fallbackIntentMilliseconds);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function handleLinkIntent(event: MouseEvent) {
|
|
125
|
+
if (
|
|
126
|
+
event.defaultPrevented ||
|
|
127
|
+
event.button !== 0 ||
|
|
128
|
+
event.metaKey ||
|
|
129
|
+
event.ctrlKey ||
|
|
130
|
+
event.shiftKey ||
|
|
131
|
+
event.altKey ||
|
|
132
|
+
!(event.target instanceof Element)
|
|
133
|
+
) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const anchor = event.target.closest<HTMLAnchorElement>("a[href]");
|
|
138
|
+
if (
|
|
139
|
+
anchor === null ||
|
|
140
|
+
anchor.hasAttribute("download") ||
|
|
141
|
+
(anchor.target !== "" && anchor.target !== "_self")
|
|
142
|
+
) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const destination = new URL(anchor.href, window.location.href);
|
|
147
|
+
if (
|
|
148
|
+
destination.origin !== window.location.origin ||
|
|
149
|
+
`${destination.pathname}${destination.search}${destination.hash}` ===
|
|
150
|
+
`${window.location.pathname}${window.location.search}${window.location.hash}`
|
|
151
|
+
) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
startIntent();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
$effect(() => {
|
|
159
|
+
const nextRouteKey = routeKey;
|
|
160
|
+
if (observedRouteKey === "") {
|
|
161
|
+
observedRouteKey = nextRouteKey;
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (nextRouteKey === observedRouteKey) return;
|
|
165
|
+
|
|
166
|
+
observedRouteKey = nextRouteKey;
|
|
167
|
+
start();
|
|
168
|
+
void finish();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
onMount(() => {
|
|
172
|
+
const handleHistoryIntent = () => startIntent();
|
|
173
|
+
document.addEventListener("click", handleLinkIntent, true);
|
|
174
|
+
window.addEventListener("popstate", handleHistoryIntent);
|
|
175
|
+
|
|
176
|
+
return () => {
|
|
177
|
+
generation += 1;
|
|
178
|
+
document.removeEventListener("click", handleLinkIntent, true);
|
|
179
|
+
window.removeEventListener("popstate", handleHistoryIntent);
|
|
180
|
+
clearTimer(revealTimer);
|
|
181
|
+
clearTimer(trickleTimer);
|
|
182
|
+
clearTimer(intentTimer);
|
|
183
|
+
clearCompletionTimers();
|
|
184
|
+
};
|
|
185
|
+
});
|
|
186
|
+
</script>
|
|
187
|
+
|
|
188
|
+
<div
|
|
189
|
+
class="pointer-events-none fixed inset-x-0 top-0 z-[100] h-0.5 overflow-hidden"
|
|
190
|
+
aria-hidden={phase === "idle" || phase === "pending"}
|
|
191
|
+
>
|
|
192
|
+
<div
|
|
193
|
+
class={[
|
|
194
|
+
"h-full origin-left bg-foreground-strong shadow-[0_0_10px_color-mix(in_srgb,currentColor_55%,transparent)] transition-[transform,opacity] duration-180 ease-out motion-reduce:transition-none rtl:origin-right",
|
|
195
|
+
phase === "idle" || phase === "pending" ? "opacity-0" : "opacity-100",
|
|
196
|
+
]}
|
|
197
|
+
style:transform={`scaleX(${progress})`}
|
|
198
|
+
role="progressbar"
|
|
199
|
+
aria-label={runtime.i18n.t("navigation:loadingPage")}
|
|
200
|
+
aria-valuemin="0"
|
|
201
|
+
aria-valuemax="100"
|
|
202
|
+
aria-valuenow={Math.round(progress * 100)}
|
|
203
|
+
></div>
|
|
204
|
+
</div>
|