update torrents list view
This commit is contained in:
@@ -5,20 +5,15 @@ import {
|
|||||||
ArrowsClockwiseIcon,
|
ArrowsClockwiseIcon,
|
||||||
CaretDownIcon,
|
CaretDownIcon,
|
||||||
CaretUpIcon,
|
CaretUpIcon,
|
||||||
CheckCircleIcon,
|
|
||||||
DownloadSimpleIcon,
|
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
} from "@phosphor-icons/react";
|
} from "@phosphor-icons/react";
|
||||||
import { useDownloadTorrentMutation } from "../api/useDownloadTorrentMutation";
|
|
||||||
import { useDeleteItemMutation } from "../api/useDeleteItemMutation";
|
import { useDeleteItemMutation } from "../api/useDeleteItemMutation";
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import relativeTime from "dayjs/plugin/relativeTime";
|
import relativeTime from "dayjs/plugin/relativeTime";
|
||||||
import { humanFileSize } from "../utils/humanFileSize";
|
|
||||||
import { useDeleteTorrentMutation } from "../api/useDeleteTorrentMutation";
|
|
||||||
import { useRefreshItemMutation } from "../api/useRefreshItemMutation";
|
import { useRefreshItemMutation } from "../api/useRefreshItemMutation";
|
||||||
import { Loader } from "./Loader";
|
import { Loader } from "./Loader";
|
||||||
import { categories } from "../lib/categories";
|
import { Torrent } from "./Torrent";
|
||||||
|
|
||||||
dayjs.extend(relativeTime);
|
dayjs.extend(relativeTime);
|
||||||
|
|
||||||
@@ -34,9 +29,7 @@ export const Item = ({ item }: ItemProps) => {
|
|||||||
const { data: torrents } = useItemTorrentsQuery(item.id, open);
|
const { data: torrents } = useItemTorrentsQuery(item.id, open);
|
||||||
|
|
||||||
const deleteMutation = useDeleteItemMutation();
|
const deleteMutation = useDeleteItemMutation();
|
||||||
const downloadMutation = useDownloadTorrentMutation();
|
|
||||||
const refreshMutation = useRefreshItemMutation();
|
const refreshMutation = useRefreshItemMutation();
|
||||||
const deleteTorrentMutation = useDeleteTorrentMutation();
|
|
||||||
|
|
||||||
const Icon = open ? CaretUpIcon : CaretDownIcon;
|
const Icon = open ? CaretUpIcon : CaretDownIcon;
|
||||||
|
|
||||||
@@ -61,10 +54,6 @@ export const Item = ({ item }: ItemProps) => {
|
|||||||
}
|
}
|
||||||
}, [item]);
|
}, [item]);
|
||||||
|
|
||||||
const handleDownloadTorrent = (torrentId: number) => {
|
|
||||||
downloadMutation.mutate({ torrentId });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDelete = () => {
|
const handleDelete = () => {
|
||||||
if (!confirm("Do you want to delete this item?")) return;
|
if (!confirm("Do you want to delete this item?")) return;
|
||||||
|
|
||||||
@@ -95,22 +84,6 @@ export const Item = ({ item }: ItemProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteTorrent = (torrentId: number) => {
|
|
||||||
deleteTorrentMutation.mutate(
|
|
||||||
{
|
|
||||||
id: torrentId,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
onSuccess() {
|
|
||||||
queryClient.invalidateQueries({ queryKey: ["items"] });
|
|
||||||
queryClient.invalidateQueries({
|
|
||||||
queryKey: ["items", item.id, "torrents"],
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="border-t border-b border-neutral-900">
|
<div className="border-t border-b border-neutral-900">
|
||||||
<div
|
<div
|
||||||
@@ -163,49 +136,7 @@ export const Item = ({ item }: ItemProps) => {
|
|||||||
</div>
|
</div>
|
||||||
{filteredTorrents && filteredTorrents.length > 0 ? (
|
{filteredTorrents && filteredTorrents.length > 0 ? (
|
||||||
filteredTorrents.map((torrent) => (
|
filteredTorrents.map((torrent) => (
|
||||||
<div
|
<Torrent key={torrent.id} itemId={item.id} torrent={torrent} />
|
||||||
key={torrent.id}
|
|
||||||
className="flex justify-between items-center hover:bg-neutral-200 group"
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span>
|
|
||||||
<a
|
|
||||||
href={torrent.guid}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
{torrent.title}
|
|
||||||
</a>{" "}
|
|
||||||
[{formatCategory(torrent.category)}] [{torrent.indexer}]
|
|
||||||
</span>
|
|
||||||
{torrent.downloaded && (
|
|
||||||
<span title="Torrent files downloaded">
|
|
||||||
<CheckCircleIcon size={20} color="green" />
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
<button
|
|
||||||
className="hidden group-hover:inline text-[#b00420] cursor-pointer"
|
|
||||||
onClick={() => handleDeleteTorrent(torrent.id)}
|
|
||||||
>
|
|
||||||
<TrashIcon size={16} />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
<span>Seeds: {torrent.seeders}</span>
|
|
||||||
<span>Peers: {torrent.peers}</span>
|
|
||||||
<span>
|
|
||||||
PubDate: {dayjs(torrent.pubdate).format("DD.MM.YYYY")} at{" "}
|
|
||||||
{dayjs(torrent.pubdate).format("HH:mm")}
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
className="cursor-pointer"
|
|
||||||
onClick={() => handleDownloadTorrent(torrent.id)}
|
|
||||||
>
|
|
||||||
<DownloadSimpleIcon size={24} />
|
|
||||||
</button>
|
|
||||||
<span>{humanFileSize(torrent.size)}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<span>No torrents yet</span>
|
<span>No torrents yet</span>
|
||||||
@@ -215,7 +146,3 @@ export const Item = ({ item }: ItemProps) => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatCategory = (category: number): string => {
|
|
||||||
return categories[category as keyof typeof categories] || "";
|
|
||||||
};
|
|
||||||
|
|||||||
116
web/src/components/Torrent.tsx
Normal file
116
web/src/components/Torrent.tsx
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import type { ItemTorrent } from "../api/useItemTorrentsQuery";
|
||||||
|
import { categories } from "../lib/categories";
|
||||||
|
import {
|
||||||
|
ArrowSquareOutIcon,
|
||||||
|
CaretDownIcon,
|
||||||
|
CaretUpIcon,
|
||||||
|
CheckCircleIcon,
|
||||||
|
DownloadSimpleIcon,
|
||||||
|
TrashIcon,
|
||||||
|
} from "@phosphor-icons/react";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import { humanFileSize } from "../utils/humanFileSize";
|
||||||
|
import { useDeleteTorrentMutation } from "../api/useDeleteTorrentMutation";
|
||||||
|
import { useDownloadTorrentMutation } from "../api/useDownloadTorrentMutation";
|
||||||
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
export type TorrentProps = {
|
||||||
|
itemId: number;
|
||||||
|
torrent: ItemTorrent;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Torrent = ({ itemId, torrent }: TorrentProps) => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const downloadMutation = useDownloadTorrentMutation();
|
||||||
|
const deleteMutation = useDeleteTorrentMutation();
|
||||||
|
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const ChevronIcon = open ? CaretUpIcon : CaretDownIcon;
|
||||||
|
|
||||||
|
const handleDownload = () => {
|
||||||
|
downloadMutation.mutate({ torrentId: torrent.id });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = () => {
|
||||||
|
deleteMutation.mutate(
|
||||||
|
{
|
||||||
|
id: torrent.id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onSuccess() {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["items"] });
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ["items", itemId, "torrents"],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<div
|
||||||
|
className="flex items-center gap-2 hover:bg-neutral-200 cursor-pointer"
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
onClick={() => setOpen((prev) => !prev)}
|
||||||
|
>
|
||||||
|
<p>{torrent.title}</p>
|
||||||
|
{torrent.downloaded && (
|
||||||
|
<span title="Torrent files downloaded">
|
||||||
|
<CheckCircleIcon size={20} color="green" />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<div className="ml-auto flex items-center gap-1">
|
||||||
|
<span>[{formatCategory(torrent.category)}]</span>
|
||||||
|
<span>{humanFileSize(torrent.size)}</span>
|
||||||
|
<span>
|
||||||
|
<ChevronIcon size={20} />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{open && (
|
||||||
|
<div className="p-2 bg-neutral-100 rounded-md text-[15px]">
|
||||||
|
<p>Indexer: {torrent.indexer}</p>
|
||||||
|
<p>Seeders: {torrent.seeders}</p>
|
||||||
|
<p>Peers: {torrent.peers}</p>
|
||||||
|
<p>
|
||||||
|
Published: {dayjs(torrent.pubdate).format("DD.MM.YYYY")} at{" "}
|
||||||
|
{dayjs(torrent.pubdate).format("HH:mm")}
|
||||||
|
</p>
|
||||||
|
<div className="flex items-center gap-2 mt-2">
|
||||||
|
<a
|
||||||
|
href={torrent.guid}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="flex items-center gap-1"
|
||||||
|
>
|
||||||
|
<ArrowSquareOutIcon size={18} /> Open
|
||||||
|
</a>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="cursor-pointer flex items-center gap-1"
|
||||||
|
onClick={handleDownload}
|
||||||
|
>
|
||||||
|
<DownloadSimpleIcon size={18} /> Download
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="text-[#b00420] cursor-pointer flex items-center gap-1"
|
||||||
|
onClick={handleDelete}
|
||||||
|
>
|
||||||
|
<TrashIcon size={18} /> Delete torrent
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatCategory = (category: number): string => {
|
||||||
|
return categories[category as keyof typeof categories] || "";
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user