diff --git a/packages/web_ui/src/components/Dropzone.tsx b/packages/web_ui/src/components/Dropzone.tsx
new file mode 100644
index 00000000..cc9feb3e
--- /dev/null
+++ b/packages/web_ui/src/components/Dropzone.tsx
@@ -0,0 +1,51 @@
+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({ disabled = false }: { disabled?: boolean }) {
+ const isDroppingFile = useContext(DraggingContext);
+
+ const textStyle: { color?: string } = {};
+ let text = "Drop to upload";
+ if (disabled) {
+ textStyle.color = "red";
+ text = "Target is offline";
+ }
+ const borderColor = disabled ? "gray" : "rgb(22, 119, 255)";
+
+ return
;
+}
diff --git a/packages/web_ui/src/components/ModsPage.tsx b/packages/web_ui/src/components/ModsPage.tsx
index 0e5541ae..2637d1cc 100644
--- a/packages/web_ui/src/components/ModsPage.tsx
+++ b/packages/web_ui/src/components/ModsPage.tsx
@@ -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,6 +16,8 @@ import PageLayout from "./PageLayout";
import PluginExtra from "./PluginExtra";
import SectionHeader from "./SectionHeader";
import ModDetails from "./ModDetails";
+import { Dropzone } from "./Dropzone";
+import UploadButton from "./UploadButton";
const strcmp = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" }).compare;
@@ -55,11 +56,13 @@ function ImportModPackButton() {
name="string"
rules={[
{ required: true },
- { async validator(rule, value) {
- if (value) {
- lib.ModPack.fromModPackString(value);
- }
- }},
+ {
+ async validator(rule, value) {
+ if (value) {
+ lib.ModPack.fromModPackString(value);
+ }
+ },
+ },
]}
>
@@ -172,12 +175,13 @@ export default function ModsPage() {
name="file"
accept=".zip"
multiple
+ showUploadList={false}
headers={{
- "X-Access-Token": control.connector.token||"",
+ "X-Access-Token": control.connector.token || "",
}}
action={`${webRoot}api/upload-mod`}
>
-
+
;
}
@@ -219,54 +223,76 @@ export default function ModsPage() {
})}
/>
- (
- strcmp(a.name, b.name) || a.integerVersion - b.integerVersion
- ),
- },
- {
- title: "Version",
- key: "version",
- align: "right",
- render: (_, mod) => <>
- {`${mod.version} `}
- {`/ ${mod.factorioVersion}`}
- >,
- },
- {
- title: "Filename",
- dataIndex: "filename",
- responsive: ["xl"],
- sorter: (a, b) => strcmp(a.filename, b.filename),
- },
- {
- title: "Size",
- key: "size",
- responsive: ["lg"],
- render: (_, mod) => lib.formatBytes(mod.size),
- align: "right",
- sorter: (a, b) => a.size - b.size,
- },
- {
- title: "Action",
- key: "action",
- responsive: ["lg"],
- render: (_, mod) => actions(mod),
- },
- ]}
- expandable={{
- expandedRowRender: (mod: lib.ModInfo) => ,
- expandedRowClassName: () => "no-expanded-padding",
+
+ mod.id}
- />
+ action={`${webRoot}api/upload-mod`}
+ showUploadList={false}
+ >
+
+
+
(
+ strcmp(a.name, b.name) || a.integerVersion - b.integerVersion
+ ),
+ },
+ {
+ title: "Version",
+ key: "version",
+ align: "right",
+ render: (_, mod) => <>
+ {`${mod.version} `}
+ {`/ ${mod.factorioVersion}`}
+ >,
+ },
+ {
+ title: "Filename",
+ dataIndex: "filename",
+ responsive: ["xl"],
+ sorter: (a, b) => strcmp(a.filename, b.filename),
+ },
+ {
+ title: "Size",
+ key: "size",
+ responsive: ["lg"],
+ render: (_, mod) => lib.formatBytes(mod.size),
+ align: "right",
+ sorter: (a, b) => a.size - b.size,
+ },
+ {
+ title: "Action",
+ key: "action",
+ responsive: ["lg"],
+ render: (_, mod) => actions(mod),
+ },
+ ]}
+ expandable={{
+ expandedRowRender: (mod: lib.ModInfo) => ,
+ expandedRowClassName: () => "no-expanded-padding",
+ }}
+ dataSource={[...mods.values()]}
+ pagination={false}
+ rowKey={mod => mod.id}
+ />
+
+
;
}
diff --git a/packages/web_ui/src/components/SavesList.tsx b/packages/web_ui/src/components/SavesList.tsx
index 2d6f2c69..9a962a87 100644
--- a/packages/web_ui/src/components/SavesList.tsx
+++ b/packages/web_ui/src/components/SavesList.tsx
@@ -15,6 +15,8 @@ import SectionHeader from "./SectionHeader";
import { useInstances } from "../model/instance";
import { useSavesOfInstance } from "../model/saves";
import { notifyErrorHandler } from "../util/notify";
+import { Dropzone } from "./Dropzone";
+import UploadButton from "./UploadButton";
type ModalProps = {
@@ -150,7 +152,7 @@ function TransferModal(props: ModalProps) {
autoFocus
showSearch
filterOption={(input, option) => (
- (option?.title!.toLowerCase().indexOf(input.toLowerCase())??-1) >= 0
+ (option?.title!.toLowerCase().indexOf(input.toLowerCase()) ?? -1) >= 0
)}
>
{[...instances.values()].filter(
@@ -214,8 +216,8 @@ export default function SavesList(props: { instance: lib.InstanceDetails }) {
title: "Name",
render: (_, save) => <>
{save.name}
- {save.loaded && }
- {save.loadByDefault && }
+ {save.loaded && }
+ {save.loadByDefault && }
>,
sorter: (a, b) => a.name.localeCompare(b.name),
},
@@ -240,7 +242,7 @@ export default function SavesList(props: { instance: lib.InstanceDetails }) {
expandable={{
columnWidth: 33,
expandRowByClick: true,
- expandedRowRender: save =>
+ expandedRowRender: save =>
{account.hasPermission("core.instance.start") && } />
{
account.hasPermission("core.instance.save.upload")
?
- {saveTable}
+
+
+ {saveTable}
+
: saveTable
}
@@ -362,7 +374,7 @@ export default function SavesList(props: { instance: lib.InstanceDetails }) {
{file.name}