/*
* 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 Button from '@mui/material/Button';
import List from '@mui/material/List';
import Stack from '@mui/material/Stack';
import { useEffect, useMemo, useState } from 'react';
import IconButton from '@mui/material/IconButton';
import ArrowBack from '@mui/icons-material/ArrowBack';
import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
import DialogContent from '@mui/material/DialogContent';
import DialogTitle from '@mui/material/DialogTitle';
import InputAdornment from '@mui/material/InputAdornment';
import InfoIcon from '@mui/icons-material/Info';
import PopupState, { bindPopover, bindTrigger } from 'material-ui-popup-state';
import Popover from '@mui/material/Popover';
import Typography from '@mui/material/Typography';
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
import { useLingui } from '@lingui/react/macro';
import { requestManager } from '@/lib/requests/RequestManager.ts';
import { EmptyViewAbsoluteCentered } from '@/base/components/feedback/EmptyViewAbsoluteCentered.tsx';
import { LoadingPlaceholder } from '@/base/components/feedback/LoadingPlaceholder.tsx';
import { SearchTextField } from '@/base/components/inputs/SearchTextField.tsx';
import { makeToast } from '@/base/utils/Toast.ts';
import { TrackerMangaCard } from '@/features/tracker/components/cards/TrackerMangaCard.tsx';
import { DIALOG_PADDING } from '@/features/tracker/Tracker.constants.ts';
import { useGetOptionForDirection } from '@/features/theme/services/ThemeCreator.ts';
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
import type { MangaType } from '@/lib/graphql/generated/graphql.ts';
import type { MangaIdInfo } from '@/features/manga/Manga.types.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
import { applyStyles } from '@/base/utils/ApplyStyles.ts';
import type { TrackerIdInfo, TTrackerBind } from '@/features/tracker/Tracker.types.ts';
import { Tracker } from '@/features/tracker/Tracker.types.ts';
import { CustomButtonIcon } from '@/base/components/buttons/CustomButtonIcon.tsx';
import { CustomTooltip } from '@/base/components/CustomTooltip.tsx';
import { STABLE_EMPTY_ARRAY } from '@/base/Base.constants.ts';
const TrackButton = ({
mangaId,
selectedTrackerRemoteId,
trackerId,
closeSearchMode,
supportsPrivateTracking,
}: {
trackerId: TrackerIdInfo['id'];
mangaId: MangaIdInfo['id'];
selectedTrackerRemoteId: string | undefined;
closeSearchMode: () => void;
supportsPrivateTracking: boolean;
}) => {
const { t } = useLingui();
const [bindTracker, bindTrackerMutation] = requestManager.useBindTracker();
const trackManga = (asPrivate: boolean) => {
if (selectedTrackerRemoteId === undefined) {
return;
}
bindTracker({
variables: { input: { mangaId, remoteId: selectedTrackerRemoteId, trackerId, private: asPrivate } },
})
.then(() => {
makeToast(t`Tracked manga`, 'success');
closeSearchMode();
})
.catch((e) => makeToast(t`Could not track manga`, 'error', getErrorMessage(e)));
};
return (
{supportsPrivateTracking && (
trackManga(true)}
>
)}
);
};
export const TrackerSearch = ({
manga,
tracker,
closeSearchMode,
trackedId,
}: {
manga: MangaIdInfo & Pick;
tracker: TTrackerBind;
closeSearchMode: () => void;
trackedId?: string;
}) => {
const { t } = useLingui();
const getOptionForDirection = useGetOptionForDirection();
const [searchString, setSearchString] = useState(manga.title);
const [tmpSearchString, setTmpSearchString] = useState(searchString);
const [selectedTrackerRemoteId, setSelectedTrackerRemoteId] = useState(trackedId);
const trackerSearch = requestManager.useTrackerSearch(tracker.id, searchString, {
notifyOnNetworkStatusChange: true,
});
const searchResults = trackerSearch.data?.searchTracker.trackSearches ?? STABLE_EMPTY_ARRAY;
const hasResults = !!searchResults.length;
const hasNoResults = !trackerSearch.loading && !trackerSearch.error && !hasResults;
const hasError = !!trackerSearch.error && !trackerSearch.loading;
useEffect(() => {
setSelectedTrackerRemoteId(trackedId);
return () =>
trackerSearch.abortRequest(new Error(`MangaTrackerSearchCard(${tracker.id}, ${manga.id}): search changed`));
}, [searchString]);
const showTrackButton =
useMemo(
() =>
!!selectedTrackerRemoteId &&
!!searchResults.find((searchResult) => searchResult.remoteId === selectedTrackerRemoteId),
[selectedTrackerRemoteId, searchResults],
) && !hasError;
return (
<>
{getOptionForDirection(, )}
setTmpSearchString(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
setSearchString(tmpSearchString);
}
}}
onCancel={() => setTmpSearchString('')}
InputProps={{
startAdornment: tracker.id === Tracker.MYANIMELIST && (
{(popupState) => (
<>
{t`Search for a ID via "id:" (e.g. "id:13")\nLimit search to your lists via "my:" (e.g. "my:One Piece")`}
>
)}
),
}}
/>
{hasNoResults && }
{trackerSearch.loading && }
{hasError && (
trackerSearch.refetch().catch(defaultPromiseErrorHandler('TrackerSearch::refetch'))
}
/>
)}
{hasResults &&
searchResults.map((trackerManga) => (
setSelectedTrackerRemoteId(trackerManga.remoteId)}
/>
))}
{showTrackButton && (
)}
>
);
};