From 0b36c1bdc29003518eabc19164d79b082100353c Mon Sep 17 00:00:00 2001 From: cake Date: Sun, 30 Nov 2025 00:00:39 +0100 Subject: [PATCH] Changed syncshell admin user list, added filter and copy. show creation date while hoving over text. --- LightlessSync/UI/SyncshellAdminUI.cs | 423 ++++++++++++++++++--------- 1 file changed, 279 insertions(+), 144 deletions(-) diff --git a/LightlessSync/UI/SyncshellAdminUI.cs b/LightlessSync/UI/SyncshellAdminUI.cs index 27da617..342e55b 100644 --- a/LightlessSync/UI/SyncshellAdminUI.cs +++ b/LightlessSync/UI/SyncshellAdminUI.cs @@ -16,6 +16,7 @@ using LightlessSync.WebAPI; using Microsoft.Extensions.Logging; using SixLabors.ImageSharp; using System.Globalization; +using System.Numerics; namespace LightlessSync.UI; @@ -30,6 +31,7 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase private readonly PairUiService _pairUiService; private List _bannedUsers = []; private LightlessGroupProfileData? _profileData = null; + private string _userSearchFilter = string.Empty; private IDalamudTextureWrap? _pfpTextureWrap; private string _profileDescription = string.Empty; private int _multiInvites; @@ -84,10 +86,22 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase } _profileData = _lightlessProfileManager.GetLightlessGroupProfile(GroupFullInfo.Group); - using var id = ImRaii.PushId("syncshell_admin_" + GroupFullInfo.GID); + using (_uiSharedService.UidFont.Push()) - _uiSharedService.UnderlinedBigText(GroupFullInfo.GroupAliasOrGID + " Administrative Panel", UIColors.Get("LightlessBlue")); + { + var headerText = $"{GroupFullInfo.GroupAliasOrGID} Administrative Panel"; + _uiSharedService.UnderlinedBigText(headerText, UIColors.Get("LightlessBlue")); + } + + if (ImGui.IsItemHovered()) + { + ImGui.BeginTooltip(); + ImGui.Text($"{GroupFullInfo.GroupAliasOrGID} is created at:"); + ImGui.Separator(); + ImGui.Text(text: GroupFullInfo.Group.CreatedAt?.ToString("yyyy-MM-dd HH:mm:ss 'UTC'")); + ImGui.EndTooltip(); + } ImGui.Separator(); var perm = GroupFullInfo.GroupPermissions; @@ -264,151 +278,12 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase } else { - var tableFlags = ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingStretchProp; - if (pairs.Count > 10) tableFlags |= ImGuiTableFlags.ScrollY; - using var table = ImRaii.Table("userList#" + GroupFullInfo.Group.AliasOrGID, 3, tableFlags); - if (table) - { - ImGui.TableSetupColumn("Alias/UID/Note", ImGuiTableColumnFlags.None, 4); - ImGui.TableSetupColumn("Flags", ImGuiTableColumnFlags.None, 1); - ImGui.TableSetupColumn("Actions", ImGuiTableColumnFlags.None, 3); - ImGui.TableHeadersRow(); - - var groupedPairs = new Dictionary(pairs.Select(p => new KeyValuePair(p, - GroupFullInfo.GroupPairUserInfos.TryGetValue(p.UserData.UID, out GroupPairUserInfo value) ? value : null))); - - foreach (var pair in groupedPairs.OrderBy(p => - { - if (p.Value == null) return 10; - if (string.Equals(p.Key.UserData.UID, GroupFullInfo.OwnerUID, StringComparison.Ordinal)) return 0; - if (p.Value.Value.IsModerator()) return 1; - if (p.Value.Value.IsPinned()) return 2; - return 10; - }).ThenBy(p => p.Key.GetNote() ?? p.Key.UserData.AliasOrUID, StringComparer.OrdinalIgnoreCase)) - { - using var tableId = ImRaii.PushId("userTable_" + pair.Key.UserData.UID); - var isUserOwner = string.Equals(pair.Key.UserData.UID, GroupFullInfo.OwnerUID, StringComparison.Ordinal); - - ImGui.TableNextColumn(); // alias/uid/note - var note = pair.Key.GetNote(); - var text = note == null ? pair.Key.UserData.AliasOrUID : note + " (" + pair.Key.UserData.AliasOrUID + ")"; - ImGui.AlignTextToFramePadding(); - var boolcolor = UiSharedService.GetBoolColor(pair.Key.IsOnline); - UiSharedService.ColorText(text, boolcolor); - if (!string.IsNullOrEmpty(pair.Key.PlayerName)) - { - UiSharedService.AttachToolTip(pair.Key.PlayerName); - ImGui.SameLine(); - } - - ImGui.TableNextColumn(); // special flags - if (pair.Value != null && (pair.Value.Value.IsModerator() || pair.Value.Value.IsPinned() || isUserOwner)) - { - if (pair.Value.Value.IsModerator()) - { - _uiSharedService.IconText(FontAwesomeIcon.UserShield, UIColors.Get("LightlessPurple")); - UiSharedService.AttachToolTip("Moderator"); - } - if (pair.Value.Value.IsPinned() && !isUserOwner) - { - _uiSharedService.IconText(FontAwesomeIcon.Thumbtack); - UiSharedService.AttachToolTip("Pinned"); - } - if (isUserOwner) - { - _uiSharedService.IconText(FontAwesomeIcon.Crown, UIColors.Get("LightlessYellow")); - UiSharedService.AttachToolTip("Owner"); - } - } - else - { - _uiSharedService.IconText(FontAwesomeIcon.None); - } - - ImGui.TableNextColumn(); // actions - if (_isOwner) - { - using (ImRaii.PushColor(ImGuiCol.Text, UIColors.Get("LightlessYellow"))) - { - using (ImRaii.Disabled(!UiSharedService.ShiftPressed())) - { - if (_uiSharedService.IconButton(FontAwesomeIcon.Crown)) - { - _ = _apiController.GroupChangeOwnership(new(GroupFullInfo.Group, pair.Key.UserData)); - IsOpen = false; - } - } - - } - UiSharedService.AttachToolTip("Hold SHIFT and click to transfer ownership of this Syncshell to " - + (pair.Key.UserData.AliasOrUID) + Environment.NewLine + "WARNING: This action is irreversible and will close screen."); - ImGui.SameLine(); - - using (ImRaii.PushColor(ImGuiCol.Text, pair.Value != null && pair.Value.Value.IsModerator() ? UIColors.Get("DimRed") : UIColors.Get("PairBlue"))) - { - if (_uiSharedService.IconButton(FontAwesomeIcon.UserShield)) - { - GroupPairUserInfo userInfo = pair.Value ?? GroupPairUserInfo.None; - - userInfo.SetModerator(!userInfo.IsModerator()); - - _ = _apiController.GroupSetUserInfo(new GroupPairUserInfoDto(GroupFullInfo.Group, pair.Key.UserData, userInfo)); - } - } - UiSharedService.AttachToolTip(pair.Value != null && pair.Value.Value.IsModerator() ? "Demod user" : "Mod user"); - ImGui.SameLine(); - } - - if (pair.Value == null || pair.Value != null && !pair.Value.Value.IsModerator() && !isUserOwner) - { - using (ImRaii.PushColor(ImGuiCol.Text, pair.Value != null && pair.Value.Value.IsPinned() ? UIColors.Get("DimRed") : UIColors.Get("PairBlue"))) - { - if (_uiSharedService.IconButton(FontAwesomeIcon.Thumbtack)) - { - GroupPairUserInfo userInfo = pair.Value ?? GroupPairUserInfo.None; - - userInfo.SetPinned(!userInfo.IsPinned()); - - _ = _apiController.GroupSetUserInfo(new GroupPairUserInfoDto(GroupFullInfo.Group, pair.Key.UserData, userInfo)); - } - } - UiSharedService.AttachToolTip(pair.Value != null && pair.Value.Value.IsPinned() ? "Unpin user" : "Pin user"); - ImGui.SameLine(); - - using (ImRaii.PushColor(ImGuiCol.Text, UIColors.Get("DimRed"))) - { - using (ImRaii.Disabled(!UiSharedService.CtrlPressed())) - { - if (_uiSharedService.IconButton(FontAwesomeIcon.Trash)) - { - _ = _apiController.GroupRemoveUser(new GroupPairDto(GroupFullInfo.Group, pair.Key.UserData)); - } - } - } - UiSharedService.AttachToolTip("Remove user from Syncshell" - + UiSharedService.TooltipSeparator + "Hold CTRL to enable this button"); - ImGui.SameLine(); - - using (ImRaii.PushColor(ImGuiCol.Text, UIColors.Get("DimRed"))) - { - using (ImRaii.Disabled(!UiSharedService.CtrlPressed())) - { - if (_uiSharedService.IconButton(FontAwesomeIcon.Ban)) - { - Mediator.Publish(new OpenBanUserPopupMessage(pair.Key, GroupFullInfo)); - } - } - } - UiSharedService.AttachToolTip("Ban user from Syncshell" - + UiSharedService.TooltipSeparator + "Hold CTRL to enable this button"); - } - } - } + DrawUserListCustom(pairs, GroupFullInfo); } - UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f); + ImGui.TreePop(); + ImGui.Separator(); } - ImGui.Separator(); if (_uiSharedService.MediumTreeNode("Mass Cleanup", UIColors.Get("DimRed"))) { @@ -595,6 +470,266 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase inviteTab.Dispose(); } + private void DrawUserListCustom(IReadOnlyList pairs, GroupFullInfoDto GroupFullInfo) + { + ImGui.PushItemWidth(0); + _uiSharedService.IconText(FontAwesomeIcon.Search, UIColors.Get("LightlessPurple")); + ImGui.SameLine(); + + ImGui.InputTextWithHint( + "##UserSearchFilter", + "Search UID/alias or note...", + ref _userSearchFilter, + 64); + + ImGui.PopItemWidth(); + ImGui.Dummy(new Vector2(0, 4 * ImGuiHelpers.GlobalScale)); + + var groupedPairs = new Dictionary( + pairs.Select(p => new KeyValuePair( + p, + GroupFullInfo.GroupPairUserInfos.TryGetValue(p.UserData.UID, out var value) ? value : null + )) + ); + + var filter = _userSearchFilter?.Trim(); + bool hasFilter = !string.IsNullOrEmpty(filter); + if (hasFilter) + filter = filter!.ToLowerInvariant(); + + var orderedPairs = groupedPairs + .Where(p => !hasFilter || MatchesUserFilter(p.Key, filter!)) + .OrderBy(p => + { + if (p.Value == null) return 10; + if (string.Equals(p.Key.UserData.UID, GroupFullInfo.OwnerUID, StringComparison.Ordinal)) return 0; + if (p.Value.Value.IsModerator()) return 1; + if (p.Value.Value.IsPinned()) return 2; + return 10; + }) + .ThenBy(p => p.Key.GetNote() ?? p.Key.UserData.AliasOrUID, StringComparer.OrdinalIgnoreCase); + + var style = ImGui.GetStyle(); + float fullW = ImGui.GetContentRegionAvail().X; + float colUid = fullW * 0.50f; + float colFlags = fullW * 0.10f; + float colActions = fullW - colUid - colFlags - style.ItemSpacing.X * 2.5f; + + DrawUserListHeader(colUid, colFlags); + + bool useScroll = pairs.Count > 10; + float childHeight = useScroll ? 260f * ImGuiHelpers.GlobalScale : 0f; + + ImGui.BeginChild("userListScroll#" + GroupFullInfo.Group.AliasOrGID, new Vector2(0, childHeight), true); + + int rowIndex = 0; + foreach (var kv in orderedPairs) + { + var pair = kv.Key; + var userInfoOpt = kv.Value; + DrawUserRowCustom(pair, userInfoOpt, GroupFullInfo, rowIndex++, colUid, colFlags, colActions); + } + ImGui.EndChild(); + UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.0f); + } + + private static void DrawUserListHeader(float colUid, float colFlags) + { + var style = ImGui.GetStyle(); + float x0 = ImGui.GetCursorPosX(); + + ImGui.PushStyleColor(ImGuiCol.Text, UIColors.Get("LightlessPurple")); + + // Alias/UID/Note + ImGui.SetCursorPosX(x0); + ImGui.TextUnformatted("Alias / UID / Note"); + + // User Flags + ImGui.SameLine(); + ImGui.SetCursorPosX(x0 + colUid + style.ItemSpacing.X); + ImGui.TextUnformatted("Flags"); + + // User Actions + ImGui.SameLine(); + ImGui.SetCursorPosX(x0 + colUid + colFlags + style.ItemSpacing.X * 2.5f); + ImGui.TextUnformatted("Actions"); + + ImGui.PopStyleColor(); + + UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.0f); + } + private void DrawUserRowCustom(Pair pair, GroupPairUserInfo? userInfoOpt, GroupFullInfoDto GroupFullInfo, int rowIndex, float colUid, float colFlags, float colActions) + { + using var id = ImRaii.PushId("userRow_" + pair.UserData.UID); + + var style = ImGui.GetStyle(); + float x0 = ImGui.GetCursorPosX(); + + if (rowIndex % 2 == 0) + { + var drawList = ImGui.GetWindowDrawList(); + var pMin = ImGui.GetCursorScreenPos(); + var rowHeight = ImGui.GetTextLineHeightWithSpacing() * 2.5f; + var pMax = new Vector2(pMin.X + colUid + colFlags + colActions + style.ItemSpacing.X * 2.0f, + pMin.Y + rowHeight); + + var bgColor = UIColors.Get("FullBlack") with { W = 0.0f }; + drawList.AddRectFilled(pMin, pMax, ImGui.ColorConvertFloat4ToU32(bgColor)); + } + + var isUserOwner = string.Equals(pair.UserData.UID, GroupFullInfo.OwnerUID, StringComparison.Ordinal); + var userInfo = userInfoOpt ?? GroupPairUserInfo.None; + + ImGui.SetCursorPosX(x0); + ImGui.AlignTextToFramePadding(); + + var note = pair.GetNote(); + var text = note == null + ? pair.UserData.AliasOrUID + : $"{note} ({pair.UserData.AliasOrUID})"; + + var boolcolor = UiSharedService.GetBoolColor(pair.IsOnline); + UiSharedService.ColorText(text, boolcolor); + if (ImGui.IsItemClicked()) + { + ImGui.SetClipboardText(text); + } + + if (!string.IsNullOrEmpty(pair.PlayerName)) + { + UiSharedService.AttachToolTip(pair.PlayerName); + } + + ImGui.SameLine(); + ImGui.SetCursorPosX(x0 + colUid + style.ItemSpacing.X); + + if (userInfoOpt != null && (userInfo.IsModerator() || userInfo.IsPinned() || isUserOwner)) + { + if (userInfo.IsModerator()) + { + _uiSharedService.IconText(FontAwesomeIcon.UserShield, UIColors.Get("LightlessPurple")); + UiSharedService.AttachToolTip("Moderator"); + } + if (userInfo.IsPinned() && !isUserOwner) + { + _uiSharedService.IconText(FontAwesomeIcon.Thumbtack); + UiSharedService.AttachToolTip("Pinned"); + } + if (isUserOwner) + { + _uiSharedService.IconText(FontAwesomeIcon.Crown, UIColors.Get("LightlessYellow")); + UiSharedService.AttachToolTip("Owner"); + } + } + else + { + _uiSharedService.IconText(FontAwesomeIcon.None); + } + + ImGui.SameLine(); + ImGui.SetCursorPosX(x0 + colUid + colFlags + style.ItemSpacing.X * 2.0f); + + DrawUserActions(pair, GroupFullInfo, userInfo, isUserOwner); + + ImGui.Dummy(new Vector2(0, 4 * ImGuiHelpers.GlobalScale)); + } + + private void DrawUserActions(Pair pair, GroupFullInfoDto GroupFullInfo, GroupPairUserInfo userInfo, bool isUserOwner) + { + if (_isOwner) + { + // Transfer ownership to user + using (ImRaii.PushColor(ImGuiCol.Text, UIColors.Get("LightlessYellow"))) + using (ImRaii.Disabled(!UiSharedService.ShiftPressed())) + { + if (_uiSharedService.IconButton(FontAwesomeIcon.Crown)) + { + _ = _apiController.GroupChangeOwnership(new(GroupFullInfo.Group, pair.UserData)); + IsOpen = false; + } + } + + UiSharedService.AttachToolTip("Hold SHIFT and click to transfer ownership of this Syncshell to " + + pair.UserData.AliasOrUID + Environment.NewLine + + "WARNING: This action is irreversible and will close screen."); + ImGui.SameLine(); + + // Mod / Demod user + using (ImRaii.PushColor(ImGuiCol.Text, + userInfo.IsModerator() ? UIColors.Get("DimRed") : UIColors.Get("PairBlue"))) + { + if (_uiSharedService.IconButton(FontAwesomeIcon.UserShield)) + { + userInfo.SetModerator(!userInfo.IsModerator()); + _ = _apiController.GroupSetUserInfo( + new GroupPairUserInfoDto(GroupFullInfo.Group, pair.UserData, userInfo)); + } + } + + UiSharedService.AttachToolTip( + userInfo.IsModerator() ? $"Demod {pair.UserData.AliasOrUID}" : $"Mod {pair.UserData.AliasOrUID}"); + ImGui.SameLine(); + } + + if (userInfo == GroupPairUserInfo.None || (!userInfo.IsModerator() && !isUserOwner)) + { + // Pin user + using (ImRaii.PushColor(ImGuiCol.Text, + userInfo.IsPinned() ? UIColors.Get("DimRed") : UIColors.Get("PairBlue"))) + { + if (_uiSharedService.IconButton(FontAwesomeIcon.Thumbtack)) + { + userInfo.SetPinned(!userInfo.IsPinned()); + + _ = _apiController.GroupSetUserInfo( + new GroupPairUserInfoDto(GroupFullInfo.Group, pair.UserData, userInfo)); + } + } + + UiSharedService.AttachToolTip( + userInfo.IsPinned() ? $"Unpin {pair.UserData.AliasOrUID}" : $"Pin {pair.UserData.AliasOrUID}"); + ImGui.SameLine(); + + // Remove user + using (ImRaii.PushColor(ImGuiCol.Text, UIColors.Get("DimRed"))) + using (ImRaii.Disabled(!UiSharedService.CtrlPressed())) + { + if (_uiSharedService.IconButton(FontAwesomeIcon.Trash)) + { + _ = _apiController.GroupRemoveUser(new GroupPairDto(GroupFullInfo.Group, pair.UserData)); + } + } + + UiSharedService.AttachToolTip($"Remove {pair.UserData.AliasOrUID} from Syncshell" + + UiSharedService.TooltipSeparator + "Hold CTRL to enable this button"); + ImGui.SameLine(); + + // Ban user + using (ImRaii.PushColor(ImGuiCol.Text, UIColors.Get("DimRed"))) + using (ImRaii.Disabled(!UiSharedService.CtrlPressed())) + { + if (_uiSharedService.IconButton(FontAwesomeIcon.Ban)) + { + Mediator.Publish(new OpenBanUserPopupMessage(pair, GroupFullInfo)); + } + } + + UiSharedService.AttachToolTip($"Ban {pair.UserData.AliasOrUID} from Syncshell" + + UiSharedService.TooltipSeparator + "Hold CTRL to enable this button"); + } + } + + private static bool MatchesUserFilter(Pair pair, string filterLower) + { + var note = pair.GetNote() ?? string.Empty; + var uid = pair.UserData.UID ?? string.Empty; + var alias = pair.UserData.AliasOrUID ?? string.Empty; + + return note.Contains(filterLower, StringComparison.OrdinalIgnoreCase) + || uid.Contains(filterLower, StringComparison.OrdinalIgnoreCase) + || alias.Contains(filterLower, StringComparison.OrdinalIgnoreCase); + } + public override void OnClose() { Mediator.Publish(new RemoveWindowMessage(this));