Use global drag and drop hint overlay

This commit is contained in:
Danielv123 committed 2024-05-04 20:31:36 +02:00
1 parent 920b5c1655
commit c34a13b3e4
6 files changed
+149 -167

No files matched your search

@@ -0,0 +1,42 @@
import { InboxOutlined } from "@ant-design/icons";
import { DraggingContext } from "../model/is_dragging";
import { useContext } from "react";
// This component is used to display a dropzone when a file is being dragged over the parent component
export function Dropzone() {
const isDroppingFile = useContext(DraggingContext);
return <div
style={{
position: "absolute",
top: "0",
left: "0",
width: "100%",
height: "100%",
zIndex: "90",
backgroundColor: "#88888844",
borderRadius: "20px",
border: "dashed 2px rgb(22, 119, 255)",
display: isDroppingFile ? "block" : "none",
}}
>
<div
id="dropzone-icon"
style={{
fontSize: "72px",
color: "rgb(22, 119, 255)",
display: "flex",
zIndex: "100",
alignItems: "center",
justifyContent: "center",
flexDirection: "column",
height: "100%",
}}
>
<InboxOutlined />
<p style={{ fontSize: "24px", display: "block", textAlign: "center", marginTop: "8px" }}>
Drop to upload
</p>
</div>
</div>;
}
+5 -62
View File
@@ -3,7 +3,6 @@ import { useNavigate } from "react-router-dom";
import { Button, Form, Input, Modal, Popconfirm, Space, Table, Typography, Upload } from "antd";
import ImportOutlined from "@ant-design/icons/ImportOutlined";
import PlusOutlined from "@ant-design/icons/PlusOutlined";
import UploadOutlined from "@ant-design/icons/UploadOutlined";
import * as lib from "@clusterio/lib";
@@ -17,7 +16,8 @@ import PageLayout from "./PageLayout";
import PluginExtra from "./PluginExtra";
import SectionHeader from "./SectionHeader";
import ModDetails from "./ModDetails";
import { InboxOutlined } from "@ant-design/icons";
import { Dropzone } from "./Dropzone";
import UploadButton from "./UploadButton";
const strcmp = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" }).compare;
@@ -134,7 +134,6 @@ export default function ModsPage() {
let navigate = useNavigate();
let [mods] = useMods();
let [modPacks] = useModPacks();
const [isDroppingFile, setIsDroppingFile] = useState(false);
function actions(mod: lib.ModInfo) {
return <Space>
@@ -176,27 +175,13 @@ export default function ModsPage() {
name="file"
accept=".zip"
multiple
showUploadList={false}
headers={{
"X-Access-Token": control.connector.token || "",
}}
action={`${webRoot}api/upload-mod`}
>
<Button
onDragEnter={e => setIsDroppingFile(true)}
onDragLeave={e => {
// Only process if we are leaving the dropzone for an element that is not a child of the button
if ((e.currentTarget as Node).contains(e.relatedTarget as Node)) {
return;
}
setIsDroppingFile(false);
}}
onDrop={e => setIsDroppingFile(false)}
style={{
// Distinct border when dropping file
border: isDroppingFile ? "dashed 2px rgb(22, 119, 255)" : undefined,
}}
icon={isDroppingFile ? <InboxOutlined /> : <UploadOutlined />}
>Upload</Button>
<UploadButton />
</Upload>;
}
@@ -256,50 +241,8 @@ export default function ModsPage() {
position: "relative",
zIndex: "1010",
}}
onDragEnter={e => {
setIsDroppingFile(true);
}}
onDragLeave={e => {
if ((e.currentTarget as Node).contains(e.relatedTarget as Node)) {
return;
}
setIsDroppingFile(false);
}}
onDrop={e => setIsDroppingFile(false)}
>
<div
style={{
position: "absolute",
top: "0",
left: "0",
width: "100%",
height: "100%",
zIndex: "90",
backgroundColor: "#88888844",
borderRadius: "20px",
border: "dashed 2px rgb(22, 119, 255)",
display: isDroppingFile ? "block" : "none",
}}
>
<div
id="dropzone-icon"
style={{
fontSize: "72px",
color: "rgb(22, 119, 255)",
display: "flex",
zIndex: "100",
alignItems: "center",
justifyContent: "center",
flexDirection: "column",
height: "100%",
}}
>
<InboxOutlined />
<p style={{ fontSize: "24px", display: "block", textAlign: "center", marginTop: "8px" }}>
Drop to upload
</p>
</div>
</div>
<Dropzone />
<Table
columns={[
{
+6 -51
View File
@@ -15,7 +15,8 @@ import SectionHeader from "./SectionHeader";
import { useInstances } from "../model/instance";
import { useSavesOfInstance } from "../model/saves";
import { notifyErrorHandler } from "../util/notify";
import { InboxOutlined } from "@ant-design/icons";
import { Dropzone } from "./Dropzone";
import UploadButton from "./UploadButton";
type ModalProps = {
@@ -205,7 +206,6 @@ export default function SavesList(props: { instance: lib.InstanceDetails }) {
let [saves] = useSavesOfInstance(props.instance.id);
let [starting, setStarting] = useState(false);
let [uploadingFiles, setUploadingFiles] = useState<File[]>([]);
const [isDroppingFile, setIsDroppingFile] = useState<boolean>(false);
let hostOffline = ["unassigned", "unknown"].includes(props.instance.status!);
const saveTable = <Table
@@ -348,7 +348,9 @@ export default function SavesList(props: { instance: lib.InstanceDetails }) {
return <div>
<SectionHeader title="Saves" extra={<Space>
{account.hasPermission("core.instance.save.upload") && <Upload {...uploadProps} >
<Button disabled={hostOffline}>Upload save</Button>
<UploadButton disabled={hostOffline}>
Upload save
</UploadButton>
</Upload>}
{account.hasPermission("core.instance.save.create") && <CreateSaveModal instance={props.instance} />}
</Space>} />
@@ -360,55 +362,8 @@ export default function SavesList(props: { instance: lib.InstanceDetails }) {
position: "relative",
zIndex: "1010",
}}
onDragEnter={e => {
setIsDroppingFile(true);
}}
onDragLeave={e => {
if ((e.currentTarget as Node).contains(e.relatedTarget as Node)) {
return;
}
setIsDroppingFile(false);
}}
onDrop={e => setIsDroppingFile(false)}
>
<div
style={{
position: "absolute",
top: "0",
left: "0",
width: "100%",
height: "100%",
zIndex: "90",
backgroundColor: "#88888844",
borderRadius: "20px",
border: "dashed 2px rgb(22, 119, 255)",
display: isDroppingFile ? "block" : "none",
}}
>
<div
id="dropzone-icon"
style={{
fontSize: "72px",
color: "rgb(22, 119, 255)",
display: "flex",
zIndex: "100",
alignItems: "center",
justifyContent: "center",
flexDirection: "column",
height: "100%",
}}
>
<InboxOutlined />
<p style={{
fontSize: "24px",
display: "block",
textAlign: "center",
marginTop: "8px",
}}>
Drop to upload
</p>
</div>
</div>
<Dropzone />
{saveTable}
</div>
</Upload.Dragger>
+73 -54
View File
@@ -9,16 +9,18 @@ import ErrorBoundary from "./ErrorBoundary";
import ErrorPage from "./ErrorPage";
import ControlContext from "./ControlContext";
import { pages } from "../pages";
import { DraggingContext } from "../model/is_dragging";
const { Header, Sider } = Layout;
export default function SiteLayout() {
let navigate = useNavigate();
let [currentSidebarPath, setCurrentSidebarPath] = useState<string|null>(null);
let [currentSidebarPath, setCurrentSidebarPath] = useState<string | null>(null);
let account = useAccount();
let plugins = useContext(ControlContext).plugins;
const [dragging, setDragging] = useState(0);
function SetSidebar(props: { path: string|null }) {
function SetSidebar(props: { path: string | null }) {
useEffect(() => {
setCurrentSidebarPath(props.path);
});
@@ -26,7 +28,7 @@ export default function SiteLayout() {
}
let accountMenuProps: MenuProps = {
onClick: ({ key }: {key:string}) => {
onClick: ({ key }: { key: string }) => {
if (key === "user") {
navigate(`/users/${account.name}/view`);
} else if (key === "logOut") {
@@ -72,59 +74,76 @@ export default function SiteLayout() {
menuItems.push({ type: "group", label: name, children: group, key: name });
}
return <Layout style={{ minHeight: "100vh" }}>
<Header className="header">
<div className="site-logo" />
<span className="site-name">Clusterio</span>
<span className="site-version">{ webUiPackage.version }</span>
<Menu
theme="dark"
mode="horizontal"
defaultSelectedKeys={["1"]}
items={[{ label: "Dashboard", key: "1" }]}
/>
<Dropdown
className="account-dropdown-header"
placement="bottomRight"
trigger={["click"]}
menu={accountMenuProps}
>
<UserOutlined/>
</Dropdown>
</Header>
<Layout className="site-layout">
<Sider
collapsible
collapsedWidth={0}
breakpoint="md"
zeroWidthTriggerStyle={{ top: 6, zIndex: -1 }}
width={250}
className="site-layout-sider"
>
// Prevent issues with event ordering
let dragChange = 0;
function setDraggingProxy(value: number) {
dragChange += value;
setDragging(dragging + dragChange);
}
return <Layout
style={{ minHeight: "100vh" }}
onDragEnter={() => setDraggingProxy(1)}
onDragLeave={() => setDraggingProxy(-1)}
// Reset dragging state when dropped
onDrop={() => setDragging(0)}
// Allow entire document to be a drag target
onDragOver={(e) => e.preventDefault()}
>
<DraggingContext.Provider value={dragging > 0}>
<Header className="header">
<div className="site-logo" />
<span className="site-name">Clusterio</span>
<span className="site-version">{webUiPackage.version}</span>
<Menu
mode="inline"
defaultOpenKeys={[...menuGroups.keys()]}
selectedKeys={currentSidebarPath ? [currentSidebarPath] : []}
style={{ height: "100%", borderRight: 0 }}
onClick={({ key }) => navigate(key)}
items={menuItems}
theme="dark"
mode="horizontal"
defaultSelectedKeys={["1"]}
items={[{ label: "Dashboard", key: "1" }]}
/>
</Sider>
<Layout className="site-layout-content-container">
<Routes>
{combinedPages.map(({path, sidebarPath, content}) => <Route
path={path}
key={path}
element={<Fragment key={path}>
<SetSidebar path={sidebarPath ? sidebarPath : path} />
<ErrorBoundary Component={ErrorPage}>
{content}
</ErrorBoundary>
</Fragment>}
/>)}
<Route element={<SetSidebar path={null} />} />
</Routes>
<Dropdown
className="account-dropdown-header"
placement="bottomRight"
trigger={["click"]}
menu={accountMenuProps}
>
<UserOutlined />
</Dropdown>
</Header>
<Layout className="site-layout">
<Sider
collapsible
collapsedWidth={0}
breakpoint="md"
zeroWidthTriggerStyle={{ top: 6, zIndex: -1 }}
width={250}
className="site-layout-sider"
>
<Menu
mode="inline"
defaultOpenKeys={[...menuGroups.keys()]}
selectedKeys={currentSidebarPath ? [currentSidebarPath] : []}
style={{ height: "100%", borderRight: 0 }}
onClick={({ key }) => navigate(key)}
items={menuItems}
/>
</Sider>
<Layout className="site-layout-content-container">
<Routes>
{combinedPages.map(({ path, sidebarPath, content }) => <Route
path={path}
key={path}
element={<Fragment key={path}>
<SetSidebar path={sidebarPath ? sidebarPath : path} />
<ErrorBoundary Component={ErrorPage}>
{content}
</ErrorBoundary>
</Fragment>}
/>)}
<Route element={<SetSidebar path={null} />} />
</Routes>
</Layout>
</Layout>
</Layout>
</DraggingContext.Provider>
</Layout>;
}
@@ -0,0 +1,20 @@
import { useContext } from "react";
import { Button } from "antd";
import { InboxOutlined, UploadOutlined } from "@ant-design/icons";
import { DraggingContext } from "../model/is_dragging";
export default function UploadButton(props: React.ComponentProps<typeof Button>) {
const isDroppingFile = useContext(DraggingContext);
return <Button
{...props}
style={{
...props.style,
// Distinct border when dropping file
border: isDroppingFile ? "dashed 2px rgb(22, 119, 255)" : undefined,
}}
icon={isDroppingFile ? <InboxOutlined /> : props.icon || <UploadOutlined />}
>
{props.children || "Upload"}
</Button>;
}
+3
View File
@@ -0,0 +1,3 @@
import { createContext } from "react";
export const DraggingContext = createContext(false);