Files
factorio-clusterio/packages/web_ui/src/components/RoleViewPage.tsx
T
Hornwitser 8c17adf15b Refactor EventSubscriber to track its resource
Process updates of the resource EventSubscriber are subscribed to and
have it keep a mapping of id to value syncronised with event provider as
long as there are subscription callbacks registered with it.  The
mapping is kept around after the last callback is unsubscribed so that
it can be efficiently resynced with the resource based on the updatedAt
timestaps of individual elements.

This allows the EventSubscriber to provide the right building blocks to
provide its values to React components via the the useSyncExternalStore
hook.  This in turn greatly simplifies the web_ui models for the
subscribed resources.
2023-12-12 23:40:29 +01:00

144 lines
4.0 KiB
TypeScript

import React, { useEffect, useContext, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { Button, Checkbox, Form, Input, Popconfirm, Spin } from "antd";
import DeleteOutlined from "@ant-design/icons/DeleteOutlined";
import * as lib from "@clusterio/lib";
import { useAccount } from "../model/account";
import ControlContext from "./ControlContext";
import PageHeader from "./PageHeader";
import PageLayout from "./PageLayout";
import PluginExtra from "./PluginExtra";
import { notifyErrorHandler } from "../util/notify";
function useRole(id: number): [Readonly<lib.Role> | undefined, boolean, () => void] {
let control = useContext(ControlContext);
let [role, setRole] = useState<lib.Role>();
let [synced, setSynced] = useState(false);
function updateRole() {
// XXX optimize by requesting only the role in question
control.send(new lib.RoleListRequest()).then(roles => {
let match = roles.find(u => u.id === id);
if (match) {
setRole(match);
}
setSynced(true);
});
}
useEffect(() => {
updateRole();
}, [id]);
return [role, synced, updateRole];
}
export default function RoleViewPage() {
let params = useParams();
let roleId = Number(params.id);
let navigate = useNavigate();
let account = useAccount();
let control = useContext(ControlContext);
const [role, synced, updateRole] = useRole(roleId);
let [edited, setEdited] = useState(false);
let nav = [{ name: "Roles", path: "/roles" }, { name: role?.name ?? String(roleId) }];
if (!role) {
if (!synced) {
return <PageLayout nav={nav}>
<PageHeader title={String(roleId)} />
<Spin size="large" />
</PageLayout>;
}
return <PageLayout nav={nav}>
<PageHeader title="Role not found" />
<p>Role with id {roleId} was not found on the controller.</p>
</PageLayout>;
}
let initialValues = {
name: role["name"],
description: role["description"],
permissions: {
...Object.fromEntries([...lib.permissions.values()].map(perm => [
perm.name, [...role.permissions].includes(perm.name),
])),
},
};
let canUpdate = account.hasPermission("core.role.update");
return <PageLayout nav={nav}>
<Form
initialValues={initialValues}
onValuesChange={() => { setEdited(true); }}
onFinish={values => {
let newPermissions = [];
for (let [name, value] of Object.entries(values.permissions)) {
if (value) { newPermissions.push(name); }
}
control.send(
new lib.RoleUpdateRequest(roleId, values.name || "", values.description || "", newPermissions)
).then(() => {
setEdited(false);
updateRole();
}).catch(notifyErrorHandler("Error applying changes"));
}}
>
<PageHeader
title={role.name}
extra={<>
{canUpdate && <Button type={edited ? "primary" : "default"} htmlType="submit">Apply</Button>}
{account.hasPermission("core.role.delete") && <Popconfirm
title="Delete role?"
placement="bottomRight"
okText="Delete"
okButtonProps={{ danger: true }}
onConfirm={() => {
control.send(
new lib.RoleDeleteRequest(roleId)
).then(() => {
navigate("/roles");
}).catch(notifyErrorHandler("Error deleting role"));
}}
>
<Button danger >
<DeleteOutlined />
</Button>
</Popconfirm>}
</>}
/>
<Form.Item name="name" label="Name">
<Input disabled={!canUpdate}/>
</Form.Item>
<Form.Item name="description" label="Description">
<Input disabled={!canUpdate}/>
</Form.Item>
<h3>Permissions</h3>
{[...lib.permissions.values()].map(({name, title, description}) => (
<Form.Item
name={["permissions", name]}
key={name}
label={title}
tooltip={description}
valuePropName="checked"
labelCol={{ sm: { span: 22, push: 2 }}}
wrapperCol={{ sm: { span: 2, pull: 22 }}}
labelAlign="left"
colon={false}
>
<Checkbox disabled={!canUpdate} />
</Form.Item>
))}
</Form>
<PluginExtra component="RoleViewPage" role={role} />
</PageLayout>;
}