Add bulk migration
This commit is contained in:
@@ -159,12 +159,20 @@
|
|||||||
"!@mui/material/",
|
"!@mui/material/",
|
||||||
"!@mui/icons-material/",
|
"!@mui/icons-material/",
|
||||||
"!@mui/x-date-pickers/"
|
"!@mui/x-date-pickers/"
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"group": [
|
"group": [
|
||||||
"@mui/*/*/*"
|
"@mui/*/*/*"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": [
|
||||||
|
"lodash",
|
||||||
|
"lodash/*",
|
||||||
|
"!lodash/fp/*"
|
||||||
|
],
|
||||||
|
"message": "Use lodash-fp instead."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -411,6 +419,7 @@
|
|||||||
"src/features/authentication/AuthManager.ts",
|
"src/features/authentication/AuthManager.ts",
|
||||||
"src/features/chapter/services/Chapters.ts",
|
"src/features/chapter/services/Chapters.ts",
|
||||||
"src/features/metadata/services/MetadataMigrations.ts",
|
"src/features/metadata/services/MetadataMigrations.ts",
|
||||||
|
"src/features/migration/MigrationManager.ts",
|
||||||
"src/features/reader/services/ReaderControls.ts",
|
"src/features/reader/services/ReaderControls.ts",
|
||||||
"src/features/reader/services/ReaderService.ts",
|
"src/features/reader/services/ReaderService.ts",
|
||||||
"src/features/source/services/Sources.ts",
|
"src/features/source/services/Sources.ts",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|||||||
- (**WebUI Update**) Add an option to (partially) disable showing information when the webUI got updated
|
- (**WebUI Update**) Add an option to (partially) disable showing information when the webUI got updated
|
||||||
- (**Server Update**) Add an option to disable showing information when the server got updated
|
- (**Server Update**) Add an option to disable showing information when the server got updated
|
||||||
- (**Manga**) Add an option to include client data during migration
|
- (**Manga**) Add an option to include client data during migration
|
||||||
|
- (**Migrate**) Add bulk migration
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,7 @@
|
|||||||
"immer": "11.1.4",
|
"immer": "11.1.4",
|
||||||
"jsonrepair": "3.13.2",
|
"jsonrepair": "3.13.2",
|
||||||
"koration": "1.0.0",
|
"koration": "1.0.0",
|
||||||
|
"lodash": "4.18.1",
|
||||||
"material-ui-popup-state": "5.3.6",
|
"material-ui-popup-state": "5.3.6",
|
||||||
"mui-nested-menu": "4.0.3",
|
"mui-nested-menu": "4.0.3",
|
||||||
"node-vibrant": "4.0.4",
|
"node-vibrant": "4.0.4",
|
||||||
@@ -96,6 +97,7 @@
|
|||||||
"@lingui/swc-plugin": "5.11.0",
|
"@lingui/swc-plugin": "5.11.0",
|
||||||
"@lingui/vite-plugin": "5.9.2",
|
"@lingui/vite-plugin": "5.9.2",
|
||||||
"@tony.ganchev/eslint-plugin-header": "3.3.4",
|
"@tony.ganchev/eslint-plugin-header": "3.3.4",
|
||||||
|
"@types/lodash": "4.17.24",
|
||||||
"@types/node": "24.10.1",
|
"@types/node": "24.10.1",
|
||||||
"@types/react": "19.2.14",
|
"@types/react": "19.2.14",
|
||||||
"@types/react-beautiful-dnd": "13.1.8",
|
"@types/react-beautiful-dnd": "13.1.8",
|
||||||
|
|||||||
20
src/App.tsx
20
src/App.tsx
@@ -32,12 +32,17 @@ import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts'
|
|||||||
import { ReactRouter } from '@/lib/react-router/ReactRouter.ts';
|
import { ReactRouter } from '@/lib/react-router/ReactRouter.ts';
|
||||||
import { AuthManager } from '@/features/authentication/AuthManager.ts';
|
import { AuthManager } from '@/features/authentication/AuthManager.ts';
|
||||||
import { ImageProcessingType } from '@/features/settings/Settings.types.ts';
|
import { ImageProcessingType } from '@/features/settings/Settings.types.ts';
|
||||||
|
import { MigrationFABIndicator } from '@/features/migration/components/MigrationFABIndicator.tsx';
|
||||||
|
|
||||||
const { Browse } = loadable(() => import('@/features/browse/screens/Browse.tsx'), lazyLoadFallback);
|
const { Browse } = loadable(() => import('@/features/browse/screens/Browse.tsx'), lazyLoadFallback);
|
||||||
const { DownloadQueue } = loadable(() => import('@/features/downloads/screens/DownloadQueue.tsx'), lazyLoadFallback);
|
const { DownloadQueue } = loadable(() => import('@/features/downloads/screens/DownloadQueue.tsx'), lazyLoadFallback);
|
||||||
const { Library } = loadable(() => import('@/features/library/screens/Library.tsx'), lazyLoadFallback);
|
const { Library } = loadable(() => import('@/features/library/screens/Library.tsx'), lazyLoadFallback);
|
||||||
const { Manga } = loadable(() => import('@/features/manga/screens/Manga.tsx'), lazyLoadFallback);
|
const { Manga } = loadable(() => import('@/features/manga/screens/Manga.tsx'), lazyLoadFallback);
|
||||||
const { SearchAll } = loadable(() => import('@/features/global-search/screens/SearchAll.tsx'), lazyLoadFallback);
|
const { SearchAll } = loadable(() => import('@/features/global-search/screens/SearchAll.tsx'), lazyLoadFallback);
|
||||||
|
const { MigrationManualSearch } = loadable(
|
||||||
|
() => import('@/features/migration/screens/MigrationManualSearch.tsx'),
|
||||||
|
lazyLoadFallback,
|
||||||
|
);
|
||||||
const { Settings } = loadable(() => import('@/features/settings/screens/Settings.tsx'), lazyLoadFallback);
|
const { Settings } = loadable(() => import('@/features/settings/screens/Settings.tsx'), lazyLoadFallback);
|
||||||
const { About } = loadable(() => import('@/features/settings/screens/About.tsx'), lazyLoadFallback);
|
const { About } = loadable(() => import('@/features/settings/screens/About.tsx'), lazyLoadFallback);
|
||||||
const { Backup } = loadable(() => import('@/features/backup/screens/Backup.tsx'), lazyLoadFallback);
|
const { Backup } = loadable(() => import('@/features/backup/screens/Backup.tsx'), lazyLoadFallback);
|
||||||
@@ -69,7 +74,7 @@ const { ImageProcessingSetting } = loadable(
|
|||||||
const { ServerSettings } = loadable(() => import('@/features/settings/screens/ServerSettings.tsx'), lazyLoadFallback);
|
const { ServerSettings } = loadable(() => import('@/features/settings/screens/ServerSettings.tsx'), lazyLoadFallback);
|
||||||
const { BrowseSettings } = loadable(() => import('@/features/browse/screens/BrowseSettings.tsx'), lazyLoadFallback);
|
const { BrowseSettings } = loadable(() => import('@/features/browse/screens/BrowseSettings.tsx'), lazyLoadFallback);
|
||||||
const { WebUISettings } = loadable(() => import('@/features/settings/screens/WebUISettings.tsx'), lazyLoadFallback);
|
const { WebUISettings } = loadable(() => import('@/features/settings/screens/WebUISettings.tsx'), lazyLoadFallback);
|
||||||
const { Migrate } = loadable(() => import('@/features/migration/screens/Migrate.tsx'), lazyLoadFallback);
|
const { Migration } = loadable(() => import('@/features/migration/screens/Migration.tsx'), lazyLoadFallback);
|
||||||
const { DeviceSetting } = loadable(() => import('@/features/device/screens/DeviceSetting.tsx'), lazyLoadFallback);
|
const { DeviceSetting } = loadable(() => import('@/features/device/screens/DeviceSetting.tsx'), lazyLoadFallback);
|
||||||
const { TrackingSettings } = loadable(
|
const { TrackingSettings } = loadable(
|
||||||
() => import('@/features/tracker/screens/TrackingSettings.tsx'),
|
() => import('@/features/tracker/screens/TrackingSettings.tsx'),
|
||||||
@@ -293,9 +298,17 @@ const MainApp = () => {
|
|||||||
<Route path={AppRoutes.updates.match} element={<Updates />} />
|
<Route path={AppRoutes.updates.match} element={<Updates />} />
|
||||||
{!hideHistory && <Route path={AppRoutes.history.match} element={<History />} />}
|
{!hideHistory && <Route path={AppRoutes.history.match} element={<History />} />}
|
||||||
<Route path={AppRoutes.browse.match} element={<Browse />} />
|
<Route path={AppRoutes.browse.match} element={<Browse />} />
|
||||||
|
<Route path={AppRoutes.browse.match} element={<Browse />} />
|
||||||
<Route path={AppRoutes.migrate.match}>
|
<Route path={AppRoutes.migrate.match}>
|
||||||
<Route index element={<Migrate />} />
|
<Route index element={<Migration />} />
|
||||||
<Route path={AppRoutes.migrate.childRoutes.search.match} element={<SearchAll />} />
|
<Route
|
||||||
|
path={AppRoutes.migrate.childRoutes.singleMangaSearch.match}
|
||||||
|
element={<SearchAll />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path={AppRoutes.migrate.childRoutes.manualSearch.match}
|
||||||
|
element={<MigrationManualSearch />}
|
||||||
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
<Route path={AppRoutes.tracker.match} element={<TrackerOAuthLogin />} />
|
<Route path={AppRoutes.tracker.match} element={<TrackerOAuthLogin />} />
|
||||||
</Route>
|
</Route>
|
||||||
@@ -339,6 +352,7 @@ export const App: React.FC = () => (
|
|||||||
<Route path={AppRoutes.reader.match} element={<ReaderApp />} />
|
<Route path={AppRoutes.reader.match} element={<ReaderApp />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</Box>
|
</Box>
|
||||||
|
<MigrationFABIndicator />
|
||||||
</AuthGuard>
|
</AuthGuard>
|
||||||
</AppContext>
|
</AppContext>
|
||||||
);
|
);
|
||||||
|
|||||||
4
src/UtilTypes.d.ts
vendored
4
src/UtilTypes.d.ts
vendored
@@ -24,8 +24,12 @@ type NullAndUndefined<T> = T | null | undefined;
|
|||||||
|
|
||||||
type NonNullableProperties<T> = { [P in keyof T]-?: NonNullable<T[P]> };
|
type NonNullableProperties<T> = { [P in keyof T]-?: NonNullable<T[P]> };
|
||||||
|
|
||||||
|
type NonNullableProperty<T, K> = Omit<T, K> & NonNullableProperties<Pick<T, K>>;
|
||||||
|
|
||||||
type OptionalProperty<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
|
type OptionalProperty<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
|
||||||
|
|
||||||
|
type RequiredProperty<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
|
||||||
|
|
||||||
type PropertiesNever<T> = { [key in keyof T]?: never };
|
type PropertiesNever<T> = { [key in keyof T]?: never };
|
||||||
|
|
||||||
type OmitFirst<T extends any[]> = T extends [any, ...infer R] ? R : never;
|
type OmitFirst<T extends any[]> = T extends [any, ...infer R] ? R : never;
|
||||||
|
|||||||
@@ -199,15 +199,19 @@ export const AppRoutes = {
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
migrate: {
|
migrate: {
|
||||||
match: 'migrate/source/:sourceId',
|
match: 'migrate/*',
|
||||||
path: (sourceId: SourceType['id']) => `/migrate/source/${sourceId}`,
|
path: '/migrate',
|
||||||
|
|
||||||
childRoutes: {
|
childRoutes: {
|
||||||
search: {
|
singleMangaSearch: {
|
||||||
match: 'manga/:mangaId/search',
|
match: 'source/:sourceId/manga/:mangaId/search',
|
||||||
path: (sourceId: SourceType['id'], mangaId: MangaIdInfo['id'], query?: string | null | undefined) =>
|
path: (sourceId: SourceType['id'], mangaId: MangaIdInfo['id'], query?: string | null | undefined) =>
|
||||||
UrlUtil.addQueryParam(`/migrate/source/${sourceId}/manga/${mangaId}/search`, query),
|
UrlUtil.addQueryParam(`/migrate/source/${sourceId}/manga/${mangaId}/search`, query),
|
||||||
},
|
},
|
||||||
|
manualSearch: {
|
||||||
|
match: 'manual-search/:mangaId',
|
||||||
|
path: (mangaId: MangaIdInfo['id'], query?: string | null | undefined) =>
|
||||||
|
UrlUtil.addQueryParam(`/migrate/manual-search/${mangaId}`, query),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
tracker: {
|
tracker: {
|
||||||
|
|||||||
@@ -18,12 +18,14 @@ export const SelectableCollectionSelectMode = ({
|
|||||||
areNoItemsSelected,
|
areNoItemsSelected,
|
||||||
onSelectAll,
|
onSelectAll,
|
||||||
onModeChange,
|
onModeChange,
|
||||||
|
isCancelable = true,
|
||||||
}: {
|
}: {
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
areAllItemsSelected: boolean;
|
areAllItemsSelected: boolean;
|
||||||
areNoItemsSelected: boolean;
|
areNoItemsSelected: boolean;
|
||||||
onSelectAll: (selectAll: boolean) => void;
|
onSelectAll: (selectAll: boolean) => void;
|
||||||
onModeChange: (checked: boolean) => void;
|
onModeChange: (checked: boolean) => void;
|
||||||
|
isCancelable?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
|
|
||||||
@@ -36,20 +38,22 @@ export const SelectableCollectionSelectMode = ({
|
|||||||
onChange={onSelectAll}
|
onChange={onSelectAll}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<CustomTooltip title={!isActive ? t`Select all` : t`Cancel`}>
|
{isCancelable && (
|
||||||
<Checkbox
|
<CustomTooltip title={!isActive ? t`Select all` : t`Cancel`}>
|
||||||
checkedIcon={<ClearIcon />}
|
<Checkbox
|
||||||
sx={{
|
checkedIcon={<ClearIcon />}
|
||||||
padding: '8px',
|
sx={{
|
||||||
color: 'inherit',
|
padding: '8px',
|
||||||
'&.Mui-checked': {
|
|
||||||
color: 'inherit',
|
color: 'inherit',
|
||||||
},
|
'&.Mui-checked': {
|
||||||
}}
|
color: 'inherit',
|
||||||
checked={isActive}
|
},
|
||||||
onChange={(_, checked) => onModeChange(checked)}
|
}}
|
||||||
/>
|
checked={isActive}
|
||||||
</CustomTooltip>
|
onChange={(_, checked) => onModeChange(checked)}
|
||||||
|
/>
|
||||||
|
</CustomTooltip>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,17 +10,21 @@ import React, { type JSX } from 'react';
|
|||||||
import CircularProgress from '@mui/material/CircularProgress';
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
|
|
||||||
interface IProps {
|
export function LoadingPlaceholder({
|
||||||
|
children,
|
||||||
|
shouldRender,
|
||||||
|
component,
|
||||||
|
componentProps,
|
||||||
|
usePadding,
|
||||||
|
size,
|
||||||
|
}: {
|
||||||
shouldRender?: boolean | (() => boolean);
|
shouldRender?: boolean | (() => boolean);
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
component?: string | React.FunctionComponent<any> | React.ComponentClass<any, any>;
|
component?: string | React.FunctionComponent<any> | React.ComponentClass<any, any>;
|
||||||
componentProps?: any;
|
componentProps?: any;
|
||||||
usePadding?: boolean;
|
usePadding?: boolean;
|
||||||
}
|
size?: number;
|
||||||
|
}) {
|
||||||
export function LoadingPlaceholder(props: IProps) {
|
|
||||||
const { children, shouldRender, component, componentProps, usePadding } = props;
|
|
||||||
|
|
||||||
let condition = true;
|
let condition = true;
|
||||||
if (shouldRender !== undefined) {
|
if (shouldRender !== undefined) {
|
||||||
condition = shouldRender instanceof Function ? shouldRender() : shouldRender;
|
condition = shouldRender instanceof Function ? shouldRender() : shouldRender;
|
||||||
@@ -47,7 +51,7 @@ export function LoadingPlaceholder(props: IProps) {
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CircularProgress thickness={5} />
|
<CircularProgress thickness={5} size={size} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export const SnackbarWithDescription = memo(
|
|||||||
>
|
>
|
||||||
<TitleComponent>{message}</TitleComponent>
|
<TitleComponent>{message}</TitleComponent>
|
||||||
{actualDescription}
|
{actualDescription}
|
||||||
{isDescriptionTooLong || (isGraphqlException && graphqlStackTrace) ? (
|
{(isDescriptionTooLong || (isGraphqlException && graphqlStackTrace)) && (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
Confirmation.show({
|
Confirmation.show({
|
||||||
@@ -108,8 +108,6 @@ export const SnackbarWithDescription = memo(
|
|||||||
>
|
>
|
||||||
{t`Show more`}
|
{t`Show more`}
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
|
||||||
''
|
|
||||||
)}
|
)}
|
||||||
</Alert>
|
</Alert>
|
||||||
</SnackbarContent>
|
</SnackbarContent>
|
||||||
|
|||||||
@@ -15,12 +15,12 @@ import { Extensions } from '@/features/browse/extensions/Extensions.tsx';
|
|||||||
import { TabPanel } from '@/base/components/tabs/TabPanel.tsx';
|
import { TabPanel } from '@/base/components/tabs/TabPanel.tsx';
|
||||||
import { TabsWrapper } from '@/base/components/tabs/TabsWrapper.tsx';
|
import { TabsWrapper } from '@/base/components/tabs/TabsWrapper.tsx';
|
||||||
import { TabsMenu } from '@/base/components/tabs/TabsMenu.tsx';
|
import { TabsMenu } from '@/base/components/tabs/TabsMenu.tsx';
|
||||||
import { Migration } from '@/features/migration/screens/Migration.tsx';
|
|
||||||
import { useResizeObserver } from '@/base/hooks/useResizeObserver.tsx';
|
import { useResizeObserver } from '@/base/hooks/useResizeObserver.tsx';
|
||||||
import { useAppTitle } from '@/features/navigation-bar/hooks/useAppTitle.ts';
|
import { useAppTitle } from '@/features/navigation-bar/hooks/useAppTitle.ts';
|
||||||
import { BrowseTab } from '@/features/browse/Browse.types.ts';
|
import { BrowseTab } from '@/features/browse/Browse.types.ts';
|
||||||
import { GROUPED_VIRTUOSO_Z_INDEX } from '@/lib/virtuoso/Virtuoso.constants.ts';
|
import { GROUPED_VIRTUOSO_Z_INDEX } from '@/lib/virtuoso/Virtuoso.constants.ts';
|
||||||
import { SearchParam } from '@/base/Base.types.ts';
|
import { SearchParam } from '@/base/Base.types.ts';
|
||||||
|
import { Migration } from '@/features/migration/screens/Migration.tsx';
|
||||||
|
|
||||||
export function Browse() {
|
export function Browse() {
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import Card from '@mui/material/Card';
|
|||||||
import CardActionArea from '@mui/material/CardActionArea';
|
import CardActionArea from '@mui/material/CardActionArea';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { Link, useLocation, useNavigate } from 'react-router-dom';
|
import { Link, useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||||
import { StringParam, useQueryParam } from 'use-query-params';
|
import { StringParam, useQueryParam } from 'use-query-params';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import Stack from '@mui/material/Stack';
|
import Stack from '@mui/material/Stack';
|
||||||
@@ -28,7 +28,7 @@ import { AppbarSearch } from '@/base/components/AppbarSearch.tsx';
|
|||||||
import { useDebounce } from '@/base/hooks/useDebounce.ts';
|
import { useDebounce } from '@/base/hooks/useDebounce.ts';
|
||||||
import type { MangaCardProps } from '@/features/manga/Manga.types.ts';
|
import type { MangaCardProps } from '@/features/manga/Manga.types.ts';
|
||||||
import { EmptyView } from '@/base/components/feedback/EmptyView.tsx';
|
import { EmptyView } from '@/base/components/feedback/EmptyView.tsx';
|
||||||
import { STABLE_EMPTY_ARRAY } from '@/base/Base.constants.ts';
|
import { STABLE_EMPTY_ARRAY, STABLE_EMPTY_OBJECT } from '@/base/Base.constants.ts';
|
||||||
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
||||||
import { LoadingPlaceholder } from '@/base/components/feedback/LoadingPlaceholder.tsx';
|
import { LoadingPlaceholder } from '@/base/components/feedback/LoadingPlaceholder.tsx';
|
||||||
import { BaseMangaGrid } from '@/features/manga/components/BaseMangaGrid.tsx';
|
import { BaseMangaGrid } from '@/features/manga/components/BaseMangaGrid.tsx';
|
||||||
@@ -56,6 +56,9 @@ import { MUIUtil } from '@/lib/mui/MUI.util.ts';
|
|||||||
import type { MetadataBrowseSettings } from '@/features/browse/Browse.types.ts';
|
import type { MetadataBrowseSettings } from '@/features/browse/Browse.types.ts';
|
||||||
import { SourceLanguageSelect } from '@/features/source/components/SourceLanguageSelect.tsx';
|
import { SourceLanguageSelect } from '@/features/source/components/SourceLanguageSelect.tsx';
|
||||||
import { SearchParam } from '@/base/Base.types.ts';
|
import { SearchParam } from '@/base/Base.types.ts';
|
||||||
|
import { MigrationManager } from '@/features/migration/MigrationManager.ts';
|
||||||
|
import { assertIsDefined } from '@/base/Asserts.ts';
|
||||||
|
import { useBackButton } from '@/base/hooks/useBackButton.ts';
|
||||||
|
|
||||||
type SourceLoadingState = { isLoading: boolean; hasResults: boolean; emptySearch: boolean; error: any };
|
type SourceLoadingState = { isLoading: boolean; hasResults: boolean; emptySearch: boolean; error: any };
|
||||||
type SourceToLoadingStateMap = Map<string, SourceLoadingState>;
|
type SourceToLoadingStateMap = Map<string, SourceLoadingState>;
|
||||||
@@ -122,12 +125,13 @@ const SourceSearchPreview = React.memo(
|
|||||||
emptyQuery,
|
emptyQuery,
|
||||||
mode,
|
mode,
|
||||||
shouldShowOnlySourcesWithResults,
|
shouldShowOnlySourcesWithResults,
|
||||||
|
onMigrateSelect,
|
||||||
}: {
|
}: {
|
||||||
source: SourceIdInfo & SourceDisplayNameInfo & SourceNameInfo & SourceLanguageInfo;
|
source: SourceIdInfo & SourceDisplayNameInfo & SourceNameInfo & SourceLanguageInfo;
|
||||||
onSearchRequestFinished: (source: SourceIdInfo, state: SourceLoadingState) => void;
|
onSearchRequestFinished: (source: SourceIdInfo, state: SourceLoadingState) => void;
|
||||||
searchString: string | null | undefined;
|
searchString: string | null | undefined;
|
||||||
emptyQuery: boolean;
|
emptyQuery: boolean;
|
||||||
} & Pick<MangaCardProps, 'mode'> &
|
} & Pick<MangaCardProps, 'mode' | 'onMigrateSelect'> &
|
||||||
Pick<MetadataBrowseSettings, 'shouldShowOnlySourcesWithResults'>) => {
|
Pick<MetadataBrowseSettings, 'shouldShowOnlySourcesWithResults'>) => {
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
|
|
||||||
@@ -230,6 +234,7 @@ const SourceSearchPreview = React.memo(
|
|||||||
message={errorMessage}
|
message={errorMessage}
|
||||||
inLibraryIndicator
|
inLibraryIndicator
|
||||||
mode={mode}
|
mode={mode}
|
||||||
|
onMigrateSelect={onMigrateSelect}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
@@ -237,15 +242,21 @@ const SourceSearchPreview = React.memo(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export const SearchAll: React.FC = () => {
|
export const SearchAll = ({
|
||||||
|
migrationDestinationSourceIds,
|
||||||
|
}: {
|
||||||
|
migrationDestinationSourceIds?: SourceIdInfo['id'][];
|
||||||
|
}) => {
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const handleBack = useBackButton();
|
||||||
const { pathname, state } = useLocation<{ mangaTitle?: string; shouldShowOnlyPinnedSources?: boolean }>();
|
const { pathname, state } = useLocation<{ mangaTitle?: string; shouldShowOnlyPinnedSources?: boolean }>();
|
||||||
const { ref: filterHeaderRef, height: filterHeaderHeight } = useElementSize();
|
const { ref: filterHeaderRef, height: filterHeaderHeight } = useElementSize();
|
||||||
|
|
||||||
const shouldShowOnlyPinnedSources = state?.shouldShowOnlyPinnedSources ?? true;
|
const shouldShowOnlyPinnedSources = state?.shouldShowOnlyPinnedSources ?? true;
|
||||||
const isMigrateMode = pathname.startsWith('/migrate/source');
|
const isMigrateMode = pathname.startsWith('/migrate/source') || pathname.startsWith('/migrate/manual-search');
|
||||||
|
|
||||||
|
const { mangaId } = useParams<{ mangaId?: string }>() ?? STABLE_EMPTY_OBJECT;
|
||||||
const [query] = useQueryParam(SearchParam.QUERY, StringParam);
|
const [query] = useQueryParam(SearchParam.QUERY, StringParam);
|
||||||
const searchString = useDebounce(query, TRIGGER_SEARCH_THRESHOLD);
|
const searchString = useDebounce(query, TRIGGER_SEARCH_THRESHOLD);
|
||||||
|
|
||||||
@@ -255,7 +266,14 @@ export const SearchAll: React.FC = () => {
|
|||||||
} = useMetadataServerSettings();
|
} = useMetadataServerSettings();
|
||||||
|
|
||||||
const { data, loading, error, refetch } = requestManager.useGetSourceList({ notifyOnNetworkStatusChange: true });
|
const { data, loading, error, refetch } = requestManager.useGetSourceList({ notifyOnNetworkStatusChange: true });
|
||||||
const sources = data?.sources.nodes ?? STABLE_EMPTY_ARRAY;
|
const tmpSources = data?.sources.nodes ?? STABLE_EMPTY_ARRAY;
|
||||||
|
const sources = useMemo(
|
||||||
|
() =>
|
||||||
|
tmpSources.filter(
|
||||||
|
(source) => !migrationDestinationSourceIds || migrationDestinationSourceIds.includes(source.id),
|
||||||
|
),
|
||||||
|
[tmpSources, migrationDestinationSourceIds],
|
||||||
|
);
|
||||||
|
|
||||||
const [sourceToLoadingStateMap, setSourceToLoadingStateMap] = useState<SourceToLoadingStateMap>(new Map());
|
const [sourceToLoadingStateMap, setSourceToLoadingStateMap] = useState<SourceToLoadingStateMap>(new Map());
|
||||||
const debouncedSourceToLoadingStateMap = useDebounce(sourceToLoadingStateMap, 500);
|
const debouncedSourceToLoadingStateMap = useDebounce(sourceToLoadingStateMap, 500);
|
||||||
@@ -398,6 +416,19 @@ export const SearchAll: React.FC = () => {
|
|||||||
emptyQuery={!query}
|
emptyQuery={!query}
|
||||||
mode={isMigrateMode ? 'migrate.select' : 'source'}
|
mode={isMigrateMode ? 'migrate.select' : 'source'}
|
||||||
shouldShowOnlySourcesWithResults={shouldShowOnlySourcesWithResults}
|
shouldShowOnlySourcesWithResults={shouldShowOnlySourcesWithResults}
|
||||||
|
onMigrateSelect={
|
||||||
|
migrationDestinationSourceIds
|
||||||
|
? (match) => {
|
||||||
|
assertIsDefined(mangaId);
|
||||||
|
MigrationManager.selectManualMatch(Number(mangaId), {
|
||||||
|
...match,
|
||||||
|
sourceTitle: Sources.getFromCache(match.sourceId)?.displayName,
|
||||||
|
latestChapterNumber: undefined,
|
||||||
|
});
|
||||||
|
handleBack();
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import type {
|
|||||||
import type { SingleModeProps } from '@/features/manga/components/MangaActionMenuItems.tsx';
|
import type { SingleModeProps } from '@/features/manga/components/MangaActionMenuItems.tsx';
|
||||||
import type { GridLayout } from '@/base/Base.types.ts';
|
import type { GridLayout } from '@/base/Base.types.ts';
|
||||||
import type { ChapterListOptions } from '@/features/chapter/Chapter.types.ts';
|
import type { ChapterListOptions } from '@/features/chapter/Chapter.types.ts';
|
||||||
|
import type { MigrationMatch } from '@/features/migration/Migration.types.ts';
|
||||||
|
|
||||||
export type MangaCardMode = 'default' | 'source' | 'migrate.search' | 'migrate.select' | 'duplicate';
|
export type MangaCardMode = 'default' | 'source' | 'migrate.search' | 'migrate.select' | 'duplicate';
|
||||||
|
|
||||||
@@ -33,6 +34,7 @@ type MangaCardBaseProps = Pick<MangaTypeGql, 'id' | 'title' | 'sourceId'> &
|
|||||||
|
|
||||||
export type MangaIdInfo = Pick<MangaTypeGql, 'id'>;
|
export type MangaIdInfo = Pick<MangaTypeGql, 'id'>;
|
||||||
export type MangaChapterCountInfo = { chapters: Pick<MangaTypeGql['chapters'], 'totalCount'> };
|
export type MangaChapterCountInfo = { chapters: Pick<MangaTypeGql['chapters'], 'totalCount'> };
|
||||||
|
export type MangaHighestChapterNumberInfo = { highestNumberedChapter?: Pick<ChapterType, 'id' | 'chapterNumber'> };
|
||||||
export type MangaInLibraryInfo = Pick<MangaTypeGql, 'inLibrary'>;
|
export type MangaInLibraryInfo = Pick<MangaTypeGql, 'inLibrary'>;
|
||||||
export type MangaDownloadInfo = Pick<MangaTypeGql, 'downloadCount'> & MangaChapterCountInfo;
|
export type MangaDownloadInfo = Pick<MangaTypeGql, 'downloadCount'> & MangaChapterCountInfo;
|
||||||
export type MangaUnreadInfo = Pick<MangaTypeGql, 'unreadCount'> & MangaChapterCountInfo;
|
export type MangaUnreadInfo = Pick<MangaTypeGql, 'unreadCount'> & MangaChapterCountInfo;
|
||||||
@@ -42,7 +44,7 @@ export type MangaTrackRecordInfo = MangaIdInfo & {
|
|||||||
};
|
};
|
||||||
export type MangaGenreInfo = Pick<MangaTypeGql, 'genre'>;
|
export type MangaGenreInfo = Pick<MangaTypeGql, 'genre'>;
|
||||||
export type MangaSourceIdInfo = Pick<MangaTypeGql, 'sourceId'>;
|
export type MangaSourceIdInfo = Pick<MangaTypeGql, 'sourceId'>;
|
||||||
export type MangaSourceNameInfo = { source?: Maybe<Pick<SourceType, 'name'>> };
|
export type MangaSourceNameInfo = { source?: Maybe<Pick<SourceType, 'name' | 'displayName'>> };
|
||||||
export type MangaSourceLngInfo = { source?: Maybe<Pick<SourceType, 'lang'>> };
|
export type MangaSourceLngInfo = { source?: Maybe<Pick<SourceType, 'lang'>> };
|
||||||
export type MangaArtistInfo = Pick<MangaTypeGql, 'artist'>;
|
export type MangaArtistInfo = Pick<MangaTypeGql, 'artist'>;
|
||||||
export type MangaAuthorInfo = Pick<MangaTypeGql, 'author'>;
|
export type MangaAuthorInfo = Pick<MangaTypeGql, 'author'>;
|
||||||
@@ -58,6 +60,7 @@ export interface MangaCardProps {
|
|||||||
inLibraryIndicator?: boolean;
|
inLibraryIndicator?: boolean;
|
||||||
selected?: boolean | null;
|
selected?: boolean | null;
|
||||||
handleSelection?: SelectableCollectionReturnType<MangaTypeGql['id']>['handleSelection'];
|
handleSelection?: SelectableCollectionReturnType<MangaTypeGql['id']>['handleSelection'];
|
||||||
|
onMigrateSelect?: (manga: Omit<MigrationMatch, 'sourceTitle' | 'isManualMatch' | 'latestChapterNumber'>) => void;
|
||||||
mode?: MangaCardMode;
|
mode?: MangaCardMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -150,7 +150,11 @@ export const MangaActionMenuItems = ({
|
|||||||
)}
|
)}
|
||||||
{isSingleMode && (
|
{isSingleMode && (
|
||||||
<Link
|
<Link
|
||||||
to={AppRoutes.migrate.childRoutes.search.path(manga?.sourceId ?? -1, manga?.id ?? -1, manga?.title)}
|
to={AppRoutes.migrate.childRoutes.singleMangaSearch.path(
|
||||||
|
manga?.sourceId ?? -1,
|
||||||
|
manga?.id ?? -1,
|
||||||
|
manga?.title,
|
||||||
|
)}
|
||||||
state={{ mangaTitle: manga?.title }}
|
state={{ mangaTitle: manga?.title }}
|
||||||
style={{ textDecoration: 'none', color: 'inherit' }}
|
style={{ textDecoration: 'none', color: 'inherit' }}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import { useLingui } from '@lingui/react/macro';
|
|||||||
import { EmptyViewAbsoluteCentered } from '@/base/components/feedback/EmptyViewAbsoluteCentered.tsx';
|
import { EmptyViewAbsoluteCentered } from '@/base/components/feedback/EmptyViewAbsoluteCentered.tsx';
|
||||||
import { LoadingPlaceholder } from '@/base/components/feedback/LoadingPlaceholder.tsx';
|
import { LoadingPlaceholder } from '@/base/components/feedback/LoadingPlaceholder.tsx';
|
||||||
import { MangaCard } from '@/features/manga/components/cards/MangaCard.tsx';
|
import { MangaCard } from '@/features/manga/components/cards/MangaCard.tsx';
|
||||||
import type { SelectableCollectionReturnType } from '@/base/collection/hooks/useSelectableCollection.ts';
|
|
||||||
import { DEFAULT_FULL_FAB_HEIGHT } from '@/base/components/buttons/StyledFab.tsx';
|
import { DEFAULT_FULL_FAB_HEIGHT } from '@/base/components/buttons/StyledFab.tsx';
|
||||||
import type { MangaCardProps } from '@/features/manga/Manga.types.ts';
|
import type { MangaCardProps } from '@/features/manga/Manga.types.ts';
|
||||||
import type { MangaType } from '@/lib/graphql/generated/graphql.ts';
|
import type { MangaType } from '@/lib/graphql/generated/graphql.ts';
|
||||||
@@ -68,6 +67,7 @@ const createMangaCard = (
|
|||||||
selectedMangaIds?: MangaType['id'][],
|
selectedMangaIds?: MangaType['id'][],
|
||||||
handleSelection?: DefaultGridProps['handleSelection'],
|
handleSelection?: DefaultGridProps['handleSelection'],
|
||||||
mode?: MangaCardProps['mode'],
|
mode?: MangaCardProps['mode'],
|
||||||
|
onMigrateSelect?: DefaultGridProps['onMigrateSelect'],
|
||||||
) => (
|
) => (
|
||||||
<MangaCard
|
<MangaCard
|
||||||
manga={manga}
|
manga={manga}
|
||||||
@@ -76,10 +76,11 @@ const createMangaCard = (
|
|||||||
selected={isSelectModeActive ? selectedMangaIds?.includes(manga.id) : null}
|
selected={isSelectModeActive ? selectedMangaIds?.includes(manga.id) : null}
|
||||||
handleSelection={handleSelection}
|
handleSelection={handleSelection}
|
||||||
mode={mode}
|
mode={mode}
|
||||||
|
onMigrateSelect={onMigrateSelect}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
type DefaultGridProps = Pick<MangaCardProps, 'mode'> & {
|
type DefaultGridProps = Omit<MangaCardProps, 'manga' | 'selected'> & {
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
mangas: TManga[];
|
mangas: TManga[];
|
||||||
inLibraryIndicator?: boolean;
|
inLibraryIndicator?: boolean;
|
||||||
@@ -87,7 +88,6 @@ type DefaultGridProps = Pick<MangaCardProps, 'mode'> & {
|
|||||||
gridLayout?: GridLayout;
|
gridLayout?: GridLayout;
|
||||||
isSelectModeActive?: boolean;
|
isSelectModeActive?: boolean;
|
||||||
selectedMangaIds?: Required<MangaType['id']>[];
|
selectedMangaIds?: Required<MangaType['id']>[];
|
||||||
handleSelection?: SelectableCollectionReturnType<MangaType['id']>['handleSelection'];
|
|
||||||
ref?: ForwardedRef<HTMLDivElement | null>;
|
ref?: ForwardedRef<HTMLDivElement | null>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -102,6 +102,7 @@ const HorizontalGrid = ({
|
|||||||
handleSelection,
|
handleSelection,
|
||||||
mode,
|
mode,
|
||||||
ref,
|
ref,
|
||||||
|
onMigrateSelect,
|
||||||
}: DefaultGridProps) => (
|
}: DefaultGridProps) => (
|
||||||
<Grid
|
<Grid
|
||||||
ref={ref}
|
ref={ref}
|
||||||
@@ -127,6 +128,7 @@ const HorizontalGrid = ({
|
|||||||
selectedMangaIds,
|
selectedMangaIds,
|
||||||
handleSelection,
|
handleSelection,
|
||||||
mode,
|
mode,
|
||||||
|
onMigrateSelect,
|
||||||
)}
|
)}
|
||||||
</GridItemContainer>
|
</GridItemContainer>
|
||||||
))
|
))
|
||||||
@@ -149,6 +151,7 @@ const VerticalGrid = ({
|
|||||||
handleSelection,
|
handleSelection,
|
||||||
mode,
|
mode,
|
||||||
ref,
|
ref,
|
||||||
|
onMigrateSelect,
|
||||||
}: DefaultGridProps & {
|
}: DefaultGridProps & {
|
||||||
hasNextPage: boolean;
|
hasNextPage: boolean;
|
||||||
loadMore: () => void;
|
loadMore: () => void;
|
||||||
@@ -175,6 +178,7 @@ const VerticalGrid = ({
|
|||||||
selectedMangaIds,
|
selectedMangaIds,
|
||||||
handleSelection,
|
handleSelection,
|
||||||
mode,
|
mode,
|
||||||
|
onMigrateSelect,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -220,6 +224,7 @@ export const MangaGrid: React.FC<IMangaGridProps> = ({
|
|||||||
mode,
|
mode,
|
||||||
retry,
|
retry,
|
||||||
gridWrapperProps,
|
gridWrapperProps,
|
||||||
|
onMigrateSelect,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
|
|
||||||
@@ -346,6 +351,7 @@ export const MangaGrid: React.FC<IMangaGridProps> = ({
|
|||||||
selectedMangaIds={selectedMangaIds}
|
selectedMangaIds={selectedMangaIds}
|
||||||
handleSelection={handleSelection}
|
handleSelection={handleSelection}
|
||||||
mode={mode}
|
mode={mode}
|
||||||
|
onMigrateSelect={onMigrateSelect}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<VerticalGrid
|
<VerticalGrid
|
||||||
@@ -361,6 +367,7 @@ export const MangaGrid: React.FC<IMangaGridProps> = ({
|
|||||||
selectedMangaIds={selectedMangaIds}
|
selectedMangaIds={selectedMangaIds}
|
||||||
handleSelection={handleSelection}
|
handleSelection={handleSelection}
|
||||||
mode={mode}
|
mode={mode}
|
||||||
|
onMigrateSelect={onMigrateSelect}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ export const MangaToolbarMenu = ({ manga, onRefresh, refreshing }: IProps) => {
|
|||||||
<>
|
<>
|
||||||
<CustomTooltip title={t`Migrate`}>
|
<CustomTooltip title={t`Migrate`}>
|
||||||
<Link
|
<Link
|
||||||
to={AppRoutes.migrate.childRoutes.search.path(
|
to={AppRoutes.migrate.childRoutes.singleMangaSearch.path(
|
||||||
manga.sourceId,
|
manga.sourceId,
|
||||||
manga.id,
|
manga.id,
|
||||||
manga.title,
|
manga.title,
|
||||||
@@ -156,7 +156,11 @@ export const MangaToolbarMenu = ({ manga, onRefresh, refreshing }: IProps) => {
|
|||||||
<MenuItem
|
<MenuItem
|
||||||
key="migrate"
|
key="migrate"
|
||||||
component={Link}
|
component={Link}
|
||||||
to={AppRoutes.migrate.childRoutes.search.path(manga.sourceId, manga.id, manga.title)}
|
to={AppRoutes.migrate.childRoutes.singleMangaSearch.path(
|
||||||
|
manga.sourceId,
|
||||||
|
manga.id,
|
||||||
|
manga.title,
|
||||||
|
)}
|
||||||
state={{ mangaTitle: manga.title }}
|
state={{ mangaTitle: manga.title }}
|
||||||
style={{ textDecoration: 'none', color: 'inherit' }}
|
style={{ textDecoration: 'none', color: 'inherit' }}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -7,12 +7,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import PopupState, { bindMenu } from 'material-ui-popup-state';
|
import PopupState, { bindMenu } from 'material-ui-popup-state';
|
||||||
import { memo, useCallback, useMemo, useState } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useLongPress } from 'use-long-press';
|
import { useLongPress } from 'use-long-press';
|
||||||
import type { SingleModeProps } from '@/features/manga/components/MangaActionMenuItems.tsx';
|
import type { SingleModeProps } from '@/features/manga/components/MangaActionMenuItems.tsx';
|
||||||
import { MangaActionMenuItems } from '@/features/manga/components/MangaActionMenuItems.tsx';
|
import { MangaActionMenuItems } from '@/features/manga/components/MangaActionMenuItems.tsx';
|
||||||
import { Menu } from '@/base/components/menu/Menu.tsx';
|
import { Menu } from '@/base/components/menu/Menu.tsx';
|
||||||
import { MigrateDialog } from '@/features/migration/components/MigrateDialog.tsx';
|
|
||||||
import { useManageMangaLibraryState } from '@/features/manga/hooks/useManageMangaLibraryState.tsx';
|
import { useManageMangaLibraryState } from '@/features/manga/hooks/useManageMangaLibraryState.tsx';
|
||||||
import { MangaGridCard } from '@/features/manga/components/cards/MangaGridCard.tsx';
|
import { MangaGridCard } from '@/features/manga/components/cards/MangaGridCard.tsx';
|
||||||
import { MangaListCard } from '@/features/manga/components/cards/MangaListCard.tsx';
|
import { MangaListCard } from '@/features/manga/components/cards/MangaListCard.tsx';
|
||||||
@@ -22,6 +21,14 @@ import { MangaBadges } from '@/features/manga/components/MangaBadges.tsx';
|
|||||||
import { GridLayout } from '@/base/Base.types.ts';
|
import { GridLayout } from '@/base/Base.types.ts';
|
||||||
import { AppRoutes } from '@/base/AppRoute.constants.ts';
|
import { AppRoutes } from '@/base/AppRoute.constants.ts';
|
||||||
import { useMetadataServerSettings } from '@/features/settings/services/ServerSettingsMetadata.ts';
|
import { useMetadataServerSettings } from '@/features/settings/services/ServerSettingsMetadata.ts';
|
||||||
|
import { makeToast } from '@/base/utils/Toast.ts';
|
||||||
|
import { Mangas } from '@/features/manga/services/Mangas.ts';
|
||||||
|
import { t } from '@lingui/core/macro';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
import { ReactRouter } from '@/lib/react-router/ReactRouter.ts';
|
||||||
|
import { MigrationOptionsDialog } from '@/features/migration/components/MigrationOptionsDialog.tsx';
|
||||||
|
import { AwaitableComponent } from 'awaitable-component';
|
||||||
|
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler';
|
||||||
|
|
||||||
const getMangaLinkTo = (
|
const getMangaLinkTo = (
|
||||||
mode: MangaCardMode,
|
mode: MangaCardMode,
|
||||||
@@ -35,7 +42,7 @@ const getMangaLinkTo = (
|
|||||||
case 'duplicate':
|
case 'duplicate':
|
||||||
return AppRoutes.manga.path(mangaId);
|
return AppRoutes.manga.path(mangaId);
|
||||||
case 'migrate.search':
|
case 'migrate.search':
|
||||||
return AppRoutes.migrate.childRoutes.search.path(sourceId ?? '-1', mangaId, mangaTitle);
|
return AppRoutes.migrate.childRoutes.singleMangaSearch.path(sourceId ?? '-1', mangaId, mangaTitle);
|
||||||
case 'migrate.select':
|
case 'migrate.select':
|
||||||
return '';
|
return '';
|
||||||
default:
|
default:
|
||||||
@@ -44,8 +51,20 @@ const getMangaLinkTo = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const MangaCard = memo((props: MangaCardProps) => {
|
export const MangaCard = memo((props: MangaCardProps) => {
|
||||||
const { manga, gridLayout, inLibraryIndicator, selected, handleSelection, mode = 'default' } = props;
|
const {
|
||||||
|
manga,
|
||||||
|
gridLayout,
|
||||||
|
inLibraryIndicator,
|
||||||
|
selected,
|
||||||
|
handleSelection,
|
||||||
|
mode = 'default',
|
||||||
|
onMigrateSelect,
|
||||||
|
} = props;
|
||||||
const { id, firstUnreadChapter, downloadCount, unreadCount } = manga;
|
const { id, firstUnreadChapter, downloadCount, unreadCount } = manga;
|
||||||
|
|
||||||
|
const { mangaId: mangaIdAsString } = useParams<{ mangaId: string }>();
|
||||||
|
const migrationSourceMangaId = Number(mangaIdAsString);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
settings: { showContinueReadingButton },
|
settings: { showContinueReadingButton },
|
||||||
} = useMetadataServerSettings();
|
} = useMetadataServerSettings();
|
||||||
@@ -54,8 +73,6 @@ export const MangaCard = memo((props: MangaCardProps) => {
|
|||||||
|
|
||||||
const mangaLinkTo = getMangaLinkTo(mode, manga.id, manga.sourceId, manga.title);
|
const mangaLinkTo = getMangaLinkTo(mode, manga.id, manga.sourceId, manga.title);
|
||||||
|
|
||||||
const [isMigrateDialogOpen, setIsMigrateDialogOpen] = useState(false);
|
|
||||||
|
|
||||||
const handleClick = useCallback(
|
const handleClick = useCallback(
|
||||||
(event: React.MouseEvent | React.TouchEvent, openMenu?: () => void) => {
|
(event: React.MouseEvent | React.TouchEvent, openMenu?: () => void) => {
|
||||||
const isDefaultMode = mode === 'default';
|
const isDefaultMode = mode === 'default';
|
||||||
@@ -88,10 +105,42 @@ export const MangaCard = memo((props: MangaCardProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isMigrateSelectMode) {
|
if (isMigrateSelectMode) {
|
||||||
setIsMigrateDialogOpen(true);
|
const isBulkMigrationManualSearch = !!onMigrateSelect;
|
||||||
|
if (isBulkMigrationManualSearch) {
|
||||||
|
onMigrateSelect(manga);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const migrate = () => {
|
||||||
|
const optionsDialog = AwaitableComponent.showControlled(
|
||||||
|
MigrationOptionsDialog,
|
||||||
|
{
|
||||||
|
mangaIdToMigrateTo: id,
|
||||||
|
isMigrating: false,
|
||||||
|
startMigration: async (options) => {
|
||||||
|
makeToast(t`Migrating manga…`, 'info');
|
||||||
|
|
||||||
|
optionsDialog.update({ isMigrating: true });
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Mangas.migrate(migrationSourceMangaId, id, options);
|
||||||
|
|
||||||
|
optionsDialog.submit(options);
|
||||||
|
|
||||||
|
ReactRouter.navigate(AppRoutes.manga.path(id), { replace: true });
|
||||||
|
} catch (e) {
|
||||||
|
optionsDialog.update({ isMigrating: false, startMigration: () => migrate() });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ id: `manga-migration-single-manga-${migrationSourceMangaId}-${id}` },
|
||||||
|
);
|
||||||
|
optionsDialog.promise.catch(defaultPromiseErrorHandler('MangaCard::migrate'));
|
||||||
|
};
|
||||||
|
migrate();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[mode, selected, updateLibraryState, handleSelection],
|
[mode, selected, updateLibraryState, handleSelection, migrationSourceMangaId],
|
||||||
);
|
);
|
||||||
|
|
||||||
const longPressBind = useLongPress(
|
const longPressBind = useLongPress(
|
||||||
@@ -111,54 +160,49 @@ export const MangaCard = memo((props: MangaCardProps) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<PopupState variant="popover" popupId="manga-card-action-menu">
|
||||||
{isMigrateDialogOpen && (
|
{(popupState) => (
|
||||||
<MigrateDialog mangaIdToMigrateTo={manga.id} onClose={() => setIsMigrateDialogOpen(false)} />
|
<>
|
||||||
|
<MangaCardComponent
|
||||||
|
{...props}
|
||||||
|
longPressBind={longPressBind}
|
||||||
|
popupState={popupState}
|
||||||
|
handleClick={handleClick}
|
||||||
|
mangaLinkTo={mangaLinkTo}
|
||||||
|
isInLibrary={isInLibrary}
|
||||||
|
inLibraryIndicator={inLibraryIndicator}
|
||||||
|
continueReadingButton={
|
||||||
|
<ContinueReadingButton
|
||||||
|
showContinueReadingButton={showContinueReadingButton && mode === 'default'}
|
||||||
|
chapter={firstUnreadChapter}
|
||||||
|
mangaLinkTo={mangaLinkTo}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
mangaBadges={
|
||||||
|
<MangaBadges
|
||||||
|
inLibraryIndicator={inLibraryIndicator}
|
||||||
|
isInLibrary={isInLibrary}
|
||||||
|
unread={unreadCount}
|
||||||
|
downloadCount={downloadCount}
|
||||||
|
updateLibraryState={updateLibraryState}
|
||||||
|
mode={mode}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{!!handleSelection && popupState.isOpen && (
|
||||||
|
<Menu {...bindMenu(popupState)}>
|
||||||
|
{(onClose, setHideMenu) => (
|
||||||
|
<MangaActionMenuItems
|
||||||
|
manga={manga as SingleModeProps['manga']}
|
||||||
|
handleSelection={handleSelection}
|
||||||
|
onClose={onClose}
|
||||||
|
setHideMenu={setHideMenu}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Menu>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
<PopupState variant="popover" popupId="manga-card-action-menu">
|
</PopupState>
|
||||||
{(popupState) => (
|
|
||||||
<>
|
|
||||||
<MangaCardComponent
|
|
||||||
{...props}
|
|
||||||
longPressBind={longPressBind}
|
|
||||||
popupState={popupState}
|
|
||||||
handleClick={handleClick}
|
|
||||||
mangaLinkTo={mangaLinkTo}
|
|
||||||
isInLibrary={isInLibrary}
|
|
||||||
inLibraryIndicator={inLibraryIndicator}
|
|
||||||
continueReadingButton={
|
|
||||||
<ContinueReadingButton
|
|
||||||
showContinueReadingButton={showContinueReadingButton && mode === 'default'}
|
|
||||||
chapter={firstUnreadChapter}
|
|
||||||
mangaLinkTo={mangaLinkTo}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
mangaBadges={
|
|
||||||
<MangaBadges
|
|
||||||
inLibraryIndicator={inLibraryIndicator}
|
|
||||||
isInLibrary={isInLibrary}
|
|
||||||
unread={unreadCount}
|
|
||||||
downloadCount={downloadCount}
|
|
||||||
updateLibraryState={updateLibraryState}
|
|
||||||
mode={mode}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{!!handleSelection && popupState.isOpen && (
|
|
||||||
<Menu {...bindMenu(popupState)}>
|
|
||||||
{(onClose, setHideMenu) => (
|
|
||||||
<MangaActionMenuItems
|
|
||||||
manga={manga as SingleModeProps['manga']}
|
|
||||||
handleSelection={handleSelection}
|
|
||||||
onClose={onClose}
|
|
||||||
setHideMenu={setHideMenu}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Menu>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</PopupState>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
|
|
||||||
import type { MessageDescriptor } from '@lingui/core';
|
import type { MessageDescriptor } from '@lingui/core';
|
||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
import { SortBy, SortOrder } from '@/features/migration/Migration.types.ts';
|
import { MigrationEntryStatus, MigrationPhase, SortBy, SortOrder } from '@/features/migration/Migration.types.ts';
|
||||||
|
import type { MigrationState } from '@/features/migration/Migration.types.ts';
|
||||||
|
|
||||||
export const sortByToTranslation: Record<SortBy, MessageDescriptor> = {
|
export const sortByToTranslation: Record<SortBy, MessageDescriptor> = {
|
||||||
[SortBy.SOURCE_NAME]: msg`By source name`,
|
[SortBy.SOURCE_NAME]: msg`By source name`,
|
||||||
@@ -24,3 +25,70 @@ export const DEFAULT_SORT_SETTINGS = {
|
|||||||
sortBy: SortBy.SOURCE_NAME,
|
sortBy: SortBy.SOURCE_NAME,
|
||||||
sortOrder: SortOrder.ASC,
|
sortOrder: SortOrder.ASC,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const MIGRATION_LOCAL_STORAGE_KEY = 'migration_state';
|
||||||
|
|
||||||
|
export const MAX_MANGAS_IN_PARALLEL = 5;
|
||||||
|
|
||||||
|
export const MAX_SOURCES_IN_PARALLEL = 6;
|
||||||
|
|
||||||
|
export const DEFAULT_MIGRATION_STATE: MigrationState = {
|
||||||
|
phase: MigrationPhase.IDLE,
|
||||||
|
sourceId: null,
|
||||||
|
entries: {},
|
||||||
|
destinationSourceIds: [],
|
||||||
|
migrateOptions: null,
|
||||||
|
searchProgress: { total: 0, completed: 0, success: 0, failed: 0 },
|
||||||
|
migrationProgress: { total: 0, completed: 0, success: 0, failed: 0 },
|
||||||
|
startedAt: null,
|
||||||
|
lastUpdatedAt: null,
|
||||||
|
groupExpandState: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ENTRY_STATUS_TRANSLATION: Record<MigrationEntryStatus, MessageDescriptor> = {
|
||||||
|
[MigrationEntryStatus.PENDING]: msg`Pending…`,
|
||||||
|
[MigrationEntryStatus.SEARCHING]: msg`Searching…`,
|
||||||
|
[MigrationEntryStatus.SEARCH_COMPLETE]: msg`Match found`,
|
||||||
|
[MigrationEntryStatus.SEARCH_FAILED]: msg`Search failed`,
|
||||||
|
[MigrationEntryStatus.NO_MATCH]: msg`No match found`,
|
||||||
|
[MigrationEntryStatus.MIGRATING]: msg`Migrating…`,
|
||||||
|
[MigrationEntryStatus.MIGRATION_COMPLETE]: msg`Successfully migrated`,
|
||||||
|
[MigrationEntryStatus.MIGRATION_FAILED]: msg`Migration failed`,
|
||||||
|
[MigrationEntryStatus.EXCLUDED]: msg`Excluded`,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MIGRATE_SEARCH_ENTRY_GROUPS = [
|
||||||
|
MigrationEntryStatus.SEARCHING,
|
||||||
|
MigrationEntryStatus.SEARCH_FAILED,
|
||||||
|
MigrationEntryStatus.NO_MATCH,
|
||||||
|
MigrationEntryStatus.SEARCH_COMPLETE,
|
||||||
|
] as const satisfies readonly MigrationEntryStatus[];
|
||||||
|
|
||||||
|
export const MIGRATE_SEARCH_ENTRY_GROUP_EXPAND_DEFAULT_STATE: Record<
|
||||||
|
(typeof MIGRATE_SEARCH_ENTRY_GROUPS)[number],
|
||||||
|
boolean
|
||||||
|
> = {
|
||||||
|
[MigrationEntryStatus.SEARCHING]: true,
|
||||||
|
[MigrationEntryStatus.SEARCH_FAILED]: false,
|
||||||
|
[MigrationEntryStatus.NO_MATCH]: false,
|
||||||
|
[MigrationEntryStatus.SEARCH_COMPLETE]: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MIGRATE_EXECUTE_ENTRY_GROUPS = [
|
||||||
|
MigrationEntryStatus.MIGRATING,
|
||||||
|
MigrationEntryStatus.MIGRATION_FAILED,
|
||||||
|
MigrationEntryStatus.NO_MATCH,
|
||||||
|
MigrationEntryStatus.EXCLUDED,
|
||||||
|
MigrationEntryStatus.MIGRATION_COMPLETE,
|
||||||
|
] as const satisfies readonly MigrationEntryStatus[];
|
||||||
|
|
||||||
|
export const MIGRATE_EXECUTE_ENTRY_GROUP_EXPAND_DEFAULT_STATE: Record<
|
||||||
|
(typeof MIGRATE_EXECUTE_ENTRY_GROUPS)[number],
|
||||||
|
boolean
|
||||||
|
> = {
|
||||||
|
[MigrationEntryStatus.MIGRATING]: true,
|
||||||
|
[MigrationEntryStatus.MIGRATION_FAILED]: false,
|
||||||
|
[MigrationEntryStatus.NO_MATCH]: false,
|
||||||
|
[MigrationEntryStatus.EXCLUDED]: false,
|
||||||
|
[MigrationEntryStatus.MIGRATION_COMPLETE]: false,
|
||||||
|
};
|
||||||
|
|||||||
@@ -7,6 +7,23 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { GetMigratableSourcesQuery } from '@/lib/graphql/generated/graphql.ts';
|
import type { GetMigratableSourcesQuery } from '@/lib/graphql/generated/graphql.ts';
|
||||||
|
import type {
|
||||||
|
SourceDisplayNameInfo,
|
||||||
|
SourceIconInfo,
|
||||||
|
SourceIdInfo,
|
||||||
|
SourceLanguageInfo,
|
||||||
|
SourceMetaInfo,
|
||||||
|
SourceNameInfo,
|
||||||
|
} from '@/features/source/Source.types.ts';
|
||||||
|
import type {
|
||||||
|
MangaArtistInfo,
|
||||||
|
MangaAuthorInfo,
|
||||||
|
MangaIdInfo,
|
||||||
|
MangaSourceIdInfo,
|
||||||
|
MangaThumbnailInfo,
|
||||||
|
MangaTitleInfo,
|
||||||
|
} from '@/features/manga/Manga.types.ts';
|
||||||
|
import type { ChapterNumberInfo } from '@/features/chapter/Chapter.types.ts';
|
||||||
|
|
||||||
export enum SortBy {
|
export enum SortBy {
|
||||||
SOURCE_NAME,
|
SOURCE_NAME,
|
||||||
@@ -40,3 +57,69 @@ export type MetadataMigrationSettings = {
|
|||||||
migrateMetadata: boolean;
|
migrateMetadata: boolean;
|
||||||
migrateSortSettings: SortSettings;
|
migrateSortSettings: SortSettings;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum MigrationPhase {
|
||||||
|
IDLE = 'idle',
|
||||||
|
SELECT_SOURCE = 'select_source',
|
||||||
|
SELECT_MANGAS = 'select_mangas',
|
||||||
|
SELECTING_SOURCES = 'selecting_sources',
|
||||||
|
SEARCHING = 'searching',
|
||||||
|
MIGRATING = 'migrating',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum MigrationEntryStatus {
|
||||||
|
PENDING = 'pending',
|
||||||
|
SEARCHING = 'searching',
|
||||||
|
SEARCH_COMPLETE = 'search_complete',
|
||||||
|
SEARCH_FAILED = 'search_failed',
|
||||||
|
NO_MATCH = 'no_match',
|
||||||
|
MIGRATING = 'migrating',
|
||||||
|
MIGRATION_COMPLETE = 'migration_complete',
|
||||||
|
MIGRATION_FAILED = 'migration_failed',
|
||||||
|
EXCLUDED = 'excluded',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MigrationMatch
|
||||||
|
extends MangaIdInfo, MangaTitleInfo, MangaThumbnailInfo, MangaSourceIdInfo, MangaArtistInfo, MangaAuthorInfo {
|
||||||
|
sourceTitle: SourceDisplayNameInfo['displayName'] | undefined;
|
||||||
|
latestChapterNumber: ChapterNumberInfo['chapterNumber'] | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TMigrationEntry {
|
||||||
|
mangaId: MangaIdInfo['id'];
|
||||||
|
mangaTitle: MangaTitleInfo['title'];
|
||||||
|
mangaArtist: MangaArtistInfo['artist'];
|
||||||
|
mangaAuthor: MangaAuthorInfo['author'];
|
||||||
|
latestChapterNumber: ChapterNumberInfo['chapterNumber'] | undefined;
|
||||||
|
mangaThumbnailUrl: MangaThumbnailInfo['thumbnailUrl'] | undefined;
|
||||||
|
sourceId: SourceIdInfo['id'];
|
||||||
|
sourceTitle: SourceDisplayNameInfo['displayName'] | undefined;
|
||||||
|
status: MigrationEntryStatus;
|
||||||
|
searchMatches: MigrationMatch[];
|
||||||
|
manualMatches: MigrationMatch[];
|
||||||
|
selectedMatchMangaId: MangaIdInfo['id'] | null;
|
||||||
|
selectedMatchSourceId: SourceIdInfo['id'] | null;
|
||||||
|
destSourceIdToSearchState: Record<SourceIdInfo['id'], boolean | undefined>;
|
||||||
|
error: string | undefined;
|
||||||
|
isExcluded: boolean;
|
||||||
|
areMatchesExpanded: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MigratableEntry = NonNullableProperty<TMigrationEntry, 'selectedMatchMangaId' | 'selectedMatchSourceId'>;
|
||||||
|
|
||||||
|
export type MigrationProgress = { total: number; completed: number; success: number; failed: number };
|
||||||
|
|
||||||
|
export interface MigrationState {
|
||||||
|
phase: MigrationPhase;
|
||||||
|
sourceId: SourceIdInfo['id'] | null;
|
||||||
|
entries: Record<MangaIdInfo['id'], TMigrationEntry>;
|
||||||
|
destinationSourceIds: SourceIdInfo['id'][];
|
||||||
|
migrateOptions: Omit<MigrateOptions, 'mangaIdToMigrateTo'> | null;
|
||||||
|
searchProgress: MigrationProgress;
|
||||||
|
migrationProgress: MigrationProgress;
|
||||||
|
startedAt: number | null;
|
||||||
|
lastUpdatedAt: number | null;
|
||||||
|
groupExpandState: Partial<Record<MigrationEntryStatus, boolean>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SourceItem extends SourceIdInfo, SourceNameInfo, SourceLanguageInfo, SourceIconInfo, SourceMetaInfo {}
|
||||||
|
|||||||
918
src/features/migration/MigrationManager.ts
Normal file
918
src/features/migration/MigrationManager.ts
Normal file
@@ -0,0 +1,918 @@
|
|||||||
|
/*
|
||||||
|
* 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 type { LimitFunction } from 'p-limit';
|
||||||
|
import pLimit from 'p-limit';
|
||||||
|
import { create } from 'zustand';
|
||||||
|
import { immer } from 'zustand/middleware/immer';
|
||||||
|
import { devtools, persist } from 'zustand/middleware';
|
||||||
|
import {
|
||||||
|
type MigratableEntry,
|
||||||
|
type MigrateOptions,
|
||||||
|
type TMigrationEntry,
|
||||||
|
MigrationEntryStatus,
|
||||||
|
MigrationPhase,
|
||||||
|
type MigrationMatch,
|
||||||
|
type MigrationState,
|
||||||
|
} from '@/features/migration/Migration.types.ts';
|
||||||
|
import {
|
||||||
|
DEFAULT_MIGRATION_STATE,
|
||||||
|
MAX_MANGAS_IN_PARALLEL,
|
||||||
|
MAX_SOURCES_IN_PARALLEL,
|
||||||
|
MIGRATE_EXECUTE_ENTRY_GROUP_EXPAND_DEFAULT_STATE,
|
||||||
|
MIGRATE_SEARCH_ENTRY_GROUP_EXPAND_DEFAULT_STATE,
|
||||||
|
MIGRATION_LOCAL_STORAGE_KEY,
|
||||||
|
} from '@/features/migration/Migration.constants.ts';
|
||||||
|
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||||
|
import { GET_MIGRATION_SOURCE_MANGAS_FETCH } from '@/lib/graphql/source/SourceMutation.ts';
|
||||||
|
import type {
|
||||||
|
GetMigrationSourceMangasFetchMutation,
|
||||||
|
GetMigrationSourceMangasFetchMutationVariables,
|
||||||
|
GetServerSettingsQuery,
|
||||||
|
GetServerSettingsQueryVariables,
|
||||||
|
MangaMigrationFieldsFragment,
|
||||||
|
} from '@/lib/graphql/generated/graphql.ts';
|
||||||
|
import { FetchSourceMangaType } from '@/lib/graphql/generated/graphql.ts';
|
||||||
|
import { GET_SERVER_SETTINGS } from '@/lib/graphql/settings/SettingsQuery.ts';
|
||||||
|
import { MangaMigration } from '@/features/migration/MangaMigration.ts';
|
||||||
|
import type { MangaIdInfo } from '@/features/manga/Manga.types.ts';
|
||||||
|
import type { SourceIdInfo } from '@/features/source/Source.types.ts';
|
||||||
|
import { assertIsDefined } from '@/base/Asserts.ts';
|
||||||
|
import { ReactRouter } from '@/lib/react-router/ReactRouter.ts';
|
||||||
|
import { AppRoutes } from '@/base/AppRoute.constants.ts';
|
||||||
|
import { Confirmation } from '@/base/AppAwaitableComponent.ts';
|
||||||
|
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
||||||
|
import { t } from '@lingui/core/macro';
|
||||||
|
import { enhancedCleanup } from '@/base/utils/Strings.ts';
|
||||||
|
import { BrowseTab } from '@/features/browse/Browse.types.ts';
|
||||||
|
import { Mangas } from '@/features/manga/services/Mangas.ts';
|
||||||
|
import { MANGA_MIGRATION_FIELDS } from '@/lib/graphql/manga/MangaFragments.ts';
|
||||||
|
import { ZustandUtil } from '@/lib/zustand/ZustandUtil.ts';
|
||||||
|
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
|
||||||
|
import isEqual from 'lodash/fp/isEqual';
|
||||||
|
|
||||||
|
const RESUMABLE_PHASES: readonly MigrationPhase[] = [MigrationPhase.SEARCHING, MigrationPhase.MIGRATING];
|
||||||
|
|
||||||
|
const migrationStore = create<MigrationState>()(
|
||||||
|
devtools(
|
||||||
|
persist(
|
||||||
|
immer(() => ({ ...DEFAULT_MIGRATION_STATE })),
|
||||||
|
{
|
||||||
|
name: MIGRATION_LOCAL_STORAGE_KEY,
|
||||||
|
merge: (persistedState, currentState) => {
|
||||||
|
const persisted = persistedState as MigrationState | undefined;
|
||||||
|
|
||||||
|
if (!persisted || !RESUMABLE_PHASES.includes(persisted.phase)) {
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...currentState, ...persisted };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const useMigrationStore = ZustandUtil.createStoreHook(migrationStore);
|
||||||
|
|
||||||
|
export class MigrationManager {
|
||||||
|
private static abortController: AbortController | null = null;
|
||||||
|
|
||||||
|
private static mangaProcessQueue = pLimit(MAX_MANGAS_IN_PARALLEL);
|
||||||
|
private static parallelSourcesQueue: LimitFunction | undefined;
|
||||||
|
private static queueBySource = new Map<string, LimitFunction>();
|
||||||
|
|
||||||
|
private static abortAndResetAbortController(reason: unknown): void {
|
||||||
|
MigrationManager.abortController?.abort(reason);
|
||||||
|
MigrationManager.abortController = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static abortAndCreateAbortController(abortReason: unknown): AbortController {
|
||||||
|
MigrationManager.abortController?.abort(abortReason);
|
||||||
|
MigrationManager.abortController = new AbortController();
|
||||||
|
|
||||||
|
return MigrationManager.abortController;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static getOrCreateAbortController(): AbortController {
|
||||||
|
if (!MigrationManager.abortController) {
|
||||||
|
MigrationManager.abortController = new AbortController();
|
||||||
|
}
|
||||||
|
|
||||||
|
return MigrationManager.abortController;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static getParallelSourceQueue(): LimitFunction {
|
||||||
|
if (MigrationManager.parallelSourcesQueue) {
|
||||||
|
return MigrationManager.parallelSourcesQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = requestManager.graphQLClient.client.readQuery<
|
||||||
|
GetServerSettingsQuery,
|
||||||
|
GetServerSettingsQueryVariables
|
||||||
|
>({
|
||||||
|
query: GET_SERVER_SETTINGS,
|
||||||
|
});
|
||||||
|
|
||||||
|
MigrationManager.parallelSourcesQueue = pLimit(
|
||||||
|
result?.settings.maxSourcesInParallel ?? MAX_SOURCES_IN_PARALLEL,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
MigrationManager.parallelSourcesQueue = pLimit(MAX_SOURCES_IN_PARALLEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
return MigrationManager.parallelSourcesQueue!;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static getOrCreateSourceQueue(sourceId: SourceIdInfo['id']): LimitFunction {
|
||||||
|
if (this.queueBySource.has(sourceId)) {
|
||||||
|
return this.queueBySource.get(sourceId)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
const queue = pLimit(1);
|
||||||
|
this.queueBySource.set(sourceId, queue);
|
||||||
|
|
||||||
|
return queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async confirmAbort(): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
return await Confirmation.show({
|
||||||
|
title: t`Abort migration`,
|
||||||
|
message: t`Are you sure you want to abort the migration?`,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
defaultPromiseErrorHandler('MigrationManager::abort')(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async goToPreviousPhase(): Promise<boolean> {
|
||||||
|
switch (MigrationManager.getState().phase) {
|
||||||
|
case MigrationPhase.IDLE:
|
||||||
|
return true;
|
||||||
|
case MigrationPhase.SELECT_SOURCE:
|
||||||
|
MigrationManager.updateState((draft) => {
|
||||||
|
draft.phase = MigrationPhase.IDLE;
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
case MigrationPhase.SELECT_MANGAS:
|
||||||
|
MigrationManager.updateState((draft) => {
|
||||||
|
draft.phase = MigrationPhase.SELECT_SOURCE;
|
||||||
|
draft.entries = {};
|
||||||
|
});
|
||||||
|
|
||||||
|
ReactRouter.navigate(AppRoutes.browse.path(BrowseTab.MIGRATE));
|
||||||
|
return false;
|
||||||
|
case MigrationPhase.SELECTING_SOURCES:
|
||||||
|
MigrationManager.updateState((draft) => {
|
||||||
|
draft.phase = MigrationPhase.SELECT_MANGAS;
|
||||||
|
draft.destinationSourceIds = [];
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
try {
|
||||||
|
await MigrationManager.abort('goToPreviousPhase');
|
||||||
|
return false;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static isPhaseComplete(): boolean {
|
||||||
|
const { phase } = MigrationManager.getState();
|
||||||
|
|
||||||
|
switch (phase) {
|
||||||
|
case MigrationPhase.SEARCHING:
|
||||||
|
return (
|
||||||
|
MigrationManager.getState().searchProgress.completed ===
|
||||||
|
MigrationManager.getState().searchProgress.total
|
||||||
|
);
|
||||||
|
case MigrationPhase.MIGRATING:
|
||||||
|
return (
|
||||||
|
MigrationManager.getState().migrationProgress.completed ===
|
||||||
|
MigrationManager.getState().migrationProgress.total
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static getUpToDateSearchMatch(searchMatch: MigrationMatch): MigrationMatch {
|
||||||
|
const cachedEntry = Mangas.getFromCache<MangaMigrationFieldsFragment>(
|
||||||
|
searchMatch.id,
|
||||||
|
MANGA_MIGRATION_FIELDS,
|
||||||
|
'MANGA_MIGRATION_FIELDS',
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: cachedEntry?.id ?? searchMatch.id,
|
||||||
|
title: cachedEntry?.title ?? searchMatch.title,
|
||||||
|
thumbnailUrl: cachedEntry?.thumbnailUrl ?? searchMatch.thumbnailUrl,
|
||||||
|
thumbnailUrlLastFetched: cachedEntry?.thumbnailUrlLastFetched ?? searchMatch.thumbnailUrlLastFetched,
|
||||||
|
sourceId: cachedEntry?.sourceId ?? searchMatch.sourceId,
|
||||||
|
artist: cachedEntry?.artist ?? searchMatch.artist,
|
||||||
|
author: cachedEntry?.author ?? searchMatch.author,
|
||||||
|
sourceTitle: cachedEntry?.source?.displayName ?? searchMatch.sourceTitle,
|
||||||
|
latestChapterNumber: cachedEntry?.highestNumberedChapter?.chapterNumber ?? searchMatch.latestChapterNumber,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static getUpToDateMigrationEntry(entry: TMigrationEntry): TMigrationEntry {
|
||||||
|
const cachedEntry = Mangas.getFromCache<MangaMigrationFieldsFragment>(
|
||||||
|
entry.mangaId,
|
||||||
|
MANGA_MIGRATION_FIELDS,
|
||||||
|
'MANGA_MIGRATION_FIELDS',
|
||||||
|
);
|
||||||
|
|
||||||
|
const updatedEntry = {
|
||||||
|
mangaId: cachedEntry?.id ?? entry.mangaId,
|
||||||
|
mangaTitle: cachedEntry?.title ?? entry.mangaTitle,
|
||||||
|
mangaArtist: cachedEntry?.artist ?? entry.mangaArtist,
|
||||||
|
mangaAuthor: cachedEntry?.author ?? entry.mangaAuthor,
|
||||||
|
latestChapterNumber: cachedEntry?.highestNumberedChapter?.chapterNumber ?? entry.latestChapterNumber,
|
||||||
|
mangaThumbnailUrl: cachedEntry?.thumbnailUrl ?? entry.mangaThumbnailUrl,
|
||||||
|
sourceId: cachedEntry?.sourceId ?? entry.sourceId,
|
||||||
|
sourceTitle: cachedEntry?.source?.displayName ?? entry.sourceTitle,
|
||||||
|
status: entry.status,
|
||||||
|
searchMatches: entry.searchMatches.map(MigrationManager.getUpToDateSearchMatch.bind(MigrationManager)),
|
||||||
|
manualMatches: entry.manualMatches.map(MigrationManager.getUpToDateSearchMatch.bind(MigrationManager)),
|
||||||
|
selectedMatchMangaId: entry.selectedMatchMangaId,
|
||||||
|
selectedMatchSourceId: entry.selectedMatchSourceId,
|
||||||
|
destSourceIdToSearchState: entry.destSourceIdToSearchState,
|
||||||
|
error: entry.error,
|
||||||
|
isExcluded: entry.isExcluded,
|
||||||
|
areMatchesExpanded: entry.areMatchesExpanded,
|
||||||
|
} satisfies TMigrationEntry;
|
||||||
|
|
||||||
|
if (isEqual(entry, updatedEntry)) {
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
MigrationManager.updateState((draft) => {
|
||||||
|
draft.entries[entry.mangaId] = updatedEntry;
|
||||||
|
});
|
||||||
|
|
||||||
|
return updatedEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
static selectSource(sourceId: SourceIdInfo['id']): void {
|
||||||
|
MigrationManager.updateState((draft) => {
|
||||||
|
draft.phase = MigrationPhase.SELECT_MANGAS;
|
||||||
|
draft.sourceId = sourceId;
|
||||||
|
draft.entries = {};
|
||||||
|
});
|
||||||
|
ReactRouter.navigate(AppRoutes.migrate.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
static selectMangas(mangas: MangaMigrationFieldsFragment[]): void {
|
||||||
|
MigrationManager.updateState((draft) => {
|
||||||
|
draft.phase = MigrationPhase.SELECTING_SOURCES;
|
||||||
|
draft.entries = Object.fromEntries(
|
||||||
|
mangas.map((manga) => [
|
||||||
|
manga.id,
|
||||||
|
{
|
||||||
|
mangaId: manga.id,
|
||||||
|
mangaTitle: manga.title,
|
||||||
|
mangaArtist: manga.artist,
|
||||||
|
mangaAuthor: manga.author,
|
||||||
|
latestChapterNumber: manga.highestNumberedChapter?.chapterNumber,
|
||||||
|
mangaThumbnailUrl: manga.thumbnailUrl,
|
||||||
|
sourceId: manga.sourceId,
|
||||||
|
sourceTitle: manga.source?.displayName,
|
||||||
|
status: MigrationEntryStatus.PENDING,
|
||||||
|
searchMatches: [],
|
||||||
|
manualMatches: [],
|
||||||
|
selectedMatchMangaId: null,
|
||||||
|
selectedMatchSourceId: null,
|
||||||
|
destSourceIdToSearchState: {},
|
||||||
|
isExcluded: false,
|
||||||
|
areMatchesExpanded: false,
|
||||||
|
error: undefined,
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static async startSearch(destinationSourceIds: SourceIdInfo['id'][]): Promise<void> {
|
||||||
|
MigrationManager.updateState((draft) => {
|
||||||
|
draft.destinationSourceIds = destinationSourceIds;
|
||||||
|
});
|
||||||
|
|
||||||
|
const state = MigrationManager.getState();
|
||||||
|
const entryIds = Object.keys(state.entries).map(Number);
|
||||||
|
|
||||||
|
MigrationManager.updateState((draft) => {
|
||||||
|
draft.phase = MigrationPhase.SEARCHING;
|
||||||
|
draft.searchProgress = { total: entryIds.length, completed: 0, success: 0, failed: 0 };
|
||||||
|
draft.startedAt = Date.now();
|
||||||
|
draft.groupExpandState = MIGRATE_SEARCH_ENTRY_GROUP_EXPAND_DEFAULT_STATE;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await MigrationManager.search(Object.values(state.entries));
|
||||||
|
} finally {
|
||||||
|
const { searchProgress } = MigrationManager.getState();
|
||||||
|
|
||||||
|
if (searchProgress.completed === searchProgress.total) {
|
||||||
|
const allSearchesFailed = searchProgress.failed === searchProgress.total;
|
||||||
|
|
||||||
|
MigrationManager.updateState((draft) => {
|
||||||
|
draft.groupExpandState = {
|
||||||
|
...MIGRATE_SEARCH_ENTRY_GROUP_EXPAND_DEFAULT_STATE,
|
||||||
|
[MigrationEntryStatus.SEARCHING]: false,
|
||||||
|
[MigrationEntryStatus.NO_MATCH]: !allSearchesFailed && !searchProgress.success,
|
||||||
|
[MigrationEntryStatus.SEARCH_FAILED]: allSearchesFailed,
|
||||||
|
[MigrationEntryStatus.SEARCH_COMPLETE]: !!searchProgress.success,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async search(entries: TMigrationEntry[]): Promise<void> {
|
||||||
|
const { signal } = MigrationManager.abortAndCreateAbortController('search');
|
||||||
|
|
||||||
|
const searchPromises = entries.map((entry) =>
|
||||||
|
MigrationManager.mangaProcessQueue(async () => {
|
||||||
|
if (signal.aborted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await MigrationManager.searchForManga(entry.mangaId, entry.mangaTitle, signal);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.allSettled(searchPromises);
|
||||||
|
}
|
||||||
|
|
||||||
|
static getMigratableEntries(): MigratableEntry[] {
|
||||||
|
const { entries } = MigrationManager.getState();
|
||||||
|
|
||||||
|
return Object.values(entries).filter(
|
||||||
|
(entry): entry is MigratableEntry =>
|
||||||
|
entry.status === MigrationEntryStatus.SEARCH_COMPLETE &&
|
||||||
|
!entry.isExcluded &&
|
||||||
|
entry.selectedMatchMangaId != null &&
|
||||||
|
entry.selectedMatchSourceId != null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async startMigration(options: Omit<MigrateOptions, 'mangaIdToMigrateTo'>): Promise<void> {
|
||||||
|
const migratableEntries = MigrationManager.getMigratableEntries();
|
||||||
|
|
||||||
|
await Confirmation.show({
|
||||||
|
title: t`Migration information`,
|
||||||
|
message: t`The migration runs on the client on the current device, NOT the server.\nAs long as the client is open, the migration will run in the background.\nThe client can be closed. The migration will be resumed once it gets opened again on the same device it got started on.`,
|
||||||
|
actions: {
|
||||||
|
confirm: {
|
||||||
|
title: t`Understood`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
MigrationManager.updateState((draft) => {
|
||||||
|
draft.phase = MigrationPhase.MIGRATING;
|
||||||
|
draft.migrateOptions = options;
|
||||||
|
draft.migrationProgress = { total: migratableEntries.length, completed: 0, success: 0, failed: 0 };
|
||||||
|
draft.groupExpandState = MIGRATE_EXECUTE_ENTRY_GROUP_EXPAND_DEFAULT_STATE;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await MigrationManager.migrate(migratableEntries, options);
|
||||||
|
} finally {
|
||||||
|
const { migrationProgress } = MigrationManager.getState();
|
||||||
|
|
||||||
|
if (migrationProgress.completed === migrationProgress.total) {
|
||||||
|
MigrationManager.updateState((draft) => {
|
||||||
|
draft.groupExpandState = {
|
||||||
|
...MIGRATE_SEARCH_ENTRY_GROUP_EXPAND_DEFAULT_STATE,
|
||||||
|
[MigrationEntryStatus.MIGRATING]: false,
|
||||||
|
[MigrationEntryStatus.MIGRATION_FAILED]: !!migrationProgress.failed,
|
||||||
|
[MigrationEntryStatus.MIGRATION_COMPLETE]: !migrationProgress.failed,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async migrate(
|
||||||
|
entries: MigratableEntry[],
|
||||||
|
options: Omit<MigrateOptions, 'mangaIdToMigrateTo'>,
|
||||||
|
): Promise<void> {
|
||||||
|
const { signal } = MigrationManager.abortAndCreateAbortController('migrate');
|
||||||
|
|
||||||
|
const entriesBySource = Object.groupBy(entries, (entry) => entry.selectedMatchSourceId);
|
||||||
|
|
||||||
|
const migrationPromises = Object.values(entriesBySource).map((sourceEntries = []) =>
|
||||||
|
MigrationManager.mangaProcessQueue(async () => {
|
||||||
|
for (const entry of sourceEntries) {
|
||||||
|
if (signal.aborted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// oxlint-disable-next-line no-await-in-loop
|
||||||
|
await MigrationManager.migrateSingleEntry(entry.mangaId, options, signal);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
await Promise.allSettled(migrationPromises);
|
||||||
|
|
||||||
|
if (!signal.aborted) {
|
||||||
|
MigrationManager.updateState((draft) => {
|
||||||
|
draft.lastUpdatedAt = Date.now();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async abort(reason: unknown = 'abort'): Promise<boolean> {
|
||||||
|
if (!(await MigrationManager.confirmAbort())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
MigrationManager.abortAndResetAbortController(reason);
|
||||||
|
|
||||||
|
MigrationManager.reset();
|
||||||
|
|
||||||
|
ReactRouter.navigate(AppRoutes.browse.path(BrowseTab.MIGRATE));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async resume(): Promise<void> {
|
||||||
|
const state = MigrationManager.getState();
|
||||||
|
|
||||||
|
const isResumeablePhase = RESUMABLE_PHASES.includes(state.phase);
|
||||||
|
if (!isResumeablePhase) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resumeMigrationPhase = state.phase === MigrationPhase.MIGRATING && state.migrateOptions;
|
||||||
|
if (resumeMigrationPhase) {
|
||||||
|
assertIsDefined(state.migrateOptions);
|
||||||
|
|
||||||
|
const migratableEntries = MigrationManager.getMigratableEntries();
|
||||||
|
|
||||||
|
await MigrationManager.migrate(migratableEntries, state.migrateOptions);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pendingEntries = Object.values(state.entries).filter(
|
||||||
|
(entry) => entry.status === MigrationEntryStatus.PENDING,
|
||||||
|
);
|
||||||
|
|
||||||
|
await MigrationManager.search(pendingEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
static reset(): void {
|
||||||
|
MigrationManager.abortAndResetAbortController('reset');
|
||||||
|
migrationStore.setState({ ...DEFAULT_MIGRATION_STATE });
|
||||||
|
}
|
||||||
|
|
||||||
|
static excludeManga(mangaId: MangaIdInfo['id']): void {
|
||||||
|
MigrationManager.updateState((draft) => {
|
||||||
|
const entry = draft.entries[mangaId];
|
||||||
|
|
||||||
|
if (entry) {
|
||||||
|
entry.isExcluded = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static includeManga(mangaId: MangaIdInfo['id']): void {
|
||||||
|
MigrationManager.updateState((draft) => {
|
||||||
|
const entry = draft.entries[mangaId];
|
||||||
|
|
||||||
|
if (entry) {
|
||||||
|
entry.isExcluded = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static selectMatch(
|
||||||
|
mangaId: MangaIdInfo['id'],
|
||||||
|
targetMangaId: MangaIdInfo['id'],
|
||||||
|
targetSourceId: SourceIdInfo['id'],
|
||||||
|
): void {
|
||||||
|
MigrationManager.updateState((draft) => {
|
||||||
|
const entry = draft.entries[mangaId];
|
||||||
|
if (entry) {
|
||||||
|
entry.selectedMatchMangaId = targetMangaId;
|
||||||
|
entry.selectedMatchSourceId = targetSourceId;
|
||||||
|
entry.status = MigrationEntryStatus.SEARCH_COMPLETE;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static selectManualMatch(mangaId: MangaIdInfo['id'], match: MigrationMatch): void {
|
||||||
|
MigrationManager.updateState((draft) => {
|
||||||
|
const entry = draft.entries[mangaId];
|
||||||
|
if (entry) {
|
||||||
|
const isExistingSearchMatch = draft.entries[mangaId].searchMatches.some(
|
||||||
|
(searchMatch) => searchMatch.id === match.id,
|
||||||
|
);
|
||||||
|
const isExistingManualMatch = draft.entries[mangaId].manualMatches.some(
|
||||||
|
(manualMatch) => manualMatch.id === match.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
const isExistingEntry = isExistingSearchMatch || isExistingManualMatch;
|
||||||
|
if (!isExistingEntry) {
|
||||||
|
draft.entries[mangaId].manualMatches = [...draft.entries[mangaId].manualMatches, match];
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.selectedMatchMangaId = match.id;
|
||||||
|
entry.selectedMatchSourceId = match.sourceId;
|
||||||
|
entry.status = MigrationEntryStatus.SEARCH_COMPLETE;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static getState(): MigrationState {
|
||||||
|
return migrationStore.getState();
|
||||||
|
}
|
||||||
|
|
||||||
|
static isActive(): boolean {
|
||||||
|
const { phase } = migrationStore.getState();
|
||||||
|
return phase === MigrationPhase.SEARCHING || phase === MigrationPhase.MIGRATING;
|
||||||
|
}
|
||||||
|
|
||||||
|
static hasPausedMigration(): boolean {
|
||||||
|
return RESUMABLE_PHASES.includes(MigrationManager.getState().phase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static getHigherPrioritySourceIds(sourceId: SourceIdInfo['id']): SourceIdInfo['id'][] {
|
||||||
|
const { destinationSourceIds } = MigrationManager.getState();
|
||||||
|
|
||||||
|
const sourceIdPriority = destinationSourceIds.indexOf(sourceId);
|
||||||
|
return destinationSourceIds.slice(0, Math.max(0, sourceIdPriority - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static isHigherPrioritySourceUnsettled(mangaId: MangaIdInfo['id'], sourceId: SourceIdInfo['id']): boolean {
|
||||||
|
const { entries } = MigrationManager.getState();
|
||||||
|
const entry = entries[mangaId];
|
||||||
|
|
||||||
|
assertIsDefined(entry);
|
||||||
|
|
||||||
|
return MigrationManager.getHigherPrioritySourceIds(sourceId).some(
|
||||||
|
(higherPrioritySourceId) => entry.destSourceIdToSearchState[higherPrioritySourceId] == null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static hasHigherSourcePriorityMatch(mangaId: MangaIdInfo['id'], sourceId: SourceIdInfo['id']): boolean {
|
||||||
|
const { entries } = MigrationManager.getState();
|
||||||
|
const entry = entries[mangaId];
|
||||||
|
|
||||||
|
if (!entry || entry.selectedMatchSourceId == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MigrationManager.getHigherPrioritySourceIds(sourceId).some(
|
||||||
|
(higherPrioritySourceId) => entry.destSourceIdToSearchState[higherPrioritySourceId],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async findMatchesForMangaInSource(
|
||||||
|
mangaId: MangaIdInfo['id'],
|
||||||
|
mangaTitle: string,
|
||||||
|
sourceId: SourceIdInfo['id'],
|
||||||
|
signal: AbortSignal,
|
||||||
|
): Promise<MangaMigrationFieldsFragment[]> {
|
||||||
|
if (signal.aborted) {
|
||||||
|
throw new Error(signal.reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
return MigrationManager.getOrCreateSourceQueue(sourceId)(async () => {
|
||||||
|
if (signal.aborted) {
|
||||||
|
throw new Error(signal.reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MigrationManager.hasHigherSourcePriorityMatch(mangaId, sourceId)) {
|
||||||
|
throw new Error('Entry already has a selected match from a higher priority source');
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchResponse = await requestManager.graphQLClient.client.mutate<
|
||||||
|
GetMigrationSourceMangasFetchMutation,
|
||||||
|
GetMigrationSourceMangasFetchMutationVariables
|
||||||
|
>({
|
||||||
|
mutation: GET_MIGRATION_SOURCE_MANGAS_FETCH,
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
source: sourceId,
|
||||||
|
query: mangaTitle,
|
||||||
|
page: 1,
|
||||||
|
type: FetchSourceMangaType.Search,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
context: { fetchOptions: { signal } },
|
||||||
|
});
|
||||||
|
|
||||||
|
const searchMatches = searchResponse?.data?.fetchSourceManga?.mangas ?? [];
|
||||||
|
const matches = searchMatches.filter(
|
||||||
|
(searchMatch) => enhancedCleanup(searchMatch.title) === enhancedCleanup(mangaTitle),
|
||||||
|
);
|
||||||
|
|
||||||
|
const matchUpdatePromises = matches.map(async (match) => {
|
||||||
|
if (signal.aborted) {
|
||||||
|
throw new Error(signal.reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedMatch = await requestManager.getMangaFetch(match.id).response;
|
||||||
|
|
||||||
|
return updatedMatch.data?.fetchManga?.manga ?? match;
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedMatches = await Promise.all(matchUpdatePromises);
|
||||||
|
|
||||||
|
return updatedMatches;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async searchForManga(
|
||||||
|
mangaId: MangaIdInfo['id'],
|
||||||
|
mangaTitle: string,
|
||||||
|
mainSignal: AbortSignal,
|
||||||
|
): Promise<void> {
|
||||||
|
const state = MigrationManager.getState();
|
||||||
|
const entry = state.entries[mangaId];
|
||||||
|
|
||||||
|
const searchController = new AbortController();
|
||||||
|
const signal = AbortSignal.any([mainSignal, searchController.signal]);
|
||||||
|
|
||||||
|
if (!entry) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MigrationManager.updateState((draft) => {
|
||||||
|
draft.entries[mangaId].status = MigrationEntryStatus.SEARCHING;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const searchPromises = state.destinationSourceIds.map((destSourceId) =>
|
||||||
|
MigrationManager.getParallelSourceQueue()(async () => {
|
||||||
|
if (signal.aborted) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MigrationManager.hasHigherSourcePriorityMatch(mangaId, destSourceId)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const foundMatches = await (async () => {
|
||||||
|
try {
|
||||||
|
return await MigrationManager.findMatchesForMangaInSource(
|
||||||
|
mangaId,
|
||||||
|
mangaTitle,
|
||||||
|
destSourceId,
|
||||||
|
signal,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
MigrationManager.updateState((draft) => {
|
||||||
|
const draftEntry = draft.entries[mangaId];
|
||||||
|
|
||||||
|
draftEntry.destSourceIdToSearchState[destSourceId] = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
if (!foundMatches.length) {
|
||||||
|
MigrationManager.updateState((draft) => {
|
||||||
|
const draftEntry = draft.entries[mangaId];
|
||||||
|
|
||||||
|
draftEntry.destSourceIdToSearchState[destSourceId] = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
MigrationManager.updateState((draft) => {
|
||||||
|
const draftEntry = draft.entries[mangaId];
|
||||||
|
|
||||||
|
const matches = foundMatches.map((manga) => ({
|
||||||
|
id: manga.id,
|
||||||
|
title: manga.title,
|
||||||
|
artist: manga.artist,
|
||||||
|
author: manga.author,
|
||||||
|
latestChapterNumber: manga.highestNumberedChapter?.chapterNumber,
|
||||||
|
thumbnailUrl: manga.thumbnailUrl,
|
||||||
|
sourceId: manga.sourceId,
|
||||||
|
sourceTitle: manga.source?.displayName,
|
||||||
|
}));
|
||||||
|
|
||||||
|
draftEntry.destSourceIdToSearchState[destSourceId] = true;
|
||||||
|
|
||||||
|
draftEntry.searchMatches = [...draftEntry.searchMatches, ...matches];
|
||||||
|
|
||||||
|
if (!MigrationManager.hasHigherSourcePriorityMatch(mangaId, destSourceId)) {
|
||||||
|
const matchesByChapterNumber = Object.groupBy(
|
||||||
|
matches,
|
||||||
|
(match) => match.latestChapterNumber ?? -1,
|
||||||
|
);
|
||||||
|
const latestChapterNumber = Math.max(
|
||||||
|
...Object.keys(matchesByChapterNumber).map((chapterNumber) => Number(chapterNumber)),
|
||||||
|
);
|
||||||
|
const bestMatch = matchesByChapterNumber[latestChapterNumber]?.[0];
|
||||||
|
|
||||||
|
assertIsDefined(bestMatch);
|
||||||
|
|
||||||
|
draftEntry.selectedMatchMangaId = bestMatch.id;
|
||||||
|
draftEntry.selectedMatchSourceId = destSourceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!MigrationManager.isHigherPrioritySourceUnsettled(mangaId, destSourceId)) {
|
||||||
|
searchController.abort(`Found best match in source "${destSourceId}"`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const searchMatchPromises = await Promise.allSettled(searchPromises);
|
||||||
|
|
||||||
|
const hasFulfilledSearch = searchMatchPromises.some((result) => result.status === 'fulfilled');
|
||||||
|
if (!hasFulfilledSearch) {
|
||||||
|
throw new Error('All source searches failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
MigrationManager.updateState((draft) => {
|
||||||
|
const draftEntry = draft.entries[mangaId];
|
||||||
|
|
||||||
|
if (draftEntry.searchMatches.length) {
|
||||||
|
draft.searchProgress.success += 1;
|
||||||
|
draftEntry.status = MigrationEntryStatus.SEARCH_COMPLETE;
|
||||||
|
} else {
|
||||||
|
draftEntry.status = MigrationEntryStatus.NO_MATCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
draft.searchProgress.completed += 1;
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (signal.aborted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MigrationManager.updateState((draft) => {
|
||||||
|
const draftEntry = draft.entries[mangaId];
|
||||||
|
|
||||||
|
draftEntry.status = MigrationEntryStatus.SEARCH_FAILED;
|
||||||
|
draftEntry.error = getErrorMessage(error);
|
||||||
|
draft.searchProgress.completed += 1;
|
||||||
|
draft.searchProgress.failed += 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async migrateSingleEntry(
|
||||||
|
mangaId: MangaIdInfo['id'],
|
||||||
|
options: Omit<MigrateOptions, 'mangaIdToMigrateTo'>,
|
||||||
|
signal: AbortSignal,
|
||||||
|
): Promise<void> {
|
||||||
|
const state = MigrationManager.getState();
|
||||||
|
const entry = state.entries[mangaId];
|
||||||
|
|
||||||
|
if (!entry || !entry.selectedMatchSourceId || entry.selectedMatchMangaId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MigrationManager.updateState((draft) => {
|
||||||
|
draft.entries[mangaId].status = MigrationEntryStatus.MIGRATING;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (signal.aborted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await MigrationManager.getParallelSourceQueue()(() =>
|
||||||
|
MigrationManager.getOrCreateSourceQueue(entry.sourceId)(() => {
|
||||||
|
assertIsDefined(entry.selectedMatchSourceId);
|
||||||
|
|
||||||
|
return MigrationManager.getOrCreateSourceQueue(entry.selectedMatchSourceId)(async () => {
|
||||||
|
if (signal.aborted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
assertIsDefined(entry.selectedMatchMangaId);
|
||||||
|
await MangaMigration.migrate(mangaId, entry.selectedMatchMangaId, options);
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
MigrationManager.updateState((draft) => {
|
||||||
|
draft.entries[mangaId].status = MigrationEntryStatus.MIGRATION_COMPLETE;
|
||||||
|
draft.migrationProgress.success += 1;
|
||||||
|
draft.migrationProgress.completed += 1;
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (signal.aborted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MigrationManager.updateState((draft) => {
|
||||||
|
draft.entries[mangaId].status = MigrationEntryStatus.MIGRATION_FAILED;
|
||||||
|
draft.entries[mangaId].error = error instanceof Error ? error.message : String(error);
|
||||||
|
draft.migrationProgress.failed += 1;
|
||||||
|
draft.migrationProgress.completed += 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async retryEntry(id: MangaIdInfo['id']): Promise<void> {
|
||||||
|
const entry = MigrationManager.getState().entries[id];
|
||||||
|
|
||||||
|
if (!entry) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { signal } = MigrationManager.getOrCreateAbortController();
|
||||||
|
const { status } = entry;
|
||||||
|
|
||||||
|
if (status === MigrationEntryStatus.SEARCH_FAILED) {
|
||||||
|
MigrationManager.updateState((draft) => {
|
||||||
|
draft.searchProgress.completed -= 1;
|
||||||
|
draft.searchProgress.failed -= 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
await MigrationManager.searchForManga(id, entry.mangaTitle, signal);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status === MigrationEntryStatus.MIGRATION_FAILED) {
|
||||||
|
MigrationManager.updateState((draft) => {
|
||||||
|
draft.migrationProgress.completed -= 1;
|
||||||
|
draft.migrationProgress.failed -= 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
const migrationOptions = MigrationManager.getState().migrateOptions;
|
||||||
|
assertIsDefined(migrationOptions);
|
||||||
|
|
||||||
|
await MigrationManager.migrateSingleEntry(id, migrationOptions, signal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static updateState(updater: (draft: MigrationState) => void): void {
|
||||||
|
migrationStore.setState(updater);
|
||||||
|
}
|
||||||
|
|
||||||
|
static setEntryMatchesExpandState(id: MangaIdInfo['id'], expanded: boolean): void {
|
||||||
|
MigrationManager.updateState((draft) => {
|
||||||
|
const entry = draft.entries[id];
|
||||||
|
|
||||||
|
if (entry) {
|
||||||
|
entry.areMatchesExpanded = expanded;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static useEntryMatchesExpandState(id: MangaIdInfo['id']): boolean {
|
||||||
|
return useMigrationStore((state) => state.entries[id]?.areMatchesExpanded ?? false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static setGroupExpandState(status: MigrationEntryStatus, expanded: boolean): void {
|
||||||
|
MigrationManager.updateState((draft) => {
|
||||||
|
draft.groupExpandState[status] = expanded;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static useGroupExpandState(status: MigrationEntryStatus): boolean {
|
||||||
|
return useMigrationStore((state) => state.groupExpandState[status] ?? false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static usePhase(): MigrationPhase {
|
||||||
|
return useMigrationStore((state) => state.phase);
|
||||||
|
}
|
||||||
|
|
||||||
|
static useSourceId(): SourceIdInfo['id'] | null {
|
||||||
|
return useMigrationStore((state) => state.sourceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
static useEntries(): Record<number, TMigrationEntry> {
|
||||||
|
return useMigrationStore((state) => state.entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
static useSearchProgress(): MigrationState['searchProgress'] {
|
||||||
|
return useMigrationStore((state) => state.searchProgress);
|
||||||
|
}
|
||||||
|
|
||||||
|
static useMigrationProgress(): MigrationState['migrationProgress'] {
|
||||||
|
return useMigrationStore((state) => state.migrationProgress);
|
||||||
|
}
|
||||||
|
|
||||||
|
static useIsActive(): boolean {
|
||||||
|
// Listen to phase changes
|
||||||
|
MigrationManager.usePhase();
|
||||||
|
|
||||||
|
return MigrationManager.isActive();
|
||||||
|
}
|
||||||
|
}
|
||||||
68
src/features/migration/components/MIgrationEntryGroup.tsx
Normal file
68
src/features/migration/components/MIgrationEntryGroup.tsx
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* 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 type { MigrationEntryStatus, TMigrationEntry } from '@/features/migration/Migration.types.ts';
|
||||||
|
import type { ButtonProps } from '@mui/material/Button';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
import Collapse from '@mui/material/Collapse';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
|
import {} from 'react';
|
||||||
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||||
|
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
|
||||||
|
import { MigrationEntry } from '@/features/migration/components/migration-entry/MigrationEntry.tsx';
|
||||||
|
import { MigrationManager } from '@/features/migration/MigrationManager.ts';
|
||||||
|
|
||||||
|
export const MigrationEntryGroup = ({
|
||||||
|
status,
|
||||||
|
title,
|
||||||
|
entries,
|
||||||
|
color,
|
||||||
|
isMigrating = false,
|
||||||
|
}: {
|
||||||
|
status: MigrationEntryStatus;
|
||||||
|
title: string;
|
||||||
|
entries: TMigrationEntry[];
|
||||||
|
color: ButtonProps['color'];
|
||||||
|
isMigrating?: boolean;
|
||||||
|
}) => {
|
||||||
|
const isExpanded = MigrationManager.useGroupExpandState(status);
|
||||||
|
|
||||||
|
if (!entries.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack sx={{ width: '100%', gap: 2 }}>
|
||||||
|
<Button
|
||||||
|
onClick={() => MigrationManager.setGroupExpandState(status, !isExpanded)}
|
||||||
|
color={color}
|
||||||
|
variant="outlined"
|
||||||
|
startIcon={isExpanded ? <ExpandLessIcon /> : <ExpandMoreIcon />}
|
||||||
|
size="large"
|
||||||
|
sx={{
|
||||||
|
py: 2,
|
||||||
|
justifyContent: 'center',
|
||||||
|
'& .MuiButton-startIcon': {
|
||||||
|
position: 'absolute',
|
||||||
|
left: (theme) => theme.spacing(4),
|
||||||
|
margin: 0,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</Button>
|
||||||
|
<Collapse in={isExpanded} unmountOnExit>
|
||||||
|
<Stack sx={{ gap: 1 }}>
|
||||||
|
{entries.map((entry) => (
|
||||||
|
<MigrationEntry key={entry.mangaId} entry={entry} isMigrating={isMigrating} />
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</Collapse>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -11,12 +11,11 @@ import Box from '@mui/material/Box';
|
|||||||
import CardActionArea from '@mui/material/CardActionArea';
|
import CardActionArea from '@mui/material/CardActionArea';
|
||||||
import Chip from '@mui/material/Chip';
|
import Chip from '@mui/material/Chip';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||||
import type { GetMigratableSourcesQuery } from '@/lib/graphql/generated/graphql.ts';
|
import type { GetMigratableSourcesQuery } from '@/lib/graphql/generated/graphql.ts';
|
||||||
import { translateExtensionLanguage } from '@/features/extension/Extensions.utils.ts';
|
import { translateExtensionLanguage } from '@/features/extension/Extensions.utils.ts';
|
||||||
import { AppRoutes } from '@/base/AppRoute.constants.ts';
|
import { MigrationManager } from '@/features/migration/MigrationManager.ts';
|
||||||
import { ListCardAvatar } from '@/base/components/lists/cards/ListCardAvatar.tsx';
|
import { ListCardAvatar } from '@/base/components/lists/cards/ListCardAvatar.tsx';
|
||||||
import { ListCardContent } from '@/base/components/lists/cards/ListCardContent.tsx';
|
import { ListCardContent } from '@/base/components/lists/cards/ListCardContent.tsx';
|
||||||
|
|
||||||
@@ -33,7 +32,7 @@ export const MigrationCard = ({ id, name, lang, iconUrl, mangaCount }: TMigratab
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardActionArea component={Link} to={AppRoutes.migrate.path(id)}>
|
<CardActionArea onClick={() => MigrationManager.selectSource(id)}>
|
||||||
<ListCardContent sx={{ justifyContent: 'space-between' }}>
|
<ListCardContent sx={{ justifyContent: 'space-between' }}>
|
||||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||||
<ListCardAvatar
|
<ListCardAvatar
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* 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 { useLingui } from '@lingui/react/macro';
|
||||||
|
import Fab from '@mui/material/Fab';
|
||||||
|
|
||||||
|
export const MigrationContinueButton = ({
|
||||||
|
onClick,
|
||||||
|
isDisabled,
|
||||||
|
title,
|
||||||
|
}: {
|
||||||
|
onClick: () => void;
|
||||||
|
isDisabled?: boolean;
|
||||||
|
title?: string;
|
||||||
|
}) => {
|
||||||
|
const { t } = useLingui();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fab
|
||||||
|
variant="extended"
|
||||||
|
color="primary"
|
||||||
|
sx={{
|
||||||
|
position: 'fixed',
|
||||||
|
bottom: (theme) => theme.spacing(2),
|
||||||
|
right: (theme) => theme.spacing(2),
|
||||||
|
}}
|
||||||
|
disabled={isDisabled}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
{title ?? t`Continue`}
|
||||||
|
</Fab>
|
||||||
|
);
|
||||||
|
};
|
||||||
74
src/features/migration/components/MigrationFABIndicator.tsx
Normal file
74
src/features/migration/components/MigrationFABIndicator.tsx
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* 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 Chip from '@mui/material/Chip';
|
||||||
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
|
import { useNavigate, useLocation } from 'react-router-dom';
|
||||||
|
import type { MigrationProgress } from '@/features/migration/Migration.types.ts';
|
||||||
|
import { MigrationPhase } from '@/features/migration/Migration.types.ts';
|
||||||
|
import { MigrationManager } from '@/features/migration/MigrationManager.ts';
|
||||||
|
import { AppRoutes } from '@/base/AppRoute.constants.ts';
|
||||||
|
import DoneAllIcon from '@mui/icons-material/DoneAll';
|
||||||
|
import { useLayoutEffect, useState } from 'react';
|
||||||
|
|
||||||
|
const getButtonText = (phase: MigrationPhase, progress: MigrationProgress) => {
|
||||||
|
switch (phase) {
|
||||||
|
case MigrationPhase.SEARCHING:
|
||||||
|
if (progress.total === progress.completed) {
|
||||||
|
return `Searching`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `Searching (${progress.completed}/${progress.total})`;
|
||||||
|
|
||||||
|
case MigrationPhase.MIGRATING:
|
||||||
|
if (progress.total === progress.completed) {
|
||||||
|
return `Migrating`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `Migrating (${progress.completed}/${progress.total})`;
|
||||||
|
default:
|
||||||
|
return 'Migrating';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MigrationFABIndicator = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
const isActive = MigrationManager.useIsActive();
|
||||||
|
const phase = MigrationManager.usePhase();
|
||||||
|
const searchProgress = MigrationManager.useSearchProgress();
|
||||||
|
const migrationProgress = MigrationManager.useMigrationProgress();
|
||||||
|
|
||||||
|
const [isVisible, setIsVisible] = useState(true);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
setIsVisible(true);
|
||||||
|
}, [phase]);
|
||||||
|
|
||||||
|
if (!isVisible || !isActive || location.pathname.startsWith(AppRoutes.migrate.path)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Chip
|
||||||
|
icon={MigrationManager.isPhaseComplete() ? <DoneAllIcon /> : <CircularProgress size={16} color="inherit" />}
|
||||||
|
label={getButtonText(phase, phase === MigrationPhase.SEARCHING ? searchProgress : migrationProgress)}
|
||||||
|
color="primary"
|
||||||
|
onClick={() => navigate(AppRoutes.migrate.path)}
|
||||||
|
sx={{
|
||||||
|
position: 'fixed',
|
||||||
|
bottom: (theme) => theme.spacing(2),
|
||||||
|
right: (theme) => theme.spacing(2),
|
||||||
|
zIndex: (theme) => theme.zIndex.fab,
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
onDelete={() => setIsVisible(false)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -11,96 +11,83 @@ import DialogTitle from '@mui/material/DialogTitle';
|
|||||||
import DialogContent from '@mui/material/DialogContent';
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
import DialogActions from '@mui/material/DialogActions';
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import Stack from '@mui/material/Stack';
|
|
||||||
import { Link, useNavigate, useParams } from 'react-router-dom';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import FormGroup from '@mui/material/FormGroup';
|
import FormGroup from '@mui/material/FormGroup';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { CheckboxInput } from '@/base/components/inputs/CheckboxInput.tsx';
|
import { CheckboxInput } from '@/base/components/inputs/CheckboxInput.tsx';
|
||||||
import { Mangas } from '@/features/manga/services/Mangas.ts';
|
|
||||||
import { makeToast } from '@/base/utils/Toast.ts';
|
|
||||||
import {
|
import {
|
||||||
createUpdateMetadataServerSettings,
|
createUpdateMetadataServerSettings,
|
||||||
useMetadataServerSettings,
|
useMetadataServerSettings,
|
||||||
} from '@/features/settings/services/ServerSettingsMetadata.ts';
|
} from '@/features/settings/services/ServerSettingsMetadata.ts';
|
||||||
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
||||||
import type { MetadataMigrationSettings, MigrateMode } from '@/features/migration/Migration.types.ts';
|
import type { MetadataMigrationSettings, MigrateOptions } from '@/features/migration/Migration.types.ts';
|
||||||
|
import type { AwaitableComponentProps } from 'awaitable-component';
|
||||||
|
import type { MangaIdInfo } from '@/features/manga/Manga.types.ts';
|
||||||
import { AppRoutes } from '@/base/AppRoute.constants.ts';
|
import { AppRoutes } from '@/base/AppRoute.constants.ts';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
export const MigrateDialog = ({ mangaIdToMigrateTo, onClose }: { mangaIdToMigrateTo: number; onClose: () => void }) => {
|
export const MigrationOptionsDialog = ({
|
||||||
|
isVisible,
|
||||||
|
onDismiss,
|
||||||
|
onSubmit,
|
||||||
|
onExitComplete,
|
||||||
|
isMigrating = false,
|
||||||
|
mangaIdToMigrateTo,
|
||||||
|
startMigration = onSubmit,
|
||||||
|
}: AwaitableComponentProps<Omit<MigrateOptions, 'mangaIdToMigrateTo'>> & {
|
||||||
|
isMigrating?: boolean;
|
||||||
|
mangaIdToMigrateTo?: MangaIdInfo['id'];
|
||||||
|
startMigration?: (options: Omit<MigrateOptions, 'mangaIdToMigrateTo'>) => void;
|
||||||
|
}) => {
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const { mangaId: mangaIdAsString } = useParams<{ mangaId: string }>();
|
|
||||||
const mangaId = Number(mangaIdAsString);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
settings: { migrateChapters, migrateCategories, migrateTracking, deleteChapters, migrateMetadata },
|
settings: { migrateChapters, migrateCategories, migrateTracking, deleteChapters, migrateMetadata },
|
||||||
} = useMetadataServerSettings();
|
} = useMetadataServerSettings();
|
||||||
|
|
||||||
const [isMigrationInProcess, setIsMigrationInProcess] = useState(false);
|
const options: Omit<MigrateOptions, 'mangaIdToMigrateTo' | 'mode'> = {
|
||||||
|
migrateChapters,
|
||||||
const setMigrationFlag = createUpdateMetadataServerSettings<keyof MetadataMigrationSettings>(
|
migrateCategories,
|
||||||
defaultPromiseErrorHandler('MigrateDialog::updateSetting'),
|
migrateTracking,
|
||||||
);
|
deleteChapters,
|
||||||
|
migrateMetadata,
|
||||||
const migrate = async (mode: MigrateMode) => {
|
|
||||||
if (mangaId == null) {
|
|
||||||
throw new Error(`MigrateDialog::migrate: unexpected mangaId "${mangaId}"`);
|
|
||||||
}
|
|
||||||
|
|
||||||
makeToast(t`Migrating manga…`, 'info');
|
|
||||||
|
|
||||||
setIsMigrationInProcess(true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await Mangas.migrate(mangaId, mangaIdToMigrateTo, {
|
|
||||||
mode,
|
|
||||||
migrateChapters,
|
|
||||||
migrateCategories,
|
|
||||||
migrateTracking,
|
|
||||||
deleteChapters,
|
|
||||||
migrateMetadata,
|
|
||||||
});
|
|
||||||
|
|
||||||
navigate(AppRoutes.manga.path(mangaIdToMigrateTo), { replace: true });
|
|
||||||
} catch (e) {
|
|
||||||
setIsMigrationInProcess(false);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setMigrationFlag = createUpdateMetadataServerSettings<keyof MetadataMigrationSettings>(
|
||||||
|
defaultPromiseErrorHandler('MigrationOptionsDialog::updateSetting'),
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open fullWidth onClose={onClose}>
|
<Dialog open={isVisible} fullWidth onClose={onDismiss} onTransitionExited={onExitComplete}>
|
||||||
<DialogTitle>{t`Select data to include`}</DialogTitle>
|
<DialogTitle>{t`Migration options`}</DialogTitle>
|
||||||
<DialogContent dividers>
|
<DialogContent dividers>
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<CheckboxInput
|
<CheckboxInput
|
||||||
disabled={isMigrationInProcess}
|
disabled={isMigrating}
|
||||||
label={t`Chapter`}
|
label={t`Chapter`}
|
||||||
checked={migrateChapters}
|
checked={migrateChapters}
|
||||||
onChange={(_, checked) => setMigrationFlag('migrateChapters', checked)}
|
onChange={(_, checked) => setMigrationFlag('migrateChapters', checked)}
|
||||||
/>
|
/>
|
||||||
<CheckboxInput
|
<CheckboxInput
|
||||||
disabled={isMigrationInProcess}
|
disabled={isMigrating}
|
||||||
label={t`Category`}
|
label={t`Category`}
|
||||||
checked={migrateCategories}
|
checked={migrateCategories}
|
||||||
onChange={(_, checked) => setMigrationFlag('migrateCategories', checked)}
|
onChange={(_, checked) => setMigrationFlag('migrateCategories', checked)}
|
||||||
/>
|
/>
|
||||||
<CheckboxInput
|
<CheckboxInput
|
||||||
disabled={isMigrationInProcess}
|
disabled={isMigrating}
|
||||||
label={t`Tracking`}
|
label={t`Tracking`}
|
||||||
checked={migrateTracking}
|
checked={migrateTracking}
|
||||||
onChange={(_, checked) => setMigrationFlag('migrateTracking', checked)}
|
onChange={(_, checked) => setMigrationFlag('migrateTracking', checked)}
|
||||||
/>
|
/>
|
||||||
<CheckboxInput
|
<CheckboxInput
|
||||||
disabled={isMigrationInProcess}
|
disabled={isMigrating}
|
||||||
label={t`Client data`}
|
label={t`Client data`}
|
||||||
checked={migrateMetadata}
|
checked={migrateMetadata}
|
||||||
onChange={(_, checked) => setMigrationFlag('migrateMetadata', checked)}
|
onChange={(_, checked) => setMigrationFlag('migrateMetadata', checked)}
|
||||||
/>
|
/>
|
||||||
<CheckboxInput
|
<CheckboxInput
|
||||||
disabled={isMigrationInProcess}
|
disabled={isMigrating}
|
||||||
label={t`Delete downloaded`}
|
label={t`Delete downloaded`}
|
||||||
checked={deleteChapters}
|
checked={deleteChapters}
|
||||||
onChange={(_, checked) => setMigrationFlag('deleteChapters', checked)}
|
onChange={(_, checked) => setMigrationFlag('deleteChapters', checked)}
|
||||||
@@ -115,21 +102,28 @@ export const MigrateDialog = ({ mangaIdToMigrateTo, onClose }: { mangaIdToMigrat
|
|||||||
width: '100%',
|
width: '100%',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
{mangaIdToMigrateTo !== undefined && (
|
||||||
disabled={isMigrationInProcess}
|
<Button
|
||||||
component={Link}
|
disabled={isMigrating}
|
||||||
to={AppRoutes.manga.path(mangaIdToMigrateTo)}
|
component={Link}
|
||||||
>
|
to={AppRoutes.manga.path(mangaIdToMigrateTo)}
|
||||||
{t`Show entry`}
|
onClick={() => onDismiss('show entry')}
|
||||||
</Button>
|
>
|
||||||
|
{t`Show entry`}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
<Stack direction="row">
|
<Stack direction="row">
|
||||||
<Button disabled={isMigrationInProcess} onClick={onClose}>
|
<Button disabled={isMigrating} onClick={onDismiss}>
|
||||||
{t`Cancel`}
|
{t`Cancel`}
|
||||||
</Button>
|
</Button>
|
||||||
<Button disabled={isMigrationInProcess} onClick={() => migrate('copy')}>
|
<Button disabled={isMigrating} onClick={() => startMigration({ ...options, mode: 'copy' })}>
|
||||||
{t`Copy`}
|
{t`Copy`}
|
||||||
</Button>
|
</Button>
|
||||||
<Button disabled={isMigrationInProcess} onClick={() => migrate('migrate')}>
|
<Button
|
||||||
|
disabled={isMigrating}
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => startMigration({ ...options, mode: 'migrate' })}
|
||||||
|
>
|
||||||
{t`Migrate`}
|
{t`Migrate`}
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
52
src/features/migration/components/MigrationProgressBar.tsx
Normal file
52
src/features/migration/components/MigrationProgressBar.tsx
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* 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 Box from '@mui/material/Box';
|
||||||
|
import LinearProgress from '@mui/material/LinearProgress';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import { useNavBarContext } from '@/features/navigation-bar/NavbarContext.tsx';
|
||||||
|
import type { MigrationProgress } from '@/features/migration/Migration.types.ts';
|
||||||
|
|
||||||
|
export const MigrationProgressBar = ({
|
||||||
|
completed,
|
||||||
|
total,
|
||||||
|
label,
|
||||||
|
}: {
|
||||||
|
label: string;
|
||||||
|
} & MigrationProgress) => {
|
||||||
|
const { appBarHeight } = useNavBarContext();
|
||||||
|
|
||||||
|
const progress = total > 0 ? (completed / total) * 100 : 0;
|
||||||
|
|
||||||
|
if (completed === total) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: 'sticky',
|
||||||
|
top: appBarHeight,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 2,
|
||||||
|
px: 2,
|
||||||
|
py: 1,
|
||||||
|
backgroundColor: 'background.default',
|
||||||
|
zIndex: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ flexGrow: 1 }}>
|
||||||
|
<LinearProgress variant="determinate" value={progress} />
|
||||||
|
</Box>
|
||||||
|
<Typography variant="body2" color="text.secondary" sx={{ minWidth: 'fit-content' }}>
|
||||||
|
{label}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
242
src/features/migration/components/MigrationSourceList.tsx
Normal file
242
src/features/migration/components/MigrationSourceList.tsx
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
/*
|
||||||
|
* 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 { useCallback, useMemo, useState } from 'react';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import Card from '@mui/material/Card';
|
||||||
|
import Chip from '@mui/material/Chip';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import type { DragEndEvent } from '@dnd-kit/core';
|
||||||
|
import { closestCenter, DndContext } from '@dnd-kit/core';
|
||||||
|
import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
|
||||||
|
import { useLingui } from '@lingui/react/macro';
|
||||||
|
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||||
|
import { DndSortableItem } from '@/lib/dnd-kit/DndSortableItem.tsx';
|
||||||
|
import { DndKitUtil } from '@/lib/dnd-kit/DndKitUtil.ts';
|
||||||
|
import { ListCardAvatar } from '@/base/components/lists/cards/ListCardAvatar.tsx';
|
||||||
|
import { ListCardContent } from '@/base/components/lists/cards/ListCardContent.tsx';
|
||||||
|
import type { SourceItem } from '@/features/migration/Migration.types.ts';
|
||||||
|
import type { SourceIdInfo } from '@/features/source/Source.types.ts';
|
||||||
|
import type { SelectableCollectionReturnType } from '@/base/collection/hooks/useSelectableCollection.ts';
|
||||||
|
import { assertIsDefined } from '@/base/Asserts.ts';
|
||||||
|
import { VirtuosoUtil } from '@/lib/virtuoso/Virtuoso.util.tsx';
|
||||||
|
import { StyledGroupedVirtuoso } from '@/base/components/virtuoso/StyledGroupedVirtuoso.tsx';
|
||||||
|
import { StyledGroupHeader } from '@/base/components/virtuoso/StyledGroupHeader.tsx';
|
||||||
|
import { DndOverlayItem } from '@/lib/dnd-kit/DndOverlayItem';
|
||||||
|
import { noOp } from '@/lib/HelperFunctions';
|
||||||
|
import CardActionArea from '@mui/material/CardActionArea';
|
||||||
|
import { StyledGroupItemWrapper } from '@/base/components/virtuoso/StyledGroupItemWrapper.tsx';
|
||||||
|
import DragHandle from '@mui/icons-material/DragHandle';
|
||||||
|
import { languageCodeToName } from '@/base/utils/Languages.ts';
|
||||||
|
import { DEFAULT_FULL_FAB_HEIGHT } from '@/base/components/buttons/StyledFab.tsx';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
|
|
||||||
|
const SourceCard = ({
|
||||||
|
source,
|
||||||
|
onToggle,
|
||||||
|
isCurrentSource,
|
||||||
|
isSelected,
|
||||||
|
isDragging,
|
||||||
|
}: {
|
||||||
|
source: SourceItem;
|
||||||
|
onToggle: (id: SourceIdInfo['id']) => void;
|
||||||
|
isCurrentSource: boolean;
|
||||||
|
isSelected: boolean;
|
||||||
|
isDragging?: boolean;
|
||||||
|
}) => {
|
||||||
|
const { t } = useLingui();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledGroupItemWrapper>
|
||||||
|
<Card>
|
||||||
|
<CardActionArea onClick={() => onToggle(source.id)}>
|
||||||
|
<ListCardContent sx={{ justifyContent: 'space-between' }}>
|
||||||
|
<Stack sx={{ flexFlow: 'row', gap: 1, alignItems: 'center' }}>
|
||||||
|
<ListCardAvatar
|
||||||
|
iconUrl={requestManager.getValidImgUrlFor(source.iconUrl)}
|
||||||
|
alt={source.name}
|
||||||
|
slots={{ spinnerImageProps: { ignoreQueue: true } }}
|
||||||
|
/>
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
|
||||||
|
<Typography variant="h6" component="h3">
|
||||||
|
{source.name}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="caption">{languageCodeToName(source.lang)}</Typography>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
<Stack sx={{ flexDirection: 'row', gap: 4 }}>
|
||||||
|
{isCurrentSource && (
|
||||||
|
<Chip size="small" label={t`Current source`} color="primary" variant="outlined" />
|
||||||
|
)}
|
||||||
|
{isSelected && (
|
||||||
|
<Box>
|
||||||
|
<DragHandle sx={{ mr: 2, cursor: isDragging ? 'grabbing' : 'grab' }} />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</ListCardContent>
|
||||||
|
</CardActionArea>
|
||||||
|
</Card>
|
||||||
|
</StyledGroupItemWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MigrationSourceList = ({
|
||||||
|
sources,
|
||||||
|
handleSelection,
|
||||||
|
selectedSourceIds,
|
||||||
|
handlePriorityChange,
|
||||||
|
currentSourceId,
|
||||||
|
}: {
|
||||||
|
sources: SourceItem[];
|
||||||
|
handleSelection: SelectableCollectionReturnType<SourceIdInfo['id'], 'default'>['handleSelection'];
|
||||||
|
handlePriorityChange: (oldIndex: number, newIndex: number) => void;
|
||||||
|
selectedSourceIds: SourceIdInfo['id'][];
|
||||||
|
currentSourceId: SourceIdInfo['id'] | null;
|
||||||
|
}) => {
|
||||||
|
const { t } = useLingui();
|
||||||
|
const dndSensors = DndKitUtil.useSensorsForDevice();
|
||||||
|
const [dndActiveSource, setDndActiveSource] = useState<SourceItem | null>(null);
|
||||||
|
|
||||||
|
const selectedSources = useMemo(
|
||||||
|
() =>
|
||||||
|
selectedSourceIds.map((sourceId) => {
|
||||||
|
const selectedSource = sources.find((source) => source.id === sourceId);
|
||||||
|
|
||||||
|
assertIsDefined(selectedSource);
|
||||||
|
return selectedSource;
|
||||||
|
}),
|
||||||
|
[sources, selectedSourceIds],
|
||||||
|
);
|
||||||
|
const unselectedSources = useMemo(
|
||||||
|
() => sources.filter((source) => !selectedSourceIds.includes(source.id)),
|
||||||
|
[sources, selectedSourceIds],
|
||||||
|
);
|
||||||
|
const allSources = useMemo(() => [...selectedSources, ...unselectedSources], [selectedSources, unselectedSources]);
|
||||||
|
const groupedSourcesBySelectionState = useMemo<[boolean, SourceItem[]][]>(
|
||||||
|
() =>
|
||||||
|
[selectedSources.length ? [true, selectedSources] : undefined, [false, unselectedSources]].filter(
|
||||||
|
(entry) => entry !== undefined,
|
||||||
|
) as [boolean, SourceItem[]][],
|
||||||
|
[selectedSources, unselectedSources],
|
||||||
|
);
|
||||||
|
|
||||||
|
const groupCounts = useMemo(
|
||||||
|
() => groupedSourcesBySelectionState.map(([, sourcesOfGroup]) => sourcesOfGroup.length),
|
||||||
|
[groupedSourcesBySelectionState],
|
||||||
|
);
|
||||||
|
const computeItemKey = VirtuosoUtil.useCreateGroupedComputeItemKey(
|
||||||
|
groupCounts,
|
||||||
|
useCallback(
|
||||||
|
(index) => String(groupedSourcesBySelectionState[index][VirtuosoUtil.GROUP]),
|
||||||
|
[groupedSourcesBySelectionState],
|
||||||
|
),
|
||||||
|
useCallback((index) => allSources[index].id, [groupedSourcesBySelectionState]),
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleToggle = useCallback(
|
||||||
|
(id: SourceIdInfo['id']) => {
|
||||||
|
handleSelection(id, !selectedSourceIds.includes(id));
|
||||||
|
},
|
||||||
|
[handleSelection, selectedSourceIds],
|
||||||
|
);
|
||||||
|
|
||||||
|
const onDragEnd = (event: DragEndEvent) => {
|
||||||
|
const { active, over } = event;
|
||||||
|
setDndActiveSource(null);
|
||||||
|
|
||||||
|
if (!over || active.id === over.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldIndex = selectedSourceIds.indexOf(String(active.id));
|
||||||
|
const newIndex = selectedSourceIds.indexOf(String(over.id));
|
||||||
|
handlePriorityChange(oldIndex, newIndex);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DndContext
|
||||||
|
sensors={dndSensors}
|
||||||
|
collisionDetection={closestCenter}
|
||||||
|
onDragStart={(event) => setDndActiveSource(sources.find((source) => source.id === event.active.id) ?? null)}
|
||||||
|
onDragEnd={onDragEnd}
|
||||||
|
onDragCancel={() => setDndActiveSource(null)}
|
||||||
|
onDragAbort={() => setDndActiveSource(null)}
|
||||||
|
>
|
||||||
|
<SortableContext items={selectedSources} strategy={verticalListSortingStrategy}>
|
||||||
|
<StyledGroupedVirtuoso
|
||||||
|
style={{ marginBottom: DEFAULT_FULL_FAB_HEIGHT }}
|
||||||
|
persistKey="migration-source-selection"
|
||||||
|
groupCounts={groupCounts}
|
||||||
|
computeItemKey={computeItemKey}
|
||||||
|
groupContent={(index) => {
|
||||||
|
const [group] = groupedSourcesBySelectionState[index];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledGroupHeader
|
||||||
|
isFirstItem={!index}
|
||||||
|
sx={{
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
pr: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="h5" component="h2">
|
||||||
|
{group ? t`Selected` : t`Available`}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{group && !!selectedSources.length && (
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
{t`Drag to prioritize`}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</StyledGroupHeader>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
itemContent={(index, groupIndex) => {
|
||||||
|
const [isSelected] = groupedSourcesBySelectionState[groupIndex];
|
||||||
|
const source = allSources[index];
|
||||||
|
|
||||||
|
const sourceCard = (
|
||||||
|
<SourceCard
|
||||||
|
source={source}
|
||||||
|
onToggle={handleToggle}
|
||||||
|
isCurrentSource={source.id === currentSourceId}
|
||||||
|
isSelected={isSelected}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isSelected) {
|
||||||
|
return (
|
||||||
|
<DndSortableItem
|
||||||
|
key={source.id}
|
||||||
|
id={source.id}
|
||||||
|
isDragging={source.id === dndActiveSource?.id}
|
||||||
|
>
|
||||||
|
{sourceCard}
|
||||||
|
</DndSortableItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sourceCard;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</SortableContext>
|
||||||
|
<DndOverlayItem isActive={!!dndActiveSource}>
|
||||||
|
<SourceCard
|
||||||
|
source={dndActiveSource!}
|
||||||
|
onToggle={noOp}
|
||||||
|
isCurrentSource={dndActiveSource?.id === currentSourceId}
|
||||||
|
isSelected
|
||||||
|
isDragging
|
||||||
|
/>
|
||||||
|
</DndOverlayItem>
|
||||||
|
</DndContext>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,292 @@
|
|||||||
|
/*
|
||||||
|
* 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 type { MangaIdInfo } from '@/features/manga/Manga.types.ts';
|
||||||
|
import type { MigrationMatch, TMigrationEntry } from '@/features/migration/Migration.types.ts';
|
||||||
|
import { MigrationEntryStatus, MigrationPhase } from '@/features/migration/Migration.types.ts';
|
||||||
|
import { MediaQuery } from '@/base/utils/MediaQuery.tsx';
|
||||||
|
import { MigrationEntryCard } from '@/features/migration/components/migration-entry/MigrationEntryCard.tsx';
|
||||||
|
import { applyStyles } from '@/base/utils/ApplyStyles.ts';
|
||||||
|
import { MigrationEntryCardContent } from '@/features/migration/components/migration-entry/MigrationEntryCardContent.tsx';
|
||||||
|
import { ENTRY_STATUS_TRANSLATION } from '@/features/migration/Migration.constants.ts';
|
||||||
|
import { ReactRouter } from '@/lib/react-router/ReactRouter.ts';
|
||||||
|
import { AppRoutes } from '@/base/AppRoute.constants.ts';
|
||||||
|
import { MigrationManager } from '@/features/migration/MigrationManager.ts';
|
||||||
|
import { extractGraphqlExceptionInfo } from '@/lib/HelperFunctions.ts';
|
||||||
|
import { Confirmation } from '@/base/AppAwaitableComponent.ts';
|
||||||
|
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
||||||
|
import { ListCardAvatar } from '@/base/components/lists/cards/ListCardAvatar.tsx';
|
||||||
|
import { Mangas } from '@/features/manga/services/Mangas.ts';
|
||||||
|
import { TypographyMaxLines } from '@/base/components/texts/TypographyMaxLines.tsx';
|
||||||
|
import { MigrationEntryMetadataText } from '@/features/migration/components/migration-entry/MigrationEntryMetadataText.tsx';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import { useLingui } from '@lingui/react/macro';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
|
import SearchIcon from '@mui/icons-material/Search';
|
||||||
|
import CardActions from '@mui/material/CardActions';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
import { plural } from '@lingui/core/macro';
|
||||||
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||||
|
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
|
||||||
|
import { alpha } from '@mui/material/styles';
|
||||||
|
import ReplayIcon from '@mui/icons-material/Replay';
|
||||||
|
import Link from '@mui/material/Link';
|
||||||
|
import { Link as RouterLink } from 'react-router-dom';
|
||||||
|
|
||||||
|
const EntryStatus = ({
|
||||||
|
sourceMangaId,
|
||||||
|
sourceMangaTitle,
|
||||||
|
status,
|
||||||
|
isMigrating,
|
||||||
|
}: {
|
||||||
|
sourceMangaId: MangaIdInfo['id'];
|
||||||
|
sourceMangaTitle: string;
|
||||||
|
status: MigrationEntryStatus;
|
||||||
|
isMigrating: boolean;
|
||||||
|
}) => {
|
||||||
|
const { t } = useLingui();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack sx={{ alignItems: 'center', justifyContent: 'center', gap: 2 }}>
|
||||||
|
<Typography color={status === MigrationEntryStatus.NO_MATCH ? 'warning' : undefined}>
|
||||||
|
{t(ENTRY_STATUS_TRANSLATION[status])}
|
||||||
|
</Typography>
|
||||||
|
{!isMigrating && status === MigrationEntryStatus.NO_MATCH && (
|
||||||
|
<Button
|
||||||
|
startIcon={<SearchIcon />}
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => {
|
||||||
|
ReactRouter.navigate(
|
||||||
|
AppRoutes.migrate.childRoutes.manualSearch.path(sourceMangaId, sourceMangaTitle),
|
||||||
|
{
|
||||||
|
state: { mangaTitle: sourceMangaTitle },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>{t`Manual search`}</Button>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const EntryError = ({
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
isMigrating,
|
||||||
|
sourceMangaId,
|
||||||
|
error,
|
||||||
|
}: {
|
||||||
|
sourceMangaId: MangaIdInfo['id'];
|
||||||
|
isMigrating: boolean;
|
||||||
|
} & Pick<TMigrationEntry, 'error'> &
|
||||||
|
Pick<MigrationMatch, 'id' | 'title'>) => {
|
||||||
|
const { t } = useLingui();
|
||||||
|
|
||||||
|
const { phase } = MigrationManager.getState();
|
||||||
|
|
||||||
|
const MAX_ERROR_LENGTH = 100;
|
||||||
|
|
||||||
|
const { isGraphqlException, graphqlError, graphqlStackTrace } = extractGraphqlExceptionInfo(error);
|
||||||
|
const tmpError = isGraphqlException ? graphqlError : error;
|
||||||
|
const isErrorTooLong = (tmpError?.length ?? 0) > MAX_ERROR_LENGTH;
|
||||||
|
const finalError = isErrorTooLong ? tmpError?.slice(0, MAX_ERROR_LENGTH).concat('…') : tmpError;
|
||||||
|
|
||||||
|
const isSearchRetryable = status === MigrationEntryStatus.SEARCH_FAILED && phase === MigrationPhase.SEARCHING;
|
||||||
|
const isMigrationRetryable = status === MigrationEntryStatus.MIGRATION_FAILED && phase === MigrationPhase.MIGRATING;
|
||||||
|
|
||||||
|
const isRetryable = isSearchRetryable || isMigrationRetryable;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack sx={{ width: '100%', alignItems: 'center', justifyContent: 'center', gap: 2 }}>
|
||||||
|
<Stack sx={{ flexDirection: 'row', alignItems: 'center', gap: 1 }}>
|
||||||
|
<Typography color="error" title={finalError}>
|
||||||
|
{finalError}
|
||||||
|
</Typography>
|
||||||
|
{(isErrorTooLong || (isGraphqlException && graphqlStackTrace)) && (
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
Confirmation.show({
|
||||||
|
title: isMigrating ? t`Migration failed` : t`Search failed`,
|
||||||
|
message: (
|
||||||
|
<Stack sx={{ gap: 2 }}>
|
||||||
|
{isMigrating ? (
|
||||||
|
<Typography>{t`Migration for "${title}" failed with error:`}</Typography>
|
||||||
|
) : (
|
||||||
|
<Typography>{t`Search for "${title}" failed with error:`}</Typography>
|
||||||
|
)}
|
||||||
|
<Typography>{error}</Typography>
|
||||||
|
</Stack>
|
||||||
|
),
|
||||||
|
actions: {
|
||||||
|
cancel: { show: false },
|
||||||
|
confirm: {
|
||||||
|
title: t`Close`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).catch(defaultPromiseErrorHandler(`MigrationEntryRow: ${id} - ${error}`));
|
||||||
|
}}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{t`Show more`}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
{isRetryable && (
|
||||||
|
<Button
|
||||||
|
sx={{ width: 'fit-content' }}
|
||||||
|
variant="contained"
|
||||||
|
color="error"
|
||||||
|
startIcon={<ReplayIcon />}
|
||||||
|
onClick={() =>
|
||||||
|
MigrationManager.retryEntry(sourceMangaId).catch(
|
||||||
|
defaultPromiseErrorHandler('MigrationEntryRow::retry'),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t`Retry`}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const EntryData = (entry: MigrationMatch) => {
|
||||||
|
const { id, title } = entry;
|
||||||
|
|
||||||
|
const { t } = useLingui();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Link component={RouterLink} to={AppRoutes.manga.path(id)}>
|
||||||
|
<ListCardAvatar
|
||||||
|
iconUrl={Mangas.getThumbnailUrl(entry)}
|
||||||
|
alt={title}
|
||||||
|
slots={{
|
||||||
|
avatarProps: {
|
||||||
|
sx: {
|
||||||
|
width: 'unset',
|
||||||
|
height: 112,
|
||||||
|
aspectRatio: '3 / 4',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
<Stack sx={{ minWidth: 0, flex: 1 }}>
|
||||||
|
<Typography
|
||||||
|
variant="overline"
|
||||||
|
color="textSecondary"
|
||||||
|
>{t`Destination - ${entry.sourceTitle}`}</Typography>
|
||||||
|
<Link
|
||||||
|
component={RouterLink}
|
||||||
|
to={AppRoutes.manga.path(id)}
|
||||||
|
sx={{ textDecoration: 'none', color: 'inherit' }}
|
||||||
|
>
|
||||||
|
<TypographyMaxLines variant="h6" component="h3" title={title}>
|
||||||
|
{title}
|
||||||
|
</TypographyMaxLines>
|
||||||
|
</Link>
|
||||||
|
<MigrationEntryMetadataText {...entry} />
|
||||||
|
</Stack>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MigrationDestinationEntry = ({
|
||||||
|
sourceMangaId,
|
||||||
|
sourceMangaTitle,
|
||||||
|
entry,
|
||||||
|
error,
|
||||||
|
status,
|
||||||
|
otherResultsCount,
|
||||||
|
isExpanded,
|
||||||
|
setIsExpanded,
|
||||||
|
isMigrating,
|
||||||
|
}: {
|
||||||
|
sourceMangaId: MangaIdInfo['id'];
|
||||||
|
sourceMangaTitle: string;
|
||||||
|
entry: MigrationMatch | undefined;
|
||||||
|
error?: string;
|
||||||
|
status: MigrationEntryStatus;
|
||||||
|
otherResultsCount: number;
|
||||||
|
isExpanded: boolean;
|
||||||
|
setIsExpanded: (expanded: boolean) => void;
|
||||||
|
isMigrating: boolean;
|
||||||
|
}) => {
|
||||||
|
const isTabletWidth = MediaQuery.useIsTabletWidth();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MigrationEntryCard
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
...applyStyles(!isTabletWidth, {
|
||||||
|
width: '400px',
|
||||||
|
height: '100%',
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MigrationEntryCardContent>
|
||||||
|
{(() => {
|
||||||
|
if (!entry) {
|
||||||
|
return (
|
||||||
|
<EntryStatus
|
||||||
|
sourceMangaId={sourceMangaId}
|
||||||
|
sourceMangaTitle={sourceMangaTitle}
|
||||||
|
status={status}
|
||||||
|
isMigrating={isMigrating}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<EntryError
|
||||||
|
id={entry.id}
|
||||||
|
title={entry.title}
|
||||||
|
error={error}
|
||||||
|
sourceMangaId={sourceMangaId}
|
||||||
|
isMigrating={isMigrating}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <EntryData {...entry} />;
|
||||||
|
})()}
|
||||||
|
</MigrationEntryCardContent>
|
||||||
|
{!isTabletWidth && !isMigrating && !!otherResultsCount && (
|
||||||
|
<CardActions
|
||||||
|
sx={{
|
||||||
|
p: 0,
|
||||||
|
backgroundColor: 'primary.dark',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: (theme) => alpha(theme.palette.primary.dark, 0.8),
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
sx={{
|
||||||
|
width: '100%',
|
||||||
|
borderRadius: 0,
|
||||||
|
color: 'primary.contrastText',
|
||||||
|
}}
|
||||||
|
variant="text"
|
||||||
|
startIcon={isExpanded ? <ExpandLessIcon /> : <ExpandMoreIcon />}
|
||||||
|
onClick={() => setIsExpanded(!isExpanded)}
|
||||||
|
>
|
||||||
|
{plural(otherResultsCount, {
|
||||||
|
one: '# more match',
|
||||||
|
other: '# more matches',
|
||||||
|
})}
|
||||||
|
</Button>
|
||||||
|
</CardActions>
|
||||||
|
)}
|
||||||
|
</MigrationEntryCard>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
/*
|
||||||
|
* 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 type { MigrationMatch, TMigrationEntry } from '@/features/migration/Migration.types.ts';
|
||||||
|
import { MigrationManager } from '@/features/migration/MigrationManager.ts';
|
||||||
|
import Paper from '@mui/material/Paper';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { MediaQuery } from '@/base/utils/MediaQuery.tsx';
|
||||||
|
import { applyStyles } from '@/base/utils/ApplyStyles.ts';
|
||||||
|
import { MigrationSourceEntry } from '@/features/migration/components/migration-entry/MigrationSourceEntry.tsx';
|
||||||
|
import { MigrationDestinationEntry } from '@/features/migration/components/migration-entry/MigrationDestinationEntry.tsx';
|
||||||
|
import { ReactRouter } from '@/lib/react-router/ReactRouter.ts';
|
||||||
|
import { AppRoutes } from '@/base/AppRoute.constants.ts';
|
||||||
|
import { MigrationMatchedEntry } from '@/features/migration/components/migration-entry/MigrationMatchedEntry.tsx';
|
||||||
|
import { MigrationEntrySearchExcludeActions } from '@/features/migration/components/migration-entry/MigrationEntrySearchExcludeActions.tsx';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import { useLingui } from '@lingui/react/macro';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
|
import SearchIcon from '@mui/icons-material/Search';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
import Collapse from '@mui/material/Collapse';
|
||||||
|
import Divider from '@mui/material/Divider';
|
||||||
|
import { MigrationEntryStatusIndicator } from '@/features/migration/components/migration-entry/MigrationEntryStatusIndicator.tsx';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
|
||||||
|
const MigrationEntryMobile = ({
|
||||||
|
entry,
|
||||||
|
entry: { mangaId, mangaTitle, status, error, isExcluded },
|
||||||
|
destinationEntry,
|
||||||
|
otherSearchMatches,
|
||||||
|
isExpanded,
|
||||||
|
setIsExpanded,
|
||||||
|
isMigrating,
|
||||||
|
}: {
|
||||||
|
entry: TMigrationEntry;
|
||||||
|
destinationEntry: MigrationMatch | undefined;
|
||||||
|
otherSearchMatches: MigrationMatch[];
|
||||||
|
isExpanded: boolean;
|
||||||
|
setIsExpanded: (expanded: boolean) => void;
|
||||||
|
isMigrating: boolean;
|
||||||
|
}) => {
|
||||||
|
const { t } = useLingui();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MigrationSourceEntry {...entry} />
|
||||||
|
<MigrationDestinationEntry
|
||||||
|
sourceMangaId={mangaId}
|
||||||
|
sourceMangaTitle={mangaTitle}
|
||||||
|
entry={destinationEntry}
|
||||||
|
status={status}
|
||||||
|
otherResultsCount={otherSearchMatches.length}
|
||||||
|
isExpanded={isExpanded}
|
||||||
|
setIsExpanded={setIsExpanded}
|
||||||
|
isMigrating={isMigrating}
|
||||||
|
error={error}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Collapse in={isExpanded} unmountOnExit>
|
||||||
|
<Divider sx={{ mb: 1 }} />
|
||||||
|
<Stack sx={{ flexDirection: 'row', gap: 1, alignItems: 'center', justifyContent: 'space-between' }}>
|
||||||
|
<Typography variant="h6" component="h2">{t`Matches`}</Typography>
|
||||||
|
<Button
|
||||||
|
startIcon={<SearchIcon />}
|
||||||
|
variant="text"
|
||||||
|
onClick={() => {
|
||||||
|
ReactRouter.navigate(AppRoutes.migrate.childRoutes.manualSearch.path(mangaId, mangaTitle), {
|
||||||
|
state: { mangaTitle: mangaTitle },
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>{t`Manual search`}</Button>
|
||||||
|
</Stack>
|
||||||
|
<Stack sx={{ pt: 2 }}>
|
||||||
|
{otherSearchMatches.map((searchMatch) => {
|
||||||
|
if (destinationEntry?.id === searchMatch.id) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MigrationMatchedEntry key={searchMatch.id} sourceMangaId={mangaId} entry={searchMatch} />
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Stack>
|
||||||
|
</Collapse>
|
||||||
|
|
||||||
|
{!isMigrating && (
|
||||||
|
<MigrationEntrySearchExcludeActions
|
||||||
|
hasResults={!!destinationEntry}
|
||||||
|
otherResultsCount={otherSearchMatches.length}
|
||||||
|
isExpanded={isExpanded}
|
||||||
|
setIsExpanded={setIsExpanded}
|
||||||
|
isExcluded={isExcluded}
|
||||||
|
mangaId={mangaId}
|
||||||
|
mangaTitle={mangaTitle}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MigrationEntryDesktop = ({
|
||||||
|
entry,
|
||||||
|
entry: { mangaId, mangaTitle, status, error },
|
||||||
|
destinationEntry,
|
||||||
|
otherSearchMatches,
|
||||||
|
isExpanded,
|
||||||
|
setIsExpanded,
|
||||||
|
isMigrating,
|
||||||
|
}: {
|
||||||
|
entry: TMigrationEntry;
|
||||||
|
destinationEntry: MigrationMatch | undefined;
|
||||||
|
otherSearchMatches: MigrationMatch[];
|
||||||
|
isExpanded: boolean;
|
||||||
|
setIsExpanded: (expanded: boolean) => void;
|
||||||
|
isMigrating: boolean;
|
||||||
|
}) => {
|
||||||
|
const { t } = useLingui();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MigrationSourceEntry {...entry} />
|
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'stretch', gap: 2 }}>
|
||||||
|
<Box sx={{ display: 'flex', gap: 4, alignItems: 'center' }}>
|
||||||
|
<MigrationEntryStatusIndicator status={status} hasResults={!!destinationEntry} />
|
||||||
|
<MigrationDestinationEntry
|
||||||
|
sourceMangaId={mangaId}
|
||||||
|
sourceMangaTitle={mangaTitle}
|
||||||
|
entry={destinationEntry}
|
||||||
|
status={status}
|
||||||
|
otherResultsCount={otherSearchMatches.length}
|
||||||
|
isExpanded={isExpanded}
|
||||||
|
setIsExpanded={setIsExpanded}
|
||||||
|
isMigrating={isMigrating}
|
||||||
|
error={error}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{!isMigrating && (
|
||||||
|
<MigrationEntrySearchExcludeActions
|
||||||
|
hasResults={!!destinationEntry}
|
||||||
|
otherResultsCount={otherSearchMatches.length}
|
||||||
|
isExpanded={isExpanded}
|
||||||
|
setIsExpanded={setIsExpanded}
|
||||||
|
isExcluded={entry.isExcluded}
|
||||||
|
mangaId={entry.mangaId}
|
||||||
|
mangaTitle={entry.mangaTitle}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Collapse in={isExpanded && !isMigrating} unmountOnExit>
|
||||||
|
<Divider sx={{ my: 4 }} />
|
||||||
|
<Typography variant="h6" component="h2">{t`Matches`}</Typography>
|
||||||
|
<Stack sx={{ pt: 2 }}>
|
||||||
|
{otherSearchMatches.map((searchMatch) => {
|
||||||
|
if (destinationEntry?.id === searchMatch.id) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MigrationMatchedEntry
|
||||||
|
key={searchMatch.id}
|
||||||
|
sourceMangaId={entry.mangaId}
|
||||||
|
entry={searchMatch}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Stack>
|
||||||
|
</Collapse>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MigrationEntry = ({ entry: propEntry, isMigrating }: { entry: TMigrationEntry; isMigrating: boolean }) => {
|
||||||
|
const isTabletWidth = MediaQuery.useIsTabletWidth();
|
||||||
|
|
||||||
|
const entry = useMemo(() => MigrationManager.getUpToDateMigrationEntry(propEntry), [propEntry]);
|
||||||
|
|
||||||
|
const destinationEntry = useMemo(() => {
|
||||||
|
const match = entry.searchMatches.find((matchEntry) => matchEntry.id === entry.selectedMatchMangaId);
|
||||||
|
const manualMatch = entry.manualMatches.find((matchEntry) => matchEntry.id === entry.selectedMatchMangaId);
|
||||||
|
|
||||||
|
return match ?? manualMatch;
|
||||||
|
}, [entry.searchMatches, entry.selectedMatchMangaId]);
|
||||||
|
const otherMatches = useMemo(
|
||||||
|
() =>
|
||||||
|
entry.searchMatches
|
||||||
|
.filter((searchMatch) => searchMatch.id !== entry.selectedMatchMangaId)
|
||||||
|
.sort((a, b) => (b.latestChapterNumber ?? 0) - (a.latestChapterNumber ?? 0)),
|
||||||
|
[entry.searchMatches, entry.selectedMatchMangaId],
|
||||||
|
);
|
||||||
|
|
||||||
|
const MigrationComponent = useMemo(
|
||||||
|
() => (isTabletWidth ? MigrationEntryMobile : MigrationEntryDesktop),
|
||||||
|
[isTabletWidth],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Paper
|
||||||
|
sx={{
|
||||||
|
opacity: !isMigrating && entry.isExcluded ? 0.5 : 1,
|
||||||
|
...applyStyles(isTabletWidth, {
|
||||||
|
flexDirection: 'column',
|
||||||
|
display: 'flex',
|
||||||
|
p: 2,
|
||||||
|
gap: 2,
|
||||||
|
}),
|
||||||
|
...applyStyles(!isTabletWidth, {
|
||||||
|
px: 2,
|
||||||
|
py: 2,
|
||||||
|
pr: 6,
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MigrationComponent
|
||||||
|
entry={entry}
|
||||||
|
destinationEntry={destinationEntry}
|
||||||
|
isExpanded={entry.areMatchesExpanded}
|
||||||
|
setIsExpanded={() =>
|
||||||
|
MigrationManager.setEntryMatchesExpandState(entry.mangaId, !entry.areMatchesExpanded)
|
||||||
|
}
|
||||||
|
otherSearchMatches={otherMatches}
|
||||||
|
isMigrating={isMigrating}
|
||||||
|
/>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* 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 Card from '@mui/material/Card';
|
||||||
|
import { styled } from '@mui/material/styles';
|
||||||
|
|
||||||
|
export const MigrationEntryCard = styled(Card)(({ theme }) => ({
|
||||||
|
backgroundColor: theme.palette.background.default,
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: theme.palette.divider,
|
||||||
|
}));
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* 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 CardContent from '@mui/material/CardContent';
|
||||||
|
import { styled } from '@mui/material/styles';
|
||||||
|
|
||||||
|
export const MigrationEntryCardContent = styled(CardContent)(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: theme.spacing(2),
|
||||||
|
padding: theme.spacing(2),
|
||||||
|
'&:last-child': {
|
||||||
|
paddingBottom: theme.spacing(2),
|
||||||
|
},
|
||||||
|
}));
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* 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 type { MigrationMatch } from '@/features/migration/Migration.types.ts';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import { useLingui } from '@lingui/react/macro';
|
||||||
|
|
||||||
|
export const MigrationEntryMetadataText = (
|
||||||
|
entry: Pick<MigrationMatch, 'artist' | 'author' | 'latestChapterNumber'>,
|
||||||
|
) => {
|
||||||
|
const { t } = useLingui();
|
||||||
|
|
||||||
|
const latestChapterNumber = (entry.latestChapterNumber ?? 0) > 1 ? entry.latestChapterNumber : t`Unknown`;
|
||||||
|
const latestChapter = t`Latest: ${latestChapterNumber}`;
|
||||||
|
|
||||||
|
const artist = entry.artist ? `${entry.artist} - ` : '';
|
||||||
|
const author = entry.author ? `${entry.author} - ` : '';
|
||||||
|
const isSameArtistAuthor = artist === author;
|
||||||
|
const artistAuthor = isSameArtistAuthor ? artist : `${artist}${author}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Typography variant="body2" color="textSecondary">
|
||||||
|
{artistAuthor}
|
||||||
|
{latestChapter}
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
/*
|
||||||
|
* 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 type { MangaIdInfo } from '@/features/manga/Manga.types.ts';
|
||||||
|
import { MediaQuery } from '@/base/utils/MediaQuery.tsx';
|
||||||
|
import { CustomTooltip } from '@/base/components/CustomTooltip.tsx';
|
||||||
|
import { CustomButtonIcon } from '@/base/components/buttons/CustomButtonIcon.tsx';
|
||||||
|
import { ReactRouter } from '@/lib/react-router/ReactRouter.ts';
|
||||||
|
import { AppRoutes } from '@/base/AppRoute.constants.ts';
|
||||||
|
import { MigrationManager } from '@/features/migration/MigrationManager.ts';
|
||||||
|
import IconButton from '@mui/material/IconButton';
|
||||||
|
import { useLingui } from '@lingui/react/macro';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
|
import SearchIcon from '@mui/icons-material/Search';
|
||||||
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
import { plural } from '@lingui/core/macro';
|
||||||
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||||
|
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
|
||||||
|
import ButtonGroup from '@mui/material/ButtonGroup';
|
||||||
|
|
||||||
|
export const MigrationEntrySearchExcludeActions = ({
|
||||||
|
hasResults,
|
||||||
|
otherResultsCount,
|
||||||
|
isExpanded,
|
||||||
|
setIsExpanded,
|
||||||
|
isExcluded,
|
||||||
|
mangaId,
|
||||||
|
mangaTitle,
|
||||||
|
}: {
|
||||||
|
hasResults: boolean;
|
||||||
|
otherResultsCount: number;
|
||||||
|
isExpanded: boolean;
|
||||||
|
setIsExpanded: (expanded: boolean) => void;
|
||||||
|
isExcluded: boolean;
|
||||||
|
mangaId: MangaIdInfo['id'];
|
||||||
|
mangaTitle: string;
|
||||||
|
}) => {
|
||||||
|
const { t } = useLingui();
|
||||||
|
const isTabletWidth = MediaQuery.useIsTabletWidth();
|
||||||
|
|
||||||
|
if (isTabletWidth) {
|
||||||
|
if (!hasResults) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ButtonGroup variant="contained">
|
||||||
|
{!!otherResultsCount && (
|
||||||
|
<Button
|
||||||
|
sx={{ flexGrow: 1 }}
|
||||||
|
startIcon={isExpanded ? <ExpandLessIcon /> : <ExpandMoreIcon />}
|
||||||
|
onClick={() => setIsExpanded(!isExpanded)}
|
||||||
|
>
|
||||||
|
{plural(otherResultsCount, {
|
||||||
|
one: '# more match',
|
||||||
|
other: '# more matches',
|
||||||
|
})}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{!isExpanded && (
|
||||||
|
<CustomTooltip title={t`Manual search`}>
|
||||||
|
<CustomButtonIcon
|
||||||
|
sx={{
|
||||||
|
flexGrow: Number(!otherResultsCount),
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
ReactRouter.navigate(
|
||||||
|
AppRoutes.migrate.childRoutes.manualSearch.path(mangaId, mangaTitle),
|
||||||
|
{
|
||||||
|
state: { mangaTitle: mangaTitle },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SearchIcon />
|
||||||
|
</CustomButtonIcon>
|
||||||
|
</CustomTooltip>
|
||||||
|
)}
|
||||||
|
<CustomTooltip title={isExcluded ? t`Include` : t`Exclude`}>
|
||||||
|
<CustomButtonIcon
|
||||||
|
sx={{
|
||||||
|
flexGrow: Number(!otherResultsCount),
|
||||||
|
}}
|
||||||
|
onClick={() =>
|
||||||
|
isExcluded ? MigrationManager.includeManga(mangaId) : MigrationManager.excludeManga(mangaId)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{isExcluded ? <AddIcon /> : <CloseIcon />}
|
||||||
|
</CustomButtonIcon>
|
||||||
|
</CustomTooltip>
|
||||||
|
</ButtonGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack sx={{ gap: 1, justifyContent: 'center' }}>
|
||||||
|
<CustomTooltip title={isExcluded ? t`Include` : t`Exclude`} placement="auto">
|
||||||
|
<IconButton
|
||||||
|
onClick={() =>
|
||||||
|
isExcluded ? MigrationManager.includeManga(mangaId) : MigrationManager.excludeManga(mangaId)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{isExcluded ? <AddIcon /> : <CloseIcon />}
|
||||||
|
</IconButton>
|
||||||
|
</CustomTooltip>
|
||||||
|
<CustomTooltip title={t`Manual search`} placement="auto">
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
ReactRouter.navigate(AppRoutes.migrate.childRoutes.manualSearch.path(mangaId, mangaTitle));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SearchIcon />
|
||||||
|
</IconButton>
|
||||||
|
</CustomTooltip>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* 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 { MediaQuery } from '@/base/utils/MediaQuery.tsx';
|
||||||
|
import { MigrationEntryStatus } from '@/features/migration/Migration.types.ts';
|
||||||
|
import { LoadingPlaceholder } from '@/base/components/feedback/LoadingPlaceholder.tsx';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
import type { Theme } from '@mui/material/styles';
|
||||||
|
import type { SystemCssProperties } from '@mui/system/styleFunctionSx';
|
||||||
|
import PriorityHighIcon from '@mui/icons-material/PriorityHigh';
|
||||||
|
import CheckIcon from '@mui/icons-material/Check';
|
||||||
|
|
||||||
|
const StatusIndicatorWrapper = ({
|
||||||
|
backgroundColor,
|
||||||
|
statusIcon,
|
||||||
|
}: {
|
||||||
|
backgroundColor: SystemCssProperties<Theme>['backgroundColor'];
|
||||||
|
statusIcon: ReactNode;
|
||||||
|
}) => {
|
||||||
|
const isTabletWidth = MediaQuery.useIsTabletWidth();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
backgroundColor,
|
||||||
|
p: isTabletWidth ? 0 : 0.5,
|
||||||
|
m: 0,
|
||||||
|
borderRadius: isTabletWidth ? 100 : 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{statusIcon}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export const MigrationEntryStatusIndicator = ({
|
||||||
|
status,
|
||||||
|
hasResults,
|
||||||
|
}: {
|
||||||
|
status: MigrationEntryStatus;
|
||||||
|
hasResults: boolean;
|
||||||
|
}) => {
|
||||||
|
const isTabletWidth = MediaQuery.useIsTabletWidth();
|
||||||
|
|
||||||
|
if ([MigrationEntryStatus.SEARCHING, MigrationEntryStatus.MIGRATING].includes(status)) {
|
||||||
|
return <LoadingPlaceholder size={isTabletWidth ? 22 : 32} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([MigrationEntryStatus.SEARCH_FAILED, MigrationEntryStatus.MIGRATION_FAILED].includes(status)) {
|
||||||
|
return (
|
||||||
|
<StatusIndicatorWrapper
|
||||||
|
backgroundColor={(theme) => theme.palette.error.main}
|
||||||
|
statusIcon={
|
||||||
|
<PriorityHighIcon
|
||||||
|
fontSize={isTabletWidth ? undefined : 'large'}
|
||||||
|
sx={{ color: 'error.contrastText' }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status === MigrationEntryStatus.NO_MATCH) {
|
||||||
|
return (
|
||||||
|
<StatusIndicatorWrapper
|
||||||
|
backgroundColor={(theme) => theme.palette.warning.main}
|
||||||
|
statusIcon={
|
||||||
|
<PriorityHighIcon
|
||||||
|
fontSize={isTabletWidth ? undefined : 'large'}
|
||||||
|
sx={{ color: 'warning.contrastText' }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status === MigrationEntryStatus.MIGRATION_COMPLETE) {
|
||||||
|
return (
|
||||||
|
<StatusIndicatorWrapper
|
||||||
|
backgroundColor="primary.dark"
|
||||||
|
statusIcon={
|
||||||
|
<CheckIcon fontSize={isTabletWidth ? undefined : 'large'} sx={{ color: 'primary.contrastText' }} />
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasResults) {
|
||||||
|
return (
|
||||||
|
<StatusIndicatorWrapper
|
||||||
|
backgroundColor="primary.dark"
|
||||||
|
statusIcon={
|
||||||
|
<ArrowForwardIcon
|
||||||
|
fontSize={isTabletWidth ? undefined : 'large'}
|
||||||
|
sx={{ color: 'primary.contrastText' }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* 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 type { MangaIdInfo } from '@/features/manga/Manga.types.ts';
|
||||||
|
import type { MigrationMatch } from '@/features/migration/Migration.types.ts';
|
||||||
|
import { MigrationEntryCard } from '@/features/migration/components/migration-entry/MigrationEntryCard.tsx';
|
||||||
|
import { MigrationManager } from '@/features/migration/MigrationManager.ts';
|
||||||
|
import { MigrationEntryCardContent } from '@/features/migration/components/migration-entry/MigrationEntryCardContent.tsx';
|
||||||
|
import { AppRoutes } from '@/base/AppRoute.constants.ts';
|
||||||
|
import { ListCardAvatar } from '@/base/components/lists/cards/ListCardAvatar.tsx';
|
||||||
|
import { Mangas } from '@/features/manga/services/Mangas.ts';
|
||||||
|
import { TypographyMaxLines } from '@/base/components/texts/TypographyMaxLines.tsx';
|
||||||
|
import { MigrationEntryMetadataText } from '@/features/migration/components/migration-entry/MigrationEntryMetadataText.tsx';
|
||||||
|
import { MUIUtil } from '@/lib/mui/MUI.util.ts';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import { useLingui } from '@lingui/react/macro';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
import CardActionArea from '@mui/material/CardActionArea';
|
||||||
|
import Link from '@mui/material/Link';
|
||||||
|
import { Link as RouterLink } from 'react-router-dom';
|
||||||
|
|
||||||
|
export const MigrationMatchedEntry = ({
|
||||||
|
sourceMangaId,
|
||||||
|
entry,
|
||||||
|
}: {
|
||||||
|
sourceMangaId: MangaIdInfo['id'];
|
||||||
|
entry: MigrationMatch;
|
||||||
|
}) => {
|
||||||
|
const { t } = useLingui();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MigrationEntryCard sx={{ mb: 1 }}>
|
||||||
|
<CardActionArea onClick={() => MigrationManager.selectMatch(sourceMangaId, entry.id, entry.sourceId)}>
|
||||||
|
<MigrationEntryCardContent>
|
||||||
|
{(() => (
|
||||||
|
<>
|
||||||
|
<Link
|
||||||
|
component={RouterLink}
|
||||||
|
to={AppRoutes.manga.path(entry.id)}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<ListCardAvatar
|
||||||
|
iconUrl={Mangas.getThumbnailUrl(entry)}
|
||||||
|
alt={entry.title}
|
||||||
|
slots={{
|
||||||
|
avatarProps: {
|
||||||
|
sx: {
|
||||||
|
width: 'unset',
|
||||||
|
height: 80,
|
||||||
|
aspectRatio: '3 / 4',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
<Stack sx={{ minWidth: 0, flex: 1 }}>
|
||||||
|
<Typography variant="overline" color="textSecondary">
|
||||||
|
{entry.sourceTitle}
|
||||||
|
</Typography>
|
||||||
|
<Link
|
||||||
|
component={RouterLink}
|
||||||
|
to={AppRoutes.manga.path(entry.id)}
|
||||||
|
sx={{ textDecoration: 'none', color: 'inherit', width: 'max-content' }}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<TypographyMaxLines variant="h6" component="h3" title={entry.title}>
|
||||||
|
{entry.title}
|
||||||
|
</TypographyMaxLines>
|
||||||
|
</Link>
|
||||||
|
<MigrationEntryMetadataText {...entry} />
|
||||||
|
</Stack>
|
||||||
|
</>
|
||||||
|
))()}
|
||||||
|
{(() => (
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
{...MUIUtil.preventRippleProp({
|
||||||
|
onClick: () => MigrationManager.selectMatch(sourceMangaId, entry.id, entry.sourceId),
|
||||||
|
})}
|
||||||
|
>{t`Select`}</Button>
|
||||||
|
))()}
|
||||||
|
</MigrationEntryCardContent>
|
||||||
|
</CardActionArea>
|
||||||
|
</MigrationEntryCard>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
/*
|
||||||
|
* 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 type { TMigrationEntry } from '@/features/migration/Migration.types.ts';
|
||||||
|
import { MediaQuery } from '@/base/utils/MediaQuery.tsx';
|
||||||
|
import { TypographyMaxLines } from '@/base/components/texts/TypographyMaxLines.tsx';
|
||||||
|
import { MigrationEntryMetadataText } from '@/features/migration/components/migration-entry/MigrationEntryMetadataText.tsx';
|
||||||
|
import { MigrationEntryStatusIndicator } from '@/features/migration/components/migration-entry/MigrationEntryStatusIndicator.tsx';
|
||||||
|
import { AppRoutes } from '@/base/AppRoute.constants.ts';
|
||||||
|
import { ListCardAvatar } from '@/base/components/lists/cards/ListCardAvatar.tsx';
|
||||||
|
import { Mangas } from '@/features/manga/services/Mangas.ts';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import { useLingui } from '@lingui/react/macro';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
|
import Link from '@mui/material/Link';
|
||||||
|
import { Link as RouterLink } from 'react-router-dom';
|
||||||
|
|
||||||
|
export const MigrationSourceEntry = (entry: TMigrationEntry) => {
|
||||||
|
const {
|
||||||
|
mangaId,
|
||||||
|
mangaThumbnailUrl,
|
||||||
|
mangaTitle,
|
||||||
|
sourceTitle,
|
||||||
|
mangaArtist,
|
||||||
|
mangaAuthor,
|
||||||
|
latestChapterNumber,
|
||||||
|
searchMatches,
|
||||||
|
status,
|
||||||
|
} = entry;
|
||||||
|
|
||||||
|
const { t } = useLingui();
|
||||||
|
const isTabletWidth = MediaQuery.useIsTabletWidth();
|
||||||
|
|
||||||
|
if (isTabletWidth) {
|
||||||
|
return (
|
||||||
|
<Stack sx={{ flexDirection: 'row', gap: 1, alignItems: 'flex-start', justifyContent: 'space-between' }}>
|
||||||
|
<Stack>
|
||||||
|
<Typography variant="overline" color="textSecondary">{t`Source entry - ${sourceTitle}`}</Typography>
|
||||||
|
<TypographyMaxLines variant="h6" component="h3" title={mangaTitle}>
|
||||||
|
{mangaTitle}
|
||||||
|
</TypographyMaxLines>
|
||||||
|
<MigrationEntryMetadataText
|
||||||
|
artist={mangaArtist}
|
||||||
|
author={mangaAuthor}
|
||||||
|
latestChapterNumber={latestChapterNumber}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<Box sx={{ display: 'flex', gap: 4, alignItems: 'center' }}>
|
||||||
|
<MigrationEntryStatusIndicator status={status} hasResults={!!searchMatches.length} />
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, width: '400px' }}>
|
||||||
|
<Link component={RouterLink} to={AppRoutes.manga.path(mangaId)}>
|
||||||
|
<ListCardAvatar
|
||||||
|
iconUrl={Mangas.getThumbnailUrl({ ...entry, thumbnailUrl: mangaThumbnailUrl })}
|
||||||
|
alt={mangaTitle}
|
||||||
|
slots={{
|
||||||
|
avatarProps: {
|
||||||
|
sx: {
|
||||||
|
width: 'unset',
|
||||||
|
height: 112,
|
||||||
|
aspectRatio: '3 / 4',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
<Stack sx={{ minWidth: 0, flex: 1 }}>
|
||||||
|
<Typography variant="overline" color="textSecondary">{t`Source entry - ${sourceTitle}`}</Typography>
|
||||||
|
<Link
|
||||||
|
component={RouterLink}
|
||||||
|
to={AppRoutes.manga.path(mangaId)}
|
||||||
|
sx={{ textDecoration: 'none', color: 'inherit' }}
|
||||||
|
>
|
||||||
|
<TypographyMaxLines variant="h6" component="h3" title={mangaTitle}>
|
||||||
|
{mangaTitle}
|
||||||
|
</TypographyMaxLines>
|
||||||
|
</Link>
|
||||||
|
<MigrationEntryMetadataText
|
||||||
|
artist={mangaArtist}
|
||||||
|
author={mangaAuthor}
|
||||||
|
latestChapterNumber={latestChapterNumber}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 { useEffect, useState } from 'react';
|
|
||||||
import { useParams } from 'react-router-dom';
|
|
||||||
import { useLingui } from '@lingui/react/macro';
|
|
||||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
|
||||||
import type { TMigratableSource } from '@/features/migration/components/MigrationCard.tsx';
|
|
||||||
import { LoadingPlaceholder } from '@/base/components/feedback/LoadingPlaceholder.tsx';
|
|
||||||
import { EmptyViewAbsoluteCentered } from '@/base/components/feedback/EmptyViewAbsoluteCentered.tsx';
|
|
||||||
import { GridLayouts } from '@/base/components/GridLayouts.tsx';
|
|
||||||
import { useLocalStorage } from '@/base/hooks/useStorage.tsx';
|
|
||||||
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
|
||||||
import type { GetSourceMigratableQuery, GetSourceMigratableQueryVariables } from '@/lib/graphql/generated/graphql.ts';
|
|
||||||
import { GET_SOURCE_MIGRATABLE } from '@/lib/graphql/source/SourceQuery.ts';
|
|
||||||
import { SOURCE_BASE_FIELDS } from '@/lib/graphql/source/SourceFragments.ts';
|
|
||||||
import { BaseMangaGrid } from '@/features/manga/components/BaseMangaGrid.tsx';
|
|
||||||
import { GridLayout } from '@/base/Base.types.ts';
|
|
||||||
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
|
|
||||||
import { useAppTitleAndAction } from '@/features/navigation-bar/hooks/useAppTitleAndAction.ts';
|
|
||||||
|
|
||||||
export const Migrate = () => {
|
|
||||||
const { t } = useLingui();
|
|
||||||
|
|
||||||
const { sourceId: paramSourceId } = useParams<{ sourceId: string }>();
|
|
||||||
|
|
||||||
const [gridLayout, setGridLayout] = useLocalStorage('migrateGridLayout', GridLayout.List);
|
|
||||||
|
|
||||||
const fragmentSource = requestManager.graphQLClient.client.cache.readFragment<
|
|
||||||
Pick<TMigratableSource, 'id' | 'name'>
|
|
||||||
>({
|
|
||||||
id: requestManager.graphQLClient.client.cache.identify({ __typename: 'SourceType', id: paramSourceId }),
|
|
||||||
fragment: SOURCE_BASE_FIELDS,
|
|
||||||
});
|
|
||||||
|
|
||||||
const [isKnownSource, setIsKnownSource] = useState(fragmentSource !== null ? true : undefined);
|
|
||||||
const {
|
|
||||||
data: migratableSourceData,
|
|
||||||
loading: isSourceLoading,
|
|
||||||
error: sourceError,
|
|
||||||
refetch: refetchSource,
|
|
||||||
} = requestManager.useGetSource<GetSourceMigratableQuery, GetSourceMigratableQueryVariables>(
|
|
||||||
GET_SOURCE_MIGRATABLE,
|
|
||||||
paramSourceId,
|
|
||||||
{ skip: !!isKnownSource, notifyOnNetworkStatusChange: true },
|
|
||||||
);
|
|
||||||
|
|
||||||
const { sourceId, name } = {
|
|
||||||
sourceId: paramSourceId,
|
|
||||||
name: paramSourceId,
|
|
||||||
...fragmentSource,
|
|
||||||
...migratableSourceData?.source,
|
|
||||||
};
|
|
||||||
|
|
||||||
const {
|
|
||||||
data: migratableSourceMangasData,
|
|
||||||
loading: areMangasLoading,
|
|
||||||
error: mangasError,
|
|
||||||
refetch: refetchMangas,
|
|
||||||
} = requestManager.useGetMigratableSourceMangas(sourceId, {
|
|
||||||
skip: !isKnownSource,
|
|
||||||
notifyOnNetworkStatusChange: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
useAppTitleAndAction(
|
|
||||||
name ?? sourceId ?? t`Migrate`,
|
|
||||||
<GridLayouts gridLayout={gridLayout} onChange={setGridLayout} />,
|
|
||||||
[gridLayout],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isSourceLoading || isKnownSource) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsKnownSource(
|
|
||||||
!!migratableSourceData ||
|
|
||||||
!!sourceError?.message.includes("The field at path '/source' was declared as a non null type"),
|
|
||||||
);
|
|
||||||
}, [isSourceLoading, sourceError]);
|
|
||||||
|
|
||||||
const isLoadingSource = isSourceLoading || (!sourceError && !isKnownSource);
|
|
||||||
const isLoading = isLoadingSource || areMangasLoading;
|
|
||||||
if (isLoading) {
|
|
||||||
return <LoadingPlaceholder />;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasErrorSource = sourceError && isKnownSource === false;
|
|
||||||
const hasError = hasErrorSource || mangasError;
|
|
||||||
if (hasError) {
|
|
||||||
const error = (hasErrorSource ? sourceError : mangasError)!;
|
|
||||||
return (
|
|
||||||
<EmptyViewAbsoluteCentered
|
|
||||||
message={t`Unable to load data`}
|
|
||||||
messageExtra={getErrorMessage(error)}
|
|
||||||
retry={() => {
|
|
||||||
if (hasErrorSource) {
|
|
||||||
refetchSource().catch(defaultPromiseErrorHandler('Migrate::refetchSource'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mangasError) {
|
|
||||||
refetchMangas().catch(defaultPromiseErrorHandler('Migrate::refetchMangas'));
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BaseMangaGrid
|
|
||||||
hasNextPage={false}
|
|
||||||
loadMore={() => {}}
|
|
||||||
isLoading={areMangasLoading}
|
|
||||||
mangas={migratableSourceMangasData?.mangas.nodes ?? []}
|
|
||||||
gridLayout={gridLayout}
|
|
||||||
mode="migrate.search"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -6,156 +6,59 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useMemo } from 'react';
|
import { Navigate } from 'react-router-dom';
|
||||||
import List from '@mui/material/List';
|
import { MigrationPhase } from '@/features/migration/Migration.types.ts';
|
||||||
import Stack from '@mui/material/Stack';
|
import { MigrationManager } from '@/features/migration/MigrationManager.ts';
|
||||||
import IconButton from '@mui/material/IconButton';
|
import { MigrationSelectSource } from '@/features/migration/screens/MigrationSelectSource.tsx';
|
||||||
import SortByAlphaIcon from '@mui/icons-material/SortByAlpha';
|
import { MigrationSelectMangas } from '@/features/migration/screens/MigrationSelectMangas.tsx';
|
||||||
import TagIcon from '@mui/icons-material/Tag';
|
import { MigrationSelectDestinationSources } from '@/features/migration/screens/MigrationSelectDestinationSources.tsx';
|
||||||
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
|
import { MigrationSearch } from '@/features/migration/screens/MigrationSearch.tsx';
|
||||||
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
|
import { MigrationExecute } from '@/features/migration/screens/MigrationExecute.tsx';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { AppRoutes } from '@/base/AppRoute.constants.ts';
|
||||||
import { CustomTooltip } from '@/base/components/CustomTooltip.tsx';
|
import { BrowseTab } from '@/features/browse/Browse.types.ts';
|
||||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
import { useAppPageHistoryContext } from '@/base/contexts/AppPageHistoryContext.tsx';
|
||||||
import { LoadingPlaceholder } from '@/base/components/feedback/LoadingPlaceholder.tsx';
|
import { useEffect } from 'react';
|
||||||
import { EmptyViewAbsoluteCentered } from '@/base/components/feedback/EmptyViewAbsoluteCentered.tsx';
|
import { ReactRouter } from '@/lib/react-router/ReactRouter.ts';
|
||||||
import type { TMigratableSource } from '@/features/migration/components/MigrationCard.tsx';
|
|
||||||
import { MigrationCard } from '@/features/migration/components/MigrationCard.tsx';
|
|
||||||
import { StyledGroupItemWrapper } from '@/base/components/virtuoso/StyledGroupItemWrapper.tsx';
|
|
||||||
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
|
||||||
import type { SortSettings, TMigratableSourcesResult } from '@/features/migration/Migration.types.ts';
|
|
||||||
import { SortBy, SortOrder } from '@/features/migration/Migration.types.ts';
|
|
||||||
import { sortByToTranslation, sortOrderToTranslation } from '@/features/migration/Migration.constants.ts';
|
|
||||||
import {
|
|
||||||
createUpdateMetadataServerSettings,
|
|
||||||
useMetadataServerSettings,
|
|
||||||
} from '@/features/settings/services/ServerSettingsMetadata.ts';
|
|
||||||
import { makeToast } from '@/base/utils/Toast.ts';
|
|
||||||
import { useNavBarContext } from '@/features/navigation-bar/NavbarContext.tsx';
|
|
||||||
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
|
|
||||||
|
|
||||||
const getMigratableSources = (
|
export const Migration = ({ tabsMenuHeight = 0 }: { tabsMenuHeight?: number }) => {
|
||||||
mangas: TMigratableSourcesResult | undefined,
|
const phase = MigrationManager.usePhase();
|
||||||
{ sortBy, sortOrder }: SortSettings,
|
const { setOnBack } = useAppPageHistoryContext();
|
||||||
): TMigratableSource[] => {
|
|
||||||
if (!mangas) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const sourceBySourceId: Record<string, TMigratableSource> = {};
|
useEffect(() => {
|
||||||
|
if (window.location.pathname !== AppRoutes.migrate.path) {
|
||||||
|
if (!MigrationManager.isActive()) {
|
||||||
|
MigrationManager.reset();
|
||||||
|
} else {
|
||||||
|
ReactRouter.navigate(AppRoutes.migrate.path);
|
||||||
|
}
|
||||||
|
|
||||||
mangas.forEach(({ sourceId, source }) => {
|
return;
|
||||||
const uniqueSource = sourceBySourceId[sourceId] ?? {
|
|
||||||
...{ id: sourceId, name: sourceId, lang: 'unknown', iconUrl: null, mangaCount: 0, ...source },
|
|
||||||
};
|
|
||||||
|
|
||||||
sourceBySourceId[sourceId] = {
|
|
||||||
...uniqueSource,
|
|
||||||
mangaCount: uniqueSource.mangaCount + 1,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const sourcesSortedBy = Object.values(sourceBySourceId).toSorted((a, b) => {
|
|
||||||
switch (sortBy) {
|
|
||||||
case SortBy.SOURCE_NAME:
|
|
||||||
return a.name.localeCompare(b.name);
|
|
||||||
case SortBy.MANGA_COUNT:
|
|
||||||
return a.mangaCount - b.mangaCount;
|
|
||||||
default:
|
|
||||||
throw new Error(`Unexpected "sortBy" "${sortBy}"`);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
switch (sortOrder) {
|
setOnBack(() => MigrationManager.goToPreviousPhase());
|
||||||
case SortOrder.ASC:
|
|
||||||
return sourcesSortedBy;
|
return () => {
|
||||||
case SortOrder.DESC:
|
setOnBack(null);
|
||||||
return sourcesSortedBy.toReversed();
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
switch (phase) {
|
||||||
|
case MigrationPhase.IDLE:
|
||||||
|
case MigrationPhase.SELECT_SOURCE:
|
||||||
|
return <MigrationSelectSource tabsMenuHeight={tabsMenuHeight} />;
|
||||||
|
case MigrationPhase.SELECT_MANGAS:
|
||||||
|
return <MigrationSelectMangas />;
|
||||||
|
case MigrationPhase.SELECTING_SOURCES:
|
||||||
|
return <MigrationSelectDestinationSources />;
|
||||||
|
case MigrationPhase.SEARCHING:
|
||||||
|
return <MigrationSearch />;
|
||||||
|
case MigrationPhase.MIGRATING:
|
||||||
|
return <MigrationExecute />;
|
||||||
|
// @ts-ignore - fall through
|
||||||
|
case MigrationPhase.ABORTED:
|
||||||
|
MigrationManager.reset();
|
||||||
|
// fall through
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unexpected "sortOrder" "${sortOrder}"`);
|
return <Navigate to={AppRoutes.browse.path(BrowseTab.MIGRATE)} replace />;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Migration = ({ tabsMenuHeight }: { tabsMenuHeight: number }) => {
|
|
||||||
const { t } = useLingui();
|
|
||||||
const { appBarHeight } = useNavBarContext();
|
|
||||||
|
|
||||||
const {
|
|
||||||
settings: { migrateSortSettings },
|
|
||||||
} = useMetadataServerSettings();
|
|
||||||
const updateMetadataServerSettings = createUpdateMetadataServerSettings<'migrateSortSettings'>((e) =>
|
|
||||||
makeToast(t`Failed to save changes`, 'error', getErrorMessage(e)),
|
|
||||||
);
|
|
||||||
const { sortBy, sortOrder } = migrateSortSettings;
|
|
||||||
|
|
||||||
const { data, loading, error, refetch } = requestManager.useGetMigratableSources({
|
|
||||||
notifyOnNetworkStatusChange: true,
|
|
||||||
});
|
|
||||||
const migratableSources = useMemo(
|
|
||||||
() => getMigratableSources(data?.mangas.nodes, migrateSortSettings),
|
|
||||||
[data?.mangas.nodes, migrateSortSettings],
|
|
||||||
);
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return <LoadingPlaceholder />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return (
|
|
||||||
<EmptyViewAbsoluteCentered
|
|
||||||
message={t`Unable to load data`}
|
|
||||||
messageExtra={getErrorMessage(error)}
|
|
||||||
retry={() => refetch().catch(defaultPromiseErrorHandler('Migration::refetch'))}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Stack
|
|
||||||
sx={{
|
|
||||||
position: 'sticky',
|
|
||||||
top: `${appBarHeight + tabsMenuHeight}px`,
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'end',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: 1,
|
|
||||||
p: 1,
|
|
||||||
backgroundColor: 'background.default',
|
|
||||||
zIndex: 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CustomTooltip title={t(sortByToTranslation[sortBy])}>
|
|
||||||
<IconButton
|
|
||||||
color="inherit"
|
|
||||||
onClick={() =>
|
|
||||||
updateMetadataServerSettings('migrateSortSettings', { sortBy: (sortBy + 1) % 2, sortOrder })
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{sortBy ? <TagIcon /> : <SortByAlphaIcon />}
|
|
||||||
</IconButton>
|
|
||||||
</CustomTooltip>
|
|
||||||
<CustomTooltip title={t(sortOrderToTranslation[sortOrder])}>
|
|
||||||
<IconButton
|
|
||||||
color="inherit"
|
|
||||||
onClick={() =>
|
|
||||||
updateMetadataServerSettings('migrateSortSettings', {
|
|
||||||
sortBy,
|
|
||||||
sortOrder: (sortOrder + 1) % 2,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{sortOrder ? <ArrowDownwardIcon /> : <ArrowUpwardIcon />}
|
|
||||||
</IconButton>
|
|
||||||
</CustomTooltip>
|
|
||||||
</Stack>
|
|
||||||
<List sx={{ p: 0 }}>
|
|
||||||
{migratableSources.map((migratableSource) => (
|
|
||||||
<StyledGroupItemWrapper key={migratableSource.id}>
|
|
||||||
<MigrationCard {...migratableSource} />
|
|
||||||
</StyledGroupItemWrapper>
|
|
||||||
))}
|
|
||||||
</List>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|||||||
123
src/features/migration/screens/MigrationExecute.tsx
Normal file
123
src/features/migration/screens/MigrationExecute.tsx
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* 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 Stack from '@mui/material/Stack';
|
||||||
|
import { MigrationProgressBar } from '@/features/migration/components/MigrationProgressBar.tsx';
|
||||||
|
import { useLingui } from '@lingui/react/macro';
|
||||||
|
import { useAppTitleAndAction } from '@/features/navigation-bar/hooks/useAppTitleAndAction.ts';
|
||||||
|
import { MigrationManager } from '@/features/migration/MigrationManager.ts';
|
||||||
|
import { MigrationEntryStatus } from '@/features/migration/Migration.types.ts';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { DEFAULT_FULL_FAB_HEIGHT } from '@/base/components/buttons/StyledFab.tsx';
|
||||||
|
import { MigrationContinueButton } from '@/features/migration/components/MigrationContinueButton.tsx';
|
||||||
|
import { plural } from '@lingui/core/macro';
|
||||||
|
import { MigrationEntryGroup } from '@/features/migration/components/MIgrationEntryGroup.tsx';
|
||||||
|
|
||||||
|
export const MigrationExecute = () => {
|
||||||
|
const { t } = useLingui();
|
||||||
|
const entries = MigrationManager.useEntries();
|
||||||
|
const progress = MigrationManager.useMigrationProgress();
|
||||||
|
|
||||||
|
useAppTitleAndAction(MigrationManager.isPhaseComplete() ? t`Migration complete` : t`Migrating`, undefined, [
|
||||||
|
MigrationManager.isPhaseComplete(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const entryList = useMemo(() => Object.values(entries), [entries]);
|
||||||
|
const migratingEntries = useMemo(
|
||||||
|
() =>
|
||||||
|
entryList.filter((entry) =>
|
||||||
|
[MigrationEntryStatus.PENDING, MigrationEntryStatus.MIGRATING].includes(entry.status),
|
||||||
|
),
|
||||||
|
[entryList],
|
||||||
|
);
|
||||||
|
const migratedEntries = useMemo(
|
||||||
|
() => entryList.filter((entry) => entry.status === MigrationEntryStatus.MIGRATION_COMPLETE),
|
||||||
|
[entryList],
|
||||||
|
);
|
||||||
|
const failedEntries = useMemo(
|
||||||
|
() => entryList.filter((entry) => entry.status === MigrationEntryStatus.MIGRATION_FAILED),
|
||||||
|
[entryList],
|
||||||
|
);
|
||||||
|
const excludedEntries = useMemo(() => entryList.filter((entry) => entry.isExcluded), [entryList]);
|
||||||
|
const noMatchEntries = useMemo(
|
||||||
|
() => entryList.filter((entry) => entry.status === MigrationEntryStatus.NO_MATCH),
|
||||||
|
[entryList],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MigrationProgressBar
|
||||||
|
{...progress}
|
||||||
|
label={t`${progress.completed} / ${progress.total}${progress.failed > 0 ? ` (${progress.failed} failed)` : ''}`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
sx={{ p: 2, pb: DEFAULT_FULL_FAB_HEIGHT, gap: 4, flexWrap: 'wrap', justifyContent: 'center' }}
|
||||||
|
>
|
||||||
|
<MigrationEntryGroup
|
||||||
|
status={MigrationEntryStatus.MIGRATING}
|
||||||
|
title={plural(migratingEntries.length, {
|
||||||
|
one: '1 migrating entry',
|
||||||
|
other: '# migrating entries',
|
||||||
|
})}
|
||||||
|
entries={migratingEntries}
|
||||||
|
isMigrating
|
||||||
|
color="error"
|
||||||
|
/>
|
||||||
|
<MigrationEntryGroup
|
||||||
|
status={MigrationEntryStatus.MIGRATION_FAILED}
|
||||||
|
title={plural(failedEntries.length, {
|
||||||
|
one: '1 failed entry',
|
||||||
|
other: '# failed entries',
|
||||||
|
})}
|
||||||
|
entries={failedEntries}
|
||||||
|
isMigrating
|
||||||
|
color="error"
|
||||||
|
/>
|
||||||
|
<MigrationEntryGroup
|
||||||
|
status={MigrationEntryStatus.NO_MATCH}
|
||||||
|
title={plural(noMatchEntries.length, {
|
||||||
|
one: '1 entry with no match',
|
||||||
|
other: '# entries with no match',
|
||||||
|
})}
|
||||||
|
entries={noMatchEntries}
|
||||||
|
color="warning"
|
||||||
|
isMigrating
|
||||||
|
/>
|
||||||
|
<MigrationEntryGroup
|
||||||
|
status={MigrationEntryStatus.EXCLUDED}
|
||||||
|
title={plural(excludedEntries.length, {
|
||||||
|
one: '1 excluded entry',
|
||||||
|
other: '# excluded entries',
|
||||||
|
})}
|
||||||
|
entries={excludedEntries}
|
||||||
|
color="info"
|
||||||
|
isMigrating
|
||||||
|
/>
|
||||||
|
<MigrationEntryGroup
|
||||||
|
status={MigrationEntryStatus.MIGRATION_COMPLETE}
|
||||||
|
title={plural(migratedEntries.length, {
|
||||||
|
one: '1 migrated entry',
|
||||||
|
other: '# migrated entries',
|
||||||
|
})}
|
||||||
|
entries={migratedEntries}
|
||||||
|
color="success"
|
||||||
|
isMigrating
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<MigrationContinueButton
|
||||||
|
title={MigrationManager.isPhaseComplete() ? t`Done` : t`Abort`}
|
||||||
|
onClick={() =>
|
||||||
|
MigrationManager.isPhaseComplete() ? MigrationManager.reset() : MigrationManager.abort()
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
14
src/features/migration/screens/MigrationManualSearch.tsx
Normal file
14
src/features/migration/screens/MigrationManualSearch.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* 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 { SearchAll } from '@/features/global-search/screens/SearchAll.tsx';
|
||||||
|
import { MigrationManager } from '@/features/migration/MigrationManager.ts';
|
||||||
|
|
||||||
|
export const MigrationManualSearch = () => (
|
||||||
|
<SearchAll migrationDestinationSourceIds={MigrationManager.getState().destinationSourceIds} />
|
||||||
|
);
|
||||||
115
src/features/migration/screens/MigrationSearch.tsx
Normal file
115
src/features/migration/screens/MigrationSearch.tsx
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
/*
|
||||||
|
* 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 { useLingui } from '@lingui/react/macro';
|
||||||
|
import { useAppTitleAndAction } from '@/features/navigation-bar/hooks/useAppTitleAndAction.ts';
|
||||||
|
import { MigrationManager } from '@/features/migration/MigrationManager.ts';
|
||||||
|
import { MigrationProgressBar } from '@/features/migration/components/MigrationProgressBar.tsx';
|
||||||
|
import { MigrationOptionsDialog } from '@/features/migration/components/MigrationOptionsDialog.tsx';
|
||||||
|
import { MigrationContinueButton } from '@/features/migration/components/MigrationContinueButton.tsx';
|
||||||
|
import { AwaitableComponent } from 'awaitable-component';
|
||||||
|
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
||||||
|
import { DEFAULT_FULL_FAB_HEIGHT } from '@/base/components/buttons/StyledFab.tsx';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
|
import { MigrationEntryStatus } from '@/features/migration/Migration.types.ts';
|
||||||
|
import { MigrationEntryGroup } from '@/features/migration/components/MIgrationEntryGroup.tsx';
|
||||||
|
import { plural } from '@lingui/core/macro';
|
||||||
|
|
||||||
|
export const MigrationSearch = () => {
|
||||||
|
const { t } = useLingui();
|
||||||
|
const entries = MigrationManager.useEntries();
|
||||||
|
const searchProgress = MigrationManager.useSearchProgress();
|
||||||
|
|
||||||
|
const isSearchComplete = searchProgress.completed === searchProgress.total && searchProgress.total > 0;
|
||||||
|
|
||||||
|
useAppTitleAndAction(t`Search results`, undefined, []);
|
||||||
|
|
||||||
|
const entryList = useMemo(() => Object.values(entries), [entries]);
|
||||||
|
const searchingEntries = useMemo(
|
||||||
|
() => entryList.filter((entry) => entry.status === MigrationEntryStatus.SEARCHING),
|
||||||
|
[entryList],
|
||||||
|
);
|
||||||
|
const failedEntries = useMemo(
|
||||||
|
() => entryList.filter((entry) => entry.status === MigrationEntryStatus.SEARCH_FAILED),
|
||||||
|
[entryList],
|
||||||
|
);
|
||||||
|
const noMatchEntries = useMemo(
|
||||||
|
() => entryList.filter((entry) => entry.status === MigrationEntryStatus.NO_MATCH),
|
||||||
|
[entryList],
|
||||||
|
);
|
||||||
|
const matchedEntries = useMemo(
|
||||||
|
() => entryList.filter((entry) => entry.status === MigrationEntryStatus.SEARCH_COMPLETE),
|
||||||
|
[entryList],
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasMigratableEntries = useMemo(() => !!MigrationManager.getMigratableEntries().length, [entryList]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MigrationProgressBar
|
||||||
|
{...searchProgress}
|
||||||
|
label={t`${searchProgress.completed} / ${searchProgress.total}`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
sx={{ p: 2, pb: DEFAULT_FULL_FAB_HEIGHT, gap: 4, flexWrap: 'wrap', justifyContent: 'center' }}
|
||||||
|
>
|
||||||
|
<MigrationEntryGroup
|
||||||
|
status={MigrationEntryStatus.SEARCHING}
|
||||||
|
title={plural(searchingEntries.length, {
|
||||||
|
one: '1 searching',
|
||||||
|
other: '# searching',
|
||||||
|
})}
|
||||||
|
entries={searchingEntries}
|
||||||
|
color="error"
|
||||||
|
/>
|
||||||
|
<MigrationEntryGroup
|
||||||
|
status={MigrationEntryStatus.SEARCH_FAILED}
|
||||||
|
title={plural(failedEntries.length, {
|
||||||
|
one: '1 failed entry',
|
||||||
|
other: '# failed entries',
|
||||||
|
})}
|
||||||
|
entries={failedEntries}
|
||||||
|
color="error"
|
||||||
|
/>
|
||||||
|
<MigrationEntryGroup
|
||||||
|
status={MigrationEntryStatus.NO_MATCH}
|
||||||
|
title={plural(noMatchEntries.length, {
|
||||||
|
one: '1 entry with no match',
|
||||||
|
other: '# entries with no match',
|
||||||
|
})}
|
||||||
|
entries={noMatchEntries}
|
||||||
|
color="warning"
|
||||||
|
/>
|
||||||
|
<MigrationEntryGroup
|
||||||
|
status={MigrationEntryStatus.SEARCH_COMPLETE}
|
||||||
|
title={plural(matchedEntries.length, {
|
||||||
|
one: '1 matched entry',
|
||||||
|
other: '# matched entries',
|
||||||
|
})}
|
||||||
|
entries={matchedEntries}
|
||||||
|
color="success"
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<MigrationContinueButton
|
||||||
|
title={t`Start migration`}
|
||||||
|
isDisabled={!isSearchComplete || !hasMigratableEntries}
|
||||||
|
onClick={async () => {
|
||||||
|
try {
|
||||||
|
const options = await AwaitableComponent.show(MigrationOptionsDialog);
|
||||||
|
await MigrationManager.startMigration(options);
|
||||||
|
} catch (e) {
|
||||||
|
defaultPromiseErrorHandler('MigrationSearch')(e);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
/*
|
||||||
|
* 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 { useLingui } from '@lingui/react/macro';
|
||||||
|
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||||
|
import { LoadingPlaceholder } from '@/base/components/feedback/LoadingPlaceholder.tsx';
|
||||||
|
import { EmptyViewAbsoluteCentered } from '@/base/components/feedback/EmptyViewAbsoluteCentered.tsx';
|
||||||
|
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
||||||
|
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
|
||||||
|
import { MigrationManager } from '@/features/migration/MigrationManager.ts';
|
||||||
|
import { MigrationSourceList } from '@/features/migration/components/MigrationSourceList.tsx';
|
||||||
|
import { useSelectableCollection } from '@/base/collection/hooks/useSelectableCollection.ts';
|
||||||
|
import type { SourceIdInfo } from '@/features/source/Source.types.ts';
|
||||||
|
import { useCallback, useMemo } from 'react';
|
||||||
|
import { STABLE_EMPTY_ARRAY } from '@/base/Base.constants.ts';
|
||||||
|
import { arrayMove } from '@dnd-kit/sortable';
|
||||||
|
import Fab from '@mui/material/Fab';
|
||||||
|
import { useAppTitleAndAction } from '@/features/navigation-bar/hooks/useAppTitleAndAction.ts';
|
||||||
|
import PushPinIcon from '@mui/icons-material/PushPin';
|
||||||
|
import IconButton from '@mui/material/IconButton';
|
||||||
|
import { SelectableCollectionSelectMode } from '@/base/collection/components/SelectableCollectionSelectMode.tsx';
|
||||||
|
import { Sources } from '@/features/source/services/Sources.ts';
|
||||||
|
import { useMetadataServerSettings } from '@/features/settings/services/ServerSettingsMetadata.ts';
|
||||||
|
import ToggleOnIcon from '@mui/icons-material/ToggleOn';
|
||||||
|
import { CustomTooltip } from '@/base/components/CustomTooltip.tsx';
|
||||||
|
|
||||||
|
export const MigrationSelectDestinationSources = () => {
|
||||||
|
const { t } = useLingui();
|
||||||
|
const currentSourceId = MigrationManager.useSourceId();
|
||||||
|
|
||||||
|
const {
|
||||||
|
settings: { browseLanguages, showNsfw },
|
||||||
|
loading: areSettingsLoading,
|
||||||
|
request: { error: settingsError, refetch: refetchSettings },
|
||||||
|
} = useMetadataServerSettings();
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
loading: areSourcesLoading,
|
||||||
|
error: sourceError,
|
||||||
|
refetch: refetchSources,
|
||||||
|
} = requestManager.useGetSourceList({
|
||||||
|
notifyOnNetworkStatusChange: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const allSources = data?.sources.nodes ?? STABLE_EMPTY_ARRAY;
|
||||||
|
const sources = useMemo(
|
||||||
|
() =>
|
||||||
|
Sources.filter(allSources, {
|
||||||
|
languages: browseLanguages,
|
||||||
|
isNsfw: showNsfw ? undefined : false,
|
||||||
|
}),
|
||||||
|
[allSources, browseLanguages, showNsfw],
|
||||||
|
);
|
||||||
|
const sourceIds = useMemo(() => Sources.getIds(sources), [sources]);
|
||||||
|
const pinnedSourceIds = useMemo(() => Sources.getIds(Sources.filter(sources, { pinned: true })), [sources]);
|
||||||
|
const enabledSourceIds = useMemo(() => Sources.getIds(Sources.filter(sources, { enabled: true })), [sources]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
selectedItemIds,
|
||||||
|
areAllItemsSelected,
|
||||||
|
areNoItemsSelected,
|
||||||
|
handleSelectAll,
|
||||||
|
setSelectionForKey,
|
||||||
|
handleSelection,
|
||||||
|
} = useSelectableCollection<SourceIdInfo['id']>(sources.length ?? 0, {
|
||||||
|
currentKey: 'default',
|
||||||
|
initialState: useMemo(
|
||||||
|
() => ({
|
||||||
|
default: pinnedSourceIds,
|
||||||
|
}),
|
||||||
|
[pinnedSourceIds],
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
useAppTitleAndAction(
|
||||||
|
t`Select destination sources`,
|
||||||
|
<>
|
||||||
|
<CustomTooltip title={t`Select pinned sources`}>
|
||||||
|
<IconButton color="inherit" onClick={() => setSelectionForKey('default', pinnedSourceIds)}>
|
||||||
|
<PushPinIcon />
|
||||||
|
</IconButton>
|
||||||
|
</CustomTooltip>
|
||||||
|
<CustomTooltip title={t`Select enabled sources`}>
|
||||||
|
<IconButton color="inherit" onClick={() => setSelectionForKey('default', enabledSourceIds)}>
|
||||||
|
<ToggleOnIcon />
|
||||||
|
</IconButton>
|
||||||
|
</CustomTooltip>
|
||||||
|
<SelectableCollectionSelectMode
|
||||||
|
isActive
|
||||||
|
isCancelable={false}
|
||||||
|
areAllItemsSelected={areAllItemsSelected}
|
||||||
|
areNoItemsSelected={areNoItemsSelected}
|
||||||
|
onSelectAll={(selectAll) =>
|
||||||
|
handleSelectAll(selectAll, [...new Set([...selectedItemIds, ...sourceIds])])
|
||||||
|
}
|
||||||
|
onModeChange={(checked) => {
|
||||||
|
handleSelectAll(checked, [...new Set([...selectedItemIds, ...sourceIds])]);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>,
|
||||||
|
[
|
||||||
|
setSelectionForKey,
|
||||||
|
selectedItemIds,
|
||||||
|
pinnedSourceIds,
|
||||||
|
enabledSourceIds,
|
||||||
|
areAllItemsSelected,
|
||||||
|
areNoItemsSelected,
|
||||||
|
handleSelectAll,
|
||||||
|
sourceIds,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handlePriorityChange = useCallback(
|
||||||
|
(oldIndex: number, newIndex: number) => {
|
||||||
|
setSelectionForKey('default', arrayMove(selectedItemIds, oldIndex, newIndex));
|
||||||
|
},
|
||||||
|
[selectedItemIds, setSelectionForKey],
|
||||||
|
);
|
||||||
|
|
||||||
|
const loading = areSourcesLoading || areSettingsLoading;
|
||||||
|
if (loading) {
|
||||||
|
return <LoadingPlaceholder />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const error = settingsError ?? sourceError;
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<EmptyViewAbsoluteCentered
|
||||||
|
message={t`Unable to load sources`}
|
||||||
|
messageExtra={getErrorMessage(error)}
|
||||||
|
retry={() => {
|
||||||
|
if (settingsError) {
|
||||||
|
refetchSettings().catch(
|
||||||
|
defaultPromiseErrorHandler('MigrationSelectingSources::refetchSettings'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sourceError) {
|
||||||
|
refetchSources().catch(defaultPromiseErrorHandler('MigrationSelectingSources::refetchSources'));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MigrationSourceList
|
||||||
|
sources={sources}
|
||||||
|
selectedSourceIds={selectedItemIds}
|
||||||
|
handleSelection={handleSelection}
|
||||||
|
handlePriorityChange={handlePriorityChange}
|
||||||
|
currentSourceId={currentSourceId}
|
||||||
|
/>
|
||||||
|
<Fab
|
||||||
|
variant="extended"
|
||||||
|
color="primary"
|
||||||
|
sx={{
|
||||||
|
position: 'fixed',
|
||||||
|
bottom: (theme) => theme.spacing(2),
|
||||||
|
right: (theme) => theme.spacing(2),
|
||||||
|
}}
|
||||||
|
onClick={() => MigrationManager.startSearch(selectedItemIds)}
|
||||||
|
>
|
||||||
|
{t`Start Search`}
|
||||||
|
</Fab>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
171
src/features/migration/screens/MigrationSelectMangas.tsx
Normal file
171
src/features/migration/screens/MigrationSelectMangas.tsx
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
/*
|
||||||
|
* 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 { useMemo } from 'react';
|
||||||
|
import { useLingui } from '@lingui/react/macro';
|
||||||
|
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||||
|
import { LoadingPlaceholder } from '@/base/components/feedback/LoadingPlaceholder.tsx';
|
||||||
|
import { EmptyViewAbsoluteCentered } from '@/base/components/feedback/EmptyViewAbsoluteCentered.tsx';
|
||||||
|
import { BaseMangaGrid } from '@/features/manga/components/BaseMangaGrid.tsx';
|
||||||
|
import { GridLayout } from '@/base/Base.types.ts';
|
||||||
|
import { getErrorMessage, noOp } from '@/lib/HelperFunctions.ts';
|
||||||
|
import { useAppTitleAndAction } from '@/features/navigation-bar/hooks/useAppTitleAndAction.ts';
|
||||||
|
import { GridLayouts } from '@/base/components/GridLayouts.tsx';
|
||||||
|
import { useLocalStorage } from '@/base/hooks/useStorage.tsx';
|
||||||
|
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
||||||
|
import { MigrationManager } from '@/features/migration/MigrationManager.ts';
|
||||||
|
import { useSelectableCollection } from '@/base/collection/hooks/useSelectableCollection.ts';
|
||||||
|
import type { GetSourceMigratableQuery, GetSourceMigratableQueryVariables } from '@/lib/graphql/generated/graphql.ts';
|
||||||
|
import { GET_SOURCE_MIGRATABLE } from '@/lib/graphql/source/SourceQuery.ts';
|
||||||
|
import { SelectableCollectionSelectMode } from '@/base/collection/components/SelectableCollectionSelectMode.tsx';
|
||||||
|
import { Mangas } from '@/features/manga/services/Mangas.ts';
|
||||||
|
import { STABLE_EMPTY_ARRAY } from '@/base/Base.constants.ts';
|
||||||
|
import { MigrationContinueButton } from '@/features/migration/components/MigrationContinueButton.tsx';
|
||||||
|
import { AppRoutes } from '@/base/AppRoute.constants.ts';
|
||||||
|
import { ReactRouter } from '@/lib/react-router/ReactRouter.ts';
|
||||||
|
|
||||||
|
const getSourceError = (error: unknown): unknown => {
|
||||||
|
const message = getErrorMessage(error);
|
||||||
|
|
||||||
|
if (
|
||||||
|
message.includes(
|
||||||
|
"The field at path '/source' was declared as a non null type, but the code involved in retrieving data has wrongly returned a null value",
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return error;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MigrationSelectMangas = () => {
|
||||||
|
const { t } = useLingui();
|
||||||
|
const sourceId = MigrationManager.useSourceId();
|
||||||
|
const selectedMangas = MigrationManager.useEntries();
|
||||||
|
|
||||||
|
const [gridLayout, setGridLayout] = useLocalStorage('migrateGridLayout', GridLayout.List);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: migratableSourceData,
|
||||||
|
loading: isSourceLoading,
|
||||||
|
error: sourceError,
|
||||||
|
refetch: refetchSource,
|
||||||
|
} = requestManager.useGetSource<GetSourceMigratableQuery, GetSourceMigratableQueryVariables>(
|
||||||
|
GET_SOURCE_MIGRATABLE,
|
||||||
|
sourceId ?? '',
|
||||||
|
{ skip: !sourceId, notifyOnNetworkStatusChange: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: migratableSourceMangasData,
|
||||||
|
loading: areMangasLoading,
|
||||||
|
error: mangasError,
|
||||||
|
refetch: refetchMangas,
|
||||||
|
} = requestManager.useGetMigratableSourceMangas(sourceId!, {
|
||||||
|
skip: !sourceId,
|
||||||
|
notifyOnNetworkStatusChange: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mangas = migratableSourceMangasData?.mangas.nodes ?? STABLE_EMPTY_ARRAY;
|
||||||
|
const mangaIds = useMemo(() => Mangas.getIds(mangas), [mangas]);
|
||||||
|
|
||||||
|
const { selectedItemIds, handleSelection, handleSelectAll, areAllItemsSelected, areNoItemsSelected } =
|
||||||
|
useSelectableCollection<number, string>(mangas.length, {
|
||||||
|
itemIds: mangaIds,
|
||||||
|
currentKey: 'default',
|
||||||
|
initialState: useMemo(
|
||||||
|
() => ({
|
||||||
|
default: Object.keys(selectedMangas).map(Number),
|
||||||
|
}),
|
||||||
|
[selectedMangas],
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
const sourceName = migratableSourceData?.source?.displayName ?? sourceId ?? t`Migrate`;
|
||||||
|
useAppTitleAndAction(
|
||||||
|
sourceName,
|
||||||
|
<>
|
||||||
|
<GridLayouts gridLayout={gridLayout} onChange={setGridLayout} />
|
||||||
|
<SelectableCollectionSelectMode
|
||||||
|
isActive
|
||||||
|
isCancelable={false}
|
||||||
|
areAllItemsSelected={areAllItemsSelected}
|
||||||
|
areNoItemsSelected={areNoItemsSelected}
|
||||||
|
onSelectAll={(selectAll) => handleSelectAll(selectAll, [...new Set([...selectedItemIds, ...mangaIds])])}
|
||||||
|
onModeChange={(checked) => {
|
||||||
|
handleSelectAll(checked, [...new Set([...selectedItemIds, ...mangaIds])]);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>,
|
||||||
|
[gridLayout, setGridLayout, areAllItemsSelected, areNoItemsSelected, handleSelectAll, mangaIds],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleContinue = () => {
|
||||||
|
const selected = mangas.filter((manga) => selectedItemIds.includes(manga.id));
|
||||||
|
|
||||||
|
const [entry] = selected;
|
||||||
|
const isSingleManga = selected.length === 1;
|
||||||
|
|
||||||
|
if (isSingleManga && entry) {
|
||||||
|
ReactRouter.navigate(
|
||||||
|
AppRoutes.migrate.childRoutes.singleMangaSearch.path(entry.sourceId, entry.id, entry.title),
|
||||||
|
{
|
||||||
|
state: { mangaTitle: entry.title },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
MigrationManager.reset();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MigrationManager.selectMangas(selected);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isLoading = isSourceLoading || areMangasLoading;
|
||||||
|
if (isLoading) {
|
||||||
|
return <LoadingPlaceholder />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasError = getSourceError(sourceError) || mangasError;
|
||||||
|
if (hasError) {
|
||||||
|
const error = getSourceError(sourceError) ?? mangasError;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EmptyViewAbsoluteCentered
|
||||||
|
message={t`Unable to load data`}
|
||||||
|
messageExtra={getErrorMessage(error)}
|
||||||
|
retry={() => {
|
||||||
|
if (getSourceError(sourceError)) {
|
||||||
|
refetchSource().catch(defaultPromiseErrorHandler('MigrationSelectMangas::refetchSource'));
|
||||||
|
}
|
||||||
|
if (mangasError) {
|
||||||
|
refetchMangas().catch(defaultPromiseErrorHandler('MigrationSelectMangas::refetchMangas'));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<BaseMangaGrid
|
||||||
|
mode="migrate.select"
|
||||||
|
hasNextPage={false}
|
||||||
|
loadMore={noOp}
|
||||||
|
isLoading={areMangasLoading}
|
||||||
|
mangas={mangas}
|
||||||
|
gridLayout={gridLayout}
|
||||||
|
isSelectModeActive
|
||||||
|
selectedMangaIds={selectedItemIds}
|
||||||
|
handleSelection={handleSelection}
|
||||||
|
/>
|
||||||
|
<MigrationContinueButton onClick={handleContinue} isDisabled={!selectedItemIds.length} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
161
src/features/migration/screens/MigrationSelectSource.tsx
Normal file
161
src/features/migration/screens/MigrationSelectSource.tsx
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
/*
|
||||||
|
* 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 { useMemo } from 'react';
|
||||||
|
import List from '@mui/material/List';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
|
import IconButton from '@mui/material/IconButton';
|
||||||
|
import SortByAlphaIcon from '@mui/icons-material/SortByAlpha';
|
||||||
|
import TagIcon from '@mui/icons-material/Tag';
|
||||||
|
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
|
||||||
|
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
|
||||||
|
import { useLingui } from '@lingui/react/macro';
|
||||||
|
import { CustomTooltip } from '@/base/components/CustomTooltip.tsx';
|
||||||
|
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||||
|
import { LoadingPlaceholder } from '@/base/components/feedback/LoadingPlaceholder.tsx';
|
||||||
|
import { EmptyViewAbsoluteCentered } from '@/base/components/feedback/EmptyViewAbsoluteCentered.tsx';
|
||||||
|
import type { TMigratableSource } from '@/features/migration/components/MigrationCard.tsx';
|
||||||
|
import { MigrationCard } from '@/features/migration/components/MigrationCard.tsx';
|
||||||
|
import { StyledGroupItemWrapper } from '@/base/components/virtuoso/StyledGroupItemWrapper.tsx';
|
||||||
|
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
||||||
|
import type { SortSettings, TMigratableSourcesResult } from '@/features/migration/Migration.types.ts';
|
||||||
|
import { SortBy, SortOrder } from '@/features/migration/Migration.types.ts';
|
||||||
|
import { sortByToTranslation, sortOrderToTranslation } from '@/features/migration/Migration.constants.ts';
|
||||||
|
import {
|
||||||
|
createUpdateMetadataServerSettings,
|
||||||
|
useMetadataServerSettings,
|
||||||
|
} from '@/features/settings/services/ServerSettingsMetadata.ts';
|
||||||
|
import { makeToast } from '@/base/utils/Toast.ts';
|
||||||
|
import { useNavBarContext } from '@/features/navigation-bar/NavbarContext.tsx';
|
||||||
|
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
|
||||||
|
|
||||||
|
const getMigratableSources = (
|
||||||
|
mangas: TMigratableSourcesResult | undefined,
|
||||||
|
{ sortBy, sortOrder }: SortSettings,
|
||||||
|
): TMigratableSource[] => {
|
||||||
|
if (!mangas) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceBySourceId: Record<string, TMigratableSource> = {};
|
||||||
|
|
||||||
|
mangas.forEach(({ sourceId, source }) => {
|
||||||
|
const uniqueSource = sourceBySourceId[sourceId] ?? {
|
||||||
|
...{ id: sourceId, name: sourceId, lang: 'unknown', iconUrl: null, mangaCount: 0, ...source },
|
||||||
|
};
|
||||||
|
|
||||||
|
sourceBySourceId[sourceId] = {
|
||||||
|
...uniqueSource,
|
||||||
|
mangaCount: uniqueSource.mangaCount + 1,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const sourcesSortedBy = Object.values(sourceBySourceId).toSorted((a, b) => {
|
||||||
|
switch (sortBy) {
|
||||||
|
case SortBy.SOURCE_NAME:
|
||||||
|
return a.name.localeCompare(b.name);
|
||||||
|
case SortBy.MANGA_COUNT:
|
||||||
|
return a.mangaCount - b.mangaCount;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unexpected "sortBy" "${sortBy}"`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
switch (sortOrder) {
|
||||||
|
case SortOrder.ASC:
|
||||||
|
return sourcesSortedBy;
|
||||||
|
case SortOrder.DESC:
|
||||||
|
return sourcesSortedBy.toReversed();
|
||||||
|
default:
|
||||||
|
throw new Error(`Unexpected "sortOrder" "${sortOrder}"`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MigrationSelectSource = ({ tabsMenuHeight }: { tabsMenuHeight: number }) => {
|
||||||
|
const { t } = useLingui();
|
||||||
|
const { appBarHeight } = useNavBarContext();
|
||||||
|
|
||||||
|
const {
|
||||||
|
settings: { migrateSortSettings },
|
||||||
|
} = useMetadataServerSettings();
|
||||||
|
const updateMetadataServerSettings = createUpdateMetadataServerSettings<'migrateSortSettings'>((e) =>
|
||||||
|
makeToast(t`Failed to save changes`, 'error', getErrorMessage(e)),
|
||||||
|
);
|
||||||
|
const { sortBy, sortOrder } = migrateSortSettings;
|
||||||
|
|
||||||
|
const { data, loading, error, refetch } = requestManager.useGetMigratableSources({
|
||||||
|
notifyOnNetworkStatusChange: true,
|
||||||
|
});
|
||||||
|
const migratableSources = useMemo(
|
||||||
|
() => getMigratableSources(data?.mangas.nodes, migrateSortSettings),
|
||||||
|
[data?.mangas.nodes, migrateSortSettings],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <LoadingPlaceholder />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<EmptyViewAbsoluteCentered
|
||||||
|
message={t`Unable to load data`}
|
||||||
|
messageExtra={getErrorMessage(error)}
|
||||||
|
retry={() => refetch().catch(defaultPromiseErrorHandler('Migration::refetch'))}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Stack
|
||||||
|
sx={{
|
||||||
|
position: 'sticky',
|
||||||
|
top: `${appBarHeight + tabsMenuHeight}px`,
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'end',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 1,
|
||||||
|
p: 1,
|
||||||
|
backgroundColor: 'background.default',
|
||||||
|
zIndex: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CustomTooltip title={t(sortByToTranslation[sortBy])}>
|
||||||
|
<IconButton
|
||||||
|
color="inherit"
|
||||||
|
onClick={() =>
|
||||||
|
updateMetadataServerSettings('migrateSortSettings', { sortBy: (sortBy + 1) % 2, sortOrder })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{sortBy ? <TagIcon /> : <SortByAlphaIcon />}
|
||||||
|
</IconButton>
|
||||||
|
</CustomTooltip>
|
||||||
|
<CustomTooltip title={t(sortOrderToTranslation[sortOrder])}>
|
||||||
|
<IconButton
|
||||||
|
color="inherit"
|
||||||
|
onClick={() =>
|
||||||
|
updateMetadataServerSettings('migrateSortSettings', {
|
||||||
|
sortBy,
|
||||||
|
sortOrder: (sortOrder + 1) % 2,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{sortOrder ? <ArrowDownwardIcon /> : <ArrowUpwardIcon />}
|
||||||
|
</IconButton>
|
||||||
|
</CustomTooltip>
|
||||||
|
</Stack>
|
||||||
|
<List sx={{ p: 0 }}>
|
||||||
|
{migratableSources.map((migratableSource) => (
|
||||||
|
<StyledGroupItemWrapper key={migratableSource.id}>
|
||||||
|
<MigrationCard {...migratableSource} />
|
||||||
|
</StyledGroupItemWrapper>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -401,7 +401,7 @@ export const ServerSettings = () => {
|
|||||||
),
|
),
|
||||||
actions: {
|
actions: {
|
||||||
confirm: {
|
confirm: {
|
||||||
title: t`I understand`,
|
title: t`Understood`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -30,10 +30,33 @@ import {
|
|||||||
} from '@/features/settings/services/ServerSettingsMetadata.ts';
|
} from '@/features/settings/services/ServerSettingsMetadata.ts';
|
||||||
import { makeToast } from '@/base/utils/Toast.ts';
|
import { makeToast } from '@/base/utils/Toast.ts';
|
||||||
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
|
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
|
||||||
|
import type { SourceBaseFieldsFragment } from '@/lib/graphql/generated/graphql.ts';
|
||||||
|
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||||
|
import type { DocumentNode, Unmasked } from '@apollo/client';
|
||||||
|
import { SOURCE_BASE_FIELDS } from '@/lib/graphql/source/SourceFragments.ts';
|
||||||
|
|
||||||
export class Sources {
|
export class Sources {
|
||||||
static readonly LOCAL_SOURCE_ID = '0';
|
static readonly LOCAL_SOURCE_ID = '0';
|
||||||
|
|
||||||
|
static getIds(sources: SourceIdInfo[]): SourceIdInfo['id'][] {
|
||||||
|
return sources.map((source) => source.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static getFromCache<T = SourceBaseFieldsFragment>(
|
||||||
|
id: SourceIdInfo['id'],
|
||||||
|
fragment: DocumentNode = SOURCE_BASE_FIELDS,
|
||||||
|
fragmentName: string = 'SOURCE_BASE_FIELDS',
|
||||||
|
): Unmasked<T> | null {
|
||||||
|
return requestManager.graphQLClient.client.cache.readFragment<T>({
|
||||||
|
id: requestManager.graphQLClient.client.cache.identify({
|
||||||
|
__typename: 'SourceType',
|
||||||
|
id,
|
||||||
|
}),
|
||||||
|
fragment,
|
||||||
|
fragmentName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
static isLocalSource(source: SourceIdInfo): boolean {
|
static isLocalSource(source: SourceIdInfo): boolean {
|
||||||
return source.id === Sources.LOCAL_SOURCE_ID;
|
return source.id === Sources.LOCAL_SOURCE_ID;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -287,7 +287,7 @@ msgstr "حسب تاريخ التحميل"
|
|||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/category/components/CategorySelect.tsx
|
#: src/features/category/components/CategorySelect.tsx
|
||||||
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
||||||
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
||||||
#: src/features/source/browse/components/SourceOptions.tsx
|
#: src/features/source/browse/components/SourceOptions.tsx
|
||||||
@@ -419,7 +419,7 @@ msgid "Copied to clipboard"
|
|||||||
msgstr "تم النسخ إلى الحافظة"
|
msgstr "تم النسخ إلى الحافظة"
|
||||||
|
|
||||||
#: src/features/manga/components/details/MangaDetails.tsx
|
#: src/features/manga/components/details/MangaDetails.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Copy"
|
msgid "Copy"
|
||||||
msgstr "نسخ"
|
msgstr "نسخ"
|
||||||
|
|
||||||
@@ -584,7 +584,7 @@ msgstr "احذف الفصل بعد وضع علامة عليه يدويًا كم
|
|||||||
msgid "Delete chapters"
|
msgid "Delete chapters"
|
||||||
msgstr "حذف الفصول"
|
msgstr "حذف الفصول"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Delete downloaded"
|
msgid "Delete downloaded"
|
||||||
msgstr "حذف ما تم تنزيله"
|
msgstr "حذف ما تم تنزيله"
|
||||||
|
|
||||||
@@ -1134,7 +1134,7 @@ msgstr "القائمة"
|
|||||||
#: src/features/manga/components/MangaToolbarMenu.tsx
|
#: src/features/manga/components/MangaToolbarMenu.tsx
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Migrate"
|
msgid "Migrate"
|
||||||
msgstr "يهاجر"
|
msgstr "يهاجر"
|
||||||
|
|
||||||
@@ -1144,7 +1144,7 @@ msgstr "يهاجر"
|
|||||||
msgid "Migrate"
|
msgid "Migrate"
|
||||||
msgstr "نقل"
|
msgstr "نقل"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Migrating manga…"
|
msgid "Migrating manga…"
|
||||||
msgstr "جاري نقل المانجا…"
|
msgstr "جاري نقل المانجا…"
|
||||||
|
|
||||||
@@ -1503,7 +1503,7 @@ msgstr "تحديد"
|
|||||||
msgid "Select all"
|
msgid "Select all"
|
||||||
msgstr "تحديد الكل"
|
msgstr "تحديد الكل"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Select data to include"
|
msgid "Select data to include"
|
||||||
msgstr "اختر البيانات لتضمينها"
|
msgstr "اختر البيانات لتضمينها"
|
||||||
|
|
||||||
@@ -1559,7 +1559,7 @@ msgid "Show continue reading button"
|
|||||||
msgstr "إظهار زر مواصلة القراءة"
|
msgstr "إظهار زر مواصلة القراءة"
|
||||||
|
|
||||||
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Show entry"
|
msgid "Show entry"
|
||||||
msgstr "إظهار الإدخال"
|
msgstr "إظهار الإدخال"
|
||||||
|
|
||||||
@@ -2855,7 +2855,7 @@ msgstr "الفئات"
|
|||||||
|
|
||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Category"
|
msgid "Category"
|
||||||
msgstr "الفئة"
|
msgstr "الفئة"
|
||||||
|
|
||||||
@@ -2870,7 +2870,7 @@ msgstr "يغيّر ألوان السمة في صفحة المانغا بناءً
|
|||||||
#: src/features/chapter/components/cards/ChapterCard.tsx
|
#: src/features/chapter/components/cards/ChapterCard.tsx
|
||||||
#: src/features/downloads/components/DownloadAheadSetting.tsx
|
#: src/features/downloads/components/DownloadAheadSetting.tsx
|
||||||
#: src/features/downloads/screens/DownloadSettings.tsx
|
#: src/features/downloads/screens/DownloadSettings.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
||||||
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
||||||
#: src/features/tracker/components/cards/TrackerActiveCard.tsx
|
#: src/features/tracker/components/cards/TrackerActiveCard.tsx
|
||||||
@@ -2896,7 +2896,7 @@ msgid "Clear"
|
|||||||
msgstr "مسح"
|
msgstr "مسح"
|
||||||
|
|
||||||
#: src/features/backup/Backup.constants.ts
|
#: src/features/backup/Backup.constants.ts
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Client data"
|
msgid "Client data"
|
||||||
msgstr "بيانات المستخدم"
|
msgstr "بيانات المستخدم"
|
||||||
|
|
||||||
|
|||||||
@@ -374,7 +374,7 @@ msgstr "Aufruf Timeout"
|
|||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/category/components/CategorySelect.tsx
|
#: src/features/category/components/CategorySelect.tsx
|
||||||
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
||||||
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
||||||
#: src/features/source/browse/components/SourceOptions.tsx
|
#: src/features/source/browse/components/SourceOptions.tsx
|
||||||
@@ -589,7 +589,7 @@ msgid "Copied to clipboard"
|
|||||||
msgstr "In Zwischenablage kopiert"
|
msgstr "In Zwischenablage kopiert"
|
||||||
|
|
||||||
#: src/features/manga/components/details/MangaDetails.tsx
|
#: src/features/manga/components/details/MangaDetails.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Copy"
|
msgid "Copy"
|
||||||
msgstr "Kopieren"
|
msgstr "Kopieren"
|
||||||
|
|
||||||
@@ -876,7 +876,7 @@ msgstr "Lösche Kapitel, nach sie manuell als gelesen markiert wurden"
|
|||||||
msgid "Delete chapters"
|
msgid "Delete chapters"
|
||||||
msgstr "Lösche Kapitel"
|
msgstr "Lösche Kapitel"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Delete downloaded"
|
msgid "Delete downloaded"
|
||||||
msgstr "Heruntergeladene Löschen"
|
msgstr "Heruntergeladene Löschen"
|
||||||
|
|
||||||
@@ -1814,12 +1814,12 @@ msgstr "Menü"
|
|||||||
#: src/features/manga/components/MangaToolbarMenu.tsx
|
#: src/features/manga/components/MangaToolbarMenu.tsx
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/migration/screens/Migrate.tsx
|
#: src/features/migration/screens/Migrate.tsx
|
||||||
msgid "Migrate"
|
msgid "Migrate"
|
||||||
msgstr "Migrieren"
|
msgstr "Migrieren"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Migrating manga…"
|
msgid "Migrating manga…"
|
||||||
msgstr "Manga werden migriert…"
|
msgstr "Manga werden migriert…"
|
||||||
|
|
||||||
@@ -2464,7 +2464,7 @@ msgstr "Wähle ein Gerät, um dessen am Server gespeicherten UI-Einstellungen zu
|
|||||||
msgid "Select all"
|
msgid "Select all"
|
||||||
msgstr "Alle auswählen"
|
msgstr "Alle auswählen"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Select data to include"
|
msgid "Select data to include"
|
||||||
msgstr "Wähle Daten zu inkludieren"
|
msgstr "Wähle Daten zu inkludieren"
|
||||||
|
|
||||||
@@ -2544,7 +2544,7 @@ msgid "Show continue reading button"
|
|||||||
msgstr "Weiterlesen-Knopf anzeigen"
|
msgstr "Weiterlesen-Knopf anzeigen"
|
||||||
|
|
||||||
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Show entry"
|
msgid "Show entry"
|
||||||
msgstr "Eintrag anzeigen"
|
msgstr "Eintrag anzeigen"
|
||||||
|
|
||||||
@@ -2947,7 +2947,7 @@ msgstr "Tracker"
|
|||||||
|
|
||||||
#: src/features/backup/Backup.constants.ts
|
#: src/features/backup/Backup.constants.ts
|
||||||
#: src/features/manga/components/TrackMangaButton.tsx
|
#: src/features/manga/components/TrackMangaButton.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/settings/screens/Settings.tsx
|
#: src/features/settings/screens/Settings.tsx
|
||||||
#: src/features/tracker/screens/TrackingSettings.tsx
|
#: src/features/tracker/screens/TrackingSettings.tsx
|
||||||
msgid "Tracking"
|
msgid "Tracking"
|
||||||
@@ -3551,14 +3551,14 @@ msgstr "Auch von {0} entfernen"
|
|||||||
|
|
||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Category"
|
msgid "Category"
|
||||||
msgstr "Kategorie"
|
msgstr "Kategorie"
|
||||||
|
|
||||||
#: src/features/chapter/components/cards/ChapterCard.tsx
|
#: src/features/chapter/components/cards/ChapterCard.tsx
|
||||||
#: src/features/downloads/components/DownloadAheadSetting.tsx
|
#: src/features/downloads/components/DownloadAheadSetting.tsx
|
||||||
#: src/features/downloads/screens/DownloadSettings.tsx
|
#: src/features/downloads/screens/DownloadSettings.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
||||||
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
||||||
#: src/features/tracker/components/cards/TrackerActiveCard.tsx
|
#: src/features/tracker/components/cards/TrackerActiveCard.tsx
|
||||||
|
|||||||
@@ -58,11 +58,61 @@ msgstr "{0, plural, one {# Source} other {# Sources}}"
|
|||||||
msgid "{0, plural, one {# Tracker} other {# Tracker}}"
|
msgid "{0, plural, one {# Tracker} other {# Tracker}}"
|
||||||
msgstr "{0, plural, one {# Tracker} other {# Tracker}}"
|
msgstr "{0, plural, one {# Tracker} other {# Tracker}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: noMatchEntries.length
|
||||||
|
#: src/features/migration/screens/MigrationExecute.tsx
|
||||||
|
#: src/features/migration/screens/MigrationSearch.tsx
|
||||||
|
msgid "{0, plural, one {1 entry with no match} other {# entries with no match}}"
|
||||||
|
msgstr "{0, plural, one {1 entry with no match} other {# entries with no match}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: excludedEntries.length
|
||||||
|
#: src/features/migration/screens/MigrationExecute.tsx
|
||||||
|
msgid "{0, plural, one {1 excluded entry} other {# excluded entries}}"
|
||||||
|
msgstr "{0, plural, one {1 excluded entry} other {# excluded entries}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: failedEntries.length
|
||||||
|
#: src/features/migration/screens/MigrationExecute.tsx
|
||||||
|
#: src/features/migration/screens/MigrationSearch.tsx
|
||||||
|
msgid "{0, plural, one {1 failed entry} other {# failed entries}}"
|
||||||
|
msgstr "{0, plural, one {1 failed entry} other {# failed entries}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: matchedEntries.length
|
||||||
|
#: src/features/migration/screens/MigrationSearch.tsx
|
||||||
|
msgid "{0, plural, one {1 matched entry} other {# matched entries}}"
|
||||||
|
msgstr "{0, plural, one {1 matched entry} other {# matched entries}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: migratedEntries.length
|
||||||
|
#: src/features/migration/screens/MigrationExecute.tsx
|
||||||
|
msgid "{0, plural, one {1 migrated entry} other {# migrated entries}}"
|
||||||
|
msgstr "{0, plural, one {1 migrated entry} other {# migrated entries}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: migratingEntries.length
|
||||||
|
#: src/features/migration/screens/MigrationExecute.tsx
|
||||||
|
msgid "{0, plural, one {1 migrating entry} other {# migrating entries}}"
|
||||||
|
msgstr "{0, plural, one {1 migrating entry} other {# migrating entries}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: searchingEntries.length
|
||||||
|
#: src/features/migration/screens/MigrationSearch.tsx
|
||||||
|
msgid "{0, plural, one {1 searching} other {# searching}}"
|
||||||
|
msgstr "{0, plural, one {1 searching} other {# searching}}"
|
||||||
|
|
||||||
#. placeholder {0}: autoScroll.value
|
#. placeholder {0}: autoScroll.value
|
||||||
#: src/features/reader/auto-scroll/settings/quick-setting/ReaderNavBarDesktopAutoScroll.tsx
|
#: src/features/reader/auto-scroll/settings/quick-setting/ReaderNavBarDesktopAutoScroll.tsx
|
||||||
msgid "{0, plural, one {Second} other {Seconds}}"
|
msgid "{0, plural, one {Second} other {Seconds}}"
|
||||||
msgstr "{0, plural, one {Second} other {Seconds}}"
|
msgstr "{0, plural, one {Second} other {Seconds}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: searchProgress.completed
|
||||||
|
#. placeholder {1}: searchProgress.total
|
||||||
|
#: src/features/migration/screens/MigrationSearch.tsx
|
||||||
|
msgid "{0} / {1}"
|
||||||
|
msgstr "{0} / {1}"
|
||||||
|
|
||||||
|
#. placeholder {0}: progress.completed
|
||||||
|
#. placeholder {1}: progress.total
|
||||||
|
#. placeholder {2}: progress.failed > 0 ? ` (${progress.failed} failed)` : ''
|
||||||
|
#: src/features/migration/screens/MigrationExecute.tsx
|
||||||
|
msgid "{0} / {1}{2}"
|
||||||
|
msgstr "{0} / {1}{2}"
|
||||||
|
|
||||||
#. placeholder {0}: updateStatus.progress
|
#. placeholder {0}: updateStatus.progress
|
||||||
#: src/features/app-updates/components/WebUIUpdateChecker.tsx
|
#: src/features/app-updates/components/WebUIUpdateChecker.tsx
|
||||||
msgid "{0}% | Updating…"
|
msgid "{0}% | Updating…"
|
||||||
@@ -236,6 +286,11 @@ msgstr "{currentDownloadAheadLimit, plural, one {# Chapter} other {# Chapters}}"
|
|||||||
msgid "{missingChapterCount, plural, one {Missing # chapter} other {Missing # chapters}}"
|
msgid "{missingChapterCount, plural, one {Missing # chapter} other {Missing # chapters}}"
|
||||||
msgstr "{missingChapterCount, plural, one {Missing # chapter} other {Missing # chapters}}"
|
msgstr "{missingChapterCount, plural, one {Missing # chapter} other {Missing # chapters}}"
|
||||||
|
|
||||||
|
#: src/features/migration/components/migration-entry/MigrationDestinationEntry.tsx
|
||||||
|
#: src/features/migration/components/migration-entry/MigrationEntrySearchExcludeActions.tsx
|
||||||
|
msgid "{otherResultsCount, plural, one {# more match} other {# more matches}}"
|
||||||
|
msgstr "{otherResultsCount, plural, one {# more match} other {# more matches}}"
|
||||||
|
|
||||||
#: src/features/app-updates/components/VersionInfo.tsx
|
#: src/features/app-updates/components/VersionInfo.tsx
|
||||||
msgid "{progress}% | Updating…"
|
msgid "{progress}% | Updating…"
|
||||||
msgstr "{progress}% | Updating…"
|
msgstr "{progress}% | Updating…"
|
||||||
@@ -264,6 +319,14 @@ msgstr "A preview focused web frontend built with svelte"
|
|||||||
msgid "A-Z"
|
msgid "A-Z"
|
||||||
msgstr "A-Z"
|
msgstr "A-Z"
|
||||||
|
|
||||||
|
#: src/features/migration/screens/MigrationExecute.tsx
|
||||||
|
msgid "Abort"
|
||||||
|
msgstr "Abort"
|
||||||
|
|
||||||
|
#: src/features/migration/MigrationManager.ts
|
||||||
|
msgid "Abort migration"
|
||||||
|
msgstr "Abort migration"
|
||||||
|
|
||||||
#: src/features/navigation-bar/NavigationBar.constants.ts
|
#: src/features/navigation-bar/NavigationBar.constants.ts
|
||||||
#: src/features/settings/screens/About.tsx
|
#: src/features/settings/screens/About.tsx
|
||||||
msgid "About"
|
msgid "About"
|
||||||
@@ -359,6 +422,10 @@ msgstr "Also remove from {0}"
|
|||||||
msgid "Appearance"
|
msgid "Appearance"
|
||||||
msgstr "Appearance"
|
msgstr "Appearance"
|
||||||
|
|
||||||
|
#: src/features/migration/MigrationManager.ts
|
||||||
|
msgid "Are you sure you want to abort the migration?"
|
||||||
|
msgstr "Are you sure you want to abort the migration?"
|
||||||
|
|
||||||
#: src/features/chapter/services/Chapters.ts
|
#: src/features/chapter/services/Chapters.ts
|
||||||
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
||||||
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
||||||
@@ -442,6 +509,10 @@ msgstr "Automatically refresh metadata"
|
|||||||
msgid "Automatically use webtoon mode for entries that are detected to likely use the long strip format"
|
msgid "Automatically use webtoon mode for entries that are detected to likely use the long strip format"
|
||||||
msgstr "Automatically use webtoon mode for entries that are detected to likely use the long strip format"
|
msgstr "Automatically use webtoon mode for entries that are detected to likely use the long strip format"
|
||||||
|
|
||||||
|
#: src/features/migration/components/MigrationSourceList.tsx
|
||||||
|
msgid "Available"
|
||||||
|
msgstr "Available"
|
||||||
|
|
||||||
#: src/features/backup/screens/Backup.tsx
|
#: src/features/backup/screens/Backup.tsx
|
||||||
msgid "Back up library as a Tachiyomi backup"
|
msgid "Back up library as a Tachiyomi backup"
|
||||||
msgstr "Back up library as a Tachiyomi backup"
|
msgstr "Back up library as a Tachiyomi backup"
|
||||||
@@ -604,7 +675,7 @@ msgstr "Call timeout"
|
|||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/category/components/CategorySelect.tsx
|
#: src/features/category/components/CategorySelect.tsx
|
||||||
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationOptionsDialog.tsx
|
||||||
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
||||||
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
||||||
#: src/features/source/browse/components/SourceOptions.tsx
|
#: src/features/source/browse/components/SourceOptions.tsx
|
||||||
@@ -630,7 +701,7 @@ msgstr "Categories"
|
|||||||
|
|
||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationOptionsDialog.tsx
|
||||||
msgid "Category"
|
msgid "Category"
|
||||||
msgstr "Category"
|
msgstr "Category"
|
||||||
|
|
||||||
@@ -671,7 +742,7 @@ msgstr "Channel"
|
|||||||
#: src/features/chapter/components/cards/ChapterCard.tsx
|
#: src/features/chapter/components/cards/ChapterCard.tsx
|
||||||
#: src/features/downloads/components/DownloadAheadSetting.tsx
|
#: src/features/downloads/components/DownloadAheadSetting.tsx
|
||||||
#: src/features/downloads/screens/DownloadSettings.tsx
|
#: src/features/downloads/screens/DownloadSettings.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationOptionsDialog.tsx
|
||||||
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
||||||
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
||||||
#: src/features/tracker/components/cards/TrackerActiveCard.tsx
|
#: src/features/tracker/components/cards/TrackerActiveCard.tsx
|
||||||
@@ -753,12 +824,13 @@ msgid "Client"
|
|||||||
msgstr "Client"
|
msgstr "Client"
|
||||||
|
|
||||||
#: src/features/backup/Backup.constants.ts
|
#: src/features/backup/Backup.constants.ts
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationOptionsDialog.tsx
|
||||||
msgid "Client data"
|
msgid "Client data"
|
||||||
msgstr "Client data"
|
msgstr "Client data"
|
||||||
|
|
||||||
#: src/base/components/feedback/SnackbarWithDescription.tsx
|
#: src/base/components/feedback/SnackbarWithDescription.tsx
|
||||||
#: src/features/app-updates/components/VersionUpdateInfoDialog.tsx
|
#: src/features/app-updates/components/VersionUpdateInfoDialog.tsx
|
||||||
|
#: src/features/migration/components/migration-entry/MigrationDestinationEntry.tsx
|
||||||
msgid "Close"
|
msgid "Close"
|
||||||
msgstr "Close"
|
msgstr "Close"
|
||||||
|
|
||||||
@@ -812,6 +884,10 @@ msgstr "Connected as {currentUsername} to {currentServerAddress}"
|
|||||||
msgid "Connected as {initialUsername} to {initialServerAddress}"
|
msgid "Connected as {initialUsername} to {initialServerAddress}"
|
||||||
msgstr "Connected as {initialUsername} to {initialServerAddress}"
|
msgstr "Connected as {initialUsername} to {initialServerAddress}"
|
||||||
|
|
||||||
|
#: src/features/migration/components/MigrationContinueButton.tsx
|
||||||
|
msgid "Continue"
|
||||||
|
msgstr "Continue"
|
||||||
|
|
||||||
#: src/features/manga/components/ContinueReadingTooltip.tsx
|
#: src/features/manga/components/ContinueReadingTooltip.tsx
|
||||||
msgid ""
|
msgid ""
|
||||||
"Continue reading\n"
|
"Continue reading\n"
|
||||||
@@ -853,7 +929,7 @@ msgid "Copied to clipboard"
|
|||||||
msgstr "Copied to clipboard"
|
msgstr "Copied to clipboard"
|
||||||
|
|
||||||
#: src/features/manga/components/details/MangaDetails.tsx
|
#: src/features/manga/components/details/MangaDetails.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationOptionsDialog.tsx
|
||||||
msgid "Copy"
|
msgid "Copy"
|
||||||
msgstr "Copy"
|
msgstr "Copy"
|
||||||
|
|
||||||
@@ -1067,6 +1143,10 @@ msgstr "Creating theme…"
|
|||||||
msgid "Crimson"
|
msgid "Crimson"
|
||||||
msgstr "Crimson"
|
msgstr "Crimson"
|
||||||
|
|
||||||
|
#: src/features/migration/components/MigrationSourceList.tsx
|
||||||
|
msgid "Current source"
|
||||||
|
msgstr "Current source"
|
||||||
|
|
||||||
#: src/features/reader/viewer/components/ReaderTransitionPage.tsx
|
#: src/features/reader/viewer/components/ReaderTransitionPage.tsx
|
||||||
msgid "Current:"
|
msgid "Current:"
|
||||||
msgstr "Current:"
|
msgstr "Current:"
|
||||||
@@ -1193,7 +1273,7 @@ msgstr "Delete chapter after manually marking it as read"
|
|||||||
msgid "Delete chapters"
|
msgid "Delete chapters"
|
||||||
msgstr "Delete chapters"
|
msgstr "Delete chapters"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationOptionsDialog.tsx
|
||||||
msgid "Delete downloaded"
|
msgid "Delete downloaded"
|
||||||
msgstr "Delete downloaded"
|
msgstr "Delete downloaded"
|
||||||
|
|
||||||
@@ -1233,6 +1313,11 @@ msgstr "Deselect"
|
|||||||
msgid "Desktop"
|
msgid "Desktop"
|
||||||
msgstr "Desktop"
|
msgstr "Desktop"
|
||||||
|
|
||||||
|
#. placeholder {0}: entry.sourceTitle
|
||||||
|
#: src/features/migration/components/migration-entry/MigrationDestinationEntry.tsx
|
||||||
|
msgid "Destination - {0}"
|
||||||
|
msgstr "Destination - {0}"
|
||||||
|
|
||||||
#: src/features/device/screens/DeviceSetting.tsx
|
#: src/features/device/screens/DeviceSetting.tsx
|
||||||
#: src/features/settings/screens/Settings.tsx
|
#: src/features/settings/screens/Settings.tsx
|
||||||
msgid "Device"
|
msgid "Device"
|
||||||
@@ -1292,6 +1377,10 @@ msgstr "Does not work when marking multiple manga as read at once"
|
|||||||
msgid "Don't show this dialog again"
|
msgid "Don't show this dialog again"
|
||||||
msgstr "Don't show this dialog again"
|
msgstr "Don't show this dialog again"
|
||||||
|
|
||||||
|
#: src/features/migration/screens/MigrationExecute.tsx
|
||||||
|
msgid "Done"
|
||||||
|
msgstr "Done"
|
||||||
|
|
||||||
#: src/features/reader/settings/ReaderSettings.constants.tsx
|
#: src/features/reader/settings/ReaderSettings.constants.tsx
|
||||||
msgid "Double page"
|
msgid "Double page"
|
||||||
msgstr "Double page"
|
msgstr "Double page"
|
||||||
@@ -1352,6 +1441,10 @@ msgstr "Downloading"
|
|||||||
msgid "Downloads"
|
msgid "Downloads"
|
||||||
msgstr "Downloads"
|
msgstr "Downloads"
|
||||||
|
|
||||||
|
#: src/features/migration/components/MigrationSourceList.tsx
|
||||||
|
msgid "Drag to prioritize"
|
||||||
|
msgstr "Drag to prioritize"
|
||||||
|
|
||||||
#: src/features/theme/Themes.ts
|
#: src/features/theme/Themes.ts
|
||||||
msgid "Dune"
|
msgid "Dune"
|
||||||
msgstr "Dune"
|
msgstr "Dune"
|
||||||
@@ -1431,6 +1524,11 @@ msgstr "Error"
|
|||||||
msgid "Example for possible values: 1 (bytes), 1KB (kilobytes), 1MB (megabytes), 1GB (gigabytes)"
|
msgid "Example for possible values: 1 (bytes), 1KB (kilobytes), 1MB (megabytes), 1GB (gigabytes)"
|
||||||
msgstr "Example for possible values: 1 (bytes), 1KB (kilobytes), 1MB (megabytes), 1GB (gigabytes)"
|
msgstr "Example for possible values: 1 (bytes), 1KB (kilobytes), 1MB (megabytes), 1GB (gigabytes)"
|
||||||
|
|
||||||
|
#: src/features/migration/components/migration-entry/MigrationEntrySearchExcludeActions.tsx
|
||||||
|
#: src/features/migration/components/migration-entry/MigrationEntrySearchExcludeActions.tsx
|
||||||
|
msgid "Exclude"
|
||||||
|
msgstr "Exclude"
|
||||||
|
|
||||||
#: src/features/chapter/components/ChapterExcludeSanlatorsFilter.tsx
|
#: src/features/chapter/components/ChapterExcludeSanlatorsFilter.tsx
|
||||||
msgid "Exclude scanlators"
|
msgid "Exclude scanlators"
|
||||||
msgstr "Exclude scanlators"
|
msgstr "Exclude scanlators"
|
||||||
@@ -1440,6 +1538,10 @@ msgstr "Exclude scanlators"
|
|||||||
msgid "Exclude: {excludedCategoriesText}"
|
msgid "Exclude: {excludedCategoriesText}"
|
||||||
msgstr "Exclude: {excludedCategoriesText}"
|
msgstr "Exclude: {excludedCategoriesText}"
|
||||||
|
|
||||||
|
#: src/features/migration/Migration.constants.ts
|
||||||
|
msgid "Excluded"
|
||||||
|
msgstr "Excluded"
|
||||||
|
|
||||||
#: src/features/reader/hotkeys/settings/components/ReaderSettingHotkey.tsx
|
#: src/features/reader/hotkeys/settings/components/ReaderSettingHotkey.tsx
|
||||||
#: src/features/reader/overlay/navigation/components/ReaderExitButton.tsx
|
#: src/features/reader/overlay/navigation/components/ReaderExitButton.tsx
|
||||||
msgid "Exit reader"
|
msgid "Exit reader"
|
||||||
@@ -1495,7 +1597,7 @@ msgstr "Failed to disconnect from KOReader Sync server."
|
|||||||
#: src/features/global-search/screens/SearchAll.tsx
|
#: src/features/global-search/screens/SearchAll.tsx
|
||||||
#: src/features/history/screens/HistorySettings.tsx
|
#: src/features/history/screens/HistorySettings.tsx
|
||||||
#: src/features/library/components/LibraryOptionsPanel.tsx
|
#: src/features/library/components/LibraryOptionsPanel.tsx
|
||||||
#: src/features/migration/screens/Migration.tsx
|
#: src/features/migration/screens/MigrationSelectSource.tsx
|
||||||
#: src/features/settings/components/globalUpdate/GlobalUpdateSettings.tsx
|
#: src/features/settings/components/globalUpdate/GlobalUpdateSettings.tsx
|
||||||
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
||||||
#: src/features/settings/components/webUI/WebUIUpdateIntervalSetting.tsx
|
#: src/features/settings/components/webUI/WebUIUpdateIntervalSetting.tsx
|
||||||
@@ -1742,10 +1844,6 @@ msgstr "How many chapters should get downloaded while reading."
|
|||||||
msgid "How much time FlareSolverr has to handle the request"
|
msgid "How much time FlareSolverr has to handle the request"
|
||||||
msgstr "How much time FlareSolverr has to handle the request"
|
msgstr "How much time FlareSolverr has to handle the request"
|
||||||
|
|
||||||
#: src/features/settings/screens/ServerSettings.tsx
|
|
||||||
msgid "I understand"
|
|
||||||
msgstr "I understand"
|
|
||||||
|
|
||||||
#: src/features/app-updates/components/VersionUpdateInfoDialog.tsx
|
#: src/features/app-updates/components/VersionUpdateInfoDialog.tsx
|
||||||
msgid "Ignore"
|
msgid "Ignore"
|
||||||
msgstr "Ignore"
|
msgstr "Ignore"
|
||||||
@@ -1803,6 +1901,11 @@ msgstr ""
|
|||||||
msgid "In Library"
|
msgid "In Library"
|
||||||
msgstr "In Library"
|
msgstr "In Library"
|
||||||
|
|
||||||
|
#: src/features/migration/components/migration-entry/MigrationEntrySearchExcludeActions.tsx
|
||||||
|
#: src/features/migration/components/migration-entry/MigrationEntrySearchExcludeActions.tsx
|
||||||
|
msgid "Include"
|
||||||
|
msgstr "Include"
|
||||||
|
|
||||||
#: src/features/backup/screens/Backup.tsx
|
#: src/features/backup/screens/Backup.tsx
|
||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
msgid "Include: {includedCategoriesText}"
|
msgid "Include: {includedCategoriesText}"
|
||||||
@@ -1988,6 +2091,10 @@ msgstr "Latest fetched chapter"
|
|||||||
msgid "Latest uploaded chapter"
|
msgid "Latest uploaded chapter"
|
||||||
msgstr "Latest uploaded chapter"
|
msgstr "Latest uploaded chapter"
|
||||||
|
|
||||||
|
#: src/features/migration/components/migration-entry/MigrationEntryMetadataText.tsx
|
||||||
|
msgid "Latest: {latestChapterNumber}"
|
||||||
|
msgstr "Latest: {latestChapterNumber}"
|
||||||
|
|
||||||
#: src/features/theme/Themes.ts
|
#: src/features/theme/Themes.ts
|
||||||
msgid "Lavender"
|
msgid "Lavender"
|
||||||
msgstr "Lavender"
|
msgstr "Lavender"
|
||||||
@@ -2150,6 +2257,13 @@ msgstr "Manhua"
|
|||||||
msgid "Manhwa"
|
msgid "Manhwa"
|
||||||
msgstr "Manhwa"
|
msgstr "Manhwa"
|
||||||
|
|
||||||
|
#: src/features/migration/components/migration-entry/MigrationDestinationEntry.tsx
|
||||||
|
#: src/features/migration/components/migration-entry/MigrationEntry.tsx
|
||||||
|
#: src/features/migration/components/migration-entry/MigrationEntrySearchExcludeActions.tsx
|
||||||
|
#: src/features/migration/components/migration-entry/MigrationEntrySearchExcludeActions.tsx
|
||||||
|
msgid "Manual search"
|
||||||
|
msgstr "Manual search"
|
||||||
|
|
||||||
#: src/features/chapter/components/ChaptersToolbarMenu.tsx
|
#: src/features/chapter/components/ChaptersToolbarMenu.tsx
|
||||||
msgid "Mark all as read"
|
msgid "Mark all as read"
|
||||||
msgstr "Mark all as read"
|
msgstr "Mark all as read"
|
||||||
@@ -2178,6 +2292,15 @@ msgstr "Mark selected as read"
|
|||||||
msgid "Mark selected as unread"
|
msgid "Mark selected as unread"
|
||||||
msgstr "Mark selected as unread"
|
msgstr "Mark selected as unread"
|
||||||
|
|
||||||
|
#: src/features/migration/Migration.constants.ts
|
||||||
|
msgid "Match found"
|
||||||
|
msgstr "Match found"
|
||||||
|
|
||||||
|
#: src/features/migration/components/migration-entry/MigrationEntry.tsx
|
||||||
|
#: src/features/migration/components/migration-entry/MigrationEntry.tsx
|
||||||
|
msgid "Matches"
|
||||||
|
msgstr "Matches"
|
||||||
|
|
||||||
#: src/features/settings/screens/ServerSettings.tsx
|
#: src/features/settings/screens/ServerSettings.tsx
|
||||||
msgid "Maximum log file size"
|
msgid "Maximum log file size"
|
||||||
msgstr "Maximum log file size"
|
msgstr "Maximum log file size"
|
||||||
@@ -2199,8 +2322,8 @@ msgstr "Menu"
|
|||||||
#: src/features/manga/components/MangaToolbarMenu.tsx
|
#: src/features/manga/components/MangaToolbarMenu.tsx
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationOptionsDialog.tsx
|
||||||
#: src/features/migration/screens/Migrate.tsx
|
#: src/features/migration/screens/MigrationSelectMangas.tsx
|
||||||
msgid "Migrate"
|
msgid "Migrate"
|
||||||
msgstr "Migrate"
|
msgstr "Migrate"
|
||||||
|
|
||||||
@@ -2209,10 +2332,39 @@ msgstr "Migrate"
|
|||||||
msgid "Migrate \"{0}\""
|
msgid "Migrate \"{0}\""
|
||||||
msgstr "Migrate \"{0}\""
|
msgstr "Migrate \"{0}\""
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/screens/MigrationExecute.tsx
|
||||||
|
msgid "Migrating"
|
||||||
|
msgstr "Migrating"
|
||||||
|
|
||||||
|
#: src/features/manga/components/cards/MangaCard.tsx
|
||||||
msgid "Migrating manga…"
|
msgid "Migrating manga…"
|
||||||
msgstr "Migrating manga…"
|
msgstr "Migrating manga…"
|
||||||
|
|
||||||
|
#: src/features/migration/Migration.constants.ts
|
||||||
|
msgid "Migrating…"
|
||||||
|
msgstr "Migrating…"
|
||||||
|
|
||||||
|
#: src/features/migration/screens/MigrationExecute.tsx
|
||||||
|
msgid "Migration complete"
|
||||||
|
msgstr "Migration complete"
|
||||||
|
|
||||||
|
#: src/features/migration/components/migration-entry/MigrationDestinationEntry.tsx
|
||||||
|
#: src/features/migration/Migration.constants.ts
|
||||||
|
msgid "Migration failed"
|
||||||
|
msgstr "Migration failed"
|
||||||
|
|
||||||
|
#: src/features/migration/components/migration-entry/MigrationDestinationEntry.tsx
|
||||||
|
msgid "Migration for \"{title}\" failed with error:"
|
||||||
|
msgstr "Migration for \"{title}\" failed with error:"
|
||||||
|
|
||||||
|
#: src/features/migration/MigrationManager.ts
|
||||||
|
msgid "Migration information"
|
||||||
|
msgstr "Migration information"
|
||||||
|
|
||||||
|
#: src/features/migration/components/MigrationOptionsDialog.tsx
|
||||||
|
msgid "Migration options"
|
||||||
|
msgstr "Migration options"
|
||||||
|
|
||||||
#: src/features/settings/components/images/Processing.tsx
|
#: src/features/settings/components/images/Processing.tsx
|
||||||
msgid "MIME-Type"
|
msgid "MIME-Type"
|
||||||
msgstr "MIME-Type"
|
msgstr "MIME-Type"
|
||||||
@@ -2314,6 +2466,10 @@ msgstr "No manga found"
|
|||||||
msgid "No manga matches this filter"
|
msgid "No manga matches this filter"
|
||||||
msgstr "No manga matches this filter"
|
msgstr "No manga matches this filter"
|
||||||
|
|
||||||
|
#: src/features/migration/Migration.constants.ts
|
||||||
|
msgid "No match found"
|
||||||
|
msgstr "No match found"
|
||||||
|
|
||||||
#: src/features/reader/viewer/ReaderChapterViewer.tsx
|
#: src/features/reader/viewer/ReaderChapterViewer.tsx
|
||||||
msgid "No pages found"
|
msgid "No pages found"
|
||||||
msgstr "No pages found"
|
msgstr "No pages found"
|
||||||
@@ -2497,6 +2653,10 @@ msgstr "Password"
|
|||||||
msgid "Paused — {count} remaining"
|
msgid "Paused — {count} remaining"
|
||||||
msgstr "Paused — {count} remaining"
|
msgstr "Paused — {count} remaining"
|
||||||
|
|
||||||
|
#: src/features/migration/Migration.constants.ts
|
||||||
|
msgid "Pending…"
|
||||||
|
msgstr "Pending…"
|
||||||
|
|
||||||
#: src/features/settings/components/koreaderSync/KoreaderSyncSettings.tsx
|
#: src/features/settings/components/koreaderSync/KoreaderSyncSettings.tsx
|
||||||
msgid "Percentage Tolerance"
|
msgid "Percentage Tolerance"
|
||||||
msgstr "Percentage Tolerance"
|
msgstr "Percentage Tolerance"
|
||||||
@@ -2735,6 +2895,7 @@ msgstr "Resume"
|
|||||||
#: src/base/components/SpinnerImage.tsx
|
#: src/base/components/SpinnerImage.tsx
|
||||||
#: src/features/chapter/components/buttons/ChapterDownloadRetryButton.tsx
|
#: src/features/chapter/components/buttons/ChapterDownloadRetryButton.tsx
|
||||||
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
||||||
|
#: src/features/migration/components/migration-entry/MigrationDestinationEntry.tsx
|
||||||
msgid "Retry"
|
msgid "Retry"
|
||||||
msgstr "Retry"
|
msgstr "Retry"
|
||||||
|
|
||||||
@@ -2825,10 +2986,19 @@ msgstr "Scroll forward"
|
|||||||
msgid "Search"
|
msgid "Search"
|
||||||
msgstr "Search"
|
msgstr "Search"
|
||||||
|
|
||||||
|
#: src/features/migration/components/migration-entry/MigrationDestinationEntry.tsx
|
||||||
|
#: src/features/migration/Migration.constants.ts
|
||||||
|
msgid "Search failed"
|
||||||
|
msgstr "Search failed"
|
||||||
|
|
||||||
#: src/features/library/screens/Library.tsx
|
#: src/features/library/screens/Library.tsx
|
||||||
msgid "Search for \"{query}\" globally"
|
msgid "Search for \"{query}\" globally"
|
||||||
msgstr "Search for \"{query}\" globally"
|
msgstr "Search for \"{query}\" globally"
|
||||||
|
|
||||||
|
#: src/features/migration/components/migration-entry/MigrationDestinationEntry.tsx
|
||||||
|
msgid "Search for \"{title}\" failed with error:"
|
||||||
|
msgstr "Search for \"{title}\" failed with error:"
|
||||||
|
|
||||||
#: src/features/tracker/components/TrackerSearch.tsx
|
#: src/features/tracker/components/TrackerSearch.tsx
|
||||||
msgid ""
|
msgid ""
|
||||||
"Search for a ID via \"id:<ID>\" (e.g. \"id:13\")\n"
|
"Search for a ID via \"id:<ID>\" (e.g. \"id:13\")\n"
|
||||||
@@ -2846,10 +3016,18 @@ msgstr "Search parameters"
|
|||||||
msgid "Search parameters ({0})"
|
msgid "Search parameters ({0})"
|
||||||
msgstr "Search parameters ({0})"
|
msgstr "Search parameters ({0})"
|
||||||
|
|
||||||
|
#: src/features/migration/screens/MigrationSearch.tsx
|
||||||
|
msgid "Search results"
|
||||||
|
msgstr "Search results"
|
||||||
|
|
||||||
#: src/features/library/screens/LibrarySettings.tsx
|
#: src/features/library/screens/LibrarySettings.tsx
|
||||||
msgid "Search results will include manga that do not match the current filters"
|
msgid "Search results will include manga that do not match the current filters"
|
||||||
msgstr "Search results will include manga that do not match the current filters"
|
msgstr "Search results will include manga that do not match the current filters"
|
||||||
|
|
||||||
|
#: src/features/migration/Migration.constants.ts
|
||||||
|
msgid "Searching…"
|
||||||
|
msgstr "Searching…"
|
||||||
|
|
||||||
#: src/features/settings/components/images/Processing.tsx
|
#: src/features/settings/components/images/Processing.tsx
|
||||||
#: src/features/settings/components/images/Processing.tsx
|
#: src/features/settings/components/images/Processing.tsx
|
||||||
#: src/features/settings/screens/ServerSettings.tsx
|
#: src/features/settings/screens/ServerSettings.tsx
|
||||||
@@ -2872,6 +3050,7 @@ msgstr "See the official <0>MUI documentation</0> for how to customize the theme
|
|||||||
#: src/features/chapter/components/cards/ChapterCard.tsx
|
#: src/features/chapter/components/cards/ChapterCard.tsx
|
||||||
#: src/features/manga/components/MangaActionMenuItems.tsx
|
#: src/features/manga/components/MangaActionMenuItems.tsx
|
||||||
#: src/features/manga/components/MangaOptionButton.tsx
|
#: src/features/manga/components/MangaOptionButton.tsx
|
||||||
|
#: src/features/migration/components/migration-entry/MigrationMatchedEntry.tsx
|
||||||
msgid "Select"
|
msgid "Select"
|
||||||
msgstr "Select"
|
msgstr "Select"
|
||||||
|
|
||||||
@@ -2885,9 +3064,21 @@ msgstr "Select a device to use its server stored UI settings"
|
|||||||
msgid "Select all"
|
msgid "Select all"
|
||||||
msgstr "Select all"
|
msgstr "Select all"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/screens/MigrationSelectDestinationSources.tsx
|
||||||
msgid "Select data to include"
|
msgid "Select destination sources"
|
||||||
msgstr "Select data to include"
|
msgstr "Select destination sources"
|
||||||
|
|
||||||
|
#: src/features/migration/screens/MigrationSelectDestinationSources.tsx
|
||||||
|
msgid "Select enabled sources"
|
||||||
|
msgstr "Select enabled sources"
|
||||||
|
|
||||||
|
#: src/features/migration/screens/MigrationSelectDestinationSources.tsx
|
||||||
|
msgid "Select pinned sources"
|
||||||
|
msgstr "Select pinned sources"
|
||||||
|
|
||||||
|
#: src/features/migration/components/MigrationSourceList.tsx
|
||||||
|
msgid "Selected"
|
||||||
|
msgstr "Selected"
|
||||||
|
|
||||||
#: src/features/reader/filters/settings/components/ReaderSettingSepia.tsx
|
#: src/features/reader/filters/settings/components/ReaderSettingSepia.tsx
|
||||||
msgid "Sepia"
|
msgid "Sepia"
|
||||||
@@ -2976,7 +3167,7 @@ msgid "Show continue reading button"
|
|||||||
msgstr "Show continue reading button"
|
msgstr "Show continue reading button"
|
||||||
|
|
||||||
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationOptionsDialog.tsx
|
||||||
msgid "Show entry"
|
msgid "Show entry"
|
||||||
msgstr "Show entry"
|
msgstr "Show entry"
|
||||||
|
|
||||||
@@ -2992,6 +3183,7 @@ msgstr "Show less"
|
|||||||
#: src/base/components/feedback/EmptyView.tsx
|
#: src/base/components/feedback/EmptyView.tsx
|
||||||
#: src/base/components/feedback/SnackbarWithDescription.tsx
|
#: src/base/components/feedback/SnackbarWithDescription.tsx
|
||||||
#: src/features/global-search/screens/SearchAll.tsx
|
#: src/features/global-search/screens/SearchAll.tsx
|
||||||
|
#: src/features/migration/components/migration-entry/MigrationDestinationEntry.tsx
|
||||||
#: src/features/tracker/components/cards/TrackerMangaCard.tsx
|
#: src/features/tracker/components/cards/TrackerMangaCard.tsx
|
||||||
msgid "Show more"
|
msgid "Show more"
|
||||||
msgstr "Show more"
|
msgstr "Show more"
|
||||||
@@ -3126,6 +3318,11 @@ msgstr "Source"
|
|||||||
msgid "Source Configuration"
|
msgid "Source Configuration"
|
||||||
msgstr "Source Configuration"
|
msgstr "Source Configuration"
|
||||||
|
|
||||||
|
#: src/features/migration/components/migration-entry/MigrationSourceEntry.tsx
|
||||||
|
#: src/features/migration/components/migration-entry/MigrationSourceEntry.tsx
|
||||||
|
msgid "Source entry - {sourceTitle}"
|
||||||
|
msgstr "Source entry - {sourceTitle}"
|
||||||
|
|
||||||
#: src/features/chapter/components/ChapterOptions.tsx
|
#: src/features/chapter/components/ChapterOptions.tsx
|
||||||
msgid "Source title"
|
msgid "Source title"
|
||||||
msgstr "Source title"
|
msgstr "Source title"
|
||||||
@@ -3147,6 +3344,10 @@ msgstr "Start"
|
|||||||
msgid "Start date"
|
msgid "Start date"
|
||||||
msgstr "Start date"
|
msgstr "Start date"
|
||||||
|
|
||||||
|
#: src/features/migration/screens/MigrationSearch.tsx
|
||||||
|
msgid "Start migration"
|
||||||
|
msgstr "Start migration"
|
||||||
|
|
||||||
#: src/features/manga/components/ContinueReadingTooltip.tsx
|
#: src/features/manga/components/ContinueReadingTooltip.tsx
|
||||||
msgid ""
|
msgid ""
|
||||||
"Start reading\n"
|
"Start reading\n"
|
||||||
@@ -3157,6 +3358,10 @@ msgstr ""
|
|||||||
"#{chapterNumber} — {name}\n"
|
"#{chapterNumber} — {name}\n"
|
||||||
"{scanlator}"
|
"{scanlator}"
|
||||||
|
|
||||||
|
#: src/features/migration/screens/MigrationSelectDestinationSources.tsx
|
||||||
|
msgid "Start Search"
|
||||||
|
msgstr "Start Search"
|
||||||
|
|
||||||
#: src/features/library/components/LibraryOptionsPanel.tsx
|
#: src/features/library/components/LibraryOptionsPanel.tsx
|
||||||
#: src/features/tracker/components/cards/TrackerMangaCard.tsx
|
#: src/features/tracker/components/cards/TrackerMangaCard.tsx
|
||||||
msgid "Started"
|
msgid "Started"
|
||||||
@@ -3195,6 +3400,10 @@ msgstr "Submit"
|
|||||||
msgid "Success"
|
msgid "Success"
|
||||||
msgstr "Success"
|
msgstr "Success"
|
||||||
|
|
||||||
|
#: src/features/migration/Migration.constants.ts
|
||||||
|
msgid "Successfully migrated"
|
||||||
|
msgstr "Successfully migrated"
|
||||||
|
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
msgid "Successfully migrated manga"
|
msgid "Successfully migrated manga"
|
||||||
msgstr "Successfully migrated manga"
|
msgstr "Successfully migrated manga"
|
||||||
@@ -3264,6 +3473,16 @@ msgstr "The login will be handled by the client."
|
|||||||
msgid "The login will be handled by the server."
|
msgid "The login will be handled by the server."
|
||||||
msgstr "The login will be handled by the server."
|
msgstr "The login will be handled by the server."
|
||||||
|
|
||||||
|
#: src/features/migration/MigrationManager.ts
|
||||||
|
msgid ""
|
||||||
|
"The migration runs on the client on the current device, NOT the server.\n"
|
||||||
|
"As long as the client is open, the migration will run in the background.\n"
|
||||||
|
"The client can be closed. The migration will be resumed once it gets opened again on the same device it got started on."
|
||||||
|
msgstr ""
|
||||||
|
"The migration runs on the client on the current device, NOT the server.\n"
|
||||||
|
"As long as the client is open, the migration will run in the background.\n"
|
||||||
|
"The client can be closed. The migration will be resumed once it gets opened again on the same device it got started on."
|
||||||
|
|
||||||
#: src/features/reader/services/ReaderControls.ts
|
#: src/features/reader/services/ReaderControls.ts
|
||||||
msgid ""
|
msgid ""
|
||||||
"The next chapter has a different scanlator then the current one.\n"
|
"The next chapter has a different scanlator then the current one.\n"
|
||||||
@@ -3408,7 +3627,7 @@ msgstr "Trackers"
|
|||||||
|
|
||||||
#: src/features/backup/Backup.constants.ts
|
#: src/features/backup/Backup.constants.ts
|
||||||
#: src/features/manga/components/TrackMangaButton.tsx
|
#: src/features/manga/components/TrackMangaButton.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationOptionsDialog.tsx
|
||||||
#: src/features/settings/screens/Settings.tsx
|
#: src/features/settings/screens/Settings.tsx
|
||||||
#: src/features/tracker/screens/TrackingSettings.tsx
|
#: src/features/tracker/screens/TrackingSettings.tsx
|
||||||
msgid "Tracking"
|
msgid "Tracking"
|
||||||
@@ -3444,8 +3663,8 @@ msgstr "UI Login"
|
|||||||
#: src/features/library/screens/LibrarySettings.tsx
|
#: src/features/library/screens/LibrarySettings.tsx
|
||||||
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
||||||
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
||||||
#: src/features/migration/screens/Migrate.tsx
|
#: src/features/migration/screens/MigrationSelectMangas.tsx
|
||||||
#: src/features/migration/screens/Migration.tsx
|
#: src/features/migration/screens/MigrationSelectSource.tsx
|
||||||
#: src/features/reader/screens/Reader.tsx
|
#: src/features/reader/screens/Reader.tsx
|
||||||
#: src/features/reader/settings/screens/GlobalReaderSettings.tsx
|
#: src/features/reader/settings/screens/GlobalReaderSettings.tsx
|
||||||
#: src/features/reader/viewer/ReaderChapterViewer.tsx
|
#: src/features/reader/viewer/ReaderChapterViewer.tsx
|
||||||
@@ -3465,6 +3684,15 @@ msgstr "UI Login"
|
|||||||
msgid "Unable to load data"
|
msgid "Unable to load data"
|
||||||
msgstr "Unable to load data"
|
msgstr "Unable to load data"
|
||||||
|
|
||||||
|
#: src/features/migration/screens/MigrationSelectDestinationSources.tsx
|
||||||
|
msgid "Unable to load sources"
|
||||||
|
msgstr "Unable to load sources"
|
||||||
|
|
||||||
|
#: src/features/migration/MigrationManager.ts
|
||||||
|
#: src/features/settings/screens/ServerSettings.tsx
|
||||||
|
msgid "Understood"
|
||||||
|
msgstr "Understood"
|
||||||
|
|
||||||
#: src/features/extension/Extensions.constants.ts
|
#: src/features/extension/Extensions.constants.ts
|
||||||
msgid "Uninstall"
|
msgid "Uninstall"
|
||||||
msgstr "Uninstall"
|
msgstr "Uninstall"
|
||||||
@@ -3475,6 +3703,7 @@ msgstr "Uninstalling"
|
|||||||
|
|
||||||
#: src/features/manga/components/details/MangaDetails.tsx
|
#: src/features/manga/components/details/MangaDetails.tsx
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
|
#: src/features/migration/components/migration-entry/MigrationEntryMetadataText.tsx
|
||||||
#: src/features/settings/components/koreaderSync/KoreaderSyncSettings.tsx
|
#: src/features/settings/components/koreaderSync/KoreaderSyncSettings.tsx
|
||||||
#: src/features/tracker/Tracker.constants.ts
|
#: src/features/tracker/Tracker.constants.ts
|
||||||
msgid "Unknown"
|
msgid "Unknown"
|
||||||
|
|||||||
@@ -370,7 +370,7 @@ msgstr "Por fecha de subida"
|
|||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/category/components/CategorySelect.tsx
|
#: src/features/category/components/CategorySelect.tsx
|
||||||
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
||||||
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
||||||
#: src/features/source/browse/components/SourceOptions.tsx
|
#: src/features/source/browse/components/SourceOptions.tsx
|
||||||
@@ -562,7 +562,7 @@ msgid "Copied to clipboard"
|
|||||||
msgstr "Copiado al portapapeles"
|
msgstr "Copiado al portapapeles"
|
||||||
|
|
||||||
#: src/features/manga/components/details/MangaDetails.tsx
|
#: src/features/manga/components/details/MangaDetails.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Copy"
|
msgid "Copy"
|
||||||
msgstr "Copiar"
|
msgstr "Copiar"
|
||||||
|
|
||||||
@@ -848,7 +848,7 @@ msgstr "Borrar el capítulo después de marcarlo manualmente como leído"
|
|||||||
msgid "Delete chapters"
|
msgid "Delete chapters"
|
||||||
msgstr "Borrar los capítulos"
|
msgstr "Borrar los capítulos"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Delete downloaded"
|
msgid "Delete downloaded"
|
||||||
msgstr "Eliminar descargados"
|
msgstr "Eliminar descargados"
|
||||||
|
|
||||||
@@ -1770,12 +1770,12 @@ msgstr "Menú"
|
|||||||
#: src/features/manga/components/MangaToolbarMenu.tsx
|
#: src/features/manga/components/MangaToolbarMenu.tsx
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/migration/screens/Migrate.tsx
|
#: src/features/migration/screens/Migrate.tsx
|
||||||
msgid "Migrate"
|
msgid "Migrate"
|
||||||
msgstr "Migración"
|
msgstr "Migración"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Migrating manga…"
|
msgid "Migrating manga…"
|
||||||
msgstr "Migrando datos…"
|
msgstr "Migrando datos…"
|
||||||
|
|
||||||
@@ -2418,7 +2418,7 @@ msgstr "Selecciona un dispositivo para usar su configuración de interfaz almace
|
|||||||
msgid "Select all"
|
msgid "Select all"
|
||||||
msgstr "Seleccionar todo"
|
msgstr "Seleccionar todo"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Select data to include"
|
msgid "Select data to include"
|
||||||
msgstr "Seleccionar datos a incluir"
|
msgstr "Seleccionar datos a incluir"
|
||||||
|
|
||||||
@@ -2498,7 +2498,7 @@ msgid "Show continue reading button"
|
|||||||
msgstr "Mostrar botón de seguir leyendo"
|
msgstr "Mostrar botón de seguir leyendo"
|
||||||
|
|
||||||
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Show entry"
|
msgid "Show entry"
|
||||||
msgstr "Mostrar entrada"
|
msgstr "Mostrar entrada"
|
||||||
|
|
||||||
@@ -2899,7 +2899,7 @@ msgstr "Rastreadores"
|
|||||||
|
|
||||||
#: src/features/backup/Backup.constants.ts
|
#: src/features/backup/Backup.constants.ts
|
||||||
#: src/features/manga/components/TrackMangaButton.tsx
|
#: src/features/manga/components/TrackMangaButton.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/settings/screens/Settings.tsx
|
#: src/features/settings/screens/Settings.tsx
|
||||||
#: src/features/tracker/screens/TrackingSettings.tsx
|
#: src/features/tracker/screens/TrackingSettings.tsx
|
||||||
msgid "Tracking"
|
msgid "Tracking"
|
||||||
|
|||||||
@@ -373,7 +373,7 @@ msgstr "محدودیت زمانی فراخوانی"
|
|||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/category/components/CategorySelect.tsx
|
#: src/features/category/components/CategorySelect.tsx
|
||||||
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
||||||
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
||||||
#: src/features/source/browse/components/SourceOptions.tsx
|
#: src/features/source/browse/components/SourceOptions.tsx
|
||||||
@@ -586,7 +586,7 @@ msgid "Copied to clipboard"
|
|||||||
msgstr "در کلیپ بورد کپی شد"
|
msgstr "در کلیپ بورد کپی شد"
|
||||||
|
|
||||||
#: src/features/manga/components/details/MangaDetails.tsx
|
#: src/features/manga/components/details/MangaDetails.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Copy"
|
msgid "Copy"
|
||||||
msgstr "کپی"
|
msgstr "کپی"
|
||||||
|
|
||||||
@@ -872,7 +872,7 @@ msgstr "حذف چپتر بعد از علامت گذاری دستی به ع
|
|||||||
msgid "Delete chapters"
|
msgid "Delete chapters"
|
||||||
msgstr "حذف چپتر ها"
|
msgstr "حذف چپتر ها"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Delete downloaded"
|
msgid "Delete downloaded"
|
||||||
msgstr "حذف دانلودشدهها"
|
msgstr "حذف دانلودشدهها"
|
||||||
|
|
||||||
@@ -1807,12 +1807,12 @@ msgstr "منو"
|
|||||||
#: src/features/manga/components/MangaToolbarMenu.tsx
|
#: src/features/manga/components/MangaToolbarMenu.tsx
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/migration/screens/Migrate.tsx
|
#: src/features/migration/screens/Migrate.tsx
|
||||||
msgid "Migrate"
|
msgid "Migrate"
|
||||||
msgstr "انتقال"
|
msgstr "انتقال"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Migrating manga…"
|
msgid "Migrating manga…"
|
||||||
msgstr "در حال انتقال مانگا…"
|
msgstr "در حال انتقال مانگا…"
|
||||||
|
|
||||||
@@ -2439,7 +2439,7 @@ msgstr "دستگاهی را برای استفاده از تنظیمات رابط
|
|||||||
msgid "Select all"
|
msgid "Select all"
|
||||||
msgstr "انتخاب همه"
|
msgstr "انتخاب همه"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Select data to include"
|
msgid "Select data to include"
|
||||||
msgstr "انتخاب داده برای لحاظ شدن"
|
msgstr "انتخاب داده برای لحاظ شدن"
|
||||||
|
|
||||||
@@ -2519,7 +2519,7 @@ msgid "Show continue reading button"
|
|||||||
msgstr "نمایش دکمه ادامه خواندن"
|
msgstr "نمایش دکمه ادامه خواندن"
|
||||||
|
|
||||||
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Show entry"
|
msgid "Show entry"
|
||||||
msgstr "نمایش محتوا"
|
msgstr "نمایش محتوا"
|
||||||
|
|
||||||
@@ -2925,7 +2925,7 @@ msgstr "ردیابها"
|
|||||||
|
|
||||||
#: src/features/backup/Backup.constants.ts
|
#: src/features/backup/Backup.constants.ts
|
||||||
#: src/features/manga/components/TrackMangaButton.tsx
|
#: src/features/manga/components/TrackMangaButton.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/settings/screens/Settings.tsx
|
#: src/features/settings/screens/Settings.tsx
|
||||||
#: src/features/tracker/screens/TrackingSettings.tsx
|
#: src/features/tracker/screens/TrackingSettings.tsx
|
||||||
msgid "Tracking"
|
msgid "Tracking"
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ msgstr "Naka-bookmark"
|
|||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/category/components/CategorySelect.tsx
|
#: src/features/category/components/CategorySelect.tsx
|
||||||
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
||||||
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
||||||
#: src/features/source/browse/components/SourceOptions.tsx
|
#: src/features/source/browse/components/SourceOptions.tsx
|
||||||
@@ -139,7 +139,7 @@ msgid "Convert images to different formats"
|
|||||||
msgstr "I-convert ang mga larawan sa iba't ibang mga format"
|
msgstr "I-convert ang mga larawan sa iba't ibang mga format"
|
||||||
|
|
||||||
#: src/features/manga/components/details/MangaDetails.tsx
|
#: src/features/manga/components/details/MangaDetails.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Copy"
|
msgid "Copy"
|
||||||
msgstr "Kopyahin"
|
msgstr "Kopyahin"
|
||||||
|
|
||||||
@@ -498,7 +498,7 @@ msgstr "Markahan ang napili bilang hindi pa nababasa"
|
|||||||
#: src/features/manga/components/MangaToolbarMenu.tsx
|
#: src/features/manga/components/MangaToolbarMenu.tsx
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Migrate"
|
msgid "Migrate"
|
||||||
msgstr "Mag-migrate"
|
msgstr "Mag-migrate"
|
||||||
|
|
||||||
|
|||||||
@@ -365,7 +365,7 @@ msgstr "Par date de mise en ligne"
|
|||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/category/components/CategorySelect.tsx
|
#: src/features/category/components/CategorySelect.tsx
|
||||||
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
||||||
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
||||||
#: src/features/source/browse/components/SourceOptions.tsx
|
#: src/features/source/browse/components/SourceOptions.tsx
|
||||||
@@ -534,7 +534,7 @@ msgid "Copied to clipboard"
|
|||||||
msgstr "Copié dans le presse-papiers"
|
msgstr "Copié dans le presse-papiers"
|
||||||
|
|
||||||
#: src/features/manga/components/details/MangaDetails.tsx
|
#: src/features/manga/components/details/MangaDetails.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Copy"
|
msgid "Copy"
|
||||||
msgstr "Copier"
|
msgstr "Copier"
|
||||||
|
|
||||||
@@ -811,7 +811,7 @@ msgstr "Supprimer les chapitres après les avoir manuellement marqués comme lus
|
|||||||
msgid "Delete chapters"
|
msgid "Delete chapters"
|
||||||
msgstr "Supprimer les chapitres"
|
msgstr "Supprimer les chapitres"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Delete downloaded"
|
msgid "Delete downloaded"
|
||||||
msgstr "Supprimer les téléchargements"
|
msgstr "Supprimer les téléchargements"
|
||||||
|
|
||||||
@@ -1658,12 +1658,12 @@ msgstr "Menu"
|
|||||||
#: src/features/manga/components/MangaToolbarMenu.tsx
|
#: src/features/manga/components/MangaToolbarMenu.tsx
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/migration/screens/Migrate.tsx
|
#: src/features/migration/screens/Migrate.tsx
|
||||||
msgid "Migrate"
|
msgid "Migrate"
|
||||||
msgstr "Migrer"
|
msgstr "Migrer"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Migrating manga…"
|
msgid "Migrating manga…"
|
||||||
msgstr "Migration des mangas…"
|
msgstr "Migration des mangas…"
|
||||||
|
|
||||||
@@ -2287,7 +2287,7 @@ msgstr "Sélectionnez un appareil pour utiliser ses paramètres d'interface util
|
|||||||
msgid "Select all"
|
msgid "Select all"
|
||||||
msgstr "Tout sélectionner"
|
msgstr "Tout sélectionner"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Select data to include"
|
msgid "Select data to include"
|
||||||
msgstr "Sélectionner les données à inclure"
|
msgstr "Sélectionner les données à inclure"
|
||||||
|
|
||||||
@@ -2344,7 +2344,7 @@ msgid "Show continue reading button"
|
|||||||
msgstr "Afficher le bouton de reprise de lecture"
|
msgstr "Afficher le bouton de reprise de lecture"
|
||||||
|
|
||||||
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Show entry"
|
msgid "Show entry"
|
||||||
msgstr "Afficher l'entrée"
|
msgstr "Afficher l'entrée"
|
||||||
|
|
||||||
|
|||||||
@@ -643,7 +643,7 @@ msgstr "הבקשה נכשלה (Timeout)"
|
|||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/category/components/CategorySelect.tsx
|
#: src/features/category/components/CategorySelect.tsx
|
||||||
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
||||||
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
||||||
#: src/features/source/browse/components/SourceOptions.tsx
|
#: src/features/source/browse/components/SourceOptions.tsx
|
||||||
@@ -669,7 +669,7 @@ msgstr "קטגוריות"
|
|||||||
|
|
||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Category"
|
msgid "Category"
|
||||||
msgstr "קטגורייה"
|
msgstr "קטגורייה"
|
||||||
|
|
||||||
@@ -706,7 +706,7 @@ msgstr "ערוץ"
|
|||||||
#: src/features/chapter/components/cards/ChapterCard.tsx
|
#: src/features/chapter/components/cards/ChapterCard.tsx
|
||||||
#: src/features/downloads/components/DownloadAheadSetting.tsx
|
#: src/features/downloads/components/DownloadAheadSetting.tsx
|
||||||
#: src/features/downloads/screens/DownloadSettings.tsx
|
#: src/features/downloads/screens/DownloadSettings.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
||||||
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
||||||
#: src/features/tracker/components/cards/TrackerActiveCard.tsx
|
#: src/features/tracker/components/cards/TrackerActiveCard.tsx
|
||||||
@@ -894,7 +894,7 @@ msgid "Copied to clipboard"
|
|||||||
msgstr "הועתק ללוח"
|
msgstr "הועתק ללוח"
|
||||||
|
|
||||||
#: src/features/manga/components/details/MangaDetails.tsx
|
#: src/features/manga/components/details/MangaDetails.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Copy"
|
msgid "Copy"
|
||||||
msgstr "העתק"
|
msgstr "העתק"
|
||||||
|
|
||||||
@@ -1225,7 +1225,7 @@ msgstr "שרתי מעקב"
|
|||||||
|
|
||||||
#: src/features/backup/Backup.constants.ts
|
#: src/features/backup/Backup.constants.ts
|
||||||
#: src/features/manga/components/TrackMangaButton.tsx
|
#: src/features/manga/components/TrackMangaButton.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/settings/screens/Settings.tsx
|
#: src/features/settings/screens/Settings.tsx
|
||||||
#: src/features/tracker/screens/TrackingSettings.tsx
|
#: src/features/tracker/screens/TrackingSettings.tsx
|
||||||
msgid "Tracking"
|
msgid "Tracking"
|
||||||
@@ -1277,7 +1277,7 @@ msgstr "בחירה"
|
|||||||
msgid "Select all"
|
msgid "Select all"
|
||||||
msgstr "בחירת הכל"
|
msgstr "בחירת הכל"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Delete downloaded"
|
msgid "Delete downloaded"
|
||||||
msgstr "מחיקת פרקים שהורדו"
|
msgstr "מחיקת פרקים שהורדו"
|
||||||
|
|
||||||
|
|||||||
@@ -657,7 +657,7 @@ msgstr "Feltöltés ideje szerint"
|
|||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/category/components/CategorySelect.tsx
|
#: src/features/category/components/CategorySelect.tsx
|
||||||
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
||||||
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
||||||
#: src/features/source/browse/components/SourceOptions.tsx
|
#: src/features/source/browse/components/SourceOptions.tsx
|
||||||
@@ -683,7 +683,7 @@ msgstr "Kategóriák"
|
|||||||
|
|
||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Category"
|
msgid "Category"
|
||||||
msgstr "Kategória"
|
msgstr "Kategória"
|
||||||
|
|
||||||
@@ -720,7 +720,7 @@ msgstr "Csatorna"
|
|||||||
#: src/features/chapter/components/cards/ChapterCard.tsx
|
#: src/features/chapter/components/cards/ChapterCard.tsx
|
||||||
#: src/features/downloads/components/DownloadAheadSetting.tsx
|
#: src/features/downloads/components/DownloadAheadSetting.tsx
|
||||||
#: src/features/downloads/screens/DownloadSettings.tsx
|
#: src/features/downloads/screens/DownloadSettings.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
||||||
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
||||||
#: src/features/tracker/components/cards/TrackerActiveCard.tsx
|
#: src/features/tracker/components/cards/TrackerActiveCard.tsx
|
||||||
@@ -877,7 +877,7 @@ msgid "Copied to clipboard"
|
|||||||
msgstr "Másolva a vágólapra"
|
msgstr "Másolva a vágólapra"
|
||||||
|
|
||||||
#: src/features/manga/components/details/MangaDetails.tsx
|
#: src/features/manga/components/details/MangaDetails.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Copy"
|
msgid "Copy"
|
||||||
msgstr "Másolás"
|
msgstr "Másolás"
|
||||||
|
|
||||||
@@ -1202,7 +1202,7 @@ msgstr "Fejezet törlése olvasottnak jelölés után"
|
|||||||
msgid "Delete chapters"
|
msgid "Delete chapters"
|
||||||
msgstr "Fejezetek törlése"
|
msgstr "Fejezetek törlése"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Delete downloaded"
|
msgid "Delete downloaded"
|
||||||
msgstr "Letöltöttek törlése"
|
msgstr "Letöltöttek törlése"
|
||||||
|
|
||||||
@@ -2058,7 +2058,7 @@ msgstr "Menü"
|
|||||||
#: src/features/manga/components/MangaToolbarMenu.tsx
|
#: src/features/manga/components/MangaToolbarMenu.tsx
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/migration/screens/Migrate.tsx
|
#: src/features/migration/screens/Migrate.tsx
|
||||||
msgid "Migrate"
|
msgid "Migrate"
|
||||||
msgstr "Átmozgatás"
|
msgstr "Átmozgatás"
|
||||||
@@ -2068,7 +2068,7 @@ msgstr "Átmozgatás"
|
|||||||
msgid "Migrate \"{0}\""
|
msgid "Migrate \"{0}\""
|
||||||
msgstr "\"{0}\" átmozgatása"
|
msgstr "\"{0}\" átmozgatása"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Migrating manga…"
|
msgid "Migrating manga…"
|
||||||
msgstr "Manga átmozgatás alatt…"
|
msgstr "Manga átmozgatás alatt…"
|
||||||
|
|
||||||
@@ -2781,7 +2781,7 @@ msgid "Show only downloaded chapters"
|
|||||||
msgstr "Csak a letöltött fejezetek megjelenítése"
|
msgstr "Csak a letöltött fejezetek megjelenítése"
|
||||||
|
|
||||||
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Show entry"
|
msgid "Show entry"
|
||||||
msgstr "Elem megjelenítése"
|
msgstr "Elem megjelenítése"
|
||||||
|
|
||||||
@@ -3150,7 +3150,7 @@ msgstr "Követők"
|
|||||||
|
|
||||||
#: src/features/backup/Backup.constants.ts
|
#: src/features/backup/Backup.constants.ts
|
||||||
#: src/features/manga/components/TrackMangaButton.tsx
|
#: src/features/manga/components/TrackMangaButton.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/settings/screens/Settings.tsx
|
#: src/features/settings/screens/Settings.tsx
|
||||||
#: src/features/tracker/screens/TrackingSettings.tsx
|
#: src/features/tracker/screens/TrackingSettings.tsx
|
||||||
msgid "Tracking"
|
msgid "Tracking"
|
||||||
|
|||||||
@@ -235,7 +235,7 @@ msgstr "Berdasarkan tanggal diunggah"
|
|||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/category/components/CategorySelect.tsx
|
#: src/features/category/components/CategorySelect.tsx
|
||||||
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
||||||
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
||||||
#: src/features/source/browse/components/SourceOptions.tsx
|
#: src/features/source/browse/components/SourceOptions.tsx
|
||||||
@@ -351,7 +351,7 @@ msgid "Copied to clipboard"
|
|||||||
msgstr "Disalin ke papan klip"
|
msgstr "Disalin ke papan klip"
|
||||||
|
|
||||||
#: src/features/manga/components/details/MangaDetails.tsx
|
#: src/features/manga/components/details/MangaDetails.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Copy"
|
msgid "Copy"
|
||||||
msgstr "Salin"
|
msgstr "Salin"
|
||||||
|
|
||||||
@@ -497,7 +497,7 @@ msgstr "Hapus bab setelah menandainya secara manual sebagai telah dibaca"
|
|||||||
msgid "Delete chapters"
|
msgid "Delete chapters"
|
||||||
msgstr "Hapus bab"
|
msgstr "Hapus bab"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Delete downloaded"
|
msgid "Delete downloaded"
|
||||||
msgstr "Hapus yang diunduh"
|
msgstr "Hapus yang diunduh"
|
||||||
|
|
||||||
@@ -1014,7 +1014,7 @@ msgstr "Menu"
|
|||||||
#: src/features/manga/components/MangaToolbarMenu.tsx
|
#: src/features/manga/components/MangaToolbarMenu.tsx
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Migrate"
|
msgid "Migrate"
|
||||||
msgstr "Bermigasi"
|
msgstr "Bermigasi"
|
||||||
|
|
||||||
@@ -1024,7 +1024,7 @@ msgstr "Bermigasi"
|
|||||||
msgid "Migrate"
|
msgid "Migrate"
|
||||||
msgstr "Migrasi"
|
msgstr "Migrasi"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Migrating manga…"
|
msgid "Migrating manga…"
|
||||||
msgstr "Memindahkan manga…"
|
msgstr "Memindahkan manga…"
|
||||||
|
|
||||||
@@ -1388,7 +1388,7 @@ msgstr "Pilih"
|
|||||||
msgid "Select all"
|
msgid "Select all"
|
||||||
msgstr "Pilih Semua"
|
msgstr "Pilih Semua"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Select data to include"
|
msgid "Select data to include"
|
||||||
msgstr "Pilih data untuk disertakan"
|
msgstr "Pilih data untuk disertakan"
|
||||||
|
|
||||||
@@ -1409,7 +1409,7 @@ msgid "Show continue reading button"
|
|||||||
msgstr "Tampilkan tombol lanjutkan membaca"
|
msgstr "Tampilkan tombol lanjutkan membaca"
|
||||||
|
|
||||||
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Show entry"
|
msgid "Show entry"
|
||||||
msgstr "Tampilkan entri"
|
msgstr "Tampilkan entri"
|
||||||
|
|
||||||
|
|||||||
@@ -368,7 +368,7 @@ msgstr "Timeout della chiamata"
|
|||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/category/components/CategorySelect.tsx
|
#: src/features/category/components/CategorySelect.tsx
|
||||||
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
||||||
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
||||||
#: src/features/source/browse/components/SourceOptions.tsx
|
#: src/features/source/browse/components/SourceOptions.tsx
|
||||||
@@ -560,7 +560,7 @@ msgid "Copied to clipboard"
|
|||||||
msgstr "Copiato negli appunti"
|
msgstr "Copiato negli appunti"
|
||||||
|
|
||||||
#: src/features/manga/components/details/MangaDetails.tsx
|
#: src/features/manga/components/details/MangaDetails.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Copy"
|
msgid "Copy"
|
||||||
msgstr "Copia"
|
msgstr "Copia"
|
||||||
|
|
||||||
@@ -846,7 +846,7 @@ msgstr "Cancella un capitolo dopo averlo manualmente segnato come letto"
|
|||||||
msgid "Delete chapters"
|
msgid "Delete chapters"
|
||||||
msgstr "Cancella capitoli"
|
msgstr "Cancella capitoli"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Delete downloaded"
|
msgid "Delete downloaded"
|
||||||
msgstr "Cancellare gli scaricati"
|
msgstr "Cancellare gli scaricati"
|
||||||
|
|
||||||
@@ -1731,12 +1731,12 @@ msgstr "Menu"
|
|||||||
#: src/features/manga/components/MangaToolbarMenu.tsx
|
#: src/features/manga/components/MangaToolbarMenu.tsx
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/migration/screens/Migrate.tsx
|
#: src/features/migration/screens/Migrate.tsx
|
||||||
msgid "Migrate"
|
msgid "Migrate"
|
||||||
msgstr "Migrare"
|
msgstr "Migrare"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Migrating manga…"
|
msgid "Migrating manga…"
|
||||||
msgstr "Migrazione dei manga…"
|
msgstr "Migrazione dei manga…"
|
||||||
|
|
||||||
@@ -2369,7 +2369,7 @@ msgstr "Selezionare un dispositivo per utilizzare le impostazioni dell'interfacc
|
|||||||
msgid "Select all"
|
msgid "Select all"
|
||||||
msgstr "Seleziona tutto"
|
msgstr "Seleziona tutto"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Select data to include"
|
msgid "Select data to include"
|
||||||
msgstr "Selezionare i dati da includere"
|
msgstr "Selezionare i dati da includere"
|
||||||
|
|
||||||
@@ -2445,7 +2445,7 @@ msgid "Show continue reading button"
|
|||||||
msgstr "Mostra il pulsante Continua a leggere"
|
msgstr "Mostra il pulsante Continua a leggere"
|
||||||
|
|
||||||
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Show entry"
|
msgid "Show entry"
|
||||||
msgstr "Mostra la voce"
|
msgstr "Mostra la voce"
|
||||||
|
|
||||||
@@ -2839,7 +2839,7 @@ msgstr "Trackers"
|
|||||||
|
|
||||||
#: src/features/backup/Backup.constants.ts
|
#: src/features/backup/Backup.constants.ts
|
||||||
#: src/features/manga/components/TrackMangaButton.tsx
|
#: src/features/manga/components/TrackMangaButton.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/settings/screens/Settings.tsx
|
#: src/features/settings/screens/Settings.tsx
|
||||||
#: src/features/tracker/screens/TrackingSettings.tsx
|
#: src/features/tracker/screens/TrackingSettings.tsx
|
||||||
msgid "Tracking"
|
msgid "Tracking"
|
||||||
|
|||||||
@@ -361,7 +361,7 @@ msgstr "アップロード日順"
|
|||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/category/components/CategorySelect.tsx
|
#: src/features/category/components/CategorySelect.tsx
|
||||||
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
||||||
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
||||||
#: src/features/source/browse/components/SourceOptions.tsx
|
#: src/features/source/browse/components/SourceOptions.tsx
|
||||||
@@ -526,7 +526,7 @@ msgid "Copied to clipboard"
|
|||||||
msgstr "クリップボードにコピーしました"
|
msgstr "クリップボードにコピーしました"
|
||||||
|
|
||||||
#: src/features/manga/components/details/MangaDetails.tsx
|
#: src/features/manga/components/details/MangaDetails.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Copy"
|
msgid "Copy"
|
||||||
msgstr "コピー"
|
msgstr "コピー"
|
||||||
|
|
||||||
@@ -795,7 +795,7 @@ msgstr "手動で既読にした後に章を削除"
|
|||||||
msgid "Delete chapters"
|
msgid "Delete chapters"
|
||||||
msgstr "章を削除"
|
msgstr "章を削除"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Delete downloaded"
|
msgid "Delete downloaded"
|
||||||
msgstr "ダウンロード済を削除"
|
msgstr "ダウンロード済を削除"
|
||||||
|
|
||||||
@@ -1608,12 +1608,12 @@ msgstr "メニュー"
|
|||||||
#: src/features/manga/components/MangaToolbarMenu.tsx
|
#: src/features/manga/components/MangaToolbarMenu.tsx
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/migration/screens/Migrate.tsx
|
#: src/features/migration/screens/Migrate.tsx
|
||||||
msgid "Migrate"
|
msgid "Migrate"
|
||||||
msgstr "移行"
|
msgstr "移行"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Migrating manga…"
|
msgid "Migrating manga…"
|
||||||
msgstr "マンガを移行中…"
|
msgstr "マンガを移行中…"
|
||||||
|
|
||||||
@@ -2229,7 +2229,7 @@ msgstr "サーバーに保存された UI 設定を使用するデバイスを
|
|||||||
msgid "Select all"
|
msgid "Select all"
|
||||||
msgstr "すべて選択"
|
msgstr "すべて選択"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Select data to include"
|
msgid "Select data to include"
|
||||||
msgstr "含めるデータを選択"
|
msgstr "含めるデータを選択"
|
||||||
|
|
||||||
@@ -2286,7 +2286,7 @@ msgid "Show continue reading button"
|
|||||||
msgstr "続きを読むボタンを表示"
|
msgstr "続きを読むボタンを表示"
|
||||||
|
|
||||||
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Show entry"
|
msgid "Show entry"
|
||||||
msgstr "エントリーを表示"
|
msgstr "エントリーを表示"
|
||||||
|
|
||||||
@@ -3020,14 +3020,14 @@ msgstr "カテゴリー"
|
|||||||
|
|
||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Category"
|
msgid "Category"
|
||||||
msgstr "カテゴリー"
|
msgstr "カテゴリー"
|
||||||
|
|
||||||
#: src/features/chapter/components/cards/ChapterCard.tsx
|
#: src/features/chapter/components/cards/ChapterCard.tsx
|
||||||
#: src/features/downloads/components/DownloadAheadSetting.tsx
|
#: src/features/downloads/components/DownloadAheadSetting.tsx
|
||||||
#: src/features/downloads/screens/DownloadSettings.tsx
|
#: src/features/downloads/screens/DownloadSettings.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
||||||
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
||||||
#: src/features/tracker/components/cards/TrackerActiveCard.tsx
|
#: src/features/tracker/components/cards/TrackerActiveCard.tsx
|
||||||
@@ -3038,7 +3038,7 @@ msgstr "章"
|
|||||||
|
|
||||||
#: src/features/backup/Backup.constants.ts
|
#: src/features/backup/Backup.constants.ts
|
||||||
#: src/features/manga/components/TrackMangaButton.tsx
|
#: src/features/manga/components/TrackMangaButton.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/settings/screens/Settings.tsx
|
#: src/features/settings/screens/Settings.tsx
|
||||||
#: src/features/tracker/screens/TrackingSettings.tsx
|
#: src/features/tracker/screens/TrackingSettings.tsx
|
||||||
msgid "Tracking"
|
msgid "Tracking"
|
||||||
|
|||||||
@@ -373,7 +373,7 @@ msgstr "호출 시간 초과"
|
|||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/category/components/CategorySelect.tsx
|
#: src/features/category/components/CategorySelect.tsx
|
||||||
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
||||||
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
||||||
#: src/features/source/browse/components/SourceOptions.tsx
|
#: src/features/source/browse/components/SourceOptions.tsx
|
||||||
@@ -586,7 +586,7 @@ msgid "Copied to clipboard"
|
|||||||
msgstr "클립보드에 복사됨"
|
msgstr "클립보드에 복사됨"
|
||||||
|
|
||||||
#: src/features/manga/components/details/MangaDetails.tsx
|
#: src/features/manga/components/details/MangaDetails.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Copy"
|
msgid "Copy"
|
||||||
msgstr "복사"
|
msgstr "복사"
|
||||||
|
|
||||||
@@ -872,7 +872,7 @@ msgstr "읽음으로 표시하면 챕터 삭제"
|
|||||||
msgid "Delete chapters"
|
msgid "Delete chapters"
|
||||||
msgstr "챕터 삭제"
|
msgstr "챕터 삭제"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Delete downloaded"
|
msgid "Delete downloaded"
|
||||||
msgstr "다운로드된 항목 삭제"
|
msgstr "다운로드된 항목 삭제"
|
||||||
|
|
||||||
@@ -1807,12 +1807,12 @@ msgstr "메뉴"
|
|||||||
#: src/features/manga/components/MangaToolbarMenu.tsx
|
#: src/features/manga/components/MangaToolbarMenu.tsx
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/migration/screens/Migrate.tsx
|
#: src/features/migration/screens/Migrate.tsx
|
||||||
msgid "Migrate"
|
msgid "Migrate"
|
||||||
msgstr "마이그레이션"
|
msgstr "마이그레이션"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Migrating manga…"
|
msgid "Migrating manga…"
|
||||||
msgstr "만화 마이그레이션 중…"
|
msgstr "만화 마이그레이션 중…"
|
||||||
|
|
||||||
@@ -2449,7 +2449,7 @@ msgstr "서버에 저장된 UI 설정을 사용할 기기를 선택하세요"
|
|||||||
msgid "Select all"
|
msgid "Select all"
|
||||||
msgstr "모두 선택"
|
msgstr "모두 선택"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Select data to include"
|
msgid "Select data to include"
|
||||||
msgstr "포함할 데이터 선택"
|
msgstr "포함할 데이터 선택"
|
||||||
|
|
||||||
@@ -2525,7 +2525,7 @@ msgid "Show continue reading button"
|
|||||||
msgstr "계속 읽기 버튼 표시"
|
msgstr "계속 읽기 버튼 표시"
|
||||||
|
|
||||||
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Show entry"
|
msgid "Show entry"
|
||||||
msgstr "항목 표시"
|
msgstr "항목 표시"
|
||||||
|
|
||||||
@@ -2931,7 +2931,7 @@ msgstr "추적"
|
|||||||
|
|
||||||
#: src/features/backup/Backup.constants.ts
|
#: src/features/backup/Backup.constants.ts
|
||||||
#: src/features/manga/components/TrackMangaButton.tsx
|
#: src/features/manga/components/TrackMangaButton.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/settings/screens/Settings.tsx
|
#: src/features/settings/screens/Settings.tsx
|
||||||
#: src/features/tracker/screens/TrackingSettings.tsx
|
#: src/features/tracker/screens/TrackingSettings.tsx
|
||||||
msgid "Tracking"
|
msgid "Tracking"
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ msgstr "Op uploaddatum"
|
|||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/category/components/CategorySelect.tsx
|
#: src/features/category/components/CategorySelect.tsx
|
||||||
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
||||||
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
||||||
#: src/features/source/browse/components/SourceOptions.tsx
|
#: src/features/source/browse/components/SourceOptions.tsx
|
||||||
@@ -285,7 +285,7 @@ msgid "Copied to clipboard"
|
|||||||
msgstr "Gekopieerd naar klembord"
|
msgstr "Gekopieerd naar klembord"
|
||||||
|
|
||||||
#: src/features/manga/components/details/MangaDetails.tsx
|
#: src/features/manga/components/details/MangaDetails.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Copy"
|
msgid "Copy"
|
||||||
msgstr "Kopiëren"
|
msgstr "Kopiëren"
|
||||||
|
|
||||||
@@ -413,7 +413,7 @@ msgstr "Verwijder het hoofdstuk nadat hij handmatig als gelezen is gemarkeerd"
|
|||||||
msgid "Delete chapters"
|
msgid "Delete chapters"
|
||||||
msgstr "Verwijder hoofdstukken"
|
msgstr "Verwijder hoofdstukken"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Delete downloaded"
|
msgid "Delete downloaded"
|
||||||
msgstr "Verwijder downloads"
|
msgstr "Verwijder downloads"
|
||||||
|
|
||||||
@@ -877,7 +877,7 @@ msgstr "Menu"
|
|||||||
#: src/features/manga/components/MangaToolbarMenu.tsx
|
#: src/features/manga/components/MangaToolbarMenu.tsx
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Migrate"
|
msgid "Migrate"
|
||||||
msgstr "Migreren"
|
msgstr "Migreren"
|
||||||
|
|
||||||
@@ -887,7 +887,7 @@ msgstr "Migreren"
|
|||||||
msgid "Migrate"
|
msgid "Migrate"
|
||||||
msgstr "Migreren"
|
msgstr "Migreren"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Migrating manga…"
|
msgid "Migrating manga…"
|
||||||
msgstr "Manga migreren…"
|
msgstr "Manga migreren…"
|
||||||
|
|
||||||
@@ -1197,7 +1197,7 @@ msgstr "Selecteer"
|
|||||||
msgid "Select all"
|
msgid "Select all"
|
||||||
msgstr "Alles selecteren"
|
msgstr "Alles selecteren"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Select data to include"
|
msgid "Select data to include"
|
||||||
msgstr "Selecteer gegevens die u wilt opnemen"
|
msgstr "Selecteer gegevens die u wilt opnemen"
|
||||||
|
|
||||||
@@ -1218,7 +1218,7 @@ msgid "Show continue reading button"
|
|||||||
msgstr "Knop doorgaan met lezen tonen"
|
msgstr "Knop doorgaan met lezen tonen"
|
||||||
|
|
||||||
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Show entry"
|
msgid "Show entry"
|
||||||
msgstr "Invoer tonen"
|
msgstr "Invoer tonen"
|
||||||
|
|
||||||
|
|||||||
@@ -362,7 +362,7 @@ msgstr "Według daty przesłania"
|
|||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/category/components/CategorySelect.tsx
|
#: src/features/category/components/CategorySelect.tsx
|
||||||
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
||||||
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
||||||
#: src/features/source/browse/components/SourceOptions.tsx
|
#: src/features/source/browse/components/SourceOptions.tsx
|
||||||
@@ -527,7 +527,7 @@ msgid "Copied to clipboard"
|
|||||||
msgstr "Skopiowano do schowka"
|
msgstr "Skopiowano do schowka"
|
||||||
|
|
||||||
#: src/features/manga/components/details/MangaDetails.tsx
|
#: src/features/manga/components/details/MangaDetails.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Copy"
|
msgid "Copy"
|
||||||
msgstr "Kopiuj"
|
msgstr "Kopiuj"
|
||||||
|
|
||||||
@@ -796,7 +796,7 @@ msgstr "Usuń rozdział po ręcznym oznaczeniu go jako przeczytany"
|
|||||||
msgid "Delete chapters"
|
msgid "Delete chapters"
|
||||||
msgstr "Usuń rozdziały"
|
msgstr "Usuń rozdziały"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Delete downloaded"
|
msgid "Delete downloaded"
|
||||||
msgstr "Usuń pobrane"
|
msgstr "Usuń pobrane"
|
||||||
|
|
||||||
@@ -1617,12 +1617,12 @@ msgstr "Menu"
|
|||||||
#: src/features/manga/components/MangaToolbarMenu.tsx
|
#: src/features/manga/components/MangaToolbarMenu.tsx
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/migration/screens/Migrate.tsx
|
#: src/features/migration/screens/Migrate.tsx
|
||||||
msgid "Migrate"
|
msgid "Migrate"
|
||||||
msgstr "Migruj"
|
msgstr "Migruj"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Migrating manga…"
|
msgid "Migrating manga…"
|
||||||
msgstr "Migruję mangę…"
|
msgstr "Migruję mangę…"
|
||||||
|
|
||||||
@@ -2238,7 +2238,7 @@ msgstr "Wybierz urządzenie, aby użyć jego ustawień interfejsu użytkownika z
|
|||||||
msgid "Select all"
|
msgid "Select all"
|
||||||
msgstr "Wybierz wszystko"
|
msgstr "Wybierz wszystko"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Select data to include"
|
msgid "Select data to include"
|
||||||
msgstr "Wybierz dane do uwzględnienia"
|
msgstr "Wybierz dane do uwzględnienia"
|
||||||
|
|
||||||
@@ -2295,7 +2295,7 @@ msgid "Show continue reading button"
|
|||||||
msgstr "Pokaż przycisk kontynuowania czytania"
|
msgstr "Pokaż przycisk kontynuowania czytania"
|
||||||
|
|
||||||
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Show entry"
|
msgid "Show entry"
|
||||||
msgstr "Pokaż pozycję"
|
msgstr "Pokaż pozycję"
|
||||||
|
|
||||||
@@ -3152,7 +3152,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/features/backup/Backup.constants.ts
|
#: src/features/backup/Backup.constants.ts
|
||||||
#: src/features/manga/components/TrackMangaButton.tsx
|
#: src/features/manga/components/TrackMangaButton.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/settings/screens/Settings.tsx
|
#: src/features/settings/screens/Settings.tsx
|
||||||
#: src/features/tracker/screens/TrackingSettings.tsx
|
#: src/features/tracker/screens/TrackingSettings.tsx
|
||||||
msgid "Tracking"
|
msgid "Tracking"
|
||||||
@@ -3192,14 +3192,14 @@ msgstr "Kategorie"
|
|||||||
|
|
||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Category"
|
msgid "Category"
|
||||||
msgstr "Kategoria"
|
msgstr "Kategoria"
|
||||||
|
|
||||||
#: src/features/chapter/components/cards/ChapterCard.tsx
|
#: src/features/chapter/components/cards/ChapterCard.tsx
|
||||||
#: src/features/downloads/components/DownloadAheadSetting.tsx
|
#: src/features/downloads/components/DownloadAheadSetting.tsx
|
||||||
#: src/features/downloads/screens/DownloadSettings.tsx
|
#: src/features/downloads/screens/DownloadSettings.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
||||||
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
||||||
#: src/features/tracker/components/cards/TrackerActiveCard.tsx
|
#: src/features/tracker/components/cards/TrackerActiveCard.tsx
|
||||||
@@ -3217,7 +3217,7 @@ msgid "Clear"
|
|||||||
msgstr "Wyczyść"
|
msgstr "Wyczyść"
|
||||||
|
|
||||||
#: src/features/backup/Backup.constants.ts
|
#: src/features/backup/Backup.constants.ts
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Client data"
|
msgid "Client data"
|
||||||
msgstr "Dane klienta"
|
msgstr "Dane klienta"
|
||||||
|
|
||||||
|
|||||||
@@ -325,7 +325,7 @@ msgstr "Por data de upload"
|
|||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/category/components/CategorySelect.tsx
|
#: src/features/category/components/CategorySelect.tsx
|
||||||
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
||||||
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
||||||
#: src/features/source/browse/components/SourceOptions.tsx
|
#: src/features/source/browse/components/SourceOptions.tsx
|
||||||
@@ -474,7 +474,7 @@ msgid "Copied to clipboard"
|
|||||||
msgstr "Copiado para área de transferencia"
|
msgstr "Copiado para área de transferencia"
|
||||||
|
|
||||||
#: src/features/manga/components/details/MangaDetails.tsx
|
#: src/features/manga/components/details/MangaDetails.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Copy"
|
msgid "Copy"
|
||||||
msgstr "Copiar"
|
msgstr "Copiar"
|
||||||
|
|
||||||
@@ -723,7 +723,7 @@ msgstr "Apagar capítulo depois de marcar manualmente como lido"
|
|||||||
msgid "Delete chapters"
|
msgid "Delete chapters"
|
||||||
msgstr "Apagar capítulos"
|
msgstr "Apagar capítulos"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Delete downloaded"
|
msgid "Delete downloaded"
|
||||||
msgstr "Remover baixados"
|
msgstr "Remover baixados"
|
||||||
|
|
||||||
@@ -1443,12 +1443,12 @@ msgstr "Menu"
|
|||||||
#: src/features/manga/components/MangaToolbarMenu.tsx
|
#: src/features/manga/components/MangaToolbarMenu.tsx
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/migration/screens/Migrate.tsx
|
#: src/features/migration/screens/Migrate.tsx
|
||||||
msgid "Migrate"
|
msgid "Migrate"
|
||||||
msgstr "Migrar"
|
msgstr "Migrar"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Migrating manga…"
|
msgid "Migrating manga…"
|
||||||
msgstr "Migrando mangá…"
|
msgstr "Migrando mangá…"
|
||||||
|
|
||||||
@@ -1994,7 +1994,7 @@ msgstr "Selecione um dispositivo para usar suas configurações de interface do
|
|||||||
msgid "Select all"
|
msgid "Select all"
|
||||||
msgstr "Selecionar todos"
|
msgstr "Selecionar todos"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Select data to include"
|
msgid "Select data to include"
|
||||||
msgstr "Selecionar dados para incluir"
|
msgstr "Selecionar dados para incluir"
|
||||||
|
|
||||||
@@ -2044,7 +2044,7 @@ msgid "Show continue reading button"
|
|||||||
msgstr "Mostrar botão de continuar leitura"
|
msgstr "Mostrar botão de continuar leitura"
|
||||||
|
|
||||||
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Show entry"
|
msgid "Show entry"
|
||||||
msgstr "Mostrar entrada"
|
msgstr "Mostrar entrada"
|
||||||
|
|
||||||
|
|||||||
@@ -344,7 +344,7 @@ msgstr "Por data do envio"
|
|||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/category/components/CategorySelect.tsx
|
#: src/features/category/components/CategorySelect.tsx
|
||||||
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
||||||
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
||||||
#: src/features/source/browse/components/SourceOptions.tsx
|
#: src/features/source/browse/components/SourceOptions.tsx
|
||||||
@@ -497,7 +497,7 @@ msgid "Copied to clipboard"
|
|||||||
msgstr "Copiado para a área de transferência"
|
msgstr "Copiado para a área de transferência"
|
||||||
|
|
||||||
#: src/features/manga/components/details/MangaDetails.tsx
|
#: src/features/manga/components/details/MangaDetails.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Copy"
|
msgid "Copy"
|
||||||
msgstr "Copiar"
|
msgstr "Copiar"
|
||||||
|
|
||||||
@@ -766,7 +766,7 @@ msgstr "Deletar o capítulo após marcá-lo manualmente como lido"
|
|||||||
msgid "Delete chapters"
|
msgid "Delete chapters"
|
||||||
msgstr "Deletar capítulos"
|
msgstr "Deletar capítulos"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Delete downloaded"
|
msgid "Delete downloaded"
|
||||||
msgstr "Excluir baixado"
|
msgstr "Excluir baixado"
|
||||||
|
|
||||||
@@ -1536,12 +1536,12 @@ msgstr "Menu"
|
|||||||
#: src/features/manga/components/MangaToolbarMenu.tsx
|
#: src/features/manga/components/MangaToolbarMenu.tsx
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/migration/screens/Migrate.tsx
|
#: src/features/migration/screens/Migrate.tsx
|
||||||
msgid "Migrate"
|
msgid "Migrate"
|
||||||
msgstr "Migrar"
|
msgstr "Migrar"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Migrating manga…"
|
msgid "Migrating manga…"
|
||||||
msgstr "Migrando manga…"
|
msgstr "Migrando manga…"
|
||||||
|
|
||||||
@@ -2129,7 +2129,7 @@ msgstr "Selecione um dispositivo para usar suas configurações de interface do
|
|||||||
msgid "Select all"
|
msgid "Select all"
|
||||||
msgstr "Selecionar todos"
|
msgstr "Selecionar todos"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Select data to include"
|
msgid "Select data to include"
|
||||||
msgstr "Selecione dados para incluir"
|
msgstr "Selecione dados para incluir"
|
||||||
|
|
||||||
@@ -2182,7 +2182,7 @@ msgid "Show continue reading button"
|
|||||||
msgstr "Mostrar o botão de continue lendo"
|
msgstr "Mostrar o botão de continue lendo"
|
||||||
|
|
||||||
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Show entry"
|
msgid "Show entry"
|
||||||
msgstr "Mostrar entrada"
|
msgstr "Mostrar entrada"
|
||||||
|
|
||||||
|
|||||||
@@ -379,7 +379,7 @@ msgstr "Тайм-аут запроса"
|
|||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/category/components/CategorySelect.tsx
|
#: src/features/category/components/CategorySelect.tsx
|
||||||
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
||||||
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
||||||
#: src/features/source/browse/components/SourceOptions.tsx
|
#: src/features/source/browse/components/SourceOptions.tsx
|
||||||
@@ -595,7 +595,7 @@ msgid "Copied to clipboard"
|
|||||||
msgstr "Скопированно в буфер обмена"
|
msgstr "Скопированно в буфер обмена"
|
||||||
|
|
||||||
#: src/features/manga/components/details/MangaDetails.tsx
|
#: src/features/manga/components/details/MangaDetails.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Copy"
|
msgid "Copy"
|
||||||
msgstr "Скопировать"
|
msgstr "Скопировать"
|
||||||
|
|
||||||
@@ -881,7 +881,7 @@ msgstr "Удаление главы после ручной пометки ее
|
|||||||
msgid "Delete chapters"
|
msgid "Delete chapters"
|
||||||
msgstr "Удалять главы"
|
msgstr "Удалять главы"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Delete downloaded"
|
msgid "Delete downloaded"
|
||||||
msgstr "Удалить загруженное"
|
msgstr "Удалить загруженное"
|
||||||
|
|
||||||
@@ -1823,12 +1823,12 @@ msgstr "Меню"
|
|||||||
#: src/features/manga/components/MangaToolbarMenu.tsx
|
#: src/features/manga/components/MangaToolbarMenu.tsx
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/migration/screens/Migrate.tsx
|
#: src/features/migration/screens/Migrate.tsx
|
||||||
msgid "Migrate"
|
msgid "Migrate"
|
||||||
msgstr "Мигрировать"
|
msgstr "Мигрировать"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Migrating manga…"
|
msgid "Migrating manga…"
|
||||||
msgstr "Миграция манги…"
|
msgstr "Миграция манги…"
|
||||||
|
|
||||||
@@ -2474,7 +2474,7 @@ msgstr "Выберите устройство, чтобы использоват
|
|||||||
msgid "Select all"
|
msgid "Select all"
|
||||||
msgstr "Выбрать все"
|
msgstr "Выбрать все"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Select data to include"
|
msgid "Select data to include"
|
||||||
msgstr "Выберите данные для включения"
|
msgstr "Выберите данные для включения"
|
||||||
|
|
||||||
@@ -2554,7 +2554,7 @@ msgid "Show continue reading button"
|
|||||||
msgstr "Кнопка продолжить чтение"
|
msgstr "Кнопка продолжить чтение"
|
||||||
|
|
||||||
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Show entry"
|
msgid "Show entry"
|
||||||
msgstr "Показать записи"
|
msgstr "Показать записи"
|
||||||
|
|
||||||
@@ -2959,7 +2959,7 @@ msgstr "Отслеживания"
|
|||||||
|
|
||||||
#: src/features/backup/Backup.constants.ts
|
#: src/features/backup/Backup.constants.ts
|
||||||
#: src/features/manga/components/TrackMangaButton.tsx
|
#: src/features/manga/components/TrackMangaButton.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/settings/screens/Settings.tsx
|
#: src/features/settings/screens/Settings.tsx
|
||||||
#: src/features/tracker/screens/TrackingSettings.tsx
|
#: src/features/tracker/screens/TrackingSettings.tsx
|
||||||
msgid "Tracking"
|
msgid "Tracking"
|
||||||
@@ -3531,14 +3531,14 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Category"
|
msgid "Category"
|
||||||
msgstr "Категория"
|
msgstr "Категория"
|
||||||
|
|
||||||
#: src/features/chapter/components/cards/ChapterCard.tsx
|
#: src/features/chapter/components/cards/ChapterCard.tsx
|
||||||
#: src/features/downloads/components/DownloadAheadSetting.tsx
|
#: src/features/downloads/components/DownloadAheadSetting.tsx
|
||||||
#: src/features/downloads/screens/DownloadSettings.tsx
|
#: src/features/downloads/screens/DownloadSettings.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
||||||
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
||||||
#: src/features/tracker/components/cards/TrackerActiveCard.tsx
|
#: src/features/tracker/components/cards/TrackerActiveCard.tsx
|
||||||
|
|||||||
@@ -360,7 +360,7 @@ msgstr "பதிவேற்றுவதன் மூலம்"
|
|||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/category/components/CategorySelect.tsx
|
#: src/features/category/components/CategorySelect.tsx
|
||||||
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
||||||
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
||||||
#: src/features/source/browse/components/SourceOptions.tsx
|
#: src/features/source/browse/components/SourceOptions.tsx
|
||||||
@@ -525,7 +525,7 @@ msgid "Copied to clipboard"
|
|||||||
msgstr "இடைநிலைப்பலகைக்கு நகலெடுக்கப்பட்டது"
|
msgstr "இடைநிலைப்பலகைக்கு நகலெடுக்கப்பட்டது"
|
||||||
|
|
||||||
#: src/features/manga/components/details/MangaDetails.tsx
|
#: src/features/manga/components/details/MangaDetails.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Copy"
|
msgid "Copy"
|
||||||
msgstr "நகலெடு"
|
msgstr "நகலெடு"
|
||||||
|
|
||||||
@@ -794,7 +794,7 @@ msgstr "அத்தியாயத்தை கைமுறையாக வா
|
|||||||
msgid "Delete chapters"
|
msgid "Delete chapters"
|
||||||
msgstr "அத்தியாயங்களை நீக்கு"
|
msgstr "அத்தியாயங்களை நீக்கு"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Delete downloaded"
|
msgid "Delete downloaded"
|
||||||
msgstr "பதிவிறக்கம் செய்யப்பட்டதை நீக்கு"
|
msgstr "பதிவிறக்கம் செய்யப்பட்டதை நீக்கு"
|
||||||
|
|
||||||
@@ -1607,12 +1607,12 @@ msgstr "பட்டியல்"
|
|||||||
#: src/features/manga/components/MangaToolbarMenu.tsx
|
#: src/features/manga/components/MangaToolbarMenu.tsx
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/migration/screens/Migrate.tsx
|
#: src/features/migration/screens/Migrate.tsx
|
||||||
msgid "Migrate"
|
msgid "Migrate"
|
||||||
msgstr "இடம்பெயர்வு"
|
msgstr "இடம்பெயர்வு"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Migrating manga…"
|
msgid "Migrating manga…"
|
||||||
msgstr "இடம்பெயரும் மங்கா…"
|
msgstr "இடம்பெயரும் மங்கா…"
|
||||||
|
|
||||||
@@ -2228,7 +2228,7 @@ msgstr "அதன் சேவையக சேமிக்கப்பட்ட
|
|||||||
msgid "Select all"
|
msgid "Select all"
|
||||||
msgstr "அனைத்தையும் தெரிவுசெய்"
|
msgstr "அனைத்தையும் தெரிவுசெய்"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Select data to include"
|
msgid "Select data to include"
|
||||||
msgstr "சேர்க்க தரவைத் தேர்ந்தெடுக்கவும்"
|
msgstr "சேர்க்க தரவைத் தேர்ந்தெடுக்கவும்"
|
||||||
|
|
||||||
@@ -2285,7 +2285,7 @@ msgid "Show continue reading button"
|
|||||||
msgstr "தொடர்ந்து வாசிப்பு பொத்தானைக் காட்டு"
|
msgstr "தொடர்ந்து வாசிப்பு பொத்தானைக் காட்டு"
|
||||||
|
|
||||||
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Show entry"
|
msgid "Show entry"
|
||||||
msgstr "நுழைவு காட்டு"
|
msgstr "நுழைவு காட்டு"
|
||||||
|
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ msgstr "ค้นหา"
|
|||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/category/components/CategorySelect.tsx
|
#: src/features/category/components/CategorySelect.tsx
|
||||||
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
||||||
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
||||||
#: src/features/source/browse/components/SourceOptions.tsx
|
#: src/features/source/browse/components/SourceOptions.tsx
|
||||||
@@ -184,7 +184,7 @@ msgid "Copied to clipboard"
|
|||||||
msgstr "คัดลอกไปยังคลิปบอร์ดแล้ว"
|
msgstr "คัดลอกไปยังคลิปบอร์ดแล้ว"
|
||||||
|
|
||||||
#: src/features/manga/components/details/MangaDetails.tsx
|
#: src/features/manga/components/details/MangaDetails.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Copy"
|
msgid "Copy"
|
||||||
msgstr "คัดลอก"
|
msgstr "คัดลอก"
|
||||||
|
|
||||||
@@ -591,7 +591,7 @@ msgstr "เมนู"
|
|||||||
#: src/features/manga/components/MangaToolbarMenu.tsx
|
#: src/features/manga/components/MangaToolbarMenu.tsx
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Migrate"
|
msgid "Migrate"
|
||||||
msgstr "ย้าย"
|
msgstr "ย้าย"
|
||||||
|
|
||||||
|
|||||||
@@ -329,7 +329,7 @@ msgstr "Çağrı zaman aşımı"
|
|||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/category/components/CategorySelect.tsx
|
#: src/features/category/components/CategorySelect.tsx
|
||||||
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
||||||
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
||||||
#: src/features/source/browse/components/SourceOptions.tsx
|
#: src/features/source/browse/components/SourceOptions.tsx
|
||||||
@@ -487,7 +487,7 @@ msgid "Copied to clipboard"
|
|||||||
msgstr "Panoya kopyalandı"
|
msgstr "Panoya kopyalandı"
|
||||||
|
|
||||||
#: src/features/manga/components/details/MangaDetails.tsx
|
#: src/features/manga/components/details/MangaDetails.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Copy"
|
msgid "Copy"
|
||||||
msgstr "Kopyala"
|
msgstr "Kopyala"
|
||||||
|
|
||||||
@@ -720,7 +720,7 @@ msgstr "Okundu olarak işaretlendikten sonra bölümü sil"
|
|||||||
msgid "Delete chapters"
|
msgid "Delete chapters"
|
||||||
msgstr "Bölümleri sil"
|
msgstr "Bölümleri sil"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Delete downloaded"
|
msgid "Delete downloaded"
|
||||||
msgstr "İndirilenleri sil"
|
msgstr "İndirilenleri sil"
|
||||||
|
|
||||||
@@ -1464,12 +1464,12 @@ msgstr "Menü"
|
|||||||
#: src/features/manga/components/MangaToolbarMenu.tsx
|
#: src/features/manga/components/MangaToolbarMenu.tsx
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/migration/screens/Migrate.tsx
|
#: src/features/migration/screens/Migrate.tsx
|
||||||
msgid "Migrate"
|
msgid "Migrate"
|
||||||
msgstr "Taşı"
|
msgstr "Taşı"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Migrating manga…"
|
msgid "Migrating manga…"
|
||||||
msgstr "Manga taşınıyor…"
|
msgstr "Manga taşınıyor…"
|
||||||
|
|
||||||
@@ -2004,7 +2004,7 @@ msgstr "Sunucusunda depolanan kullanıcı arayüzü ayarlarını kullanmak için
|
|||||||
msgid "Select all"
|
msgid "Select all"
|
||||||
msgstr "Hepsini seç"
|
msgstr "Hepsini seç"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Select data to include"
|
msgid "Select data to include"
|
||||||
msgstr "Dahil edilecek verileri seçin"
|
msgstr "Dahil edilecek verileri seçin"
|
||||||
|
|
||||||
@@ -2049,7 +2049,7 @@ msgid "Show continue reading button"
|
|||||||
msgstr "Okumaya devam et düğmesini göster"
|
msgstr "Okumaya devam et düğmesini göster"
|
||||||
|
|
||||||
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Show entry"
|
msgid "Show entry"
|
||||||
msgstr "Öğeyi göster"
|
msgstr "Öğeyi göster"
|
||||||
|
|
||||||
@@ -2800,14 +2800,14 @@ msgstr "Kategoriler"
|
|||||||
|
|
||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Category"
|
msgid "Category"
|
||||||
msgstr "Kategori"
|
msgstr "Kategori"
|
||||||
|
|
||||||
#: src/features/chapter/components/cards/ChapterCard.tsx
|
#: src/features/chapter/components/cards/ChapterCard.tsx
|
||||||
#: src/features/downloads/components/DownloadAheadSetting.tsx
|
#: src/features/downloads/components/DownloadAheadSetting.tsx
|
||||||
#: src/features/downloads/screens/DownloadSettings.tsx
|
#: src/features/downloads/screens/DownloadSettings.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
||||||
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
||||||
#: src/features/tracker/components/cards/TrackerActiveCard.tsx
|
#: src/features/tracker/components/cards/TrackerActiveCard.tsx
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ msgstr "За джерелом"
|
|||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/category/components/CategorySelect.tsx
|
#: src/features/category/components/CategorySelect.tsx
|
||||||
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
||||||
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
||||||
#: src/features/source/browse/components/SourceOptions.tsx
|
#: src/features/source/browse/components/SourceOptions.tsx
|
||||||
@@ -317,7 +317,7 @@ msgid "Continuous vertical"
|
|||||||
msgstr "Вертикальний безперервний"
|
msgstr "Вертикальний безперервний"
|
||||||
|
|
||||||
#: src/features/manga/components/details/MangaDetails.tsx
|
#: src/features/manga/components/details/MangaDetails.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Copy"
|
msgid "Copy"
|
||||||
msgstr "Скопіювати"
|
msgstr "Скопіювати"
|
||||||
|
|
||||||
@@ -910,7 +910,7 @@ msgstr "Позначити вибране як непрочитане"
|
|||||||
#: src/features/manga/components/MangaToolbarMenu.tsx
|
#: src/features/manga/components/MangaToolbarMenu.tsx
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Migrate"
|
msgid "Migrate"
|
||||||
msgstr "Мігрувати"
|
msgstr "Мігрувати"
|
||||||
|
|
||||||
@@ -920,7 +920,7 @@ msgstr "Мігрувати"
|
|||||||
msgid "Migrate"
|
msgid "Migrate"
|
||||||
msgstr "Мігрувати"
|
msgstr "Мігрувати"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Migrating manga…"
|
msgid "Migrating manga…"
|
||||||
msgstr "Міграція манґи…"
|
msgstr "Міграція манґи…"
|
||||||
|
|
||||||
@@ -1198,7 +1198,7 @@ msgstr "Виберіть пристрій для використання йог
|
|||||||
msgid "Select all"
|
msgid "Select all"
|
||||||
msgstr "Вибрати все"
|
msgstr "Вибрати все"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Select data to include"
|
msgid "Select data to include"
|
||||||
msgstr "Вибрати дані, які потрібно включити"
|
msgstr "Вибрати дані, які потрібно включити"
|
||||||
|
|
||||||
@@ -1246,7 +1246,7 @@ msgid "Show continue reading button"
|
|||||||
msgstr "Показувати кнопку продовження читання"
|
msgstr "Показувати кнопку продовження читання"
|
||||||
|
|
||||||
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Show entry"
|
msgid "Show entry"
|
||||||
msgstr "Показати елемент"
|
msgstr "Показати елемент"
|
||||||
|
|
||||||
|
|||||||
@@ -374,7 +374,7 @@ msgstr "Hết thời gian chờ"
|
|||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/category/components/CategorySelect.tsx
|
#: src/features/category/components/CategorySelect.tsx
|
||||||
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
||||||
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
||||||
#: src/features/source/browse/components/SourceOptions.tsx
|
#: src/features/source/browse/components/SourceOptions.tsx
|
||||||
@@ -587,7 +587,7 @@ msgid "Copied to clipboard"
|
|||||||
msgstr "Đã sao chép vào bảng nhớ tạm"
|
msgstr "Đã sao chép vào bảng nhớ tạm"
|
||||||
|
|
||||||
#: src/features/manga/components/details/MangaDetails.tsx
|
#: src/features/manga/components/details/MangaDetails.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Copy"
|
msgid "Copy"
|
||||||
msgstr "Sao chép"
|
msgstr "Sao chép"
|
||||||
|
|
||||||
@@ -873,7 +873,7 @@ msgstr "Xóa chương sau khi được đánh dấu là đã đọc"
|
|||||||
msgid "Delete chapters"
|
msgid "Delete chapters"
|
||||||
msgstr "Xóa chương"
|
msgstr "Xóa chương"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Delete downloaded"
|
msgid "Delete downloaded"
|
||||||
msgstr "Xóa tải về"
|
msgstr "Xóa tải về"
|
||||||
|
|
||||||
@@ -1808,12 +1808,12 @@ msgstr "Danh sách"
|
|||||||
#: src/features/manga/components/MangaToolbarMenu.tsx
|
#: src/features/manga/components/MangaToolbarMenu.tsx
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/migration/screens/Migrate.tsx
|
#: src/features/migration/screens/Migrate.tsx
|
||||||
msgid "Migrate"
|
msgid "Migrate"
|
||||||
msgstr "Di chuyển"
|
msgstr "Di chuyển"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Migrating manga…"
|
msgid "Migrating manga…"
|
||||||
msgstr "Di chuyển truyện…"
|
msgstr "Di chuyển truyện…"
|
||||||
|
|
||||||
@@ -2460,7 +2460,7 @@ msgstr "Chọn một thiết bị để sử dụng các cài đặt cá nhân
|
|||||||
msgid "Select all"
|
msgid "Select all"
|
||||||
msgstr "Chọn tất cả"
|
msgstr "Chọn tất cả"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Select data to include"
|
msgid "Select data to include"
|
||||||
msgstr "Lụa chọn dữ liệu để thêm vào"
|
msgstr "Lụa chọn dữ liệu để thêm vào"
|
||||||
|
|
||||||
@@ -2540,7 +2540,7 @@ msgid "Show continue reading button"
|
|||||||
msgstr "Hiển thị nút tiếp tục đọc"
|
msgstr "Hiển thị nút tiếp tục đọc"
|
||||||
|
|
||||||
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Show entry"
|
msgid "Show entry"
|
||||||
msgstr "Hiển thị mục"
|
msgstr "Hiển thị mục"
|
||||||
|
|
||||||
@@ -2946,7 +2946,7 @@ msgstr "Dịch vụ"
|
|||||||
|
|
||||||
#: src/features/backup/Backup.constants.ts
|
#: src/features/backup/Backup.constants.ts
|
||||||
#: src/features/manga/components/TrackMangaButton.tsx
|
#: src/features/manga/components/TrackMangaButton.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/settings/screens/Settings.tsx
|
#: src/features/settings/screens/Settings.tsx
|
||||||
#: src/features/tracker/screens/TrackingSettings.tsx
|
#: src/features/tracker/screens/TrackingSettings.tsx
|
||||||
msgid "Tracking"
|
msgid "Tracking"
|
||||||
|
|||||||
@@ -390,7 +390,7 @@ msgstr "调用超时"
|
|||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/category/components/CategorySelect.tsx
|
#: src/features/category/components/CategorySelect.tsx
|
||||||
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
||||||
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
||||||
#: src/features/source/browse/components/SourceOptions.tsx
|
#: src/features/source/browse/components/SourceOptions.tsx
|
||||||
@@ -604,7 +604,7 @@ msgid "Copied to clipboard"
|
|||||||
msgstr "已复制到剪贴板"
|
msgstr "已复制到剪贴板"
|
||||||
|
|
||||||
#: src/features/manga/components/details/MangaDetails.tsx
|
#: src/features/manga/components/details/MangaDetails.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Copy"
|
msgid "Copy"
|
||||||
msgstr "复制"
|
msgstr "复制"
|
||||||
|
|
||||||
@@ -890,7 +890,7 @@ msgstr "手动标记为已读后删除章节"
|
|||||||
msgid "Delete chapters"
|
msgid "Delete chapters"
|
||||||
msgstr "删除章节"
|
msgstr "删除章节"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Delete downloaded"
|
msgid "Delete downloaded"
|
||||||
msgstr "删除已下载项"
|
msgstr "删除已下载项"
|
||||||
|
|
||||||
@@ -1822,12 +1822,12 @@ msgstr "菜单"
|
|||||||
#: src/features/manga/components/MangaToolbarMenu.tsx
|
#: src/features/manga/components/MangaToolbarMenu.tsx
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/migration/screens/Migrate.tsx
|
#: src/features/migration/screens/Migrate.tsx
|
||||||
msgid "Migrate"
|
msgid "Migrate"
|
||||||
msgstr "迁移"
|
msgstr "迁移"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Migrating manga…"
|
msgid "Migrating manga…"
|
||||||
msgstr "正在迁移漫画…"
|
msgstr "正在迁移漫画…"
|
||||||
|
|
||||||
@@ -2464,7 +2464,7 @@ msgstr "选择一个设备以使用其存储在服务器中的UI设置"
|
|||||||
msgid "Select all"
|
msgid "Select all"
|
||||||
msgstr "全选"
|
msgstr "全选"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Select data to include"
|
msgid "Select data to include"
|
||||||
msgstr "选择要包含的数据"
|
msgstr "选择要包含的数据"
|
||||||
|
|
||||||
@@ -2540,7 +2540,7 @@ msgid "Show continue reading button"
|
|||||||
msgstr "显示继续阅读按钮"
|
msgstr "显示继续阅读按钮"
|
||||||
|
|
||||||
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Show entry"
|
msgid "Show entry"
|
||||||
msgstr "显示条目"
|
msgstr "显示条目"
|
||||||
|
|
||||||
@@ -2943,7 +2943,7 @@ msgstr "追踪列表"
|
|||||||
|
|
||||||
#: src/features/backup/Backup.constants.ts
|
#: src/features/backup/Backup.constants.ts
|
||||||
#: src/features/manga/components/TrackMangaButton.tsx
|
#: src/features/manga/components/TrackMangaButton.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/settings/screens/Settings.tsx
|
#: src/features/settings/screens/Settings.tsx
|
||||||
#: src/features/tracker/screens/TrackingSettings.tsx
|
#: src/features/tracker/screens/TrackingSettings.tsx
|
||||||
msgid "Tracking"
|
msgid "Tracking"
|
||||||
@@ -3262,14 +3262,14 @@ msgstr "{0}% | 更新中……"
|
|||||||
|
|
||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Category"
|
msgid "Category"
|
||||||
msgstr "分类"
|
msgstr "分类"
|
||||||
|
|
||||||
#: src/features/chapter/components/cards/ChapterCard.tsx
|
#: src/features/chapter/components/cards/ChapterCard.tsx
|
||||||
#: src/features/downloads/components/DownloadAheadSetting.tsx
|
#: src/features/downloads/components/DownloadAheadSetting.tsx
|
||||||
#: src/features/downloads/screens/DownloadSettings.tsx
|
#: src/features/downloads/screens/DownloadSettings.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
||||||
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
||||||
#: src/features/tracker/components/cards/TrackerActiveCard.tsx
|
#: src/features/tracker/components/cards/TrackerActiveCard.tsx
|
||||||
|
|||||||
@@ -365,7 +365,7 @@ msgstr "依照更新日期"
|
|||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/category/components/CategorySelect.tsx
|
#: src/features/category/components/CategorySelect.tsx
|
||||||
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
#: src/features/category/components/CreateOrEditCategoryDialog.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
|
||||||
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
#: src/features/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx
|
||||||
#: src/features/source/browse/components/SourceOptions.tsx
|
#: src/features/source/browse/components/SourceOptions.tsx
|
||||||
@@ -548,7 +548,7 @@ msgid "Copied to clipboard"
|
|||||||
msgstr "已複製到剪貼簿"
|
msgstr "已複製到剪貼簿"
|
||||||
|
|
||||||
#: src/features/manga/components/details/MangaDetails.tsx
|
#: src/features/manga/components/details/MangaDetails.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Copy"
|
msgid "Copy"
|
||||||
msgstr "複製"
|
msgstr "複製"
|
||||||
|
|
||||||
@@ -834,7 +834,7 @@ msgstr "手動標記為已讀後刪除章節"
|
|||||||
msgid "Delete chapters"
|
msgid "Delete chapters"
|
||||||
msgstr "刪除章節"
|
msgstr "刪除章節"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Delete downloaded"
|
msgid "Delete downloaded"
|
||||||
msgstr "刪除已下載項目"
|
msgstr "刪除已下載項目"
|
||||||
|
|
||||||
@@ -1702,12 +1702,12 @@ msgstr "選單"
|
|||||||
#: src/features/manga/components/MangaToolbarMenu.tsx
|
#: src/features/manga/components/MangaToolbarMenu.tsx
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/manga/Manga.constants.ts
|
#: src/features/manga/Manga.constants.ts
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/migration/screens/Migrate.tsx
|
#: src/features/migration/screens/Migrate.tsx
|
||||||
msgid "Migrate"
|
msgid "Migrate"
|
||||||
msgstr "遷移"
|
msgstr "遷移"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Migrating manga…"
|
msgid "Migrating manga…"
|
||||||
msgstr "正在遷移漫畫…"
|
msgstr "正在遷移漫畫…"
|
||||||
|
|
||||||
@@ -2319,7 +2319,7 @@ msgstr "選擇一個裝置以使用它儲存在伺服器中的 UI 設定"
|
|||||||
msgid "Select all"
|
msgid "Select all"
|
||||||
msgstr "全選"
|
msgstr "全選"
|
||||||
|
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Select data to include"
|
msgid "Select data to include"
|
||||||
msgstr "選擇要包含的資料"
|
msgstr "選擇要包含的資料"
|
||||||
|
|
||||||
@@ -2395,7 +2395,7 @@ msgid "Show continue reading button"
|
|||||||
msgstr "顯示繼續閱讀按鈕"
|
msgstr "顯示繼續閱讀按鈕"
|
||||||
|
|
||||||
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
#: src/features/manga/hooks/useManageMangaLibraryState.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Show entry"
|
msgid "Show entry"
|
||||||
msgstr "顯示條目"
|
msgstr "顯示條目"
|
||||||
|
|
||||||
@@ -2788,7 +2788,7 @@ msgstr "追蹤器"
|
|||||||
|
|
||||||
#: src/features/backup/Backup.constants.ts
|
#: src/features/backup/Backup.constants.ts
|
||||||
#: src/features/manga/components/TrackMangaButton.tsx
|
#: src/features/manga/components/TrackMangaButton.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/settings/screens/Settings.tsx
|
#: src/features/settings/screens/Settings.tsx
|
||||||
#: src/features/tracker/screens/TrackingSettings.tsx
|
#: src/features/tracker/screens/TrackingSettings.tsx
|
||||||
msgid "Tracking"
|
msgid "Tracking"
|
||||||
@@ -3108,14 +3108,14 @@ msgstr "呼叫逾時"
|
|||||||
|
|
||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
#: src/features/category/components/CategoriesInclusionSetting.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
msgid "Category"
|
msgid "Category"
|
||||||
msgstr "分類"
|
msgstr "分類"
|
||||||
|
|
||||||
#: src/features/chapter/components/cards/ChapterCard.tsx
|
#: src/features/chapter/components/cards/ChapterCard.tsx
|
||||||
#: src/features/downloads/components/DownloadAheadSetting.tsx
|
#: src/features/downloads/components/DownloadAheadSetting.tsx
|
||||||
#: src/features/downloads/screens/DownloadSettings.tsx
|
#: src/features/downloads/screens/DownloadSettings.tsx
|
||||||
#: src/features/migration/components/MigrateDialog.tsx
|
#: src/features/migration/components/MigrationSingleMangaDialog.tsx
|
||||||
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
||||||
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
#: src/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopChapterNavigation.tsx
|
||||||
#: src/features/tracker/components/cards/TrackerActiveCard.tsx
|
#: src/features/tracker/components/cards/TrackerActiveCard.tsx
|
||||||
|
|||||||
@@ -3599,7 +3599,7 @@ export type GetCategoryMangasQueryVariables = Exact<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type GetCategoryMangasQuery = { __typename?: 'Query', category: { __typename?: 'CategoryType', id: number, mangas: { __typename?: 'MangaNodeList', totalCount: number, nodes: Array<{ __typename?: 'MangaType', genre: Array<string>, lastFetchedAt?: string | null, inLibraryAt: string, status: MangaStatus, artist?: string | null, author?: string | null, description?: string | null, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, sourceId: string, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, meta: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }>, source?: { __typename?: 'SourceType', id: string, displayName: string } | null, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number, nodes: Array<{ __typename?: 'TrackRecordType', id: number, trackerId: number }> }, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null }>, pageInfo: { __typename?: 'PageInfo', endCursor?: string | null, hasNextPage: boolean, hasPreviousPage: boolean, startCursor?: string | null } } } };
|
export type GetCategoryMangasQuery = { __typename?: 'Query', category: { __typename?: 'CategoryType', id: number, mangas: { __typename?: 'MangaNodeList', totalCount: number, nodes: Array<{ __typename?: 'MangaType', genre: Array<string>, lastFetchedAt?: string | null, inLibraryAt: string, status: MangaStatus, artist?: string | null, author?: string | null, description?: string | null, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, sourceId: string, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, meta: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }>, source?: { __typename?: 'SourceType', id: string, displayName: string } | null, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number, nodes: Array<{ __typename?: 'TrackRecordType', id: number, trackerId: number }> }, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null, highestNumberedChapter?: { __typename?: 'ChapterType', id: number, chapterNumber: number } | null }>, pageInfo: { __typename?: 'PageInfo', endCursor?: string | null, hasNextPage: boolean, hasPreviousPage: boolean, startCursor?: string | null } } } };
|
||||||
|
|
||||||
export type ChapterMetaFieldsFragment = { __typename?: 'ChapterMetaType', chapterId: number, key: string, value: string };
|
export type ChapterMetaFieldsFragment = { __typename?: 'ChapterMetaType', chapterId: number, key: string, value: string };
|
||||||
|
|
||||||
@@ -3627,7 +3627,7 @@ export type GetMangaChaptersFetchMutationVariables = Exact<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type GetMangaChaptersFetchMutation = { __typename?: 'Mutation', fetchChapters?: { __typename?: 'FetchChaptersPayload', chapters: Array<{ __typename?: 'ChapterType', fetchedAt: string, uploadDate: string, lastReadAt: string, id: number, name: string, mangaId: number, scanlator?: string | null, realUrl?: string | null, sourceOrder: number, chapterNumber: number, isRead: boolean, isDownloaded: boolean, isBookmarked: boolean, manga: { __typename?: 'MangaType', id: number, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null } }> } | null };
|
export type GetMangaChaptersFetchMutation = { __typename?: 'Mutation', fetchChapters?: { __typename?: 'FetchChaptersPayload', chapters: Array<{ __typename?: 'ChapterType', fetchedAt: string, uploadDate: string, lastReadAt: string, id: number, name: string, mangaId: number, scanlator?: string | null, realUrl?: string | null, sourceOrder: number, chapterNumber: number, isRead: boolean, isDownloaded: boolean, isBookmarked: boolean, manga: { __typename?: 'MangaType', id: number, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null, highestNumberedChapter?: { __typename?: 'ChapterType', id: number, chapterNumber: number } | null } }> } | null };
|
||||||
|
|
||||||
export type UpdateChapterMutationVariables = Exact<{
|
export type UpdateChapterMutationVariables = Exact<{
|
||||||
input: UpdateChapterInput;
|
input: UpdateChapterInput;
|
||||||
@@ -3914,13 +3914,15 @@ export type MangaBaseFieldsFragment = { __typename?: 'MangaType', id: number, ti
|
|||||||
|
|
||||||
export type MangaChapterStatFieldsFragment = { __typename?: 'MangaType', id: number, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, chapters: { __typename?: 'ChapterNodeList', totalCount: number } };
|
export type MangaChapterStatFieldsFragment = { __typename?: 'MangaType', id: number, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, chapters: { __typename?: 'ChapterNodeList', totalCount: number } };
|
||||||
|
|
||||||
export type MangaChapterNodeFieldsFragment = { __typename?: 'MangaType', firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null };
|
export type MangaChapterNodeFieldsFragment = { __typename?: 'MangaType', firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null, highestNumberedChapter?: { __typename?: 'ChapterType', id: number, chapterNumber: number } | null };
|
||||||
|
|
||||||
export type MangaReaderFieldsFragment = { __typename?: 'MangaType', genre: Array<string>, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, sourceId: string, source?: { __typename?: 'SourceType', id: string, name: string, displayName: string, lang: string } | null, meta: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }>, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number } };
|
export type MangaReaderFieldsFragment = { __typename?: 'MangaType', genre: Array<string>, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, sourceId: string, source?: { __typename?: 'SourceType', id: string, name: string, displayName: string, lang: string } | null, meta: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }>, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number } };
|
||||||
|
|
||||||
export type MangaLibraryFieldsFragment = { __typename?: 'MangaType', genre: Array<string>, lastFetchedAt?: string | null, inLibraryAt: string, status: MangaStatus, artist?: string | null, author?: string | null, description?: string | null, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, sourceId: string, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, meta: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }>, source?: { __typename?: 'SourceType', id: string, displayName: string } | null, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number, nodes: Array<{ __typename?: 'TrackRecordType', id: number, trackerId: number }> }, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null };
|
export type MangaLibraryFieldsFragment = { __typename?: 'MangaType', genre: Array<string>, lastFetchedAt?: string | null, inLibraryAt: string, status: MangaStatus, artist?: string | null, author?: string | null, description?: string | null, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, sourceId: string, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, meta: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }>, source?: { __typename?: 'SourceType', id: string, displayName: string } | null, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number, nodes: Array<{ __typename?: 'TrackRecordType', id: number, trackerId: number }> }, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null, highestNumberedChapter?: { __typename?: 'ChapterType', id: number, chapterNumber: number } | null };
|
||||||
|
|
||||||
export type MangaScreenFieldsFragment = { __typename?: 'MangaType', artist?: string | null, author?: string | null, description?: string | null, status: MangaStatus, realUrl?: string | null, sourceId: string, genre: Array<string>, lastFetchedAt?: string | null, inLibraryAt: string, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, meta: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }>, source?: { __typename?: 'SourceType', id: string, displayName: string } | null, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number, nodes: Array<{ __typename?: 'TrackRecordType', id: number, trackerId: number }> }, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null };
|
export type MangaMigrationFieldsFragment = { __typename?: 'MangaType', artist?: string | null, author?: string | null, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, sourceId: string, source?: { __typename?: 'SourceType', id: string, name: string, displayName: string } | null, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null, highestNumberedChapter?: { __typename?: 'ChapterType', id: number, chapterNumber: number } | null };
|
||||||
|
|
||||||
|
export type MangaScreenFieldsFragment = { __typename?: 'MangaType', artist?: string | null, author?: string | null, description?: string | null, status: MangaStatus, realUrl?: string | null, sourceId: string, genre: Array<string>, lastFetchedAt?: string | null, inLibraryAt: string, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, meta: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }>, source?: { __typename?: 'SourceType', id: string, name: string, displayName: string } | null, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number, nodes: Array<{ __typename?: 'TrackRecordType', id: number, trackerId: number }> }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null, highestNumberedChapter?: { __typename?: 'ChapterType', id: number, chapterNumber: number } | null, chapters: { __typename?: 'ChapterNodeList', totalCount: number } };
|
||||||
|
|
||||||
export type MangaLibraryDuplicateScreenFieldsFragment = { __typename?: 'MangaType', description?: string | null, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, sourceId: string, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, chapters: { __typename?: 'ChapterNodeList', totalCount: number } };
|
export type MangaLibraryDuplicateScreenFieldsFragment = { __typename?: 'MangaType', description?: string | null, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, sourceId: string, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, chapters: { __typename?: 'ChapterNodeList', totalCount: number } };
|
||||||
|
|
||||||
@@ -3929,7 +3931,7 @@ export type GetMangaFetchMutationVariables = Exact<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type GetMangaFetchMutation = { __typename?: 'Mutation', fetchManga?: { __typename?: 'FetchMangaPayload', manga: { __typename?: 'MangaType', artist?: string | null, author?: string | null, description?: string | null, status: MangaStatus, realUrl?: string | null, sourceId: string, genre: Array<string>, lastFetchedAt?: string | null, inLibraryAt: string, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, meta: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }>, source?: { __typename?: 'SourceType', id: string, displayName: string } | null, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number, nodes: Array<{ __typename?: 'TrackRecordType', id: number, trackerId: number }> }, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null } } | null };
|
export type GetMangaFetchMutation = { __typename?: 'Mutation', fetchManga?: { __typename?: 'FetchMangaPayload', manga: { __typename?: 'MangaType', artist?: string | null, author?: string | null, description?: string | null, status: MangaStatus, realUrl?: string | null, sourceId: string, genre: Array<string>, lastFetchedAt?: string | null, inLibraryAt: string, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, meta: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }>, source?: { __typename?: 'SourceType', id: string, name: string, displayName: string } | null, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number, nodes: Array<{ __typename?: 'TrackRecordType', id: number, trackerId: number }> }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null, highestNumberedChapter?: { __typename?: 'ChapterType', id: number, chapterNumber: number } | null, chapters: { __typename?: 'ChapterNodeList', totalCount: number } } } | null };
|
||||||
|
|
||||||
export type GetMangaToMigrateToFetchMutationVariables = Exact<{
|
export type GetMangaToMigrateToFetchMutationVariables = Exact<{
|
||||||
id: Scalars['Int']['input'];
|
id: Scalars['Int']['input'];
|
||||||
@@ -3992,7 +3994,7 @@ export type GetMangaScreenQueryVariables = Exact<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type GetMangaScreenQuery = { __typename?: 'Query', manga: { __typename?: 'MangaType', artist?: string | null, author?: string | null, description?: string | null, status: MangaStatus, realUrl?: string | null, sourceId: string, genre: Array<string>, lastFetchedAt?: string | null, inLibraryAt: string, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, meta: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }>, source?: { __typename?: 'SourceType', id: string, displayName: string } | null, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number, nodes: Array<{ __typename?: 'TrackRecordType', id: number, trackerId: number }> }, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null } };
|
export type GetMangaScreenQuery = { __typename?: 'Query', manga: { __typename?: 'MangaType', artist?: string | null, author?: string | null, description?: string | null, status: MangaStatus, realUrl?: string | null, sourceId: string, genre: Array<string>, lastFetchedAt?: string | null, inLibraryAt: string, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, meta: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }>, source?: { __typename?: 'SourceType', id: string, name: string, displayName: string } | null, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number, nodes: Array<{ __typename?: 'TrackRecordType', id: number, trackerId: number }> }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null, highestNumberedChapter?: { __typename?: 'ChapterType', id: number, chapterNumber: number } | null, chapters: { __typename?: 'ChapterNodeList', totalCount: number } } };
|
||||||
|
|
||||||
export type GetMangaReaderQueryVariables = Exact<{
|
export type GetMangaReaderQueryVariables = Exact<{
|
||||||
id: Scalars['Int']['input'];
|
id: Scalars['Int']['input'];
|
||||||
@@ -4052,7 +4054,7 @@ export type GetMangasLibraryQueryVariables = Exact<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type GetMangasLibraryQuery = { __typename?: 'Query', mangas: { __typename?: 'MangaNodeList', totalCount: number, nodes: Array<{ __typename?: 'MangaType', genre: Array<string>, lastFetchedAt?: string | null, inLibraryAt: string, status: MangaStatus, artist?: string | null, author?: string | null, description?: string | null, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, sourceId: string, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, meta: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }>, source?: { __typename?: 'SourceType', id: string, displayName: string } | null, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number, nodes: Array<{ __typename?: 'TrackRecordType', id: number, trackerId: number }> }, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null }>, pageInfo: { __typename?: 'PageInfo', endCursor?: string | null, hasNextPage: boolean, hasPreviousPage: boolean, startCursor?: string | null } } };
|
export type GetMangasLibraryQuery = { __typename?: 'Query', mangas: { __typename?: 'MangaNodeList', totalCount: number, nodes: Array<{ __typename?: 'MangaType', genre: Array<string>, lastFetchedAt?: string | null, inLibraryAt: string, status: MangaStatus, artist?: string | null, author?: string | null, description?: string | null, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, sourceId: string, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, meta: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }>, source?: { __typename?: 'SourceType', id: string, displayName: string } | null, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number, nodes: Array<{ __typename?: 'TrackRecordType', id: number, trackerId: number }> }, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null, highestNumberedChapter?: { __typename?: 'ChapterType', id: number, chapterNumber: number } | null }>, pageInfo: { __typename?: 'PageInfo', endCursor?: string | null, hasNextPage: boolean, hasPreviousPage: boolean, startCursor?: string | null } } };
|
||||||
|
|
||||||
export type GetMangasDuplicatesQueryVariables = Exact<{
|
export type GetMangasDuplicatesQueryVariables = Exact<{
|
||||||
after?: InputMaybe<Scalars['Cursor']['input']>;
|
after?: InputMaybe<Scalars['Cursor']['input']>;
|
||||||
@@ -4073,7 +4075,7 @@ export type GetMigratableSourceMangasQueryVariables = Exact<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type GetMigratableSourceMangasQuery = { __typename?: 'Query', mangas: { __typename?: 'MangaNodeList', nodes: Array<{ __typename?: 'MangaType', id: number, title: string, thumbnailUrl?: string | null, sourceId: string, categories: { __typename?: 'CategoryNodeList', nodes: Array<{ __typename?: 'CategoryType', id: number }> } }> } };
|
export type GetMigratableSourceMangasQuery = { __typename?: 'Query', mangas: { __typename?: 'MangaNodeList', nodes: Array<{ __typename?: 'MangaType', artist?: string | null, author?: string | null, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, sourceId: string, source?: { __typename?: 'SourceType', id: string, name: string, displayName: string } | null, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null, highestNumberedChapter?: { __typename?: 'ChapterType', id: number, chapterNumber: number } | null }> } };
|
||||||
|
|
||||||
export type GetLibraryMangaCountQueryVariables = Exact<{ [key: string]: never; }>;
|
export type GetLibraryMangaCountQueryVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
@@ -4224,6 +4226,13 @@ export type GetSourceMangasFetchMutationVariables = Exact<{
|
|||||||
|
|
||||||
export type GetSourceMangasFetchMutation = { __typename?: 'Mutation', fetchSourceManga?: { __typename?: 'FetchSourceMangaPayload', hasNextPage: boolean, mangas: Array<{ __typename?: 'MangaType', id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, sourceId: string }> } | null };
|
export type GetSourceMangasFetchMutation = { __typename?: 'Mutation', fetchSourceManga?: { __typename?: 'FetchSourceMangaPayload', hasNextPage: boolean, mangas: Array<{ __typename?: 'MangaType', id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, sourceId: string }> } | null };
|
||||||
|
|
||||||
|
export type GetMigrationSourceMangasFetchMutationVariables = Exact<{
|
||||||
|
input: FetchSourceMangaInput;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type GetMigrationSourceMangasFetchMutation = { __typename?: 'Mutation', fetchSourceManga?: { __typename?: 'FetchSourceMangaPayload', hasNextPage: boolean, mangas: Array<{ __typename?: 'MangaType', artist?: string | null, author?: string | null, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, sourceId: string, source?: { __typename?: 'SourceType', id: string, name: string, displayName: string } | null, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null, highestNumberedChapter?: { __typename?: 'ChapterType', id: number, chapterNumber: number } | null }> } | null };
|
||||||
|
|
||||||
export type UpdateSourcePreferencesMutationVariables = Exact<{
|
export type UpdateSourcePreferencesMutationVariables = Exact<{
|
||||||
input: UpdateSourcePreferenceInput;
|
input: UpdateSourcePreferenceInput;
|
||||||
}>;
|
}>;
|
||||||
@@ -4383,15 +4392,15 @@ export type TrackerSearchQueryVariables = Exact<{
|
|||||||
|
|
||||||
export type TrackerSearchQuery = { __typename?: 'Query', searchTracker: { __typename?: 'SearchTrackerPayload', trackSearches: Array<{ __typename?: 'TrackSearchType', id: number, remoteId: string, title: string, trackingUrl: string, coverUrl: string, publishingType: string, startDate: string, publishingStatus: string, summary: string, score: number, totalChapters: number }> } };
|
export type TrackerSearchQuery = { __typename?: 'Query', searchTracker: { __typename?: 'SearchTrackerPayload', trackSearches: Array<{ __typename?: 'TrackSearchType', id: number, remoteId: string, title: string, trackingUrl: string, coverUrl: string, publishingType: string, startDate: string, publishingStatus: string, summary: string, score: number, totalChapters: number }> } };
|
||||||
|
|
||||||
export type UpdaterMangaFieldsFragment = { __typename?: 'MangaUpdateType', status: MangaJobStatus, manga: { __typename?: 'MangaType', id: number, title: string, thumbnailUrl?: string | null, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null } };
|
export type UpdaterMangaFieldsFragment = { __typename?: 'MangaUpdateType', status: MangaJobStatus, manga: { __typename?: 'MangaType', id: number, title: string, thumbnailUrl?: string | null, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null, highestNumberedChapter?: { __typename?: 'ChapterType', id: number, chapterNumber: number } | null } };
|
||||||
|
|
||||||
export type UpdaterCategoryFieldsFragment = { __typename?: 'CategoryUpdateType', status: CategoryJobStatus, category: { __typename?: 'CategoryType', id: number, name: string } };
|
export type UpdaterCategoryFieldsFragment = { __typename?: 'CategoryUpdateType', status: CategoryJobStatus, category: { __typename?: 'CategoryType', id: number, name: string } };
|
||||||
|
|
||||||
export type UpdaterJobInfoFieldsFragment = { __typename?: 'UpdaterJobsInfoType', isRunning: boolean, totalJobs: number, finishedJobs: number, skippedCategoriesCount: number, skippedMangasCount: number };
|
export type UpdaterJobInfoFieldsFragment = { __typename?: 'UpdaterJobsInfoType', isRunning: boolean, totalJobs: number, finishedJobs: number, skippedCategoriesCount: number, skippedMangasCount: number };
|
||||||
|
|
||||||
export type UpdaterStatusFieldsFragment = { __typename?: 'LibraryUpdateStatus', jobsInfo: { __typename?: 'UpdaterJobsInfoType', isRunning: boolean, totalJobs: number, finishedJobs: number, skippedCategoriesCount: number, skippedMangasCount: number }, categoryUpdates: Array<{ __typename?: 'CategoryUpdateType', status: CategoryJobStatus, category: { __typename?: 'CategoryType', id: number, name: string } }>, mangaUpdates: Array<{ __typename?: 'MangaUpdateType', status: MangaJobStatus, manga: { __typename?: 'MangaType', id: number, title: string, thumbnailUrl?: string | null, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null } }> };
|
export type UpdaterStatusFieldsFragment = { __typename?: 'LibraryUpdateStatus', jobsInfo: { __typename?: 'UpdaterJobsInfoType', isRunning: boolean, totalJobs: number, finishedJobs: number, skippedCategoriesCount: number, skippedMangasCount: number }, categoryUpdates: Array<{ __typename?: 'CategoryUpdateType', status: CategoryJobStatus, category: { __typename?: 'CategoryType', id: number, name: string } }>, mangaUpdates: Array<{ __typename?: 'MangaUpdateType', status: MangaJobStatus, manga: { __typename?: 'MangaType', id: number, title: string, thumbnailUrl?: string | null, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null, highestNumberedChapter?: { __typename?: 'ChapterType', id: number, chapterNumber: number } | null } }> };
|
||||||
|
|
||||||
export type UpdaterSubscriptionFieldsFragment = { __typename?: 'UpdaterUpdates', omittedUpdates: boolean, jobsInfo: { __typename?: 'UpdaterJobsInfoType', isRunning: boolean, totalJobs: number, finishedJobs: number, skippedCategoriesCount: number, skippedMangasCount: number }, categoryUpdates: Array<{ __typename?: 'CategoryUpdateType', status: CategoryJobStatus, category: { __typename?: 'CategoryType', id: number, name: string } }>, mangaUpdates: Array<{ __typename?: 'MangaUpdateType', status: MangaJobStatus, manga: { __typename?: 'MangaType', id: number, title: string, thumbnailUrl?: string | null, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null } }> };
|
export type UpdaterSubscriptionFieldsFragment = { __typename?: 'UpdaterUpdates', omittedUpdates: boolean, jobsInfo: { __typename?: 'UpdaterJobsInfoType', isRunning: boolean, totalJobs: number, finishedJobs: number, skippedCategoriesCount: number, skippedMangasCount: number }, categoryUpdates: Array<{ __typename?: 'CategoryUpdateType', status: CategoryJobStatus, category: { __typename?: 'CategoryType', id: number, name: string } }>, mangaUpdates: Array<{ __typename?: 'MangaUpdateType', status: MangaJobStatus, manga: { __typename?: 'MangaType', id: number, title: string, thumbnailUrl?: string | null, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null, highestNumberedChapter?: { __typename?: 'ChapterType', id: number, chapterNumber: number } | null } }> };
|
||||||
|
|
||||||
export type UpdaterStartStopFieldsFragment = { __typename?: 'UpdateStatus', isRunning: boolean };
|
export type UpdaterStartStopFieldsFragment = { __typename?: 'UpdateStatus', isRunning: boolean };
|
||||||
|
|
||||||
@@ -4400,7 +4409,7 @@ export type UpdateLibraryMutationVariables = Exact<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type UpdateLibraryMutation = { __typename?: 'Mutation', updateLibrary?: { __typename?: 'UpdateLibraryPayload', updateStatus: { __typename?: 'LibraryUpdateStatus', jobsInfo: { __typename?: 'UpdaterJobsInfoType', isRunning: boolean, totalJobs: number, finishedJobs: number, skippedCategoriesCount: number, skippedMangasCount: number }, categoryUpdates: Array<{ __typename?: 'CategoryUpdateType', status: CategoryJobStatus, category: { __typename?: 'CategoryType', id: number, name: string } }>, mangaUpdates: Array<{ __typename?: 'MangaUpdateType', status: MangaJobStatus, manga: { __typename?: 'MangaType', id: number, title: string, thumbnailUrl?: string | null, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null } }> } } | null };
|
export type UpdateLibraryMutation = { __typename?: 'Mutation', updateLibrary?: { __typename?: 'UpdateLibraryPayload', updateStatus: { __typename?: 'LibraryUpdateStatus', jobsInfo: { __typename?: 'UpdaterJobsInfoType', isRunning: boolean, totalJobs: number, finishedJobs: number, skippedCategoriesCount: number, skippedMangasCount: number }, categoryUpdates: Array<{ __typename?: 'CategoryUpdateType', status: CategoryJobStatus, category: { __typename?: 'CategoryType', id: number, name: string } }>, mangaUpdates: Array<{ __typename?: 'MangaUpdateType', status: MangaJobStatus, manga: { __typename?: 'MangaType', id: number, title: string, thumbnailUrl?: string | null, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null, highestNumberedChapter?: { __typename?: 'ChapterType', id: number, chapterNumber: number } | null } }> } } | null };
|
||||||
|
|
||||||
export type StopUpdaterMutationVariables = Exact<{
|
export type StopUpdaterMutationVariables = Exact<{
|
||||||
input?: InputMaybe<UpdateStopInput>;
|
input?: InputMaybe<UpdateStopInput>;
|
||||||
@@ -4412,7 +4421,7 @@ export type StopUpdaterMutation = { __typename?: 'Mutation', updateStop: { __typ
|
|||||||
export type GetUpdateStatusQueryVariables = Exact<{ [key: string]: never; }>;
|
export type GetUpdateStatusQueryVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
export type GetUpdateStatusQuery = { __typename?: 'Query', libraryUpdateStatus: { __typename?: 'LibraryUpdateStatus', jobsInfo: { __typename?: 'UpdaterJobsInfoType', isRunning: boolean, totalJobs: number, finishedJobs: number, skippedCategoriesCount: number, skippedMangasCount: number }, categoryUpdates: Array<{ __typename?: 'CategoryUpdateType', status: CategoryJobStatus, category: { __typename?: 'CategoryType', id: number, name: string } }>, mangaUpdates: Array<{ __typename?: 'MangaUpdateType', status: MangaJobStatus, manga: { __typename?: 'MangaType', id: number, title: string, thumbnailUrl?: string | null, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null } }> } };
|
export type GetUpdateStatusQuery = { __typename?: 'Query', libraryUpdateStatus: { __typename?: 'LibraryUpdateStatus', jobsInfo: { __typename?: 'UpdaterJobsInfoType', isRunning: boolean, totalJobs: number, finishedJobs: number, skippedCategoriesCount: number, skippedMangasCount: number }, categoryUpdates: Array<{ __typename?: 'CategoryUpdateType', status: CategoryJobStatus, category: { __typename?: 'CategoryType', id: number, name: string } }>, mangaUpdates: Array<{ __typename?: 'MangaUpdateType', status: MangaJobStatus, manga: { __typename?: 'MangaType', id: number, title: string, thumbnailUrl?: string | null, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null, highestNumberedChapter?: { __typename?: 'ChapterType', id: number, chapterNumber: number } | null } }> } };
|
||||||
|
|
||||||
export type GetLastUpdateTimestampQueryVariables = Exact<{ [key: string]: never; }>;
|
export type GetLastUpdateTimestampQueryVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
@@ -4424,7 +4433,7 @@ export type UpdaterSubscriptionVariables = Exact<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type UpdaterSubscription = { __typename?: 'Subscription', libraryUpdateStatusChanged: { __typename?: 'UpdaterUpdates', omittedUpdates: boolean, jobsInfo: { __typename?: 'UpdaterJobsInfoType', isRunning: boolean, totalJobs: number, finishedJobs: number, skippedCategoriesCount: number, skippedMangasCount: number }, categoryUpdates: Array<{ __typename?: 'CategoryUpdateType', status: CategoryJobStatus, category: { __typename?: 'CategoryType', id: number, name: string } }>, mangaUpdates: Array<{ __typename?: 'MangaUpdateType', status: MangaJobStatus, manga: { __typename?: 'MangaType', id: number, title: string, thumbnailUrl?: string | null, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null } }> } };
|
export type UpdaterSubscription = { __typename?: 'Subscription', libraryUpdateStatusChanged: { __typename?: 'UpdaterUpdates', omittedUpdates: boolean, jobsInfo: { __typename?: 'UpdaterJobsInfoType', isRunning: boolean, totalJobs: number, finishedJobs: number, skippedCategoriesCount: number, skippedMangasCount: number }, categoryUpdates: Array<{ __typename?: 'CategoryUpdateType', status: CategoryJobStatus, category: { __typename?: 'CategoryType', id: number, name: string } }>, mangaUpdates: Array<{ __typename?: 'MangaUpdateType', status: MangaJobStatus, manga: { __typename?: 'MangaType', id: number, title: string, thumbnailUrl?: string | null, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number, chapterNumber: number, name: string, scanlator?: string | null } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null, highestNumberedChapter?: { __typename?: 'ChapterType', id: number, chapterNumber: number } | null } }> } };
|
||||||
|
|
||||||
export type UserLoginMutationVariables = Exact<{
|
export type UserLoginMutationVariables = Exact<{
|
||||||
password: Scalars['String']['input'];
|
password: Scalars['String']['input'];
|
||||||
|
|||||||
@@ -73,6 +73,10 @@ export const MANGA_CHAPTER_NODE_FIELDS = gql`
|
|||||||
id
|
id
|
||||||
uploadDate
|
uploadDate
|
||||||
}
|
}
|
||||||
|
highestNumberedChapter {
|
||||||
|
id
|
||||||
|
chapterNumber
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -143,12 +147,33 @@ export const MANGA_LIBRARY_FIELDS = gql`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const MANGA_MIGRATION_FIELDS = gql`
|
||||||
|
${MANGA_BASE_FIELDS}
|
||||||
|
${MANGA_CHAPTER_NODE_FIELDS}
|
||||||
|
|
||||||
|
fragment MANGA_MIGRATION_FIELDS on MangaType {
|
||||||
|
...MANGA_BASE_FIELDS
|
||||||
|
...MANGA_CHAPTER_NODE_FIELDS
|
||||||
|
artist
|
||||||
|
author
|
||||||
|
source {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
displayName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
export const MANGA_SCREEN_FIELDS = gql`
|
export const MANGA_SCREEN_FIELDS = gql`
|
||||||
${MANGA_LIBRARY_FIELDS}
|
${MANGA_LIBRARY_FIELDS}
|
||||||
${MANGA_META_FIELDS}
|
${MANGA_META_FIELDS}
|
||||||
|
${MANGA_CHAPTER_NODE_FIELDS}
|
||||||
|
${MANGA_MIGRATION_FIELDS}
|
||||||
|
|
||||||
fragment MANGA_SCREEN_FIELDS on MangaType {
|
fragment MANGA_SCREEN_FIELDS on MangaType {
|
||||||
...MANGA_LIBRARY_FIELDS
|
...MANGA_LIBRARY_FIELDS
|
||||||
|
...MANGA_CHAPTER_NODE_FIELDS
|
||||||
|
...MANGA_MIGRATION_FIELDS
|
||||||
|
|
||||||
artist
|
artist
|
||||||
author
|
author
|
||||||
@@ -164,6 +189,7 @@ export const MANGA_SCREEN_FIELDS = gql`
|
|||||||
sourceId
|
sourceId
|
||||||
source {
|
source {
|
||||||
id
|
id
|
||||||
|
name
|
||||||
displayName
|
displayName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
MANGA_LIBRARY_DUPLICATE_SCREEN_FIELDS,
|
MANGA_LIBRARY_DUPLICATE_SCREEN_FIELDS,
|
||||||
MANGA_LIBRARY_FIELDS,
|
MANGA_LIBRARY_FIELDS,
|
||||||
MANGA_META_FIELDS,
|
MANGA_META_FIELDS,
|
||||||
|
MANGA_MIGRATION_FIELDS,
|
||||||
MANGA_READER_FIELDS,
|
MANGA_READER_FIELDS,
|
||||||
MANGA_SCREEN_FIELDS,
|
MANGA_SCREEN_FIELDS,
|
||||||
} from '@/lib/graphql/manga/MangaFragments.ts';
|
} from '@/lib/graphql/manga/MangaFragments.ts';
|
||||||
@@ -236,18 +237,12 @@ export const GET_MANGAS_DUPLICATES = gql`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const GET_MIGRATABLE_SOURCE_MANGAS = gql`
|
export const GET_MIGRATABLE_SOURCE_MANGAS = gql`
|
||||||
|
${MANGA_MIGRATION_FIELDS}
|
||||||
|
|
||||||
query GET_MIGRATABLE_SOURCE_MANGAS($sourceId: LongString!) {
|
query GET_MIGRATABLE_SOURCE_MANGAS($sourceId: LongString!) {
|
||||||
mangas(condition: { sourceId: $sourceId, inLibrary: true }) {
|
mangas(condition: { sourceId: $sourceId, inLibrary: true }) {
|
||||||
nodes {
|
nodes {
|
||||||
id
|
...MANGA_MIGRATION_FIELDS
|
||||||
title
|
|
||||||
thumbnailUrl
|
|
||||||
sourceId
|
|
||||||
categories {
|
|
||||||
nodes {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import gql from 'graphql-tag';
|
import gql from 'graphql-tag';
|
||||||
import { SOURCE_META_FIELDS, SOURCE_SETTING_FIELDS } from '@/lib/graphql/source/SourceFragments.ts';
|
import { SOURCE_META_FIELDS, SOURCE_SETTING_FIELDS } from '@/lib/graphql/source/SourceFragments.ts';
|
||||||
import { MANGA_BASE_FIELDS } from '@/lib/graphql/manga/MangaFragments.ts';
|
import { MANGA_BASE_FIELDS, MANGA_MIGRATION_FIELDS } from '@/lib/graphql/manga/MangaFragments.ts';
|
||||||
|
|
||||||
export const GET_SOURCE_MANGAS_FETCH = gql`
|
export const GET_SOURCE_MANGAS_FETCH = gql`
|
||||||
${MANGA_BASE_FIELDS}
|
${MANGA_BASE_FIELDS}
|
||||||
@@ -23,6 +23,19 @@ export const GET_SOURCE_MANGAS_FETCH = gql`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const GET_MIGRATION_SOURCE_MANGAS_FETCH = gql`
|
||||||
|
${MANGA_MIGRATION_FIELDS}
|
||||||
|
|
||||||
|
mutation GET_MIGRATION_SOURCE_MANGAS_FETCH($input: FetchSourceMangaInput!) {
|
||||||
|
fetchSourceManga(input: $input) {
|
||||||
|
hasNextPage
|
||||||
|
mangas {
|
||||||
|
...MANGA_MIGRATION_FIELDS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
export const UPDATE_SOURCE_PREFERENCES = gql`
|
export const UPDATE_SOURCE_PREFERENCES = gql`
|
||||||
${SOURCE_SETTING_FIELDS}
|
${SOURCE_SETTING_FIELDS}
|
||||||
|
|
||||||
|
|||||||
10
yarn.lock
10
yarn.lock
@@ -3587,6 +3587,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
|
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
|
||||||
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
|
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
|
||||||
|
|
||||||
|
"@types/lodash@4.17.24":
|
||||||
|
version "4.17.24"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.24.tgz#4ae334fc62c0e915ca8ed8e35dcc6d4eeb29215f"
|
||||||
|
integrity sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==
|
||||||
|
|
||||||
"@types/node@*":
|
"@types/node@*":
|
||||||
version "20.12.2"
|
version "20.12.2"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.2.tgz#9facdd11102f38b21b4ebedd9d7999663343d72e"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.2.tgz#9facdd11102f38b21b4ebedd9d7999663343d72e"
|
||||||
@@ -7034,6 +7039,11 @@ lodash.sortby@^4.7.0:
|
|||||||
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
|
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
|
||||||
integrity sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==
|
integrity sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==
|
||||||
|
|
||||||
|
lodash@4.18.1:
|
||||||
|
version "4.18.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.18.1.tgz#ff2b66c1f6326d59513de2407bf881439812771c"
|
||||||
|
integrity sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==
|
||||||
|
|
||||||
lodash@^4.17.20, lodash@~4.17.0:
|
lodash@^4.17.20, lodash@~4.17.0:
|
||||||
version "4.17.21"
|
version "4.17.21"
|
||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||||
|
|||||||
Reference in New Issue
Block a user