feat/improvements #37
@ -26,20 +26,25 @@ export const MangaChapter = ({
|
|||||||
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const { mutate: mutateDownloadChapterArchive } = useDownloadContentArchive({
|
const { mutate: mutateDownloadChapterArchive, isPending: isDownloading } =
|
||||||
mutation: {
|
useDownloadContentArchive({
|
||||||
onSuccess: (data, { mangaContentId }) => {
|
mutation: {
|
||||||
const url = window.URL.createObjectURL(data);
|
onMutate: ({ mangaContentId }) => setDownloadingId(mangaContentId),
|
||||||
const link = document.createElement("a");
|
onSuccess: (data, { mangaContentId }) => {
|
||||||
link.href = url;
|
const url = window.URL.createObjectURL(data);
|
||||||
link.setAttribute("download", `${mangaContentId}.cbz`);
|
const link = document.createElement("a");
|
||||||
document.body.appendChild(link);
|
link.href = url;
|
||||||
link.click();
|
link.setAttribute("download", `${mangaContentId}.cbz`);
|
||||||
link.remove();
|
document.body.appendChild(link);
|
||||||
window.URL.revokeObjectURL(url);
|
link.click();
|
||||||
|
link.remove();
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
},
|
||||||
|
onSettled: () => setDownloadingId(null),
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
});
|
|
||||||
|
const [downloadingId, setDownloadingId] = useState<number | null>(null);
|
||||||
|
|
||||||
const { mutate, isPending: isPendingFetchChapter } =
|
const { mutate, isPending: isPendingFetchChapter } =
|
||||||
useFetchContentProviderContent({
|
useFetchContentProviderContent({
|
||||||
@ -78,13 +83,12 @@ export const MangaChapter = ({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={chapter.id}
|
key={chapter.id}
|
||||||
className="flex items-center justify-between rounded-lg border border-border bg-background p-3"
|
className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between rounded-lg border border-border bg-background p-4"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-4">
|
||||||
<div
|
<div
|
||||||
className={`flex h-8 w-8 items-center justify-center rounded-full ${
|
className={`flex h-8 w-8 items-center justify-center rounded-full ${chapter.isRead ? "bg-primary/20" : "bg-muted"
|
||||||
chapter.isRead ? "bg-primary/20" : "bg-muted"
|
}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{chapter.isRead ? (
|
{chapter.isRead ? (
|
||||||
<Check className="h-4 w-4 text-primary" />
|
<Check className="h-4 w-4 text-primary" />
|
||||||
@ -94,35 +98,57 @@ export const MangaChapter = ({
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="min-w-0 flex-1 flex items-center justify-between gap-2">
|
||||||
<p className="text-sm font-medium text-foreground flex items-center gap-2">
|
<div className="min-w-0 flex-1">
|
||||||
{chapter.language?.code && (
|
<p className="text-sm font-medium text-foreground flex items-center gap-2 flex-wrap min-w-0">
|
||||||
<ReactCountryFlag
|
{chapter.language?.code && (
|
||||||
countryCode={chapter.language.code.split("-")[1]}
|
<ReactCountryFlag
|
||||||
svg
|
countryCode={chapter.language.code.split("-")[1]}
|
||||||
style={{
|
svg
|
||||||
width: "1.2em",
|
style={{
|
||||||
height: "1.2em",
|
width: "1.2em",
|
||||||
}}
|
height: "1.2em",
|
||||||
title={chapter.language.name}
|
}}
|
||||||
/>
|
title={chapter.language.name}
|
||||||
)}
|
/>
|
||||||
{chapter.title}
|
)}
|
||||||
</p>
|
<span className="truncate flex-1">{chapter.title}</span>
|
||||||
{chapter.downloaded && (
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
In database
|
|
||||||
</p>
|
</p>
|
||||||
|
{chapter.downloaded && (
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
In database
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{chapter.downloaded && (
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() =>
|
||||||
|
mutateDownloadChapterArchive({
|
||||||
|
mangaContentId: chapter.id,
|
||||||
|
params: { contentArchiveFileType: "CBZ" },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="sm:hidden h-8 w-8 p-0"
|
||||||
|
disabled={isDownloading && downloadingId === chapter.id}
|
||||||
|
>
|
||||||
|
{isDownloading && downloadingId === chapter.id ? (
|
||||||
|
<Loader2 className="h-4 w-4 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<Download className="h-4 w-4" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{chapter.downloaded ? (
|
{chapter.downloaded ? (
|
||||||
<div className="flex gap-2">
|
<div className="flex flex-col gap-3 sm:flex-row sm:gap-2 w-full sm:w-auto">
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => handleReadChapter(chapter.id)}
|
onClick={() => handleReadChapter(chapter.id)}
|
||||||
className="gap-2"
|
className="gap-2 w-full sm:w-auto"
|
||||||
>
|
>
|
||||||
<Eye className="h-4 w-4" />
|
<Eye className="h-4 w-4" />
|
||||||
Read
|
Read
|
||||||
@ -136,9 +162,14 @@ export const MangaChapter = ({
|
|||||||
params: { contentArchiveFileType: "CBZ" },
|
params: { contentArchiveFileType: "CBZ" },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="gap-2"
|
className="hidden sm:flex gap-2 w-full sm:w-auto"
|
||||||
|
disabled={isDownloading && downloadingId === chapter.id}
|
||||||
>
|
>
|
||||||
<Download className="h-4 w-4" />
|
{isDownloading && downloadingId === chapter.id ? (
|
||||||
|
<Loader2 className="h-4 w-4 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<Download className="h-4 w-4" />
|
||||||
|
)}
|
||||||
Download
|
Download
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@ -148,7 +179,7 @@ export const MangaChapter = ({
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => fetchChapter(chapter.id)}
|
onClick={() => fetchChapter(chapter.id)}
|
||||||
disabled={isPendingFetchChapter}
|
disabled={isPendingFetchChapter}
|
||||||
className="gap-2 cursor-pointer"
|
className="gap-2 cursor-pointer w-full sm:w-auto"
|
||||||
>
|
>
|
||||||
<Database className="h-4 w-4" />
|
<Database className="h-4 w-4" />
|
||||||
{isPendingFetchChapter && fetchingId === chapter.id
|
{isPendingFetchChapter && fetchingId === chapter.id
|
||||||
|
|||||||
@ -3,7 +3,6 @@ import { useEffect, useRef, useState } from "react";
|
|||||||
import { useNavigate, useParams } from "react-router";
|
import { useNavigate, useParams } from "react-router";
|
||||||
import { useGetMangaContentImages } from "@/api/generated/content/content.ts";
|
import { useGetMangaContentImages } from "@/api/generated/content/content.ts";
|
||||||
import { useMarkContentAsRead } from "@/api/generated/user-interaction/user-interaction.ts";
|
import { useMarkContentAsRead } from "@/api/generated/user-interaction/user-interaction.ts";
|
||||||
import { ThemeToggle } from "@/components/ThemeToggle.tsx";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@ -314,76 +313,85 @@ const Chapter = () => {
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
{/* HEADER */}
|
{/* HEADER */}
|
||||||
<header className="sticky top-0 z-50 border-b border-border bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
<header className="sticky top-0 z-50 border-b border-border bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 text-xs sm:text-sm">
|
||||||
<div className="px-4 py-4 sm:px-8">
|
<div className="px-2 py-2 sm:px-4 sm:py-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between gap-2 sm:gap-4">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-1 sm:gap-4 flex-1 min-w-0">
|
||||||
<Button
|
<div className="flex items-center gap-1 shrink-0">
|
||||||
variant="ghost"
|
<Button
|
||||||
size="sm"
|
variant="ghost"
|
||||||
onClick={() => navigate(`/manga/${mangaId}`)}
|
size="sm"
|
||||||
className="gap-2"
|
onClick={() => navigate(`/manga/${mangaId}`)}
|
||||||
>
|
className="gap-2 px-2 sm:px-3"
|
||||||
<ArrowLeft className="h-4 w-4" />
|
>
|
||||||
<span className="hidden sm:inline">Back</span>
|
<ArrowLeft className="h-4 w-4" />
|
||||||
</Button>
|
<span className="hidden md:inline">Back</span>
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => navigate("/")}
|
onClick={() => navigate("/")}
|
||||||
className="gap-2"
|
className="gap-2 px-1.5 sm:px-3"
|
||||||
>
|
>
|
||||||
<Home className="h-4 w-4" />
|
<Home className="h-4 w-4" />
|
||||||
<span className="hidden sm:inline">Home</span>
|
<span className="hidden md:inline">Home</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<div className="hidden sm:block">
|
|
||||||
<h1 className="text-sm font-semibold text-foreground">
|
|
||||||
{data.data.mangaTitle}
|
|
||||||
</h1>
|
|
||||||
<p className="text-xs text-muted-foreground text-center">
|
|
||||||
{chapterNumber}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{previousContentId && (
|
<div className="flex items-center gap-1 shrink-0">
|
||||||
<Button
|
{previousContentId && (
|
||||||
variant="ghost"
|
<Button
|
||||||
size="sm"
|
variant="ghost"
|
||||||
onClick={goToPreviousChapter}
|
size="sm"
|
||||||
className="gap-1"
|
onClick={goToPreviousChapter}
|
||||||
>
|
className="gap-1 px-1.5"
|
||||||
<ChevronLeft className="h-4 w-4" />
|
>
|
||||||
<span className="hidden sm:inline">Prev</span>
|
<ChevronLeft className="h-4 w-4" />
|
||||||
</Button>
|
<span className="hidden sm:inline">Prev</span>
|
||||||
)}
|
</Button>
|
||||||
{nextContentId && (
|
)}
|
||||||
<Button
|
{nextContentId && (
|
||||||
variant="ghost"
|
<Button
|
||||||
size="sm"
|
variant="ghost"
|
||||||
onClick={goToNextChapter}
|
size="sm"
|
||||||
className="gap-1"
|
onClick={goToNextChapter}
|
||||||
>
|
className="gap-1 px-1.5"
|
||||||
<span className="hidden sm:inline">Next</span>
|
>
|
||||||
<ChevronRight className="h-4 w-4" />
|
<span className="hidden sm:inline">Next</span>
|
||||||
</Button>
|
<ChevronRight className="h-4 w-4" />
|
||||||
)}
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Title Info - now visible on all screens in the middle */}
|
||||||
|
<div className="flex-1 min-w-0 px-1 text-center">
|
||||||
|
<h1 className="text-[10px] sm:text-xs font-semibold text-foreground truncate">
|
||||||
|
{data.data.mangaTitle}
|
||||||
|
</h1>
|
||||||
|
<p className="text-[8px] sm:text-[10px] text-muted-foreground truncate">
|
||||||
|
Chapter {chapterNumber}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-2 sm:gap-4 shrink-0">
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-[10px] sm:text-sm text-muted-foreground whitespace-nowrap">
|
||||||
Page {currentPage} / {images.length}
|
{currentPage} / {images.length}
|
||||||
</span>
|
</span>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => setInfiniteScroll((v) => !v)}
|
onClick={() => setInfiniteScroll((v) => !v)}
|
||||||
className="text-xs"
|
className="h-8 px-2 sm:px-3 text-[10px] sm:text-xs"
|
||||||
>
|
>
|
||||||
{infiniteScroll ? "Single Page Mode" : "Scroll Mode"}
|
{infiniteScroll ? (
|
||||||
|
<span className="">Single</span>
|
||||||
|
) : (
|
||||||
|
<span className="">Scroll</span>
|
||||||
|
)}
|
||||||
|
<span className="hidden sm:inline"> Mode</span>
|
||||||
</Button>
|
</Button>
|
||||||
<ThemeToggle />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -391,18 +399,10 @@ const Chapter = () => {
|
|||||||
|
|
||||||
{/* MAIN */}
|
{/* MAIN */}
|
||||||
<main
|
<main
|
||||||
className={`mx-auto max-w-4xl ${
|
className={`mx-auto max-w-4xl ${infiniteScroll ? "px-0 py-0" : "px-4 py-8"
|
||||||
infiniteScroll ? "px-0 py-0" : "px-4 py-8"
|
}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<div className="mb-4 sm:hidden">
|
{/* Removed mobile header from main as it's now in the sticky header */}
|
||||||
<h1 className="text-lg font-semibold text-foreground">
|
|
||||||
{data.data.mangaTitle}
|
|
||||||
</h1>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
{chapterNumber}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* ------------------------------------------------------------------ */}
|
{/* ------------------------------------------------------------------ */}
|
||||||
{/* MODE 1 --- INFINITE SCROLL MODE */}
|
{/* MODE 1 --- INFINITE SCROLL MODE */}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import {
|
|||||||
ChevronDown,
|
ChevronDown,
|
||||||
Database,
|
Database,
|
||||||
Heart,
|
Heart,
|
||||||
|
MoreVertical,
|
||||||
Star,
|
Star,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
@ -30,6 +31,12 @@ import {
|
|||||||
CollapsibleContent,
|
CollapsibleContent,
|
||||||
CollapsibleTrigger,
|
CollapsibleTrigger,
|
||||||
} from "@/components/ui/collapsible.tsx";
|
} from "@/components/ui/collapsible.tsx";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu.tsx";
|
||||||
import { useAuth } from "@/contexts/AuthContext.tsx";
|
import { useAuth } from "@/contexts/AuthContext.tsx";
|
||||||
import { useUIState } from "@/contexts/UIStateContext.tsx";
|
import { useUIState } from "@/contexts/UIStateContext.tsx";
|
||||||
import { MangaChapter } from "@/features/manga/MangaChapter.tsx";
|
import { MangaChapter } from "@/features/manga/MangaChapter.tsx";
|
||||||
@ -155,9 +162,9 @@ const Manga = () => {
|
|||||||
{/* Content Shell */}
|
{/* Content Shell */}
|
||||||
<main className="px-8 py-8">
|
<main className="px-8 py-8">
|
||||||
<div className="mx-auto max-w-7xl">
|
<div className="mx-auto max-w-7xl">
|
||||||
<div className="grid gap-8 lg:grid-cols-[300px_1fr]">
|
<div className="grid gap-8 md:grid-cols-[300px_1fr]">
|
||||||
{/* Cover Skeleton */}
|
{/* Cover Skeleton */}
|
||||||
<Skeleton className="aspect-2/3 w-full rounded-lg lg:sticky lg:top-8" />
|
<Skeleton className="aspect-2/3 w-full rounded-lg md:sticky md:top-8" />
|
||||||
|
|
||||||
{/* Details Skeleton */}
|
{/* Details Skeleton */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
@ -252,10 +259,10 @@ const Manga = () => {
|
|||||||
<main className="px-8 py-8">
|
<main className="px-8 py-8">
|
||||||
<div className="mx-auto max-w-7xl">
|
<div className="mx-auto max-w-7xl">
|
||||||
{/* Manga Info Section */}
|
{/* Manga Info Section */}
|
||||||
<div className="grid gap-8 lg:grid-cols-[300px_1fr]">
|
<div className="grid gap-8 md:grid-cols-[300px_1fr]">
|
||||||
{/* Cover */}
|
{/* Cover */}
|
||||||
<div className="lg:sticky lg:top-24">
|
<div className="md:sticky md:top-24">
|
||||||
<div className="relative aspect-2/3 overflow-hidden rounded-lg border border-border bg-muted">
|
<div className="relative aspect-2/3 overflow-hidden rounded-lg border border-border bg-muted w-full">
|
||||||
<img
|
<img
|
||||||
src={
|
src={
|
||||||
(mangaData.data?.coverImageKey &&
|
(mangaData.data?.coverImageKey &&
|
||||||
@ -273,14 +280,14 @@ const Manga = () => {
|
|||||||
{/* Details */}
|
{/* Details */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<div className="mb-3 flex items-center justify-between gap-4">
|
<div className="mb-4 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||||
<h1 className="text-balance text-4xl font-bold tracking-tight text-foreground">
|
<h1 className="text-balance text-3xl sm:text-4xl font-bold tracking-tight text-foreground">
|
||||||
{mangaData.data?.title}
|
{mangaData.data?.title}
|
||||||
</h1>
|
</h1>
|
||||||
<div className="flex gap-4 items-center">
|
<div className="flex gap-2 items-center flex-wrap">
|
||||||
<Badge
|
<Badge
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className="border border-border bg-card text-foreground max-h-6"
|
className="border border-border bg-card text-foreground whitespace-nowrap"
|
||||||
>
|
>
|
||||||
{mangaData.data?.status}
|
{mangaData.data?.status}
|
||||||
</Badge>
|
</Badge>
|
||||||
@ -294,7 +301,7 @@ const Manga = () => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{isAuthenticated && (
|
{isAuthenticated && (
|
||||||
<>
|
<div className="flex gap-2 ml-auto sm:ml-0">
|
||||||
<Button
|
<Button
|
||||||
size="icon"
|
size="icon"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@ -338,7 +345,7 @@ const Manga = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -395,6 +402,18 @@ const Manga = () => {
|
|||||||
dangerouslySetInnerHTML={{ __html: mangaData.data?.synopsis ?? "" }}
|
dangerouslySetInnerHTML={{ __html: mangaData.data?.synopsis ?? "" }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{mangaData.data?.genres.map((genre) => (
|
||||||
|
<Badge
|
||||||
|
key={genre}
|
||||||
|
variant="outline"
|
||||||
|
className="border-border text-foreground"
|
||||||
|
>
|
||||||
|
{genre}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="grid gap-4 sm:grid-cols-2">
|
<div className="grid gap-4 sm:grid-cols-2">
|
||||||
<div className="flex items-center gap-3 rounded-lg border border-border bg-card p-4">
|
<div className="flex items-center gap-3 rounded-lg border border-border bg-card p-4">
|
||||||
<Star className="h-5 w-5 fill-primary text-primary" />
|
<Star className="h-5 w-5 fill-primary text-primary" />
|
||||||
@ -446,18 +465,6 @@ const Manga = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-wrap gap-2">
|
|
||||||
{mangaData.data?.genres.map((genre) => (
|
|
||||||
<Badge
|
|
||||||
key={genre}
|
|
||||||
variant="outline"
|
|
||||||
className="border-border text-foreground"
|
|
||||||
>
|
|
||||||
{genre}
|
|
||||||
</Badge>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -489,83 +496,85 @@ const Manga = () => {
|
|||||||
asChild
|
asChild
|
||||||
className={`flex-1 ${provider.chaptersAvailable > 0 ? "cursor-pointer" : "cursor-default"}`}
|
className={`flex-1 ${provider.chaptersAvailable > 0 ? "cursor-pointer" : "cursor-default"}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center gap-4">
|
||||||
<div className="flex items-center gap-4">
|
<Database className="h-5 w-5 text-primary" />
|
||||||
<Database className="h-5 w-5 text-primary" />
|
<div className="text-left">
|
||||||
<div className="text-left">
|
<p className="font-semibold text-foreground">
|
||||||
<p className="font-semibold text-foreground">
|
{provider.providerName}
|
||||||
{provider.providerName}
|
</p>
|
||||||
</p>
|
<p className="text-sm text-muted-foreground whitespace-nowrap">
|
||||||
<p className="text-sm text-muted-foreground">
|
{provider.chaptersDownloaded} downloaded •{" "}
|
||||||
{provider.chaptersDownloaded} downloaded •{" "}
|
{provider.chaptersAvailable} available
|
||||||
{provider.chaptersAvailable} available
|
</p>
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
|
|
||||||
{provider.supportsChapterFetch && provider.active && (
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex gap-4 px-4">
|
<CollapsibleTrigger asChild>
|
||||||
{provider.chaptersAvailable > 0 && (
|
<div
|
||||||
<Button
|
className={
|
||||||
size="sm"
|
provider.chaptersAvailable > 0
|
||||||
variant="outline"
|
? "cursor-pointer"
|
||||||
disabled={isPending || fetchAllPending}
|
: "invisible"
|
||||||
onClick={(e) => {
|
}
|
||||||
e.stopPropagation();
|
|
||||||
fetchAllMutate({
|
|
||||||
mangaContentProviderId: provider.id ?? -1,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
className="gap-2"
|
|
||||||
>
|
|
||||||
{fetchAllPending ? (
|
|
||||||
<Spinner />
|
|
||||||
) : (
|
|
||||||
<Database className="h-4 w-4" />
|
|
||||||
)}
|
|
||||||
Fetch all from Provider
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
disabled={isPending || fetchAllPending}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
mutate({
|
|
||||||
mangaContentProviderId: provider.id ?? -1,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
className="gap-2"
|
|
||||||
>
|
>
|
||||||
{isPending ? (
|
<ChevronDown
|
||||||
<Spinner />
|
className={`h-5 w-5 text-muted-foreground transition-transform ${expandedProviderIds.includes(provider.id ?? -1)
|
||||||
) : (
|
? "rotate-180"
|
||||||
<Database className="h-4 w-4" />
|
: ""
|
||||||
)}
|
}`}
|
||||||
Fetch list from Provider
|
/>
|
||||||
</Button>
|
</div>
|
||||||
</div>
|
</CollapsibleTrigger>
|
||||||
)}
|
|
||||||
|
|
||||||
<CollapsibleTrigger asChild>
|
{provider.supportsChapterFetch && provider.active && (
|
||||||
<div
|
<DropdownMenu>
|
||||||
className={
|
<DropdownMenuTrigger asChild>
|
||||||
provider.chaptersAvailable > 0
|
<Button
|
||||||
? "cursor-pointer"
|
variant="ghost"
|
||||||
: "invisible"
|
size="icon"
|
||||||
}
|
disabled={isPending || fetchAllPending}
|
||||||
>
|
onClick={(e) => e.stopPropagation()}
|
||||||
<ChevronDown
|
>
|
||||||
className={`h-5 w-5 text-muted-foreground transition-transform ${expandedProviderIds.includes(provider.id ?? -1)
|
{isPending || fetchAllPending ? (
|
||||||
? "rotate-180"
|
<Spinner />
|
||||||
: ""
|
) : (
|
||||||
}`}
|
<MoreVertical className="h-4 w-4" />
|
||||||
/>
|
)}
|
||||||
</div>
|
</Button>
|
||||||
</CollapsibleTrigger>
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
{provider.chaptersAvailable > 0 && (
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={(e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
fetchAllMutate({
|
||||||
|
mangaContentProviderId: provider.id ?? -1,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
className="gap-2"
|
||||||
|
>
|
||||||
|
<Database className="h-4 w-4" />
|
||||||
|
Fetch all from Provider
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={(e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
mutate({
|
||||||
|
mangaContentProviderId: provider.id ?? -1,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
className="gap-2"
|
||||||
|
>
|
||||||
|
<Database className="h-4 w-4" />
|
||||||
|
Fetch list from Provider
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CollapsibleContent>
|
<CollapsibleContent>
|
||||||
<MangaChapter
|
<MangaChapter
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user