Skip to content

Commit

Permalink
Use gql for "mangas" I - get manga
Browse files Browse the repository at this point in the history
  • Loading branch information
schroda committed Oct 27, 2023
1 parent a5b4b95 commit 89dc053
Show file tree
Hide file tree
Showing 14 changed files with 170 additions and 126 deletions.
46 changes: 35 additions & 11 deletions src/components/manga/MangaDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ import FavoriteIcon from '@mui/icons-material/Favorite';
import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder';
import PublicIcon from '@mui/icons-material/Public';
import { styled } from '@mui/material/styles';
import React, { useEffect } from 'react';
import React, { useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { mutate } from 'swr';
import { t as translate } from 'i18next';
import Button from '@mui/material/Button';
import { IManga, ISource } from '@/typings';
import { ISource } from '@/typings';
import requestManager from '@/lib/requests/RequestManager.ts';
import makeToast from '@/components/util/Toast';
import { MangaType } from '@/lib/graphql/generated/graphql.ts';

const DetailsWrapper = styled('div')(({ theme }) => ({
width: '100%',
Expand Down Expand Up @@ -125,19 +126,44 @@ const Genres = styled('div')(() => ({
},
}));

const OpenSourceButton = ({ url }: { url?: string | null }) => {
const { t } = useTranslation();

const button = useMemo(
() => (
<Button disabled={!!url} startIcon={<PublicIcon />} size="large">
{t('global.button.open_site')}
</Button>
),
[url],
);

if (!url) {
return button;
}

return (
<a href={url} target="_blank" rel="noreferrer">
<Button startIcon={<PublicIcon />} size="large">
{t('global.button.open_site')}
</Button>
</a>
);
};

interface IProps {
manga: IManga;
manga: MangaType;
}

function getSourceName(source: ISource) {
function getSourceName(source?: ISource | null) {
if (!source) {
return translate('global.label.unknown');
}

return source.displayName ?? source.id;
}

function getValueOrUnknown(val: string) {
function getValueOrUnknown(val?: string | null) {
return val || 'UNKNOWN';
}

Expand Down Expand Up @@ -169,7 +195,9 @@ const MangaDetails: React.FC<IProps> = ({ manga }) => {
<TopContentWrapper>
<ThumbnailMetadataWrapper>
<Thumbnail>
<img src={requestManager.getValidImgUrlFor(manga.thumbnailUrl)} alt="Manga Thumbnail" />
{manga.thumbnailUrl && (
<img src={requestManager.getValidImgUrlFor(manga.thumbnailUrl)} alt="Manga Thumbnail" />
)}
</Thumbnail>
<Metadata>
<h1>{manga.title}</h1>
Expand All @@ -195,11 +223,7 @@ const MangaDetails: React.FC<IProps> = ({ manga }) => {
{manga.inLibrary ? t('manga.button.in_library') : t('manga.button.add_to_library')}
</Button>
</div>
<a href={manga.realUrl} target="_blank" rel="noreferrer">
<Button startIcon={<PublicIcon />} size="large">
{t('global.button.open_site')}
</Button>
</a>
<OpenSourceButton url={manga.realUrl} />
</MangaButtonsContainer>
</TopContentWrapper>
<BottomContentWrapper>
Expand Down
4 changes: 2 additions & 2 deletions src/components/manga/MangaToolbarMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ import {
} from '@mui/material';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { IManga } from '@/typings';
import CategorySelect from '@/components/navbar/action/CategorySelect';
import { MangaType } from '@/lib/graphql/generated/graphql.ts';

interface IProps {
manga: IManga;
manga: MangaType;
onRefresh: () => any;
refreshing: boolean;
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/manga/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const useRefreshManga = (mangaId: string) => {
const handleRefresh = useCallback(async () => {
setFetchingOnline(true);
await Promise.all([
requestManager.getManga(mangaId, true).response.then((res) => {
requestManager.getMangaFetch(mangaId).response.then((res) => {
mutate(`${RequestManager.API_VERSION}manga/${mangaId}`, res, { revalidate: false });
}),
requestManager.getMangaChapters(mangaId, true).response.then((res) =>
Expand Down
5 changes: 3 additions & 2 deletions src/components/navbar/ReaderNavBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ import ListItemText from '@mui/material/ListItemText';
import ListItemSecondaryAction from '@mui/material/ListItemSecondaryAction';
import Collapse from '@mui/material/Collapse';
import { useTranslation } from 'react-i18next';
import { ChapterOffset, IChapter, IManga, IMangaCard, IReaderSettings } from '@/typings';
import { ChapterOffset, IChapter, IReaderSettings } from '@/typings';
import ReaderSettingsOptions from '@/components/reader/ReaderSettingsOptions';
import { MangaType } from '@/lib/graphql/generated/graphql.ts';

const Root = styled('div')(({ theme }) => ({
top: 0,
Expand Down Expand Up @@ -114,7 +115,7 @@ const OpenDrawerButton = styled(IconButton)(({ theme }) => ({
interface IProps {
settings: IReaderSettings;
setSettingValue: (key: keyof IReaderSettings, value: string | boolean) => void;
manga: IManga | IMangaCard;
manga: Pick<MangaType, 'id'>;
chapter: IChapter;
curPage: number;
scrollToPage: (page: number) => void;
Expand Down
2 changes: 1 addition & 1 deletion src/components/util/EmptyView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ function getRandomErrorFace() {

interface IProps {
message: string;
messageExtra?: JSX.Element;
messageExtra?: JSX.Element | string;
}

export default function EmptyView({ message, messageExtra }: IProps) {
Expand Down
48 changes: 29 additions & 19 deletions src/lib/requests/RequestManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ import {
GetExtensionsQueryVariables,
GetGlobalMetadatasQuery,
GetGlobalMetadatasQueryVariables,
GetMangaFetchMutation,
GetMangaFetchMutationVariables,
GetMangaQuery,
GetMangaQueryVariables,
GetSourceQuery,
GetSourceQueryVariables,
GetSourcesQuery,
Expand Down Expand Up @@ -125,7 +129,12 @@ import {
UPDATE_EXTENSION,
} from '@/lib/graphql/mutations/ExtensionMutation.ts';
import { GET_SOURCE, GET_SOURCES } from '@/lib/graphql/queries/SourceQuery.ts';
import { SET_MANGA_METADATA, UPDATE_MANGA, UPDATE_MANGA_CATEGORIES } from '@/lib/graphql/mutations/MangaMutation.ts';
import {
GET_MANGA_FETCH,
SET_MANGA_METADATA,
UPDATE_MANGA,
UPDATE_MANGA_CATEGORIES,
} from '@/lib/graphql/mutations/MangaMutation.ts';
import { GET_MANGA, GET_MANGAS } from '@/lib/graphql/queries/MangaQuery.ts';
import { GET_CATEGORIES, GET_CATEGORY, GET_CATEGORY_MANGAS } from '@/lib/graphql/queries/CategoryQuery.ts';
import { GET_SOURCE_MANGAS_FETCH } from '@/lib/graphql/mutations/SourceMutation.ts';
Expand Down Expand Up @@ -712,27 +721,28 @@ export class RequestManager {

public useGetManga(
mangaId: number | string,
{ doOnlineFetch, ...swrOptions }: SWROptions<IManga> & RequestOption = {},
): AbortableSWRResponse<IManga> {
const onlineFetch = doOnlineFetch ? '?onlineFetch=true' : '';
return this.doRequest(HttpMethod.SWR_GET, `manga/${mangaId}${onlineFetch}`, {
swrOptions,
});
options?: QueryHookOptions<GetMangaQuery, GetMangaQueryVariables>,
): AbortableApolloUseQueryResponse<GetMangaQuery, GetMangaQueryVariables> {
return this.doRequestNew(GQLMethod.USE_QUERY, GET_MANGA, { id: Number(mangaId) }, options);
}

public getManga(mangaId: number | string, doOnlineFetch?: boolean): AbortableAxiosResponse<IManga> {
const onlineFetch = doOnlineFetch ? '?onlineFetch=true' : '';
return this.doRequest(HttpMethod.GET, `manga/${mangaId}${onlineFetch}`);
}

public useGetFullManga(
public getMangaFetch(
mangaId: number | string,
{ doOnlineFetch, ...swrOptions }: SWROptions<IManga> & RequestOption = {},
): AbortableSWRResponse<IManga> {
const onlineFetch = doOnlineFetch ? '?onlineFetch=true' : '';
return this.doRequest(HttpMethod.SWR_GET, `manga/${mangaId}/full${onlineFetch}`, {
swrOptions,
});
options?: MutationOptions<GetMangaFetchMutation, GetMangaFetchMutationVariables>,
): AbortableApolloMutationResponse<GetMangaFetchMutation> {
return this.doRequestNew<GetMangaFetchMutation, GetMangaFetchMutationVariables>(
GQLMethod.MUTATION,
GET_MANGA_FETCH,
{
input: {
id: Number(mangaId),
},
},
{
refetchQueries: [GET_MANGA, GET_MANGAS],
...options,
},
);
}

public getMangaThumbnailUrl(mangaId: number): string {
Expand Down
27 changes: 17 additions & 10 deletions src/screens/Manga.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { CircularProgress, IconButton, Stack, Tooltip, Box } from '@mui/material
import React, { useContext, useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';
import { isNetworkRequestInFlight } from '@apollo/client/core/networkStatus';
import requestManager from '@/lib/requests/RequestManager.ts';
import NavbarContext, { useSetDefaultBackTo } from '@/components/context/NavbarContext';
import ChapterList from '@/components/manga/ChapterList';
Expand All @@ -19,8 +20,9 @@ import MangaDetails from '@/components/manga/MangaDetails';
import MangaToolbarMenu from '@/components/manga/MangaToolbarMenu';
import EmptyView from '@/components/util/EmptyView';
import LoadingPlaceholder from '@/components/util/LoadingPlaceholder';
import { MangaType } from '@/lib/graphql/generated/graphql.ts';

const AUTOFETCH_AGE = 60 * 60 * 24; // 24 hours
const AUTOFETCH_AGE = 1000 * 60 * 60 * 24; // 24 hours

const Manga: React.FC = () => {
const { t } = useTranslation();
Expand All @@ -29,21 +31,26 @@ const Manga: React.FC = () => {
const { id } = useParams<{ id: string }>();
const autofetchedRef = useRef(false);

const { data: manga, error, isLoading, isValidating, mutate } = requestManager.useGetManga(id);
const { data, error, loading: isLoading, networkStatus, refetch } = requestManager.useGetManga(id);
const isValidating = isNetworkRequestInFlight(networkStatus);
const manga = data?.manga as MangaType | undefined;

const [refresh, { loading: refreshing }] = useRefreshManga(id);
useSetDefaultBackTo('library');

useEffect(() => {
// Automatically fetch manga from source if data is older then 24 hours
// Automatically fetch manga from source if data is older then 24 hours OR manga is not initialized yet
// Automatic fetch is done only once, to prevent issues when server does
// not update age for some reason (ie. error on source side)
if (manga == null) return;
if (
manga.inLibrary &&
(manga.age > AUTOFETCH_AGE || manga.chaptersAge > AUTOFETCH_AGE) &&
autofetchedRef.current === false
) {

const isOutdated =
Date.now() - Number(manga.lastFetchedAt) * 1000 > AUTOFETCH_AGE ||
Date.now() - Number(manga.chaptersLastFetchedAt) * 1000 > AUTOFETCH_AGE;
const refetchBecauseOutdated = manga.inLibrary && isOutdated;

const doFetch = !autofetchedRef.current && (refetchBecauseOutdated || !manga.initialized);
if (doFetch) {
autofetchedRef.current = true;
refresh();
}
Expand All @@ -67,7 +74,7 @@ const Manga: React.FC = () => {
</>
}
>
<IconButton onClick={() => mutate()}>
<IconButton onClick={() => refetch()}>
<Warning color="error" />
</IconButton>
</Tooltip>
Expand All @@ -80,7 +87,7 @@ const Manga: React.FC = () => {
{manga && <MangaToolbarMenu manga={manga} onRefresh={refresh} refreshing={refreshing} />}
</Stack>,
);
}, [t, error, isValidating, refreshing, mutate, manga, refresh]);
}, [t, error, isValidating, refreshing, manga, refresh]);

if (error && !manga) {
return <EmptyView message={t('manga.error.label.request_failure')} messageExtra={error.message ?? error} />;
Expand Down
32 changes: 19 additions & 13 deletions src/screens/Reader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
*/

import CircularProgress from '@mui/material/CircularProgress';
import { useCallback, useContext, useEffect, useState } from 'react';
import { useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { useLocation, useNavigate, useParams } from 'react-router-dom';
import { Box } from '@mui/material';
import { useTranslation } from 'react-i18next';
import { ChapterOffset, IChapter, IManga, IMangaCard, IReaderSettings, ReaderType, TranslationKey } from '@/typings';
import { ChapterOffset, IChapter, IReaderSettings, ReaderType, TranslationKey } from '@/typings';
import requestManager from '@/lib/requests/RequestManager.ts';
import {
checkAndHandleMissingStoredReaderSettings,
Expand All @@ -27,6 +27,7 @@ import VerticalPager from '@/components/reader/pager/VerticalPager';
import ReaderNavBar from '@/components/navbar/ReaderNavBar';
import NavbarContext from '@/components/context/NavbarContext';
import makeToast from '@/components/util/Toast';
import { MangaType } from '@/lib/graphql/generated/graphql.ts';

const isDupChapter = async (chapterIndex: number, currentChapter: IChapter) => {
const nextChapter = await requestManager.getChapter(currentChapter.mangaId, chapterIndex).response;
Expand Down Expand Up @@ -93,17 +94,22 @@ export default function Reader() {
const location = useLocation();

const { chapterIndex, mangaId } = useParams<{ chapterIndex: string; mangaId: string }>();
const {
data: manga = {
id: +mangaId,
title: '',
thumbnailUrl: '',
genre: [],
inLibraryAt: 0,
lastReadAt: 0,
} as IMangaCard | IManga,
isLoading: isMangaLoading,
} = requestManager.useGetManga(mangaId);

const initialManga = useMemo(
() =>
({
id: +mangaId,
title: '',
thumbnailUrl: '',
genre: [],
inLibraryAt: 0,
lastReadAt: 0,
}) as unknown as MangaType,
[mangaId],
);

const { data, loading: isMangaLoading } = requestManager.useGetManga(mangaId);
const manga = (data?.manga as MangaType) ?? initialManga;
const { data: chapter = initialChapter, isLoading: isChapterLoading } = requestManager.useGetChapter(
mangaId,
chapterIndex,
Expand Down
10 changes: 7 additions & 3 deletions src/screens/settings/DefaultReaderSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { Box } from '@mui/material';
import CircularProgress from '@mui/material/CircularProgress';
import { useTranslation } from 'react-i18next';
import { IReaderSettings } from '@/typings';
import { requestUpdateServerMetadata } from '@/util/metadata';
import { convertToGqlMeta, requestUpdateServerMetadata } from '@/util/metadata';
import {
checkAndHandleMissingStoredReaderSettings,
getDefaultSettings,
Expand All @@ -34,7 +34,7 @@ export default function DefaultReaderSettings() {
useSetDefaultBackTo('settings');

const setSettingValue = (key: keyof IReaderSettings, value: string | boolean) => {
requestUpdateServerMetadata(metadata ?? {}, [[key, value]]).catch(() =>
requestUpdateServerMetadata(convertToGqlMeta(metadata)! ?? {}, [[key, value]]).catch(() =>
makeToast(t('reader.settings.error.label.failed_to_save_settings'), 'warning'),
);
};
Expand All @@ -54,7 +54,11 @@ export default function DefaultReaderSettings() {
);
}

checkAndHandleMissingStoredReaderSettings({ meta: metadata }, 'server', getDefaultSettings()).catch(() => {});
checkAndHandleMissingStoredReaderSettings(
{ meta: convertToGqlMeta(metadata)! },
'server',
getDefaultSettings(),
).catch(() => {});

return (
<ReaderSettingsOptions
Expand Down
4 changes: 2 additions & 2 deletions src/screens/settings/SearchSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import ListItemIcon from '@mui/material/ListItemIcon';
import SearchIcon from '@mui/icons-material/Search';
import { useTranslation } from 'react-i18next';
import { SearchMetadataKeys } from '@/typings';
import { requestUpdateServerMetadata } from '@/util/metadata';
import { convertToGqlMeta, requestUpdateServerMetadata } from '@/util/metadata';
import { useSearchSettings } from '@/util/searchSettings';
import makeToast from '@/components/util/Toast';
import { useSetDefaultBackTo } from '@/components/context/NavbarContext';
Expand All @@ -24,7 +24,7 @@ export default function SearchSettings() {
useSetDefaultBackTo('settings');

const setSettingValue = (key: SearchMetadataKeys, value: boolean) => {
requestUpdateServerMetadata(metadata ?? {}, [[key, value]]).catch(() =>
requestUpdateServerMetadata(convertToGqlMeta(metadata)! ?? {}, [[key, value]]).catch(() =>
makeToast(t('search.error.label.failed_to_save_settings'), 'warning'),
);
};
Expand Down
Loading

0 comments on commit 89dc053

Please sign in to comment.