mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
[chore-521] Move create playlist action
This commit is contained in:
@@ -1,38 +1,96 @@
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { DragEvent, ReactNode, useCallback, useState } from "react";
|
||||
import { DragEvent, ReactNode, useCallback, useMemo, useState } from "react";
|
||||
import { BsmImage } from "./bsm-image.component";
|
||||
import BeatImpatient from "../../../../assets/images/apngs/beat-impatient.png";
|
||||
import { useThemeColor } from "renderer/hooks/use-theme-color.hook";
|
||||
import Color from "color";
|
||||
import { cn } from "renderer/helpers/css-class.helpers";
|
||||
import { CloseIcon } from "../svgs/icons/close-icon.component";
|
||||
import { OpenDialogOptions } from "electron";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { useService } from "renderer/hooks/use-service.hook";
|
||||
import { IpcService } from "renderer/services/ipc.service";
|
||||
import { NotificationService } from "renderer/services/notification.service";
|
||||
import { lastValueFrom } from "rxjs";
|
||||
|
||||
type Props = Readonly<{
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
open?: boolean;
|
||||
text?: string;
|
||||
subtext?: string;
|
||||
onDrop?: (event: DragEvent<HTMLDivElement>) => void;
|
||||
onFiles?: (paths: string[]) => void;
|
||||
onClose?: () => void;
|
||||
filters?: OpenDialogOptions["filters"];
|
||||
dialogOptions?: {
|
||||
text?: string;
|
||||
dialog?: Exclude<OpenDialogOptions, "filters">;
|
||||
}
|
||||
}>;
|
||||
|
||||
// Supports drop and drop functionality for files.
|
||||
export function Dropzone({
|
||||
children,
|
||||
className,
|
||||
open,
|
||||
text,
|
||||
subtext,
|
||||
onDrop,
|
||||
filters,
|
||||
dialogOptions,
|
||||
onFiles,
|
||||
onClose
|
||||
}: Props) {
|
||||
|
||||
const ipc = useService(IpcService);
|
||||
const notification = useService(NotificationService);
|
||||
|
||||
const t = useTranslation();
|
||||
|
||||
const [dragCount, setDragCount] = useState(0);
|
||||
const [dropZoneDragging, setDropZoneDragging] = useState(false);
|
||||
const themeColor = useThemeColor("first-color");
|
||||
const color = new Color(themeColor).lighten(.25).saturate(.8).hex();
|
||||
|
||||
const supportedExtensions = useMemo(() => {
|
||||
if(!filters?.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return filters.flatMap(filter => filter.extensions ?? []);
|
||||
}, [filters]);
|
||||
|
||||
const handleDrop = useCallback((event: DragEvent<HTMLDivElement>) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
setDragCount(0);
|
||||
if (onDrop) onDrop(event);
|
||||
}, []);
|
||||
|
||||
setDragCount(() => 0);
|
||||
setDropZoneDragging(() => false);
|
||||
|
||||
if (!onFiles || !event.dataTransfer?.files?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const paths = Array.from(event.dataTransfer.files).reduce<string[]>((acc, file) => {
|
||||
const path = window.electron.webUtils.getPathForFile(file);
|
||||
|
||||
if(supportedExtensions.some(ext => path.endsWith(ext))) {
|
||||
acc.push(path);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
if(!paths.length) {
|
||||
notification.notifyError({
|
||||
title: t("notifications.shared.errors.titles.file-not-supported"),
|
||||
desc: t("notifications.shared.errors.msg.file-not-supported", { types: supportedExtensions.join(", ") }),
|
||||
duration: 8000
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
onFiles(paths);
|
||||
}, [supportedExtensions]);
|
||||
|
||||
const onDragOver = useCallback((event: DragEvent<HTMLDivElement>) => {
|
||||
event.preventDefault();
|
||||
@@ -51,14 +109,33 @@ export function Dropzone({
|
||||
setDragCount(prev => prev - 1);
|
||||
}, []);
|
||||
|
||||
const openDialog = useCallback(async () => {
|
||||
if(!dialogOptions?.dialog) {
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await lastValueFrom(ipc.sendV2("open-dialog", { ...dialogOptions.dialog, filters }));
|
||||
|
||||
if(res?.canceled || !res?.filePaths?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
onFiles?.(res.filePaths);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={className} onDragEnter={onDragEnter} onDragLeave={onDragLeave}>
|
||||
<div className={cn("relative", className)} onDragEnter={onDragEnter} onDragLeave={onDragLeave}>
|
||||
<AnimatePresence>
|
||||
{dragCount ? (
|
||||
{(dragCount || open) ? (
|
||||
<div onDragEnter={onDragEnter} onDragLeave={onDragLeave} onDrop={onDragLeave} className="absolute size-full top-0 right-0 z-10">
|
||||
<motion.div className="absolute size-full top-0 right-0 bg-black" initial={{ opacity: 0 }} animate={{ opacity: .6 }} exit={{ opacity: 0 }}/>
|
||||
<motion.div className="absolute size-full top-0 right-0 flex justify-center items-center p-5" initial={{ y: "100%" }} animate={{ y: "0" }} exit={{ y: "100%" }}>
|
||||
<div className="h-72 max-h-full w-full max-w-lg sm:h-96 sm:max-w-2xl bg-theme-3 rounded-md shadow-black shadow-lg p-7">
|
||||
<div className="relative h-72 max-h-full w-full max-w-lg sm:h-96 sm:max-w-2xl bg-theme-3 rounded-md shadow-black shadow-lg p-7">
|
||||
{onClose && (
|
||||
<button className="absolute top-0.5 right-0.5 size-7" onClick={onClose}>
|
||||
<CloseIcon className="size-full"/>
|
||||
</button>
|
||||
)}
|
||||
<div
|
||||
onDragOver={onDragOver}
|
||||
onDrop={handleDrop}
|
||||
@@ -77,6 +154,9 @@ export function Dropzone({
|
||||
<BsmImage className="size-28 pointer-events-none" image={BeatImpatient}/>
|
||||
<span className="text-2xl font-bold pointer-events-none">{text}</span>
|
||||
<span className="pointer-events-none">{subtext}</span>
|
||||
{dialogOptions?.dialog && (
|
||||
<button className="underline hover:no-underline" onClick={openDialog}>{dialogOptions?.text ?? t("drop-zone.or-browse-files")}</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
Reference in New Issue
Block a user