From 900af91013e04c6d3cf7b8502c6df073a912af34 Mon Sep 17 00:00:00 2001 From: CakeAndBanana Date: Mon, 8 Sep 2025 18:27:05 +0200 Subject: [PATCH] Added UI elements for syncshell folders --- LightlessSync.sln | 8 +- LightlessSync/Plugin.cs | 5 +- .../ServerConfigurationManager.cs | 4 +- LightlessSync/UI/CompactUI.cs | 55 ++++-- .../UI/Components/DrawFolderGroup.cs | 180 +++++++++++------- LightlessSync/UI/Components/DrawFolderTag.cs | 4 +- .../{RenameTagUi.cs => RenamePairTagUi.cs} | 30 +-- .../UI/Components/RenameSyncshellTagUi.cs | 79 ++++++++ .../UI/Components/SelectSyncshellForTagUi.cs | 86 +++++++++ .../UI/Components/SelectTagForSyncshellUi.cs | 132 +++++++++++++ LightlessSync/UI/DrawEntityFactory.cs | 28 ++- 11 files changed, 489 insertions(+), 122 deletions(-) rename LightlessSync/UI/Components/{RenameTagUi.cs => RenamePairTagUi.cs} (68%) create mode 100644 LightlessSync/UI/Components/RenameSyncshellTagUi.cs create mode 100644 LightlessSync/UI/Components/SelectSyncshellForTagUi.cs create mode 100644 LightlessSync/UI/Components/SelectTagForSyncshellUi.cs diff --git a/LightlessSync.sln b/LightlessSync.sln index 5b7ca3c..f24f6b2 100644 --- a/LightlessSync.sln +++ b/LightlessSync.sln @@ -22,16 +22,16 @@ Global Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {BB929046-4CD2-B174-EBAA-C756AC3AC8DA}.Debug|Any CPU.ActiveCfg = Release|x64 - {BB929046-4CD2-B174-EBAA-C756AC3AC8DA}.Debug|Any CPU.Build.0 = Release|x64 + {BB929046-4CD2-B174-EBAA-C756AC3AC8DA}.Debug|Any CPU.ActiveCfg = Debug|x64 + {BB929046-4CD2-B174-EBAA-C756AC3AC8DA}.Debug|Any CPU.Build.0 = Debug|x64 {BB929046-4CD2-B174-EBAA-C756AC3AC8DA}.Debug|x64.ActiveCfg = Debug|x64 {BB929046-4CD2-B174-EBAA-C756AC3AC8DA}.Debug|x64.Build.0 = Debug|x64 {BB929046-4CD2-B174-EBAA-C756AC3AC8DA}.Release|Any CPU.ActiveCfg = Release|x64 {BB929046-4CD2-B174-EBAA-C756AC3AC8DA}.Release|Any CPU.Build.0 = Release|x64 {BB929046-4CD2-B174-EBAA-C756AC3AC8DA}.Release|x64.ActiveCfg = Release|x64 {BB929046-4CD2-B174-EBAA-C756AC3AC8DA}.Release|x64.Build.0 = Release|x64 - {A4E42AFA-5045-7E81-937F-3A320AC52987}.Debug|Any CPU.ActiveCfg = Release|Any CPU - {A4E42AFA-5045-7E81-937F-3A320AC52987}.Debug|Any CPU.Build.0 = Release|Any CPU + {A4E42AFA-5045-7E81-937F-3A320AC52987}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A4E42AFA-5045-7E81-937F-3A320AC52987}.Debug|Any CPU.Build.0 = Debug|Any CPU {A4E42AFA-5045-7E81-937F-3A320AC52987}.Debug|x64.ActiveCfg = Debug|Any CPU {A4E42AFA-5045-7E81-937F-3A320AC52987}.Debug|x64.Build.0 = Debug|Any CPU {A4E42AFA-5045-7E81-937F-3A320AC52987}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/LightlessSync/Plugin.cs b/LightlessSync/Plugin.cs index cf9b41c..239c784 100644 --- a/LightlessSync/Plugin.cs +++ b/LightlessSync/Plugin.cs @@ -130,7 +130,9 @@ public sealed class Plugin : IDalamudPlugin s.GetRequiredService(), s.GetRequiredService())); collection.AddSingleton(); - collection.AddSingleton(); + collection.AddSingleton(); + collection.AddSingleton(); + collection.AddSingleton(); collection.AddSingleton((s) => new EventAggregator(pluginInterface.ConfigDirectory.FullName, s.GetRequiredService>(), s.GetRequiredService())); collection.AddSingleton((s) => new DalamudUtilService(s.GetRequiredService>(), @@ -200,6 +202,7 @@ public sealed class Plugin : IDalamudPlugin collection.AddScoped(); collection.AddScoped(); collection.AddScoped(); + collection.AddScoped(); collection.AddScoped(); collection.AddScoped(); collection.AddScoped(); diff --git a/LightlessSync/Services/ServerConfiguration/ServerConfigurationManager.cs b/LightlessSync/Services/ServerConfiguration/ServerConfigurationManager.cs index fa586f5..2ffdeb7 100644 --- a/LightlessSync/Services/ServerConfiguration/ServerConfigurationManager.cs +++ b/LightlessSync/Services/ServerConfiguration/ServerConfigurationManager.cs @@ -341,7 +341,7 @@ public class ServerConfigurationManager } else { - CurrentPairTagStorage().UidServerPairedUserTags[syncshellName] = [tagName]; + CurrentSyncshellTagStorage().SyncshellPairedTags[syncshellName] = [tagName]; } _syncshellTagConfig.Save(); @@ -608,7 +608,7 @@ public class ServerConfigurationManager private void TryCreateCurrentSyncshellTagStorage() { - if (!_pairTagConfig.Current.ServerTagStorage.ContainsKey(CurrentApiUrl)) + if (!_syncshellTagConfig.Current.ServerTagStorage.ContainsKey(CurrentApiUrl)) { _syncshellTagConfig.Current.ServerTagStorage[CurrentApiUrl] = new(); } diff --git a/LightlessSync/UI/CompactUI.cs b/LightlessSync/UI/CompactUI.cs index 62af359..c15b8d4 100644 --- a/LightlessSync/UI/CompactUI.cs +++ b/LightlessSync/UI/CompactUI.cs @@ -1,6 +1,5 @@ using Dalamud.Bindings.ImGui; using Dalamud.Interface; -using Dalamud.Interface.Colors; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using Dalamud.Utility; @@ -36,9 +35,12 @@ public class CompactUi : WindowMediatorSubscriberBase private readonly DrawEntityFactory _drawEntityFactory; private readonly FileUploadManager _fileTransferManager; private readonly PairManager _pairManager; - private readonly SelectTagForPairUi _selectGroupForPairUi; + private readonly SelectTagForPairUi _selectTagForPairUi; + private readonly SelectTagForSyncshellUi _selectTagForSyncshellUi; + private readonly SelectSyncshellForTagUi _selectSyncshellForTagUi; + private readonly RenameSyncshellTagUi _renameSyncshellTagUi; private readonly SelectPairForTagUi _selectPairsForGroupUi; - private readonly RenameTagUi _renameTagUi; + private readonly RenamePairTagUi _renamePairTagUi; private readonly IpcManager _ipcManager; private readonly ServerConfigurationManager _serverManager; private readonly TopTabMenu _tabMenu; @@ -57,7 +59,9 @@ public class CompactUi : WindowMediatorSubscriberBase public CompactUi(ILogger logger, UiSharedService uiShared, LightlessConfigService configService, ApiController apiController, PairManager pairManager, ServerConfigurationManager serverManager, LightlessMediator mediator, FileUploadManager fileTransferManager, - TagHandler tagHandler, DrawEntityFactory drawEntityFactory, SelectTagForPairUi selectTagForPairUi, SelectPairForTagUi selectPairForTagUi, RenameTagUi renameTagUi, + TagHandler tagHandler, DrawEntityFactory drawEntityFactory, + SelectTagForPairUi selectTagForPairUi, SelectPairForTagUi selectPairForTagUi, RenamePairTagUi renameTagUi, + SelectTagForSyncshellUi selectTagForSyncshellUi, SelectSyncshellForTagUi selectSyncshellForTagUi, RenameSyncshellTagUi renameSyncshellTagUi, PerformanceCollectorService performanceCollectorService, IpcManager ipcManager) : base(logger, mediator, "###LightlessSyncMainUI", performanceCollectorService) { @@ -69,9 +73,12 @@ public class CompactUi : WindowMediatorSubscriberBase _fileTransferManager = fileTransferManager; _tagHandler = tagHandler; _drawEntityFactory = drawEntityFactory; - _selectGroupForPairUi = selectTagForPairUi; + _selectTagForPairUi = selectTagForPairUi; + _selectTagForSyncshellUi = selectTagForSyncshellUi; + _selectSyncshellForTagUi = selectSyncshellForTagUi; + _renameSyncshellTagUi = renameSyncshellTagUi; _selectPairsForGroupUi = selectPairForTagUi; - _renameTagUi = renameTagUi; + _renamePairTagUi = renameTagUi; _ipcManager = ipcManager; _tabMenu = new TopTabMenu(Mediator, _apiController, _pairManager, _uiSharedService); @@ -199,9 +206,12 @@ public class CompactUi : WindowMediatorSubscriberBase float pairlistEnd = ImGui.GetCursorPosY(); using (ImRaii.PushId("transfers")) DrawTransfers(); _transferPartHeight = ImGui.GetCursorPosY() - pairlistEnd - ImGui.GetTextLineHeight(); - using (ImRaii.PushId("group-user-popup")) _selectPairsForGroupUi.Draw(_pairManager.DirectPairs); - using (ImRaii.PushId("group-user-edit")) _renameTagUi.Draw(_pairManager.DirectPairs); - using (ImRaii.PushId("grouping-popup")) _selectGroupForPairUi.Draw(); + using (ImRaii.PushId("group-pair-popup")) _selectPairsForGroupUi.Draw(_pairManager.DirectPairs); + using (ImRaii.PushId("group-syncshell-popup")) _selectSyncshellForTagUi.Draw(_pairManager.Groups.Values.ToList()); + using (ImRaii.PushId("group-pair-edit")) _renamePairTagUi.Draw(); + using (ImRaii.PushId("group-syncshell-edit")) _renameSyncshellTagUi.Draw(); + using (ImRaii.PushId("grouping-pair-popup")) _selectTagForPairUi.Draw(); + using (ImRaii.PushId("grouping-syncshell-popup")) _selectTagForSyncshellUi.Draw(); } if (_configService.Current.OpenPopupOnAdd && _pairManager.LastAddedUser != null) @@ -534,12 +544,29 @@ public class CompactUi : WindowMediatorSubscriberBase _logger.LogInformation($"Loading {syncshellTags.Count} syncshell tags"); foreach (var syncshelltag in syncshellTags) { - var allTagPairs = ImmutablePairList(allPairs - .Where(u => FilterTagUsers(u, syncshelltag))); - var filteredTagPairs = BasicSortedDictionary(filteredPairs - .Where(u => FilterTagUsers(u, syncshelltag) && FilterOnlineOrPausedSelf(u))); + foreach (var group in _pairManager.GroupPairs.Select(g => g.Key).OrderBy(g => g.GroupAliasOrGID, StringComparer.OrdinalIgnoreCase)) + { + var allGroupPairs = ImmutablePairList(allPairs + .Where(u => FilterTagSyncshells(u, syncshelltag))); - drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(syncshelltag, filteredTagPairs, allTagPairs)); + var filteredGroupPairs = filteredPairs + .Where(u => FilterTagSyncshells(u, syncshelltag) && FilterOnlineOrPausedSelf(u)) + .OrderByDescending(u => u.Key.IsOnline) + .ThenBy(u => + { + if (string.Equals(u.Key.UserData.UID, group.OwnerUID, StringComparison.Ordinal)) return 0; + if (group.GroupPairUserInfos.TryGetValue(u.Key.UserData.UID, out var info)) + { + if (info.IsModerator()) return 1; + if (info.IsPinned()) return 2; + } + return u.Key.IsVisible ? 3 : 4; + }) + .ThenBy(AlphabeticalSort, StringComparer.OrdinalIgnoreCase) + .ToDictionary(k => k.Key, k => k.Value); + + groupFolders.Add(_drawEntityFactory.CreateDrawGroupFolder(group, filteredGroupPairs, allGroupPairs, syncshelltag)); + } } var allOnlineNotTaggedPairs = ImmutablePairList(allPairs diff --git a/LightlessSync/UI/Components/DrawFolderGroup.cs b/LightlessSync/UI/Components/DrawFolderGroup.cs index d6e5b04..a3e9644 100644 --- a/LightlessSync/UI/Components/DrawFolderGroup.cs +++ b/LightlessSync/UI/Components/DrawFolderGroup.cs @@ -19,16 +19,26 @@ public class DrawFolderGroup : DrawFolderBase private readonly GroupFullInfoDto _groupFullInfoDto; private readonly IdDisplayHandler _idDisplayHandler; private readonly LightlessMediator _lightlessMediator; + private readonly SelectSyncshellForTagUi _selectSyncshellForTagUi; + private readonly RenameSyncshellTagUi _renameTagUi; + private readonly SelectTagForSyncshellUi _selectTagForSyncshellUi; + private readonly bool _isTag; public DrawFolderGroup(string id, GroupFullInfoDto groupFullInfoDto, ApiController apiController, IImmutableList drawPairs, IImmutableList allPairs, TagHandler tagHandler, IdDisplayHandler idDisplayHandler, - LightlessMediator lightlessMediator, UiSharedService uiSharedService) : + LightlessMediator lightlessMediator, UiSharedService uiSharedService, + SelectSyncshellForTagUi selectSyncshellForTagUi, RenameSyncshellTagUi renameTagUi, SelectTagForSyncshellUi selectTagForSyncshellUi, + bool isTag) : base(id, drawPairs, allPairs, tagHandler, uiSharedService) { _groupFullInfoDto = groupFullInfoDto; _apiController = apiController; _idDisplayHandler = idDisplayHandler; _lightlessMediator = lightlessMediator; + _selectSyncshellForTagUi = selectSyncshellForTagUi; + _renameTagUi = renameTagUi; + _selectTagForSyncshellUi = selectTagForSyncshellUi; + _isTag = isTag; } protected override bool RenderIfEmpty => true; @@ -81,81 +91,109 @@ public class DrawFolderGroup : DrawFolderBase protected override void DrawMenu(float menuWidth) { - ImGui.TextUnformatted("Syncshell Menu (" + _groupFullInfoDto.GroupAliasOrGID + ")"); - ImGui.Separator(); - - ImGui.TextUnformatted("General Syncshell Actions"); - if (_uiSharedService.IconTextButton(FontAwesomeIcon.Copy, "Copy ID", menuWidth, true)) - { - ImGui.CloseCurrentPopup(); - ImGui.SetClipboardText(_groupFullInfoDto.GroupAliasOrGID); - } - UiSharedService.AttachToolTip("Copy Syncshell ID to Clipboard"); - - if (_uiSharedService.IconTextButton(FontAwesomeIcon.StickyNote, "Copy Notes", menuWidth, true)) - { - ImGui.CloseCurrentPopup(); - ImGui.SetClipboardText(UiSharedService.GetNotes(DrawPairs.Select(k => k.Pair).ToList())); - } - UiSharedService.AttachToolTip("Copies all your notes for all users in this Syncshell to the clipboard." + Environment.NewLine + "They can be imported via Settings -> General -> Notes -> Import notes from clipboard"); - - if (_uiSharedService.IconTextButton(FontAwesomeIcon.ArrowCircleLeft, "Leave Syncshell", menuWidth, true) && UiSharedService.CtrlPressed()) - { - _ = _apiController.GroupLeave(_groupFullInfoDto); - ImGui.CloseCurrentPopup(); - } - UiSharedService.AttachToolTip("Hold CTRL and click to leave this Syncshell" + (!string.Equals(_groupFullInfoDto.OwnerUID, _apiController.UID, StringComparison.Ordinal) - ? string.Empty : Environment.NewLine + "WARNING: This action is irreversible" + Environment.NewLine + "Leaving an owned Syncshell will transfer the ownership to a random person in the Syncshell.")); - - ImGui.Separator(); - ImGui.TextUnformatted("Permission Settings"); - var perm = _groupFullInfoDto.GroupUserPermissions; - bool disableSounds = perm.IsDisableSounds(); - bool disableAnims = perm.IsDisableAnimations(); - bool disableVfx = perm.IsDisableVFX(); - - if ((_groupFullInfoDto.GroupPermissions.IsPreferDisableAnimations() != disableAnims - || _groupFullInfoDto.GroupPermissions.IsPreferDisableSounds() != disableSounds - || _groupFullInfoDto.GroupPermissions.IsPreferDisableVFX() != disableVfx) - && _uiSharedService.IconTextButton(FontAwesomeIcon.Check, "Align with suggested permissions", menuWidth, true)) - { - perm.SetDisableVFX(_groupFullInfoDto.GroupPermissions.IsPreferDisableVFX()); - perm.SetDisableSounds(_groupFullInfoDto.GroupPermissions.IsPreferDisableSounds()); - perm.SetDisableAnimations(_groupFullInfoDto.GroupPermissions.IsPreferDisableAnimations()); - _ = _apiController.GroupChangeIndividualPermissionState(new(_groupFullInfoDto.Group, new(_apiController.UID), perm)); - ImGui.CloseCurrentPopup(); - } - - if (_uiSharedService.IconTextButton(disableSounds ? FontAwesomeIcon.VolumeUp : FontAwesomeIcon.VolumeOff, disableSounds ? "Enable Sound Sync" : "Disable Sound Sync", menuWidth, true)) - { - perm.SetDisableSounds(!disableSounds); - _ = _apiController.GroupChangeIndividualPermissionState(new(_groupFullInfoDto.Group, new(_apiController.UID), perm)); - ImGui.CloseCurrentPopup(); - } - - if (_uiSharedService.IconTextButton(disableAnims ? FontAwesomeIcon.Running : FontAwesomeIcon.Stop, disableAnims ? "Enable Animation Sync" : "Disable Animation Sync", menuWidth, true)) - { - perm.SetDisableAnimations(!disableAnims); - _ = _apiController.GroupChangeIndividualPermissionState(new(_groupFullInfoDto.Group, new(_apiController.UID), perm)); - ImGui.CloseCurrentPopup(); - } - - if (_uiSharedService.IconTextButton(disableVfx ? FontAwesomeIcon.Sun : FontAwesomeIcon.Circle, disableVfx ? "Enable VFX Sync" : "Disable VFX Sync", menuWidth, true)) - { - perm.SetDisableVFX(!disableVfx); - _ = _apiController.GroupChangeIndividualPermissionState(new(_groupFullInfoDto.Group, new(_apiController.UID), perm)); - ImGui.CloseCurrentPopup(); - } - - if (IsModerator || IsOwner) + if (!_isTag) { + ImGui.TextUnformatted("Syncshell Menu (" + _groupFullInfoDto.GroupAliasOrGID + ")"); ImGui.Separator(); - ImGui.TextUnformatted("Syncshell Admin Functions"); - if (_uiSharedService.IconTextButton(FontAwesomeIcon.Cog, "Open Admin Panel", menuWidth, true)) + + ImGui.TextUnformatted("General Syncshell Actions"); + if (_uiSharedService.IconTextButton(FontAwesomeIcon.Copy, "Copy ID", menuWidth, true)) { ImGui.CloseCurrentPopup(); - _lightlessMediator.Publish(new OpenSyncshellAdminPanel(_groupFullInfoDto)); + ImGui.SetClipboardText(_groupFullInfoDto.GroupAliasOrGID); } + UiSharedService.AttachToolTip("Copy Syncshell ID to Clipboard"); + + if (_uiSharedService.IconTextButton(FontAwesomeIcon.StickyNote, "Copy Notes", menuWidth, true)) + { + ImGui.CloseCurrentPopup(); + ImGui.SetClipboardText(UiSharedService.GetNotes(DrawPairs.Select(k => k.Pair).ToList())); + } + UiSharedService.AttachToolTip("Copies all your notes for all users in this Syncshell to the clipboard." + Environment.NewLine + "They can be imported via Settings -> General -> Notes -> Import notes from clipboard"); + + if (_uiSharedService.IconTextButton(FontAwesomeIcon.Folder, "Syncshell Groups", menuWidth, true)) + { + ImGui.CloseCurrentPopup(); + _selectTagForSyncshellUi.Open(_groupFullInfoDto); + } + UiSharedService.AttachToolTip("Choose syncshell groups for " + _groupFullInfoDto.GID); + + if (_uiSharedService.IconTextButton(FontAwesomeIcon.ArrowCircleLeft, "Leave Syncshell", menuWidth, true) && UiSharedService.CtrlPressed()) + { + _ = _apiController.GroupLeave(_groupFullInfoDto); + ImGui.CloseCurrentPopup(); + } + UiSharedService.AttachToolTip("Hold CTRL and click to leave this Syncshell" + (!string.Equals(_groupFullInfoDto.OwnerUID, _apiController.UID, StringComparison.Ordinal) + ? string.Empty : Environment.NewLine + "WARNING: This action is irreversible" + Environment.NewLine + "Leaving an owned Syncshell will transfer the ownership to a random person in the Syncshell.")); + + ImGui.Separator(); + ImGui.TextUnformatted("Permission Settings"); + var perm = _groupFullInfoDto.GroupUserPermissions; + bool disableSounds = perm.IsDisableSounds(); + bool disableAnims = perm.IsDisableAnimations(); + bool disableVfx = perm.IsDisableVFX(); + + if ((_groupFullInfoDto.GroupPermissions.IsPreferDisableAnimations() != disableAnims + || _groupFullInfoDto.GroupPermissions.IsPreferDisableSounds() != disableSounds + || _groupFullInfoDto.GroupPermissions.IsPreferDisableVFX() != disableVfx) + && _uiSharedService.IconTextButton(FontAwesomeIcon.Check, "Align with suggested permissions", menuWidth, true)) + { + perm.SetDisableVFX(_groupFullInfoDto.GroupPermissions.IsPreferDisableVFX()); + perm.SetDisableSounds(_groupFullInfoDto.GroupPermissions.IsPreferDisableSounds()); + perm.SetDisableAnimations(_groupFullInfoDto.GroupPermissions.IsPreferDisableAnimations()); + _ = _apiController.GroupChangeIndividualPermissionState(new(_groupFullInfoDto.Group, new(_apiController.UID), perm)); + ImGui.CloseCurrentPopup(); + } + + if (_uiSharedService.IconTextButton(disableSounds ? FontAwesomeIcon.VolumeUp : FontAwesomeIcon.VolumeOff, disableSounds ? "Enable Sound Sync" : "Disable Sound Sync", menuWidth, true)) + { + perm.SetDisableSounds(!disableSounds); + _ = _apiController.GroupChangeIndividualPermissionState(new(_groupFullInfoDto.Group, new(_apiController.UID), perm)); + ImGui.CloseCurrentPopup(); + } + + if (_uiSharedService.IconTextButton(disableAnims ? FontAwesomeIcon.Running : FontAwesomeIcon.Stop, disableAnims ? "Enable Animation Sync" : "Disable Animation Sync", menuWidth, true)) + { + perm.SetDisableAnimations(!disableAnims); + _ = _apiController.GroupChangeIndividualPermissionState(new(_groupFullInfoDto.Group, new(_apiController.UID), perm)); + ImGui.CloseCurrentPopup(); + } + + if (_uiSharedService.IconTextButton(disableVfx ? FontAwesomeIcon.Sun : FontAwesomeIcon.Circle, disableVfx ? "Enable VFX Sync" : "Disable VFX Sync", menuWidth, true)) + { + perm.SetDisableVFX(!disableVfx); + _ = _apiController.GroupChangeIndividualPermissionState(new(_groupFullInfoDto.Group, new(_apiController.UID), perm)); + ImGui.CloseCurrentPopup(); + } + + if (IsModerator || IsOwner) + { + ImGui.Separator(); + ImGui.TextUnformatted("Syncshell Admin Functions"); + if (_uiSharedService.IconTextButton(FontAwesomeIcon.Cog, "Open Admin Panel", menuWidth, true)) + { + ImGui.CloseCurrentPopup(); + _lightlessMediator.Publish(new OpenSyncshellAdminPanel(_groupFullInfoDto)); + } + } + } + else + { + ImGui.TextUnformatted("Group Menu"); + if (_uiSharedService.IconTextButton(FontAwesomeIcon.Users, "Select Syncshells", menuWidth, isInPopup: true)) + { + _selectSyncshellForTagUi.Open(_id); + } + if (_uiSharedService.IconTextButton(FontAwesomeIcon.Edit, "Rename Syncshell Group", menuWidth, isInPopup: true)) + { + _renameTagUi.Open(_id); + } + if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, "Delete Syncshell Group", menuWidth, isInPopup: true) && UiSharedService.CtrlPressed()) + { + _tagHandler.RemoveSyncshellTag(_id); + } + UiSharedService.AttachToolTip("Hold CTRL to remove this Group permanently." + Environment.NewLine + + "Note: this will not unpair the Group."); } } diff --git a/LightlessSync/UI/Components/DrawFolderTag.cs b/LightlessSync/UI/Components/DrawFolderTag.cs index 3dd4195..0c114e1 100644 --- a/LightlessSync/UI/Components/DrawFolderTag.cs +++ b/LightlessSync/UI/Components/DrawFolderTag.cs @@ -13,10 +13,10 @@ public class DrawFolderTag : DrawFolderBase { private readonly ApiController _apiController; private readonly SelectPairForTagUi _selectPairForTagUi; - private readonly RenameTagUi _renameTagUi; + private readonly RenamePairTagUi _renameTagUi; public DrawFolderTag(string id, IImmutableList drawPairs, IImmutableList allPairs, - TagHandler tagHandler, ApiController apiController, SelectPairForTagUi selectPairForTagUi, RenameTagUi renameTagUi, UiSharedService uiSharedService) + TagHandler tagHandler, ApiController apiController, SelectPairForTagUi selectPairForTagUi, RenamePairTagUi renameTagUi, UiSharedService uiSharedService) : base(id, drawPairs, allPairs, tagHandler, uiSharedService) { _apiController = apiController; diff --git a/LightlessSync/UI/Components/RenameTagUi.cs b/LightlessSync/UI/Components/RenamePairTagUi.cs similarity index 68% rename from LightlessSync/UI/Components/RenameTagUi.cs rename to LightlessSync/UI/Components/RenamePairTagUi.cs index aa67a7b..aa77783 100644 --- a/LightlessSync/UI/Components/RenameTagUi.cs +++ b/LightlessSync/UI/Components/RenamePairTagUi.cs @@ -1,36 +1,34 @@ using Dalamud.Bindings.ImGui; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; -using LightlessSync.PlayerData.Pairs; using LightlessSync.UI.Handlers; using System.Numerics; namespace LightlessSync.UI.Components; -public class RenameTagUi +public class RenamePairTagUi { private readonly TagHandler _tagHandler; private readonly UiSharedService _uiSharedService; private string _desiredName = string.Empty; private bool _opened = false; - private HashSet _peopleInGroup = new(StringComparer.Ordinal); private bool _show = false; private string _tag = string.Empty; - public RenameTagUi(TagHandler tagHandler, UiSharedService uiSharedService) + public RenamePairTagUi(TagHandler tagHandler, UiSharedService uiSharedService) { _tagHandler = tagHandler; _uiSharedService = uiSharedService; } - public void Draw(List pairs) + public void Draw() { var workHeight = ImGui.GetMainViewport().WorkSize.Y / ImGuiHelpers.GlobalScale; var minSize = new Vector2(300, workHeight < 110 ? workHeight : 110) * ImGuiHelpers.GlobalScale; var maxSize = new Vector2(300, 110) * ImGuiHelpers.GlobalScale; - var popupName = $"Renaming Group {_tag}"; + var popupName = $"Renaming Pair Group {_tag}"; if (!_show) { @@ -55,7 +53,7 @@ public class RenameTagUi { if (_uiSharedService.IconTextButton(Dalamud.Interface.FontAwesomeIcon.Plus, "Rename Group")) { - RenameTag(pairs, _tag, _desiredName); + RenameTag(_tag, _desiredName); _show = false; } } @@ -69,25 +67,13 @@ public class RenameTagUi public void Open(string tag) { - _peopleInGroup = _tagHandler.GetOtherUidsForTag(tag); _tag = tag; _desiredName = ""; _show = true; } - public void RenameTag(List pairs, string oldTag, string newTag) - { - //Removal of old tag - _tagHandler.RemovePairTag(oldTag); - //Creation of new tag and adding of old group pairs in new one. - _tagHandler.AddPairTag(newTag); - foreach (Pair pair in pairs) - { - var isInTag = _peopleInGroup.Contains(pair.UserData.UID); - if (isInTag) - { - _tagHandler.AddPairTagToPairedUid(pair.UserData.UID, newTag); - } - } + public void RenameTag(string oldTag, string newTag) + { + _tagHandler.RenamePairTag(oldTag, newTag); } } \ No newline at end of file diff --git a/LightlessSync/UI/Components/RenameSyncshellTagUi.cs b/LightlessSync/UI/Components/RenameSyncshellTagUi.cs new file mode 100644 index 0000000..a4edcde --- /dev/null +++ b/LightlessSync/UI/Components/RenameSyncshellTagUi.cs @@ -0,0 +1,79 @@ +using Dalamud.Bindings.ImGui; +using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; +using LightlessSync.UI.Handlers; + +using System.Numerics; + +namespace LightlessSync.UI.Components; + +public class RenameSyncshellTagUi +{ + private readonly TagHandler _tagHandler; + private readonly UiSharedService _uiSharedService; + private string _desiredName = string.Empty; + private bool _opened = false; + private bool _show = false; + private string _tag = string.Empty; + + public RenameSyncshellTagUi(TagHandler tagHandler, UiSharedService uiSharedService) + { + _tagHandler = tagHandler; + _uiSharedService = uiSharedService; + } + + public void Draw() + { + var workHeight = ImGui.GetMainViewport().WorkSize.Y / ImGuiHelpers.GlobalScale; + var minSize = new Vector2(300, workHeight < 110 ? workHeight : 110) * ImGuiHelpers.GlobalScale; + var maxSize = new Vector2(300, 110) * ImGuiHelpers.GlobalScale; + + var popupName = $"Renaming Pair Group {_tag}"; + + if (!_show) + { + _opened = false; + } + + if (_show && !_opened) + { + ImGui.SetNextWindowSize(minSize); + UiSharedService.CenterNextWindow(minSize.X, minSize.Y, ImGuiCond.Always); + ImGui.OpenPopup(popupName); + _opened = true; + } + + ImGui.SetNextWindowSizeConstraints(minSize, maxSize); + if (ImGui.BeginPopupModal(popupName, ref _show, ImGuiWindowFlags.Popup | ImGuiWindowFlags.Modal)) + { + ImGui.TextUnformatted($"Renaming {_tag}"); + + ImGui.InputTextWithHint("##desiredname", "Enter new group name", ref _desiredName, 255, ImGuiInputTextFlags.None); + using (ImRaii.Disabled(string.IsNullOrEmpty(_desiredName))) + { + if (_uiSharedService.IconTextButton(Dalamud.Interface.FontAwesomeIcon.Plus, "Rename Group")) + { + RenameTag(_tag, _desiredName); + _show = false; + } + } + ImGui.EndPopup(); + } + else + { + _show = false; + } + } + + public void Open(string tag) + { + _tag = tag; + _desiredName = ""; + _show = true; + } + + public void RenameTag(string oldTag, string newTag) + { + _tagHandler.RenamePairTag(oldTag, newTag); + } +} \ No newline at end of file diff --git a/LightlessSync/UI/Components/SelectSyncshellForTagUi.cs b/LightlessSync/UI/Components/SelectSyncshellForTagUi.cs new file mode 100644 index 0000000..288776c --- /dev/null +++ b/LightlessSync/UI/Components/SelectSyncshellForTagUi.cs @@ -0,0 +1,86 @@ +using Dalamud.Bindings.ImGui; +using Dalamud.Interface.Utility; +using LightlessSync.API.Dto.Group; +using LightlessSync.UI.Handlers; + +using System.Numerics; + +namespace LightlessSync.UI.Components; + +public class SelectSyncshellForTagUi +{ + private readonly TagHandler _tagHandler; + private string _filter = string.Empty; + private bool _opened = false; + private HashSet _syncshellsInGroup = new(StringComparer.Ordinal); + private bool _show = false; + private string _tag = string.Empty; + + public SelectSyncshellForTagUi(TagHandler tagHandler) + { + _tagHandler = tagHandler; + } + + public void Draw(List groups) + { + var workHeight = ImGui.GetMainViewport().WorkSize.Y / ImGuiHelpers.GlobalScale; + var minSize = new Vector2(300, workHeight < 400 ? workHeight : 400) * ImGuiHelpers.GlobalScale; + var maxSize = new Vector2(300, 1000) * ImGuiHelpers.GlobalScale; + + var popupName = $"Choose Syncshells for Group {_tag}"; + + if (!_show) + { + _opened = false; + } + + if (_show && !_opened) + { + ImGui.SetNextWindowSize(minSize); + UiSharedService.CenterNextWindow(minSize.X, minSize.Y, ImGuiCond.Always); + ImGui.OpenPopup(popupName); + _opened = true; + } + + ImGui.SetNextWindowSizeConstraints(minSize, maxSize); + if (ImGui.BeginPopupModal(popupName, ref _show, ImGuiWindowFlags.Popup | ImGuiWindowFlags.Modal)) + { + ImGui.TextUnformatted($"Select syncshells for group {_tag}"); + + ImGui.InputTextWithHint("##filter", "Filter", ref _filter, 255, ImGuiInputTextFlags.None); + foreach (var group in groups + .Where(g => string.IsNullOrEmpty(_filter) || g.GID.Contains(_filter, StringComparison.OrdinalIgnoreCase)) + .OrderBy(g => g.GID, StringComparer.OrdinalIgnoreCase) + .ToList()) + { + var isInGroup = _syncshellsInGroup.Contains(group.GID); + if (ImGui.Checkbox(group.GID, ref isInGroup)) + { + if (isInGroup) + { + _tagHandler.AddTagToSyncshell(group.GID, _tag); + _syncshellsInGroup.Add(group.GID); + } + else + { + _tagHandler.RemoveTagFromSyncshell(group.GID, _tag); + _syncshellsInGroup.Remove(group.GID); + } + } + } + ImGui.EndPopup(); + } + else + { + _filter = string.Empty; + _show = false; + } + } + + public void Open(string tag) + { + _syncshellsInGroup = _tagHandler.GetOtherSyncshellsForTag(tag); + _tag = tag; + _show = true; + } +} \ No newline at end of file diff --git a/LightlessSync/UI/Components/SelectTagForSyncshellUi.cs b/LightlessSync/UI/Components/SelectTagForSyncshellUi.cs new file mode 100644 index 0000000..5473263 --- /dev/null +++ b/LightlessSync/UI/Components/SelectTagForSyncshellUi.cs @@ -0,0 +1,132 @@ +using Dalamud.Bindings.ImGui; +using Dalamud.Interface; +using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; +using Dalamud.Utility; +using LightlessSync.API.Dto.Group; +using LightlessSync.UI.Handlers; + +using System.Numerics; + +namespace LightlessSync.UI.Components; + +public class SelectTagForSyncshellUi +{ + private readonly TagHandler _tagHandler; + private readonly UiSharedService _uiSharedService; + + /// + /// The group UI is always open for a specific pair. This defines which pair the UI is open for. + /// + /// + private GroupFullInfoDto? _group; + + /// + /// Should the panel show, yes/no + /// + private bool _show; + + /// + /// For the add category option, this stores the currently typed in tag name + /// + private string _tagNameToAdd = ""; + + public SelectTagForSyncshellUi(TagHandler tagHandler, UiSharedService uiSharedService) + { + _show = false; + _group = null; + _tagHandler = tagHandler; + _uiSharedService = uiSharedService; + } + + public void Draw() + { + if (_group == null) + { + return; + } + + var name = _group.GID; + var popupName = $"Choose Groups for {_group.GID}"; + // Is the popup supposed to show but did not open yet? Open it + if (_show) + { + ImGui.OpenPopup(popupName); + _show = false; + } + + if (ImGui.BeginPopup(popupName)) + { + var tags = _tagHandler.GetAllSyncshellTagsSorted(); + var childHeight = tags.Count != 0 ? tags.Count * 25 : 1; + var childSize = new Vector2(0, childHeight > 100 ? 100 : childHeight) * ImGuiHelpers.GlobalScale; + + ImGui.TextUnformatted($"Select the groups you want {name} to be in."); + if (ImGui.BeginChild(name + "##listGroups", childSize)) + { + foreach (var tag in tags) + { + using (ImRaii.PushId($"groups-syncshell-{_group.GID}-{tag}")) DrawGroupName(_group, tag); + } + ImGui.EndChild(); + } + + ImGui.Separator(); + ImGui.TextUnformatted($"Create a new group for {name}."); + if (_uiSharedService.IconButton(FontAwesomeIcon.Plus)) + { + HandleAddTag(); + } + ImGui.SameLine(); + ImGui.InputTextWithHint("##category_name", "New Group", ref _tagNameToAdd, 40); + if (ImGui.IsKeyDown(ImGuiKey.Enter)) + { + HandleAddTag(); + } + ImGui.EndPopup(); + } + } + + public void Open(GroupFullInfoDto group) + { + _group = group; + // Using "_show" here to de-couple the opening of the popup + // The popup name is derived from the name the user currently sees, which is + // based on the showUidForEntry dictionary. + // We'd have to derive the name here to open it popup modal here, when the Open() is called + _show = true; + } + + private void DrawGroupName(GroupFullInfoDto group, string name) + { + var hasTag = _tagHandler.HasSyncshellTag(group.GID, name); + if (ImGui.Checkbox(name, ref hasTag)) + { + if (hasTag) + { + _tagHandler.AddTagToSyncshell(group.GID, name); + } + else + { + _tagHandler.RemoveTagFromSyncshell(group.GID, name); + } + } + } + + private void HandleAddTag() + { + if (!_tagNameToAdd.IsNullOrWhitespace()) + { + _tagHandler.AddSyncshellTag(_tagNameToAdd); + if (_group != null) + { + _tagHandler.AddTagToSyncshell(_group.GID, _tagNameToAdd); + } + _tagNameToAdd = string.Empty; + } + else + { + _tagNameToAdd = string.Empty; + } + } +} \ No newline at end of file diff --git a/LightlessSync/UI/DrawEntityFactory.cs b/LightlessSync/UI/DrawEntityFactory.cs index 01b6738..625d660 100644 --- a/LightlessSync/UI/DrawEntityFactory.cs +++ b/LightlessSync/UI/DrawEntityFactory.cs @@ -23,21 +23,25 @@ public class DrawEntityFactory private readonly PlayerPerformanceConfigService _playerPerformanceConfigService; private readonly CharaDataManager _charaDataManager; private readonly SelectTagForPairUi _selectTagForPairUi; - private readonly RenameTagUi _renameTagUi; + private readonly RenamePairTagUi _renamePairTagUi; + private readonly SelectTagForSyncshellUi _selectTagForSyncshellUi; + private readonly RenameSyncshellTagUi _renameSyncshellTagUi; + private readonly SelectSyncshellForTagUi _selectSyncshellForTagUi; private readonly TagHandler _tagHandler; private readonly IdDisplayHandler _uidDisplayHandler; public DrawEntityFactory(ILogger logger, ApiController apiController, IdDisplayHandler uidDisplayHandler, - SelectTagForPairUi selectTagForPairUi, RenameTagUi renameTagUi, LightlessMediator mediator, + SelectTagForPairUi selectTagForPairUi, RenamePairTagUi renamePairTagUi, LightlessMediator mediator, TagHandler tagHandler, SelectPairForTagUi selectPairForTagUi, ServerConfigurationManager serverConfigurationManager, UiSharedService uiSharedService, - PlayerPerformanceConfigService playerPerformanceConfigService, CharaDataManager charaDataManager) + PlayerPerformanceConfigService playerPerformanceConfigService, CharaDataManager charaDataManager, + SelectTagForSyncshellUi selectTagForSyncshellUi, RenameSyncshellTagUi renameSyncshellTagUi, SelectSyncshellForTagUi selectSyncshellForTagUi) { _logger = logger; _apiController = apiController; _uidDisplayHandler = uidDisplayHandler; _selectTagForPairUi = selectTagForPairUi; - _renameTagUi = renameTagUi; + _renamePairTagUi = renamePairTagUi; _mediator = mediator; _tagHandler = tagHandler; _selectPairForTagUi = selectPairForTagUi; @@ -45,6 +49,9 @@ public class DrawEntityFactory _uiSharedService = uiSharedService; _playerPerformanceConfigService = playerPerformanceConfigService; _charaDataManager = charaDataManager; + _selectTagForSyncshellUi = selectTagForSyncshellUi; + _renameSyncshellTagUi = renameSyncshellTagUi; + _selectSyncshellForTagUi = selectSyncshellForTagUi; } public DrawFolderGroup CreateDrawGroupFolder(GroupFullInfoDto groupFullInfoDto, @@ -53,7 +60,16 @@ public class DrawEntityFactory { return new DrawFolderGroup(groupFullInfoDto.Group.GID, groupFullInfoDto, _apiController, filteredPairs.Select(p => CreateDrawPair(groupFullInfoDto.Group.GID + p.Key.UserData.UID, p.Key, p.Value, groupFullInfoDto)).ToImmutableList(), - allPairs, _tagHandler, _uidDisplayHandler, _mediator, _uiSharedService); + allPairs, _tagHandler, _uidDisplayHandler, _mediator, _uiSharedService, _selectSyncshellForTagUi, _renameSyncshellTagUi, _selectTagForSyncshellUi, false); + } + + public DrawFolderGroup CreateDrawGroupFolder(GroupFullInfoDto groupFullInfoDto, + Dictionary> filteredPairs, + IImmutableList allPairs, string tag) + { + return new DrawFolderGroup(tag, groupFullInfoDto, _apiController, + filteredPairs.Select(p => CreateDrawPair(tag, p.Key, p.Value, groupFullInfoDto)).ToImmutableList(), + allPairs, _tagHandler, _uidDisplayHandler, _mediator, _uiSharedService, _selectSyncshellForTagUi, _renameSyncshellTagUi, _selectTagForSyncshellUi, true); } public DrawFolderTag CreateDrawTagFolder(string tag, @@ -61,7 +77,7 @@ public class DrawEntityFactory IImmutableList allPairs) { return new(tag, filteredPairs.Select(u => CreateDrawPair(tag, u.Key, u.Value, currentGroup: null)).ToImmutableList(), - allPairs, _tagHandler, _apiController, _selectPairForTagUi, _renameTagUi, _uiSharedService); + allPairs, _tagHandler, _apiController, _selectPairForTagUi, _renamePairTagUi, _uiSharedService); } public DrawUserPair CreateDrawPair(string id, Pair user, List groups, GroupFullInfoDto? currentGroup)