feat: add configurable operator appearance and layout

This commit is contained in:
2026-07-23 11:14:10 +09:00
parent cf117de144
commit f43483533a
34 changed files with 5942 additions and 740 deletions

View File

@@ -358,7 +358,10 @@ public sealed class LegacyOperatorSettingsTests
sceneDirectory,
resourceDirectory,
backgroundDirectory,
NavigationExpanded: false);
NavigationExpanded: false,
ColorTheme: LegacyOperatorColorTheme.Dark,
ViewMode: LegacyOperatorViewMode.Cards,
StartWorkspace: LegacyOperatorStartWorkspace.LastWorkspace);
var firstSave = store.Save(settings);
var secondSave = store.Save(settings with { NavigationExpanded = true });
@@ -370,6 +373,11 @@ public sealed class LegacyOperatorSettingsTests
Assert.Null(secondSave.WarningMessage);
Assert.True(loaded.LoadedFromDisk);
Assert.True(loaded.Settings.NavigationExpanded);
Assert.Equal(LegacyOperatorColorTheme.Dark, loaded.Settings.ColorTheme);
Assert.Equal(LegacyOperatorViewMode.Cards, loaded.Settings.ViewMode);
Assert.Equal(
LegacyOperatorStartWorkspace.LastWorkspace,
loaded.Settings.StartWorkspace);
Assert.Equal(Path.GetFullPath(sceneDirectory), loaded.Settings.SceneDirectory);
Assert.Equal(Path.GetFullPath(resourceDirectory), loaded.Settings.ResourceDirectory);
Assert.Equal(
@@ -389,9 +397,20 @@ public sealed class LegacyOperatorSettingsTests
"sceneDirectory",
"resourceDirectory",
"backgroundDirectory",
"navigationExpanded"
"navigationExpanded",
"colorTheme",
"viewMode",
"startWorkspace"
],
names);
Assert.Equal(
LegacyOperatorSettings.CurrentSchemaVersion,
document.RootElement.GetProperty("schemaVersion").GetInt32());
Assert.Equal("dark", document.RootElement.GetProperty("colorTheme").GetString());
Assert.Equal("cards", document.RootElement.GetProperty("viewMode").GetString());
Assert.Equal(
"lastWorkspace",
document.RootElement.GetProperty("startWorkspace").GetString());
Assert.DoesNotContain(
document.RootElement.EnumerateObject(),
property => property.Name.Contains("password", StringComparison.OrdinalIgnoreCase) ||
@@ -399,13 +418,38 @@ public sealed class LegacyOperatorSettingsTests
property.Name.Contains("live", StringComparison.OrdinalIgnoreCase));
}
[Fact]
public void Load_LegacySchemaUsesClosedAppearanceDefaults()
{
using var directory = new TemporaryDirectory();
var configurationPath = Path.Combine(directory.Path, "settings.json");
File.WriteAllText(
configurationPath,
"{\"schemaVersion\":1,\"sceneDirectory\":null,\"resourceDirectory\":null,\"backgroundDirectory\":null,\"navigationExpanded\":false}",
new UTF8Encoding(false));
var result = new LegacyOperatorSettingsStore(configurationPath).Load();
Assert.True(result.LoadedFromDisk);
Assert.Equal(LegacyOperatorSettingsStoreFailure.None, result.Failure);
Assert.False(result.Settings.NavigationExpanded);
Assert.Equal(LegacyOperatorColorTheme.System, result.Settings.ColorTheme);
Assert.Equal(LegacyOperatorViewMode.Automatic, result.Settings.ViewMode);
Assert.Equal(
LegacyOperatorStartWorkspace.StockCut,
result.Settings.StartWorkspace);
}
[Theory]
[InlineData("{}", LegacyOperatorSettingsStoreFailure.InvalidJson)]
[InlineData("{\"schemaVersion\":2,\"sceneDirectory\":null,\"resourceDirectory\":null,\"backgroundDirectory\":null,\"navigationExpanded\":true}", LegacyOperatorSettingsStoreFailure.UnsupportedSchema)]
[InlineData("{\"schemaVersion\":3,\"sceneDirectory\":null,\"resourceDirectory\":null,\"backgroundDirectory\":null,\"navigationExpanded\":true}", LegacyOperatorSettingsStoreFailure.UnsupportedSchema)]
[InlineData("{\"schemaVersion\":1,\"sceneDirectory\":null,\"resourceDirectory\":null,\"backgroundDirectory\":null,\"navigationExpanded\":true,\"unexpected\":1}", LegacyOperatorSettingsStoreFailure.InvalidJson)]
[InlineData("{\"schemaVersion\":1,\"schemaVersion\":1,\"sceneDirectory\":null,\"resourceDirectory\":null,\"backgroundDirectory\":null,\"navigationExpanded\":true}", LegacyOperatorSettingsStoreFailure.InvalidJson)]
[InlineData("{\"SchemaVersion\":1,\"sceneDirectory\":null,\"resourceDirectory\":null,\"backgroundDirectory\":null,\"navigationExpanded\":true}", LegacyOperatorSettingsStoreFailure.InvalidJson)]
[InlineData("{\"schemaVersion\":1,\"sceneDirectory\":\"relative\",\"resourceDirectory\":null,\"backgroundDirectory\":null,\"navigationExpanded\":true}", LegacyOperatorSettingsStoreFailure.InvalidSettings)]
[InlineData("{\"schemaVersion\":2,\"sceneDirectory\":null,\"resourceDirectory\":null,\"backgroundDirectory\":null,\"navigationExpanded\":true,\"colorTheme\":\"mbn\",\"viewMode\":\"automatic\",\"startWorkspace\":\"stockCut\"}", LegacyOperatorSettingsStoreFailure.InvalidJson)]
[InlineData("{\"schemaVersion\":2,\"sceneDirectory\":null,\"resourceDirectory\":null,\"backgroundDirectory\":null,\"navigationExpanded\":true,\"colorTheme\":\"system\",\"viewMode\":\"grid\",\"startWorkspace\":\"stockCut\"}", LegacyOperatorSettingsStoreFailure.InvalidJson)]
[InlineData("{\"schemaVersion\":2,\"sceneDirectory\":null,\"resourceDirectory\":null,\"backgroundDirectory\":null,\"navigationExpanded\":true,\"colorTheme\":\"system\",\"viewMode\":\"automatic\",\"startWorkspace\":\"other\"}", LegacyOperatorSettingsStoreFailure.InvalidJson)]
public void Load_StrictMalformedOrUnsafeJsonFailsClosed(
string json,
LegacyOperatorSettingsStoreFailure expectedFailure)
@@ -463,6 +507,31 @@ public sealed class LegacyOperatorSettingsTests
AssertPathFree(result.WarningMessage, settings.SceneDirectory!);
}
[Theory]
[InlineData(99, 0, 0)]
[InlineData(0, 99, 0)]
[InlineData(0, 0, 99)]
public void Save_RejectsUndefinedAppearanceValuesWithoutCreatingAConfigurationFile(
int colorTheme,
int viewMode,
int startWorkspace)
{
using var directory = new TemporaryDirectory();
var configurationPath = Path.Combine(directory.Path, "settings.json");
var settings = LegacyOperatorSettings.Default with
{
ColorTheme = (LegacyOperatorColorTheme)colorTheme,
ViewMode = (LegacyOperatorViewMode)viewMode,
StartWorkspace = (LegacyOperatorStartWorkspace)startWorkspace
};
var result = new LegacyOperatorSettingsStore(configurationPath).Save(settings);
Assert.False(result.Succeeded);
Assert.Equal(LegacyOperatorSettingsStoreFailure.InvalidSettings, result.Failure);
Assert.False(File.Exists(configurationPath));
}
[Fact]
public void AbbreviateForDisplay_IsBoundedAndNeverReturnsInvalidInput()
{