Extract app routes into constant

This commit is contained in:
schroda
2024-12-11 20:10:59 +01:00
parent 8c4ff516c0
commit cff0ed5ec3
31 changed files with 292 additions and 86 deletions

View File

@@ -22,6 +22,7 @@ import { lazyLoadFallback } from '@/modules/core/utils/LazyLoad.tsx';
import { ErrorBoundary } from '@/modules/core/components/ErrorBoundary.tsx'; import { ErrorBoundary } from '@/modules/core/components/ErrorBoundary.tsx';
import { useNavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx'; import { useNavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx';
import { Reader } from '@/modules/reader/screens/Reader.tsx'; import { Reader } from '@/modules/reader/screens/Reader.tsx';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
const { Browse } = loadable(() => import('@/modules/browse/screens/Browse.tsx'), lazyLoadFallback); const { Browse } = loadable(() => import('@/modules/browse/screens/Browse.tsx'), lazyLoadFallback);
const { DownloadQueue } = loadable(() => import('@/modules/downloads/screens/DownloadQueue.tsx'), lazyLoadFallback); const { DownloadQueue } = loadable(() => import('@/modules/downloads/screens/DownloadQueue.tsx'), lazyLoadFallback);
@@ -113,48 +114,51 @@ const MainApp = () => {
<ErrorBoundary> <ErrorBoundary>
<Routes> <Routes>
{/* General Routes */} {/* General Routes */}
<Route path="/" element={<Navigate to="/library" replace />} /> <Route path={AppRoutes.root.match} element={<Navigate to={AppRoutes.library.path} replace />} />
<Route path="*" element={<Navigate to="/" replace />} /> <Route path={AppRoutes.matchAll.match} element={<Navigate to={AppRoutes.root.path} replace />} />
<Route path="settings"> <Route path={AppRoutes.settings.match}>
<Route index element={<Settings />} /> <Route index element={<Settings />} />
<Route path="about" element={<About />} /> <Route path={AppRoutes.settings.childRoutes.about.match} element={<About />} />
<Route path="categories" element={<CategorySettings />} /> <Route path={AppRoutes.settings.childRoutes.categories.match} element={<CategorySettings />} />
<Route path="reader" element={<GlobalReaderSettings />} /> <Route path={AppRoutes.settings.childRoutes.reader.match} element={<GlobalReaderSettings />} />
<Route path="library"> <Route path={AppRoutes.settings.childRoutes.library.match}>
<Route index element={<LibrarySettings />} /> <Route index element={<LibrarySettings />} />
<Route path="duplicates" element={<LibraryDuplicates />} /> <Route
path={AppRoutes.settings.childRoutes.library.childRoutes.duplicates.match}
element={<LibraryDuplicates />}
/>
</Route> </Route>
<Route path="download" element={<DownloadSettings />} /> <Route path={AppRoutes.settings.childRoutes.download.match} element={<DownloadSettings />} />
<Route path="backup" element={<Backup />} /> <Route path={AppRoutes.settings.childRoutes.backup.match} element={<Backup />} />
<Route path="server" element={<ServerSettings />} /> <Route path={AppRoutes.settings.childRoutes.server.match} element={<ServerSettings />} />
<Route path="webui" element={<WebUISettings />} /> <Route path={AppRoutes.settings.childRoutes.webui.match} element={<WebUISettings />} />
<Route path="browse" element={<BrowseSettings />} /> <Route path={AppRoutes.settings.childRoutes.browse.match} element={<BrowseSettings />} />
<Route path="device" element={<DeviceSetting />} /> <Route path={AppRoutes.settings.childRoutes.device.match} element={<DeviceSetting />} />
<Route path="tracking" element={<TrackingSettings />} /> <Route path={AppRoutes.settings.childRoutes.tracking.match} element={<TrackingSettings />} />
<Route path="appearance" element={<Appearance />} /> <Route path={AppRoutes.settings.childRoutes.appearance.match} element={<Appearance />} />
</Route> </Route>
{/* Manga Routes */} {/* Manga Routes */}
<Route path="sources"> <Route path={AppRoutes.sources.match}>
<Route index element={<Navigate to="/" replace />} /> <Route index element={<Navigate to={AppRoutes.root.path} replace />} />
<Route path=":sourceId" element={<SourceMangas />} /> <Route path={AppRoutes.sources.childRoutes.browse.match} element={<SourceMangas />} />
<Route path=":sourceId/configure/" element={<SourceConfigure />} /> <Route path={AppRoutes.sources.childRoutes.configure.match} element={<SourceConfigure />} />
<Route path="all/search/" element={<SearchAll />} /> <Route path={AppRoutes.sources.childRoutes.searchAll.match} element={<SearchAll />} />
</Route> </Route>
<Route path="downloads" element={<DownloadQueue />} /> <Route path={AppRoutes.downloads.match} element={<DownloadQueue />} />
<Route path="manga/:id"> <Route path={AppRoutes.manga.match}>
<Route path="chapter/:chapterNum" element={null} /> <Route path={AppRoutes.manga.childRoutes.reader.match} element={null} />
<Route index element={<Manga />} /> <Route index element={<Manga />} />
</Route> </Route>
<Route path="library" element={<Library />} /> <Route path={AppRoutes.library.match} element={<Library />} />
<Route path="updates" element={<Updates />} /> <Route path={AppRoutes.updates.match} element={<Updates />} />
<Route path="browse" element={<Browse />} /> <Route path={AppRoutes.browse.match} element={<Browse />} />
<Route path="migrate/source/:sourceId"> <Route path={AppRoutes.migrate.match}>
<Route index element={<Migrate />} /> <Route index element={<Migrate />} />
<Route path="manga/:mangaId/search" element={<SearchAll />} /> <Route path={AppRoutes.migrate.childRoutes.search.match} element={<SearchAll />} />
</Route> </Route>
<Route path="tracker/login/oauth" element={<TrackerOAuthLogin />} /> <Route path={AppRoutes.tracker.match} element={<TrackerOAuthLogin />} />
</Routes> </Routes>
</ErrorBoundary> </ErrorBoundary>
</Box> </Box>
@@ -164,7 +168,7 @@ const MainApp = () => {
const ReaderApp = () => ( const ReaderApp = () => (
<ErrorBoundary> <ErrorBoundary>
<Routes> <Routes>
<Route path="*" element={<Reader />} /> <Route path={AppRoutes.matchAll.match} element={<Reader />} />
</Routes> </Routes>
</ErrorBoundary> </ErrorBoundary>
); );
@@ -181,8 +185,8 @@ export const App: React.FC = () => (
<DefaultNavBar /> <DefaultNavBar />
</Box> </Box>
<Routes> <Routes>
<Route path="*" element={<MainApp />} /> <Route path={AppRoutes.matchAll.match} element={<MainApp />} />
<Route path="manga/:mangaId/chapter/:chapterIndex/*" element={<ReaderApp />} /> <Route path={AppRoutes.reader.match} element={<ReaderApp />} />
</Routes> </Routes>
</Box> </Box>
</AppContext> </AppContext>

View File

@@ -20,6 +20,7 @@ import { VersionUpdateInfoDialog } from '@/modules/app-updates/components/Versio
import { useMetadataServerSettings } from '@/modules/settings/services/ServerSettingsMetadata.ts'; import { useMetadataServerSettings } from '@/modules/settings/services/ServerSettingsMetadata.ts';
import { useLocalStorage } from '@/modules/core/hooks/useStorage.tsx'; import { useLocalStorage } from '@/modules/core/hooks/useStorage.tsx';
import { getVersion } from '@/modules/app-updates/services/AppUpdateChecker.tsx'; import { getVersion } from '@/modules/app-updates/services/AppUpdateChecker.tsx';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
const disabledUpdateCheck = () => Promise.resolve(); const disabledUpdateCheck = () => Promise.resolve();
@@ -87,7 +88,7 @@ export const ServerUpdateChecker = () => {
return null; return null;
} }
const isAboutPage = window.location.pathname === '/settings/about'; const isAboutPage = window.location.pathname === AppRoutes.settings.childRoutes.about.path;
if (isAboutPage) { if (isAboutPage) {
return null; return null;
} }

View File

@@ -35,6 +35,7 @@ import { LoadingPlaceholder } from '@/modules/core/components/placeholder/Loadin
import { EmptyViewAbsoluteCentered } from '@/modules/core/components/placeholder/EmptyViewAbsoluteCentered.tsx'; import { EmptyViewAbsoluteCentered } from '@/modules/core/components/placeholder/EmptyViewAbsoluteCentered.tsx';
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts'; import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
import { ServerSettings } from '@/modules/settings/Settings.types.ts'; import { ServerSettings } from '@/modules/settings/Settings.types.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
type BackupSettingsType = Pick<ServerSettings, 'backupPath' | 'backupTime' | 'backupInterval' | 'backupTTL'>; type BackupSettingsType = Pick<ServerSettings, 'backupPath' | 'backupTime' | 'backupInterval' | 'backupTTL'>;
@@ -357,7 +358,7 @@ export function Backup() {
<Button <Button
onClick={closeInvalidBackupDialog} onClick={closeInvalidBackupDialog}
component={Link} component={Link}
to="/browse" to={AppRoutes.browse.path}
autoFocus={!!validationResult?.missingSources.length} autoFocus={!!validationResult?.missingSources.length}
variant={validationResult?.missingSources.length ? 'contained' : 'text'} variant={validationResult?.missingSources.length ? 'contained' : 'text'}
> >
@@ -368,7 +369,7 @@ export function Backup() {
<Button <Button
onClick={closeInvalidBackupDialog} onClick={closeInvalidBackupDialog}
component={Link} component={Link}
to="/settings/tracking" to={AppRoutes.tracker.path}
autoFocus={!!validationResult?.missingTrackers.length} autoFocus={!!validationResult?.missingTrackers.length}
variant={validationResult?.missingTrackers.length ? 'contained' : 'text'} variant={validationResult?.missingTrackers.length ? 'contained' : 'text'}
> >

View File

@@ -33,6 +33,7 @@ import {
} from '@/lib/graphql/generated/graphql.ts'; } from '@/lib/graphql/generated/graphql.ts';
import { GET_CATEGORIES_BASE } from '@/lib/graphql/queries/CategoryQuery.ts'; import { GET_CATEGORIES_BASE } from '@/lib/graphql/queries/CategoryQuery.ts';
import { GET_MANGA_CATEGORIES } from '@/lib/graphql/queries/MangaQuery.ts'; import { GET_MANGA_CATEGORIES } from '@/lib/graphql/queries/MangaQuery.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
type BaseProps = { type BaseProps = {
open: boolean; open: boolean;
@@ -232,7 +233,7 @@ export function CategorySelect(props: CategorySelectProps) {
width: '100%', width: '100%',
}} }}
> >
<Button component={Link} to="/settings/categories"> <Button component={Link} to={AppRoutes.settings.childRoutes.categories.path}>
{t(allCategories.length ? 'global.button.edit' : 'global.button.create')} {t(allCategories.length ? 'global.button.edit' : 'global.button.create')}
</Button> </Button>
<Stack direction="row"> <Stack direction="row">

View File

@@ -22,6 +22,7 @@ import { CHAPTER_LIST_FIELDS } from '@/lib/graphql/fragments/ChapterFragments.ts
import { DirectionOffset, TranslationKey } from '@/Base.types.ts'; import { DirectionOffset, TranslationKey } from '@/Base.types.ts';
import { MangaIdInfo } from '@/modules/manga/Manga.types.ts'; import { MangaIdInfo } from '@/modules/manga/Manga.types.ts';
import { ReaderResumeMode } from '@/modules/reader/types/Reader.types.ts'; import { ReaderResumeMode } from '@/modules/reader/types/Reader.types.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
export type ChapterAction = 'download' | 'delete' | 'bookmark' | 'unbookmark' | 'mark_as_read' | 'mark_as_unread'; export type ChapterAction = 'download' | 'delete' | 'bookmark' | 'unbookmark' | 'mark_as_read' | 'mark_as_unread';
@@ -118,7 +119,7 @@ export class Chapters {
} }
static getReaderUrl<Chapter extends ChapterMangaInfo & ChapterSourceOrderInfo>(chapter: Chapter): string { static getReaderUrl<Chapter extends ChapterMangaInfo & ChapterSourceOrderInfo>(chapter: Chapter): string {
return `/manga/${chapter.mangaId}/chapter/${chapter.sourceOrder}`; return AppRoutes.reader.path(chapter.mangaId, chapter.sourceOrder);
} }
static isDownloading(id: number): boolean { static isDownloading(id: number): boolean {

View File

@@ -0,0 +1,157 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { SourceType } from '@/lib/graphql/generated/graphql.ts';
import { MangaIdInfo } from '@/modules/manga/Manga.types.ts';
import { ChapterSourceOrderInfo } from '@/modules/chapter/services/Chapters.ts';
type AppRouteInfo = {
match: string;
path?: string | ((...args: any[]) => string);
};
type TAppRoutes = Record<string, AppRouteInfo & { childRoutes?: TAppRoutes }>;
export const AppRoutes = {
root: {
match: '/',
path: '/',
},
matchAll: {
match: '*',
},
settings: {
path: '/settings',
match: 'settings',
childRoutes: {
about: {
match: 'about',
path: '/settings/about',
},
categories: {
match: 'categories',
path: '/settings/categories',
},
reader: {
match: 'reader',
path: '/settings/reader',
},
library: {
match: 'library',
path: '/settings/library',
childRoutes: {
duplicates: {
match: 'duplicates',
path: '/settings/library/duplicates',
},
},
},
download: {
match: 'download',
path: '/settings/download',
},
backup: {
match: 'backup',
path: '/settings/backup',
},
server: {
match: 'server',
path: '/settings/server',
},
webui: {
match: 'webui',
path: '/settings/webui',
},
browse: {
match: 'browse',
path: '/settings/browse',
},
device: {
match: 'device',
path: '/settings/device',
},
tracking: {
match: 'tracking',
path: '/settings/tracking',
},
appearance: {
match: 'appearance',
path: '/settings/appearance',
},
},
},
sources: {
match: 'sources',
path: '/sources',
childRoutes: {
browse: {
match: ':sourceId',
path: (sourceId: SourceType['id']) => `/sources/${sourceId}`,
},
configure: {
match: ':sourceId/configure',
path: (sourceId: SourceType['id']) => `/sources/${sourceId}/configure`,
},
searchAll: {
match: 'all/search',
path: '/sources/all/search',
},
},
},
downloads: {
match: 'downloads',
path: '/downloads',
},
manga: {
match: 'manga/:id',
path: (mangaId: MangaIdInfo['id']) => `/manga/${mangaId}`,
childRoutes: {
reader: {
match: 'chapter/:chapterNum',
path: (mangaId: MangaIdInfo['id'], chapterNum: ChapterSourceOrderInfo['sourceOrder']) =>
`/manga/${mangaId}/chapter/${chapterNum}`,
},
},
},
library: {
match: 'library',
path: '/library',
},
updates: {
match: 'updates',
path: '/updates',
},
browse: {
match: 'browse',
path: '/browse',
},
migrate: {
match: 'migrate/source/:sourceId',
path: (sourceId: SourceType['id']) => `/migrate/source/${sourceId}`,
childRoutes: {
search: {
match: 'manga/:mangaId/search',
path: (sourceId: SourceType['id'], mangaId: MangaIdInfo['id']) =>
`/migrate/source/${sourceId}/manga/${mangaId}/search`,
},
},
},
tracker: {
match: 'tracker/login/oauth',
path: '/tracker/login/oauth',
},
reader: {
match: '/manga/:mangaId/chapter/:chapterIndex',
path: (mangaId: MangaIdInfo['id'], chapterIndex: ChapterSourceOrderInfo['sourceOrder']) =>
`/manga/${mangaId}/chapter/${chapterIndex}`,
},
} satisfies TAppRoutes;

View File

@@ -8,6 +8,7 @@
import { useLocation, useNavigate } from 'react-router-dom'; import { useLocation, useNavigate } from 'react-router-dom';
import { useNavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx'; import { useNavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
const PAGES_TO_IGNORE: readonly RegExp[] = [/\/manga\/[0-9]+\/chapter\/[0-9]+/g]; const PAGES_TO_IGNORE: readonly RegExp[] = [/\/manga\/[0-9]+\/chapter\/[0-9]+/g];
@@ -27,6 +28,6 @@ export const useBackButton = () => {
return; return;
} }
navigate('/library'); navigate(AppRoutes.library.path);
}; };
}; };

View File

@@ -35,6 +35,7 @@ import { LoadingPlaceholder } from '@/modules/core/components/placeholder/Loadin
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts'; import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
import { ChapterDownloadStatus, ChapterIdInfo } from '@/modules/chapter/services/Chapters.ts'; import { ChapterDownloadStatus, ChapterIdInfo } from '@/modules/chapter/services/Chapters.ts';
import { DownloaderState, DownloadState } from '@/lib/graphql/generated/graphql.ts'; import { DownloaderState, DownloadState } from '@/lib/graphql/generated/graphql.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
const HeightPreservingItem = ({ children, ...props }: BoxProps) => ( const HeightPreservingItem = ({ children, ...props }: BoxProps) => (
// the height is necessary to prevent the item container from collapsing, which confuses Virtuoso measurements // the height is necessary to prevent the item container from collapsing, which confuses Virtuoso measurements
@@ -59,7 +60,7 @@ const DownloadChapterItem = ({
return ( return (
<Box {...provided.draggableProps} {...provided.dragHandleProps} ref={provided.innerRef} sx={{ p: 1, pb: 0 }}> <Box {...provided.draggableProps} {...provided.dragHandleProps} ref={provided.innerRef} sx={{ p: 1, pb: 0 }}>
<Card> <Card>
<CardActionArea component={Link} to={`/manga/${item.manga.id}`}> <CardActionArea component={Link} to={AppRoutes.manga.path(item.manga.id)}>
<CardContent <CardContent
sx={{ sx={{
display: 'flex', display: 'flex',

View File

@@ -39,6 +39,7 @@ import {
} from '@/modules/extension/Extensions.utils.ts'; } from '@/modules/extension/Extensions.utils.ts';
import { ExtensionAction, ExtensionGroupState, ExtensionState } from '@/modules/extension/Extensions.types.ts'; import { ExtensionAction, ExtensionGroupState, ExtensionState } from '@/modules/extension/Extensions.types.ts';
import { EXTENSION_ACTION_TO_FAILURE_TRANSLATION_KEY_MAP } from '@/modules/extension/Extensions.constants.ts'; import { EXTENSION_ACTION_TO_FAILURE_TRANSLATION_KEY_MAP } from '@/modules/extension/Extensions.constants.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
const LANGUAGE = 0; const LANGUAGE = 0;
const EXTENSIONS = 1; const EXTENSIONS = 1;
@@ -223,7 +224,7 @@ export function Extensions({ tabsMenuHeight }: { tabsMenuHeight: number }) {
}} }}
> >
<Typography>{t('extension.label.add_repository_info')}</Typography> <Typography>{t('extension.label.add_repository_info')}</Typography>
<Button component={Link} variant="contained" to="/settings/browse"> <Button component={Link} variant="contained" to={AppRoutes.browse.path}>
{t('settings.title')} {t('settings.title')}
</Button> </Button>
</Stack> </Stack>

View File

@@ -29,6 +29,7 @@ import { SourceType } from '@/lib/graphql/generated/graphql.ts';
import { BaseMangaGrid } from '@/modules/manga/components/BaseMangaGrid.tsx'; import { BaseMangaGrid } from '@/modules/manga/components/BaseMangaGrid.tsx';
import { EmptyViewAbsoluteCentered } from '@/modules/core/components/placeholder/EmptyViewAbsoluteCentered.tsx'; import { EmptyViewAbsoluteCentered } from '@/modules/core/components/placeholder/EmptyViewAbsoluteCentered.tsx';
import { translateExtensionLanguage } from '@/modules/extension/Extensions.utils.ts'; import { translateExtensionLanguage } from '@/modules/extension/Extensions.utils.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
type SourceLoadingState = { isLoading: boolean; hasResults: boolean; emptySearch: boolean }; type SourceLoadingState = { isLoading: boolean; hasResults: boolean; emptySearch: boolean };
type SourceToLoadingStateMap = Map<string, SourceLoadingState>; type SourceToLoadingStateMap = Map<string, SourceLoadingState>;
@@ -149,7 +150,11 @@ const SourceSearchPreview = React.memo(
return ( return (
<> <>
<Card sx={{ mb: 1 }}> <Card sx={{ mb: 1 }}>
<CardActionArea component={Link} to={`/sources/${id}?query=${searchString}`} sx={{ p: 3 }}> <CardActionArea
component={Link}
to={`${AppRoutes.sources.childRoutes.browse.path(id)}?query=${searchString}`}
sx={{ p: 3 }}
>
<Typography variant="h5">{displayName}</Typography> <Typography variant="h5">{displayName}</Typography>
<Typography variant="caption">{translateExtensionLanguage(lang)}</Typography> <Typography variant="caption">{translateExtensionLanguage(lang)}</Typography>
</CardActionArea> </CardActionArea>

View File

@@ -37,6 +37,7 @@ import {
import { GET_CATEGORIES_SETTINGS } from '@/lib/graphql/queries/CategoryQuery.ts'; import { GET_CATEGORIES_SETTINGS } from '@/lib/graphql/queries/CategoryQuery.ts';
import { GET_MANGAS_BASE } from '@/lib/graphql/queries/MangaQuery.ts'; import { GET_MANGAS_BASE } from '@/lib/graphql/queries/MangaQuery.ts';
import { MetadataLibrarySettings } from '@/modules/library/Library.types.ts'; import { MetadataLibrarySettings } from '@/modules/library/Library.types.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
const removeNonLibraryMangasFromCategories = async (): Promise<void> => { const removeNonLibraryMangasFromCategories = async (): Promise<void> => {
try { try {
@@ -182,7 +183,7 @@ export function LibrarySettings() {
secondary={t('library.settings.advanced.database.cleanup.label.description')} secondary={t('library.settings.advanced.database.cleanup.label.description')}
/> />
</ListItemButton> </ListItemButton>
<ListItemLink to="duplicates"> <ListItemLink to={AppRoutes.settings.childRoutes.library.childRoutes.duplicates.path}>
<ListItemText <ListItemText
primary={t('library.settings.advanced.duplicates.label.title')} primary={t('library.settings.advanced.duplicates.label.title')}
secondary={t('library.settings.advanced.duplicates.label.description')} secondary={t('library.settings.advanced.duplicates.label.description')}

View File

@@ -10,7 +10,6 @@ import { useTranslation } from 'react-i18next';
import Button from '@mui/material/Button'; import Button from '@mui/material/Button';
import Tooltip from '@mui/material/Tooltip'; import Tooltip from '@mui/material/Tooltip';
import PlayArrowIcon from '@mui/icons-material/PlayArrow'; import PlayArrowIcon from '@mui/icons-material/PlayArrow';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { ChapterReadInfo, Chapters, ChapterSourceOrderInfo } from '@/modules/chapter/services/Chapters.ts'; import { ChapterReadInfo, Chapters, ChapterSourceOrderInfo } from '@/modules/chapter/services/Chapters.ts';
@@ -39,7 +38,7 @@ export const ContinueReadingButton = ({
size="small" size="small"
sx={{ minWidth: 'unset', py: 0.5, px: 0.75 }} sx={{ minWidth: 'unset', py: 0.5, px: 0.75 }}
component={Link} component={Link}
to={`${mangaLinkTo}chapter/${chapter.sourceOrder}`} to={`${mangaLinkTo}/chapter/${chapter.sourceOrder}`}
state={{ resumeMode: Chapters.getReaderResumeMode(chapter) }} state={{ resumeMode: Chapters.getReaderResumeMode(chapter) }}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()} onMouseDown={(e) => e.stopPropagation()}

View File

@@ -35,6 +35,7 @@ import { NestedMenuItem } from '@/modules/core/components/menu/NestedMenuItem.ts
import { MangaChapterStatFieldsFragment, MangaType } from '@/lib/graphql/generated/graphql.ts'; import { MangaChapterStatFieldsFragment, MangaType } from '@/lib/graphql/generated/graphql.ts';
import { MangaAction, MangaDownloadInfo, MangaIdInfo, MangaUnreadInfo } from '@/modules/manga/Manga.types.ts'; import { MangaAction, MangaDownloadInfo, MangaIdInfo, MangaUnreadInfo } from '@/modules/manga/Manga.types.ts';
import { actionToTranslationKey } from '@/modules/manga/Manga.constants.ts'; import { actionToTranslationKey } from '@/modules/manga/Manga.constants.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
const ACTION_DISABLES_SELECTION_MODE: MangaAction[] = ['remove_from_library'] as const; const ACTION_DISABLES_SELECTION_MODE: MangaAction[] = ['remove_from_library'] as const;
@@ -156,7 +157,7 @@ export const MangaActionMenuItems = ({
)} )}
{isSingleMode && ( {isSingleMode && (
<Link <Link
to={`/migrate/source/${manga?.sourceId}/manga/${manga?.id}/search?query=${manga?.title}`} to={`${AppRoutes.migrate.childRoutes.search.path(manga?.sourceId ?? -1, manga?.id ?? -1)}?query=${manga?.title}`}
state={{ mangaTitle: manga?.title }} state={{ mangaTitle: manga?.title }}
style={{ textDecoration: 'none', color: 'inherit' }} style={{ textDecoration: 'none', color: 'inherit' }}
> >

View File

@@ -23,6 +23,7 @@ import { useTheme } from '@mui/material/styles';
import useMediaQuery from '@mui/material/useMediaQuery'; import useMediaQuery from '@mui/material/useMediaQuery';
import { useCategorySelect } from '@/modules/category/hooks/useCategorySelect.tsx'; import { useCategorySelect } from '@/modules/category/hooks/useCategorySelect.tsx';
import { MangaType } from '@/lib/graphql/generated/graphql.ts'; import { MangaType } from '@/lib/graphql/generated/graphql.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
interface IProps { interface IProps {
manga: Pick<MangaType, 'id' | 'inLibrary' | 'sourceId' | 'title'>; manga: Pick<MangaType, 'id' | 'inLibrary' | 'sourceId' | 'title'>;
@@ -65,7 +66,7 @@ export const MangaToolbarMenu = ({ manga, onRefresh, refreshing }: IProps) => {
<> <>
<Tooltip title={t('global.button.migrate')}> <Tooltip title={t('global.button.migrate')}>
<Link <Link
to={`/migrate/source/${manga.sourceId}/manga/${manga.id}/search?query=${manga.title}`} to={`${AppRoutes.migrate.childRoutes.search.path(manga.sourceId, manga.id)}?query=${manga.title}`}
state={{ mangaTitle: manga.title }} state={{ mangaTitle: manga.title }}
style={{ textDecoration: 'none', color: 'inherit' }} style={{ textDecoration: 'none', color: 'inherit' }}
> >
@@ -125,7 +126,7 @@ export const MangaToolbarMenu = ({ manga, onRefresh, refreshing }: IProps) => {
<MenuItem <MenuItem
key="migrate" key="migrate"
component={Link} component={Link}
to={`/migrate/source/${manga.sourceId}/manga/${manga.id}/search?query=${manga.title}`} to={`${AppRoutes.migrate.childRoutes.search.path(manga.sourceId, manga.id)}?query=${manga.title}`}
state={{ mangaTitle: manga.title }} state={{ mangaTitle: manga.title }}
style={{ textDecoration: 'none', color: 'inherit' }} style={{ textDecoration: 'none', color: 'inherit' }}
> >

View File

@@ -19,8 +19,8 @@ import { Trackers } from '@/modules/tracker/services/Trackers.ts';
import { CustomIconButton } from '@/modules/core/components/buttons/CustomIconButton.tsx'; import { CustomIconButton } from '@/modules/core/components/buttons/CustomIconButton.tsx';
import { GetTrackersSettingsQuery, MangaType } from '@/lib/graphql/generated/graphql.ts'; import { GetTrackersSettingsQuery, MangaType } from '@/lib/graphql/generated/graphql.ts';
import { GET_TRACKERS_SETTINGS } from '@/lib/graphql/queries/TrackerQuery.ts'; import { GET_TRACKERS_SETTINGS } from '@/lib/graphql/queries/TrackerQuery.ts';
import { MangaTrackRecordInfo } from '@/modules/manga/Manga.types.ts'; import { MangaTrackRecordInfo } from '@/modules/manga/Manga.types.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
export const TrackMangaButton = ({ manga }: { manga: MangaTrackRecordInfo & Pick<MangaType, 'title'> }) => { export const TrackMangaButton = ({ manga }: { manga: MangaTrackRecordInfo & Pick<MangaType, 'title'> }) => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -40,7 +40,7 @@ export const TrackMangaButton = ({ manga }: { manga: MangaTrackRecordInfo & Pick
} }
if (!loggedInTrackers.length) { if (!loggedInTrackers.length) {
navigate('/settings/tracking'); navigate(AppRoutes.tracker.path);
return; return;
} }

View File

@@ -20,6 +20,7 @@ import { MangaCardMode, MangaCardProps } from '@/modules/manga/Manga.types.ts';
import { ContinueReadingButton } from '@/modules/manga/components/ContinueReadingButton.tsx'; import { ContinueReadingButton } from '@/modules/manga/components/ContinueReadingButton.tsx';
import { MangaBadges } from '@/modules/manga/components/MangaBadges.tsx'; import { MangaBadges } from '@/modules/manga/components/MangaBadges.tsx';
import { GridLayout } from '@/modules/core/Core.types.ts'; import { GridLayout } from '@/modules/core/Core.types.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
const getMangaLinkTo = ( const getMangaLinkTo = (
mode: MangaCardMode, mode: MangaCardMode,
@@ -31,9 +32,9 @@ const getMangaLinkTo = (
case 'default': case 'default':
case 'source': case 'source':
case 'duplicate': case 'duplicate':
return `/manga/${mangaId}/`; return AppRoutes.manga.path(mangaId);
case 'migrate.search': case 'migrate.search':
return `/migrate/source/${sourceId}/manga/${mangaId}/search?query=${mangaTitle}`; return `${AppRoutes.migrate.childRoutes.search.path(sourceId ?? '-1', mangaId)}?query=${mangaTitle}`;
case 'migrate.select': case 'migrate.select':
return ''; return '';
default: default:

View File

@@ -20,6 +20,7 @@ import { Mangas } from '@/modules/manga/services/Mangas.ts';
import { awaitConfirmation } from '@/modules/core/utils/AwaitableDialog.tsx'; import { awaitConfirmation } from '@/modules/core/utils/AwaitableDialog.tsx';
import { GetCategoriesBaseQuery, GetCategoriesBaseQueryVariables, MangaType } from '@/lib/graphql/generated/graphql.ts'; import { GetCategoriesBaseQuery, GetCategoriesBaseQueryVariables, MangaType } from '@/lib/graphql/generated/graphql.ts';
import { GET_CATEGORIES_BASE } from '@/lib/graphql/queries/CategoryQuery.ts'; import { GET_CATEGORIES_BASE } from '@/lib/graphql/queries/CategoryQuery.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
export const useManageMangaLibraryState = ( export const useManageMangaLibraryState = (
manga: Pick<MangaType, 'id' | 'title'> & Partial<Pick<MangaType, 'inLibrary'>>, manga: Pick<MangaType, 'id' | 'title'> & Partial<Pick<MangaType, 'inLibrary'>>,
@@ -136,7 +137,7 @@ export const useManageMangaLibraryState = (
extra: { show: true, title: t('migrate.dialog.action.button.show_entry'), contain: true }, extra: { show: true, title: t('migrate.dialog.action.button.show_entry'), contain: true },
confirm: { title: t('global.button.add') }, confirm: { title: t('global.button.add') },
}, },
onExtra: () => navigate(`/manga/${duplicatedLibraryMangas!.data.mangas.nodes[0].id}`), onExtra: () => navigate(AppRoutes.manga.path(duplicatedLibraryMangas!.data.mangas.nodes[0].id)),
}); });
} }

View File

@@ -26,6 +26,7 @@ import {
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts'; import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
import { MetadataMigrationSettings } from '@/modules/migration/Migration.types.ts'; import { MetadataMigrationSettings } from '@/modules/migration/Migration.types.ts';
import { MigrateMode } from '@/modules/manga/Manga.types.ts'; import { MigrateMode } from '@/modules/manga/Manga.types.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
export const MigrateDialog = ({ mangaIdToMigrateTo, onClose }: { mangaIdToMigrateTo: number; onClose: () => void }) => { export const MigrateDialog = ({ mangaIdToMigrateTo, onClose }: { mangaIdToMigrateTo: number; onClose: () => void }) => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -63,7 +64,7 @@ export const MigrateDialog = ({ mangaIdToMigrateTo, onClose }: { mangaIdToMigrat
deleteChapters, deleteChapters,
}); });
navigate(`/manga/${mangaIdToMigrateTo}`, { replace: true }); navigate(AppRoutes.manga.path(mangaIdToMigrateTo), { replace: true });
} catch (e) { } catch (e) {
setIsMigrationInProcess(false); setIsMigrationInProcess(false);
} }
@@ -108,7 +109,11 @@ export const MigrateDialog = ({ mangaIdToMigrateTo, onClose }: { mangaIdToMigrat
width: '100%', width: '100%',
}} }}
> >
<Button disabled={isMigrationInProcess} component={Link} to={`/manga/${mangaIdToMigrateTo}`}> <Button
disabled={isMigrationInProcess}
component={Link}
to={AppRoutes.manga.path(mangaIdToMigrateTo)}
>
{t('migrate.dialog.action.button.show_entry')} {t('migrate.dialog.action.button.show_entry')}
</Button> </Button>
<Stack direction="row"> <Stack direction="row">

View File

@@ -19,6 +19,7 @@ import { requestManager } from '@/lib/requests/RequestManager.ts';
import { GetMigratableSourcesQuery } from '@/lib/graphql/generated/graphql.ts'; import { GetMigratableSourcesQuery } from '@/lib/graphql/generated/graphql.ts';
import { SpinnerImage } from '@/modules/core/components/SpinnerImage.tsx'; import { SpinnerImage } from '@/modules/core/components/SpinnerImage.tsx';
import { translateExtensionLanguage } from '@/modules/extension/Extensions.utils.ts'; import { translateExtensionLanguage } from '@/modules/extension/Extensions.utils.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
export type TMigratableSource = NonNullable<GetMigratableSourcesQuery['mangas']['nodes'][number]['source']> & { export type TMigratableSource = NonNullable<GetMigratableSourcesQuery['mangas']['nodes'][number]['source']> & {
mangaCount: number; mangaCount: number;
@@ -33,7 +34,7 @@ export const MigrationCard = ({ id, name, lang, iconUrl, mangaCount }: TMigratab
return ( return (
<Card> <Card>
<CardActionArea component={Link} to={`/migrate/source/${id}/`}> <CardActionArea component={Link} to={AppRoutes.migrate.path(id)}>
<CardContent <CardContent
sx={{ sx={{
display: 'flex', display: 'flex',

View File

@@ -34,38 +34,39 @@ import { DesktopSideBar } from '@/modules/navigation-bar/components/DesktopSideB
import { useResizeObserver } from '@/modules/core/hooks/useResizeObserver.tsx'; import { useResizeObserver } from '@/modules/core/hooks/useResizeObserver.tsx';
import { MobileBottomBar } from '@/modules/navigation-bar/components/MobileBottomBar.tsx'; import { MobileBottomBar } from '@/modules/navigation-bar/components/MobileBottomBar.tsx';
import { NavbarItem } from '@/modules/navigation-bar/NavigationBar.types.ts'; import { NavbarItem } from '@/modules/navigation-bar/NavigationBar.types.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
const navbarItems: Array<NavbarItem> = [ const navbarItems: Array<NavbarItem> = [
{ {
path: '/library', path: AppRoutes.library.path,
title: 'library.title', title: 'library.title',
SelectedIconComponent: CollectionsBookmarkIcon, SelectedIconComponent: CollectionsBookmarkIcon,
IconComponent: CollectionsOutlinedBookmarkIcon, IconComponent: CollectionsOutlinedBookmarkIcon,
show: 'both', show: 'both',
}, },
{ {
path: '/updates', path: AppRoutes.updates.path,
title: 'updates.title', title: 'updates.title',
SelectedIconComponent: NewReleasesIcon, SelectedIconComponent: NewReleasesIcon,
IconComponent: NewReleasesOutlinedIcon, IconComponent: NewReleasesOutlinedIcon,
show: 'both', show: 'both',
}, },
{ {
path: '/browse', path: AppRoutes.browse.path,
title: 'global.label.browse', title: 'global.label.browse',
SelectedIconComponent: ExploreIcon, SelectedIconComponent: ExploreIcon,
IconComponent: ExploreOutlinedIcon, IconComponent: ExploreOutlinedIcon,
show: 'both', show: 'both',
}, },
{ {
path: '/downloads', path: AppRoutes.downloads.path,
title: 'download.title', title: 'download.title',
SelectedIconComponent: GetAppIcon, SelectedIconComponent: GetAppIcon,
IconComponent: GetAppOutlinedIcon, IconComponent: GetAppOutlinedIcon,
show: 'both', show: 'both',
}, },
{ {
path: '/settings', path: AppRoutes.settings.path,
title: 'settings.title', title: 'settings.title',
SelectedIconComponent: SettingsIcon, SelectedIconComponent: SettingsIcon,
IconComponent: SettingsIcon, IconComponent: SettingsIcon,

View File

@@ -32,6 +32,7 @@ import { useReaderScrollbarContext } from '@/modules/reader/contexts/ReaderScrol
import { useReaderStateMangaContext } from '@/modules/reader/contexts/state/ReaderStateMangaContext.tsx'; import { useReaderStateMangaContext } from '@/modules/reader/contexts/state/ReaderStateMangaContext.tsx';
import { useReaderStateChaptersContext } from '@/modules/reader/contexts/state/ReaderStateChaptersContext.tsx'; import { useReaderStateChaptersContext } from '@/modules/reader/contexts/state/ReaderStateChaptersContext.tsx';
import { LoadingPlaceholder } from '@/modules/core/components/placeholder/LoadingPlaceholder'; import { LoadingPlaceholder } from '@/modules/core/components/placeholder/LoadingPlaceholder';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
const DEFAULT_MANGA = { id: -1, title: '' }; const DEFAULT_MANGA = { id: -1, title: '' };
const DEFAULT_CHAPTER = { id: -1, name: '', realUrl: '', isBookmarked: false }; const DEFAULT_CHAPTER = { id: -1, name: '', realUrl: '', isBookmarked: false };
@@ -79,7 +80,7 @@ export const ReaderOverlayHeaderMobile = ({ isVisible }: MobileHeaderProps) => {
<TypographyMaxLines lines={1} component="h1" variant="h5"> <TypographyMaxLines lines={1} component="h1" variant="h5">
<Link <Link
component={RouterLink} component={RouterLink}
to={`/manga/${mangaId}`} to={AppRoutes.manga.path(mangaId)}
sx={{ textDecoration: 'none', color: 'inherit' }} sx={{ textDecoration: 'none', color: 'inherit' }}
> >
{title} {title}

View File

@@ -12,6 +12,7 @@ import Link from '@mui/material/Link';
import { Link as RouterLink } from 'react-router-dom'; import { Link as RouterLink } from 'react-router-dom';
import { TypographyMaxLines } from '@/modules/core/components/TypographyMaxLines'; import { TypographyMaxLines } from '@/modules/core/components/TypographyMaxLines';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
export const ReaderNavBarDesktopMetadata = ({ export const ReaderNavBarDesktopMetadata = ({
mangaId, mangaId,
@@ -25,7 +26,11 @@ export const ReaderNavBarDesktopMetadata = ({
<Stack> <Stack>
<Tooltip title={mangaTitle} placement="right"> <Tooltip title={mangaTitle} placement="right">
<TypographyMaxLines lines={3} variant="h6" component="h1" sx={{ textAlign: 'center' }}> <TypographyMaxLines lines={3} variant="h6" component="h1" sx={{ textAlign: 'center' }}>
<Link component={RouterLink} to={`/manga/${mangaId}`} sx={{ textDecoration: 'none', color: 'inherit' }}> <Link
component={RouterLink}
to={AppRoutes.manga.path(mangaId)}
sx={{ textDecoration: 'none', color: 'inherit' }}
>
{mangaTitle} {mangaTitle}
</Link> </Link>
</TypographyMaxLines> </TypographyMaxLines>

View File

@@ -27,6 +27,7 @@ import { useReaderStateMangaContext } from '@/modules/reader/contexts/state/Read
import { applyStyles } from '@/modules/core/utils/ApplyStyles.ts'; import { applyStyles } from '@/modules/core/utils/ApplyStyles.ts';
import { isContinuousReadingMode } from '@/modules/reader/utils/ReaderSettings.utils.tsx'; import { isContinuousReadingMode } from '@/modules/reader/utils/ReaderSettings.utils.tsx';
import { useNavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx'; import { useNavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
const ChapterInfo = ({ const ChapterInfo = ({
title, title,
@@ -173,7 +174,7 @@ export const ReaderTransitionPage = ({
e.stopPropagation(); e.stopPropagation();
}} }}
variant="contained" variant="contained"
to={`/manga/${manga?.id}`} to={AppRoutes.manga.path(manga?.id ?? -1)}
> >
{t('reader.transition_page.exit.manga_page')} {t('reader.transition_page.exit.manga_page')}
</Button> </Button>

View File

@@ -43,6 +43,7 @@ import {
} from '@/modules/reader/utils/Reader.utils.ts'; } from '@/modules/reader/utils/Reader.utils.ts';
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts'; import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
import { Queue } from '@/lib/Queue.ts'; import { Queue } from '@/lib/Queue.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
const DIRECTION_TO_INVERTED: Record<Direction, Direction> = { const DIRECTION_TO_INVERTED: Record<Direction, Direction> = {
ltr: 'rtl', ltr: 'rtl',
@@ -330,7 +331,7 @@ export class ReaderService {
return () => {}; return () => {};
} }
return navigate(`/manga/${manga.id}`); return navigate(AppRoutes.manga.path(manga.id));
}, [manga]); }, [manga]);
switch (exitMode) { switch (exitMode) {

View File

@@ -29,6 +29,7 @@ import { ListItemLink } from '@/modules/core/components/ListItemLink.tsx';
import { NavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx'; import { NavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx';
import { requestManager } from '@/lib/requests/RequestManager.ts'; import { requestManager } from '@/lib/requests/RequestManager.ts';
import { makeToast } from '@/modules/core/utils/Toast.ts'; import { makeToast } from '@/modules/core/utils/Toast.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
export function Settings() { export function Settings() {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -57,43 +58,43 @@ export function Settings() {
return ( return (
<List sx={{ padding: 0 }}> <List sx={{ padding: 0 }}>
<ListItemLink to="/settings/appearance"> <ListItemLink to={AppRoutes.settings.childRoutes.appearance.path}>
<ListItemIcon> <ListItemIcon>
<PaletteIcon /> <PaletteIcon />
</ListItemIcon> </ListItemIcon>
<ListItemText primary={t('settings.appearance.title')} /> <ListItemText primary={t('settings.appearance.title')} />
</ListItemLink> </ListItemLink>
<ListItemLink to="/settings/categories"> <ListItemLink to={AppRoutes.settings.childRoutes.categories.path}>
<ListItemIcon> <ListItemIcon>
<ListAltIcon /> <ListAltIcon />
</ListItemIcon> </ListItemIcon>
<ListItemText primary={t('category.title.category_other')} /> <ListItemText primary={t('category.title.category_other')} />
</ListItemLink> </ListItemLink>
<ListItemLink to="/settings/reader"> <ListItemLink to={AppRoutes.settings.childRoutes.reader.path}>
<ListItemIcon> <ListItemIcon>
<AutoStoriesIcon /> <AutoStoriesIcon />
</ListItemIcon> </ListItemIcon>
<ListItemText primary={t('reader.settings.title.reader_settings')} /> <ListItemText primary={t('reader.settings.title.reader_settings')} />
</ListItemLink> </ListItemLink>
<ListItemLink to="/settings/library"> <ListItemLink to={AppRoutes.settings.childRoutes.library.path}>
<ListItemIcon> <ListItemIcon>
<CollectionsOutlinedBookmarkIcon /> <CollectionsOutlinedBookmarkIcon />
</ListItemIcon> </ListItemIcon>
<ListItemText primary={t('library.title')} /> <ListItemText primary={t('library.title')} />
</ListItemLink> </ListItemLink>
<ListItemLink to="/settings/download"> <ListItemLink to={AppRoutes.settings.childRoutes.download.path}>
<ListItemIcon> <ListItemIcon>
<GetAppOutlinedIcon /> <GetAppOutlinedIcon />
</ListItemIcon> </ListItemIcon>
<ListItemText primary={t('download.title')} /> <ListItemText primary={t('download.title')} />
</ListItemLink> </ListItemLink>
<ListItemLink to="/settings/tracking"> <ListItemLink to={AppRoutes.settings.childRoutes.tracking.path}>
<ListItemIcon> <ListItemIcon>
<SyncIcon /> <SyncIcon />
</ListItemIcon> </ListItemIcon>
<ListItemText primary={t('tracking.title')} /> <ListItemText primary={t('tracking.title')} />
</ListItemLink> </ListItemLink>
<ListItemLink to="/settings/backup"> <ListItemLink to={AppRoutes.settings.childRoutes.backup.path}>
<ListItemIcon> <ListItemIcon>
<BackupIcon /> <BackupIcon />
</ListItemIcon> </ListItemIcon>
@@ -109,31 +110,31 @@ export function Settings() {
secondary={t('settings.clear_cache.label.description')} secondary={t('settings.clear_cache.label.description')}
/> />
</ListItemButton> </ListItemButton>
<ListItemLink to="/settings/browse"> <ListItemLink to={AppRoutes.settings.childRoutes.browse.path}>
<ListItemIcon> <ListItemIcon>
<ExploreOutlinedIcon /> <ExploreOutlinedIcon />
</ListItemIcon> </ListItemIcon>
<ListItemText primary={t('global.label.browse')} /> <ListItemText primary={t('global.label.browse')} />
</ListItemLink> </ListItemLink>
<ListItemLink to="/settings/device"> <ListItemLink to={AppRoutes.settings.childRoutes.device.path}>
<ListItemIcon> <ListItemIcon>
<DevicesIcon /> <DevicesIcon />
</ListItemIcon> </ListItemIcon>
<ListItemText primary={t('settings.device.title.device')} /> <ListItemText primary={t('settings.device.title.device')} />
</ListItemLink> </ListItemLink>
<ListItemLink to="/settings/webui"> <ListItemLink to={AppRoutes.settings.childRoutes.webui.path}>
<ListItemIcon> <ListItemIcon>
<WebIcon /> <WebIcon />
</ListItemIcon> </ListItemIcon>
<ListItemText primary={t('settings.webui.title.webui')} /> <ListItemText primary={t('settings.webui.title.webui')} />
</ListItemLink> </ListItemLink>
<ListItemLink to="/settings/server"> <ListItemLink to={AppRoutes.settings.childRoutes.server.path}>
<ListItemIcon> <ListItemIcon>
<DnsIcon /> <DnsIcon />
</ListItemIcon> </ListItemIcon>
<ListItemText primary={t('settings.server.title.server')} /> <ListItemText primary={t('settings.server.title.server')} />
</ListItemLink> </ListItemLink>
<ListItemLink to="/settings/about"> <ListItemLink to={AppRoutes.settings.childRoutes.about.path}>
<ListItemIcon> <ListItemIcon>
<InfoIcon /> <InfoIcon />
</ListItemIcon> </ListItemIcon>

View File

@@ -23,6 +23,7 @@ import { SpinnerImage } from '@/modules/core/components/SpinnerImage.tsx';
import { GetSourcesListQuery } from '@/lib/graphql/generated/graphql.ts'; import { GetSourcesListQuery } from '@/lib/graphql/generated/graphql.ts';
import { MediaQuery } from '@/modules/core/utils/MediaQuery.tsx'; import { MediaQuery } from '@/modules/core/utils/MediaQuery.tsx';
import { translateExtensionLanguage } from '@/modules/extension/Extensions.utils.ts'; import { translateExtensionLanguage } from '@/modules/extension/Extensions.utils.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
interface IProps { interface IProps {
source: GetSourcesListQuery['sources']['nodes'][number]; source: GetSourcesListQuery['sources']['nodes'][number];
@@ -59,7 +60,7 @@ export const SourceCard: React.FC<IProps> = (props: IProps) => {
> >
<CardActionArea <CardActionArea
component={Link} component={Link}
to={`/sources/${id}`} to={AppRoutes.sources.childRoutes.browse.path(id)}
state={{ contentType: SourceContentType.POPULAR, clearCache: true }} state={{ contentType: SourceContentType.POPULAR, clearCache: true }}
> >
<CardContent <CardContent
@@ -135,7 +136,7 @@ export const SourceCard: React.FC<IProps> = (props: IProps) => {
<Button <Button
variant="outlined" variant="outlined"
component={Link} component={Link}
to={`/sources/${id}`} to={AppRoutes.sources.childRoutes.browse.path(id)}
state={{ contentType: SourceContentType.LATEST, clearCache: true }} state={{ contentType: SourceContentType.LATEST, clearCache: true }}
> >
{t('global.button.latest')} {t('global.button.latest')}
@@ -145,7 +146,7 @@ export const SourceCard: React.FC<IProps> = (props: IProps) => {
<Button <Button
variant="outlined" variant="outlined"
component={Link} component={Link}
to={`/sources/${id}`} to={AppRoutes.sources.childRoutes.browse.path(id)}
state={{ contentType: SourceContentType.POPULAR, clearCache: true }} state={{ contentType: SourceContentType.POPULAR, clearCache: true }}
> >
{t('global.button.popular')} {t('global.button.popular')}

View File

@@ -51,6 +51,7 @@ import { EmptyView } from '@/modules/core/components/placeholder/EmptyView.tsx';
import { EmptyViewAbsoluteCentered } from '@/modules/core/components/placeholder/EmptyViewAbsoluteCentered.tsx'; import { EmptyViewAbsoluteCentered } from '@/modules/core/components/placeholder/EmptyViewAbsoluteCentered.tsx';
import { MangaIdInfo } from '@/modules/manga/Manga.types.ts'; import { MangaIdInfo } from '@/modules/manga/Manga.types.ts';
import { GridLayout } from '@/modules/core/Core.types.ts'; import { GridLayout } from '@/modules/core/Core.types.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
const DEFAULT_SOURCE: Pick<SourceType, 'id'> = { id: '-1' }; const DEFAULT_SOURCE: Pick<SourceType, 'id'> = { id: '-1' };
@@ -407,7 +408,7 @@ export function SourceMangas() {
{source?.isConfigurable && ( {source?.isConfigurable && (
<Tooltip title={t('settings.title')}> <Tooltip title={t('settings.title')}>
<IconButton <IconButton
onClick={() => navigate(`/sources/${sourceId}/configure/`)} onClick={() => navigate(AppRoutes.sources.childRoutes.configure.path(sourceId))}
aria-label="display more actions" aria-label="display more actions"
edge="end" edge="end"
color="inherit" color="inherit"

View File

@@ -29,6 +29,7 @@ import { EmptyViewAbsoluteCentered } from '@/modules/core/components/placeholder
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts'; import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
import { SourceType } from '@/lib/graphql/generated/graphql.ts'; import { SourceType } from '@/lib/graphql/generated/graphql.ts';
import { translateExtensionLanguage } from '@/modules/extension/Extensions.utils.ts'; import { translateExtensionLanguage } from '@/modules/extension/Extensions.utils.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
function sourceToLangList(sources: Pick<SourceType, 'id' | 'lang'>[]) { function sourceToLangList(sources: Pick<SourceType, 'id' | 'lang'>[]) {
const result = new Set<string>(); const result = new Set<string>();
@@ -107,7 +108,11 @@ export function Sources() {
setAction( setAction(
<> <>
<Tooltip title={t('search.title.global_search')}> <Tooltip title={t('search.title.global_search')}>
<IconButton onClick={() => navigate('/sources/all/search/')} size="large" color="inherit"> <IconButton
onClick={() => navigate(AppRoutes.sources.childRoutes.searchAll.path)}
size="large"
color="inherit"
>
<TravelExploreIcon /> <TravelExploreIcon />
</IconButton> </IconButton>
</Tooltip> </Tooltip>

View File

@@ -22,6 +22,7 @@ import { GetMangaTrackRecordsQuery, GetTrackersBindQuery, MangaType } from '@/li
import { GET_TRACKERS_BIND } from '@/lib/graphql/queries/TrackerQuery.ts'; import { GET_TRACKERS_BIND } from '@/lib/graphql/queries/TrackerQuery.ts';
import { GET_MANGA_TRACK_RECORDS } from '@/lib/graphql/queries/MangaQuery.ts'; import { GET_MANGA_TRACK_RECORDS } from '@/lib/graphql/queries/MangaQuery.ts';
import { MangaIdInfo } from '@/modules/manga/Manga.types.ts'; import { MangaIdInfo } from '@/modules/manga/Manga.types.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
const getTrackerMode = (id: number, trackersInUse: number[], searchModeForTracker?: number): TrackerMode => { const getTrackerMode = (id: number, trackersInUse: number[], searchModeForTracker?: number): TrackerMode => {
if (id === searchModeForTracker) { if (id === searchModeForTracker) {
@@ -64,7 +65,7 @@ export const TrackManga = ({ manga }: { manga: MangaIdInfo & Pick<MangaType, 'ti
useEffect(() => { useEffect(() => {
if (!loading && !error && !trackersInUse.length && !loggedInTrackers.length) { if (!loading && !error && !trackersInUse.length && !loggedInTrackers.length) {
navigate('/settings/tracking'); navigate(AppRoutes.tracker.path);
} }
}, [loading]); }, [loading]);

View File

@@ -11,6 +11,7 @@ import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { requestManager } from '@/lib/requests/RequestManager.ts'; import { requestManager } from '@/lib/requests/RequestManager.ts';
import { makeToast } from '@/modules/core/utils/Toast.ts'; import { makeToast } from '@/modules/core/utils/Toast.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
export const TrackerOAuthLogin = () => { export const TrackerOAuthLogin = () => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -38,7 +39,7 @@ export const TrackerOAuthLogin = () => {
makeToast(t('tracking.action.login.label.failure', { name: trackerName }), 'error'); makeToast(t('tracking.action.login.label.failure', { name: trackerName }), 'error');
} }
navigate('/settings/tracking', { replace: true }); navigate(AppRoutes.tracker.path, { replace: true });
}; };
login(); login();

View File

@@ -37,6 +37,7 @@ import { TypographyMaxLines } from '@/modules/core/components/TypographyMaxLines
import { ChapterIdInfo, ChapterMangaInfo } from '@/modules/chapter/services/Chapters.ts'; import { ChapterIdInfo, ChapterMangaInfo } from '@/modules/chapter/services/Chapters.ts';
import { makeToast } from '@/modules/core/utils/Toast.ts'; import { makeToast } from '@/modules/core/utils/Toast.ts';
import { VirtuosoUtil } from '@/lib/virtuoso/Virtuoso.util.tsx'; import { VirtuosoUtil } from '@/lib/virtuoso/Virtuoso.util.tsx';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
const groupByDate = (updates: Pick<ChapterType, 'fetchedAt'>[]): [date: string, items: number][] => { const groupByDate = (updates: Pick<ChapterType, 'fetchedAt'>[]): [date: string, items: number][] => {
if (!updates.length) { if (!updates.length) {
@@ -189,7 +190,7 @@ export const Updates: React.FC = () => {
<Card> <Card>
<CardActionArea <CardActionArea
component={Link} component={Link}
to={`/manga/${chapter.manga.id}/chapter/${chapter.sourceOrder}`} to={AppRoutes.reader.path(chapter.manga.id, chapter.sourceOrder)}
state={location.state} state={location.state}
sx={{ sx={{
color: (theme) => theme.palette.text[chapter.isRead ? 'disabled' : 'primary'], color: (theme) => theme.palette.text[chapter.isRead ? 'disabled' : 'primary'],
@@ -204,7 +205,10 @@ export const Updates: React.FC = () => {
}} }}
> >
<Box sx={{ display: 'flex' }}> <Box sx={{ display: 'flex' }}>
<Link to={`/manga/${chapter.manga.id}`} style={{ textDecoration: 'none' }}> <Link
to={AppRoutes.manga.path(chapter.manga.id)}
style={{ textDecoration: 'none' }}
>
<Avatar <Avatar
variant="rounded" variant="rounded"
sx={{ sx={{