mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
[feature-75] update drop zone UI
This commit is contained in:
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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\"."
|
||||
}
|
||||
|
||||
@@ -65,6 +65,10 @@
|
||||
"empty-maps": {
|
||||
"text": "マップがありません",
|
||||
"button": "マップをダウンロードする"
|
||||
},
|
||||
"drop-zone": {
|
||||
"text": "マップをインポート",
|
||||
"subtext": "ZIPファイルをここにドロップしてマップをインポート"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,10 @@
|
||||
"empty-maps": {
|
||||
"text": "Нет карт",
|
||||
"button": "Скачать карты"
|
||||
},
|
||||
"drop-zone": {
|
||||
"text": "Импортируйте свои карты",
|
||||
"subtext": "Перетащите сюда файлы ZIP, чтобы импортировать свои карты"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,10 @@
|
||||
"empty-maps": {
|
||||
"text": "無譜面",
|
||||
"button": "下載譜面"
|
||||
},
|
||||
"drop-zone": {
|
||||
"text": "導入您的地圖",
|
||||
"subtext": "將ZIP文件拖放到此處以導入您的地圖"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user