diff --git a/packages/web_ui/src/components/ModPackViewPage.jsx b/packages/web_ui/src/components/ModPackViewPage.jsx index 0d2a426f..69393489 100644 --- a/packages/web_ui/src/components/ModPackViewPage.jsx +++ b/packages/web_ui/src/components/ModPackViewPage.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useContext, useRef, useState } from "react"; +import React, { Fragment, useEffect, useContext, useRef, useState } from "react"; import { useHistory, useParams } from "react-router-dom"; import { Button, Card, Checkbox, Col, ConfigProvider, Descriptions, Form, Input, PageHeader, Pagination, @@ -353,49 +353,115 @@ function pickValue(map) { return [...map].map(([name, value]) => [name, value.value]); } -function SettingsTable(props) { - function controls(scope, fields) { - return fields.map(([fieldName, fieldValue]) => { - const isBoolean = typeof fieldValue === "boolean"; - const isNumber = typeof fieldValue === "number"; +function groupToMap(array, fn) { + const map = new Map(); + for (let index = 0; index < array.length; index++) { + let key = fn(array[index], index, array); + if (!map.has(key)) { + map.set(key, []); + } + map.get(key).push(array[index]); + } + return map; +} - return `${mod.name}_${mod.version}`)); + const modTitles = new Map(modList + .filter(mod => modsInPack.has(`${mod.name}_${mod.version}`)) + .map(mod => [`${mod.name}`, mod.title]) + ); + + const types = ["bool-setting", "int-setting", "double-setting", "string-setting"]; + let prototypes = Object.entries(props.prototypes) + .filter(([type, _]) => types.includes(type)) + .flatMap(([_, settingPrototypes]) => Object.values(settingPrototypes)) + ; + + function controls(scope, storedFields) { + let fields = new Map(prototypes + .filter(p => p.setting_type === scope) + .sort((a, b) => strcmp(a.mod, b.mod) || strcmp(a.order || "", b.order || "") || strcmp(a.name, b.name)) + .map(p => [p.name, p]) + ); + for (let [name, value] of storedFields) { + if (!fields.has(name)) { + let type; + if (typeof value === "boolean") { + type = "bool-setting"; + } else if (typeof value === "number") { + type = "double-setting"; + } else if (typeof value === "string") { + type = "string-setting"; + } else { + throw Error(`Unhandled setting type ${typeof value}`); } - > - {isBoolean ? : } - ; - }); + fields.set(name, { mod: "Unknown Fields", name, setting_type: scope, type }); + } + } + let fieldsByMod = groupToMap([...fields.values()], prototype => prototype.mod); + return [...fieldsByMod].map(([mod, fieldsForMod]) => + + {props.locale.get(`mod-name.${mod}`) || modTitles.get(mod) || mod} + + {[...fieldsForMod].map(prototype => { + const name = prototype.name; + const isBoolean = prototype.type === "bool-setting"; + const isNumber = ["int-setting", "double-setting"].includes(prototype.type); + const isSelect = Boolean(prototype.allowed_values); + let input; + if (isBoolean) { + input = ; + } else if (isSelect) { + const options = Object.values(prototype.allowed_values).map(value => ({ + value, + label: props.locale.get(`string-mod-setting.${name}-${value}`) || value, + })); + input = ; + } + + return + {input} + ; + })} + ); } return <> - Use clusterioctl to add and remove fields. - Startup + Run export data on an instance to update available fields. + Startup {controls("startup", pickValue(props.modPack.settings["startup"]))} - Map + Map These only apply for new saves. {controls("runtime-global", pickValue(props.modPack.settings["runtime-global"]))} - Per Player + Per Player Default settings for new players, these only apply for new saves. {controls("runtime-per-user", pickValue(props.modPack.settings["runtime-per-user"]))} ; @@ -457,6 +523,33 @@ function ExportButton(props) { ; } +function useExportedAsset(modPack, asset) { + let [assetData, setAssetData] = useState(asset === "locale" ? new Map() : {}); + let assetFilename; + if (modPack instanceof libData.ModPack && modPack.exportManifest && modPack.exportManifest.assets[asset]) { + assetFilename = modPack.exportManifest.assets[asset]; + } + useEffect(() => { + async function load() { + if (!assetFilename) { + return; + } + + let response = await fetch(`${staticRoot}static/${assetFilename}`); + if (response.ok) { + let data = await response.json(); + setAssetData(asset === "locale" ? new Map(data) : data); + } else { + logger.error(`Error loading mod pack asset "${asset}" (${response.status}): ${await response.text()}`); + } + } + + load().catch(notifyErrorHandler(`Loading mod pack asset "${asset}" failed`)); + }, [assetFilename]); + + return assetData; +} + export default function ModPackViewPage() { let account = useAccount(); let history = useHistory(); @@ -466,6 +559,8 @@ export default function ModPackViewPage() { const [form] = Form.useForm(); let [modPack] = useModPack(modPackId); + let prototypes = useExportedAsset(modPack, "settings"); + let locale = useExportedAsset(modPack, "locale"); let [changes, setChanges] = useState([]); let [modifiedModPack, setModifiedModPack] = useState(modPack); let syncTimeout = useRef(); @@ -657,7 +752,14 @@ export default function ModPackViewPage() { - +