309 lines
11 KiB
TypeScript
309 lines
11 KiB
TypeScript
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
DialogTrigger,
|
|
} from "@/components/ui/dialog";
|
|
import { Button } from "@/components/ui/button";
|
|
import React, { useState } from "react";
|
|
import useSWRMutation from "swr/mutation";
|
|
import { POST } from "@/lib/api";
|
|
import { useDebounce } from "use-debounce";
|
|
import { useToast } from "@/hooks/use-toast";
|
|
import { AutoDownloadButton } from "@/components/auto-download-button";
|
|
import { useTelegramChat } from "@/hooks/use-telegram-chat";
|
|
import { useTelegramAccount } from "@/hooks/use-telegram-account";
|
|
import {
|
|
Accordion,
|
|
AccordionContent,
|
|
AccordionItem,
|
|
AccordionTrigger,
|
|
} from "./ui/accordion";
|
|
import { Label } from "@/components/ui/label";
|
|
import { Input } from "@/components/ui/input";
|
|
import { type AutoDownloadRule, type FileType } from "@/lib/types";
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@/components/ui/select";
|
|
import { Badge } from "./ui/badge";
|
|
import { X } from "lucide-react";
|
|
|
|
export default function AutoDownloadDialog() {
|
|
const { accountId } = useTelegramAccount();
|
|
const { isLoading, chat, reload } = useTelegramChat();
|
|
const { toast } = useToast();
|
|
const [open, setOpen] = useState(false);
|
|
const [rule, setRule] = useState<AutoDownloadRule>({
|
|
query: "",
|
|
fileTypes: [],
|
|
});
|
|
const { trigger: triggerAuto, isMutating: isAutoMutating } = useSWRMutation(
|
|
!accountId || !chat
|
|
? undefined
|
|
: `/file/auto-download?telegramId=${accountId}&chatId=${chat?.id}`,
|
|
(key, { arg }: { arg: { rule: AutoDownloadRule } }) => {
|
|
return POST(key, arg);
|
|
},
|
|
{
|
|
onSuccess: () => {
|
|
toast({
|
|
title: chat?.autoEnabled
|
|
? "Auto download disabled for this chat!"
|
|
: "Auto download enabled for this chat!",
|
|
});
|
|
void reload();
|
|
setTimeout(() => {
|
|
setOpen(false);
|
|
}, 1000);
|
|
},
|
|
},
|
|
);
|
|
|
|
const [debounceIsAutoMutating] = useDebounce(isAutoMutating, 500, {
|
|
leading: true,
|
|
});
|
|
|
|
if (isLoading) {
|
|
return <div className="h-8 w-32 animate-pulse bg-gray-200"></div>;
|
|
}
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={setOpen}>
|
|
<DialogTrigger
|
|
asChild
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
setOpen(!open);
|
|
}}
|
|
>
|
|
{chat && <AutoDownloadButton autoEnabled={chat.autoEnabled} />}
|
|
</DialogTrigger>
|
|
<DialogContent
|
|
aria-describedby={undefined}
|
|
onPointerDownOutside={() => setOpen(false)}
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
<DialogHeader>
|
|
<DialogTitle>
|
|
{chat?.autoEnabled
|
|
? "Disable Auto Download"
|
|
: "Enable auto download for this chat?"}
|
|
</DialogTitle>
|
|
</DialogHeader>
|
|
<DialogDescription></DialogDescription>
|
|
{chat?.autoEnabled ? (
|
|
<div className="space-y-4">
|
|
{chat.autoRule && (
|
|
<div className="space-y-4">
|
|
<div className="flex items-center">
|
|
<Label className="text-sm font-semibold text-gray-900">
|
|
Current Rule
|
|
</Label>
|
|
<Badge
|
|
variant="outline"
|
|
className="ml-2 border-blue-200 bg-blue-50 px-2 py-0.5 text-xs text-blue-700"
|
|
>
|
|
Active
|
|
</Badge>
|
|
</div>
|
|
|
|
<div className="space-y-3">
|
|
{/* Filter Keyword Section */}
|
|
<div className="rounded-lg bg-gray-50 p-3">
|
|
<div className="flex flex-col space-y-1">
|
|
<span className="text-xs font-medium text-gray-500">
|
|
Filter Keyword
|
|
</span>
|
|
<span className="text-sm text-gray-900">
|
|
{chat.autoRule.query || "No keyword specified"}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="rounded-lg bg-gray-50 p-3">
|
|
<span className="text-xs font-medium text-gray-500">
|
|
File Types
|
|
</span>
|
|
<div className="mt-2 flex flex-wrap gap-2">
|
|
{chat.autoRule.fileTypes.length > 0 ? (
|
|
chat.autoRule.fileTypes.map((type) => (
|
|
<Badge
|
|
key={type}
|
|
variant="secondary"
|
|
className="flex items-center gap-1 border-gray-200 bg-white px-3 py-1 capitalize text-gray-700"
|
|
>
|
|
{type}
|
|
</Badge>
|
|
))
|
|
) : (
|
|
<span className="text-sm text-gray-500">
|
|
No file types selected
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
<div className="space-y-4 rounded-md border border-gray-200 bg-gray-50 p-4">
|
|
<div className="flex items-start">
|
|
<span className="mr-3 mt-1.5 h-3 w-2 flex-shrink-0 rounded-full bg-yellow-400"></span>
|
|
<p className="text-sm leading-6 text-gray-700">
|
|
This will disable auto download for this chat. You can always
|
|
enable it later.
|
|
</p>
|
|
</div>
|
|
<div className="flex items-start">
|
|
<span className="mr-3 mt-1.5 h-3 w-2 flex-shrink-0 rounded-full bg-yellow-400"></span>
|
|
<p className="text-sm leading-6 text-gray-700">
|
|
Files that are being downloaded will be paused and you can
|
|
enable automatic downloading again later.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div className="space-y-4">
|
|
<div className="space-y-4 rounded-md border border-gray-200 bg-gray-50 p-4">
|
|
<div className="flex items-start">
|
|
<span className="mr-3 mt-1.5 h-3 w-2 flex-shrink-0 rounded-full bg-cyan-400"></span>
|
|
<p className="text-sm leading-6 text-gray-700">
|
|
This will enable auto download for this chat. Files will be
|
|
downloaded automatically.
|
|
</p>
|
|
</div>
|
|
<div className="flex items-start">
|
|
<span className="mr-3 mt-1.5 h-3 w-2 flex-shrink-0 rounded-full bg-cyan-400"></span>
|
|
<p className="text-sm leading-6 text-gray-700">
|
|
Files in historical messages will be downloaded first, and
|
|
then files in new messages will be downloaded automatically.
|
|
</p>
|
|
</div>
|
|
<div className="flex items-start">
|
|
<span className="mr-3 mt-1.5 h-3 w-2 flex-shrink-0 rounded-full bg-cyan-400"></span>
|
|
<p className="text-sm leading-6 text-gray-700">
|
|
Download Order:
|
|
<span className="ml-1 rounded bg-blue-100 px-2">
|
|
{"Photo -> Video -> Audio -> File"}
|
|
</span>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<AutoDownloadRule value={rule} onChange={setRule} />
|
|
</div>
|
|
)}
|
|
<div className="flex justify-end gap-2">
|
|
<Button
|
|
onClick={() => triggerAuto({ rule })}
|
|
variant={chat?.autoEnabled ? "destructive" : "default"}
|
|
disabled={debounceIsAutoMutating}
|
|
>
|
|
{debounceIsAutoMutating ? "Submitting..." : "Submit"}
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => setOpen(false)}
|
|
disabled={debounceIsAutoMutating}
|
|
>
|
|
Cancel
|
|
</Button>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
}
|
|
|
|
interface AutoDownloadRuleProps {
|
|
value: AutoDownloadRule;
|
|
onChange: (value: AutoDownloadRule) => void;
|
|
}
|
|
|
|
function AutoDownloadRule({ value, onChange }: AutoDownloadRuleProps) {
|
|
const handleQueryChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
onChange({
|
|
...value,
|
|
query: e.target.value,
|
|
});
|
|
};
|
|
|
|
const handleFileTypeSelect = (type: string) => {
|
|
if (value.fileTypes.includes(type as Exclude<FileType, "media">)) {
|
|
return;
|
|
}
|
|
|
|
onChange({
|
|
...value,
|
|
fileTypes: [...value.fileTypes, type as Exclude<FileType, "media">],
|
|
});
|
|
};
|
|
|
|
const removeFileType = (typeToRemove: string) => {
|
|
onChange({
|
|
...value,
|
|
fileTypes: value.fileTypes.filter((type) => type !== typeToRemove),
|
|
});
|
|
};
|
|
|
|
return (
|
|
<Accordion type="single" collapsible>
|
|
<AccordionItem value="advanced">
|
|
<AccordionTrigger className="hover:no-underline">
|
|
Advanced
|
|
</AccordionTrigger>
|
|
<AccordionContent>
|
|
<div className="flex flex-col space-y-4 rounded-md border p-4 shadow">
|
|
<div className="flex flex-col space-y-2">
|
|
<Label htmlFor="filter-keyword">Filter Keyword</Label>
|
|
<Input
|
|
id="filter-keyword"
|
|
type="text"
|
|
className="w-full"
|
|
placeholder="Enter a keyword to filter files"
|
|
value={value.query}
|
|
onChange={handleQueryChange}
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex flex-col space-y-2">
|
|
<Label htmlFor="fileTypes">Filter File Types</Label>
|
|
<Select onValueChange={handleFileTypeSelect}>
|
|
<SelectTrigger id="fileTypes">
|
|
<SelectValue placeholder="Select File Types" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{["photo", "video", "audio", "file"].map((type) => (
|
|
<SelectItem key={type} value={type}>
|
|
{type}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
|
|
<div className="mt-2 flex flex-wrap gap-2">
|
|
{value.fileTypes.map((type) => (
|
|
<Badge
|
|
key={type}
|
|
className="flex items-center gap-1 px-2 py-1"
|
|
variant="secondary"
|
|
>
|
|
{type}
|
|
<X
|
|
className="h-3 w-3 cursor-pointer"
|
|
onClick={() => removeFileType(type)}
|
|
/>
|
|
</Badge>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</AccordionContent>
|
|
</AccordionItem>
|
|
</Accordion>
|
|
);
|
|
}
|