From 08e3b19f41fde58a32fad0e4838b55e7fa5e143f Mon Sep 17 00:00:00 2001 From: CakeAndBanana Date: Mon, 8 Sep 2025 22:21:36 +0200 Subject: [PATCH] Added new folder type for syncshells, changes in factory --- LightlessSync/UI/CompactUI.cs | 46 +++-- .../UI/Components/DrawFolderGroup.cs | 185 ++++++++---------- .../DrawGroupedSyncshellTagFolder.cs | 81 ++++++++ .../UI/Components/SelectTagForSyncshellUi.cs | 4 +- LightlessSync/UI/DrawEntityFactory.cs | 11 +- 5 files changed, 189 insertions(+), 138 deletions(-) create mode 100644 LightlessSync/UI/Components/DrawGroupedSyncshellTagFolder.cs diff --git a/LightlessSync/UI/CompactUI.cs b/LightlessSync/UI/CompactUI.cs index c15b8d4..659f542 100644 --- a/LightlessSync/UI/CompactUI.cs +++ b/LightlessSync/UI/CompactUI.cs @@ -7,6 +7,7 @@ using LightlessSync.API.Data.Extensions; using LightlessSync.API.Dto.Group; using LightlessSync.Interop.Ipc; using LightlessSync.LightlessConfiguration; +using LightlessSync.LightlessConfiguration.Configurations; using LightlessSync.PlayerData.Handlers; using LightlessSync.PlayerData.Pairs; using LightlessSync.Services; @@ -474,8 +475,6 @@ public class CompactUi : WindowMediatorSubscriberBase && (_configService.Current.ShowSyncshellUsersInVisible || !(!_configService.Current.ShowSyncshellUsersInVisible && !u.Key.IsDirectlyPaired)); bool FilterTagUsers(KeyValuePair> u, string tag) => u.Key.IsDirectlyPaired && !u.Key.IsOneSidedPair && _tagHandler.HasPairTag(u.Key.UserData.UID, tag); - bool FilterTagSyncshells(KeyValuePair> u, string tag) - => u.Key.IsDirectlyPaired && !u.Key.IsOneSidedPair && _tagHandler.HasSyncshellTag(u.Key.UserData.UID, tag); bool FilterGroupUsers(KeyValuePair> u, GroupFullInfoDto group) => u.Value.Exists(g => string.Equals(g.GID, group.GID, StringComparison.Ordinal)); bool FilterNotTaggedUsers(KeyValuePair> u) @@ -499,6 +498,7 @@ public class CompactUi : WindowMediatorSubscriberBase } List groupFolders = new(); + foreach (var group in _pairManager.GroupPairs.Select(g => g.Key).OrderBy(g => g.GroupAliasOrGID, StringComparer.OrdinalIgnoreCase)) { var allGroupPairs = ImmutablePairList(allPairs @@ -544,28 +544,36 @@ public class CompactUi : WindowMediatorSubscriberBase _logger.LogInformation($"Loading {syncshellTags.Count} syncshell tags"); foreach (var syncshelltag in syncshellTags) { + List syncshellFolderTags = new(); 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))); + if (_tagHandler.HasSyncshellTag(group.GID, syncshelltag)) + { + var allGroupPairs = ImmutablePairList(allPairs + .Where(u => FilterGroupUsers(u, group))); - 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)) + var filteredGroupPairs = filteredPairs + .Where(u => FilterOnlineOrPausedSelf(u)) + .OrderByDescending(u => u.Key.IsOnline) + .ThenBy(u => { - 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); + 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); + syncshellFolderTags.Add(_drawEntityFactory.CreateDrawGroupFolder(group, filteredGroupPairs, allGroupPairs)); + } + } - groupFolders.Add(_drawEntityFactory.CreateDrawGroupFolder(group, filteredGroupPairs, allGroupPairs, syncshelltag)); + if (syncshellFolderTags.Count > 0) + { + drawFolders.Add(new DrawGroupedSyncshellTagFolder(syncshelltag, syncshellFolderTags, _tagHandler, _uiSharedService)); } } diff --git a/LightlessSync/UI/Components/DrawFolderGroup.cs b/LightlessSync/UI/Components/DrawFolderGroup.cs index a3e9644..58d0a64 100644 --- a/LightlessSync/UI/Components/DrawFolderGroup.cs +++ b/LightlessSync/UI/Components/DrawFolderGroup.cs @@ -19,26 +19,18 @@ 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, - SelectSyncshellForTagUi selectSyncshellForTagUi, RenameSyncshellTagUi renameTagUi, SelectTagForSyncshellUi selectTagForSyncshellUi, - bool isTag) : + LightlessMediator lightlessMediator, UiSharedService uiSharedService, SelectTagForSyncshellUi selectTagForSyncshellUi) : 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; @@ -91,109 +83,88 @@ public class DrawFolderGroup : DrawFolderBase protected override void DrawMenu(float menuWidth) { - if (!_isTag) + ImGui.TextUnformatted("Syncshell Menu (" + _groupFullInfoDto.GroupAliasOrGID + ")"); + ImGui.Separator(); + + ImGui.TextUnformatted("General Syncshell Actions"); + if (_uiSharedService.IconTextButton(FontAwesomeIcon.Copy, "Copy ID", menuWidth, true)) { - ImGui.TextUnformatted("Syncshell Menu (" + _groupFullInfoDto.GroupAliasOrGID + ")"); - ImGui.Separator(); + ImGui.CloseCurrentPopup(); + ImGui.SetClipboardText(_groupFullInfoDto.GroupAliasOrGID); + } + UiSharedService.AttachToolTip("Copy Syncshell ID to Clipboard"); - 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.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 + if (_uiSharedService.IconTextButton(FontAwesomeIcon.StickyNote, "Copy Notes", menuWidth, true)) { - ImGui.TextUnformatted("Group Menu"); - if (_uiSharedService.IconTextButton(FontAwesomeIcon.Users, "Select Syncshells", menuWidth, isInPopup: 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)) { - _selectSyncshellForTagUi.Open(_id); + ImGui.CloseCurrentPopup(); + _lightlessMediator.Publish(new OpenSyncshellAdminPanel(_groupFullInfoDto)); } - 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/DrawGroupedSyncshellTagFolder.cs b/LightlessSync/UI/Components/DrawGroupedSyncshellTagFolder.cs new file mode 100644 index 0000000..85559ac --- /dev/null +++ b/LightlessSync/UI/Components/DrawGroupedSyncshellTagFolder.cs @@ -0,0 +1,81 @@ +using Dalamud.Bindings.ImGui; +using Dalamud.Interface; +using Dalamud.Interface.Utility.Raii; +using LightlessSync.UI.Handlers; +using System.Collections.Immutable; +using System.Numerics; + +namespace LightlessSync.UI.Components; + +public class DrawGroupedSyncshellTagFolder : IDrawFolder +{ + private readonly string _tag; + private readonly IEnumerable _groups; + private readonly TagHandler _tagHandler; + private readonly UiSharedService _uiSharedService; + private bool _wasHovered = false; + + public IImmutableList DrawPairs => throw new NotSupportedException(); + public int OnlinePairs => _groups.SelectMany(g => g.DrawPairs).Where(g => g.Pair.IsOnline).DistinctBy(g => g.Pair.UserData.UID).Count(); + public int TotalPairs => _groups.Sum(g => g.TotalPairs); + + public DrawGroupedSyncshellTagFolder(string tag, IEnumerable groups, TagHandler tagHandler, UiSharedService uiSharedService) + { + _tag = tag; + _groups = groups; + _tagHandler = tagHandler; + _uiSharedService = uiSharedService; + } + + public void Draw() + { + if (!_groups.Any()) return; + + string _id = $"__folder_{_tag}"; + using var id = ImRaii.PushId(_id); + var color = ImRaii.PushColor(ImGuiCol.ChildBg, ImGui.GetColorU32(ImGuiCol.FrameBgHovered), _wasHovered); + using (ImRaii.Child("folder__" + _id, new System.Numerics.Vector2(UiSharedService.GetWindowContentRegionWidth() - ImGui.GetCursorPosX(), ImGui.GetFrameHeight()))) + { + ImGui.Dummy(new Vector2(0f, ImGui.GetFrameHeight())); + using (ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(0f, 0f))) + ImGui.SameLine(); + + var icon = _tagHandler.IsTagOpen(_id) ? FontAwesomeIcon.CaretDown : FontAwesomeIcon.CaretRight; + ImGui.AlignTextToFramePadding(); + + _uiSharedService.IconText(icon); + if (ImGui.IsItemClicked()) + { + _tagHandler.SetTagOpen(_id, !_tagHandler.IsTagOpen(_id)); + } + + ImGui.SameLine(); + ImGui.AlignTextToFramePadding(); + _uiSharedService.IconText(FontAwesomeIcon.UsersRectangle); + using (ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemSpacing with { X = ImGui.GetStyle().ItemSpacing.X / 2f })) + { + ImGui.SameLine(); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted("[" + OnlinePairs.ToString() + "]"); + } + UiSharedService.AttachToolTip(OnlinePairs + " online in all of your joined syncshells" + Environment.NewLine + + TotalPairs + " pairs combined in all of your joined syncshells"); + ImGui.SameLine(); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted(_tag); + } + color.Dispose(); + _wasHovered = ImGui.IsItemHovered(); + + ImGui.Separator(); + + if (_tagHandler.IsTagOpen(_id)) + { + using var indent = ImRaii.PushIndent(20f); + foreach (var entry in _groups) + { + entry.Draw(); + } + } + } +} diff --git a/LightlessSync/UI/Components/SelectTagForSyncshellUi.cs b/LightlessSync/UI/Components/SelectTagForSyncshellUi.cs index 5473263..f9947e9 100644 --- a/LightlessSync/UI/Components/SelectTagForSyncshellUi.cs +++ b/LightlessSync/UI/Components/SelectTagForSyncshellUi.cs @@ -46,8 +46,8 @@ public class SelectTagForSyncshellUi return; } - var name = _group.GID; - var popupName = $"Choose Groups for {_group.GID}"; + var name = _group.GroupAliasOrGID; + var popupName = $"Choose Groups for {_group.GroupAliasOrGID}"; // Is the popup supposed to show but did not open yet? Open it if (_show) { diff --git a/LightlessSync/UI/DrawEntityFactory.cs b/LightlessSync/UI/DrawEntityFactory.cs index 625d660..ec5d368 100644 --- a/LightlessSync/UI/DrawEntityFactory.cs +++ b/LightlessSync/UI/DrawEntityFactory.cs @@ -60,16 +60,7 @@ 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, _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); + allPairs, _tagHandler, _uidDisplayHandler, _mediator, _uiSharedService, _selectTagForSyncshellUi); } public DrawFolderTag CreateDrawTagFolder(string tag,