Skip to content
This repository was archived by the owner on Feb 3, 2025. It is now read-only.

Commit 6100e6c

Browse files
committed
feat: add ability to fix out-of-sync chapters
1 parent de19737 commit 6100e6c

14 files changed

+436
-64
lines changed

src/components/chaptersTable.tsx

+4-22
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import { DataTable } from 'mantine-datatable';
33

44
import dayjs from 'dayjs';
55

6-
import { ActionIcon, Center, Tooltip } from '@mantine/core';
7-
import { IconAlertTriangle, IconCheck, IconRefresh } from '@tabler/icons-react';
6+
import { Center, Tooltip } from '@mantine/core';
7+
import { IconAlertTriangle, IconCheck } from '@tabler/icons-react';
88
import prettyBytes from 'pretty-bytes';
99
import { useEffect, useState } from 'react';
1010

@@ -18,13 +18,7 @@ export type MangaWithMetadataAndChaptersAndOutOfSyncChaptersAndLibrary = Prisma.
1818

1919
const PAGE_SIZE = 100;
2020

21-
export function ChaptersTable({
22-
manga,
23-
onCheckOutOfSync,
24-
}: {
25-
manga: MangaWithMetadataAndChaptersAndOutOfSyncChaptersAndLibrary;
26-
onCheckOutOfSync: () => void;
27-
}) {
21+
export function ChaptersTable({ manga }: { manga: MangaWithMetadataAndChaptersAndOutOfSyncChaptersAndLibrary }) {
2822
const [page, setPage] = useState(1);
2923
const [records, setRecords] = useState(manga.chapters.slice(0, PAGE_SIZE));
3024

@@ -64,21 +58,9 @@ export function ChaptersTable({
6458
title: (
6559
<Center>
6660
<span>Status</span>
67-
<Tooltip withArrow label="Check for out of sync chapters">
68-
<ActionIcon
69-
variant="transparent"
70-
color="blue"
71-
onClick={(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
72-
e.stopPropagation();
73-
onCheckOutOfSync();
74-
}}
75-
>
76-
<IconRefresh size={16} />
77-
</ActionIcon>
78-
</Tooltip>
7961
</Center>
8062
),
81-
width: 90,
63+
width: 70,
8264
render: ({ id }) =>
8365
manga.outOfSyncChapters.find((c) => c.id === id) ? (
8466
<Tooltip withArrow label="This chapter is out of sync with the source.">
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { ActionIcon, Code, Text, Tooltip } from '@mantine/core';
2+
import { showNotification } from '@mantine/notifications';
3+
import { IconCheck, IconRefreshAlert, IconX } from '@tabler/icons-react';
4+
import { useRouter } from 'next/router';
5+
import { trpc } from '../utils/trpc';
6+
import { useOutOfSyncChapterModal } from './outOfSyncChapterModal';
7+
8+
export function CheckOutOfSyncChaptersButton() {
9+
const router = useRouter();
10+
const { id } = router.query;
11+
12+
const checkOutOfSyncChaptersMutation = trpc.manga.checkOutOfSyncChapters.useMutation();
13+
14+
const check = async () => {
15+
try {
16+
await checkOutOfSyncChaptersMutation.mutateAsync({ id: id ? parseInt(id as string, 10) : null });
17+
showNotification({
18+
icon: <IconCheck size={18} />,
19+
color: 'teal',
20+
autoClose: true,
21+
title: 'Out of Sync Chapters',
22+
message: <Text>Started checking for out of sync chapters in background</Text>,
23+
});
24+
} catch (err) {
25+
showNotification({
26+
icon: <IconX size={18} />,
27+
color: 'red',
28+
autoClose: true,
29+
title: 'Out of Sync Chapters',
30+
message: (
31+
<Text>
32+
<Code color="red">{`${err}`}</Code>
33+
</Text>
34+
),
35+
});
36+
}
37+
};
38+
39+
const outOfSyncChapterModal = useOutOfSyncChapterModal(
40+
'Check for out of sync chapters?',
41+
'This will look for out of sync chapters and mark them',
42+
check,
43+
);
44+
45+
return (
46+
<Tooltip withArrow position="bottom-end" label="Check for the out of sync chapters">
47+
<ActionIcon
48+
onClick={outOfSyncChapterModal}
49+
variant="transparent"
50+
sx={(theme) => ({
51+
backgroundColor: theme.white,
52+
color: theme.black,
53+
'&:hover': {
54+
backgroundColor: theme.colors.gray[0],
55+
},
56+
})}
57+
size="lg"
58+
>
59+
<IconRefreshAlert size={18} strokeWidth={2} />
60+
</ActionIcon>
61+
</Tooltip>
62+
);
63+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { ActionIcon, Code, Text, Tooltip } from '@mantine/core';
2+
import { showNotification } from '@mantine/notifications';
3+
import { IconCheck, IconHammer, IconX } from '@tabler/icons-react';
4+
import { useRouter } from 'next/router';
5+
import { trpc } from '../utils/trpc';
6+
import { useOutOfSyncChapterModal } from './outOfSyncChapterModal';
7+
8+
export function FixOutOfSyncChaptersButton() {
9+
const router = useRouter();
10+
const { id } = router.query;
11+
12+
const fixOutOfSyncChaptersMutation = trpc.manga.fixOutOfSyncChapters.useMutation();
13+
14+
const fix = async () => {
15+
try {
16+
await fixOutOfSyncChaptersMutation.mutateAsync({ id: id ? parseInt(id as string, 10) : null });
17+
showNotification({
18+
icon: <IconCheck size={18} />,
19+
color: 'teal',
20+
autoClose: true,
21+
title: 'Out of Sync Chapters',
22+
message: <Text>Started fixing out of sync chapters in background</Text>,
23+
});
24+
} catch (err) {
25+
showNotification({
26+
icon: <IconX size={18} />,
27+
color: 'red',
28+
autoClose: true,
29+
title: 'Out of Sync Chapters',
30+
message: (
31+
<Text>
32+
<Code color="red">{`${err}`}</Code>
33+
</Text>
34+
),
35+
});
36+
}
37+
};
38+
39+
const outOfSyncChapterModal = useOutOfSyncChapterModal(
40+
'Fix out of sync chapters?',
41+
'This will remove all chapters marked as out of sync and download the new ones',
42+
fix,
43+
);
44+
45+
return (
46+
<Tooltip withArrow position="bottom-end" label="Fix out of sync chapters">
47+
<ActionIcon
48+
onClick={outOfSyncChapterModal}
49+
variant="transparent"
50+
sx={(theme) => ({
51+
backgroundColor: theme.white,
52+
color: theme.black,
53+
'&:hover': {
54+
backgroundColor: theme.colors.gray[0],
55+
},
56+
})}
57+
size="lg"
58+
>
59+
<IconHammer size={18} strokeWidth={2} />
60+
</ActionIcon>
61+
</Tooltip>
62+
);
63+
}

src/components/header.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { Box, Container, createStyles, Group, Header, Title, UnstyledButton } from '@mantine/core';
22
import Image from 'next/image';
33
import Link from 'next/link';
4+
import { CheckOutOfSyncChaptersButton } from './checkOutOfSyncChaptersButton';
5+
import { FixOutOfSyncChaptersButton } from './fixOutOfSyncChaptersButton';
46
import { SearchControl } from './headerSearch';
57
import { SettingsMenuButton } from './settingsMenu';
68

@@ -47,6 +49,8 @@ export function KaizokuHeader() {
4749

4850
<Group position="center" spacing={5}>
4951
<SearchControl />
52+
<FixOutOfSyncChaptersButton />
53+
<CheckOutOfSyncChaptersButton />
5054
<SettingsMenuButton />
5155
</Group>
5256
</Box>

src/components/headerSearch.tsx

+1-2
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export function SearchControl() {
4242
useEffect(() => {
4343
if (mangaQuery.data) {
4444
const mangaActions: SpotlightAction[] = mangaQuery.data.map((m) => ({
45-
title: m.title,
45+
title: `${m.title} ${m.outOfSyncChapters.length > 0 ? ' (Out of Sync)' : ''}`,
4646
description: `${m.metadata.summary.split(' ').slice(0, 50).join(' ')}...`,
4747
group: m.source,
4848
icon: (
@@ -73,7 +73,6 @@ export function SearchControl() {
7373
}
7474
// eslint-disable-next-line react-hooks/exhaustive-deps
7575
}, [addMangaModal, mangaQuery.data, router]);
76-
7776
return (
7877
<SpotlightProvider
7978
actions={actions}

src/components/navbar.tsx

+27-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
IconCircleCheck,
2222
IconClock,
2323
IconFileReport,
24+
IconRefreshAlert,
2425
} from '@tabler/icons-react';
2526
import { inferProcedureOutput } from '@trpc/server';
2627
import dayjs from 'dayjs';
@@ -165,18 +166,30 @@ function ActivityItem({
165166
icon,
166167
count,
167168
href,
169+
onClick,
168170
color,
169171
}: {
170172
name: string;
171173
icon: ReactNode;
172174
count: number;
173-
href: string;
175+
href?: string;
176+
onClick?: () => void;
174177
color: MantineColor;
175178
}) {
176179
const { classes } = useStyles();
177180

178181
return (
179-
<NextLink target="_blank" href={href} className={classes.activity}>
182+
<NextLink
183+
target="_blank"
184+
href={href || '#'}
185+
className={classes.activity}
186+
onClick={(e: React.MouseEvent<HTMLAnchorElement>) => {
187+
if (onClick) {
188+
e.preventDefault();
189+
onClick();
190+
}
191+
}}
192+
>
180193
<Grid gutter={5}>
181194
<Grid.Col span="content" style={{ display: 'flex', alignItems: 'center' }}>
182195
{icon}
@@ -195,6 +208,11 @@ function ActivityItem({
195208
);
196209
}
197210

211+
ActivityItem.defaultProps = {
212+
href: undefined,
213+
onClick: undefined,
214+
};
215+
198216
type ActivityType = inferProcedureOutput<AppRouter['manga']['activity']>;
199217

200218
function Activity({ data }: { data: ActivityType }) {
@@ -235,6 +253,13 @@ function Activity({ data }: { data: ActivityType }) {
235253
count={data.completed}
236254
href="/bull/queues/queue/downloadQueue?status=completed"
237255
/>
256+
<ActivityItem
257+
icon={<IconRefreshAlert size={20} strokeWidth={1.5} />}
258+
name="Out of Sync"
259+
color="violet"
260+
count={data.outOfSync}
261+
onClick={() => {}}
262+
/>
238263
</>
239264
);
240265
}
+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { Box, Button, Text } from '@mantine/core';
2+
import { useModals } from '@mantine/modals';
3+
4+
function OutOfSyncChapterModalContent({
5+
onOk,
6+
onClose,
7+
body,
8+
}: {
9+
onOk: () => void;
10+
onClose: () => void;
11+
body: string;
12+
}) {
13+
return (
14+
<>
15+
<Text mb={4} size="sm">
16+
{body}
17+
</Text>
18+
<Box
19+
sx={(theme) => ({
20+
display: 'flex',
21+
gap: theme.spacing.xs,
22+
justifyContent: 'end',
23+
marginTop: theme.spacing.md,
24+
})}
25+
>
26+
<Button variant="default" color="dark" onClick={onClose}>
27+
Cancel
28+
</Button>
29+
<Button
30+
variant="filled"
31+
color="teal"
32+
onClick={() => {
33+
onOk();
34+
onClose();
35+
}}
36+
>
37+
Ok
38+
</Button>
39+
</Box>
40+
</>
41+
);
42+
}
43+
44+
export const useOutOfSyncChapterModal = (title: string, body: string, onOk: () => void) => {
45+
const modals = useModals();
46+
47+
const openRemoveModal = () => {
48+
const id = modals.openModal({
49+
title,
50+
centered: true,
51+
children: <OutOfSyncChapterModalContent onOk={onOk} onClose={() => modals.closeModal(id)} body={body} />,
52+
});
53+
};
54+
55+
return openRemoveModal;
56+
};

src/pages/manga/[id].tsx

+1-10
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ export default function MangaPage() {
88
const router = useRouter();
99
const { id } = router.query;
1010

11-
const checkOutOfSyncChaptersMutation = trpc.manga.checkOutOfSyncChapters.useMutation();
1211
const mangaQuery = trpc.manga.get.useQuery(
1312
{
1413
id: parseInt(id as string, 10),
@@ -34,15 +33,7 @@ export default function MangaPage() {
3433
<MangaDetail manga={mangaQuery.data} />
3534
</Box>
3635
<Box sx={{ marginTop: 20, overflow: 'hidden', flex: 1 }}>
37-
<ChaptersTable
38-
manga={mangaQuery.data}
39-
onCheckOutOfSync={async () => {
40-
await checkOutOfSyncChaptersMutation.mutateAsync({
41-
id: mangaQuery.data.id,
42-
});
43-
mangaQuery.refetch();
44-
}}
45-
/>
36+
<ChaptersTable manga={mangaQuery.data} />
4637
</Box>
4738
</Box>
4839
);

src/server/index.ts

+4
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import express, { Request, Response } from 'express';
55
import next from 'next';
66
import { logger } from '../utils/logging';
77
import { checkChaptersQueue, scheduleAll } from './queue/checkChapters';
8+
import { checkOutOfSyncChaptersQueue } from './queue/checkOutOfSyncChapters';
89
import { downloadQueue } from './queue/download';
10+
import { fixOutOfSyncChaptersQueue } from './queue/fixOutOfSyncChapters';
911
import { integrationQueue } from './queue/integration';
1012
import { notificationQueue } from './queue/notify';
1113
import { updateMetadataQueue } from './queue/updateMetadata';
@@ -24,6 +26,8 @@ createBullBoard({
2426
new BullAdapter(notificationQueue),
2527
new BullAdapter(updateMetadataQueue),
2628
new BullAdapter(integrationQueue),
29+
new BullAdapter(checkOutOfSyncChaptersQueue),
30+
new BullAdapter(fixOutOfSyncChaptersQueue),
2731
],
2832
serverAdapter,
2933
});

0 commit comments

Comments
 (0)