diff --git a/packages/web_ui/src/components/BaseConfigTree.tsx b/packages/web_ui/src/components/BaseConfigTree.tsx index c5f60849..be22aeb2 100644 --- a/packages/web_ui/src/components/BaseConfigTree.tsx +++ b/packages/web_ui/src/components/BaseConfigTree.tsx @@ -1,7 +1,9 @@ import * as lib from "@clusterio/lib"; import React, { useEffect, useState } from "react"; -import { Button, Card, Checkbox, Form, Input, InputNumber, Space, Spin, Tooltip, Tree, Typography } from "antd"; +import { + Button, Card, Checkbox, Form, FormInstance, Input, InputNumber, Space, Spin, Tooltip, Tree, Typography, +} from "antd"; import ReloadOutlined from "@ant-design/icons/ReloadOutlined"; const { Title } = Typography; @@ -25,6 +27,20 @@ function getInitialValues(config: lib.Config, props: BaseConfigTreeProps) { return initialValues; } +function renderInput(def: lib.FieldDefinition) { + if (def.type === "boolean") { + return ; + } + if (def.type === "string") { + return ; + } + if (def.type === "number") { + return ; + } + + return `Unknown type ${def.type}`; +} + type BaseConfigTreeProps = { ConfigClass: typeof lib.ControllerConfig | typeof lib.HostConfig | typeof lib.InstanceConfig; retrieveConfig: () => Promise; @@ -38,7 +54,7 @@ export default function BaseConfigTree(props: BaseConfigTreeProps) { let [config, setConfig] = useState|null>(null); let [form] = Form.useForm(); let [changedFields, setChangedFields] = useState>(new Set()); - let [errorFields, setErrorFields] = useState(new Map()); + let [errorFields, setErrorFields] = useState(new Map()); let [applying, setApplying] = useState(false); const available = props.available ?? true; @@ -56,20 +72,6 @@ export default function BaseConfigTree(props: BaseConfigTreeProps) { } }, [props.id, available]); - function renderInput(def: lib.FieldDefinition) { - if (def.type === "boolean") { - return ; - } - if (def.type === "string") { - return ; - } - if (def.type === "number") { - return ; - } - - return `Unknown type ${def.type}`; - } - if (!available) { return <> Config @@ -109,12 +111,109 @@ export default function BaseConfigTree(props: BaseConfigTreeProps) { } } + const [propsMap, treeData] = computeTreeData( + form, + initialValues, + errorFields, + config, + setConfig, + props.ConfigClass, + onValuesChange, + ); + + return <> + + Config + <Space style={{ float: "right" }}> + <Button + type="primary" + size="small" + loading={applying} + onClick={async () => { + setApplying(true); + for (let field of changedFields) { + let value = form.getFieldValue(field); + let request; + if (propsMap.has(field)) { + let [fieldName, prop] = propsMap.get(field); + request = props.setProp(fieldName, prop, value); + + } else { + request = props.setField(field, value === null ? "" : String(value)); + } + + try { + await request; + setChangedFields(changed => { + let newChanged = new Set(changed); + newChanged.delete(field); + return newChanged; + }); + + } catch (err: any) { + setErrorFields(errors => { + let newErrors = new Map(errors); + newErrors.set(field, err.message); + return newErrors; + }); + } + } + + await updateConfig(); + setApplying(false); + + if (props.onApply) { + props.onApply(); + } + }} + disabled={changedFields.size === 0} + > + Apply + </Button> + <Button + size="small" + onClick={() => { + form.resetFields(); + setChangedFields(new Set()); + setErrorFields(new Map()); + }} + > + Revert + </Button> + </Space> + + +
+ changedFields.has(node.key)} + /> + +
+ ; +} + +function computeTreeData( + form: FormInstance, + initialValues: any, + errorFields: Map, + config: lib.Config, + setConfig: (newConfig: lib.Config) => void, + ConfigClass: BaseConfigTreeProps["ConfigClass"], + onValuesChange: (changedValues: object) => void, +) { let restartTip = ; - type ChildNode = { key: string; children: any[]; @@ -129,7 +228,7 @@ export default function BaseConfigTree(props: BaseConfigTreeProps) { let treeData = []; let propsMap = new Map(); const groups = new Map(); - for (let [fieldName, def] of Object.entries(props.ConfigClass.fieldDefinitions)) { + for (let [fieldName, def] of Object.entries(ConfigClass.fieldDefinitions)) { const [groupName, field] = lib.splitOn(".", fieldName); let group = groups.get(groupName); if (!group) { @@ -221,7 +320,7 @@ export default function BaseConfigTree(props: BaseConfigTreeProps) { let propName = form.getFieldValue(`${newPropPath}.name`); let propValue = form.getFieldValue(`${newPropPath}.value`); if (!Object.prototype.hasOwnProperty.call(value, propName)) { - let newConfig = props.ConfigClass.fromJSON(config!.toJSON(), "control"); + let newConfig = ConfigClass.fromJSON(config!.toJSON(), "control"); (newConfig as lib.Config).setProp(fieldName, propName, null); setConfig(newConfig); } @@ -264,81 +363,5 @@ export default function BaseConfigTree(props: BaseConfigTreeProps) { treeData.push(treeNode); } - return <> - - Config - <Space style={{ float: "right" }}> - <Button - type="primary" - size="small" - loading={applying} - onClick={async () => { - setApplying(true); - for (let field of changedFields) { - let value = form.getFieldValue(field); - let request; - if (propsMap.has(field)) { - let [fieldName, prop] = propsMap.get(field); - request = props.setProp(fieldName, prop, value); - - } else { - request = props.setField(field, value === null ? "" : String(value)); - } - - try { - await request; - setChangedFields(changed => { - let newChanged = new Set(changed); - newChanged.delete(field); - return newChanged; - }); - - } catch (err: any) { - setErrorFields(errors => { - let newErrors = new Map(errors); - newErrors.set(field, err.message); - return newErrors; - }); - } - } - - await updateConfig(); - setApplying(false); - - if (props.onApply) { - props.onApply(); - } - }} - disabled={changedFields.size === 0} - > - Apply - </Button> - <Button - size="small" - onClick={() => { - form.resetFields(); - setChangedFields(new Set()); - setErrorFields(new Map()); - }} - > - Revert - </Button> - </Space> - - -
- changedFields.has(node.key)} - /> - -
- ; + return [propsMap, treeData] as [typeof propsMap, typeof treeData]; }