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

@@ -20,6 +20,7 @@ import { VersionUpdateInfoDialog } from '@/modules/app-updates/components/Versio
import { useMetadataServerSettings } from '@/modules/settings/services/ServerSettingsMetadata.ts';
import { useLocalStorage } from '@/modules/core/hooks/useStorage.tsx';
import { getVersion } from '@/modules/app-updates/services/AppUpdateChecker.tsx';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
const disabledUpdateCheck = () => Promise.resolve();
@@ -87,7 +88,7 @@ export const ServerUpdateChecker = () => {
return null;
}
const isAboutPage = window.location.pathname === '/settings/about';
const isAboutPage = window.location.pathname === AppRoutes.settings.childRoutes.about.path;
if (isAboutPage) {
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 { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.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'>;
@@ -357,7 +358,7 @@ export function Backup() {
<Button
onClick={closeInvalidBackupDialog}
component={Link}
to="/browse"
to={AppRoutes.browse.path}
autoFocus={!!validationResult?.missingSources.length}
variant={validationResult?.missingSources.length ? 'contained' : 'text'}
>
@@ -368,7 +369,7 @@ export function Backup() {
<Button
onClick={closeInvalidBackupDialog}
component={Link}
to="/settings/tracking"
to={AppRoutes.tracker.path}
autoFocus={!!validationResult?.missingTrackers.length}
variant={validationResult?.missingTrackers.length ? 'contained' : 'text'}
>

View File

@@ -33,6 +33,7 @@ import {
} from '@/lib/graphql/generated/graphql.ts';
import { GET_CATEGORIES_BASE } from '@/lib/graphql/queries/CategoryQuery.ts';
import { GET_MANGA_CATEGORIES } from '@/lib/graphql/queries/MangaQuery.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
type BaseProps = {
open: boolean;
@@ -232,7 +233,7 @@ export function CategorySelect(props: CategorySelectProps) {
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')}
</Button>
<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 { MangaIdInfo } from '@/modules/manga/Manga.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';
@@ -118,7 +119,7 @@ export class Chapters {
}
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 {

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

View File

@@ -39,6 +39,7 @@ import {
} from '@/modules/extension/Extensions.utils.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 { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
const LANGUAGE = 0;
const EXTENSIONS = 1;
@@ -223,7 +224,7 @@ export function Extensions({ tabsMenuHeight }: { tabsMenuHeight: number }) {
}}
>
<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')}
</Button>
</Stack>

View File

@@ -29,6 +29,7 @@ import { SourceType } from '@/lib/graphql/generated/graphql.ts';
import { BaseMangaGrid } from '@/modules/manga/components/BaseMangaGrid.tsx';
import { EmptyViewAbsoluteCentered } from '@/modules/core/components/placeholder/EmptyViewAbsoluteCentered.tsx';
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 SourceToLoadingStateMap = Map<string, SourceLoadingState>;
@@ -149,7 +150,11 @@ const SourceSearchPreview = React.memo(
return (
<>
<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="caption">{translateExtensionLanguage(lang)}</Typography>
</CardActionArea>

View File

@@ -37,6 +37,7 @@ import {
import { GET_CATEGORIES_SETTINGS } from '@/lib/graphql/queries/CategoryQuery.ts';
import { GET_MANGAS_BASE } from '@/lib/graphql/queries/MangaQuery.ts';
import { MetadataLibrarySettings } from '@/modules/library/Library.types.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
const removeNonLibraryMangasFromCategories = async (): Promise<void> => {
try {
@@ -182,7 +183,7 @@ export function LibrarySettings() {
secondary={t('library.settings.advanced.database.cleanup.label.description')}
/>
</ListItemButton>
<ListItemLink to="duplicates">
<ListItemLink to={AppRoutes.settings.childRoutes.library.childRoutes.duplicates.path}>
<ListItemText
primary={t('library.settings.advanced.duplicates.label.title')}
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 Tooltip from '@mui/material/Tooltip';
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
import { Link } from 'react-router-dom';
import { ChapterReadInfo, Chapters, ChapterSourceOrderInfo } from '@/modules/chapter/services/Chapters.ts';
@@ -39,7 +38,7 @@ export const ContinueReadingButton = ({
size="small"
sx={{ minWidth: 'unset', py: 0.5, px: 0.75 }}
component={Link}
to={`${mangaLinkTo}chapter/${chapter.sourceOrder}`}
to={`${mangaLinkTo}/chapter/${chapter.sourceOrder}`}
state={{ resumeMode: Chapters.getReaderResumeMode(chapter) }}
onClick={(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 { MangaAction, MangaDownloadInfo, MangaIdInfo, MangaUnreadInfo } from '@/modules/manga/Manga.types.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;
@@ -156,7 +157,7 @@ export const MangaActionMenuItems = ({
)}
{isSingleMode && (
<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 }}
style={{ textDecoration: 'none', color: 'inherit' }}
>

View File

@@ -23,6 +23,7 @@ import { useTheme } from '@mui/material/styles';
import useMediaQuery from '@mui/material/useMediaQuery';
import { useCategorySelect } from '@/modules/category/hooks/useCategorySelect.tsx';
import { MangaType } from '@/lib/graphql/generated/graphql.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
interface IProps {
manga: Pick<MangaType, 'id' | 'inLibrary' | 'sourceId' | 'title'>;
@@ -65,7 +66,7 @@ export const MangaToolbarMenu = ({ manga, onRefresh, refreshing }: IProps) => {
<>
<Tooltip title={t('global.button.migrate')}>
<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 }}
style={{ textDecoration: 'none', color: 'inherit' }}
>
@@ -125,7 +126,7 @@ export const MangaToolbarMenu = ({ manga, onRefresh, refreshing }: IProps) => {
<MenuItem
key="migrate"
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 }}
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 { GetTrackersSettingsQuery, MangaType } from '@/lib/graphql/generated/graphql.ts';
import { GET_TRACKERS_SETTINGS } from '@/lib/graphql/queries/TrackerQuery.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'> }) => {
const { t } = useTranslation();
@@ -40,7 +40,7 @@ export const TrackMangaButton = ({ manga }: { manga: MangaTrackRecordInfo & Pick
}
if (!loggedInTrackers.length) {
navigate('/settings/tracking');
navigate(AppRoutes.tracker.path);
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 { MangaBadges } from '@/modules/manga/components/MangaBadges.tsx';
import { GridLayout } from '@/modules/core/Core.types.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
const getMangaLinkTo = (
mode: MangaCardMode,
@@ -31,9 +32,9 @@ const getMangaLinkTo = (
case 'default':
case 'source':
case 'duplicate':
return `/manga/${mangaId}/`;
return AppRoutes.manga.path(mangaId);
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':
return '';
default:

View File

@@ -20,6 +20,7 @@ import { Mangas } from '@/modules/manga/services/Mangas.ts';
import { awaitConfirmation } from '@/modules/core/utils/AwaitableDialog.tsx';
import { GetCategoriesBaseQuery, GetCategoriesBaseQueryVariables, MangaType } from '@/lib/graphql/generated/graphql.ts';
import { GET_CATEGORIES_BASE } from '@/lib/graphql/queries/CategoryQuery.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
export const useManageMangaLibraryState = (
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 },
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 { MetadataMigrationSettings } from '@/modules/migration/Migration.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 }) => {
const { t } = useTranslation();
@@ -63,7 +64,7 @@ export const MigrateDialog = ({ mangaIdToMigrateTo, onClose }: { mangaIdToMigrat
deleteChapters,
});
navigate(`/manga/${mangaIdToMigrateTo}`, { replace: true });
navigate(AppRoutes.manga.path(mangaIdToMigrateTo), { replace: true });
} catch (e) {
setIsMigrationInProcess(false);
}
@@ -108,7 +109,11 @@ export const MigrateDialog = ({ mangaIdToMigrateTo, onClose }: { mangaIdToMigrat
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')}
</Button>
<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 { SpinnerImage } from '@/modules/core/components/SpinnerImage.tsx';
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']> & {
mangaCount: number;
@@ -33,7 +34,7 @@ export const MigrationCard = ({ id, name, lang, iconUrl, mangaCount }: TMigratab
return (
<Card>
<CardActionArea component={Link} to={`/migrate/source/${id}/`}>
<CardActionArea component={Link} to={AppRoutes.migrate.path(id)}>
<CardContent
sx={{
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 { MobileBottomBar } from '@/modules/navigation-bar/components/MobileBottomBar.tsx';
import { NavbarItem } from '@/modules/navigation-bar/NavigationBar.types.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
const navbarItems: Array<NavbarItem> = [
{
path: '/library',
path: AppRoutes.library.path,
title: 'library.title',
SelectedIconComponent: CollectionsBookmarkIcon,
IconComponent: CollectionsOutlinedBookmarkIcon,
show: 'both',
},
{
path: '/updates',
path: AppRoutes.updates.path,
title: 'updates.title',
SelectedIconComponent: NewReleasesIcon,
IconComponent: NewReleasesOutlinedIcon,
show: 'both',
},
{
path: '/browse',
path: AppRoutes.browse.path,
title: 'global.label.browse',
SelectedIconComponent: ExploreIcon,
IconComponent: ExploreOutlinedIcon,
show: 'both',
},
{
path: '/downloads',
path: AppRoutes.downloads.path,
title: 'download.title',
SelectedIconComponent: GetAppIcon,
IconComponent: GetAppOutlinedIcon,
show: 'both',
},
{
path: '/settings',
path: AppRoutes.settings.path,
title: 'settings.title',
SelectedIconComponent: 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 { useReaderStateChaptersContext } from '@/modules/reader/contexts/state/ReaderStateChaptersContext.tsx';
import { LoadingPlaceholder } from '@/modules/core/components/placeholder/LoadingPlaceholder';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
const DEFAULT_MANGA = { id: -1, title: '' };
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">
<Link
component={RouterLink}
to={`/manga/${mangaId}`}
to={AppRoutes.manga.path(mangaId)}
sx={{ textDecoration: 'none', color: 'inherit' }}
>
{title}

View File

@@ -12,6 +12,7 @@ import Link from '@mui/material/Link';
import { Link as RouterLink } from 'react-router-dom';
import { TypographyMaxLines } from '@/modules/core/components/TypographyMaxLines';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
export const ReaderNavBarDesktopMetadata = ({
mangaId,
@@ -25,7 +26,11 @@ export const ReaderNavBarDesktopMetadata = ({
<Stack>
<Tooltip title={mangaTitle} placement="right">
<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}
</Link>
</TypographyMaxLines>

View File

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

View File

@@ -43,6 +43,7 @@ import {
} from '@/modules/reader/utils/Reader.utils.ts';
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
import { Queue } from '@/lib/Queue.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
const DIRECTION_TO_INVERTED: Record<Direction, Direction> = {
ltr: 'rtl',
@@ -330,7 +331,7 @@ export class ReaderService {
return () => {};
}
return navigate(`/manga/${manga.id}`);
return navigate(AppRoutes.manga.path(manga.id));
}, [manga]);
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 { requestManager } from '@/lib/requests/RequestManager.ts';
import { makeToast } from '@/modules/core/utils/Toast.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
export function Settings() {
const { t } = useTranslation();
@@ -57,43 +58,43 @@ export function Settings() {
return (
<List sx={{ padding: 0 }}>
<ListItemLink to="/settings/appearance">
<ListItemLink to={AppRoutes.settings.childRoutes.appearance.path}>
<ListItemIcon>
<PaletteIcon />
</ListItemIcon>
<ListItemText primary={t('settings.appearance.title')} />
</ListItemLink>
<ListItemLink to="/settings/categories">
<ListItemLink to={AppRoutes.settings.childRoutes.categories.path}>
<ListItemIcon>
<ListAltIcon />
</ListItemIcon>
<ListItemText primary={t('category.title.category_other')} />
</ListItemLink>
<ListItemLink to="/settings/reader">
<ListItemLink to={AppRoutes.settings.childRoutes.reader.path}>
<ListItemIcon>
<AutoStoriesIcon />
</ListItemIcon>
<ListItemText primary={t('reader.settings.title.reader_settings')} />
</ListItemLink>
<ListItemLink to="/settings/library">
<ListItemLink to={AppRoutes.settings.childRoutes.library.path}>
<ListItemIcon>
<CollectionsOutlinedBookmarkIcon />
</ListItemIcon>
<ListItemText primary={t('library.title')} />
</ListItemLink>
<ListItemLink to="/settings/download">
<ListItemLink to={AppRoutes.settings.childRoutes.download.path}>
<ListItemIcon>
<GetAppOutlinedIcon />
</ListItemIcon>
<ListItemText primary={t('download.title')} />
</ListItemLink>
<ListItemLink to="/settings/tracking">
<ListItemLink to={AppRoutes.settings.childRoutes.tracking.path}>
<ListItemIcon>
<SyncIcon />
</ListItemIcon>
<ListItemText primary={t('tracking.title')} />
</ListItemLink>
<ListItemLink to="/settings/backup">
<ListItemLink to={AppRoutes.settings.childRoutes.backup.path}>
<ListItemIcon>
<BackupIcon />
</ListItemIcon>
@@ -109,31 +110,31 @@ export function Settings() {
secondary={t('settings.clear_cache.label.description')}
/>
</ListItemButton>
<ListItemLink to="/settings/browse">
<ListItemLink to={AppRoutes.settings.childRoutes.browse.path}>
<ListItemIcon>
<ExploreOutlinedIcon />
</ListItemIcon>
<ListItemText primary={t('global.label.browse')} />
</ListItemLink>
<ListItemLink to="/settings/device">
<ListItemLink to={AppRoutes.settings.childRoutes.device.path}>
<ListItemIcon>
<DevicesIcon />
</ListItemIcon>
<ListItemText primary={t('settings.device.title.device')} />
</ListItemLink>
<ListItemLink to="/settings/webui">
<ListItemLink to={AppRoutes.settings.childRoutes.webui.path}>
<ListItemIcon>
<WebIcon />
</ListItemIcon>
<ListItemText primary={t('settings.webui.title.webui')} />
</ListItemLink>
<ListItemLink to="/settings/server">
<ListItemLink to={AppRoutes.settings.childRoutes.server.path}>
<ListItemIcon>
<DnsIcon />
</ListItemIcon>
<ListItemText primary={t('settings.server.title.server')} />
</ListItemLink>
<ListItemLink to="/settings/about">
<ListItemLink to={AppRoutes.settings.childRoutes.about.path}>
<ListItemIcon>
<InfoIcon />
</ListItemIcon>

View File

@@ -23,6 +23,7 @@ import { SpinnerImage } from '@/modules/core/components/SpinnerImage.tsx';
import { GetSourcesListQuery } from '@/lib/graphql/generated/graphql.ts';
import { MediaQuery } from '@/modules/core/utils/MediaQuery.tsx';
import { translateExtensionLanguage } from '@/modules/extension/Extensions.utils.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
interface IProps {
source: GetSourcesListQuery['sources']['nodes'][number];
@@ -59,7 +60,7 @@ export const SourceCard: React.FC<IProps> = (props: IProps) => {
>
<CardActionArea
component={Link}
to={`/sources/${id}`}
to={AppRoutes.sources.childRoutes.browse.path(id)}
state={{ contentType: SourceContentType.POPULAR, clearCache: true }}
>
<CardContent
@@ -135,7 +136,7 @@ export const SourceCard: React.FC<IProps> = (props: IProps) => {
<Button
variant="outlined"
component={Link}
to={`/sources/${id}`}
to={AppRoutes.sources.childRoutes.browse.path(id)}
state={{ contentType: SourceContentType.LATEST, clearCache: true }}
>
{t('global.button.latest')}
@@ -145,7 +146,7 @@ export const SourceCard: React.FC<IProps> = (props: IProps) => {
<Button
variant="outlined"
component={Link}
to={`/sources/${id}`}
to={AppRoutes.sources.childRoutes.browse.path(id)}
state={{ contentType: SourceContentType.POPULAR, clearCache: true }}
>
{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 { MangaIdInfo } from '@/modules/manga/Manga.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' };
@@ -407,7 +408,7 @@ export function SourceMangas() {
{source?.isConfigurable && (
<Tooltip title={t('settings.title')}>
<IconButton
onClick={() => navigate(`/sources/${sourceId}/configure/`)}
onClick={() => navigate(AppRoutes.sources.childRoutes.configure.path(sourceId))}
aria-label="display more actions"
edge="end"
color="inherit"

View File

@@ -29,6 +29,7 @@ import { EmptyViewAbsoluteCentered } from '@/modules/core/components/placeholder
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
import { SourceType } from '@/lib/graphql/generated/graphql.ts';
import { translateExtensionLanguage } from '@/modules/extension/Extensions.utils.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
function sourceToLangList(sources: Pick<SourceType, 'id' | 'lang'>[]) {
const result = new Set<string>();
@@ -107,7 +108,11 @@ export function Sources() {
setAction(
<>
<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 />
</IconButton>
</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_MANGA_TRACK_RECORDS } from '@/lib/graphql/queries/MangaQuery.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 => {
if (id === searchModeForTracker) {
@@ -64,7 +65,7 @@ export const TrackManga = ({ manga }: { manga: MangaIdInfo & Pick<MangaType, 'ti
useEffect(() => {
if (!loading && !error && !trackersInUse.length && !loggedInTrackers.length) {
navigate('/settings/tracking');
navigate(AppRoutes.tracker.path);
}
}, [loading]);

View File

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

View File

@@ -37,6 +37,7 @@ import { TypographyMaxLines } from '@/modules/core/components/TypographyMaxLines
import { ChapterIdInfo, ChapterMangaInfo } from '@/modules/chapter/services/Chapters.ts';
import { makeToast } from '@/modules/core/utils/Toast.ts';
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][] => {
if (!updates.length) {
@@ -189,7 +190,7 @@ export const Updates: React.FC = () => {
<Card>
<CardActionArea
component={Link}
to={`/manga/${chapter.manga.id}/chapter/${chapter.sourceOrder}`}
to={AppRoutes.reader.path(chapter.manga.id, chapter.sourceOrder)}
state={location.state}
sx={{
color: (theme) => theme.palette.text[chapter.isRead ? 'disabled' : 'primary'],
@@ -204,7 +205,10 @@ export const Updates: React.FC = () => {
}}
>
<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
variant="rounded"
sx={{