diff --git a/packages/web_ui/src/components/BaseConfigTree.tsx b/packages/web_ui/src/components/BaseConfigTree.tsx index a17a11dd..00f5962d 100644 --- a/packages/web_ui/src/components/BaseConfigTree.tsx +++ b/packages/web_ui/src/components/BaseConfigTree.tsx @@ -9,6 +9,7 @@ import * as lib from "@clusterio/lib"; import ControlContext from "./ControlContext"; import { InputComponent, InputComponentProps } from "../BaseWebPlugin"; import type { Control } from "../util/websocket"; +import { notifyErrorHandler } from "../util/notify"; const { Title } = Typography; @@ -67,8 +68,7 @@ function renderInput(inputComponents: Record, def: lib.F type BaseConfigTreeProps = { ConfigClass: typeof lib.ControllerConfig | typeof lib.HostConfig | typeof lib.InstanceConfig; retrieveConfig: () => Promise; - setField: (field:string, value:any) => Promise; - setProp: (field:string, prop:string, value:any) => Promise; + setConfig: (fields: Record>) => Promise; id?: number; onApply?: () => void; available?: boolean; @@ -157,32 +157,25 @@ export default function BaseConfigTree(props: BaseConfigTreeProps) { loading={applying} onClick={async () => { setApplying(true); - for (let field of changedFields) { - let value = form.getFieldValue(field); - let request; + const fields: Record> = {}; + for (const field of changedFields) { + const value = form.getFieldValue(field); if (propsMap.has(field)) { - let [fieldName, prop] = propsMap.get(field)!; - request = props.setProp(fieldName, prop, value); + const [fieldName, prop] = propsMap.get(field)!; + const fieldValue = (fields[fieldName] ?? {}) as Record; + fields[fieldName] = fieldValue; + fieldValue[prop] = value ? JSON.parse(value) : undefined; } else { - request = props.setField(field, value === null ? "" : String(value)); + fields[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; - }); - } + try { + await props.setConfig(fields); + setChangedFields(new Set()); + } catch (err: any) { + notifyErrorHandler(err.message); } await updateConfig(); @@ -325,26 +318,23 @@ function computeTreeData( } if (!def.validator) { return; } - - // TODO this needs to use stage and stageProp to avoid deadlocks - const staging = config.staging; - staging.setProp(fieldName, prop, fieldValue); + config.stageProp(fieldName, prop, fieldValue); for (const dependsOn of def.dependsOn ?? []) { const dependsOnDef = ConfigClass.fieldDefinitions[dependsOn as any] as typeof def; try { if (propsMap.has(dependsOn)) { const [name, otherProp] = propsMap.get(dependsOn)!; - staging.setProp(name, otherProp, JSON.parse(getFieldValue(dependsOn))); + config.stageProp(name, otherProp, JSON.parse(getFieldValue(dependsOn))); } else if (dependsOnDef.type !== "object") { - staging.set(dependsOn, getFieldValue(dependsOn)); + config.stage(dependsOn, getFieldValue(dependsOn)); } } catch (err) { throw new Error(`Failed to fetch field: ${dependsOn}`); } } - def.validator(fieldValue, staging); + def.validator(fieldValue, config.staging); }})]} > @@ -418,26 +408,23 @@ function computeTreeData( rules={[({getFieldValue}) => ({ validator: async (_, fieldValue) => { if (!def.validator) { return; } - - // TODO this needs to use stage and stageProp to avoid deadlocks - const staging = config.staging; - staging.set(fieldName, fieldValue); + config.stage(fieldName, fieldValue); for (const dependsOn of def.dependsOn ?? []) { const dependsOnDef = ConfigClass.fieldDefinitions[dependsOn as any] as typeof def; try { if (propsMap.has(dependsOn)) { const [name, prop] = propsMap.get(dependsOn)!; - staging.setProp(name, prop, JSON.parse(getFieldValue(dependsOn))); + config.stageProp(name, prop, JSON.parse(getFieldValue(dependsOn))); } else if (dependsOnDef.type !== "object") { - staging.set(dependsOn, getFieldValue(dependsOn)); + config.stage(dependsOn, getFieldValue(dependsOn)); } } catch (err) { throw new Error(`Failed to fetch field: ${dependsOn}`); } } - def.validator(fieldValue, staging); + def.validator(fieldValue, config.staging); }, })]} > diff --git a/packages/web_ui/src/components/ControllerConfigTree.tsx b/packages/web_ui/src/components/ControllerConfigTree.tsx index a369fab7..aecd5c03 100644 --- a/packages/web_ui/src/components/ControllerConfigTree.tsx +++ b/packages/web_ui/src/components/ControllerConfigTree.tsx @@ -13,30 +13,13 @@ export default function ControllerConfigTree() { return await control.send(new lib.ControllerConfigGetRequest()); } - async function setField(field: string, value: any) { - await control.send(new lib.ControllerConfigSetFieldRequest( - field, - String(value), - )); - } - - async function setProp(field: string, prop: string, value: any) { - if (value) { - try { - value = JSON.parse(value); - } catch (err) { - return; - } - } else { - value = undefined; - } - await control.send(new lib.ControllerConfigSetPropRequest(field, prop, value)); + async function setConfig(fields: Record>) { + await control.send(new lib.ControllerConfigSetRequest(fields)); } return ; } diff --git a/packages/web_ui/src/components/HostConfigTree.tsx b/packages/web_ui/src/components/HostConfigTree.tsx index aef520f0..caf8d4a1 100644 --- a/packages/web_ui/src/components/HostConfigTree.tsx +++ b/packages/web_ui/src/components/HostConfigTree.tsx @@ -13,28 +13,14 @@ export default function HostConfigTree(props: { id: number, available: boolean } return await control.sendTo({ hostId: props.id }, new lib.HostConfigGetRequest()); } - async function setField(field: string, value: any) { - await control.sendTo({ hostId: props.id }, new lib.HostConfigSetFieldRequest(field, value)); - } - - async function setProp(field: string, prop: string, value: any) { - if (value) { - try { - value = JSON.parse(value); - } catch (err) { - return; - } - } else { - value = undefined; - } - await control.sendTo({ hostId: props.id }, new lib.HostConfigSetPropRequest(field, prop, value)); + async function setConfig(fields: Record>) { + await control.send(new lib.HostConfigSetRequest(fields)); } return ; diff --git a/packages/web_ui/src/components/InstanceConfigTree.tsx b/packages/web_ui/src/components/InstanceConfigTree.tsx index c9a45530..ff028ddd 100644 --- a/packages/web_ui/src/components/InstanceConfigTree.tsx +++ b/packages/web_ui/src/components/InstanceConfigTree.tsx @@ -17,28 +17,14 @@ export default function InstanceConfigTree(props: InstanceConfigTreeProps) { return await control.send(new lib.InstanceConfigGetRequest(props.id)); } - async function setField(field: string, value: any) { - await control.send(new lib.InstanceConfigSetFieldRequest(props.id, field, value)); - } - - async function setProp(field: string, prop: string, value: any) { - if (value) { - try { - value = JSON.parse(value); - } catch (err) { - return; - } - } else { - value = undefined; - } - await control.send(new lib.InstanceConfigSetPropRequest(props.id, field, prop, value)); + async function setConfig(fields: Record>) { + await control.send(new lib.InstanceConfigSetRequest(props.id, fields)); } return ; }