[chore-521] Move create playlist action

This commit is contained in:
MathieuG-P
2024-10-20 18:10:01 +02:00
parent b59c37ebbd
commit 0efc56f8d6
24 changed files with 703 additions and 109 deletions
@@ -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>