refactor(web): routes (#25313)

This commit is contained in:
Jason Rasmussen
2026-01-16 16:11:09 -05:00
committed by GitHub
parent 07675a2de4
commit 8196bd9bbd
87 changed files with 425 additions and 383 deletions
+2 -2
View File
@@ -4,9 +4,9 @@
import Albums from '$lib/components/album-page/albums-list.svelte';
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
import { AppRoute } from '$lib/constants';
import GroupTab from '$lib/elements/GroupTab.svelte';
import SearchBar from '$lib/elements/SearchBar.svelte';
import { Route } from '$lib/route';
import { AlbumFilter, albumViewSettings } from '$lib/stores/preferences.store';
import { createAlbumAndRedirect } from '$lib/utils/album-utils';
import { t } from 'svelte-i18n';
@@ -22,7 +22,7 @@
let albumGroups: string[] = $state([]);
</script>
<UserPageLayout title={data.meta.title} use={[[scrollMemory, { routeStartsWith: AppRoute.ALBUMS }]]}>
<UserPageLayout title={data.meta.title} use={[[scrollMemory, { routeStartsWith: Route.albums() }]]}>
{#snippet buttons()}
<div class="flex place-items-center gap-2">
<AlbumsControls {albumGroups} bind:searchQuery />
@@ -29,7 +29,7 @@
import TagAction from '$lib/components/timeline/actions/TagAction.svelte';
import AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte';
import Timeline from '$lib/components/timeline/Timeline.svelte';
import { AlbumPageViewMode, AppRoute } from '$lib/constants';
import { AlbumPageViewMode } from '$lib/constants';
import { activityManager } from '$lib/managers/activity-manager.svelte';
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
@@ -37,6 +37,7 @@
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
import AlbumOptionsModal from '$lib/modals/AlbumOptionsModal.svelte';
import SharedLinkCreateModal from '$lib/modals/SharedLinkCreateModal.svelte';
import { Route } from '$lib/route';
import {
getAlbumActions,
getAlbumAssetsActions,
@@ -90,7 +91,7 @@
let oldAt: AssetGridRouteSearchParams | null | undefined = $state();
let backUrl: string = $state(AppRoute.ALBUMS);
let backUrl: string = $state(Route.albums());
let viewMode: AlbumPageViewMode = $state(AlbumPageViewMode.VIEW);
let timelineManager = $state<TimelineManager>() as TimelineManager;
@@ -108,13 +109,13 @@
}
if (isAlbumsRoute(route) || isPeopleRoute(route)) {
url = AppRoute.ALBUMS;
url = Route.albums();
}
backUrl = url || AppRoute.ALBUMS;
backUrl = url || Route.albums();
if (backUrl === AppRoute.SHARED_LINKS) {
backUrl = history.state?.backUrl || AppRoute.ALBUMS;
if (backUrl === Route.sharedLinks()) {
backUrl = history.state?.backUrl || Route.albums();
}
});
@@ -347,7 +348,7 @@
/>
<CommandPaletteDefaultProvider name={$t('album')} actions={[AddAssets, Upload]} />
<div class="flex overflow-hidden" use:scrollMemoryClearer={{ routeStartsWith: AppRoute.ALBUMS }}>
<div class="flex overflow-hidden" use:scrollMemoryClearer={{ routeStartsWith: Route.albums() }}>
<div class="relative w-full shrink">
<main class="relative h-dvh overflow-hidden px-2 md:px-6 max-md:pt-(--navbar-height-md) pt-(--navbar-height)">
<Timeline
+2 -2
View File
@@ -4,7 +4,7 @@
import LicenseActivationSuccess from '$lib/components/shared-components/purchasing/purchase-activation-success.svelte';
import LicenseContent from '$lib/components/shared-components/purchasing/purchase-content.svelte';
import SupporterBadge from '$lib/components/shared-components/side-bar/supporter-badge.svelte';
import { AppRoute } from '$lib/constants';
import { Route } from '$lib/route';
import { purchaseStore } from '$lib/stores/purchase.store';
import { Alert, Container, Stack } from '@immich/ui';
import { mdiAlertCircleOutline } from '@mdi/js';
@@ -32,7 +32,7 @@
{/if}
{#if showLicenseActivated || data.isActivated === true}
<LicenseActivationSuccess onDone={() => goto(AppRoute.PHOTOS, { replaceState: false })} />
<LicenseActivationSuccess onDone={() => goto(Route.photos(), { replaceState: false })} />
{:else}
<LicenseContent
onActivate={() => {
+3 -7
View File
@@ -4,9 +4,9 @@
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
import SingleGridRow from '$lib/components/shared-components/single-grid-row.svelte';
import { AppRoute } from '$lib/constants';
import { Route } from '$lib/route';
import { websocketEvents } from '$lib/stores/websocket';
import { getAssetThumbnailUrl, getPeopleThumbnailUrl } from '$lib/utils';
import { getMetadataSearchQuery } from '$lib/utils/metadata-search';
import { AssetMediaSize, type SearchExploreResponseDto } from '@immich/sdk';
import { Icon } from '@immich/ui';
import { mdiHeart } from '@mdi/js';
@@ -81,7 +81,7 @@
<div class="flex justify-between">
<p class="mb-4 font-medium dark:text-immich-dark-fg">{$t('places')}</p>
<a
href={AppRoute.PLACES}
href={Route.places()}
class="pe-4 text-sm font-medium hover:text-immich-primary dark:text-immich-dark-fg dark:hover:text-immich-dark-primary"
draggable="false">{$t('view_all')}</a
>
@@ -89,11 +89,7 @@
<SingleGridRow class="grid grid-flow-col md:grid-auto-fill-36 grid-auto-fill-28 gap-x-4">
{#snippet children({ itemCount })}
{#each places.slice(0, itemCount) as item (item.data.id)}
<a
class="relative"
href="{AppRoute.SEARCH}?{getMetadataSearchQuery({ city: item.value })}"
draggable="false"
>
<a class="relative" href={Route.search({ city: item.value })} draggable="false">
<div class="flex justify-center overflow-hidden rounded-xl brightness-75 filter">
<img
src={getAssetThumbnailUrl({ id: item.data.id, size: AssetMediaSize.Thumbnail })}
@@ -11,8 +11,9 @@
import SetVisibilityAction from '$lib/components/timeline/actions/SetVisibilityAction.svelte';
import AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte';
import Timeline from '$lib/components/timeline/Timeline.svelte';
import { AppRoute, AssetAction } from '$lib/constants';
import { AssetAction } from '$lib/constants';
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import { Route } from '$lib/route';
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { AssetVisibility, lockAuthSession } from '@immich/sdk';
import { Button } from '@immich/ui';
@@ -45,7 +46,7 @@
const handleLock = async () => {
await lockAuthSession();
await goto(AppRoute.PHOTOS);
await goto(Route.photos());
};
</script>
@@ -1,4 +1,4 @@
import { AppRoute } from '$lib/constants';
import { Route } from '$lib/route';
import { authenticate } from '$lib/utils/auth';
import { getFormatter } from '$lib/utils/i18n';
import { getAuthStatus } from '@immich/sdk';
@@ -10,7 +10,7 @@ export const load = (async ({ url }) => {
const { isElevated, pinCode } = await getAuthStatus();
if (!isElevated || !pinCode) {
redirect(307, `${AppRoute.AUTH_PIN_PROMPT}?continue=${encodeURIComponent(url.pathname + url.search)}`);
redirect(307, Route.pinPrompt({ continue: url.pathname + url.search }));
}
const $t = await getFormatter();
@@ -2,10 +2,11 @@
import { goto } from '$app/navigation';
import type { AssetCursor } from '$lib/components/asset-viewer/asset-viewer.svelte';
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
import { AppRoute, timeToLoadTheMap } from '$lib/constants';
import { timeToLoadTheMap } from '$lib/constants';
import Portal from '$lib/elements/Portal.svelte';
import { authManager } from '$lib/managers/auth-manager.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { Route } from '$lib/route';
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import { handlePromiseError } from '$lib/utils';
import { delay } from '$lib/utils/asset-utils';
@@ -30,7 +31,7 @@
});
if (!featureFlagsManager.value.map) {
handlePromiseError(goto(AppRoute.PHOTOS));
handlePromiseError(goto(Route.photos()));
}
async function onViewAssets(assetIds: string[]) {
@@ -7,7 +7,7 @@
import DownloadAction from '$lib/components/timeline/actions/DownloadAction.svelte';
import AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte';
import Timeline from '$lib/components/timeline/Timeline.svelte';
import { AppRoute } from '$lib/constants';
import { Route } from '$lib/route';
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { AssetVisibility } from '@immich/sdk';
import { mdiArrowLeft, mdiPlus } from '@mdi/js';
@@ -53,7 +53,7 @@
<DownloadAction />
</AssetSelectControlBar>
{:else}
<ControlAppBar showBackButton backIcon={mdiArrowLeft} onClose={() => goto(AppRoute.SHARING)}>
<ControlAppBar showBackButton backIcon={mdiArrowLeft} onClose={() => goto(Route.sharing())}>
{#snippet leading()}
<p class="whitespace-nowrap text-immich-fg dark:text-immich-dark-fg">
{data.partner.name}'s photos
@@ -31,6 +31,7 @@
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
import PersonMergeSuggestionModal from '$lib/modals/PersonMergeSuggestionModal.svelte';
import { Route } from '$lib/route';
import { getPersonActions } from '$lib/services/person.service';
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
@@ -73,7 +74,7 @@
let viewMode: PersonPageViewMode = $state(PersonPageViewMode.VIEW_ASSETS);
let isEditingName = $state(false);
let previousRoute: string = $state(AppRoute.EXPLORE);
let previousRoute = $state<string>(Route.explore());
let personMerge1: PersonResponseDto | undefined = $state();
let personMerge2: PersonResponseDto | undefined = $state();
let potentialMergePeople: PersonResponseDto[] = $state([]);
@@ -20,8 +20,9 @@
import TagAction from '$lib/components/timeline/actions/TagAction.svelte';
import AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte';
import Timeline from '$lib/components/timeline/Timeline.svelte';
import { AppRoute, AssetAction, QueryParameter } from '$lib/constants';
import { AssetAction } from '$lib/constants';
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import { Route } from '$lib/route';
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import { isFaceEditMode } from '$lib/stores/face-edit.svelte';
@@ -95,7 +96,7 @@
memoryStore.memories.map((memory) => ({
id: memory.id,
title: $memoryLaneTitle(memory),
href: `${AppRoute.MEMORY}?${QueryParameter.ID}=${memory.assets[0].id}`,
href: Route.memories({ id: memory.assets[0].id }),
alt: $t('memory_lane_title', { values: { title: $getAltText(toTimelineAsset(memory.assets[0])) } }),
src: getAssetThumbnailUrl(memory.assets[0].id),
})),
@@ -20,9 +20,10 @@
import SetVisibilityAction from '$lib/components/timeline/actions/SetVisibilityAction.svelte';
import TagAction from '$lib/components/timeline/actions/TagAction.svelte';
import AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte';
import { AppRoute, QueryParameter } from '$lib/constants';
import { QueryParameter } from '$lib/constants';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import type { Viewport } from '$lib/managers/timeline-manager/types';
import { Route } from '$lib/route';
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import { lang, locale } from '$lib/stores/preferences.store';
@@ -55,7 +56,7 @@
// The GalleryViewer pushes it's own history state, which causes weird
// behavior for history.back(). To prevent that we store the previous page
// manually and navigate back to that.
let previousRoute = $state(AppRoute.EXPLORE as string);
let previousRoute = $state<string>(Route.explore());
let nextPage = $state(1);
let searchResultAlbums: AlbumResponseDto[] = $state([]);
@@ -108,11 +109,11 @@
const route = from?.route?.id;
if (isPeopleRoute(route)) {
previousRoute = AppRoute.PHOTOS;
previousRoute = Route.photos();
}
if (isAlbumsRoute(route)) {
previousRoute = AppRoute.EXPLORE;
previousRoute = Route.explore();
}
tick()
@@ -4,8 +4,9 @@
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
import OnEvents from '$lib/components/OnEvents.svelte';
import SharedLinkCard from '$lib/components/sharedlinks-page/SharedLinkCard.svelte';
import { AppRoute } from '$lib/constants';
import { type SharedLinkTab } from '$lib/constants';
import GroupTab from '$lib/elements/GroupTab.svelte';
import { Route } from '$lib/route';
import { getAllSharedLinks, SharedLinkType, type SharedLinkResponseDto } from '@immich/sdk';
import { Container } from '@immich/ui';
import { onMount, type Snippet } from 'svelte';
@@ -29,9 +30,7 @@
await refresh();
});
type Filter = 'all' | 'album' | 'individual';
const filterMap: Record<Filter, string> = {
const filterMap: Record<SharedLinkTab, string> = {
all: $t('all'),
album: $t('albums'),
individual: $t('individual_shares'),
@@ -46,9 +45,6 @@
};
let selectedTab = $derived(getActiveTab(page.url));
const handleSelectTab = async (value: string) => {
await goto(`${AppRoute.SHARED_LINKS}?filter=${value}`);
};
let filteredSharedLinks = $derived(
sharedLinks.filter(
@@ -76,7 +72,13 @@
<UserPageLayout title={data.meta.title}>
{#snippet buttons()}
<div class="hidden xl:block h-10">
<GroupTab label={$t('show_shared_links')} {filters} {labels} selected={selectedTab} onSelect={handleSelectTab} />
<GroupTab
label={$t('show_shared_links')}
{filters}
{labels}
selected={selectedTab}
onSelect={(value) => goto(Route.sharedLinks({ filter: value as SharedLinkTab }))}
/>
</div>
{/snippet}
@@ -1,4 +1,5 @@
import { AppRoute, UUID_REGEX } from '$lib/constants';
import { UUID_REGEX } from '$lib/constants';
import { Route } from '$lib/route';
import { authenticate } from '$lib/utils/auth';
import { getFormatter } from '$lib/utils/i18n';
import { getAllSharedLinks } from '@immich/sdk';
@@ -9,12 +10,12 @@ export const load = (async ({ params, url }) => {
await authenticate(url);
if (!UUID_REGEX.test(params.id)) {
redirect(307, AppRoute.SHARED_LINKS);
redirect(307, Route.sharedLinks());
}
const [sharedLink] = await getAllSharedLinks({ id: params.id });
if (!sharedLink) {
redirect(307, AppRoute.SHARED_LINKS);
redirect(307, Route.sharedLinks());
}
const $t = await getFormatter();
@@ -1,7 +1,7 @@
<script lang="ts">
import { goto } from '$app/navigation';
import SharedLinkExpiration from '$lib/components/SharedLinkExpiration.svelte';
import { AppRoute } from '$lib/constants';
import { Route } from '$lib/route';
import { handleUpdateSharedLink } from '$lib/services/shared-link.service';
import { SharedLinkType } from '@immich/sdk';
import { Field, FormModal, Input, PasswordInput, Switch, Text } from '@immich/ui';
@@ -27,7 +27,7 @@
let expiresAt = $state(sharedLink.expiresAt);
const onClose = async () => {
await goto(`${AppRoute.SHARED_LINKS}`);
await goto(Route.sharedLinks());
};
const onSubmit = async () => {
+3 -3
View File
@@ -4,7 +4,7 @@
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
import UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
import { AppRoute } from '$lib/constants';
import { Route } from '$lib/route';
import {
AlbumFilter,
AlbumGroupBy,
@@ -48,7 +48,7 @@
>
<Text class="hidden md:block">{$t('create_album')}</Text>
</Button>
<Button leadingIcon={mdiLink} href={AppRoute.SHARED_LINKS} size="small" variant="ghost" color="secondary">
<Button leadingIcon={mdiLink} href={Route.sharedLinks()} size="small" variant="ghost" color="secondary">
<Text class="hidden md:block">{$t('shared_links')}</Text>
</Button>
</HStack>
@@ -64,7 +64,7 @@
<div class="flex flex-row flex-wrap gap-4">
{#each data.partners as partner (partner.id)}
<a
href="{AppRoute.PARTNERS}/{partner.id}"
href={Route.viewPartner(partner)}
class="flex gap-4 rounded-lg px-5 py-4 transition-all hover:bg-gray-200 dark:hover:bg-gray-700"
>
<UserAvatar user={partner} size="lg" />
@@ -1,7 +1,5 @@
import { AppRoute } from '$lib/constants';
import { Route } from '$lib/route';
import { redirect } from '@sveltejs/kit';
import type { PageLoad } from './$types';
export const load = (() => {
redirect(307, AppRoute.SHARED_LINKS);
}) satisfies PageLoad;
export const load = (() => redirect(307, Route.sharedLinks())) satisfies PageLoad;
@@ -8,10 +8,10 @@
import SelectAllAssets from '$lib/components/timeline/actions/SelectAllAction.svelte';
import AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte';
import Timeline from '$lib/components/timeline/Timeline.svelte';
import { AppRoute } from '$lib/constants';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import { Route } from '$lib/route';
import { getTrashActions } from '$lib/services/trash.service';
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { handlePromiseError } from '$lib/utils';
@@ -30,7 +30,7 @@
const assetInteraction = new AssetInteraction();
if (!featureFlagsManager.value.trash) {
handlePromiseError(goto(AppRoute.PHOTOS));
handlePromiseError(goto(Route.photos()));
}
const handleEscape = () => {
@@ -4,10 +4,10 @@
import { shortcuts } from '$lib/actions/shortcut';
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
import DuplicatesCompareControl from '$lib/components/utilities-page/duplicates/duplicates-compare-control.svelte';
import { AppRoute } from '$lib/constants';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import DuplicatesInformationModal from '$lib/modals/DuplicatesInformationModal.svelte';
import ShortcutsModal from '$lib/modals/ShortcutsModal.svelte';
import { Route } from '$lib/route';
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import { locale } from '$lib/stores/preferences.store';
import { stackAssets } from '$lib/utils/asset-utils';
@@ -107,7 +107,7 @@
duplicates = duplicates.filter((duplicate) => duplicate.duplicateId !== duplicateId);
deletedNotification(trashIds.length);
await correctDuplicatesIndexAndGo(duplicatesIndex);
await navigateToIndex(duplicatesIndex);
},
trashIds.length > 0 && !featureFlagsManager.value.trash ? $t('delete_duplicates_confirmation') : undefined,
trashIds.length > 0 && !featureFlagsManager.value.trash ? $t('permanently_delete') : undefined,
@@ -119,7 +119,7 @@
const duplicateAssetIds = assets.map((asset) => asset.id);
await updateAssets({ assetBulkUpdateDto: { ids: duplicateAssetIds, duplicateId: null } });
duplicates = duplicates.filter((duplicate) => duplicate.duplicateId !== duplicateId);
await correctDuplicatesIndexAndGo(duplicatesIndex);
await navigateToIndex(duplicatesIndex);
};
const handleDeduplicateAll = async () => {
@@ -152,7 +152,7 @@
deletedNotification(idsToDelete.length);
page.url.searchParams.delete('index');
await goto(`${AppRoute.DUPLICATES}`);
await goto(Route.duplicatesUtility());
},
prompt,
confirmText,
@@ -169,41 +169,32 @@
toastManager.success($t('resolved_all_duplicates'));
page.url.searchParams.delete('index');
await goto(`${AppRoute.DUPLICATES}`);
await goto(Route.duplicatesUtility());
},
$t('bulk_keep_duplicates_confirmation', { values: { count: ids.length } }),
$t('confirm'),
);
};
const handleFirst = async () => {
await correctDuplicatesIndexAndGo(0);
};
const handlePrevious = async () => {
await correctDuplicatesIndexAndGo(Math.max(duplicatesIndex - 1, 0));
};
const handleFirst = () => navigateToIndex(0);
const handlePrevious = () => navigateToIndex(Math.max(duplicatesIndex - 1, 0));
const handlePreviousShortcut = async () => {
if ($showAssetViewer) {
return;
}
await handlePrevious();
};
const handleNext = async () => {
await correctDuplicatesIndexAndGo(Math.min(duplicatesIndex + 1, duplicates.length - 1));
};
const handleNext = async () => navigateToIndex(Math.min(duplicatesIndex + 1, duplicates.length - 1));
const handleNextShortcut = async () => {
if ($showAssetViewer) {
return;
}
await handleNext();
};
const handleLast = async () => {
await correctDuplicatesIndexAndGo(duplicates.length - 1);
};
const correctDuplicatesIndexAndGo = async (index: number) => {
page.url.searchParams.set('index', correctDuplicatesIndex(index).toString());
await goto(`${AppRoute.DUPLICATES}?${page.url.searchParams.toString()}`);
};
const handleLast = () => navigateToIndex(duplicates.length - 1);
const navigateToIndex = async (index: number) =>
goto(Route.duplicatesUtility({ index: correctDuplicatesIndex(index) }));
</script>
<svelte:document
@@ -1,8 +1,5 @@
import { AppRoute } from '$lib/constants';
import { Route } from '$lib/route';
import { redirect } from '@sveltejs/kit';
import type { PageLoad } from './$types';
export const load = (({ params }) => {
const photoId = params.photoId;
return redirect(307, `${AppRoute.PHOTOS}/${photoId}`);
}) satisfies PageLoad;
export const load = (({ params }) => redirect(307, Route.viewAsset({ id: params.photoId }))) satisfies PageLoad;
@@ -7,8 +7,8 @@
import WorkflowJsonEditor from '$lib/components/workflows/WorkflowJsonEditor.svelte';
import WorkflowSummarySidebar from '$lib/components/workflows/WorkflowSummary.svelte';
import WorkflowTriggerCard from '$lib/components/workflows/WorkflowTriggerCard.svelte';
import { AppRoute } from '$lib/constants';
import AddWorkflowStepModal from '$lib/modals/AddWorkflowStepModal.svelte';
import { Route } from '$lib/route';
import {
buildWorkflowPayload,
getActionsByContext,
@@ -580,7 +580,7 @@
<WorkflowSummarySidebar trigger={selectedTrigger} filters={selectedFilters} actions={selectedActions} />
</main>
<ControlAppBar onClose={() => goto(AppRoute.WORKFLOWS)} backIcon={mdiArrowLeft} tailwindClasses="fixed! top-0! w-full">
<ControlAppBar onClose={() => goto(Route.workflows())} backIcon={mdiArrowLeft} tailwindClasses="fixed! top-0! w-full">
{#snippet leading()}
<Text>{data.meta.title}</Text>
{/snippet}