Add suport for color mod setting (#568)

Implement the color-setting type in ModPack and display a color picker
for it in the Web UI. This type of setting was added in Factorio 1.1.77.
This commit is contained in:
Hornwitser authored and GitHub committed 2024-01-21 23:48:26 +01:00
1 parent d6d6c052aa
commit 72264f0c53
7 files changed
+114 -13

No files matched your search

+7
View File
@@ -1,5 +1,12 @@
# Changelog
## Version 2.0.0-alpha.15
### Features
- Added support for `color-settings` Mod settings (#568)[https://github.com/clusterio/clusterio/issues/568].
## Version 2.0.0-alpha.14
This release does not retain backwards compatibility with 2.0.0-alpha.13 meaning all parts of the cluster needs to updated at the same time.
+18 -1
View File
@@ -1097,7 +1097,13 @@ modPackCommands.add(new lib.Command({
function setModPackSettings(
modPack: lib.ModPack,
args: { boolSetting?: string[], intSetting?: string[], doubleSetting?: string[], stringSetting?: string[] },
args: {
boolSetting?: string[],
intSetting?: string[],
doubleSetting?: string[],
stringSetting?: string[],
colorSetting?: string[],
},
) {
function doSettings(settings: string[], cast: (value: string) => (string | number | boolean)) {
for (let i = 0; i + 2 < settings.length; i += 3) {
@@ -1134,6 +1140,13 @@ function setModPackSettings(
return number;
});
doSettings(args.stringSetting || [], value => value);
doSettings(args.colorSetting || [], value => {
let color = JSON.parse(value);
if (typeof color !== "object" || color === null) {
throw new lib.CommandError("color value must be a JSON object with r, g, b, a properties");
}
return color;
});
}
function setModPackMods(modPack: lib.ModPack, mods: string[] | undefined) {
@@ -1173,6 +1186,7 @@ modPackCommands.add(new lib.Command({
"int-setting": { describe: "Set int setting", array: true, nargs: 3, type: "string" },
"double-setting": { describe: "Set double setting", array: true, nargs: 3, type: "string" },
"string-setting": { describe: "Set string setting", array: true, nargs: 3, type: "string" },
"color-setting": { describe: "Set color setting", array: true, nargs: 3, type: "string" },
});
}],
handler: async function(
@@ -1186,6 +1200,7 @@ modPackCommands.add(new lib.Command({
intSetting?: string[],
doubleSetting?: string[],
stringSetting?: string[],
colorSetting?: string[],
},
control: Control,
) {
@@ -1244,6 +1259,7 @@ modPackCommands.add(new lib.Command({
"int-setting": { describe: "Set int setting", array: true, nargs: 3, type: "string" },
"double-setting": { describe: "Set double setting", array: true, nargs: 3, type: "string" },
"string-setting": { describe: "Set string setting", array: true, nargs: 3, type: "string" },
"color-setting": { describe: "Set color setting", array: true, nargs: 3, type: "string" },
"remove-setting": { describe: "Remove a setting", array: true, nargs: 2, type: "string" },
});
}],
@@ -1261,6 +1277,7 @@ modPackCommands.add(new lib.Command({
intSetting?: string[],
doubleSetting?: string[],
stringSetting?: string[],
colorSetting?: string[],
removeSetting?: string[],
},
control: Control,
+11 -3
View File
@@ -8,16 +8,24 @@ import { integerFactorioVersion } from "./version";
import type { Logger } from "../logging";
export const ModSettingColor = Type.Object({
r: Type.Number(),
g: Type.Number(),
b: Type.Number(),
a: Type.Number(),
});
export type ModSettingColor = Static<typeof ModSettingColor>;
/**
* A setting for a mod.
*/
export interface ModSetting {
/** Value of the given mod setting. */
value: boolean | number | string;
value: boolean | number | string | ModSettingColor;
}
const ModSettingJsonSchema = Type.Object({
"value": Type.Union([Type.Boolean(), Type.Number(), Type.String()]),
"value": Type.Union([Type.Boolean(), Type.Number(), Type.String(), ModSettingColor]),
});
const ModSettingsJsonSchema = Type.Record(Type.String(), ModSettingJsonSchema);
@@ -335,7 +343,7 @@ export default class ModPack {
* @param logger - Logger used to report warnings on.
*/
fillDefaultSettings(settingPrototypes: Record<string, object>, logger: Logger) {
const knownTypes = ["bool-setting", "int-setting", "double-setting", "string-setting"];
const knownTypes = ["bool-setting", "int-setting", "double-setting", "string-setting", "color-setting"];
let prototypes = Object.entries(settingPrototypes)
.filter(([type, _]) => knownTypes.includes(type))
.flatMap(([_, settings]) => Object.values(settings))
+1 -1
View File
@@ -5,7 +5,7 @@
*/
export { default as ExportManifest } from "./ExportManifest";
export { default as ModInfo } from "./ModInfo";
export { default as ModPack, ModSetting, ModRecord } from "./ModPack";
export { default as ModPack, ModSetting, ModSettingColor, ModRecord } from "./ModPack";
export { default as ModuleInfo } from "./ModuleInfo";
export { default as Permission } from "./Permission";
export { default as PlayerStats } from "./PlayerStats";
@@ -1,7 +1,7 @@
import React, { Fragment, memo, useCallback, useEffect, useContext, useRef, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import {
Button, Card, Checkbox, Col, ConfigProvider, Descriptions, Form, Input, Pagination,
Button, Card, ColorPicker, Checkbox, Col, ConfigProvider, Descriptions, Form, Input, Pagination,
Popconfirm, Row, Table, Tag, Typography, Select, Skeleton, Space, Spin, Switch, Modal, Tooltip,
TablePaginationConfig,
} from "antd";
@@ -404,7 +404,7 @@ function hasChange(changes: ModChange[], field:Partial<ModChange>) {
}
// picks out the value property from settings.
function pickValue(map: Map<string, lib.ModSetting>): [string, string|number|boolean][] {
function pickValue(map: Map<string, lib.ModSetting>): [string, lib.ModSetting["value"]][] {
return [...map].map(([name, value]) => [name, value.value]);
}
@@ -420,6 +420,25 @@ function groupToMap(array: any[], fn: (...args:any) => any) {
return map;
}
function InputColor(props: {
value: lib.ModSettingColor,
onChange: (value: lib.ModSettingColor) => void,
}) {
const value = props.value;
return <ColorPicker
defaultFormat="rgb"
defaultValue={`rgba(${value.r * 255}, ${value.g * 255}, ${value.b * 255}, ${value.a}`}
onChangeComplete={(color) => {
const rgb = color.toRgb();
props.onChange({
r: rgb.r / 255,
g: rgb.g / 255,
b: rgb.b / 255,
a: rgb.a,
});
}}
/>;
}
type SettingsTableFieldProps = {
name: string;
type: string;
@@ -431,11 +450,15 @@ type SettingsTableFieldProps = {
const SettingsTableField = memo((props: SettingsTableFieldProps) => {
const name = props.name;
const isBoolean = props.type === "bool-setting";
const isColor = props.type === "color-setting";
const isNumber = ["int-setting", "double-setting"].includes(props.type);
const isSelect = Boolean(props.allowedValues);
let input;
if (isBoolean) {
input = <Checkbox />;
} else if (isColor) {
const PartialInputColor = InputColor as () => React.JSX.Element;
input = <PartialInputColor />;
} else if (isSelect) {
const options = Object.values(props.allowedValues).map(value => ({
value,
@@ -484,7 +507,7 @@ function SettingsTable(props: SettingsTableProps) {
.map(mod => [mod.name, mod.title])
);
const types = ["bool-setting", "int-setting", "double-setting", "string-setting"];
const types = ["bool-setting", "int-setting", "double-setting", "string-setting", "color-setting"];
let prototypes = Object.entries(props.prototypes)
.filter(([type, _]) => types.includes(type))
.flatMap<any>(([_, settingPrototypes]: [string, any]) => Object.values(settingPrototypes))
@@ -492,7 +515,7 @@ function SettingsTable(props: SettingsTableProps) {
function controls(
scope: "startup"|"runtime-global"|"runtime-per-user",
storedFields: [string, string|number|boolean][]
storedFields: [string, lib.ModSetting["value"]][]
) {
let fields = new Map(prototypes
.filter(p => p.setting_type === scope)
@@ -508,6 +531,8 @@ function SettingsTable(props: SettingsTableProps) {
type = "double-setting";
} else if (typeof value === "string") {
type = "string-setting";
} else if (typeof value === "object" && "r" in value) {
type = "color-setting";
} else {
throw Error(`Unhandled setting type ${typeof value}`);
}
+9 -3
View File
@@ -81,6 +81,10 @@ async function getUsers() {
return new Map(users.map(user => [user.name, user]));
}
function jsonArg(value) {
return `"${JSON.stringify(value).replace(/"/g, process.platform === "win32" ? '""' : '\\"')}"`;
}
describe("Integration of Clusterio", function() {
describe("clusteriocontroller", function() {
describe("bootstrap generate-user-token", function() {
@@ -502,8 +506,7 @@ describe("Integration of Clusterio", function() {
];
for (let [prop, value] of testConfigs) {
value = `"${JSON.stringify(value).replace(/"/g, process.platform === "win32" ? '""' : '\\"')}"`;
let args = `test factorio.settings ${prop} ${value}`;
let args = `test factorio.settings ${prop} ${jsonArg(value)}`;
await execCtl(`instance config set-prop ${args}`);
}
@@ -876,6 +879,7 @@ describe("Integration of Clusterio", function() {
assert(modPacks.some(modPack => modPack.name === "empty-pack"), "created pack is not in the list");
});
it("should allow setting all fields", async function() {
const color = { r: 1, g: 0, b: 1, a: 0 };
await execCtl(
"mod-pack create full-pack 0.17.59 " +
"--description Description " +
@@ -883,7 +887,8 @@ describe("Integration of Clusterio", function() {
"--bool-setting startup MyBool true " +
"--int-setting runtime-global MyInt 1235 " +
"--double-setting runtime-global MyDouble 12.25 " +
"--string-setting runtime-per-user MyString a-string"
"--string-setting runtime-per-user MyString a-string " +
`--color-setting runtime-per-user MyColor ${jsonArg(color)}`
);
let modPacks = await getControl().send(new lib.ModPackListRequest());
let modPack = modPacks.find(entry => entry.name === "full-pack");
@@ -898,6 +903,7 @@ describe("Integration of Clusterio", function() {
reference.settings["runtime-global"].set("MyInt", { value: 1235 });
reference.settings["runtime-global"].set("MyDouble", { value: 12.25 });
reference.settings["runtime-per-user"].set("MyString", { value: "a-string" });
reference.settings["runtime-per-user"].set("MyColor", { value: color });
reference.updatedAtMs = modPack.updatedAtMs;
assert.deepEqual(modPack, reference);
});
+39 -1
View File
@@ -39,6 +39,7 @@ describe("lib/data/ModPack", function() {
},
"runtime-per-user": {
"string-setting": { "value": "a string" },
"color-setting": { "value": { "r": 1, "g": 1, "b": 0, "a": 1 } },
},
}}));
check(ModPack.fromJSON({ export_manifest: { assets: { setting: "settings.json" }}}));
@@ -60,6 +61,7 @@ describe("lib/data/ModPack", function() {
},
"runtime-per-user": {
"string-setting": { "value": "a string" },
"color-setting": { "value": { "r": 1, "g": 1, "b": 0, "a": 1 } },
},
},
export_manifest: { assets: { setting: "settings.json" }},
@@ -101,6 +103,14 @@ describe("lib/data/ModPack", function() {
default_value: "a string",
},
},
"color-setting": {
"color": {
name: "color",
type: "color-setting",
setting_type: "runtime-per-user",
default_value: { "r": 1, "g": 1, "b": 1, "a": 1 },
},
},
};
const mockLogger = { warn: () => {} };
it("should fill in defaults for settings", function() {
@@ -115,6 +125,7 @@ describe("lib/data/ModPack", function() {
},
"runtime-per-user": {
"string": { "value": "a string" },
"color": { "value": { "r": 1, "g": 1, "b": 1, "a": 1 } },
},
});
});
@@ -128,6 +139,7 @@ describe("lib/data/ModPack", function() {
},
"runtime-per-user": {
"string": { "value": "spam" },
"color": { "value": { "r": 0, "g": 0.5, "b": 1, "a": 1 } },
},
}});
pack.fillDefaultSettings(prototypes, mockLogger);
@@ -140,6 +152,7 @@ describe("lib/data/ModPack", function() {
},
"runtime-per-user": {
"string": { "value": "spam" },
"color": { "value": { "r": 0, "g": 0.5, "b": 1, "a": 1 } },
},
});
});
@@ -204,6 +217,7 @@ describe("lib/data/ModPack", function() {
},
"runtime-per-user": {
"string-setting": { "value": "a string" },
"color-setting": { "value": { "r": 1, "g": 0.5, "b": 0, "a": 1 } },
},
}});
@@ -248,7 +262,7 @@ describe("lib/data/ModPack", function() {
...istr("runtime-per-user"), // name
Uint8Array.from([5, 0]), // dictionary
new Uint8Array(Uint32Array.from([1]).buffer), // items
new Uint8Array(Uint32Array.from([2]).buffer), // items
...istr("string-setting"), // name
Uint8Array.from([5, 0]), // dictinary
@@ -257,6 +271,30 @@ describe("lib/data/ModPack", function() {
...istr("value"), // name
Uint8Array.from([3, 0, 0, "a string".length]), // string
Buffer.from("a string"),
...istr("color-setting"), // name
Uint8Array.from([5, 0]), // dictinary
new Uint8Array(Uint32Array.from([1]).buffer), // items
...istr("value"), // name
Uint8Array.from([5, 0]), // dictinary
new Uint8Array(Uint32Array.from([4]).buffer), // items
...istr("r"), // name
Uint8Array.from([2, 0]), // number
new Uint8Array(Float64Array.from([1]).buffer),
...istr("g"), // name
Uint8Array.from([2, 0]), // number
new Uint8Array(Float64Array.from([0.5]).buffer),
...istr("b"), // name
Uint8Array.from([2, 0]), // number
new Uint8Array(Float64Array.from([0]).buffer),
...istr("a"), // name
Uint8Array.from([2, 0]), // number
new Uint8Array(Float64Array.from([1]).buffer),
])
);
/* eslint-enable indent */