Files
factorio-clusterio/packages/web_ui/src/components/InstanceControlButton.tsx
T
bbassieandClaude Opus 5.5 666dd48412 Show the instance control button as loading while starting or stopping
The button offered Stop while the instance was starting whenever the
start came from somewhere else, such as Load save, another tab or ctl.
It now shows the action in progress as loading until the status
settles. Kill stays available from the dropdown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 22:52:09 +00:00

134 lines
3.7 KiB
TypeScript

import React, { useContext, useState } from "react";
import { Popconfirm } from "antd";
import * as lib from "@clusterio/lib";
import ControlContext from "./ControlContext";
import { useAccount } from "../model/account";
import { notifyErrorHandler } from "../util/notify";
import VariableDropdownButton, { VariableDropdownButtonProps } from "./VariableDropdownButton";
type InstanceControlButtonProps = Omit<VariableDropdownButtonProps, "actions"> & {
instance: lib.InstanceDetails;
onFinish?: () => void;
};
export const InstanceControlButtonPermissions = [
"core.instance.start", "core.instance.restart", "core.instance.stop", "core.instance.kill",
] as const;
export default function InstanceControlButton(props: InstanceControlButtonProps) {
const account = useAccount();
const control = useContext(ControlContext);
const [switching, setSwitching] = useState<boolean>(false);
const [popconfirm, setPopconfirm] = useState<string | null>(null);
const instanceId = props.instance.id;
const instanceStatus = props.instance.status;
// While starting or stopping the button shows the action in progress as loading.
const showStart = ["stopped", "starting"].includes(instanceStatus);
const showStop = ["running", "stopping"].includes(instanceStatus);
const transitioning = ["starting", "stopping"].includes(instanceStatus);
function actionFinish() {
setSwitching(false);
if (props.onFinish) {
props.onFinish();
}
}
const actions: VariableDropdownButtonProps["actions"] = [];
if (account.hasPermission("core.instance.start") && showStart) {
actions.push({
key: "start",
label: "Start",
onClick: () => {
setSwitching(true);
control.sendTo(
{ instanceId },
new lib.InstanceStartRequest(undefined),
).catch(
notifyErrorHandler("Error starting instance")
).finally(actionFinish);
},
});
} else if (account.hasPermission("core.instance.stop")) { // else if because it is stop or start
actions.push({
key: "stop",
label: "Stop",
danger: true,
onClick: () => {
setSwitching(true);
control.sendTo(
{ instanceId },
new lib.InstanceStopRequest(),
).catch(
notifyErrorHandler("Error stopping instance")
).finally(actionFinish);
},
});
}
if (account.hasPermission("core.instance.restart")) {
actions.push({
key: "restart",
label: "Restart",
disabled: instanceStatus !== "running",
onClick: () => {
setSwitching(true);
control.sendTo(
{ instanceId },
new lib.InstanceRestartRequest(undefined),
).catch(
notifyErrorHandler("Error restarting instance")
).finally(actionFinish);
},
});
}
if (account.hasPermission("core.instance.kill")) {
actions.push({
key: "kill",
label: "Kill",
danger: true,
disabled: instanceStatus === "stopped",
onClick: () => setPopconfirm("kill"),
});
}
return <div style={{ position: "relative", display: "inline-block" }}>
<VariableDropdownButton
{...props}
type="primary"
actions={actions}
loading={switching || transitioning}
disabled={!(showStart || showStop)}
/>
<Popconfirm
title={<>
Killing this instance may leave you save and player data corrupted.
Are you sure you want to kill the instance?
</>}
open={popconfirm === "kill"}
placement="bottomRight"
okText="Kill"
okButtonProps={{ danger: true }}
onCancel={() => setPopconfirm(null)}
onConfirm={() => {
control.sendTo(
{ instanceId },
new lib.InstanceKillRequest(),
).catch(
notifyErrorHandler("Error killing instance")
).finally(actionFinish);
setPopconfirm(null);
}}
>
{/* Used to position the popup under the button */}
<span style={{ position: "absolute", right: 0, top: "100%", width: 1, height: 1 }} />
</Popconfirm>
</div>;
}