From c17f5ae5163838f01f3bf9fe844a8ec599c5af47 Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Tue, 5 May 2026 19:00:54 +0200 Subject: [PATCH] refactor(tabs): split TabIconGlyphResolver, single-source glyph pool, polish --- HellionChat/Configuration.cs | 5 +- HellionChat/Ui/TabIconGlyphResolver.cs | 78 +++++++++++++++++ HellionChat/Ui/TabIconMapping.cs | 112 ++++--------------------- 3 files changed, 94 insertions(+), 101 deletions(-) create mode 100644 HellionChat/Ui/TabIconGlyphResolver.cs diff --git a/HellionChat/Configuration.cs b/HellionChat/Configuration.cs index d005aa8..5fdb909 100755 --- a/HellionChat/Configuration.cs +++ b/HellionChat/Configuration.cs @@ -395,9 +395,8 @@ public class Tab public string Name = Language.Tab_DefaultName; // v1.2.0 — optionaler FontAwesome-Glyph-Name. Null bedeutet: - // Default-Mapping aus TabIconMapping greift (basiert auf Tab-Name - // oder erstem SelectedChannels-Eintrag). User können hier per - // Settings → Tabs einen eigenen Glyph setzen. + // Default-Mapping aus TabIconMapping greift (basiert auf Tab-Name). + // User können hier per Settings → Tabs einen eigenen Glyph setzen. public string? Icon = null; [Obsolete("Removed in favor of SelectedChannels")] diff --git a/HellionChat/Ui/TabIconGlyphResolver.cs b/HellionChat/Ui/TabIconGlyphResolver.cs new file mode 100644 index 0000000..f587c12 --- /dev/null +++ b/HellionChat/Ui/TabIconGlyphResolver.cs @@ -0,0 +1,78 @@ +namespace HellionChat.Ui; + +/// +/// Reine String-Resolver-Logik ohne Dalamud-Dependency. Bewusst in +/// eigener Datei (Dependency-Boundary auf File-Level sichtbar), damit +/// Tests (HellionChat.Tests, Microsoft.NET.Sdk ohne Dalamud-Reference) +/// sie aufrufen können, ohne dass die JIT beim Methodenaufruf die +/// Dalamud-Assembly laden muss. +/// +/// Wird im Settings-UI (T7) für die Glyph-Picker-Combobox und im +/// Render-Code indirekt über +/// verwendet. +/// +internal static class TabIconGlyphResolver +{ + /// + /// Picker-Options-Pool — Single Source of Truth für das Glyph-Set. + /// Reihenfolge ist die UI-Reihenfolge im Settings-Tab Icon-Combobox. + /// + public static readonly IReadOnlyList PickerOptions = + ["comment", "comments", "cog", "users", "user-friends", "link", + "envelope", "clock", "hashtag", "star", "heart", "bell", + "bookmark", "flag", "fire"]; + + /// + /// Glyph-Set, das überhaupt als Override akzeptiert wird. Aus + /// abgeleitet — KnownGlyphs nie + /// manuell pflegen. + /// + private static readonly HashSet KnownGlyphs = + new(PickerOptions, StringComparer.OrdinalIgnoreCase); + + /// + /// Tab-Name → Default-Glyph-Name. Tab.Name wird per Lokalisierung + /// gesetzt; wir matchen daher gegen einen Pool aus DE/EN-Synonymen. + /// + private static readonly Dictionary NameDefaults = new(StringComparer.OrdinalIgnoreCase) + { + ["allgemein"] = "comment", + ["general"] = "comment", + ["system"] = "cog", + ["free company"] = "users", + ["fc"] = "users", + ["gruppe"] = "user-friends", + ["group"] = "user-friends", + ["party"] = "user-friends", + ["linkshell"] = "link", + ["ls"] = "link", + ["cwls"] = "link", + ["tells"] = "envelope", + ["tell"] = "envelope", + }; + + /// + /// Test-Surface: Glyph-Name-Resolver ohne Dalamud-Dependency. + /// Reihenfolge: + /// 1. Tab.Icon-Override (falls gesetzt und nicht nur Whitespace): + /// a) bekannter Glyph → diesen Glyph + /// b) unbekannter Glyph → harter Fallback "hashtag" (User hat + /// bewusst etwas gesetzt, also überstimmt das die Defaults) + /// 2. Auto-Tell-Tab → "clock" + /// 3. Tab-Name-Default (-Lookup) + /// 4. Fallback "hashtag" + /// + public static string ResolveGlyphName(Tab tab) + { + if (!string.IsNullOrWhiteSpace(tab.Icon)) + return KnownGlyphs.Contains(tab.Icon) ? tab.Icon : "hashtag"; + + if (tab.IsTempTab) + return "clock"; + + if (tab.Name is { } name && NameDefaults.TryGetValue(name, out var byName)) + return byName; + + return "hashtag"; + } +} diff --git a/HellionChat/Ui/TabIconMapping.cs b/HellionChat/Ui/TabIconMapping.cs index 259dcb4..208efee 100644 --- a/HellionChat/Ui/TabIconMapping.cs +++ b/HellionChat/Ui/TabIconMapping.cs @@ -8,21 +8,24 @@ namespace HellionChat.Ui; /// User können in Settings → Tabs per Tab.Icon-Override eigene /// FontAwesome-Glyphen setzen. /// -/// Aufteilung: -/// - + -/// sind reine String-Logik und stehen aus Test-Sicht ohne -/// Dalamud-Dependency zur Verfügung (siehe Hilfsklasse -/// in derselben Datei, die ohne -/// Dalamud-Imports auskommt). -/// - liefert den FontAwesomeIcon-Enum-Wert -/// für Render-Code (T3/T5/T7) und ist daher Dalamud-abhängig. +/// Diese Klasse ist Dalamud-abhängig (FontAwesomeIcon-Enum). Die +/// reine String-Resolver-Logik liegt bewusst in +/// (eigene Datei, ohne +/// Dalamud-Imports), damit Tests sie ohne Dalamud-Reference aufrufen +/// können. /// internal static class TabIconMapping { /// /// FontAwesome-Glyph-Name → Icon-Enum-Lookup. Wird für die - /// Production-Resolve-API benötigt. Enthält nur Glyphen aus - /// . + /// Production-Resolve-API benötigt. + /// + /// INVARIANTE: Jeder Key in muss auch in + /// stehen. Wird + /// ein Glyph zu PickerOptions hinzugefügt, aber nicht hier, fällt + /// die Override-Auflösung still auf + /// zurück (degraded, kein Crash). Build-Time-Enforcement ist nicht + /// möglich, weil PickerOptions ohne Dalamud-Reference auskommt. /// private static readonly Dictionary GlyphLookup = new(StringComparer.OrdinalIgnoreCase) { @@ -43,22 +46,10 @@ internal static class TabIconMapping ["fire"] = FontAwesomeIcon.Fire, }; - /// - /// Picker-Options-Pool — Pass-through zu . - /// - public static IReadOnlyList PickerOptions => TabIconGlyphResolver.PickerOptions; - - /// - /// Pass-through zu . - /// Liegt hier nochmal als Convenience-Alias, damit Aufrufer nur - /// kennen müssen. - /// - public static string ResolveGlyphName(Tab tab) => TabIconGlyphResolver.ResolveGlyphName(tab); - /// /// Production-Surface: liefert das Icon für einen Tab. Wrapper um /// plus - /// Enum-Lookup. Wird von Render-Code (T3, T5, T7) verwendet. + /// Enum-Lookup. Wird von Render-Code (T3, T5) verwendet. /// public static FontAwesomeIcon Resolve(Tab tab) { @@ -68,78 +59,3 @@ internal static class TabIconMapping : FontAwesomeIcon.Hashtag; } } - -/// -/// Reine String-Resolver-Logik ohne Dalamud-Dependency. Bewusst -/// separat, damit Tests (HellionChat.Tests, Microsoft.NET.Sdk ohne -/// Dalamud-Reference) sie aufrufen können, ohne dass die JIT beim -/// Methodenaufruf die Dalamud-Assembly laden muss. -/// -internal static class TabIconGlyphResolver -{ - /// - /// Glyph-Set, das überhaupt als Override akzeptiert wird. Spiegelt - /// die Keys aus . - /// - private static readonly HashSet KnownGlyphs = new(StringComparer.OrdinalIgnoreCase) - { - "comment", "comments", "cog", "users", "user-friends", "link", - "envelope", "clock", "hashtag", "star", "heart", "bell", - "bookmark", "flag", "fire", - }; - - /// - /// Picker-Options-Pool. Wird im Settings-Tab Icon-Combobox angezeigt. - /// Reihenfolge ist die UI-Reihenfolge. - /// - public static readonly IReadOnlyList PickerOptions = - ["comment", "comments", "cog", "users", "user-friends", "link", - "envelope", "clock", "hashtag", "star", "heart", "bell", - "bookmark", "flag", "fire"]; - - /// - /// Tab-Name → Default-Glyph-Name. Tab.Name wird per Lokalisierung - /// gesetzt; wir matchen daher gegen einen Pool aus DE/EN-Synonymen. - /// - private static readonly Dictionary NameDefaults = new(StringComparer.OrdinalIgnoreCase) - { - ["allgemein"] = "comment", - ["general"] = "comment", - ["system"] = "cog", - ["free company"] = "users", - ["fc"] = "users", - ["gruppe"] = "user-friends", - ["group"] = "user-friends", - ["party"] = "user-friends", - ["linkshell"] = "link", - ["ls"] = "link", - ["cwls"] = "link", - ["tells"] = "envelope", - ["tell"] = "envelope", - }; - - /// - /// Test-Surface: Glyph-Name-Resolver ohne Dalamud-Dependency. - /// Reihenfolge: - /// 1. Tab.Icon-Override (falls gesetzt): - /// a) bekannter Glyph → diesen Glyph - /// b) unbekannter Glyph → harter Fallback "hashtag" (User hat - /// bewusst etwas gesetzt, also überstimmt das die Defaults) - /// 2. Auto-Tell-Tab → "clock" - /// 3. Tab-Name-Default (-Lookup) - /// 4. Fallback "hashtag" - /// - public static string ResolveGlyphName(Tab tab) - { - if (!string.IsNullOrEmpty(tab.Icon)) - return KnownGlyphs.Contains(tab.Icon) ? tab.Icon : "hashtag"; - - if (tab.IsTempTab) - return "clock"; - - if (NameDefaults.TryGetValue(tab.Name, out var byName)) - return byName; - - return "hashtag"; - } -}