/* * 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 FavoriteIcon from '@mui/icons-material/Favorite'; import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder'; import { styled } from '@mui/material/styles'; import { ComponentProps, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { t as translate } from 'i18next'; import Link from '@mui/material/Link'; import Typography from '@mui/material/Typography'; import { useLongPress } from 'use-long-press'; import Chip from '@mui/material/Chip'; import Tooltip from '@mui/material/Tooltip'; import LaunchIcon from '@mui/icons-material/Launch'; import Collapse from '@mui/material/Collapse'; import Stack from '@mui/material/Stack'; import IconButton from '@mui/material/IconButton'; import ExpandLessIcon from '@mui/icons-material/ExpandLess'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import { makeToast } from '@/components/util/Toast'; import { Mangas, MangaThumbnailInfo, MangaTrackRecordInfo } from '@/lib/data/Mangas.ts'; import { SpinnerImage } from '@/components/util/SpinnerImage.tsx'; import { CustomIconButton } from '@/components/atoms/CustomIconButton'; import { TrackMangaButton } from '@/components/manga/TrackMangaButton.tsx'; import { useManageMangaLibraryState } from '@/components/manga/useManageMangaLibraryState.tsx'; import { Metadata as BaseMetadata } from '@/components/atoms/Metadata.tsx'; import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts'; import { MangaType, SourceType } from '@/lib/graphql/generated/graphql.ts'; import { useLocalStorage } from '@/util/useStorage.tsx'; const DetailsWrapper = styled('div')(({ theme }) => ({ display: 'flex', flexDirection: 'column', gap: theme.spacing(2), padding: theme.spacing(1), [theme.breakpoints.up('md')]: { flexBasis: '40%', height: 'calc(100vh - 64px)', overflowY: 'auto', }, })); const TopContentWrapper = styled('div')(() => ({})); const ThumbnailMetadataWrapper = styled('div')(() => ({ display: 'flex', })); const Thumbnail = styled('div')(({ theme }) => ({ '& img': { borderRadius: 4, width: '150px', height: 'auto', [theme.breakpoints.up('lg')]: { width: '200px', }, [theme.breakpoints.up('xl')]: { width: '300px', }, }, })); const MetadataContainer = styled('div')(({ theme }) => ({ marginLeft: theme.spacing(1), })); const Metadata = (props: ComponentProps) => ; const MangaButtonsContainer = styled('div')(({ theme }) => ({ display: 'flex', gap: theme.spacing(1), })); const OpenSourceButton = ({ url }: { url?: string | null }) => { const { t } = useTranslation(); return ( ); }; function getSourceName(source?: Pick | null): string { if (!source) { return translate('global.label.unknown'); } return source.displayName ?? source.id; } function getValueOrUnknown(val?: string | null) { return val ?? translate('global.label.unknown'); } const DESCRIPTION_COLLAPSED_SIZE = 75; const DescriptionGenre = ({ manga: { description, genre: genres }, }: { manga: Pick; }) => { const [isCollapsed, setIsCollapsed] = useLocalStorage('isDescriptionGenreCollapsed', true); return ( <> {description} setIsCollapsed(!isCollapsed)} sx={{ pt: 1, alignItems: 'center', cursor: 'pointer', position: isCollapsed ? 'absolute' : null, width: '100%', bottom: 0, background: (theme) => `linear-gradient(transparent 1px, ${theme.palette.background.paper})`, }} > (theme.palette.mode === 'light' ? 'black' : 'text') }}> {isCollapsed ? : } {genres.map((genre) => ( ))} ); }; export const MangaDetails = ({ manga, }: { manga: Pick< MangaType, 'id' | 'title' | 'author' | 'artist' | 'status' | 'inLibrary' | 'realUrl' | 'description' | 'genre' > & MangaThumbnailInfo & MangaTrackRecordInfo & { source?: Pick | null; }; }) => { const { t } = useTranslation(); useEffect(() => { if (!manga.source) { makeToast(translate('source.error.label.source_not_found'), 'error'); } }, [manga.source]); const { CategorySelectComponent, updateLibraryState } = useManageMangaLibraryState(manga); const copyTitleLongPressBind = useLongPress(async () => { try { await navigator.clipboard.writeText(manga.title); makeToast(t('global.label.copied'), 'info'); } catch (e) { defaultPromiseErrorHandler('MangaDetails::copyTitleLongPress')(e); } }); return ( <> {manga.title} {manga.inLibrary ? : } {manga.inLibrary ? t('manga.button.in_library') : t('manga.button.add_to_library')} {CategorySelectComponent} ); };