Skip to content

Commit

Permalink
feat: add new manga to spotlight actions
Browse files Browse the repository at this point in the history
  • Loading branch information
oae committed Oct 16, 2022
1 parent 1abe552 commit ae65666
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 103 deletions.
Binary file added public/new-manga.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
51 changes: 30 additions & 21 deletions src/components/addManga/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createStyles, Paper, Tooltip } from '@mantine/core';
import { useModals } from '@mantine/modals';
import { IconPlus } from '@tabler/icons';
import { useMemo } from 'react';
import { AddMangaForm } from './form';

const useStyles = createStyles((theme) => ({
Expand All @@ -22,33 +23,41 @@ const useStyles = createStyles((theme) => ({
},
}));

export const useAddMangaModal = () => {
const modals = useModals();

return useMemo(
() => (onAdd: () => void) => {
const id = modals.openModal({
overflow: 'inside',
trapFocus: true,
size: 'xl',
closeOnClickOutside: false,
closeOnEscape: true,
title: 'Add a new manga',
centered: true,
children: (
<AddMangaForm
onClose={() => {
modals.closeModal(id);
onAdd();
}}
/>
),
});
},
[modals],
);
};

export function AddManga({ onAdd }: { onAdd: () => void }) {
const { classes } = useStyles();
const modals = useModals();

const openCreateModal = () => {
const id = modals.openModal({
overflow: 'inside',
trapFocus: true,
size: 'xl',
closeOnClickOutside: false,
closeOnEscape: true,
title: 'Add a new manga',
centered: true,
children: (
<AddMangaForm
onClose={() => {
modals.closeModal(id);
onAdd();
}}
/>
),
});
};
const addMangaModal = useAddMangaModal();

return (
<Tooltip label="Add a new manga" position="bottom">
<Paper shadow="lg" p="md" radius="md" className={classes.card} onClick={openCreateModal}>
<Paper shadow="lg" p="md" radius="md" className={classes.card} onClick={() => addMangaModal(onAdd)}>
<IconPlus color="darkblue" opacity={0.5} size={96} />
</Paper>
</Tooltip>
Expand Down
1 change: 1 addition & 0 deletions src/components/addManga/steps/sourceStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export function SourceStep({ form }: { form: UseFormReturnType<FormType> }) {
return (
<Box>
<Select
data-autofocus
data={selectData || []}
label="Available sources"
placeholder="Select a source"
Expand Down
59 changes: 28 additions & 31 deletions src/components/chaptersTable.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { Box } from '@mantine/core';
import { Prisma } from '@prisma/client';
import { DataTable } from 'mantine-datatable';

import dayjs from 'dayjs';

import { showNotification } from '@mantine/notifications';
import prettyBytes from 'pretty-bytes';
import { useEffect, useState } from 'react';
import { showNotification } from '@mantine/notifications';
import { sanitizer } from '../utils/sanitize';

const mangaWithMetadataAndChaptersLibrary = Prisma.validator<Prisma.MangaArgs>()({
Expand All @@ -28,36 +27,34 @@ export function ChaptersTable({ manga }: { manga: MangaWithMetadataAndChaptersLi
}, [manga.chapters, page]);

return (
<Box sx={{ height: 700, marginTop: 30 }}>
<DataTable
withBorder
withColumnBorders
striped
highlightOnHover
records={records}
recordsPerPage={PAGE_SIZE}
page={page}
totalRecords={manga.chapters.length}
onPageChange={(p) => setPage(p)}
columns={[
{ accessor: 'Download Date', render: ({ createdAt }) => dayjs(createdAt).fromNow() },
{ accessor: 'Chapter', render: ({ index }) => `No #${index + 1}` },
<DataTable
withBorder
withColumnBorders
striped
highlightOnHover
records={records}
recordsPerPage={PAGE_SIZE}
page={page}
totalRecords={manga.chapters.length}
onPageChange={(p) => setPage(p)}
columns={[
{ accessor: 'Download Date', render: ({ createdAt }) => dayjs(createdAt).fromNow() },
{ accessor: 'Chapter', render: ({ index }) => `No #${index + 1}` },
{
accessor: 'File',
render: ({ fileName }) => `${manga.library.path}/${sanitizer(manga.title)}/${fileName}`,
},
{ accessor: 'Size', render: ({ size }) => prettyBytes(size) },
]}
rowContextMenu={{
items: () => [
{
accessor: 'File',
render: ({ fileName }) => `${manga.library.path}/${sanitizer(manga.title)}/${fileName}`,
key: 'download',
title: 'Download Again',
onClick: () => showNotification({ message: `Chapter queued for the download` }),
},
{ accessor: 'Size', render: ({ size }) => prettyBytes(size) },
]}
rowContextMenu={{
items: () => [
{
key: 'download',
title: 'Download Again',
onClick: () => showNotification({ message: `Chapter queued for the download` }),
},
],
}}
/>
</Box>
],
}}
/>
);
}
62 changes: 37 additions & 25 deletions src/components/headerSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,63 +4,75 @@ import { IconSearch } from '@tabler/icons';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
import { trpc } from '../utils/trpc';
import { useAddMangaModal } from './addManga';

const useStyles = createStyles((theme, { disabled }: { disabled: boolean }) => ({
const useStyles = createStyles((theme) => ({
root: {
height: 34,
width: 250,
paddingLeft: theme.spacing.sm,
paddingRight: 10,
borderRadius: theme.radius.sm,
color: theme.colors.gray[5],
backgroundColor: disabled ? theme.colors.gray[3] : theme.white,
cursor: disabled ? 'not-allowed' : 'pointer',
backgroundColor: theme.white,
cursor: 'pointer',
outline: '0 !important',
},
}));

export function SearchControl() {
const [actions, setActions] = useState<SpotlightAction[]>([]);
const addMangaModal = useAddMangaModal();

const router = useRouter();
const mangaQuery = trpc.manga.query.useQuery();
const isDisabled = !mangaQuery.data || mangaQuery.data.length === 0;
const { classes, cx } = useStyles({ disabled: isDisabled });
const { classes, cx } = useStyles();

useEffect(() => {
if (mangaQuery.data) {
setActions(
mangaQuery.data.map((m) => ({
title: m.title,
description: `${m.metadata.summary.split(' ').slice(0, 50).join(' ')}...`,
group: m.source,
icon: (
<Image
withPlaceholder
placeholder={<Image src="/cover-not-found.jpg" alt={m.title} width={60} height={100} />}
src={m.metadata.cover}
width={60}
height={100}
/>
),
const mangaActions: SpotlightAction[] = mangaQuery.data.map((m) => ({
title: m.title,
description: `${m.metadata.summary.split(' ').slice(0, 50).join(' ')}...`,
group: m.source,
icon: (
<Image
withPlaceholder
placeholder={<Image src="/cover-not-found.jpg" alt={m.title} width={60} height={100} />}
src={m.metadata.cover}
width={60}
height={100}
/>
),
closeOnTrigger: true,
onTrigger: () => router.push(`/manga/${m.id}`),
}));

setActions([
{
title: 'Add Manga',
group: ' ',
description: 'You can add new manga from several sources',
icon: <Image withPlaceholder src="/new-manga.png" width={60} height={100} />,
closeOnTrigger: true,
onTrigger: () => router.push(`/manga/${m.id}`),
})),
);
onTrigger: () => addMangaModal(() => mangaQuery.refetch()),
},
...mangaActions,
]);
}
}, [mangaQuery.data, router]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [addMangaModal, mangaQuery.data, router]);

return (
<SpotlightProvider
actions={actions}
searchIcon={<IconSearch size={18} />}
highlightQuery
limit={5}
disabled={isDisabled}
searchPlaceholder="Search..."
shortcut="ctrl + p"
nothingFoundMessage="Nothing found..."
>
<UnstyledButton className={cx(classes.root)} onClick={() => openSpotlight()} disabled={isDisabled}>
<UnstyledButton className={cx(classes.root)} onClick={() => openSpotlight()}>
<Grid gutter={5}>
<Grid.Col span="content" style={{ display: 'flex', alignItems: 'center' }}>
<IconSearch size={14} stroke={1.5} />
Expand Down
42 changes: 23 additions & 19 deletions src/components/mangaCard.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ActionIcon, Badge, Code, createStyles, Paper, Skeleton, Text, Title } from '@mantine/core';
import { openConfirmModal } from '@mantine/modals';
import { IconX } from '@tabler/icons';
import { useMemo } from 'react';

const useStyles = createStyles((theme, _params, getRef) => ({
skeletonCard: {
Expand Down Expand Up @@ -58,24 +59,27 @@ interface ArticleCardImageProps {
onClick: () => void;
}

const createRemoveModal = (title: string, onRemove: () => void) => {
const openRemoveModal = () =>
openConfirmModal({
title: `Remove ${title}?`,
centered: true,
children: (
<Text size="sm">
Are you sure you want to remove
<Code className="text-base font-bold" color="red">
{title}
</Code>
? This action is destructive and all downloaded files will be removed
</Text>
),
labels: { confirm: 'Remove', cancel: 'Cancel' },
confirmProps: { color: 'red' },
onConfirm: onRemove,
});
const useRemoveModal = (title: string, onRemove: () => void) => {
const openRemoveModal = useMemo(
() => () =>
openConfirmModal({
title: `Remove ${title}?`,
centered: true,
children: (
<Text size="sm">
Are you sure you want to remove
<Code className="text-base font-bold" color="red">
{title}
</Code>
? This action is destructive and all downloaded files will be removed
</Text>
),
labels: { confirm: 'Remove', cancel: 'Cancel' },
confirmProps: { color: 'red' },
onConfirm: onRemove,
}),
[onRemove, title],
);

return openRemoveModal;
};
Expand All @@ -88,7 +92,7 @@ export function SkeletonMangaCard() {

export function MangaCard({ cover, title, badge, onRemove, onClick }: ArticleCardImageProps) {
const { classes } = useStyles();
const removeModal = createRemoveModal(title, onRemove);
const removeModal = useRemoveModal(title, onRemove);

return (
<Paper
Expand Down
14 changes: 7 additions & 7 deletions src/pages/manga/[id].tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Grid, LoadingOverlay } from '@mantine/core';
import { Box, LoadingOverlay } from '@mantine/core';
import { useRouter } from 'next/router';
import { ChaptersTable } from '../../components/chaptersTable';
import { MangaDetail } from '../../components/mangaDetail';
Expand Down Expand Up @@ -28,13 +28,13 @@ export default function LibraryPage() {
}

return (
<Grid gutter={5}>
<Grid.Col span={12}>
<Box sx={{ display: 'flex', flexDirection: 'column', height: 'calc(100vh - 88px)' }}>
<Box sx={{ flexBasis: 'fit-content' }}>
<MangaDetail manga={mangaQuery.data} />
</Grid.Col>
<Grid.Col span={12}>
</Box>
<Box sx={{ marginTop: 20, overflow: 'hidden', flex: 1 }}>
<ChaptersTable manga={mangaQuery.data} />
</Grid.Col>
</Grid>
</Box>
</Box>
);
}
1 change: 1 addition & 0 deletions src/server/utils/mangal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ export const getChapterFromLocal = async (chapterFile: string) => {
return {
index: getChapterIndexFromFile(chapterFile)!,
size: stat.size,
createdAt: stat.birthtime,
fileName: path.basename(chapterFile),
};
};
Expand Down

0 comments on commit ae65666

Please sign in to comment.