Update typings
This commit is contained in:
@@ -14,11 +14,11 @@ import Avatar from '@mui/material/Avatar';
|
|||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import { Box } from '@mui/material';
|
import { Box } from '@mui/material';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Extension, TranslationKey } from '@/typings';
|
import { PartialExtension, TranslationKey } from '@/typings';
|
||||||
import requestManager from '@/lib/requests/RequestManager.ts';
|
import requestManager from '@/lib/requests/RequestManager.ts';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
extension: Extension;
|
extension: PartialExtension;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ExtensionAction {
|
enum ExtensionAction {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import requestManager from '@/lib/requests/RequestManager.ts';
|
import requestManager from '@/lib/requests/RequestManager.ts';
|
||||||
import { GridLayout, useLibraryOptionsContext } from '@/components/context/LibraryOptionsContext';
|
import { GridLayout, useLibraryOptionsContext } from '@/components/context/LibraryOptionsContext';
|
||||||
import SpinnerImage from '@/components/util/SpinnerImage';
|
import SpinnerImage from '@/components/util/SpinnerImage';
|
||||||
import { MangaType } from '@/lib/graphql/generated/graphql.ts';
|
import { TPartialManga } from '@/typings.ts';
|
||||||
|
|
||||||
const BottomGradient = styled('div')({
|
const BottomGradient = styled('div')({
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
@@ -66,7 +66,7 @@ const BadgeContainer = styled('div')({
|
|||||||
});
|
});
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
manga: MangaType;
|
manga: TPartialManga;
|
||||||
gridLayout?: GridLayout;
|
gridLayout?: GridLayout;
|
||||||
inLibraryIndicator?: boolean;
|
inLibraryIndicator?: boolean;
|
||||||
}
|
}
|
||||||
@@ -120,10 +120,10 @@ const MangaCard = (props: IProps) => {
|
|||||||
{t('manga.button.in_library')}
|
{t('manga.button.in_library')}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
{showUnreadBadge && unread! > 0 && (
|
{showUnreadBadge && (unread ?? 0) > 0 && (
|
||||||
<Typography sx={{ backgroundColor: 'primary.dark' }}>{unread}</Typography>
|
<Typography sx={{ backgroundColor: 'primary.dark' }}>{unread}</Typography>
|
||||||
)}
|
)}
|
||||||
{showDownloadBadge && downloadCount! > 0 && (
|
{showDownloadBadge && (downloadCount ?? 0) > 0 && (
|
||||||
<Typography
|
<Typography
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: 'success.dark',
|
backgroundColor: 'success.dark',
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import LoadingPlaceholder from '@/components/util/LoadingPlaceholder';
|
|||||||
import MangaCard from '@/components/MangaCard';
|
import MangaCard from '@/components/MangaCard';
|
||||||
import { GridLayout } from '@/components/context/LibraryOptionsContext';
|
import { GridLayout } from '@/components/context/LibraryOptionsContext';
|
||||||
import useLocalStorage from '@/util/useLocalStorage';
|
import useLocalStorage from '@/util/useLocalStorage';
|
||||||
import { MangaType } from '@/lib/graphql/generated/graphql.ts';
|
import { TPartialManga } from '@/typings.ts';
|
||||||
|
|
||||||
const GridContainer = React.forwardRef<HTMLDivElement, GridTypeMap['props']>(({ children, ...props }, ref) => (
|
const GridContainer = React.forwardRef<HTMLDivElement, GridTypeMap['props']>(({ children, ...props }, ref) => (
|
||||||
<Grid {...props} ref={ref} container sx={{ paddingLeft: '5px', paddingRight: '13px' }}>
|
<Grid {...props} ref={ref} container sx={{ paddingLeft: '5px', paddingRight: '13px' }}>
|
||||||
@@ -40,13 +40,13 @@ const GridItemContainerWithDimension = (
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const createMangaCard = (manga: MangaType, gridLayout?: GridLayout, inLibraryIndicator?: boolean) => (
|
const createMangaCard = (manga: TPartialManga, gridLayout?: GridLayout, inLibraryIndicator?: boolean) => (
|
||||||
<MangaCard key={manga.id} manga={manga} gridLayout={gridLayout} inLibraryIndicator={inLibraryIndicator} />
|
<MangaCard key={manga.id} manga={manga} gridLayout={gridLayout} inLibraryIndicator={inLibraryIndicator} />
|
||||||
);
|
);
|
||||||
|
|
||||||
type DefaultGridProps = {
|
type DefaultGridProps = {
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
mangas: MangaType[];
|
mangas: TPartialManga[];
|
||||||
inLibraryIndicator?: boolean;
|
inLibraryIndicator?: boolean;
|
||||||
GridItemContainer: (props: GridTypeMap['props'] & Partial<GridItemProps>) => JSX.Element;
|
GridItemContainer: (props: GridTypeMap['props'] & Partial<GridItemProps>) => JSX.Element;
|
||||||
gridLayout?: GridLayout;
|
gridLayout?: GridLayout;
|
||||||
@@ -150,7 +150,7 @@ const VerticalGrid = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export interface IMangaGridProps {
|
export interface IMangaGridProps {
|
||||||
mangas: MangaType[];
|
mangas: TPartialManga[];
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
message?: string;
|
message?: string;
|
||||||
messageExtra?: JSX.Element;
|
messageExtra?: JSX.Element;
|
||||||
|
|||||||
@@ -9,13 +9,12 @@
|
|||||||
import React, { useEffect, useMemo } from 'react';
|
import React, { useEffect, useMemo } from 'react';
|
||||||
import { StringParam, useQueryParam } from 'use-query-params';
|
import { StringParam, useQueryParam } from 'use-query-params';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { LibrarySortMode, NullAndUndefined } from '@/typings';
|
import { LibrarySortMode, NullAndUndefined, TManga } from '@/typings';
|
||||||
import { useSearchSettings } from '@/util/searchSettings';
|
import { useSearchSettings } from '@/util/searchSettings';
|
||||||
import { useLibraryOptionsContext } from '@/components/context/LibraryOptionsContext';
|
import { useLibraryOptionsContext } from '@/components/context/LibraryOptionsContext';
|
||||||
import MangaGrid from '@/components/MangaGrid';
|
import MangaGrid from '@/components/MangaGrid';
|
||||||
import { MangaType } from '@/lib/graphql/generated/graphql.ts';
|
|
||||||
|
|
||||||
const unreadFilter = (unread: NullAndUndefined<boolean>, { unreadCount }: MangaType): boolean => {
|
const unreadFilter = (unread: NullAndUndefined<boolean>, { unreadCount }: TManga): boolean => {
|
||||||
switch (unread) {
|
switch (unread) {
|
||||||
case true:
|
case true:
|
||||||
return !!unreadCount && unreadCount >= 1;
|
return !!unreadCount && unreadCount >= 1;
|
||||||
@@ -26,7 +25,7 @@ const unreadFilter = (unread: NullAndUndefined<boolean>, { unreadCount }: MangaT
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const downloadedFilter = (downloaded: NullAndUndefined<boolean>, { downloadCount }: MangaType): boolean => {
|
const downloadedFilter = (downloaded: NullAndUndefined<boolean>, { downloadCount }: TManga): boolean => {
|
||||||
switch (downloaded) {
|
switch (downloaded) {
|
||||||
case true:
|
case true:
|
||||||
return !!downloadCount && downloadCount >= 1;
|
return !!downloadCount && downloadCount >= 1;
|
||||||
@@ -37,24 +36,24 @@ const downloadedFilter = (downloaded: NullAndUndefined<boolean>, { downloadCount
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const queryFilter = (query: NullAndUndefined<string>, { title }: MangaType): boolean => {
|
const queryFilter = (query: NullAndUndefined<string>, { title }: TManga): boolean => {
|
||||||
if (!query) return true;
|
if (!query) return true;
|
||||||
return title.toLowerCase().includes(query.toLowerCase());
|
return title.toLowerCase().includes(query.toLowerCase());
|
||||||
};
|
};
|
||||||
|
|
||||||
const queryGenreFilter = (query: NullAndUndefined<string>, { genre }: MangaType): boolean => {
|
const queryGenreFilter = (query: NullAndUndefined<string>, { genre }: TManga): boolean => {
|
||||||
if (!query) return true;
|
if (!query) return true;
|
||||||
const queries = query.split(',').map((str) => str.toLowerCase().trim());
|
const queries = query.split(',').map((str) => str.toLowerCase().trim());
|
||||||
return queries.every((element) => genre.map((el) => el.toLowerCase()).includes(element));
|
return queries.every((element) => genre.map((el) => el.toLowerCase()).includes(element));
|
||||||
};
|
};
|
||||||
|
|
||||||
const filterManga = (
|
const filterManga = (
|
||||||
mangas: MangaType[],
|
mangas: TManga[],
|
||||||
query: NullAndUndefined<string>,
|
query: NullAndUndefined<string>,
|
||||||
unread: NullAndUndefined<boolean>,
|
unread: NullAndUndefined<boolean>,
|
||||||
downloaded: NullAndUndefined<boolean>,
|
downloaded: NullAndUndefined<boolean>,
|
||||||
ignoreFilters: boolean,
|
ignoreFilters: boolean,
|
||||||
): MangaType[] =>
|
): TManga[] =>
|
||||||
mangas.filter((manga) => {
|
mangas.filter((manga) => {
|
||||||
const ignoreFiltersWhileSearching = ignoreFilters && query?.length;
|
const ignoreFiltersWhileSearching = ignoreFilters && query?.length;
|
||||||
const matchesSearch = queryFilter(query, manga) || queryGenreFilter(query, manga);
|
const matchesSearch = queryFilter(query, manga) || queryGenreFilter(query, manga);
|
||||||
@@ -64,20 +63,20 @@ const filterManga = (
|
|||||||
return matchesSearch && matchesFilters;
|
return matchesSearch && matchesFilters;
|
||||||
});
|
});
|
||||||
|
|
||||||
const sortByUnread = (a: MangaType, b: MangaType): number => (a.unreadCount ?? 0) - (b.unreadCount ?? 0);
|
const sortByUnread = (a: TManga, b: TManga): number => (a.unreadCount ?? 0) - (b.unreadCount ?? 0);
|
||||||
|
|
||||||
const sortByTitle = (a: MangaType, b: MangaType): number => a.title.localeCompare(b.title);
|
const sortByTitle = (a: TManga, b: TManga): number => a.title.localeCompare(b.title);
|
||||||
|
|
||||||
const sortByDateAdded = (a: MangaType, b: MangaType): number => Number(a.inLibraryAt) - Number(b.inLibraryAt);
|
const sortByDateAdded = (a: TManga, b: TManga): number => Number(a.inLibraryAt) - Number(b.inLibraryAt);
|
||||||
|
|
||||||
const sortByLastRead = (a: MangaType, b: MangaType): number =>
|
const sortByLastRead = (a: TManga, b: TManga): number =>
|
||||||
Number(b.lastReadChapter?.lastReadAt ?? 0) - Number(a.lastReadChapter?.lastReadAt ?? 0);
|
Number(b.lastReadChapter?.lastReadAt ?? 0) - Number(a.lastReadChapter?.lastReadAt ?? 0);
|
||||||
|
|
||||||
const sortManga = (
|
const sortManga = (
|
||||||
manga: MangaType[],
|
manga: TManga[],
|
||||||
sort: NullAndUndefined<LibrarySortMode>,
|
sort: NullAndUndefined<LibrarySortMode>,
|
||||||
desc: NullAndUndefined<boolean>,
|
desc: NullAndUndefined<boolean>,
|
||||||
): MangaType[] => {
|
): TManga[] => {
|
||||||
const result = [...manga];
|
const result = [...manga];
|
||||||
|
|
||||||
switch (sort) {
|
switch (sort) {
|
||||||
@@ -105,7 +104,7 @@ const sortManga = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface LibraryMangaGridProps {
|
interface LibraryMangaGridProps {
|
||||||
mangas: MangaType[];
|
mangas: TManga[];
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
message?: string;
|
message?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,10 +30,11 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import requestManager from '@/lib/requests/RequestManager.ts';
|
import requestManager from '@/lib/requests/RequestManager.ts';
|
||||||
import { getUploadDateString } from '@/util/date';
|
import { getUploadDateString } from '@/util/date';
|
||||||
import DownloadStateIndicator from '@/components/molecules/DownloadStateIndicator';
|
import DownloadStateIndicator from '@/components/molecules/DownloadStateIndicator';
|
||||||
import { ChapterType, DownloadType, UpdateChapterPatchInput } from '@/lib/graphql/generated/graphql.ts';
|
import { DownloadType, UpdateChapterPatchInput } from '@/lib/graphql/generated/graphql.ts';
|
||||||
|
import { TChapter } from '@/typings.ts';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
chapter: ChapterType;
|
chapter: TChapter;
|
||||||
chapterIds: number[];
|
chapterIds: number[];
|
||||||
downloadChapter: DownloadType | undefined;
|
downloadChapter: DownloadType | undefined;
|
||||||
showChapterNumber: boolean;
|
showChapterNumber: boolean;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import Typography from '@mui/material/Typography';
|
|||||||
import React, { ComponentProps, useEffect, useMemo, useRef, useState } from 'react';
|
import React, { ComponentProps, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { Virtuoso } from 'react-virtuoso';
|
import { Virtuoso } from 'react-virtuoso';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { TranslationKey } from '@/typings';
|
import { TChapter, TManga, TranslationKey } from '@/typings';
|
||||||
import requestManager from '@/lib/requests/RequestManager.ts';
|
import requestManager from '@/lib/requests/RequestManager.ts';
|
||||||
import ChapterCard from '@/components/manga/ChapterCard';
|
import ChapterCard from '@/components/manga/ChapterCard';
|
||||||
import ResumeFab from '@/components/manga/ResumeFAB';
|
import ResumeFab from '@/components/manga/ResumeFAB';
|
||||||
@@ -21,7 +21,7 @@ import makeToast from '@/components/util/Toast';
|
|||||||
import ChaptersToolbarMenu from '@/components/manga/ChaptersToolbarMenu';
|
import ChaptersToolbarMenu from '@/components/manga/ChaptersToolbarMenu';
|
||||||
import SelectionFAB from '@/components/manga/SelectionFAB';
|
import SelectionFAB from '@/components/manga/SelectionFAB';
|
||||||
import { DEFAULT_FULL_FAB_HEIGHT } from '@/components/util/StyledFab';
|
import { DEFAULT_FULL_FAB_HEIGHT } from '@/components/util/StyledFab';
|
||||||
import { ChapterType, DownloadType, MangaType, UpdateChapterPatchInput } from '@/lib/graphql/generated/graphql.ts';
|
import { DownloadType, UpdateChapterPatchInput } from '@/lib/graphql/generated/graphql.ts';
|
||||||
|
|
||||||
const StyledVirtuoso = styled(Virtuoso)(({ theme }) => ({
|
const StyledVirtuoso = styled(Virtuoso)(({ theme }) => ({
|
||||||
listStyle: 'none',
|
listStyle: 'none',
|
||||||
@@ -68,13 +68,13 @@ const actionsStrings: {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export interface IChapterWithMeta {
|
export interface IChapterWithMeta {
|
||||||
chapter: ChapterType;
|
chapter: TChapter;
|
||||||
downloadChapter: DownloadType | undefined;
|
downloadChapter: DownloadType | undefined;
|
||||||
selected: boolean | null;
|
selected: boolean | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
manga: MangaType;
|
manga: TManga;
|
||||||
isRefreshing: boolean;
|
isRefreshing: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,10 +88,7 @@ const ChapterList: React.FC<IProps> = ({ manga, isRefreshing }) => {
|
|||||||
|
|
||||||
const [options, dispatch] = useChapterOptions(manga.id);
|
const [options, dispatch] = useChapterOptions(manga.id);
|
||||||
const { data: chaptersData, loading: isLoading, refetch } = requestManager.useGetMangaChapters(manga.id);
|
const { data: chaptersData, loading: isLoading, refetch } = requestManager.useGetMangaChapters(manga.id);
|
||||||
const chapters = useMemo(
|
const chapters = useMemo(() => chaptersData?.chapters.nodes ?? [], [chaptersData?.chapters.nodes]);
|
||||||
() => (chaptersData?.chapters.nodes as ChapterType[]) ?? [],
|
|
||||||
[chaptersData?.chapters.nodes],
|
|
||||||
);
|
|
||||||
const mangaChapterIds = useMemo(() => chapters.map((chapter) => chapter.id), [chapters]);
|
const mangaChapterIds = useMemo(() => chapters.map((chapter) => chapter.id), [chapters]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -14,10 +14,9 @@ import React, { useEffect, useMemo } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { t as translate } from 'i18next';
|
import { t as translate } from 'i18next';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import { ISource } from '@/typings';
|
import { ISource, TManga } from '@/typings';
|
||||||
import requestManager from '@/lib/requests/RequestManager.ts';
|
import requestManager from '@/lib/requests/RequestManager.ts';
|
||||||
import makeToast from '@/components/util/Toast';
|
import makeToast from '@/components/util/Toast';
|
||||||
import { MangaType } from '@/lib/graphql/generated/graphql.ts';
|
|
||||||
|
|
||||||
const DetailsWrapper = styled('div')(({ theme }) => ({
|
const DetailsWrapper = styled('div')(({ theme }) => ({
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@@ -151,7 +150,7 @@ const OpenSourceButton = ({ url }: { url?: string | null }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
manga: MangaType;
|
manga: TManga;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSourceName(source?: ISource | null) {
|
function getSourceName(source?: ISource | null) {
|
||||||
|
|||||||
@@ -22,10 +22,10 @@ import {
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import CategorySelect from '@/components/navbar/action/CategorySelect';
|
import CategorySelect from '@/components/navbar/action/CategorySelect';
|
||||||
import { MangaType } from '@/lib/graphql/generated/graphql.ts';
|
import { TManga } from '@/typings.ts';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
manga: MangaType;
|
manga: TManga;
|
||||||
onRefresh: () => any;
|
onRefresh: () => any;
|
||||||
refreshing: boolean;
|
refreshing: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ import {
|
|||||||
ChapterOptionsReducerAction,
|
ChapterOptionsReducerAction,
|
||||||
ChapterSortMode,
|
ChapterSortMode,
|
||||||
NullAndUndefined,
|
NullAndUndefined,
|
||||||
|
TChapter,
|
||||||
TranslationKey,
|
TranslationKey,
|
||||||
} from '@/typings';
|
} from '@/typings';
|
||||||
import { useReducerLocalStorage } from '@/util/useLocalStorage';
|
import { useReducerLocalStorage } from '@/util/useLocalStorage';
|
||||||
import { ChapterType } from '@/lib/graphql/generated/graphql.ts';
|
|
||||||
|
|
||||||
const defaultChapterOptions: ChapterListOptions = {
|
const defaultChapterOptions: ChapterListOptions = {
|
||||||
active: false,
|
active: false,
|
||||||
@@ -46,7 +46,7 @@ function chapterOptionsReducer(state: ChapterListOptions, actions: ChapterOption
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function unreadFilter(unread: NullAndUndefined<boolean>, { isRead: isChapterRead }: ChapterType) {
|
export function unreadFilter(unread: NullAndUndefined<boolean>, { isRead: isChapterRead }: TChapter) {
|
||||||
switch (unread) {
|
switch (unread) {
|
||||||
case true:
|
case true:
|
||||||
return !isChapterRead;
|
return !isChapterRead;
|
||||||
@@ -57,7 +57,7 @@ export function unreadFilter(unread: NullAndUndefined<boolean>, { isRead: isChap
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function downloadFilter(downloaded: NullAndUndefined<boolean>, { isDownloaded: chapterDownload }: ChapterType) {
|
function downloadFilter(downloaded: NullAndUndefined<boolean>, { isDownloaded: chapterDownload }: TChapter) {
|
||||||
switch (downloaded) {
|
switch (downloaded) {
|
||||||
case true:
|
case true:
|
||||||
return chapterDownload;
|
return chapterDownload;
|
||||||
@@ -68,7 +68,7 @@ function downloadFilter(downloaded: NullAndUndefined<boolean>, { isDownloaded: c
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function bookmarkedFilter(bookmarked: NullAndUndefined<boolean>, { isBookmarked: chapterBookmarked }: ChapterType) {
|
function bookmarkedFilter(bookmarked: NullAndUndefined<boolean>, { isBookmarked: chapterBookmarked }: TChapter) {
|
||||||
switch (bookmarked) {
|
switch (bookmarked) {
|
||||||
case true:
|
case true:
|
||||||
return chapterBookmarked;
|
return chapterBookmarked;
|
||||||
@@ -79,7 +79,7 @@ function bookmarkedFilter(bookmarked: NullAndUndefined<boolean>, { isBookmarked:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function filterAndSortChapters(chapters: ChapterType[], options: ChapterListOptions): ChapterType[] {
|
export function filterAndSortChapters(chapters: TChapter[], options: ChapterListOptions): TChapter[] {
|
||||||
const filtered = options.active
|
const filtered = options.active
|
||||||
? chapters.filter(
|
? chapters.filter(
|
||||||
(chp) =>
|
(chp) =>
|
||||||
|
|||||||
@@ -24,9 +24,8 @@ import ListItemText from '@mui/material/ListItemText';
|
|||||||
import ListItemSecondaryAction from '@mui/material/ListItemSecondaryAction';
|
import ListItemSecondaryAction from '@mui/material/ListItemSecondaryAction';
|
||||||
import Collapse from '@mui/material/Collapse';
|
import Collapse from '@mui/material/Collapse';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ChapterOffset, IReaderSettings } from '@/typings';
|
import { ChapterOffset, IReaderSettings, TChapter, TManga } from '@/typings';
|
||||||
import ReaderSettingsOptions from '@/components/reader/ReaderSettingsOptions';
|
import ReaderSettingsOptions from '@/components/reader/ReaderSettingsOptions';
|
||||||
import { ChapterType, MangaType } from '@/lib/graphql/generated/graphql.ts';
|
|
||||||
|
|
||||||
const Root = styled('div')(({ theme }) => ({
|
const Root = styled('div')(({ theme }) => ({
|
||||||
top: 0,
|
top: 0,
|
||||||
@@ -115,8 +114,8 @@ const OpenDrawerButton = styled(IconButton)(({ theme }) => ({
|
|||||||
interface IProps {
|
interface IProps {
|
||||||
settings: IReaderSettings;
|
settings: IReaderSettings;
|
||||||
setSettingValue: (key: keyof IReaderSettings, value: string | boolean) => void;
|
setSettingValue: (key: keyof IReaderSettings, value: string | boolean) => void;
|
||||||
manga: MangaType;
|
manga: TManga;
|
||||||
chapter: ChapterType;
|
chapter: TChapter;
|
||||||
curPage: number;
|
curPage: number;
|
||||||
scrollToPage: (page: number) => void;
|
scrollToPage: (page: number) => void;
|
||||||
openNextChapter: (offset: ChapterOffset, setHistory: (nextChapterIndex: number) => void) => Promise<void>;
|
openNextChapter: (offset: ChapterOffset, setHistory: (nextChapterIndex: number) => void) => Promise<void>;
|
||||||
|
|||||||
@@ -8,9 +8,9 @@
|
|||||||
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import MangaGrid, { IMangaGridProps } from '@/components/MangaGrid';
|
import MangaGrid, { IMangaGridProps } from '@/components/MangaGrid';
|
||||||
import { MangaType } from '@/lib/graphql/generated/graphql.ts';
|
import { TPartialManga } from '@/typings.ts';
|
||||||
|
|
||||||
function filterManga(mangas: MangaType[]): MangaType[] {
|
function filterManga(mangas: TPartialManga[]): TPartialManga[] {
|
||||||
return mangas;
|
return mangas;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ import { NavbarToolbar } from '@/components/navbar/DefaultNavBar';
|
|||||||
import DownloadStateIndicator from '@/components/molecules/DownloadStateIndicator';
|
import DownloadStateIndicator from '@/components/molecules/DownloadStateIndicator';
|
||||||
import EmptyView from '@/components/util/EmptyView';
|
import EmptyView from '@/components/util/EmptyView';
|
||||||
import NavbarContext from '@/components/context/NavbarContext';
|
import NavbarContext from '@/components/context/NavbarContext';
|
||||||
import { ChapterType, DownloadType } from '@/lib/graphql/generated/graphql.ts';
|
import { DownloadType } from '@/lib/graphql/generated/graphql.ts';
|
||||||
|
import { TChapter } from '@/typings.ts';
|
||||||
|
|
||||||
const DownloadQueue: React.FC = () => {
|
const DownloadQueue: React.FC = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -55,7 +56,7 @@ const DownloadQueue: React.FC = () => {
|
|||||||
return <EmptyView message={t('download.queue.label.no_downloads')} />;
|
return <EmptyView message={t('download.queue.label.no_downloads')} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDelete = async (chapter: ChapterType) => {
|
const handleDelete = async (chapter: TChapter) => {
|
||||||
const isRunning = status === 'STARTED';
|
const isRunning = status === 'STARTED';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -30,12 +30,12 @@ import { makeToaster } from '@/components/util/Toast';
|
|||||||
import LangSelect from '@/components/navbar/action/LangSelect';
|
import LangSelect from '@/components/navbar/action/LangSelect';
|
||||||
import NavbarContext from '@/components/context/NavbarContext';
|
import NavbarContext from '@/components/context/NavbarContext';
|
||||||
import ExtensionCard from '@/components/ExtensionCard';
|
import ExtensionCard from '@/components/ExtensionCard';
|
||||||
import { Extension } from '@/typings.ts';
|
import { PartialExtension } from '@/typings.ts';
|
||||||
|
|
||||||
const LANGUAGE = 0;
|
const LANGUAGE = 0;
|
||||||
const EXTENSIONS = 1;
|
const EXTENSIONS = 1;
|
||||||
|
|
||||||
function getExtensionsInfo(extensions: Extension[]): {
|
function getExtensionsInfo(extensions: PartialExtension[]): {
|
||||||
allLangs: string[];
|
allLangs: string[];
|
||||||
groupedExtensions: GroupedExtensionsResult;
|
groupedExtensions: GroupedExtensionsResult;
|
||||||
} {
|
} {
|
||||||
@@ -132,7 +132,7 @@ export default function MangaExtensions() {
|
|||||||
[shownLangs, groupedExtensions],
|
[shownLangs, groupedExtensions],
|
||||||
);
|
);
|
||||||
|
|
||||||
const flatRenderItems: (Extension | string)[] = filteredGroupedExtensions.flat(2);
|
const flatRenderItems: (PartialExtension | string)[] = filteredGroupedExtensions.flat(2);
|
||||||
|
|
||||||
const [toasts, makeToast] = makeToaster(useState<React.ReactElement[]>([]));
|
const [toasts, makeToast] = makeToaster(useState<React.ReactElement[]>([]));
|
||||||
|
|
||||||
@@ -229,7 +229,7 @@ export default function MangaExtensions() {
|
|||||||
</Typography>
|
</Typography>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const item = flatRenderItems[index] as Extension;
|
const item = flatRenderItems[index] as PartialExtension;
|
||||||
|
|
||||||
return <ExtensionCard key={item.apkName} extension={item} />;
|
return <ExtensionCard key={item.apkName} extension={item} />;
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Chip, Tab, Tabs, styled, Box } from '@mui/material';
|
import { Chip, Tab, Tabs, styled, Box } from '@mui/material';
|
||||||
import React, { useContext, useEffect, useMemo, useRef } from 'react';
|
import React, { useContext, useEffect, useMemo } from 'react';
|
||||||
import { useQueryParam, NumberParam } from 'use-query-params';
|
import { useQueryParam, NumberParam } from 'use-query-params';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import requestManager from '@/lib/requests/RequestManager.ts';
|
import requestManager from '@/lib/requests/RequestManager.ts';
|
||||||
@@ -20,7 +20,6 @@ import LibraryMangaGrid from '@/components/library/LibraryMangaGrid';
|
|||||||
import AppbarSearch from '@/components/util/AppbarSearch';
|
import AppbarSearch from '@/components/util/AppbarSearch';
|
||||||
import UpdateChecker from '@/components/library/UpdateChecker';
|
import UpdateChecker from '@/components/library/UpdateChecker';
|
||||||
import { useLibraryOptionsContext } from '@/components/context/LibraryOptionsContext';
|
import { useLibraryOptionsContext } from '@/components/context/LibraryOptionsContext';
|
||||||
import { MangaType } from '@/lib/graphql/generated/graphql.ts';
|
|
||||||
|
|
||||||
const StyledGridWrapper = styled(Box)(({ theme }) => ({
|
const StyledGridWrapper = styled(Box)(({ theme }) => ({
|
||||||
// TabsMenu height + TabsMenu bottom padding - grid item top padding
|
// TabsMenu height + TabsMenu bottom padding - grid item top padding
|
||||||
@@ -85,7 +84,7 @@ export default function Library() {
|
|||||||
error: mangaError,
|
error: mangaError,
|
||||||
loading: mangaLoading,
|
loading: mangaLoading,
|
||||||
} = requestManager.useGetCategoryMangas(activeTab?.id, { skip: !activeTab, nextFetchPolicy: 'cache-only' });
|
} = requestManager.useGetCategoryMangas(activeTab?.id, { skip: !activeTab, nextFetchPolicy: 'cache-only' });
|
||||||
const mangas = (categoryMangaResponse?.category.mangas.nodes as unknown as MangaType[]) ?? [];
|
const mangas = categoryMangaResponse?.category.mangas.nodes ?? [];
|
||||||
|
|
||||||
const { setTitle, setAction } = useContext(NavbarContext);
|
const { setTitle, setAction } = useContext(NavbarContext);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import MangaDetails from '@/components/manga/MangaDetails';
|
|||||||
import MangaToolbarMenu from '@/components/manga/MangaToolbarMenu';
|
import MangaToolbarMenu from '@/components/manga/MangaToolbarMenu';
|
||||||
import EmptyView from '@/components/util/EmptyView';
|
import EmptyView from '@/components/util/EmptyView';
|
||||||
import LoadingPlaceholder from '@/components/util/LoadingPlaceholder';
|
import LoadingPlaceholder from '@/components/util/LoadingPlaceholder';
|
||||||
import { MangaType } from '@/lib/graphql/generated/graphql.ts';
|
|
||||||
|
|
||||||
const AUTOFETCH_AGE = 1000 * 60 * 60 * 24; // 24 hours
|
const AUTOFETCH_AGE = 1000 * 60 * 60 * 24; // 24 hours
|
||||||
|
|
||||||
@@ -33,7 +32,7 @@ const Manga: React.FC = () => {
|
|||||||
|
|
||||||
const { data, error, loading: isLoading, networkStatus, refetch } = requestManager.useGetManga(id);
|
const { data, error, loading: isLoading, networkStatus, refetch } = requestManager.useGetManga(id);
|
||||||
const isValidating = isNetworkRequestInFlight(networkStatus);
|
const isValidating = isNetworkRequestInFlight(networkStatus);
|
||||||
const manga = data?.manga as MangaType | undefined;
|
const manga = data?.manga;
|
||||||
|
|
||||||
const [refresh, { loading: refreshing }] = useRefreshManga(id);
|
const [refresh, { loading: refreshing }] = useRefreshManga(id);
|
||||||
useSetDefaultBackTo('library');
|
useSetDefaultBackTo('library');
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'r
|
|||||||
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||||
import { Box } from '@mui/material';
|
import { Box } from '@mui/material';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ChapterOffset, IReaderSettings, ReaderType, TranslationKey } from '@/typings';
|
import { ChapterOffset, IReaderSettings, ReaderType, TChapter, TManga, TranslationKey } from '@/typings';
|
||||||
import requestManager from '@/lib/requests/RequestManager.ts';
|
import requestManager from '@/lib/requests/RequestManager.ts';
|
||||||
import {
|
import {
|
||||||
checkAndHandleMissingStoredReaderSettings,
|
checkAndHandleMissingStoredReaderSettings,
|
||||||
@@ -27,9 +27,8 @@ import VerticalPager from '@/components/reader/pager/VerticalPager';
|
|||||||
import ReaderNavBar from '@/components/navbar/ReaderNavBar';
|
import ReaderNavBar from '@/components/navbar/ReaderNavBar';
|
||||||
import NavbarContext from '@/components/context/NavbarContext';
|
import NavbarContext from '@/components/context/NavbarContext';
|
||||||
import makeToast from '@/components/util/Toast';
|
import makeToast from '@/components/util/Toast';
|
||||||
import { ChapterType, MangaType } from '@/lib/graphql/generated/graphql.ts';
|
|
||||||
|
|
||||||
const isDupChapter = async (chapterIndex: number, currentChapter: ChapterType) => {
|
const isDupChapter = async (chapterIndex: number, currentChapter: TChapter) => {
|
||||||
const nextChapter = await requestManager.getChapter(currentChapter.manga.id, chapterIndex).response;
|
const nextChapter = await requestManager.getChapter(currentChapter.manga.id, chapterIndex).response;
|
||||||
|
|
||||||
return nextChapter.data.chapter.chapterNumber === currentChapter.chapterNumber;
|
return nextChapter.data.chapter.chapterNumber === currentChapter.chapterNumber;
|
||||||
@@ -42,7 +41,7 @@ const isDupChapter = async (chapterIndex: number, currentChapter: ChapterType) =
|
|||||||
*/
|
*/
|
||||||
const getOffsetChapter = async (
|
const getOffsetChapter = async (
|
||||||
chapterIndex: number,
|
chapterIndex: number,
|
||||||
currentChapter: ChapterType,
|
currentChapter: TChapter,
|
||||||
skipDupChapters: boolean,
|
skipDupChapters: boolean,
|
||||||
offset: ChapterOffset,
|
offset: ChapterOffset,
|
||||||
): Promise<number> => {
|
): Promise<number> => {
|
||||||
@@ -86,7 +85,7 @@ const initialChapter = {
|
|||||||
chapterCount: 0,
|
chapterCount: 0,
|
||||||
lastPageRead: 0,
|
lastPageRead: 0,
|
||||||
name: 'Loading...',
|
name: 'Loading...',
|
||||||
} as unknown as ChapterType;
|
} as unknown as TChapter;
|
||||||
|
|
||||||
export default function Reader() {
|
export default function Reader() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -105,17 +104,17 @@ export default function Reader() {
|
|||||||
inLibraryAt: 0,
|
inLibraryAt: 0,
|
||||||
lastReadAt: 0,
|
lastReadAt: 0,
|
||||||
chapters: { totalCount: 0 },
|
chapters: { totalCount: 0 },
|
||||||
}) as unknown as MangaType,
|
}) as unknown as TManga,
|
||||||
[mangaId],
|
[mangaId],
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data, loading: isMangaLoading } = requestManager.useGetManga(mangaId);
|
const { data, loading: isMangaLoading } = requestManager.useGetManga(mangaId);
|
||||||
const loadedChapter = useRef<ChapterType | null>(null);
|
const loadedChapter = useRef<TChapter | null>(null);
|
||||||
const isChapterLoaded =
|
const isChapterLoaded =
|
||||||
Number(mangaId) === loadedChapter.current?.manga.id &&
|
Number(mangaId) === loadedChapter.current?.manga.id &&
|
||||||
Number(chapterIndex) === loadedChapter.current?.sourceOrder &&
|
Number(chapterIndex) === loadedChapter.current?.sourceOrder &&
|
||||||
loadedChapter.current?.pageCount !== -1;
|
loadedChapter.current?.pageCount !== -1;
|
||||||
const manga = (data?.manga as MangaType) ?? initialManga;
|
const manga = data?.manga ?? initialManga;
|
||||||
const { data: chapterData, loading: isChapterLoading } = requestManager.useGetMangaChapter(mangaId, chapterIndex, {
|
const { data: chapterData, loading: isChapterLoading } = requestManager.useGetMangaChapter(mangaId, chapterIndex, {
|
||||||
skip: isChapterLoaded,
|
skip: isChapterLoaded,
|
||||||
});
|
});
|
||||||
@@ -129,7 +128,7 @@ export default function Reader() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (chapterData?.chapter) {
|
if (chapterData?.chapter) {
|
||||||
return chapterData.chapter as ChapterType;
|
return chapterData.chapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import LangSelect from '@/components/navbar/action/LangSelect';
|
|||||||
import MangaGrid from '@/components/MangaGrid';
|
import MangaGrid from '@/components/MangaGrid';
|
||||||
import NavbarContext from '@/components/context/NavbarContext';
|
import NavbarContext from '@/components/context/NavbarContext';
|
||||||
import { useDebounce } from '@/components/manga/hooks';
|
import { useDebounce } from '@/components/manga/hooks';
|
||||||
import { MangaType } from '@/lib/graphql/generated/graphql.ts';
|
|
||||||
|
|
||||||
type SourceLoadingState = { isLoading: boolean; hasResults: boolean; emptySearch: boolean };
|
type SourceLoadingState = { isLoading: boolean; hasResults: boolean; emptySearch: boolean };
|
||||||
type SourceToLoadingStateMap = Map<string, SourceLoadingState>;
|
type SourceToLoadingStateMap = Map<string, SourceLoadingState>;
|
||||||
@@ -103,7 +102,7 @@ const SourceSearchPreview = React.memo(
|
|||||||
skipRequest: !searchString,
|
skipRequest: !searchString,
|
||||||
});
|
});
|
||||||
const { data: searchResult, isLoading, error, abortRequest } = results[0]!;
|
const { data: searchResult, isLoading, error, abortRequest } = results[0]!;
|
||||||
const mangas = (searchResult?.fetchSourceManga.mangas as MangaType[]) ?? [];
|
const mangas = searchResult?.fetchSourceManga.mangas ?? [];
|
||||||
const noMangasFound = !isLoading && !mangas.length;
|
const noMangasFound = !isLoading && !mangas.length;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { Box, Button, styled, useTheme, useMediaQuery } from '@mui/material';
|
|||||||
import FavoriteIcon from '@mui/icons-material/Favorite';
|
import FavoriteIcon from '@mui/icons-material/Favorite';
|
||||||
import NewReleasesIcon from '@mui/icons-material/NewReleases';
|
import NewReleasesIcon from '@mui/icons-material/NewReleases';
|
||||||
import FilterListIcon from '@mui/icons-material/FilterList';
|
import FilterListIcon from '@mui/icons-material/FilterList';
|
||||||
import { TranslationKey } from '@/typings';
|
import { TPartialManga, TranslationKey } from '@/typings';
|
||||||
import requestManager, { AbortableApolloUseMutationPaginatedResponse } from '@/lib/requests/RequestManager.ts';
|
import requestManager, { AbortableApolloUseMutationPaginatedResponse } from '@/lib/requests/RequestManager.ts';
|
||||||
import { useDebounce } from '@/components/manga/hooks';
|
import { useDebounce } from '@/components/manga/hooks';
|
||||||
import { useLibraryOptionsContext } from '@/components/context/LibraryOptionsContext';
|
import { useLibraryOptionsContext } from '@/components/context/LibraryOptionsContext';
|
||||||
@@ -29,7 +29,6 @@ import SourceMangaGrid from '@/components/source/SourceMangaGrid';
|
|||||||
import {
|
import {
|
||||||
GetSourceMangasFetchMutation,
|
GetSourceMangasFetchMutation,
|
||||||
GetSourceMangasFetchMutationVariables,
|
GetSourceMangasFetchMutationVariables,
|
||||||
MangaType,
|
|
||||||
} from '@/lib/graphql/generated/graphql.ts';
|
} from '@/lib/graphql/generated/graphql.ts';
|
||||||
|
|
||||||
const ContentTypeMenu = styled('div')(({ theme }) => ({
|
const ContentTypeMenu = styled('div')(({ theme }) => ({
|
||||||
@@ -87,8 +86,8 @@ const SOURCE_CONTENT_TYPE_TO_ERROR_MSG_KEY: { [contentType in SourceContentType]
|
|||||||
[SourceContentType.SEARCH]: 'manga.error.label.no_mangas_found',
|
[SourceContentType.SEARCH]: 'manga.error.label.no_mangas_found',
|
||||||
};
|
};
|
||||||
|
|
||||||
const getUniqueMangas = (mangas: MangaType[]): MangaType[] => {
|
const getUniqueMangas = (mangas: TPartialManga[]): TPartialManga[] => {
|
||||||
const uniqueMangas: MangaType[] = [];
|
const uniqueMangas: TPartialManga[] = [];
|
||||||
|
|
||||||
mangas.forEach((manga) => {
|
mangas.forEach((manga) => {
|
||||||
const isDuplicate = uniqueMangas.some((uniqueManga) => uniqueManga.id === manga.id);
|
const isDuplicate = uniqueMangas.some((uniqueManga) => uniqueManga.id === manga.id);
|
||||||
@@ -166,7 +165,7 @@ const useSourceManga = (
|
|||||||
.map((page) => page.data?.fetchSourceManga.mangas ?? [])
|
.map((page) => page.data?.fetchSourceManga.mangas ?? [])
|
||||||
.reduce((prevList, list) => [...prevList, ...list], []),
|
.reduce((prevList, list) => [...prevList, ...list], []),
|
||||||
[pages],
|
[pages],
|
||||||
) as MangaType[];
|
);
|
||||||
const uniqueItems = useMemo(() => getUniqueMangas(items), [items]);
|
const uniqueItems = useMemo(() => getUniqueMangas(items), [items]);
|
||||||
|
|
||||||
if (!uniqueItems.length) {
|
if (!uniqueItems.length) {
|
||||||
@@ -224,7 +223,7 @@ export default function SourceMangas() {
|
|||||||
filtersToApply,
|
filtersToApply,
|
||||||
isLargeScreen ? 2 : 1,
|
isLargeScreen ? 2 : 1,
|
||||||
);
|
);
|
||||||
const mangas = (data?.fetchSourceManga.mangas as MangaType[]) ?? [];
|
const mangas = data?.fetchSourceManga.mangas ?? [];
|
||||||
const hasNextPage = data?.fetchSourceManga.hasNextPage ?? false;
|
const hasNextPage = data?.fetchSourceManga.hasNextPage ?? false;
|
||||||
|
|
||||||
const { data: sourceData } = requestManager.useGetSource(sourceId);
|
const { data: sourceData } = requestManager.useGetSource(sourceId);
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ import LoadingPlaceholder from '@/components/util/LoadingPlaceholder';
|
|||||||
import EmptyView from '@/components/util/EmptyView';
|
import EmptyView from '@/components/util/EmptyView';
|
||||||
import DownloadStateIndicator from '@/components/molecules/DownloadStateIndicator';
|
import DownloadStateIndicator from '@/components/molecules/DownloadStateIndicator';
|
||||||
import NavbarContext from '@/components/context/NavbarContext';
|
import NavbarContext from '@/components/context/NavbarContext';
|
||||||
import { ChapterType, DownloadType } from '@/lib/graphql/generated/graphql.ts';
|
import { DownloadType } from '@/lib/graphql/generated/graphql.ts';
|
||||||
|
import { TChapter } from '@/typings.ts';
|
||||||
|
|
||||||
const StyledGroupedVirtuoso = styled(GroupedVirtuoso)(({ theme }) => ({
|
const StyledGroupedVirtuoso = styled(GroupedVirtuoso)(({ theme }) => ({
|
||||||
// 64px header
|
// 64px header
|
||||||
@@ -81,7 +82,7 @@ function getDateString(date: Date) {
|
|||||||
return date.toLocaleDateString();
|
return date.toLocaleDateString();
|
||||||
}
|
}
|
||||||
|
|
||||||
const groupByDate = (updates: ChapterType[]): [date: string, items: number][] => {
|
const groupByDate = (updates: TChapter[]): [date: string, items: number][] => {
|
||||||
if (!updates.length) {
|
if (!updates.length) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -111,7 +112,7 @@ const Updates: React.FC = () => {
|
|||||||
});
|
});
|
||||||
const hasNextPage = !!chapterUpdateData?.chapters.pageInfo.hasNextPage;
|
const hasNextPage = !!chapterUpdateData?.chapters.pageInfo.hasNextPage;
|
||||||
const endCursor = chapterUpdateData?.chapters.pageInfo.endCursor;
|
const endCursor = chapterUpdateData?.chapters.pageInfo.endCursor;
|
||||||
const updateEntries = (chapterUpdateData?.chapters.nodes as ChapterType[]) ?? [];
|
const updateEntries = chapterUpdateData?.chapters.nodes ?? [];
|
||||||
const groupedUpdates = useMemo(() => groupByDate(updateEntries), [updateEntries]);
|
const groupedUpdates = useMemo(() => groupByDate(updateEntries), [updateEntries]);
|
||||||
const groupCounts: number[] = useMemo(() => groupedUpdates.map((group) => group[1]), [groupedUpdates]);
|
const groupCounts: number[] = useMemo(() => groupedUpdates.map((group) => group[1]), [groupedUpdates]);
|
||||||
const { data: downloaderData } = requestManager.useDownloadSubscription();
|
const { data: downloaderData } = requestManager.useDownloadSubscription();
|
||||||
@@ -123,7 +124,7 @@ const Updates: React.FC = () => {
|
|||||||
setAction(null);
|
setAction(null);
|
||||||
}, [t]);
|
}, [t]);
|
||||||
|
|
||||||
const downloadForChapter = (chapter: ChapterType) => {
|
const downloadForChapter = (chapter: TChapter) => {
|
||||||
const {
|
const {
|
||||||
sourceOrder,
|
sourceOrder,
|
||||||
manga: { id: mangaId },
|
manga: { id: mangaId },
|
||||||
@@ -131,7 +132,7 @@ const Updates: React.FC = () => {
|
|||||||
return queue.find((q) => sourceOrder === q.chapter.sourceOrder && mangaId === q.chapter.manga.id);
|
return queue.find((q) => sourceOrder === q.chapter.sourceOrder && mangaId === q.chapter.manga.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
const downloadChapter = (chapter: ChapterType) => {
|
const downloadChapter = (chapter: TChapter) => {
|
||||||
requestManager.addChapterToDownloadQueue(chapter.id);
|
requestManager.addChapterToDownloadQueue(chapter.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import requestManager from '@/lib/requests/RequestManager.ts';
|
|||||||
import StrictModeDroppable from '@/lib/StrictModeDroppable';
|
import StrictModeDroppable from '@/lib/StrictModeDroppable';
|
||||||
import { DEFAULT_FULL_FAB_HEIGHT } from '@/components/util/StyledFab';
|
import { DEFAULT_FULL_FAB_HEIGHT } from '@/components/util/StyledFab';
|
||||||
import NavbarContext, { useSetDefaultBackTo } from '@/components/context/NavbarContext';
|
import NavbarContext, { useSetDefaultBackTo } from '@/components/context/NavbarContext';
|
||||||
import { CategoryType } from '@/lib/graphql/generated/graphql.ts';
|
import { TCategory } from '@/typings.ts';
|
||||||
|
|
||||||
const getItemStyle = (
|
const getItemStyle = (
|
||||||
isDragging: boolean,
|
isDragging: boolean,
|
||||||
@@ -58,7 +58,7 @@ export default function Categories() {
|
|||||||
if (res.length > 0 && res[0].name === 'Default') {
|
if (res.length > 0 && res[0].name === 'Default') {
|
||||||
res.shift();
|
res.shift();
|
||||||
}
|
}
|
||||||
return res as CategoryType[];
|
return res;
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
const [categoryToEdit, setCategoryToEdit] = useState<number>(-1); // -1 means new category
|
const [categoryToEdit, setCategoryToEdit] = useState<number>(-1); // -1 means new category
|
||||||
@@ -69,7 +69,7 @@ export default function Categories() {
|
|||||||
|
|
||||||
useSetDefaultBackTo('settings');
|
useSetDefaultBackTo('settings');
|
||||||
|
|
||||||
const categoryReorder = (list: CategoryType[], from: number, to: number) => {
|
const categoryReorder = (list: TCategory[], from: number, to: number) => {
|
||||||
const reorderedCategory = list[from];
|
const reorderedCategory = list[from];
|
||||||
const newData = [...list];
|
const newData = [...list];
|
||||||
const [removed] = newData.splice(from, 1);
|
const [removed] = newData.splice(from, 1);
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ import makeToast from '@/components/util/Toast';
|
|||||||
import ThreeStateCheckboxInput from '@/components/atoms/ThreeStateCheckboxInput';
|
import ThreeStateCheckboxInput from '@/components/atoms/ThreeStateCheckboxInput';
|
||||||
import NavbarContext, { useSetDefaultBackTo } from '@/components/context/NavbarContext';
|
import NavbarContext, { useSetDefaultBackTo } from '@/components/context/NavbarContext';
|
||||||
import SearchSettings from '@/screens/settings/SearchSettings';
|
import SearchSettings from '@/screens/settings/SearchSettings';
|
||||||
import { CategoryType, IncludeInUpdate } from '@/lib/graphql/generated/graphql.ts';
|
import { IncludeInUpdate } from '@/lib/graphql/generated/graphql.ts';
|
||||||
|
import { TCategory } from '@/typings.ts';
|
||||||
|
|
||||||
const CategoriesDiv = styled('div')({
|
const CategoriesDiv = styled('div')({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -62,7 +63,7 @@ const includeInUpdateStatusToBoolean = (status: IncludeInUpdate): boolean | null
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getCategoryUpdateInfo = (
|
const getCategoryUpdateInfo = (
|
||||||
categories: CategoryType[],
|
categories: TCategory[],
|
||||||
areIncluded: boolean,
|
areIncluded: boolean,
|
||||||
unsetCategories: number,
|
unsetCategories: number,
|
||||||
allCategories: number,
|
allCategories: number,
|
||||||
@@ -100,19 +101,19 @@ export default function LibrarySettings() {
|
|||||||
useSetDefaultBackTo('settings');
|
useSetDefaultBackTo('settings');
|
||||||
|
|
||||||
const { data, error: requestError } = requestManager.useGetCategories();
|
const { data, error: requestError } = requestManager.useGetCategories();
|
||||||
const categories = (data?.categories.nodes ?? []) as CategoryType[];
|
const categories = data?.categories.nodes ?? [];
|
||||||
const [dialogCategories, setDialogCategories] = useState<CategoryType[]>(categories);
|
const [dialogCategories, setDialogCategories] = useState<TCategory[]>(categories);
|
||||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setDialogCategories(categories);
|
setDialogCategories(categories);
|
||||||
}, [categories]);
|
}, [categories]);
|
||||||
|
|
||||||
const unsetCategories: CategoryType[] =
|
const unsetCategories: TCategory[] =
|
||||||
categories?.filter((category) => category.includeInUpdate === IncludeInUpdate.Unset) ?? [];
|
categories?.filter((category) => category.includeInUpdate === IncludeInUpdate.Unset) ?? [];
|
||||||
const excludedCategories: CategoryType[] =
|
const excludedCategories: TCategory[] =
|
||||||
categories?.filter((category) => category.includeInUpdate === IncludeInUpdate.Exclude) ?? [];
|
categories?.filter((category) => category.includeInUpdate === IncludeInUpdate.Exclude) ?? [];
|
||||||
const includedCategories: CategoryType[] =
|
const includedCategories: TCategory[] =
|
||||||
categories?.filter((category) => category.includeInUpdate === IncludeInUpdate.Include) ?? [];
|
categories?.filter((category) => category.includeInUpdate === IncludeInUpdate.Include) ?? [];
|
||||||
const excludedCategoriesText = getCategoryUpdateInfo(
|
const excludedCategoriesText = getCategoryUpdateInfo(
|
||||||
excludedCategories,
|
excludedCategories,
|
||||||
@@ -129,7 +130,7 @@ export default function LibrarySettings() {
|
|||||||
requestError,
|
requestError,
|
||||||
);
|
);
|
||||||
|
|
||||||
const updateCategory = (category: CategoryType) =>
|
const updateCategory = (category: TCategory) =>
|
||||||
requestManager.updateCategory(category.id, { includeInUpdate: category.includeInUpdate }).response;
|
requestManager.updateCategory(category.id, { includeInUpdate: category.includeInUpdate }).response;
|
||||||
|
|
||||||
const updateCategories = async () => {
|
const updateCategories = async () => {
|
||||||
@@ -218,7 +219,7 @@ export default function LibrarySettings() {
|
|||||||
const categoryIndex = dialogCategories.findIndex(
|
const categoryIndex = dialogCategories.findIndex(
|
||||||
(category_) => category_ === category,
|
(category_) => category_ === category,
|
||||||
);
|
);
|
||||||
const updatedDialogCategories: CategoryType[] = [
|
const updatedDialogCategories: TCategory[] = [
|
||||||
...dialogCategories.slice(0, categoryIndex),
|
...dialogCategories.slice(0, categoryIndex),
|
||||||
{
|
{
|
||||||
...category,
|
...category,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { Extension, TranslationKey } from '@/typings';
|
import { PartialExtension, TranslationKey } from '@/typings';
|
||||||
import { DefaultLanguage, langCodeToName } from '@/util/language';
|
import { DefaultLanguage, langCodeToName } from '@/util/language';
|
||||||
|
|
||||||
export enum ExtensionState {
|
export enum ExtensionState {
|
||||||
@@ -16,16 +16,16 @@ export enum ExtensionState {
|
|||||||
OBSOLETE = 'OBSOLETE',
|
OBSOLETE = 'OBSOLETE',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GroupedExtensionsResult<KEY extends string = string> = [KEY, Extension[]][];
|
export type GroupedExtensionsResult<KEY extends string = string> = [KEY, PartialExtension[]][];
|
||||||
|
|
||||||
export type GroupedByExtensionState = {
|
export type GroupedByExtensionState = {
|
||||||
[state in ExtensionState]: Extension[];
|
[state in ExtensionState]: PartialExtension[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GroupedByLanguage = {
|
export type GroupedByLanguage = {
|
||||||
[language in DefaultLanguage]: Extension[];
|
[language in DefaultLanguage]: PartialExtension[];
|
||||||
} & {
|
} & {
|
||||||
[language: string]: Extension[];
|
[language: string]: PartialExtension[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GroupedExtensions = GroupedByExtensionState & GroupedByLanguage;
|
export type GroupedExtensions = GroupedByExtensionState & GroupedByLanguage;
|
||||||
|
|||||||
@@ -11,10 +11,11 @@ import { SvgIconTypeMap } from '@mui/material/SvgIcon/SvgIcon';
|
|||||||
import { ParseKeys } from 'i18next';
|
import { ParseKeys } from 'i18next';
|
||||||
import { Location } from 'react-router-dom';
|
import { Location } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
ChapterType,
|
GetCategoryQuery,
|
||||||
ExtensionType,
|
GetChapterQuery,
|
||||||
|
GetExtensionQuery,
|
||||||
|
GetMangaQuery,
|
||||||
GetSourceQuery,
|
GetSourceQuery,
|
||||||
MangaType,
|
|
||||||
MetaType,
|
MetaType,
|
||||||
SourcePreferenceChangeInput,
|
SourcePreferenceChangeInput,
|
||||||
} from '@/lib/graphql/generated/graphql.ts';
|
} from '@/lib/graphql/generated/graphql.ts';
|
||||||
@@ -33,6 +34,8 @@ export type RecursivePartial<T> = {
|
|||||||
: T[P];
|
: T[P];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type OptionalProperty<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
|
||||||
|
|
||||||
type GenericLocation<State = any> = Omit<Location, 'state'> & { state?: State };
|
type GenericLocation<State = any> = Omit<Location, 'state'> & { state?: State };
|
||||||
|
|
||||||
declare module 'react-router-dom' {
|
declare module 'react-router-dom' {
|
||||||
@@ -43,7 +46,7 @@ declare module 'react-router-dom' {
|
|||||||
|
|
||||||
export type TranslationKey = ParseKeys;
|
export type TranslationKey = ParseKeys;
|
||||||
|
|
||||||
export type Extension = Omit<ExtensionType, 'source'>;
|
export type PartialExtension = GetExtensionQuery['extension'];
|
||||||
|
|
||||||
export interface ISource {
|
export interface ISource {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -105,6 +108,10 @@ export interface IMangaCard {
|
|||||||
lastReadAt: number;
|
lastReadAt: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type TManga = GetMangaQuery['manga'];
|
||||||
|
|
||||||
|
export type TPartialManga = OptionalProperty<TManga, 'unreadCount' | 'downloadCount' | 'categories' | 'chapters'>;
|
||||||
|
|
||||||
export interface IManga {
|
export interface IManga {
|
||||||
id: number;
|
id: number;
|
||||||
sourceId: string;
|
sourceId: string;
|
||||||
@@ -166,12 +173,16 @@ export interface IMangaChapter {
|
|||||||
chapter: IChapter;
|
chapter: IChapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type TChapter = GetChapterQuery['chapter'];
|
||||||
|
|
||||||
export enum IncludeInGlobalUpdate {
|
export enum IncludeInGlobalUpdate {
|
||||||
EXCLUDE = 0,
|
EXCLUDE = 0,
|
||||||
INCLUDE = 1,
|
INCLUDE = 1,
|
||||||
UNSET = -1,
|
UNSET = -1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type TCategory = GetCategoryQuery['category'];
|
||||||
|
|
||||||
export interface ICategory {
|
export interface ICategory {
|
||||||
id: number;
|
id: number;
|
||||||
order: number;
|
order: number;
|
||||||
@@ -230,8 +241,8 @@ export interface IReaderProps {
|
|||||||
curPage: number;
|
curPage: number;
|
||||||
initialPage: number;
|
initialPage: number;
|
||||||
settings: IReaderSettings;
|
settings: IReaderSettings;
|
||||||
manga: MangaType;
|
manga: TManga;
|
||||||
chapter: ChapterType;
|
chapter: TChapter;
|
||||||
nextChapter: () => void;
|
nextChapter: () => void;
|
||||||
prevChapter: () => void;
|
prevChapter: () => void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,12 @@ import {
|
|||||||
Metadata,
|
Metadata,
|
||||||
MetadataHolder,
|
MetadataHolder,
|
||||||
MetadataKeyValuePair,
|
MetadataKeyValuePair,
|
||||||
|
TCategory,
|
||||||
|
TChapter,
|
||||||
|
TManga,
|
||||||
} from '@/typings';
|
} from '@/typings';
|
||||||
import requestManager from '@/lib/requests/RequestManager.ts';
|
import requestManager from '@/lib/requests/RequestManager.ts';
|
||||||
import { CategoryType, ChapterType, MangaType, MetaType } from '@/lib/graphql/generated/graphql.ts';
|
import { MetaType } from '@/lib/graphql/generated/graphql.ts';
|
||||||
|
|
||||||
const APP_METADATA_KEY_PREFIX = 'webUI_';
|
const APP_METADATA_KEY_PREFIX = 'webUI_';
|
||||||
|
|
||||||
@@ -322,16 +325,16 @@ export const requestUpdateMetadataValue = async (
|
|||||||
|
|
||||||
switch (holderType) {
|
switch (holderType) {
|
||||||
case 'category':
|
case 'category':
|
||||||
await requestManager.setCategoryMeta((metadataHolder as CategoryType).id, metadataKey, value).response;
|
await requestManager.setCategoryMeta((metadataHolder as TCategory).id, metadataKey, value).response;
|
||||||
break;
|
break;
|
||||||
case 'chapter':
|
case 'chapter':
|
||||||
await requestManager.setChapterMeta((metadataHolder as ChapterType).id, metadataKey, value).response;
|
await requestManager.setChapterMeta((metadataHolder as TChapter).id, metadataKey, value).response;
|
||||||
break;
|
break;
|
||||||
case 'global':
|
case 'global':
|
||||||
await requestManager.setGlobalMetadata(metadataKey, value).response;
|
await requestManager.setGlobalMetadata(metadataKey, value).response;
|
||||||
break;
|
break;
|
||||||
case 'manga':
|
case 'manga':
|
||||||
await requestManager.setMangaMeta((metadataHolder as MangaType).id, metadataKey, value).response;
|
await requestManager.setMangaMeta((metadataHolder as TManga).id, metadataKey, value).response;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(`requestUpdateMetadataValue: unknown holderType "${holderType}"`);
|
throw new Error(`requestUpdateMetadataValue: unknown holderType "${holderType}"`);
|
||||||
@@ -351,16 +354,16 @@ export const requestUpdateServerMetadata = async (
|
|||||||
): Promise<void[]> => requestUpdateMetadata({ meta: serverMetadata }, 'global', keysToValues);
|
): Promise<void[]> => requestUpdateMetadata({ meta: serverMetadata }, 'global', keysToValues);
|
||||||
|
|
||||||
export const requestUpdateMangaMetadata = async (
|
export const requestUpdateMangaMetadata = async (
|
||||||
manga: MangaType,
|
manga: TManga,
|
||||||
keysToValues: MetadataKeyValuePair[],
|
keysToValues: MetadataKeyValuePair[],
|
||||||
): Promise<void[]> => requestUpdateMetadata(manga, 'manga', keysToValues);
|
): Promise<void[]> => requestUpdateMetadata(manga, 'manga', keysToValues);
|
||||||
|
|
||||||
export const requestUpdateChapterMetadata = async (
|
export const requestUpdateChapterMetadata = async (
|
||||||
chapter: ChapterType,
|
chapter: TChapter,
|
||||||
keysToValues: MetadataKeyValuePair[],
|
keysToValues: MetadataKeyValuePair[],
|
||||||
): Promise<void[]> => requestUpdateMetadata(chapter, 'chapter', keysToValues);
|
): Promise<void[]> => requestUpdateMetadata(chapter, 'chapter', keysToValues);
|
||||||
|
|
||||||
export const requestUpdateCategoryMetadata = async (
|
export const requestUpdateCategoryMetadata = async (
|
||||||
category: CategoryType,
|
category: TCategory,
|
||||||
keysToValues: MetadataKeyValuePair[],
|
keysToValues: MetadataKeyValuePair[],
|
||||||
): Promise<void[]> => requestUpdateMetadata(category, 'category', keysToValues);
|
): Promise<void[]> => requestUpdateMetadata(category, 'category', keysToValues);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
* 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 { Metadata, IReaderSettings, MetadataKeyValuePair, GqlMetaHolder } from '@/typings';
|
import { Metadata, IReaderSettings, MetadataKeyValuePair, GqlMetaHolder, TManga } from '@/typings';
|
||||||
import requestManager from '@/lib/requests/RequestManager.ts';
|
import requestManager from '@/lib/requests/RequestManager.ts';
|
||||||
import {
|
import {
|
||||||
convertFromGqlMeta,
|
convertFromGqlMeta,
|
||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
requestUpdateMangaMetadata,
|
requestUpdateMangaMetadata,
|
||||||
requestUpdateServerMetadata,
|
requestUpdateServerMetadata,
|
||||||
} from '@/util/metadata';
|
} from '@/util/metadata';
|
||||||
import { MangaType, MetaType } from '@/lib/graphql/generated/graphql.ts';
|
import { MetaType } from '@/lib/graphql/generated/graphql.ts';
|
||||||
|
|
||||||
type UndefinedReaderSettings = {
|
type UndefinedReaderSettings = {
|
||||||
[setting in keyof IReaderSettings]: IReaderSettings[setting] | undefined;
|
[setting in keyof IReaderSettings]: IReaderSettings[setting] | undefined;
|
||||||
@@ -103,7 +103,7 @@ export const checkAndHandleMissingStoredReaderSettings = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (metadataHolderType === 'manga') {
|
if (metadataHolderType === 'manga') {
|
||||||
await requestUpdateMangaMetadata(metadataHolder as MangaType, settingsToUpdate);
|
await requestUpdateMangaMetadata(metadataHolder as TManga, settingsToUpdate);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user