Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
import { ChapterTablePagination } from './ChapterTablePagination';
import {
chapterDownloadStatusesState,
chapterFilterGroupNamesState,
chapterListState,
seriesState,
sortedFilteredChapterListState,
Expand All @@ -34,35 +35,48 @@ import { Chapter, Languages, Series } from '@tiyo/common';
import routes from '@/common/constants/routes.json';
import {
DropdownMenu,
DropdownMenuGroup,
DropdownMenuSub,
DropdownMenuPortal,
DropdownMenuSubTrigger,
DropdownMenuSubContent,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
DropdownMenuItem
} from '@houdoku/ui/components/DropdownMenu';
import { Button } from '@houdoku/ui/components/Button';
import { ScrollArea } from '@houdoku/ui/components/ScrollArea';
import {
ArrowDown,
ArrowUp,
ChevronsUpDown,
Download,
Eye,
EyeOff,
EllipsisVertical,
Filter,
FileCheck,
LanguagesIcon,
Play,
Settings2,
X,
} from 'lucide-react';
import { ChapterTableLanguageFilter } from './ChapterTableLanguageFilter';
import { ChapterTableGroupFilter } from './ChapterTableGroupFilter';
import { markChapters } from '@/renderer/features/library/utils';
import { downloaderClient } from '@/renderer/services/downloader';
import ipcChannels from '@/common/constants/ipcChannels.json';
import { Badge } from '@houdoku/ui/components/Badge';
import { Checkbox } from '@houdoku/ui/components/Checkbox';
import { Separator } from '@houdoku/ui/components/Separator';
import { TableColumnSortOrder } from '@/common/models/types';
import { FS_METADATA } from '@/common/temp_fs_metadata';
import { ContextMenu, ContextMenuTrigger } from '@houdoku/ui/components/ContextMenu';
import { ChapterTableContextMenu } from './ChapterTableContextMenu';
import { ChapterTableReadFilter } from './ChapterTableReadFilter';
import { useEffect } from 'react';
import { currentTaskState } from '@/renderer/state/downloaderStates';

Expand All @@ -83,7 +97,8 @@ export function ChapterTable(props: ChapterTableProps) {
const setSeries = useSetRecoilState(seriesState);
const [chapterList, setChapterList] = useRecoilState(chapterListState);
const sortedFilteredChapterList = useRecoilValue(sortedFilteredChapterListState);
const chapterLanguages = useRecoilValue(chapterLanguagesState);
const [filterGroupNames, setFilterGroupNames] = useRecoilState(chapterFilterGroupNamesState);
const [chapterLanguages, setChapterLanguages] = useRecoilState(chapterLanguagesState);
const [chapterListVolOrder, setChapterListVolOrder] = useRecoilState(chapterListVolOrderState);
const [chapterListChOrder, setChapterListChOrder] = useRecoilState(chapterListChOrderState);
const [chapterDownloadStatuses, setChapterDownloadStatuses] = useRecoilState(
Expand All @@ -99,11 +114,8 @@ export function ChapterTable(props: ChapterTableProps) {
<div className="flex justify-start">
<span className="w-5 h-5">
<Checkbox
checked={
table.getIsAllRowsSelected() ||
(table.getIsSomePageRowsSelected() && 'indeterminate')
}
onCheckedChange={(value) => table.toggleAllRowsSelected(!!value)}
checked={table.getIsAllPageRowsSelected()}
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
/>
</span>
</div>
Expand Down Expand Up @@ -269,6 +281,10 @@ export function ChapterTable(props: ChapterTableProps) {
return table.getSelectedRowModel().rows.map((row) => row.original) as Chapter[];
};

const getAllChapters = (): Chapter[] => {
return table.getCoreRowModel().rows.map((row) => row.original) as Chapter[];
};

const getNextUnreadChapter = () => {
return sortedFilteredChapterList
.slice()
Expand Down Expand Up @@ -300,6 +316,17 @@ export function ChapterTable(props: ChapterTableProps) {
);
};

const setAllRead = (read: boolean) => {
markChapters(
getAllChapters(),
props.series,
read,
setChapterList,
setSeries,
chapterLanguages,
);
};

const downloadSelected = () => {
downloaderClient.add(
getSelectedChapters().map((chapter) => ({
Expand All @@ -311,79 +338,218 @@ export function ChapterTable(props: ChapterTableProps) {
downloaderClient.start();
};

const downloadAll = () => {
downloaderClient.add(
getAllChapters().map((chapter) => ({
chapter,
series: props.series,
downloadsDir: customDownloadsDir || defaultDownloadsDir,
})),
);
downloaderClient.start();
};

return (
<div className="space-y-2 pb-4">
<div className="flex items-center justify-between">
{table.getIsSomeRowsSelected() || table.getIsAllRowsSelected() ? (
<div className="flex space-x-2 items-end">
<Button className="ml-auto" onClick={() => setSelectedRead(true)}>
<Eye className="w-4 h-4" />
Mark selected read
</Button>
<Button className="ml-auto" onClick={() => setSelectedRead(false)}>
<EyeOff className="w-4 h-4" />
Mark selected unread
</Button>
{/* TODO add confirmation prompt */}
<Button className="ml-auto" onClick={() => downloadSelected()}>
<Download className="w-4 h-4" />
Download selected
</Button>
</div>
) : (
<>
<div className="flex space-x-2">
<ChapterTableLanguageFilter />
<ChapterTableGroupFilter
uniqueGroupNames={Array.from(
new Set(chapterList.map((chapter) => chapter.groupName)),
<div className="flex space-x-2">
<DropdownMenu>
<Button variant="outline">
<DropdownMenuTrigger asChild>
<div className="flex flex-row items-center">
<Filter className="w-4 h-4 mr-2" />
Filters
</div>
</DropdownMenuTrigger>
{(chapterLanguages.length > 0 || filterGroupNames.length > 0) && (
<Separator orientation="vertical" className="mx-2 h-4" />
)}
<div className="flex flex-row flex-wrap gap-1">
{chapterLanguages.length > 0 && (
<Badge
variant="default"
className="rounded-sm px-2 py-0.5 font-normal flex items-center"
onClick={() => {
setChapterLanguages([]);
}}
>
<X className="w-3 h-3 mr-1 text-red-500" />
Languages
</Badge>
)}
{filterGroupNames.length > 0 && (
<Badge
variant="default"
className="rounded-sm px-2 py-0.5 font-normal flex items-center"
onClick={() => {
setFilterGroupNames([]);
}}
>
<X className="w-3 h-3 mr-1 text-red-500" />
Groups
</Badge>
)}
/>
</div>
<div className="flex space-x-2">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto">
<Settings2 className="w-4 h-4" />
View
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Columns</DropdownMenuLabel>
<DropdownMenuSeparator />
{table
.getAllColumns()
.filter((column) => column.getCanHide())
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) => column.toggleVisibility(!!value)}
onSelect={(event) => event.preventDefault()}
</div>
</Button>
<DropdownMenuContent className="w-51 flex flex-col" align="start" alignOffset={-17} sideOffset={12}>
<DropdownMenuItem asChild>
<ChapterTableLanguageFilter />
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem asChild className="w-full">
<ChapterTableGroupFilter
uniqueGroupNames={Array.from(
new Set(chapterList.map((chapter) => chapter.groupName)),
)}
/>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<ChapterTableReadFilter />
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">
<EllipsisVertical className="w-4 h-4" />
Options</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-51" align="start">
<DropdownMenuGroup>
<DropdownMenuLabel>Download Chapters</DropdownMenuLabel>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<div
className="flex items-center w-full"
>
<Download className="w-4 h-4 mr-2" />
Download
</div>
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuItem>
<div
onClick={() => downloadAll()}
className="flex items-center w-full"
>
{column.id}
</DropdownMenuCheckboxItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
{getNextUnreadChapter() && (
<Link to={`${routes.READER}/${props.series.id}/${getNextUnreadChapter()?.id}`}>
<Button variant="outline">
<Play className="w-4 h-4" />
Continue
</Button>
</Link>
)}
</div>
</>
)}
<span>All Chapters</span>
</div>
</DropdownMenuItem>
<DropdownMenuItem>
<div
onClick={() => downloadSelected()}
className="flex items-center w-full"
>
<span>Selected Chapters</span>
</div>
</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuLabel>Mark Chapters</DropdownMenuLabel>
<DropdownMenuSub>
<DropdownMenuSubTrigger>All Chapters</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuItem>
<div
onClick={() => setAllRead(true)}
className="flex items-center w-full"
>
<Eye className="w-4 h-4 mr-2" />
<span>Read</span>
</div>
</DropdownMenuItem>
<DropdownMenuItem>
<div
onClick={() => setAllRead(false)}
className="flex items-center w-full"
>
<EyeOff className="w-4 h-4 mr-2" />
<span>Unread</span>
</div>
</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>

<DropdownMenuSub>
<DropdownMenuSubTrigger>Selected Chapters</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuItem>
<div
onClick={() => setSelectedRead(true)}
className="flex items-center w-full"
>
<Eye className="w-4 h-4 mr-2" />
<span>Read</span>
</div>
</DropdownMenuItem>
<DropdownMenuItem>
<div
onClick={() => setSelectedRead(false)}
className="flex items-center w-full"
>
<EyeOff className="w-4 h-4 mr-2" />
<span>Unread</span>
</div>
</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>

<div className="flex space-x-2">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto">
<Settings2 className="w-4 h-4" />
View
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Columns</DropdownMenuLabel>
<DropdownMenuSeparator />
{table
.getAllColumns()
.filter((column) => column.getCanHide())
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) => column.toggleVisibility(!!value)}
onSelect={(event) => event.preventDefault()}
>
{column.id}
</DropdownMenuCheckboxItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
{getNextUnreadChapter() && (
<Link to={`${routes.READER}/${props.series.id}/${getNextUnreadChapter()?.id}`}>
<Button variant="outline">
<Play className="w-4 h-4" />
Continue
</Button>
</Link>
)}
</div>
</div>
<div className="rounded-md border">
<div className="rounded-md border portrait:max-h-[65vh] landscape:max-h-[50vh] overflow-y-auto">
<Table>
<TableHeader>
<TableHeader className="sticky top-0 bg-secondary">
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
Expand Down
Loading