feat: implemented storage management
fix: Enabled fallback secrets feat: Made header stats actually work feat: Made steam deck keyboard auto open for some inputs fix: Made keybaord also work with shortcuts (no tooltips yet)
This commit is contained in:
parent
62f16cbcc1
commit
e4df8fb9fb
55 changed files with 1675 additions and 398 deletions
|
|
@ -1,8 +1,9 @@
|
|||
import { createFileRoute } from '@tanstack/react-router';
|
||||
import { useSessionStorage } from 'usehooks-ts';
|
||||
import { CollectionsDetail } from '../components/CollectionsDetail';
|
||||
import { getRomsApiRomsGetOptions } from '../../clients/romm/@tanstack/react-query.gen';
|
||||
import { getCollectionApiCollectionsIdGetOptions, getRomsApiRomsGetOptions } from '../../clients/romm/@tanstack/react-query.gen';
|
||||
import { DefaultRommStaleTime } from '../../shared/constants';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
export const Route = createFileRoute('/collection/$id')({
|
||||
component: RouteComponent,
|
||||
|
|
@ -15,12 +16,13 @@ export const Route = createFileRoute('/collection/$id')({
|
|||
function RouteComponent ()
|
||||
{
|
||||
const { id } = Route.useParams();
|
||||
const { data: collection } = useQuery({ ...getCollectionApiCollectionsIdGetOptions({ path: { id: Number(id) } }) });
|
||||
const [, setBackground] = useSessionStorage<string | undefined>(
|
||||
"home-background",
|
||||
undefined,
|
||||
);
|
||||
|
||||
return (
|
||||
<CollectionsDetail setBackground={setBackground} filters={{ collectionId: Number(id) }} />
|
||||
<CollectionsDetail setBackground={setBackground} title={<div className="divider font-semibold text-2xl">{collection?.name}</div>} filters={{ collection_id: Number(id) }} />
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -205,7 +205,15 @@ function MainActions (data: { game: FrontEndGameTypeDetailed; })
|
|||
mutationFn: async () =>
|
||||
{
|
||||
const { error } = await rommApi.api.romm.game({ source: data.game.id.source })({ id: data.game.id.id }).play.post();
|
||||
if (error) throw error;
|
||||
if (error)
|
||||
{
|
||||
if (error.value.message)
|
||||
{
|
||||
toast.error(error.value.message);
|
||||
}
|
||||
|
||||
throw error;
|
||||
};
|
||||
}
|
||||
});
|
||||
const [progress, setProgress] = useState<number | undefined>(undefined);
|
||||
|
|
|
|||
|
|
@ -13,23 +13,16 @@ import
|
|||
import
|
||||
{
|
||||
createFileRoute,
|
||||
useLocation,
|
||||
useNavigate,
|
||||
} from "@tanstack/react-router";
|
||||
import { useMutation, useSuspenseQuery } from "@tanstack/react-query";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import
|
||||
{
|
||||
FocusContext,
|
||||
useFocusable,
|
||||
} from "@noriginmedia/norigin-spatial-navigation";
|
||||
import classNames from "classnames";
|
||||
import { DefaultRommStaleTime, RPC_URL } from "../../shared/constants";
|
||||
import { useEventListener } from "usehooks-ts";
|
||||
import
|
||||
{
|
||||
getCollectionsApiCollectionsGetOptions,
|
||||
} from "../../clients/romm/@tanstack/react-query.gen";
|
||||
import { CardList, GameMetaExtra } from "../components/CardList";
|
||||
import { HeaderUI } from "../components/Header";
|
||||
import { FilterUI } from "../components/Filters";
|
||||
import { AnimatedBackground, AnimatedBackgroundContext } from "../components/AnimatedBackground";
|
||||
|
|
@ -47,10 +40,11 @@ import { GamePadButtonCode, useShortcutContext, useShortcuts } from "../scripts/
|
|||
import z from "zod";
|
||||
import { Router } from "..";
|
||||
import CollectionList from "../components/CollectionList";
|
||||
import { zodValidator } from '@tanstack/zod-adapter';
|
||||
|
||||
export const Route = createFileRoute("/")({
|
||||
component: ConsoleHomeUI,
|
||||
validateSearch: z.object({ filter: z.string().optional().default('games') })
|
||||
validateSearch: zodValidator(z.object({ filter: z.string().optional().default('games') }))
|
||||
});
|
||||
|
||||
const filters = {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@ import { Router } from '..';
|
|||
import { useEffect, useState } from 'react';
|
||||
import { rommApi } from '../scripts/clientApi';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { GamePadButtonCode, useShortcutContext, useShortcuts } from '../scripts/shortcuts';
|
||||
import { useFocusable } from '@noriginmedia/norigin-spatial-navigation';
|
||||
import Shortcuts from '../components/Shortcuts';
|
||||
|
||||
export const Route = createFileRoute('/launcher/$source/$id')({
|
||||
component: RouteComponent,
|
||||
|
|
@ -20,13 +23,11 @@ function RouteComponent ()
|
|||
}
|
||||
|
||||
const { source, id } = Route.useParams();
|
||||
const { ref, focusKey } = useFocusable({ focusKey: `launching-${source}-${id}` });
|
||||
const { data } = useQuery({ queryKey: ['romm', 'game'], queryFn: () => rommApi.api.romm.game({ source })({ id }).get() });
|
||||
|
||||
useEventListener("cancel", (e) =>
|
||||
{
|
||||
e.stopPropagation();
|
||||
HandleGoBack();
|
||||
});
|
||||
useShortcuts(focusKey, () => [{ label: "Back", button: GamePadButtonCode.B, action: HandleGoBack }]);
|
||||
const { shortcuts } = useShortcutContext();
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
|
|
@ -41,18 +42,27 @@ function RouteComponent ()
|
|||
}
|
||||
};
|
||||
|
||||
es.addEventListener('refresh', HandleGoBack);
|
||||
es.addEventListener('refresh', () =>
|
||||
{
|
||||
HandleGoBack();
|
||||
});
|
||||
|
||||
es.onerror = HandleGoBack;
|
||||
es.onerror = () =>
|
||||
{
|
||||
HandleGoBack();
|
||||
};
|
||||
|
||||
return () => es.close();
|
||||
}, []);
|
||||
|
||||
|
||||
return <AnimatedBackground backgroundKey='game-details'>
|
||||
return <AnimatedBackground ref={ref} backgroundKey='game-details'>
|
||||
<div className='flex shadow-2xs shadow-black flex-col absolute w-screen h-screen overflow-hidden justify-center items-center gap-4'>
|
||||
<DotsLoading />
|
||||
<h1 className='font-semibold'>Launching {data?.data?.name} ...</h1>
|
||||
</div>
|
||||
<div className='absolute bot'>
|
||||
<Shortcuts shortcuts={shortcuts} />
|
||||
</div>
|
||||
</AnimatedBackground>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { createFileRoute, useNavigate } from "@tanstack/react-router";
|
||||
import { useEventListener, useSessionStorage } from "usehooks-ts";
|
||||
import { CollectionsDetail } from "../components/CollectionsDetail";
|
||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||
import { useQuery, useSuspenseQuery } from "@tanstack/react-query";
|
||||
import { DefaultRommStaleTime, RPC_URL } from "../../shared/constants";
|
||||
import { Suspense } from "react";
|
||||
import { rommApi } from "../scripts/clientApi";
|
||||
|
|
@ -10,10 +10,21 @@ export const Route = createFileRoute("/platform/$source/$id")({
|
|||
component: RouteComponent
|
||||
});
|
||||
|
||||
function PlatformTitle ()
|
||||
function PlatformTitle (data: { platformSlug?: string, platformName?: string; })
|
||||
{
|
||||
return <div className="flex flex-col gap-2 pl-2 text-2xl font-semibold text-base-content justify-center drop-shadow">
|
||||
|
||||
<div className="divider mb-6 mt-0">
|
||||
{!!data.platformSlug && <img className="size-14 rounded-full p-2" src={`${RPC_URL(__HOST__)}/api/romm/assets/platforms/${data.platformSlug.toLocaleLowerCase()}.svg`} ></img>}
|
||||
{data.platformName}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
function RouteComponent ()
|
||||
{
|
||||
const { source, id } = Route.useParams();
|
||||
const { data: platform } = useSuspenseQuery({
|
||||
const { data: platform } = useQuery({
|
||||
queryKey: ['platform', source, id], queryFn: async () =>
|
||||
{
|
||||
const { data, error } = await rommApi.api.romm.platforms({ source })({ id }).get();
|
||||
|
|
@ -22,33 +33,18 @@ function PlatformTitle ()
|
|||
}, staleTime: DefaultRommStaleTime
|
||||
});
|
||||
|
||||
return <div className="flex flex-col gap-2 pl-2 text-2xl font-semibold text-base-content justify-center drop-shadow">
|
||||
|
||||
<div className="divider mb-6 mt-0">
|
||||
<img className="size-14 rounded-full p-2" src={`${RPC_URL(__HOST__)}/api/romm/assets/platforms/${platform.slug.toLocaleLowerCase()}.svg`} ></img>
|
||||
{platform.display_name}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
function RouteComponent ()
|
||||
{
|
||||
const { id } = Route.useParams();
|
||||
|
||||
const [, setBackground] = useSessionStorage<string | undefined>(
|
||||
"home-background",
|
||||
undefined,
|
||||
);
|
||||
const navigate = useNavigate();
|
||||
useEventListener("cancel", () => navigate({ to: "/", viewTransition: { types: ['zoom-out'] } }));
|
||||
|
||||
return (
|
||||
<div className="w-full h-full">
|
||||
<CollectionsDetail
|
||||
title={<Suspense><PlatformTitle /></Suspense>}
|
||||
{!!platform && <CollectionsDetail
|
||||
title={<PlatformTitle platformSlug={platform.slug} platformName={platform.name} />}
|
||||
setBackground={setBackground}
|
||||
filters={{ platformId: Number(id) }}
|
||||
/>
|
||||
filters={{ platform_id: Number(id), platform_slug: platform.slug, platform_source: source }}
|
||||
/>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,10 +51,6 @@ function RouteComponent ()
|
|||
<th>Machine</th>
|
||||
<td>{systemInfo?.data?.machine}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Space</th>
|
||||
<td>{!!systemInfo?.data && `${prettyBytes(systemInfo?.data?.freeSpace)} Free / ${prettyBytes(systemInfo?.data?.totalSpace)} Total | ${(1 - (systemInfo?.data?.freeSpace / systemInfo?.data?.totalSpace)).toLocaleString('en-GB', { style: "percent" })}`}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Steam Deck</th>
|
||||
<td>{systemInfo?.data?.steamDeck ?? 'false'}</td>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,68 @@
|
|||
import { FocusContext, useFocusable } from '@noriginmedia/norigin-spatial-navigation';
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
import { SettingsOption } from '../../components/options/SettingsOption';
|
||||
import { Block, createFileRoute, useBlocker } from '@tanstack/react-router';
|
||||
import DownloadDirectoryOption from '@/mainview/components/options/DownloadDirectoryOption';
|
||||
import { useIsMutating, useMutation, useQuery } from '@tanstack/react-query';
|
||||
import { changeDownloadsMutation, downloadDrivesQuery } from '@/mainview/scripts/queries';
|
||||
import { DownloadsDrive } from '@/shared/constants';
|
||||
import prettyBytes from 'pretty-bytes';
|
||||
import classNames from 'classnames';
|
||||
import { GamePadButtonCode, Shortcut, useShortcuts } from '@/mainview/scripts/shortcuts';
|
||||
import { Download, FolderOpen, HardDrive, Usb } from 'lucide-react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import { OptionSpace } from '@/mainview/components/options/OptionSpace';
|
||||
import data from '@emulators';
|
||||
import { Button } from '@/mainview/components/options/Button';
|
||||
import { systemApi } from '@/mainview/scripts/clientApi';
|
||||
|
||||
export const Route = createFileRoute('/settings/directories')({
|
||||
component: RouteComponent,
|
||||
});
|
||||
|
||||
function DriveComponent (data: { drive: DownloadsDrive; downloadsSize: number; refetchDrives: () => void; })
|
||||
{
|
||||
const { ref, focused, focusKey } = useFocusable({ focusKey: data.drive.device });
|
||||
const isMoving = useIsMutating(changeDownloadsMutation);
|
||||
const usedWithoutDownlods = data.drive.used - (data.drive.isCurrentlyUsed ? data.downloadsSize : 0);
|
||||
const usedPercent = usedWithoutDownlods / data.drive.size;
|
||||
const usedPercentRaw = data.drive.used / data.drive.size;
|
||||
const changeDownloads = useMutation({ ...changeDownloadsMutation, onSuccess: data.refetchDrives }); data.drive.unusableReason;
|
||||
const shortcuts: Shortcut[] = [];
|
||||
if (!data.drive.unusableReason && isMoving <= 0)
|
||||
{
|
||||
shortcuts.push({ label: "Move Downloads", button: GamePadButtonCode.A, action: () => changeDownloads.mutate(data.drive.mountPoint) });
|
||||
}
|
||||
useShortcuts(focusKey, () => shortcuts, [shortcuts]);
|
||||
|
||||
|
||||
return <li ref={ref} className={twMerge('flex flex-col p-4 bg-base-300 rounded-2xl gap-1',
|
||||
classNames({
|
||||
"ring-7": focused,
|
||||
"border-dashed border-primary border-7": data.drive.isCurrentlyUsed,
|
||||
"border-solid": data.drive.unusableReason === 'already_used',
|
||||
"ring-error": data.drive.unusableReason === 'not_enough_space',
|
||||
}))}>
|
||||
<div className='flex gap-2 font-semibold'>{data.drive.isRemovable ? <Usb /> : <HardDrive />}{data.drive.label}</div>
|
||||
<small className='opacity-60'>{data.drive.mountPoint}</small>
|
||||
<div className='flex gap-2'>
|
||||
{prettyBytes(data.drive.size - data.drive.used)} Free
|
||||
{data.drive.unusableReason === 'not_enough_space' && <p className='text-error'>(Not Enough Space)</p>}
|
||||
{data.drive.unusableReason === 'already_used' && <p>(Currently Used)</p>}
|
||||
{data.drive.unusableReason !== 'already_used' && data.drive.isCurrentlyUsed && <p className='opacity-60'>(Custom Path)</p>}
|
||||
</div>
|
||||
|
||||
<div className={twMerge("progress", classNames({
|
||||
"progress-warning": usedPercent > 0.8,
|
||||
"progress-error": data.drive.unusableReason === 'not_enough_space',
|
||||
}))}>
|
||||
<div className={twMerge('h-full bg-primary', classNames({
|
||||
"bg-warning": usedPercent > 0.8,
|
||||
"bg-error": data.drive.unusableReason === 'not_enough_space',
|
||||
}))} style={{ width: usedPercent.toLocaleString('en-US', { style: 'percent' }) }}></div>
|
||||
{!!data.drive.isCurrentlyUsed && <div className="h-full bg-base-content" style={{ width: usedPercentRaw.toLocaleString('en-US', { style: 'percent' }) }}></div>}
|
||||
</div>
|
||||
</li>;
|
||||
}
|
||||
|
||||
function RouteComponent ()
|
||||
{
|
||||
const { focus } = Route.useSearch();
|
||||
|
|
@ -13,14 +70,34 @@ function RouteComponent ()
|
|||
preferredChildFocusKey: focus
|
||||
});
|
||||
|
||||
const isMoving = useIsMutating(changeDownloadsMutation);
|
||||
const { data: drives, refetch } = useQuery({ ...downloadDrivesQuery, refetchInterval: isMoving > 0 ? 1000 : undefined });
|
||||
|
||||
return <FocusContext value={focusKey}>
|
||||
<Block shouldBlockFn={() => isMoving} withResolver={false} />
|
||||
<ul ref={ref} className="list rounded-box gap-2">
|
||||
<div className="divider text-2xl mt-0 md:mt-4">
|
||||
<div className="flex flex-col">
|
||||
<h3>Romm</h3>
|
||||
</div>
|
||||
<Download className='size-16' /> Downloads ({drives?.downloadsSize ? prettyBytes(drives?.downloadsSize) : '?'})
|
||||
</div>
|
||||
<SettingsOption label="Download Path" id="downloadPath" type="text" />
|
||||
<ul className='p-2 grid grid-cols-2 gap-3'>
|
||||
{drives?.drives.filter(d => d.mountPoint).map(d => <DriveComponent refetchDrives={refetch} downloadsSize={drives.downloadsSize} drive={d} />)}
|
||||
</ul>
|
||||
<DownloadDirectoryOption
|
||||
isDirectoryPicker
|
||||
requireConfirmation
|
||||
allowNewFolderCreation
|
||||
label="Custom Download Path"
|
||||
id="downloadPath"
|
||||
type="text" >
|
||||
|
||||
</DownloadDirectoryOption>
|
||||
<OptionSpace label="Config Path" id='config'>
|
||||
<div className='flex gap-2 items-center'>
|
||||
{drives?.configPath}
|
||||
<Button id='open-config' type='button' onAction={() => systemApi.api.system.open.post({ url: drives?.configPath ?? '' })} ><FolderOpen /></Button>
|
||||
</div>
|
||||
</OptionSpace>
|
||||
</ul>
|
||||
</FocusContext>;
|
||||
|
||||
</FocusContext >;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { useMutation, useQuery } from '@tanstack/react-query';
|
|||
import { settingsApi } from '../../scripts/clientApi';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { Button } from '../../components/options/Button';
|
||||
import { Check, ChevronDown, SearchAlert, Trash, TriangleAlert } from 'lucide-react';
|
||||
import { Check, ChevronDown, FolderSearch, SearchAlert, Trash, TriangleAlert } from 'lucide-react';
|
||||
import { ContextDialog, ContextList, DialogEntry, OptionElement } from '../../components/ContextDialog';
|
||||
import classNames from 'classnames';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
|
@ -13,6 +13,8 @@ import { RPC_URL } from '../../../shared/constants';
|
|||
import emulators from '@emulators';
|
||||
import { FocusContext, setFocus, useFocusable } from '@noriginmedia/norigin-spatial-navigation';
|
||||
import { GamePadButtonCode, useShortcuts } from '@/mainview/scripts/shortcuts';
|
||||
import FilePicker from '@/mainview/components/FilePicker';
|
||||
import { dirname } from 'pathe';
|
||||
|
||||
export const Route = createFileRoute('/settings/emulators')({
|
||||
component: RouteComponent,
|
||||
|
|
@ -90,6 +92,7 @@ function NewEmulatorPath (data: { addOverride: (emulator: string) => void; isAdd
|
|||
|
||||
function EmulatorPath (data: { id: string; })
|
||||
{
|
||||
const [isSearching, setIsSearching] = useState(false);
|
||||
const [dirty, setDirty] = useState(false);
|
||||
const [localValue, setLocalValue] = useState<string | undefined>();
|
||||
const { data: remoteValue } = useQuery({
|
||||
|
|
@ -109,6 +112,8 @@ function EmulatorPath (data: { id: string; })
|
|||
{
|
||||
ctx.client.invalidateQueries({ queryKey: ["emulator", data.id] });
|
||||
ctx.client.invalidateQueries({ queryKey: ["auto-emulators"] });
|
||||
setLocalValue(v);
|
||||
setDirty(false);
|
||||
}
|
||||
});
|
||||
const deleteMutation = useMutation({
|
||||
|
|
@ -129,11 +134,23 @@ function EmulatorPath (data: { id: string; })
|
|||
{
|
||||
if (dirty)
|
||||
{
|
||||
setDirty(false);
|
||||
setSettingMutation.mutate(localValue ?? '');
|
||||
}
|
||||
}, [dirty, setDirty, localValue]);
|
||||
|
||||
const handleCloseSearch = () =>
|
||||
{
|
||||
setIsSearching(false);
|
||||
setFocus(`search-${data.id}`);
|
||||
};
|
||||
|
||||
const handleSelectPath = (path: string) =>
|
||||
{
|
||||
setIsSearching(false);
|
||||
setSettingMutation.mutate(path);
|
||||
setFocus(`search-${data.id}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<OptionSpace label={<><p className='font-semibold'>{data.id}</p><small className='text-base-content/40'>{emulators[data.id]}</small></>}>
|
||||
<div className='flex gap-2'>
|
||||
|
|
@ -150,9 +167,33 @@ function EmulatorPath (data: { id: string; })
|
|||
}}
|
||||
value={localValue}
|
||||
/>
|
||||
<Button id={`delete-${data.id}`} className='p-2' onAction={() => deleteMutation.mutate()} type='button' >
|
||||
<Button shortcutLabel="Remove" id={`delete-${data.id}`} className='p-2' onAction={() => deleteMutation.mutate()} type='button' >
|
||||
<Trash />
|
||||
</Button>
|
||||
<Button
|
||||
id={`search-${data.id}`}
|
||||
className='p-2'
|
||||
onAction={() => setIsSearching(true)}
|
||||
shortcutLabel={"Search"}
|
||||
type='button' >
|
||||
<FolderSearch />
|
||||
</Button>
|
||||
<ContextDialog
|
||||
className='h-[80vh] w-[60vw]'
|
||||
id={`file-picker-${data.id}`}
|
||||
open={isSearching}
|
||||
close={handleCloseSearch}
|
||||
preferredChildFocusKey={`main-download-path-${data.id}`}
|
||||
>
|
||||
{isSearching && <FilePicker
|
||||
onSelect={handleSelectPath}
|
||||
key={`download-path-${data.id}`}
|
||||
startingPath={remoteValue ? dirname(remoteValue) : undefined}
|
||||
id={`download-path-${data.id}`}
|
||||
cancel={handleCloseSearch}
|
||||
/>
|
||||
}
|
||||
</ContextDialog>
|
||||
</div>
|
||||
</OptionSpace>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ function MenuItem (data: {
|
|||
const acitve = matchRoute({ to: data.route });
|
||||
const handleNonFocusSelect = () => navigate({ to: data.return ? PopSource('settings') ?? data.route : data.route, viewTransition: data.viewTransition });
|
||||
const { ref, focusSelf, focused } = useFocusable({
|
||||
focusKey: data.route,
|
||||
focusKey: `menu-item-${data.route}`,
|
||||
forceFocus: !!acitve,
|
||||
onFocus: () =>
|
||||
{
|
||||
|
|
@ -119,8 +119,8 @@ function SettingsMenu (data: {})
|
|||
/>
|
||||
<MenuItem
|
||||
focusSelect
|
||||
route="/settings/visual"
|
||||
label="Visual"
|
||||
route="/settings/interface"
|
||||
label="Interface"
|
||||
icon={<MonitorCog />}
|
||||
/>
|
||||
<MenuItem
|
||||
|
|
@ -156,18 +156,12 @@ function SettingsMenu (data: {})
|
|||
function HandleGoBack ()
|
||||
{
|
||||
|
||||
if (document.activeElement && document.activeElement !== document.body && document.activeElement instanceof HTMLElement)
|
||||
const source = PopSource('settings');
|
||||
if (source)
|
||||
{
|
||||
document.activeElement.blur();
|
||||
} else
|
||||
{
|
||||
const source = PopSource('settings');
|
||||
if (source)
|
||||
{
|
||||
console.log("Found source ", source, " to go back to");
|
||||
}
|
||||
Router.navigate({ to: source ?? "/", viewTransition: { types: ['zoom-out'] } });
|
||||
console.log("Found source ", source, " to go back to");
|
||||
}
|
||||
Router.navigate({ to: source ?? "/", viewTransition: { types: ['zoom-out'] } });
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue