[feature-75] update drop zone UI

This commit is contained in:
MathieuG-P
2024-10-16 19:11:57 +02:00
parent be347dc6ba
commit 4e6948693d
10 changed files with 108 additions and 62 deletions
+4
View File
@@ -65,6 +65,10 @@
"empty-maps": {
"text": "Keine Maps",
"button": "Maps herunterladen"
},
"drop-zone": {
"text": "Deine Karten importieren",
"subtext": "Lasse deine ZIP-Dateien hier fallen, um deine Karten zu importieren"
}
}
}
+5
View File
@@ -65,7 +65,12 @@
"empty-maps": {
"text": "No maps",
"button": "Download maps"
},
"drop-zone": {
"text": "Import your maps",
"subtext": "Drop your zip files here to import your maps"
}
}
}
},
+4
View File
@@ -65,6 +65,10 @@
"empty-maps": {
"text": "No hay mapas",
"button": "Descargar mapas"
},
"drop-zone": {
"text": "Importar tus mapas",
"subtext": "Suelta tus archivos zip aquí para importar tus mapas"
}
}
}
+8 -4
View File
@@ -65,6 +65,10 @@
"empty-maps": {
"text": "Aucune map",
"button": "Télécharger des maps"
},
"drop-zone": {
"text": "Importer vos maps",
"subtext": "Déposez vos fichiers zip ici pour importer vos maps"
}
}
}
@@ -508,12 +512,12 @@
},
"import-map": {
"titles": {
"success": "Importation de la carte terminée",
"error": "Une erreur est survenue lors de l'importation de la carte"
"success": "Importation des maps terminée",
"error": "Une erreur est survenue lors de l'importation des maps"
},
"msgs": {
"success": "Toutes les cartes ont été importées avec succès.",
"some-success": "Certaines cartes ont été importées avec succès.",
"success": "Toutes les maps ont été importées avec succès.",
"some-success": "Certaines maps ont été importées avec succès.",
"only-accept-zip": "Seules les fichiers ZIP sont pris en charge.",
"invalid-zip": "Le(s) fichier(s) Zip ne contient/contiennent aucun fichier \"Info.dat\"."
}
+4
View File
@@ -65,6 +65,10 @@
"empty-maps": {
"text": "マップがありません",
"button": "マップをダウンロードする"
},
"drop-zone": {
"text": "マップをインポート",
"subtext": "ZIPファイルをここにドロップしてマップをインポート"
}
}
}
+4
View File
@@ -65,6 +65,10 @@
"empty-maps": {
"text": "Нет карт",
"button": "Скачать карты"
},
"drop-zone": {
"text": "Импортируйте свои карты",
"subtext": "Перетащите сюда файлы ZIP, чтобы импортировать свои карты"
}
}
}
+4
View File
@@ -65,6 +65,10 @@
"empty-maps": {
"text": "無譜面",
"button": "下載譜面"
},
"drop-zone": {
"text": "導入您的地圖",
"subtext": "將ZIP文件拖放到此處以導入您的地圖"
}
}
}
+4
View File
@@ -65,6 +65,10 @@
"empty-maps": {
"text": "无谱面",
"button": "下载谱面"
},
"drop-zone": {
"text": "导入您的地图",
"subtext": "将ZIP文件拖放到此处以导入您的地图"
}
}
}
@@ -204,11 +204,8 @@ export function MapsPlaylistsPanel({ version, isActive }: Props) {
onDrop={event => {
handleFileDrop(event.dataTransfer.files);
}}
overlay={
<div className="text-3xl pointer-events-none">
Drop map files here 😃
</div>
}
text={t("pages.version-viewer.maps.tabs.maps.drop-zone.text")}
subtext={t("pages.version-viewer.maps.tabs.maps.drop-zone.subtext")}
>
<LocalMapsListPanel className="w-full h-full shrink-0" isActive={isActive && tabIndex === 0} ref={mapsRef} version={version} filter={mapFilter} search={search} linkedState={mapsLinkedState} />
</Dropzone>
@@ -1,72 +1,88 @@
import React, { ReactNode, useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { DragEvent, ReactNode, useCallback, 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";
type Props = Readonly<{
children?: ReactNode;
className?: string;
overlay?: ReactNode;
overlayColor?: string;
overlayZIndex?: number;
onDrop?: (event: React.DragEvent<HTMLDivElement>) => void;
text?: string;
subtext?: string;
onDrop?: (event: DragEvent<HTMLDivElement>) => void;
}>;
// Supports drop and drop functionality for files.
export function Dropzone({
children,
className,
overlay,
overlayColor,
overlayZIndex,
onDrop
text,
subtext,
onDrop,
}: Props) {
const [dragging, setDragging] = useState(false);
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 normalizeZIndex = (zIndex?: number) => {
return (!zIndex || zIndex <= 0) ? 50 : zIndex;
}
const handleDrop = useCallback((event: DragEvent<HTMLDivElement>) => {
event.preventDefault();
event.stopPropagation();
setDragCount(0);
if (onDrop) onDrop(event);
}, []);
const renderFileOverlay = () => {
return (
<div
className="absolute w-full h-full flex items-center justify-center"
style={{
backgroundColor: overlayColor || "#000000CC",
zIndex: normalizeZIndex(overlayZIndex),
}}
onDrop={event => {
event.preventDefault();
event.stopPropagation();
setDragging(false);
if (onDrop) onDrop(event);
}}
onDragOver={event => {
event.preventDefault();
event.stopPropagation();
}}
onDragEnter={event => {
// Should consume the event 1 more time to avoid multiple onDragEnter
// triggers by the parent div
event.preventDefault();
event.stopPropagation();
}}
onDragLeave={event => {
event.preventDefault();
event.stopPropagation();
setDragging(false);
}}
>
{overlay}
</div>
)
}
const onDragOver = useCallback((event: DragEvent<HTMLDivElement>) => {
event.preventDefault();
event.stopPropagation();
}, []);
const onDragEnter = useCallback((event: DragEvent<HTMLDivElement>) => {
event.preventDefault();
event.stopPropagation();
setDragCount(prev => prev + 1);
}, []);
const onDragLeave = useCallback((event: DragEvent<HTMLDivElement>) => {
event.preventDefault();
event.stopPropagation();
setDragCount(prev => prev - 1);
}, []);
return (
<div className={className} onDragEnter={event => {
event.preventDefault();
event.stopPropagation();
setDragging(true);
}}>
{dragging && renderFileOverlay()}
<div className={className} onDragEnter={onDragEnter} onDragLeave={onDragLeave}>
<AnimatePresence>
{dragCount ? (
<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
onDragOver={onDragOver}
onDrop={handleDrop}
onDragEnter={() => setDropZoneDragging(true)}
onDragLeave={() => setDropZoneDragging(false)}
className="relative size-full z-[1] border-dashed border-3 rounded-md flex flex-col gap-1 justify-center items-center"
style={{ borderColor: color }}
>
<div className="absolute size-full top-0 right-0 pointer-events-none -z-[1] transition-opacity duration-150" style={{
backgroundColor: "transparent",
backgroundImage: `radial-gradient(${color} 1px, transparent 0)`,
backgroundSize: "60px 60px",
backgroundPosition: "5px",
opacity: dropZoneDragging ? .7 : .2,
}}/>
<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>
</div>
</div>
</motion.div>
</div>
): undefined}
</AnimatePresence>
{children}
</div>
);