From abe28e931cd9705145941dfc730854fca7f41fff Mon Sep 17 00:00:00 2001 From: defnotken Date: Thu, 11 Sep 2025 23:43:11 +0200 Subject: [PATCH] 1.11.6 (#4) 1.11.6 Changelog (In Progress) --- * Update submodule reference * Update dalamud sdk * Reworked the Syncshell Admin Page - Fixed that owners are visible in the list, Removed Pin/Remove/Ban buttons on Owners. - Styling is done similiar as settings page. - Added 1 or 3 day(s) option for inactive check. + Added new functions on the Server Top Bar button - Right click on the button will disconnect you from Lightless - Shift+Left click will open the settings page + Added colors section in the settings to change accent colors. - The nameplate coloring has been moved to this section + Added pin option from Dalamud in the UI. + Added ability to pause syncing while going in Instance/Duty + Added functionality to make syncshell folders + Fixed nameplate bug in PVP + added self-threshold warning Co-authored-by: defnotken Co-authored-by: CakeAndBanana Co-authored-by: thijmenh Co-authored-by: choco Co-authored-by: cake Co-authored-by: choco Reviewed-on: https://git.lightless-sync.org/Lightless-Sync/LightlessClient/pulls/4 --- .gitmodules | 2 +- LightlessAPI | 2 +- .../Configurations/LightlessConfig.cs | 3 +- .../Configurations/PairTagStorage.cs | 7 + .../Configurations/PlayerPerformanceConfig.cs | 1 + .../Configurations/ServerTagConfig.cs | 9 - .../Configurations/SyncshellTagStorage.cs | 7 + ...{ServerTagStorage.cs => PairTagStorage.cs} | 2 +- .../Models/SyncshellTagStorage.cs | 8 + ...nfigService.cs => PairTagConfigService.cs} | 4 +- .../SyncshellTagConfigService.cs | 14 + LightlessSync/LightlessPlugin.cs | 4 +- LightlessSync/LightlessSync.csproj | 4 +- .../PlayerData/Handlers/PairHandler.cs | 47 +- LightlessSync/Plugin.cs | 15 +- LightlessSync/Services/DalamudUtilService.cs | 23 +- LightlessSync/Services/Mediator/Messages.cs | 2 + LightlessSync/Services/NameplateService.cs | 6 +- .../ServerConfigurationManager.cs | 220 ++++-- LightlessSync/Services/UiFactory.cs | 1 - LightlessSync/Services/UiService.cs | 1 - .../UI/CharaDataHubUi.GposeTogether.cs | 4 +- LightlessSync/UI/CharaDataHubUi.McdOnline.cs | 30 +- .../UI/CharaDataHubUi.NearbyPoses.cs | 4 +- LightlessSync/UI/CharaDataHubUi.cs | 14 +- LightlessSync/UI/CompactUI.cs | 152 +++-- .../UI/Components/DrawFolderGroup.cs | 13 +- LightlessSync/UI/Components/DrawFolderTag.cs | 6 +- .../UI/Components/DrawGroupedGroupFolder.cs | 80 ++- LightlessSync/UI/Components/DrawUserPair.cs | 6 +- .../{RenameTagUi.cs => RenamePairTagUi.cs} | 32 +- .../UI/Components/RenameSyncshellTagUi.cs | 79 +++ .../UI/Components/SelectPairForTagUi.cs | 2 +- .../UI/Components/SelectSyncshellForTagUi.cs | 86 +++ .../UI/Components/SelectTagForPairUi.cs | 12 +- .../UI/Components/SelectTagForSyncshellUi.cs | 132 ++++ LightlessSync/UI/DataAnalysisUi.cs | 26 +- LightlessSync/UI/DrawEntityFactory.cs | 28 +- LightlessSync/UI/DtrEntry.cs | 43 +- LightlessSync/UI/EventViewerUI.cs | 4 +- LightlessSync/UI/Handlers/IdDisplayHandler.cs | 4 +- LightlessSync/UI/Handlers/TagHandler.cs | 155 ++++- LightlessSync/UI/IntroUI.cs | 10 +- LightlessSync/UI/JoinSyncshellUI.cs | 4 +- LightlessSync/UI/PopoutProfileUi.cs | 4 +- LightlessSync/UI/SettingsUi.cs | 148 ++-- LightlessSync/UI/StandaloneProfileUi.cs | 4 +- LightlessSync/UI/SyncshellAdminUI.cs | 632 ++++++++++-------- LightlessSync/UI/UIColors.cs | 76 ++- LightlessSync/UI/UISharedService.cs | 8 +- LightlessSync/packages.lock.json | 6 +- PenumbraAPI | 2 +- 52 files changed, 1557 insertions(+), 631 deletions(-) create mode 100644 LightlessSync/LightlessConfiguration/Configurations/PairTagStorage.cs delete mode 100644 LightlessSync/LightlessConfiguration/Configurations/ServerTagConfig.cs create mode 100644 LightlessSync/LightlessConfiguration/Configurations/SyncshellTagStorage.cs rename LightlessSync/LightlessConfiguration/Models/{ServerTagStorage.cs => PairTagStorage.cs} (92%) create mode 100644 LightlessSync/LightlessConfiguration/Models/SyncshellTagStorage.cs rename LightlessSync/LightlessConfiguration/{ServerTagConfigService.cs => PairTagConfigService.cs} (62%) create mode 100644 LightlessSync/LightlessConfiguration/SyncshellTagConfigService.cs rename LightlessSync/UI/Components/{RenameTagUi.cs => RenamePairTagUi.cs} (66%) 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/.gitmodules b/.gitmodules index b3983c8..fe64c7f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "LightlessAPI"] path = LightlessAPI - url = https://github.com/Light-Public-Syncshells/LightlessAPI + url = https://git.lightless-sync.org/Lightless-Sync/LightlessAPI.git [submodule "PenumbraAPI"] path = PenumbraAPI url = https://github.com/Ottermandias/Penumbra.Api.git diff --git a/LightlessAPI b/LightlessAPI index 3a69c94..a337481 160000 --- a/LightlessAPI +++ b/LightlessAPI @@ -1 +1 @@ -Subproject commit 3a69c94f7fb10a0cfc51c857b2194640fda48140 +Subproject commit a337481243a11490f3a115ca1ac0abfdd62c0554 diff --git a/LightlessSync/LightlessConfiguration/Configurations/LightlessConfig.cs b/LightlessSync/LightlessConfiguration/Configurations/LightlessConfig.cs index 660cddf..cfe5277 100644 --- a/LightlessSync/LightlessConfiguration/Configurations/LightlessConfig.cs +++ b/LightlessSync/LightlessConfiguration/Configurations/LightlessConfig.cs @@ -1,4 +1,4 @@ -using LightlessSync.LightlessConfiguration.Models; +using LightlessSync.LightlessConfiguration.Models; using LightlessSync.UI; using Microsoft.Extensions.Logging; @@ -15,6 +15,7 @@ public class LightlessConfig : ILightlessConfiguration public bool PreferNoteInDtrTooltip { get; set; } = false; public bool IsNameplateColorsEnabled { get; set; } = false; public DtrEntry.Colors NameplateColors { get; set; } = new(Foreground: 0xE69138u, Glow: 0xFFBA47u); + public Dictionary CustomUIColors { get; set; } = new(StringComparer.OrdinalIgnoreCase); public bool UseColorsInDtr { get; set; } = true; public DtrEntry.Colors DtrColorsDefault { get; set; } = default; public DtrEntry.Colors DtrColorsNotConnected { get; set; } = new(Glow: 0x0428FFu); diff --git a/LightlessSync/LightlessConfiguration/Configurations/PairTagStorage.cs b/LightlessSync/LightlessConfiguration/Configurations/PairTagStorage.cs new file mode 100644 index 0000000..37d1966 --- /dev/null +++ b/LightlessSync/LightlessConfiguration/Configurations/PairTagStorage.cs @@ -0,0 +1,7 @@ +namespace LightlessSync.LightlessConfiguration.Configurations; + +public class PairTagStorage : ILightlessConfiguration +{ + public Dictionary ServerTagStorage { get; set; } = new(StringComparer.OrdinalIgnoreCase); + public int Version { get; set; } = 0; +} \ No newline at end of file diff --git a/LightlessSync/LightlessConfiguration/Configurations/PlayerPerformanceConfig.cs b/LightlessSync/LightlessConfiguration/Configurations/PlayerPerformanceConfig.cs index f311a12..d87f3c3 100644 --- a/LightlessSync/LightlessConfiguration/Configurations/PlayerPerformanceConfig.cs +++ b/LightlessSync/LightlessConfiguration/Configurations/PlayerPerformanceConfig.cs @@ -13,4 +13,5 @@ public class PlayerPerformanceConfig : ILightlessConfiguration public int VRAMSizeAutoPauseThresholdMiB { get; set; } = 550; public int TrisAutoPauseThresholdThousands { get; set; } = 250; public List UIDsToIgnore { get; set; } = new(); + public bool PauseInInstanceDuty { get; set; } = false; } \ No newline at end of file diff --git a/LightlessSync/LightlessConfiguration/Configurations/ServerTagConfig.cs b/LightlessSync/LightlessConfiguration/Configurations/ServerTagConfig.cs deleted file mode 100644 index bdfe68f..0000000 --- a/LightlessSync/LightlessConfiguration/Configurations/ServerTagConfig.cs +++ /dev/null @@ -1,9 +0,0 @@ -using LightlessSync.LightlessConfiguration.Models; - -namespace LightlessSync.LightlessConfiguration.Configurations; - -public class ServerTagConfig : ILightlessConfiguration -{ - public Dictionary ServerTagStorage { get; set; } = new(StringComparer.OrdinalIgnoreCase); - public int Version { get; set; } = 0; -} \ No newline at end of file diff --git a/LightlessSync/LightlessConfiguration/Configurations/SyncshellTagStorage.cs b/LightlessSync/LightlessConfiguration/Configurations/SyncshellTagStorage.cs new file mode 100644 index 0000000..73cc9ec --- /dev/null +++ b/LightlessSync/LightlessConfiguration/Configurations/SyncshellTagStorage.cs @@ -0,0 +1,7 @@ +namespace LightlessSync.LightlessConfiguration.Configurations; + +public class SyncshellTagStorage : ILightlessConfiguration +{ + public Dictionary ServerTagStorage { get; set; } = new(StringComparer.OrdinalIgnoreCase); + public int Version { get; set; } = 0; +} \ No newline at end of file diff --git a/LightlessSync/LightlessConfiguration/Models/ServerTagStorage.cs b/LightlessSync/LightlessConfiguration/Models/PairTagStorage.cs similarity index 92% rename from LightlessSync/LightlessConfiguration/Models/ServerTagStorage.cs rename to LightlessSync/LightlessConfiguration/Models/PairTagStorage.cs index 75d3f46..b8edeb8 100644 --- a/LightlessSync/LightlessConfiguration/Models/ServerTagStorage.cs +++ b/LightlessSync/LightlessConfiguration/Models/PairTagStorage.cs @@ -1,7 +1,7 @@ namespace LightlessSync.LightlessConfiguration.Models; [Serializable] -public class ServerTagStorage +public class PairTagStorage { public HashSet OpenPairTags { get; set; } = new(StringComparer.Ordinal); public HashSet ServerAvailablePairTags { get; set; } = new(StringComparer.Ordinal); diff --git a/LightlessSync/LightlessConfiguration/Models/SyncshellTagStorage.cs b/LightlessSync/LightlessConfiguration/Models/SyncshellTagStorage.cs new file mode 100644 index 0000000..7a3d409 --- /dev/null +++ b/LightlessSync/LightlessConfiguration/Models/SyncshellTagStorage.cs @@ -0,0 +1,8 @@ +namespace LightlessSync.LightlessConfiguration.Models; + +[Serializable] +public class SyncshellTagStorage +{ + public HashSet ServerAvailableSyncshellTags { get; set; } = new(StringComparer.Ordinal); + public Dictionary> SyncshellPairedTags { get; set; } = new(StringComparer.Ordinal); +} diff --git a/LightlessSync/LightlessConfiguration/ServerTagConfigService.cs b/LightlessSync/LightlessConfiguration/PairTagConfigService.cs similarity index 62% rename from LightlessSync/LightlessConfiguration/ServerTagConfigService.cs rename to LightlessSync/LightlessConfiguration/PairTagConfigService.cs index b31e746..cc4c559 100644 --- a/LightlessSync/LightlessConfiguration/ServerTagConfigService.cs +++ b/LightlessSync/LightlessConfiguration/PairTagConfigService.cs @@ -2,11 +2,11 @@ namespace LightlessSync.LightlessConfiguration; -public class ServerTagConfigService : ConfigurationServiceBase +public class PairTagConfigService : ConfigurationServiceBase { public const string ConfigName = "servertags.json"; - public ServerTagConfigService(string configDir) : base(configDir) + public PairTagConfigService(string configDir) : base(configDir) { } diff --git a/LightlessSync/LightlessConfiguration/SyncshellTagConfigService.cs b/LightlessSync/LightlessConfiguration/SyncshellTagConfigService.cs new file mode 100644 index 0000000..b62bf7c --- /dev/null +++ b/LightlessSync/LightlessConfiguration/SyncshellTagConfigService.cs @@ -0,0 +1,14 @@ +using LightlessSync.LightlessConfiguration.Configurations; + +namespace LightlessSync.LightlessConfiguration; + +public class SyncshellTagConfigService : ConfigurationServiceBase +{ + public const string ConfigName = "syncshelltags.json"; + + public SyncshellTagConfigService(string configDir) : base(configDir) + { + } + + public override string ConfigurationName => ConfigName; +} \ No newline at end of file diff --git a/LightlessSync/LightlessPlugin.cs b/LightlessSync/LightlessPlugin.cs index 85582a0..66d2c2b 100644 --- a/LightlessSync/LightlessPlugin.cs +++ b/LightlessSync/LightlessPlugin.cs @@ -1,10 +1,11 @@ -using LightlessSync.FileCache; +using LightlessSync.FileCache; using LightlessSync.LightlessConfiguration; using LightlessSync.PlayerData.Pairs; using LightlessSync.PlayerData.Services; using LightlessSync.Services; using LightlessSync.Services.Mediator; using LightlessSync.Services.ServerConfiguration; +using LightlessSync.UI; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -98,6 +99,7 @@ public class LightlessPlugin : MediatorSubscriberBase, IHostedService Mediator.Subscribe(this, (_) => DalamudUtilOnLogIn()); Mediator.Subscribe(this, (_) => DalamudUtilOnLogOut()); + UIColors.Initialize(_lightlessConfigService); Mediator.StartQueueProcessing(); return Task.CompletedTask; diff --git a/LightlessSync/LightlessSync.csproj b/LightlessSync/LightlessSync.csproj index 548517f..8c54253 100644 --- a/LightlessSync/LightlessSync.csproj +++ b/LightlessSync/LightlessSync.csproj @@ -1,9 +1,9 @@  - + - 1.11.5 + 1.11.6 https://github.com/Light-Public-Syncshells/LightlessClient diff --git a/LightlessSync/PlayerData/Handlers/PairHandler.cs b/LightlessSync/PlayerData/Handlers/PairHandler.cs index 2153f87..edde269 100644 --- a/LightlessSync/PlayerData/Handlers/PairHandler.cs +++ b/LightlessSync/PlayerData/Handlers/PairHandler.cs @@ -90,18 +90,20 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase }); Mediator.Subscribe(this, (msg) => { - if (IsVisible && _dataReceivedInDowntime != null) - { - ApplyCharacterData(_dataReceivedInDowntime.ApplicationId, - _dataReceivedInDowntime.CharacterData, _dataReceivedInDowntime.Forced); - _dataReceivedInDowntime = null; - } + EnableSync(); }); Mediator.Subscribe(this, _ => { - _dataReceivedInDowntime = null; - _downloadCancellationTokenSource = _downloadCancellationTokenSource?.CancelRecreate(); - _applicationCancellationTokenSource = _applicationCancellationTokenSource?.CancelRecreate(); + DisableSync(); + }); + Mediator.Subscribe(this, _ => + { + DisableSync(); + }); + Mediator.Subscribe(this, (msg) => + { + EnableSync(); + }); LastAppliedDataBytes = -1; @@ -145,6 +147,16 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase return; } + if (_dalamudUtil.IsInInstance) + { + Mediator.Publish(new EventMessage(new Event(PlayerName, Pair.UserData, nameof(PairHandler), EventSeverity.Warning, + "Cannot apply character data: you are in an instance, deferring application"))); + Logger.LogDebug("[BASE-{appBase}] Received data but player is in instance", applicationBase); + _dataReceivedInDowntime = new(applicationBase, characterData, forceApplyCustomization); + SetUploading(isUploading: false); + return; + } + if (_charaHandler == null || (PlayerCharacter == IntPtr.Zero)) { Mediator.Publish(new EventMessage(new Event(PlayerName, Pair.UserData, nameof(PairHandler), EventSeverity.Warning, @@ -716,4 +728,21 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase Logger.LogDebug("[BASE-{appBase}] ModdedPaths calculated in {time}ms, missing files: {count}, total files: {total}", applicationBase, st.ElapsedMilliseconds, missingFiles.Count, moddedDictionary.Keys.Count); return [.. missingFiles]; } + + private void DisableSync() + { + _dataReceivedInDowntime = null; + _downloadCancellationTokenSource = _downloadCancellationTokenSource?.CancelRecreate(); + _applicationCancellationTokenSource = _applicationCancellationTokenSource?.CancelRecreate(); + } + + private void EnableSync() + { + if (IsVisible && _dataReceivedInDowntime != null) + { + ApplyCharacterData(_dataReceivedInDowntime.ApplicationId, + _dataReceivedInDowntime.CharacterData, _dataReceivedInDowntime.Forced); + _dataReceivedInDowntime = null; + } + } } \ No newline at end of file diff --git a/LightlessSync/Plugin.cs b/LightlessSync/Plugin.cs index 7e00038..4f704f5 100644 --- a/LightlessSync/Plugin.cs +++ b/LightlessSync/Plugin.cs @@ -130,15 +130,17 @@ 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>(), clientState, objectTable, framework, gameGui, condition, gameData, targetManager, gameConfig, s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), - s.GetRequiredService())); + s.GetRequiredService(), s.GetRequiredService())); collection.AddSingleton((s) => new DtrEntry(s.GetRequiredService>(), dtrBar, s.GetRequiredService(), - s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService())); + s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService())); collection.AddSingleton(s => new PairManager(s.GetRequiredService>(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), contextMenu)); collection.AddSingleton(); @@ -175,7 +177,8 @@ public sealed class Plugin : IDalamudPlugin collection.AddSingleton((s) => new LightlessConfigService(pluginInterface.ConfigDirectory.FullName)); collection.AddSingleton((s) => new ServerConfigService(pluginInterface.ConfigDirectory.FullName)); collection.AddSingleton((s) => new NotesConfigService(pluginInterface.ConfigDirectory.FullName)); - collection.AddSingleton((s) => new ServerTagConfigService(pluginInterface.ConfigDirectory.FullName)); + collection.AddSingleton((s) => new PairTagConfigService(pluginInterface.ConfigDirectory.FullName)); + collection.AddSingleton((s) => new SyncshellTagConfigService(pluginInterface.ConfigDirectory.FullName)); collection.AddSingleton((s) => new TransientConfigService(pluginInterface.ConfigDirectory.FullName)); collection.AddSingleton((s) => new XivDataStorageService(pluginInterface.ConfigDirectory.FullName)); collection.AddSingleton((s) => new PlayerPerformanceConfigService(pluginInterface.ConfigDirectory.FullName)); @@ -183,7 +186,8 @@ public sealed class Plugin : IDalamudPlugin collection.AddSingleton>(s => s.GetRequiredService()); collection.AddSingleton>(s => s.GetRequiredService()); collection.AddSingleton>(s => s.GetRequiredService()); - collection.AddSingleton>(s => s.GetRequiredService()); + collection.AddSingleton>(s => s.GetRequiredService()); + collection.AddSingleton>(s => s.GetRequiredService()); collection.AddSingleton>(s => s.GetRequiredService()); collection.AddSingleton>(s => s.GetRequiredService()); collection.AddSingleton>(s => s.GetRequiredService()); @@ -198,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/DalamudUtilService.cs b/LightlessSync/Services/DalamudUtilService.cs index d4d79e0..4c2bdd8 100644 --- a/LightlessSync/Services/DalamudUtilService.cs +++ b/LightlessSync/Services/DalamudUtilService.cs @@ -39,6 +39,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber private readonly IObjectTable _objectTable; private readonly PerformanceCollectorService _performanceCollector; private readonly LightlessConfigService _configService; + private readonly PlayerPerformanceConfigService _playerPerformanceConfigService; private uint? _classJobId = 0; private DateTime _delayedFrameworkUpdateCheck = DateTime.UtcNow; private string _lastGlobalBlockPlayer = string.Empty; @@ -52,7 +53,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber public DalamudUtilService(ILogger logger, IClientState clientState, IObjectTable objectTable, IFramework framework, IGameGui gameGui, ICondition condition, IDataManager gameData, ITargetManager targetManager, IGameConfig gameConfig, BlockedCharacterHandler blockedCharacterHandler, LightlessMediator mediator, PerformanceCollectorService performanceCollector, - LightlessConfigService configService) + LightlessConfigService configService, PlayerPerformanceConfigService playerPerformanceConfigService) { _logger = logger; _clientState = clientState; @@ -66,6 +67,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber Mediator = mediator; _performanceCollector = performanceCollector; _configService = configService; + _playerPerformanceConfigService = playerPerformanceConfigService; WorldData = new(() => { return gameData.GetExcelSheet(Dalamud.Game.ClientLanguage.English)! @@ -161,6 +163,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber public bool IsOnFrameworkThread => _framework.IsInFrameworkUpdateThread; public bool IsZoning => _condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51]; public bool IsInCombatOrPerforming { get; private set; } = false; + public bool IsInInstance { get; private set; } = false; public bool HasModifiedGameFiles => _gameData.HasModifiedGameDataFiles; public uint ClassJobId => _classJobId!.Value; public Lazy> JobData { get; private set; } @@ -667,20 +670,34 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber Mediator.Publish(new GposeEndMessage()); } - if ((_condition[ConditionFlag.Performing] || _condition[ConditionFlag.InCombat]) && !IsInCombatOrPerforming) + if ((_condition[ConditionFlag.Performing] || _condition[ConditionFlag.InCombat]) && !IsInCombatOrPerforming && (_condition[ConditionFlag.BoundByDuty] && !_playerPerformanceConfigService.Current.PauseInInstanceDuty)) { _logger.LogDebug("Combat/Performance start"); IsInCombatOrPerforming = true; Mediator.Publish(new CombatOrPerformanceStartMessage()); Mediator.Publish(new HaltScanMessage(nameof(IsInCombatOrPerforming))); } - else if ((!_condition[ConditionFlag.Performing] && !_condition[ConditionFlag.InCombat]) && IsInCombatOrPerforming) + else if ((!_condition[ConditionFlag.Performing] && !_condition[ConditionFlag.InCombat]) && IsInCombatOrPerforming && (_condition[ConditionFlag.BoundByDuty] && !_playerPerformanceConfigService.Current.PauseInInstanceDuty)) { _logger.LogDebug("Combat/Performance end"); IsInCombatOrPerforming = false; Mediator.Publish(new CombatOrPerformanceEndMessage()); Mediator.Publish(new ResumeScanMessage(nameof(IsInCombatOrPerforming))); } + if ((_condition[ConditionFlag.BoundByDuty]) && !IsInInstance && _playerPerformanceConfigService.Current.PauseInInstanceDuty) + { + _logger.LogDebug("Instance start"); + IsInInstance = true; + Mediator.Publish(new InstanceOrDutyStartMessage()); + Mediator.Publish(new HaltScanMessage(nameof(IsInInstance))); + } + else if (((!_condition[ConditionFlag.BoundByDuty]) && IsInInstance && _playerPerformanceConfigService.Current.PauseInInstanceDuty) || ((_condition[ConditionFlag.BoundByDuty]) && IsInInstance && !_playerPerformanceConfigService.Current.PauseInInstanceDuty)) + { + _logger.LogDebug("Instance end"); + IsInInstance = false; + Mediator.Publish(new InstanceOrDutyEndMessage()); + Mediator.Publish(new ResumeScanMessage(nameof(IsInInstance))); + } if (_condition[ConditionFlag.WatchingCutscene] && !IsInCutscene) { diff --git a/LightlessSync/Services/Mediator/Messages.cs b/LightlessSync/Services/Mediator/Messages.cs index 0f52ec5..ea800d2 100644 --- a/LightlessSync/Services/Mediator/Messages.cs +++ b/LightlessSync/Services/Mediator/Messages.cs @@ -81,6 +81,8 @@ public record CensusUpdateMessage(byte Gender, byte RaceId, byte TribeId) : Mess public record TargetPairMessage(Pair Pair) : MessageBase; public record CombatOrPerformanceStartMessage : MessageBase; public record CombatOrPerformanceEndMessage : MessageBase; +public record InstanceOrDutyStartMessage : MessageBase; +public record InstanceOrDutyEndMessage : MessageBase; public record EventMessage(Event Event) : MessageBase; public record PenumbraDirectoryChangedMessage(string? ModDirectory) : MessageBase; public record PenumbraRedrawCharacterMessage(ICharacter Character) : SameThreadMessage; diff --git a/LightlessSync/Services/NameplateService.cs b/LightlessSync/Services/NameplateService.cs index d83643a..400ae23 100644 --- a/LightlessSync/Services/NameplateService.cs +++ b/LightlessSync/Services/NameplateService.cs @@ -17,6 +17,7 @@ public class NameplateService : DisposableMediatorSubscriberBase private readonly INamePlateGui _namePlateGui; private readonly PairManager _pairManager; + public NameplateService(ILogger logger, LightlessConfigService configService, INamePlateGui namePlateGui, @@ -36,11 +37,9 @@ public class NameplateService : DisposableMediatorSubscriberBase private void OnNamePlateUpdate(INamePlateUpdateContext context, IReadOnlyList handlers) { - - if (!_configService.Current.IsNameplateColorsEnabled && !_clientState.IsPvPExcludingDen) return; + if (!_configService.Current.IsNameplateColorsEnabled || (_configService.Current.IsNameplateColorsEnabled && _clientState.IsPvPExcludingDen)) return; var visibleUsersIds = _pairManager.GetOnlineUserPairs().Where(u => u.IsVisible && u.PlayerCharacterId != uint.MaxValue).Select(u => (ulong)u.PlayerCharacterId).ToHashSet(); var colors = _configService.Current.NameplateColors; - foreach (var handler in handlers) { var playerCharacter = handler.PlayerCharacter; @@ -63,6 +62,7 @@ public class NameplateService : DisposableMediatorSubscriberBase public void RequestRedraw() { + _namePlateGui.RequestRedraw(); } diff --git a/LightlessSync/Services/ServerConfiguration/ServerConfigurationManager.cs b/LightlessSync/Services/ServerConfiguration/ServerConfigurationManager.cs index 8bff7f9..67c16e5 100644 --- a/LightlessSync/Services/ServerConfiguration/ServerConfigurationManager.cs +++ b/LightlessSync/Services/ServerConfiguration/ServerConfigurationManager.cs @@ -23,15 +23,18 @@ public class ServerConfigurationManager private readonly ILogger _logger; private readonly LightlessMediator _lightlessMediator; private readonly NotesConfigService _notesConfig; - private readonly ServerTagConfigService _serverTagConfig; + private readonly PairTagConfigService _pairTagConfig; + private readonly SyncshellTagConfigService _syncshellTagConfig; + private readonly int _maxCharactersFolder = 20; public ServerConfigurationManager(ILogger logger, ServerConfigService configService, - ServerTagConfigService serverTagConfig, NotesConfigService notesConfig, DalamudUtilService dalamudUtil, + PairTagConfigService pairTagConfig, SyncshellTagConfigService syncshellTagConfig, NotesConfigService notesConfig, DalamudUtilService dalamudUtil, LightlessConfigService lightlessConfigService, HttpClient httpClient, LightlessMediator lightlessMediator) { _logger = logger; _configService = configService; - _serverTagConfig = serverTagConfig; + _pairTagConfig = pairTagConfig; + _syncshellTagConfig = syncshellTagConfig; _notesConfig = notesConfig; _dalamudUtil = dalamudUtil; _lightlessConfigService = lightlessConfigService; @@ -258,7 +261,7 @@ public class ServerConfigurationManager { if (serverSelectionIndex == -1) serverSelectionIndex = CurrentServerIndex; var server = GetServerByIndex(serverSelectionIndex); - if (server.Authentications.Any(c => string.Equals(c.CharacterName, _dalamudUtil.GetPlayerNameAsync().GetAwaiter().GetResult(), StringComparison.Ordinal) + if (server.Authentications.Exists(c => string.Equals(c.CharacterName, _dalamudUtil.GetPlayerNameAsync().GetAwaiter().GetResult(), StringComparison.Ordinal) && c.WorldId == _dalamudUtil.GetHomeWorldIdAsync().GetAwaiter().GetResult())) return; @@ -277,15 +280,15 @@ public class ServerConfigurationManager var server = GetServerByIndex(serverSelectionIndex); server.Authentications.Add(new Authentication() { - SecretKeyIdx = server.SecretKeys.Any() ? server.SecretKeys.First().Key : -1, + SecretKeyIdx = server.SecretKeys.Count != 0 ? server.SecretKeys.First().Key : -1, }); Save(); } internal void AddOpenPairTag(string tag) { - CurrentServerTagStorage().OpenPairTags.Add(tag); - _serverTagConfig.Save(); + CurrentPairTagStorage().OpenPairTags.Add(tag); + _pairTagConfig.Save(); } internal void AddServer(ServerStorage serverStorage) @@ -294,36 +297,79 @@ public class ServerConfigurationManager Save(); } - internal void AddTag(string tag) + internal void AddPairTag(string tag) { - CurrentServerTagStorage().ServerAvailablePairTags.Add(tag); - _serverTagConfig.Save(); - _lightlessMediator.Publish(new RefreshUiMessage()); + if (tag.Length > _maxCharactersFolder) + { + CurrentPairTagStorage().ServerAvailablePairTags.Add(tag); + _pairTagConfig.Save(); + _lightlessMediator.Publish(new RefreshUiMessage()); + } + else + { + _logger.LogInformation("Couldn't save/add {tag}. Name too long to be saved", tag); + } + } + + internal void AddSyncshellTag(string tag) + { + if (tag.Length > _maxCharactersFolder) + { + CurrentSyncshellTagStorage().ServerAvailableSyncshellTags.Add(tag); + _syncshellTagConfig.Save(); + _lightlessMediator.Publish(new RefreshUiMessage()); + } + else + { + _logger.LogInformation("Couldn't save/add {tag}. Name too long to be saved", tag); + } } internal void AddTagForUid(string uid, string tagName) { - if (CurrentServerTagStorage().UidServerPairedUserTags.TryGetValue(uid, out var tags)) + if (CurrentPairTagStorage().UidServerPairedUserTags.TryGetValue(uid, out var tags)) { tags.Add(tagName); _lightlessMediator.Publish(new RefreshUiMessage()); } else { - CurrentServerTagStorage().UidServerPairedUserTags[uid] = [tagName]; + CurrentPairTagStorage().UidServerPairedUserTags[uid] = [tagName]; } - _serverTagConfig.Save(); + _pairTagConfig.Save(); } - internal bool ContainsOpenPairTag(string tag) + internal void AddTagForSyncshell(string syncshellName, string tagName) { - return CurrentServerTagStorage().OpenPairTags.Contains(tag); + if (CurrentSyncshellTagStorage().SyncshellPairedTags.TryGetValue(syncshellName, out var tags)) + { + tags.Add(tagName); + _lightlessMediator.Publish(new RefreshUiMessage()); + } + else + { + CurrentSyncshellTagStorage().SyncshellPairedTags[syncshellName] = [tagName]; + } + + _syncshellTagConfig.Save(); } - internal bool ContainsTag(string uid, string tag) + internal bool ContainsOpenPairTag(string tag) => CurrentPairTagStorage().OpenPairTags.Contains(tag); + + internal bool ContainsPairTag(string uid, string tag) { - if (CurrentServerTagStorage().UidServerPairedUserTags.TryGetValue(uid, out var tags)) + if (CurrentPairTagStorage().UidServerPairedUserTags.TryGetValue(uid, out var tags)) + { + return tags.Contains(tag, StringComparer.Ordinal); + } + + return false; + } + + internal bool ContainsSyncshellTag(string name, string tag) + { + if (CurrentSyncshellTagStorage().SyncshellPairedTags.TryGetValue(name, out var tags)) { return tags.Contains(tag, StringComparer.Ordinal); } @@ -364,30 +410,19 @@ public class ServerConfigurationManager return null; } - internal HashSet GetServerAvailablePairTags() - { - return CurrentServerTagStorage().ServerAvailablePairTags; - } + internal HashSet GetServerAvailablePairTags() => CurrentPairTagStorage().ServerAvailablePairTags; - internal Dictionary> GetUidServerPairedUserTags() - { - return CurrentServerTagStorage().UidServerPairedUserTags; - } + internal HashSet GetServerAvailableSyncshellTags() => CurrentSyncshellTagStorage().ServerAvailableSyncshellTags; - internal HashSet GetUidsForTag(string tag) - { - return CurrentServerTagStorage().UidServerPairedUserTags.Where(p => p.Value.Contains(tag, StringComparer.Ordinal)).Select(p => p.Key).ToHashSet(StringComparer.Ordinal); - } + internal Dictionary> GetUidServerPairedUserTags() => CurrentPairTagStorage().UidServerPairedUserTags; - internal bool HasTags(string uid) - { - if (CurrentServerTagStorage().UidServerPairedUserTags.TryGetValue(uid, out var tags)) - { - return tags.Any(); - } + internal HashSet GetUidsForPairTag(string tag) => CurrentPairTagStorage().UidServerPairedUserTags.Where(p => p.Value.Contains(tag, StringComparer.Ordinal)).Select(p => p.Key).ToHashSet(StringComparer.Ordinal); - return false; - } + internal HashSet GetNamesForSyncshellTag(string tag) => CurrentSyncshellTagStorage().SyncshellPairedTags.Where(p => p.Value.Contains(tag, StringComparer.Ordinal)).Select(p => p.Key).ToHashSet(StringComparer.Ordinal); + + internal bool HasPairTags(string uid) => CurrentPairTagStorage().UidServerPairedUserTags.TryGetValue(uid, out var tags) && tags.Count != 0; + + internal bool HasSyncshellTags(string name) => CurrentSyncshellTagStorage().SyncshellPairedTags.TryGetValue(name, out var tags) && tags.Count != 0; internal void RemoveCharacterFromServer(int serverSelectionIndex, Authentication item) { @@ -398,51 +433,96 @@ public class ServerConfigurationManager internal void RemoveOpenPairTag(string tag) { - CurrentServerTagStorage().OpenPairTags.Remove(tag); - _serverTagConfig.Save(); + CurrentPairTagStorage().OpenPairTags.Remove(tag); + _pairTagConfig.Save(); } - internal void RemoveTag(string tag) + internal void RemovePairTag(string tag) { - CurrentServerTagStorage().ServerAvailablePairTags.Remove(tag); - foreach (var uid in GetUidsForTag(tag)) - { - RemoveTagForUid(uid, tag, save: false); - } - _serverTagConfig.Save(); + RemoveTag(CurrentPairTagStorage().ServerAvailablePairTags, tag); + _pairTagConfig.Save(); _lightlessMediator.Publish(new RefreshUiMessage()); } + internal void RemoveSyncshellTag(string tag) + { + RemoveTag(CurrentSyncshellTagStorage().ServerAvailableSyncshellTags, tag, true); + _syncshellTagConfig.Save(); + _lightlessMediator.Publish(new RefreshUiMessage()); + } + + internal void RemoveTag(HashSet storage, string tag, bool syncshell = false) + { + storage.Remove(tag); + if (syncshell) + { + foreach (var uid in GetNamesForSyncshellTag(tag)) + { + RemoveTagForSyncshell(uid, tag, save: false); + } + } + else + { + foreach (var uid in GetUidsForPairTag(tag)) + { + RemoveTagForUid(uid, tag, save: false); + } + } + } + internal void RemoveTagForUid(string uid, string tagName, bool save = true) { - if (CurrentServerTagStorage().UidServerPairedUserTags.TryGetValue(uid, out var tags)) + if (CurrentPairTagStorage().UidServerPairedUserTags.TryGetValue(uid, out var tags)) { tags.Remove(tagName); if (save) { - _serverTagConfig.Save(); + _pairTagConfig.Save(); _lightlessMediator.Publish(new RefreshUiMessage()); } } } - internal void RenameTag(string oldName, string newName) + internal void RemoveTagForSyncshell(string name, string tagName, bool save = true) { - CurrentServerTagStorage().ServerAvailablePairTags.Remove(oldName); - CurrentServerTagStorage().ServerAvailablePairTags.Add(newName); - foreach (var existingTags in CurrentServerTagStorage().UidServerPairedUserTags.Select(k => k.Value)) + if (CurrentSyncshellTagStorage().SyncshellPairedTags.TryGetValue(name, out var tags)) { - if (existingTags.Remove(oldName)) - existingTags.Add(newName); + tags.Remove(tagName); + + if (save) + { + _syncshellTagConfig.Save(); + _lightlessMediator.Publish(new RefreshUiMessage()); + } } } - internal void SaveNotes() + internal void RenamePairTag(string oldName, string newName) => RenameTag(CurrentPairTagStorage().UidServerPairedUserTags, CurrentPairTagStorage().ServerAvailablePairTags, oldName, newName); + + internal void RenameSyncshellTag(string oldName, string newName) => RenameTag(CurrentSyncshellTagStorage().SyncshellPairedTags, CurrentSyncshellTagStorage().ServerAvailableSyncshellTags, oldName, newName); + + internal void RenameTag(Dictionary> tags, HashSet storage, string oldName, string newName) { - _notesConfig.Save(); + if (newName.Length > _maxCharactersFolder) + { + storage.Remove(oldName); + storage.Add(newName); + foreach (var existingTags in tags.Select(k => k.Value)) + { + if (existingTags.Remove(oldName)) + existingTags.Add(newName); + } + _lightlessMediator.Publish(new RefreshUiMessage()); + } + else + { + _logger.LogInformation("Couldn't save/add {tag}. Name too long to be saved", newName); + } } + internal void SaveNotes() => _notesConfig.Save(); + internal void SetNoteForGid(string gid, string note, bool save = true) { if (string.IsNullOrEmpty(gid)) return; @@ -476,10 +556,16 @@ public class ServerConfigurationManager return _notesConfig.Current.ServerNotes[CurrentApiUrl]; } - private ServerTagStorage CurrentServerTagStorage() + private PairTagStorage CurrentPairTagStorage() { - TryCreateCurrentServerTagStorage(); - return _serverTagConfig.Current.ServerTagStorage[CurrentApiUrl]; + TryCreateCurrentPairTagStorage(); + return _pairTagConfig.Current.ServerTagStorage[CurrentApiUrl]; + } + + private SyncshellTagStorage CurrentSyncshellTagStorage() + { + TryCreateCurrentSyncshellTagStorage(); + return _syncshellTagConfig.Current.ServerTagStorage[CurrentApiUrl]; } private void EnsureMainExists() @@ -499,11 +585,19 @@ public class ServerConfigurationManager } } - private void TryCreateCurrentServerTagStorage() + private void TryCreateCurrentPairTagStorage() { - if (!_serverTagConfig.Current.ServerTagStorage.ContainsKey(CurrentApiUrl)) + if (!_pairTagConfig.Current.ServerTagStorage.ContainsKey(CurrentApiUrl)) { - _serverTagConfig.Current.ServerTagStorage[CurrentApiUrl] = new(); + _pairTagConfig.Current.ServerTagStorage[CurrentApiUrl] = new(); + } + } + + private void TryCreateCurrentSyncshellTagStorage() + { + if (!_syncshellTagConfig.Current.ServerTagStorage.ContainsKey(CurrentApiUrl)) + { + _syncshellTagConfig.Current.ServerTagStorage[CurrentApiUrl] = new(); } } diff --git a/LightlessSync/Services/UiFactory.cs b/LightlessSync/Services/UiFactory.cs index aaa7f96..5aaab5e 100644 --- a/LightlessSync/Services/UiFactory.cs +++ b/LightlessSync/Services/UiFactory.cs @@ -3,7 +3,6 @@ using LightlessSync.PlayerData.Pairs; using LightlessSync.Services.Mediator; using LightlessSync.Services.ServerConfiguration; using LightlessSync.UI; -using LightlessSync.UI.Components.Popup; using LightlessSync.WebAPI; using Microsoft.Extensions.Logging; diff --git a/LightlessSync/Services/UiService.cs b/LightlessSync/Services/UiService.cs index 0aceb78..4071b61 100644 --- a/LightlessSync/Services/UiService.cs +++ b/LightlessSync/Services/UiService.cs @@ -4,7 +4,6 @@ using Dalamud.Interface.Windowing; using LightlessSync.LightlessConfiguration; using LightlessSync.Services.Mediator; using LightlessSync.UI; -using LightlessSync.UI.Components.Popup; using Microsoft.Extensions.Logging; namespace LightlessSync.Services; diff --git a/LightlessSync/UI/CharaDataHubUi.GposeTogether.cs b/LightlessSync/UI/CharaDataHubUi.GposeTogether.cs index af9b602..0416ab7 100644 --- a/LightlessSync/UI/CharaDataHubUi.GposeTogether.cs +++ b/LightlessSync/UI/CharaDataHubUi.GposeTogether.cs @@ -90,7 +90,7 @@ internal sealed partial class CharaDataHubUi if (!_uiSharedService.IsInGpose) { ImGuiHelpers.ScaledDummy(5); - UiSharedService.DrawGroupedCenteredColorText("Assigning users to characters is only available in GPose.", ImGuiColors.DalamudYellow, 300); + UiSharedService.DrawGroupedCenteredColorText("Assigning users to characters is only available in GPose.", UIColors.Get("LightlessYellow"), 300); } UiSharedService.DistanceSeparator(); ImGui.TextUnformatted("Users In Lobby"); @@ -104,7 +104,7 @@ internal sealed partial class CharaDataHubUi if (!_charaDataGposeTogetherManager.UsersInLobby.Any() && !string.IsNullOrEmpty(_charaDataGposeTogetherManager.CurrentGPoseLobbyId)) { - UiSharedService.DrawGroupedCenteredColorText("No other users in current GPose lobby", ImGuiColors.DalamudYellow); + UiSharedService.DrawGroupedCenteredColorText("No other users in current GPose lobby", UIColors.Get("LightlessYellow")); } else { diff --git a/LightlessSync/UI/CharaDataHubUi.McdOnline.cs b/LightlessSync/UI/CharaDataHubUi.McdOnline.cs index ed2c624..e86ef10 100644 --- a/LightlessSync/UI/CharaDataHubUi.McdOnline.cs +++ b/LightlessSync/UI/CharaDataHubUi.McdOnline.cs @@ -23,7 +23,7 @@ internal sealed partial class CharaDataHubUi if (dataDto == null) { ImGuiHelpers.ScaledDummy(5); - UiSharedService.DrawGroupedCenteredColorText("Select an entry above to edit its data.", ImGuiColors.DalamudYellow); + UiSharedService.DrawGroupedCenteredColorText("Select an entry above to edit its data.", UIColors.Get("LightlessYellow")); return; } @@ -31,7 +31,7 @@ internal sealed partial class CharaDataHubUi if (updateDto == null) { - UiSharedService.DrawGroupedCenteredColorText("Something went awfully wrong and there's no update DTO. Try updating Character Data via the button above.", ImGuiColors.DalamudYellow); + UiSharedService.DrawGroupedCenteredColorText("Something went awfully wrong and there's no update DTO. Try updating Character Data via the button above.", UIColors.Get("LightlessYellow")); return; } @@ -75,7 +75,7 @@ internal sealed partial class CharaDataHubUi } if (_charaDataManager.CharaUpdateTask != null && !_charaDataManager.CharaUpdateTask.IsCompleted) { - UiSharedService.ColorTextWrapped("Updating data on server, please wait.", ImGuiColors.DalamudYellow); + UiSharedService.ColorTextWrapped("Updating data on server, please wait.", UIColors.Get("LightlessYellow")); } } @@ -85,7 +85,7 @@ internal sealed partial class CharaDataHubUi { if (_charaDataManager.UploadProgress != null) { - UiSharedService.ColorTextWrapped(_charaDataManager.UploadProgress.Value ?? string.Empty, ImGuiColors.DalamudYellow); + UiSharedService.ColorTextWrapped(_charaDataManager.UploadProgress.Value ?? string.Empty, UIColors.Get("LightlessYellow")); } if ((!_charaDataManager.UploadTask?.IsCompleted ?? false) && _uiSharedService.IconTextButton(FontAwesomeIcon.Ban, "Cancel Upload")) { @@ -112,7 +112,7 @@ internal sealed partial class CharaDataHubUi UiSharedService.DrawGrouped(() => { ImGui.AlignTextToFramePadding(); - UiSharedService.ColorTextWrapped($"You have {otherUpdates} other entries with unsaved changes.", ImGuiColors.DalamudYellow); + UiSharedService.ColorTextWrapped($"You have {otherUpdates} other entries with unsaved changes.", UIColors.Get("LightlessYellow")); ImGui.SameLine(); using (ImRaii.Disabled(_charaDataManager.CharaUpdateTask != null && !_charaDataManager.CharaUpdateTask.IsCompleted)) { @@ -259,7 +259,7 @@ internal sealed partial class CharaDataHubUi ImGui.SameLine(); ImGuiHelpers.ScaledDummy(20, 1); ImGui.SameLine(); - UiSharedService.ColorTextWrapped("New data was set. It may contain files that require to be uploaded (will happen on Saving to server)", ImGuiColors.DalamudYellow); + UiSharedService.ColorTextWrapped("New data was set. It may contain files that require to be uploaded (will happen on Saving to server)", UIColors.Get("LightlessYellow")); } ImGui.TextUnformatted("Contains Manipulation Data"); @@ -414,7 +414,7 @@ internal sealed partial class CharaDataHubUi } } ImGui.SameLine(); - using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudYellow, poseCount == maxPoses)) + using (ImRaii.PushColor(ImGuiCol.Text, UIColors.Get("LightlessYellow"), poseCount == maxPoses)) ImGui.TextUnformatted($"{poseCount}/{maxPoses} poses attached"); ImGuiHelpers.ScaledDummy(5); @@ -424,7 +424,7 @@ internal sealed partial class CharaDataHubUi if (!_uiSharedService.IsInGpose && _charaDataManager.BrioAvailable) { ImGuiHelpers.ScaledDummy(5); - UiSharedService.DrawGroupedCenteredColorText("To attach pose and world data you need to be in GPose.", ImGuiColors.DalamudYellow); + UiSharedService.DrawGroupedCenteredColorText("To attach pose and world data you need to be in GPose.", UIColors.Get("LightlessYellow")); ImGuiHelpers.ScaledDummy(5); } else if (!_charaDataManager.BrioAvailable) @@ -443,7 +443,7 @@ internal sealed partial class CharaDataHubUi if (pose.Id == null) { UiSharedService.ScaledSameLine(50); - _uiSharedService.IconText(FontAwesomeIcon.Plus, ImGuiColors.DalamudYellow); + _uiSharedService.IconText(FontAwesomeIcon.Plus, UIColors.Get("LightlessYellow")); UiSharedService.AttachToolTip("This pose has not been added to the server yet. Save changes to upload this Pose data."); } @@ -451,14 +451,14 @@ internal sealed partial class CharaDataHubUi if (poseHasChanges) { UiSharedService.ScaledSameLine(50); - _uiSharedService.IconText(FontAwesomeIcon.ExclamationTriangle, ImGuiColors.DalamudYellow); + _uiSharedService.IconText(FontAwesomeIcon.ExclamationTriangle, UIColors.Get("LightlessYellow")); UiSharedService.AttachToolTip("This pose has changes that have not been saved to the server yet."); } UiSharedService.ScaledSameLine(75); if (pose.Description == null && pose.WorldData == null && pose.PoseData == null) { - UiSharedService.ColorText("Pose scheduled for deletion", ImGuiColors.DalamudYellow); + UiSharedService.ColorText("Pose scheduled for deletion", UIColors.Get("LightlessYellow")); } else { @@ -669,7 +669,7 @@ internal sealed partial class CharaDataHubUi var idText = entry.FullId; if (uDto?.HasChanges ?? false) { - UiSharedService.ColorText(idText, ImGuiColors.DalamudYellow); + UiSharedService.ColorText(idText, UIColors.Get("LightlessYellow")); UiSharedService.AttachToolTip("This entry has unsaved changes"); } else @@ -724,7 +724,7 @@ internal sealed partial class CharaDataHubUi FontAwesomeIcon eIcon = FontAwesomeIcon.None; if (!Equals(DateTime.MaxValue, entry.ExpiryDate)) eIcon = FontAwesomeIcon.Clock; - _uiSharedService.IconText(eIcon, ImGuiColors.DalamudYellow); + _uiSharedService.IconText(eIcon, UIColors.Get("LightlessYellow")); if (ImGui.IsItemClicked()) SelectedDtoId = entry.Id; if (eIcon != FontAwesomeIcon.None) { @@ -759,13 +759,13 @@ internal sealed partial class CharaDataHubUi if (_charaDataManager.OwnCharaData.Count == _charaDataManager.MaxCreatableCharaData) { ImGui.AlignTextToFramePadding(); - UiSharedService.ColorTextWrapped("You have reached the maximum Character Data entries and cannot create more.", ImGuiColors.DalamudYellow); + UiSharedService.ColorTextWrapped("You have reached the maximum Character Data entries and cannot create more.", UIColors.Get("LightlessYellow")); } } if (_charaDataManager.DataCreationTask != null && !_charaDataManager.DataCreationTask.IsCompleted) { - UiSharedService.ColorTextWrapped("Creating new character data entry on server...", ImGuiColors.DalamudYellow); + UiSharedService.ColorTextWrapped("Creating new character data entry on server...", UIColors.Get("LightlessYellow")); } else if (_charaDataManager.DataCreationTask != null && _charaDataManager.DataCreationTask.IsCompleted) { diff --git a/LightlessSync/UI/CharaDataHubUi.NearbyPoses.cs b/LightlessSync/UI/CharaDataHubUi.NearbyPoses.cs index e7d985f..f93d861 100644 --- a/LightlessSync/UI/CharaDataHubUi.NearbyPoses.cs +++ b/LightlessSync/UI/CharaDataHubUi.NearbyPoses.cs @@ -78,7 +78,7 @@ internal partial class CharaDataHubUi if (!_uiSharedService.IsInGpose) { ImGuiHelpers.ScaledDummy(5); - UiSharedService.DrawGroupedCenteredColorText("Spawning and applying pose data is only available in GPose.", ImGuiColors.DalamudYellow); + UiSharedService.DrawGroupedCenteredColorText("Spawning and applying pose data is only available in GPose.", UIColors.Get("LightlessYellow")); ImGuiHelpers.ScaledDummy(5); } @@ -93,7 +93,7 @@ internal partial class CharaDataHubUi using var indent = ImRaii.PushIndent(5f); if (_charaDataNearbyManager.NearbyData.Count == 0) { - UiSharedService.DrawGroupedCenteredColorText("No Shared World Poses found nearby.", ImGuiColors.DalamudYellow); + UiSharedService.DrawGroupedCenteredColorText("No Shared World Poses found nearby.", UIColors.Get("LightlessYellow")); } bool wasAnythingHovered = false; diff --git a/LightlessSync/UI/CharaDataHubUi.cs b/LightlessSync/UI/CharaDataHubUi.cs index f0a4eb9..9016e6c 100644 --- a/LightlessSync/UI/CharaDataHubUi.cs +++ b/LightlessSync/UI/CharaDataHubUi.cs @@ -190,7 +190,7 @@ internal sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase } if (!string.IsNullOrEmpty(_charaDataManager.DataApplicationProgress)) { - UiSharedService.ColorTextWrapped(_charaDataManager.DataApplicationProgress, ImGuiColors.DalamudYellow); + UiSharedService.ColorTextWrapped(_charaDataManager.DataApplicationProgress, UIColors.Get("LightlessYellow")); } if (_charaDataManager.DataApplicationTask != null) { @@ -436,7 +436,7 @@ internal sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase if (!_hasValidGposeTarget) { ImGuiHelpers.ScaledDummy(3); - UiSharedService.DrawGroupedCenteredColorText("Applying data is only available in GPose with a valid selected GPose target.", ImGuiColors.DalamudYellow, 350); + UiSharedService.DrawGroupedCenteredColorText("Applying data is only available in GPose with a valid selected GPose target.", UIColors.Get("LightlessYellow"), 350); } ImGuiHelpers.ScaledDummy(10); @@ -595,7 +595,7 @@ internal sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase if (_configService.Current.FavoriteCodes.Count == 0) { - UiSharedService.ColorTextWrapped("You have no favorites added. Add Favorites through the other tabs before you can use this tab.", ImGuiColors.DalamudYellow); + UiSharedService.ColorTextWrapped("You have no favorites added. Add Favorites through the other tabs before you can use this tab.", UIColors.Get("LightlessYellow")); } } } @@ -644,7 +644,7 @@ internal sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase ImGui.NewLine(); if (!_charaDataManager.DownloadMetaInfoTask?.IsCompleted ?? false) { - UiSharedService.ColorTextWrapped("Downloading meta info. Please wait.", ImGuiColors.DalamudYellow); + UiSharedService.ColorTextWrapped("Downloading meta info. Please wait.", UIColors.Get("LightlessYellow")); } if ((_charaDataManager.DownloadMetaInfoTask?.IsCompleted ?? false) && !_charaDataManager.DownloadMetaInfoTask.Result.Success) { @@ -850,12 +850,12 @@ internal sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase UiSharedService.ColorTextWrapped("Failure to read MCDF file. MCDF file is possibly corrupt. Re-export the MCDF file and try again.", ImGuiColors.DalamudRed); UiSharedService.ColorTextWrapped("Note: if this is your MCDF, try redrawing yourself, wait and re-export the file. " + - "If you received it from someone else have them do the same.", ImGuiColors.DalamudYellow); + "If you received it from someone else have them do the same.", UIColors.Get("LightlessYellow")); } } else { - UiSharedService.ColorTextWrapped("Loading Character...", ImGuiColors.DalamudYellow); + UiSharedService.ColorTextWrapped("Loading Character...", UIColors.Get("LightlessYellow")); } } } @@ -896,7 +896,7 @@ internal sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase }, Directory.Exists(_configService.Current.LastSavedCharaDataLocation) ? _configService.Current.LastSavedCharaDataLocation : null); } UiSharedService.ColorTextWrapped("Note: For best results make sure you have everything you want to be shared as well as the correct character appearance" + - " equipped and redraw your character before exporting.", ImGuiColors.DalamudYellow); + " equipped and redraw your character before exporting.", UIColors.Get("LightlessYellow")); ImGui.Unindent(); } diff --git a/LightlessSync/UI/CompactUI.cs b/LightlessSync/UI/CompactUI.cs index 5c1ae53..54a13e4 100644 --- a/LightlessSync/UI/CompactUI.cs +++ b/LightlessSync/UI/CompactUI.cs @@ -1,13 +1,14 @@ using Dalamud.Bindings.ImGui; using Dalamud.Interface; -using Dalamud.Interface.Colors; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using Dalamud.Utility; +using LightlessSync.API.Data.Enum; 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; @@ -15,6 +16,7 @@ using LightlessSync.Services.Mediator; using LightlessSync.Services.ServerConfiguration; using LightlessSync.UI.Components; using LightlessSync.UI.Handlers; +using LightlessSync.Utils; using LightlessSync.WebAPI; using LightlessSync.WebAPI.Files; using LightlessSync.WebAPI.Files.Models; @@ -30,21 +32,27 @@ namespace LightlessSync.UI; public class CompactUi : WindowMediatorSubscriberBase { + private readonly CharacterAnalyzer _characterAnalyzer; private readonly ApiController _apiController; private readonly LightlessConfigService _configService; private readonly ConcurrentDictionary> _currentDownloads = new(); private readonly DrawEntityFactory _drawEntityFactory; private readonly FileUploadManager _fileTransferManager; + private readonly PlayerPerformanceConfigService _playerPerformanceConfig; 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; private readonly TagHandler _tagHandler; private readonly UiSharedService _uiSharedService; private List _drawFolders; + private Dictionary>? _cachedAnalysis; private Pair? _lastAddedUser; private string _lastAddedUserComment = string.Empty; private Vector2 _lastPosition = Vector2.One; @@ -57,8 +65,10 @@ 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, - PerformanceCollectorService performanceCollectorService, IpcManager ipcManager) + TagHandler tagHandler, DrawEntityFactory drawEntityFactory, + SelectTagForPairUi selectTagForPairUi, SelectPairForTagUi selectPairForTagUi, RenamePairTagUi renameTagUi, + SelectTagForSyncshellUi selectTagForSyncshellUi, SelectSyncshellForTagUi selectSyncshellForTagUi, RenameSyncshellTagUi renameSyncshellTagUi, + PerformanceCollectorService performanceCollectorService, IpcManager ipcManager, CharacterAnalyzer characterAnalyzer, PlayerPerformanceConfigService playerPerformanceConfig) : base(logger, mediator, "###LightlessSyncMainUI", performanceCollectorService) { _uiSharedService = uiShared; @@ -69,13 +79,16 @@ 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); - AllowPinning = false; + AllowPinning = true; AllowClickthrough = false; TitleBarButtons = new() { @@ -137,6 +150,8 @@ public class CompactUi : WindowMediatorSubscriberBase MinimumSize = new Vector2(375, 400), MaximumSize = new Vector2(375, 2000), }; + _characterAnalyzer = characterAnalyzer; + _playerPerformanceConfig = playerPerformanceConfig; } protected override void DrawInternal() @@ -199,9 +214,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]); + 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) @@ -396,6 +414,13 @@ public class CompactUi : WindowMediatorSubscriberBase { var uidText = GetUidText(); + //Getting information of character and triangles threshold to show overlimit status in UID bar. + _cachedAnalysis = _characterAnalyzer.LastAnalysis.DeepClone(); + var groupedfiles = _cachedAnalysis.First().Value.Select(v => v.Value).GroupBy(f => f.FileType, StringComparer.Ordinal) + .OrderBy(k => k.Key, StringComparer.Ordinal).ToList(); + var actualTriCount = _cachedAnalysis.First().Value.Sum(f => f.Value.Triangles); + var isOverTriHold = actualTriCount > (_playerPerformanceConfig.Current.TrisWarningThresholdThousands * 1000); + using (_uiSharedService.UidFont.Push()) { var uidTextSize = ImGui.CalcTextSize(uidText); @@ -403,24 +428,50 @@ public class CompactUi : WindowMediatorSubscriberBase ImGui.TextColored(GetUidColor(), uidText); } + if (groupedfiles != null) + { + //Checking of VRAM threshhold + var actualVramUsage = groupedfiles.SingleOrDefault(v => string.Equals(v.Key, "tex", StringComparison.Ordinal)).Sum(f => f.OriginalSize); + var isOverVRAMUsage = _playerPerformanceConfig.Current.VRAMSizeWarningThresholdMiB * 1024 * 1024 < actualVramUsage; + + if (isOverTriHold || isOverVRAMUsage) + { + ImGui.SameLine(); + _uiSharedService.IconText(FontAwesomeIcon.ExclamationTriangle, UIColors.Get("LightlessYellow")); + string warningMessage = ""; + if (isOverTriHold) + { + warningMessage += $"You exceed your own triangles threshold by " + + $"{actualTriCount - _playerPerformanceConfig.Current.TrisWarningThresholdThousands * 1000} triangles."; + warningMessage += Environment.NewLine; + + } + if (isOverVRAMUsage) + { + warningMessage += $"You exceed your own VRAM threshold by " + + $"{UiSharedService.ByteToString(actualVramUsage - (_playerPerformanceConfig.Current.VRAMSizeWarningThresholdMiB * 1024 * 1024))}."; + } + UiSharedService.AttachToolTip(warningMessage); + } + } + if (_apiController.ServerState is ServerState.Connected) { if (ImGui.IsItemClicked()) { ImGui.SetClipboardText(_apiController.DisplayName); } - UiSharedService.AttachToolTip("Click to copy"); if (!string.Equals(_apiController.DisplayName, _apiController.UID, StringComparison.Ordinal)) { var origTextSize = ImGui.CalcTextSize(_apiController.UID); ImGui.SetCursorPosX((ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X) / 2 - (origTextSize.X / 2)); ImGui.TextColored(GetUidColor(), _apiController.UID); + UiSharedService.AttachToolTip("Click to copy"); if (ImGui.IsItemClicked()) { ImGui.SetClipboardText(_apiController.UID); } - UiSharedService.AttachToolTip("Click to copy"); } } else @@ -462,12 +513,12 @@ public class CompactUi : WindowMediatorSubscriberBase bool FilterVisibleUsers(KeyValuePair> u) => u.Key.IsVisible && (_configService.Current.ShowSyncshellUsersInVisible || !(!_configService.Current.ShowSyncshellUsersInVisible && !u.Key.IsDirectlyPaired)); - bool FilterTagusers(KeyValuePair> u, string tag) - => u.Key.IsDirectlyPaired && !u.Key.IsOneSidedPair && _tagHandler.HasTag(u.Key.UserData.UID, tag); + bool FilterTagUsers(KeyValuePair> u, string tag) + => u.Key.IsDirectlyPaired && !u.Key.IsOneSidedPair && _tagHandler.HasPairTag(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) - => u.Key.IsDirectlyPaired && !u.Key.IsOneSidedPair && !_tagHandler.HasAnyTag(u.Key.UserData.UID); + => u.Key.IsDirectlyPaired && !u.Key.IsOneSidedPair && !_tagHandler.HasAnyPairTag(u.Key.UserData.UID); bool FilterOfflineUsers(KeyValuePair> u) => ((u.Key.IsDirectlyPaired && _configService.Current.ShowSyncshellOfflineUsersSeparately) || !_configService.Current.ShowSyncshellOfflineUsersSeparately) @@ -487,46 +538,48 @@ 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 - .Where(u => FilterGroupUsers(u, group))); - - var filteredGroupPairs = filteredPairs - .Where(u => FilterGroupUsers(u, group) && 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); - + GetGroups(allPairs, filteredPairs, group, out ImmutableList allGroupPairs, out Dictionary> filteredGroupPairs); groupFolders.Add(_drawEntityFactory.CreateDrawGroupFolder(group, filteredGroupPairs, allGroupPairs)); } if (_configService.Current.GroupUpSyncshells) - drawFolders.Add(new DrawGroupedGroupFolder(groupFolders, _tagHandler, _uiSharedService)); + drawFolders.Add(new DrawGroupedGroupFolder(groupFolders, _tagHandler, _uiSharedService, _selectSyncshellForTagUi, _renameSyncshellTagUi, "")); else drawFolders.AddRange(groupFolders); - var tags = _tagHandler.GetAllTagsSorted(); + var tags = _tagHandler.GetAllPairTagsSorted(); foreach (var tag in tags) { var allTagPairs = ImmutablePairList(allPairs - .Where(u => FilterTagusers(u, tag))); + .Where(u => FilterTagUsers(u, tag))); var filteredTagPairs = BasicSortedDictionary(filteredPairs - .Where(u => FilterTagusers(u, tag) && FilterOnlineOrPausedSelf(u))); + .Where(u => FilterTagUsers(u, tag) && FilterOnlineOrPausedSelf(u))); drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(tag, filteredTagPairs, allTagPairs)); } + var syncshellTags = _tagHandler.GetAllSyncshellTagsSorted(); + foreach (var syncshelltag in syncshellTags) + { + List syncshellFolderTags = []; + foreach (var group in _pairManager.GroupPairs.Select(g => g.Key).OrderBy(g => g.GroupAliasOrGID, StringComparer.OrdinalIgnoreCase)) + { + if (_tagHandler.HasSyncshellTag(group.GID, syncshelltag)) + { + GetGroups(allPairs, filteredPairs, group, out ImmutableList allGroupPairs, out Dictionary> filteredGroupPairs); + syncshellFolderTags.Add(_drawEntityFactory.CreateDrawGroupFolder($"tag_{group.GID}", group, filteredGroupPairs, allGroupPairs)); + } + } + + if (syncshellFolderTags.Count > 0) + { + drawFolders.Add(new DrawGroupedGroupFolder(syncshellFolderTags, _tagHandler, _uiSharedService, _selectSyncshellForTagUi, _renameSyncshellTagUi, syncshelltag)); + } + } + var allOnlineNotTaggedPairs = ImmutablePairList(allPairs .Where(FilterNotTaggedUsers)); var onlineNotTaggedPairs = BasicSortedDictionary(filteredPairs @@ -561,6 +614,27 @@ public class CompactUi : WindowMediatorSubscriberBase ImmutablePairList(allPairs.Where(u => u.Key.IsOneSidedPair)))); return drawFolders; + + void GetGroups(Dictionary> allPairs, Dictionary> filteredPairs, GroupFullInfoDto group, out ImmutableList allGroupPairs, out Dictionary> filteredGroupPairs) + { + allGroupPairs = ImmutablePairList(allPairs + .Where(u => FilterGroupUsers(u, group))); + filteredGroupPairs = filteredPairs + .Where(u => FilterGroupUsers(u, group) && 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); + } } private string GetServerError() diff --git a/LightlessSync/UI/Components/DrawFolderGroup.cs b/LightlessSync/UI/Components/DrawFolderGroup.cs index d6e5b04..6de9e28 100644 --- a/LightlessSync/UI/Components/DrawFolderGroup.cs +++ b/LightlessSync/UI/Components/DrawFolderGroup.cs @@ -19,16 +19,18 @@ public class DrawFolderGroup : DrawFolderBase private readonly GroupFullInfoDto _groupFullInfoDto; private readonly IdDisplayHandler _idDisplayHandler; private readonly LightlessMediator _lightlessMediator; + private readonly SelectTagForSyncshellUi _selectTagForSyncshellUi; public DrawFolderGroup(string id, GroupFullInfoDto groupFullInfoDto, ApiController apiController, IImmutableList drawPairs, IImmutableList allPairs, TagHandler tagHandler, IdDisplayHandler idDisplayHandler, - LightlessMediator lightlessMediator, UiSharedService uiSharedService) : + LightlessMediator lightlessMediator, UiSharedService uiSharedService, SelectTagForSyncshellUi selectTagForSyncshellUi) : base(id, drawPairs, allPairs, tagHandler, uiSharedService) { _groupFullInfoDto = groupFullInfoDto; _apiController = apiController; _idDisplayHandler = idDisplayHandler; _lightlessMediator = lightlessMediator; + _selectTagForSyncshellUi = selectTagForSyncshellUi; } protected override bool RenderIfEmpty => true; @@ -99,6 +101,13 @@ public class DrawFolderGroup : DrawFolderBase } 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); @@ -185,7 +194,7 @@ public class DrawFolderGroup : DrawFolderBase _uiSharedService.IconText(FontAwesomeIcon.UsersCog, (_groupFullInfoDto.GroupPermissions.IsPreferDisableAnimations() != individualAnimDisabled || _groupFullInfoDto.GroupPermissions.IsPreferDisableSounds() != individualSoundsDisabled - || _groupFullInfoDto.GroupPermissions.IsPreferDisableVFX() != individualVFXDisabled) ? ImGuiColors.DalamudYellow : null); + || _groupFullInfoDto.GroupPermissions.IsPreferDisableVFX() != individualVFXDisabled) ? UIColors.Get("LightlessYellow") : null); if (ImGui.IsItemHovered()) { ImGui.BeginTooltip(); diff --git a/LightlessSync/UI/Components/DrawFolderTag.cs b/LightlessSync/UI/Components/DrawFolderTag.cs index 79ec3c0..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; @@ -112,7 +112,7 @@ public class DrawFolderTag : DrawFolderBase } if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, "Delete Pair Group", menuWidth, isInPopup: true) && UiSharedService.CtrlPressed()) { - _tagHandler.RemoveTag(_id); + _tagHandler.RemovePairTag(_id); } UiSharedService.AttachToolTip("Hold CTRL to remove this Group permanently." + Environment.NewLine + "Note: this will not unpair with users in this Group."); diff --git a/LightlessSync/UI/Components/DrawGroupedGroupFolder.cs b/LightlessSync/UI/Components/DrawGroupedGroupFolder.cs index 5410554..d1876ac 100644 --- a/LightlessSync/UI/Components/DrawGroupedGroupFolder.cs +++ b/LightlessSync/UI/Components/DrawGroupedGroupFolder.cs @@ -9,20 +9,27 @@ namespace LightlessSync.UI.Components; public class DrawGroupedGroupFolder : IDrawFolder { + private readonly string _tag; private readonly IEnumerable _groups; private readonly TagHandler _tagHandler; private readonly UiSharedService _uiSharedService; + private readonly SelectSyncshellForTagUi _selectSyncshellForTagUi; + private readonly RenameSyncshellTagUi _renameSyncshellTagUi; private bool _wasHovered = false; + private float _menuWidth; 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 DrawGroupedGroupFolder(IEnumerable groups, TagHandler tagHandler, UiSharedService uiSharedService) + public DrawGroupedGroupFolder(IEnumerable groups, TagHandler tagHandler, UiSharedService uiSharedService, SelectSyncshellForTagUi selectSyncshellForTagUi, RenameSyncshellTagUi renameSyncshellTagUi, string tag) { _groups = groups; _tagHandler = tagHandler; _uiSharedService = uiSharedService; + _selectSyncshellForTagUi = selectSyncshellForTagUi; + _renameSyncshellTagUi = renameSyncshellTagUi; + _tag = tag; } public void Draw() @@ -30,6 +37,11 @@ public class DrawGroupedGroupFolder : IDrawFolder if (!_groups.Any()) return; string _id = "__folder_syncshells"; + if (_tag != "") + { + _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()))) @@ -49,18 +61,36 @@ public class DrawGroupedGroupFolder : IDrawFolder ImGui.SameLine(); ImGui.AlignTextToFramePadding(); - _uiSharedService.IconText(FontAwesomeIcon.UsersRectangle); - using (ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemSpacing with { X = ImGui.GetStyle().ItemSpacing.X / 2f })) + + if (_tag != "") { - ImGui.SameLine(); - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted("[" + OnlinePairs.ToString() + "]"); + _uiSharedService.IconText(FontAwesomeIcon.FolderPlus); + } + else + { + _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("All Syncshells"); + if (_tag != "") + { + ImGui.TextUnformatted(_tag); + + ImGui.SameLine(); + DrawMenu(); + } else + { + ImGui.TextUnformatted("All Syncshells"); + } } color.Dispose(); _wasHovered = ImGui.IsItemHovered(); @@ -76,4 +106,40 @@ public class DrawGroupedGroupFolder : IDrawFolder } } } + + protected void DrawMenu() + { + var barButtonSize = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.EllipsisV); + var windowEndX = ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth(); + + ImGui.SameLine(windowEndX - barButtonSize.X); + if (_uiSharedService.IconButton(FontAwesomeIcon.EllipsisV)) + { + ImGui.OpenPopup("User Flyout Menu"); + } + if (ImGui.BeginPopup("User Flyout Menu")) + { + using (ImRaii.PushId($"buttons-syncshell-{_tag}")) GroupMenu(_menuWidth); + _menuWidth = ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X; + ImGui.EndPopup(); + } + } + + protected void GroupMenu(float menuWidth) + { + ImGui.TextUnformatted("Syncshell Group Menu"); + if (_uiSharedService.IconTextButton(FontAwesomeIcon.Users, "Select Syncshells", menuWidth, isInPopup: true)) + { + _selectSyncshellForTagUi.Open(_tag); + } + if (_uiSharedService.IconTextButton(FontAwesomeIcon.Edit, "Rename Syncshell Group", menuWidth, isInPopup: true)) + { + _renameSyncshellTagUi.Open(_tag); + } + if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, "Delete Syncshell Group", menuWidth, isInPopup: true) && UiSharedService.CtrlPressed()) + { + _tagHandler.RemoveSyncshellTag(_tag); + } + UiSharedService.AttachToolTip("Hold CTRL to remove this Group permanently."); + } } diff --git a/LightlessSync/UI/Components/DrawUserPair.cs b/LightlessSync/UI/Components/DrawUserPair.cs index b4f5848..398af1a 100644 --- a/LightlessSync/UI/Components/DrawUserPair.cs +++ b/LightlessSync/UI/Components/DrawUserPair.cs @@ -196,7 +196,7 @@ public class DrawUserPair if (_pair.IsPaused) { - using var _ = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudYellow); + using var _ = ImRaii.PushColor(ImGuiCol.Text, UIColors.Get("LightlessYellow")); _uiSharedService.IconText(FontAwesomeIcon.PauseCircle); userPairText = _pair.UserData.AliasOrUID + " is paused"; } @@ -274,7 +274,7 @@ public class DrawUserPair { ImGui.SameLine(); - _uiSharedService.IconText(FontAwesomeIcon.ExclamationTriangle, ImGuiColors.DalamudYellow); + _uiSharedService.IconText(FontAwesomeIcon.ExclamationTriangle, UIColors.Get("LightlessYellow")); string userWarningText = "WARNING: This user exceeds one or more of your defined thresholds:" + UiSharedService.TooltipSeparator; bool shownVram = false; @@ -376,7 +376,7 @@ public class DrawUserPair currentRightSide -= (_uiSharedService.GetIconSize(individualIcon).X + spacingX); ImGui.SameLine(currentRightSide); - using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudYellow, individualAnimDisabled || individualSoundsDisabled || individualVFXDisabled)) + using (ImRaii.PushColor(ImGuiCol.Text, UIColors.Get("LightlessYellow"), individualAnimDisabled || individualSoundsDisabled || individualVFXDisabled)) _uiSharedService.IconText(individualIcon); if (ImGui.IsItemHovered()) { diff --git a/LightlessSync/UI/Components/RenameTagUi.cs b/LightlessSync/UI/Components/RenamePairTagUi.cs similarity index 66% rename from LightlessSync/UI/Components/RenameTagUi.cs rename to LightlessSync/UI/Components/RenamePairTagUi.cs index 1d71f64..bac0e15 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) { @@ -50,12 +48,12 @@ public class RenameTagUi { ImGui.TextUnformatted($"Renaming {_tag}"); - ImGui.InputTextWithHint("##desiredname", "Enter new group name", ref _desiredName, 255, ImGuiInputTextFlags.None); + ImGui.InputTextWithHint("##desiredname", "Enter new group name", ref _desiredName, 20, ImGuiInputTextFlags.None); using (ImRaii.Disabled(string.IsNullOrEmpty(_desiredName))) { 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.RemoveTag(oldTag); - //Creation of new tag and adding of old group pairs in new one. - _tagHandler.AddTag(newTag); - foreach (Pair pair in pairs) - { - var isInTag = _peopleInGroup.Contains(pair.UserData.UID); - if (isInTag) - { - _tagHandler.AddTagToPairedUid(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..310fed8 --- /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 Syncshell 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, 20, 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.RenameSyncshellTag(oldTag, newTag); + } +} \ No newline at end of file diff --git a/LightlessSync/UI/Components/SelectPairForTagUi.cs b/LightlessSync/UI/Components/SelectPairForTagUi.cs index 58caf91..89db40e 100644 --- a/LightlessSync/UI/Components/SelectPairForTagUi.cs +++ b/LightlessSync/UI/Components/SelectPairForTagUi.cs @@ -60,7 +60,7 @@ public class SelectPairForTagUi { if (isInGroup) { - _tagHandler.AddTagToPairedUid(item.UserData.UID, _tag); + _tagHandler.AddPairTagToPairedUid(item.UserData.UID, _tag); _peopleInGroup.Add(item.UserData.UID); } else diff --git a/LightlessSync/UI/Components/SelectSyncshellForTagUi.cs b/LightlessSync/UI/Components/SelectSyncshellForTagUi.cs new file mode 100644 index 0000000..62dd1d6 --- /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.GroupAliasOrGID, StringComparer.OrdinalIgnoreCase) + .ToList()) + { + var isInGroup = _syncshellsInGroup.Contains(group.GID); + if (ImGui.Checkbox(group.GroupAliasOrGID, 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/SelectTagForPairUi.cs b/LightlessSync/UI/Components/SelectTagForPairUi.cs index e258f9c..fbc0751 100644 --- a/LightlessSync/UI/Components/SelectTagForPairUi.cs +++ b/LightlessSync/UI/Components/SelectTagForPairUi.cs @@ -59,7 +59,7 @@ public class SelectTagForPairUi if (ImGui.BeginPopup(popupName)) { - var tags = _tagHandler.GetAllTagsSorted(); + var tags = _tagHandler.GetAllPairTagsSorted(); var childHeight = tags.Count != 0 ? tags.Count * 25 : 1; var childSize = new Vector2(0, childHeight > 100 ? 100 : childHeight) * ImGuiHelpers.GlobalScale; @@ -80,7 +80,7 @@ public class SelectTagForPairUi HandleAddTag(); } ImGui.SameLine(); - ImGui.InputTextWithHint("##category_name", "New Group", ref _tagNameToAdd, 40); + ImGui.InputTextWithHint("##category_name", "New Group", ref _tagNameToAdd, 20); if (ImGui.IsKeyDown(ImGuiKey.Enter)) { HandleAddTag(); @@ -101,13 +101,13 @@ public class SelectTagForPairUi private void DrawGroupName(Pair pair, string name) { - var hasTagBefore = _tagHandler.HasTag(pair.UserData.UID, name); + var hasTagBefore = _tagHandler.HasPairTag(pair.UserData.UID, name); var hasTag = hasTagBefore; if (ImGui.Checkbox(name, ref hasTag)) { if (hasTag) { - _tagHandler.AddTagToPairedUid(pair.UserData.UID, name); + _tagHandler.AddPairTagToPairedUid(pair.UserData.UID, name); } else { @@ -120,10 +120,10 @@ public class SelectTagForPairUi { if (!_tagNameToAdd.IsNullOrWhitespace() && _tagNameToAdd is not (TagHandler.CustomOfflineTag or TagHandler.CustomOnlineTag or TagHandler.CustomVisibleTag)) { - _tagHandler.AddTag(_tagNameToAdd); + _tagHandler.AddPairTag(_tagNameToAdd); if (_pair != null) { - _tagHandler.AddTagToPairedUid(_pair.UserData.UID, _tagNameToAdd); + _tagHandler.AddPairTagToPairedUid(_pair.UserData.UID, _tagNameToAdd); } _tagNameToAdd = string.Empty; } diff --git a/LightlessSync/UI/Components/SelectTagForSyncshellUi.cs b/LightlessSync/UI/Components/SelectTagForSyncshellUi.cs new file mode 100644 index 0000000..b0c83db --- /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.GroupAliasOrGID; + var popupName = $"Choose Groups for {_group.GroupAliasOrGID}"; + // 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, 20); + 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/DataAnalysisUi.cs b/LightlessSync/UI/DataAnalysisUi.cs index 032b3d4..e1ef15c 100644 --- a/LightlessSync/UI/DataAnalysisUi.cs +++ b/LightlessSync/UI/DataAnalysisUi.cs @@ -371,7 +371,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase ImGuiHelpers.ScaledDummy(5); UiSharedService.DrawGroupedCenteredColorText("Important Note: If you need to fix an animation that should apply across multiple jobs, you need to repeat this process with at least one additional job, " + "otherwise the animation will only be fixed for the currently active job. This goes primarily for emotes that are used across multiple jobs.", - ImGuiColors.DalamudYellow, 800); + UIColors.Get("LightlessYellow"), 800); ImGuiHelpers.ScaledDummy(5); UiSharedService.DrawGroupedCenteredColorText("WARNING: WHILE RECORDING TRANSIENT DATA, DO NOT CHANGE YOUR APPEARANCE, ENABLED MODS OR ANYTHING. JUST DO THE ANIMATION(S) OR WHATEVER YOU NEED DOING AND STOP THE RECORDING.", ImGuiColors.DalamudRed, 800); @@ -399,7 +399,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase if (_transientResourceManager.IsTransientRecording) { ImGui.SameLine(); - UiSharedService.ColorText($"RECORDING - Time Remaining: {_transientResourceManager.RecordTimeRemaining.Value}", ImGuiColors.DalamudYellow); + UiSharedService.ColorText($"RECORDING - Time Remaining: {_transientResourceManager.RecordTimeRemaining.Value}", UIColors.Get("LightlessYellow")); ImGuiHelpers.ScaledDummy(5); UiSharedService.DrawGroupedCenteredColorText("DO NOT CHANGE YOUR APPEARANCE OR MODS WHILE RECORDING, YOU CAN ACCIDENTALLY MAKE SOME OF YOUR APPEARANCE RELATED MODS PERMANENT.", ImGuiColors.DalamudRed, 800); } @@ -422,7 +422,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase if (_transientResourceManager.RecordedTransients.Any(k => !k.AlreadyTransient)) { ImGuiHelpers.ScaledDummy(5); - UiSharedService.DrawGroupedCenteredColorText("Please review the recorded mod files before saving and deselect files that got into the recording on accident.", ImGuiColors.DalamudYellow); + UiSharedService.DrawGroupedCenteredColorText("Please review the recorded mod files before saving and deselect files that got into the recording on accident.", UIColors.Get("LightlessYellow")); ImGuiHelpers.ScaledDummy(5); } @@ -485,7 +485,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase if (isAnalyzing) { UiSharedService.ColorTextWrapped($"Analyzing {_characterAnalyzer.CurrentFile}/{_characterAnalyzer.TotalFiles}", - ImGuiColors.DalamudYellow); + UIColors.Get("LightlessYellow")); if (_uiSharedService.IconTextButton(FontAwesomeIcon.StopCircle, "Cancel analysis")) { _characterAnalyzer.CancelAnalyze(); @@ -496,7 +496,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase if (_cachedAnalysis!.Any(c => c.Value.Any(f => !f.Value.IsComputed))) { UiSharedService.ColorTextWrapped("Some entries in the analysis have file size not determined yet, press the button below to analyze your current data", - ImGuiColors.DalamudYellow); + UIColors.Get("LightlessYellow")); if (_uiSharedService.IconTextButton(FontAwesomeIcon.PlayCircle, "Start analysis (missing entries)")) { _ = _characterAnalyzer.ComputeAnalysis(print: false); @@ -592,7 +592,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase { UiSharedService.ColorText($"You exceed your own threshold by " + $"{UiSharedService.ByteToString(actualVramUsage - (currentVramWarning * 1024 * 1024))}.", - ImGuiColors.DalamudYellow); + UIColors.Get("LightlessYellow")); } } } @@ -609,7 +609,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase { UiSharedService.ColorText($"You exceed your own threshold by " + $"{actualTriCount - (currentTriWarning * 1000)} triangles.", - ImGuiColors.DalamudYellow); + UIColors.Get("LightlessYellow")); } } @@ -629,7 +629,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase { string fileGroupText = fileGroup.Key + " [" + fileGroup.Count() + "]"; var requiresCompute = fileGroup.Any(k => !k.IsComputed); - using var tabcol = ImRaii.PushColor(ImGuiCol.Tab, UiSharedService.Color(ImGuiColors.DalamudYellow), requiresCompute); + using var tabcol = ImRaii.PushColor(ImGuiCol.Tab, UiSharedService.Color(UIColors.Get("LightlessYellow")), requiresCompute); if (requiresCompute) { fileGroupText += " (!)"; @@ -668,7 +668,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase ImGui.Checkbox("Enable BC7 Conversion Mode", ref _enableBc7ConversionMode); if (_enableBc7ConversionMode) { - UiSharedService.ColorText("WARNING BC7 CONVERSION:", ImGuiColors.DalamudYellow); + UiSharedService.ColorText("WARNING BC7 CONVERSION:", UIColors.Get("LightlessYellow")); ImGui.SameLine(); UiSharedService.ColorText("Converting textures to BC7 is irreversible!", ImGuiColors.DalamudRed); UiSharedService.ColorTextWrapped("- Converting textures to BC7 will reduce their size (compressed and uncompressed) drastically. It is recommended to be used for large (4k+) textures." + @@ -676,7 +676,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase Environment.NewLine + "- Before converting textures, make sure to have the original files of the mod you are converting so you can reimport it in case of issues." + Environment.NewLine + "- Conversion will convert all found texture duplicates (entries with more than 1 file path) automatically." + Environment.NewLine + "- Converting textures to BC7 is a very expensive operation and, depending on the amount of textures to convert, will take a while to complete." - , ImGuiColors.DalamudYellow); + , UIColors.Get("LightlessYellow")); if (_texturesToConvert.Count > 0 && _uiSharedService.IconTextButton(FontAwesomeIcon.PlayCircle, "Start conversion of " + _texturesToConvert.Count + " texture(s)")) { _conversionCancellationTokenSource = _conversionCancellationTokenSource.CancelRecreate(); @@ -697,7 +697,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase ImGui.TextUnformatted("Selected file:"); ImGui.SameLine(); - UiSharedService.ColorText(_selectedHash, ImGuiColors.DalamudYellow); + UiSharedService.ColorText(_selectedHash, UIColors.Get("LightlessYellow")); if (_cachedAnalysis[_selectedObjectTab].TryGetValue(_selectedHash, out CharacterAnalyzer.FileDataEntry? item)) { @@ -823,8 +823,8 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase } if (string.Equals(_selectedHash, item.Hash, StringComparison.Ordinal)) { - ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg1, UiSharedService.Color(ImGuiColors.DalamudYellow)); - ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, UiSharedService.Color(ImGuiColors.DalamudYellow)); + ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg1, UiSharedService.Color(UIColors.Get("LightlessYellow"))); + ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, UiSharedService.Color(UIColors.Get("LightlessYellow"))); } ImGui.TextUnformatted(item.Hash); if (ImGui.IsItemClicked()) _selectedHash = item.Hash; diff --git a/LightlessSync/UI/DrawEntityFactory.cs b/LightlessSync/UI/DrawEntityFactory.cs index 01b6738..d1410ad 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, _selectTagForSyncshellUi); + } + + public DrawFolderGroup CreateDrawGroupFolder(string id, GroupFullInfoDto groupFullInfoDto, + Dictionary> filteredPairs, + IImmutableList allPairs) + { + return new DrawFolderGroup(id, groupFullInfoDto, _apiController, + filteredPairs.Select(p => CreateDrawPair(groupFullInfoDto.Group.GID + p.Key.UserData.UID, p.Key, p.Value, groupFullInfoDto)).ToImmutableList(), + allPairs, _tagHandler, _uidDisplayHandler, _mediator, _uiSharedService, _selectTagForSyncshellUi); } 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) diff --git a/LightlessSync/UI/DtrEntry.cs b/LightlessSync/UI/DtrEntry.cs index f83c4c6..0335136 100644 --- a/LightlessSync/UI/DtrEntry.cs +++ b/LightlessSync/UI/DtrEntry.cs @@ -6,7 +6,9 @@ using LightlessSync.LightlessConfiguration; using LightlessSync.LightlessConfiguration.Configurations; using LightlessSync.PlayerData.Pairs; using LightlessSync.Services.Mediator; +using LightlessSync.Services.ServerConfiguration; using LightlessSync.WebAPI; +using LightlessSync.WebAPI.SignalR.Utils; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System.Runtime.InteropServices; @@ -16,6 +18,7 @@ namespace LightlessSync.UI; public sealed class DtrEntry : IDisposable, IHostedService { private readonly ApiController _apiController; + private readonly ServerConfigurationManager _serverManager; private readonly CancellationTokenSource _cancellationTokenSource = new(); private readonly ConfigurationServiceBase _configService; private readonly IDtrBar _dtrBar; @@ -28,7 +31,7 @@ public sealed class DtrEntry : IDisposable, IHostedService private string? _tooltip; private Colors _colors; - public DtrEntry(ILogger logger, IDtrBar dtrBar, ConfigurationServiceBase configService, LightlessMediator lightlessMediator, PairManager pairManager, ApiController apiController) + public DtrEntry(ILogger logger, IDtrBar dtrBar, ConfigurationServiceBase configService, LightlessMediator lightlessMediator, PairManager pairManager, ApiController apiController, ServerConfigurationManager serverManager) { _logger = logger; _dtrBar = dtrBar; @@ -37,6 +40,7 @@ public sealed class DtrEntry : IDisposable, IHostedService _lightlessMediator = lightlessMediator; _pairManager = pairManager; _apiController = apiController; + _serverManager = serverManager; } public void Dispose() @@ -59,7 +63,7 @@ public sealed class DtrEntry : IDisposable, IHostedService public async Task StopAsync(CancellationToken cancellationToken) { - _cancellationTokenSource.Cancel(); + await _cancellationTokenSource.CancelAsync().ConfigureAwait(false); try { await _runTask!.ConfigureAwait(false); @@ -89,10 +93,43 @@ public sealed class DtrEntry : IDisposable, IHostedService { _logger.LogTrace("Creating new DtrBar entry"); var entry = _dtrBar.Get("Lightless Sync"); - entry.OnClick = _ => _lightlessMediator.Publish(new UiToggleMessage(typeof(CompactUi))); + entry.OnClick = interactionEvent => OnClickEvent(interactionEvent); return entry; } + + private void OnClickEvent(DtrInteractionEvent interactionEvent) + { + if (interactionEvent.ClickType.Equals(MouseClickType.Left) && !interactionEvent.ModifierKeys.Equals(ClickModifierKeys.Shift)) + { + _lightlessMediator.Publish(new UiToggleMessage(typeof(CompactUi))); + } + else if (interactionEvent.ClickType.Equals(MouseClickType.Left) && interactionEvent.ModifierKeys.Equals(ClickModifierKeys.Shift)) + { + _lightlessMediator.Publish(new UiToggleMessage(typeof(SettingsUi))); + } + + if (interactionEvent.ClickType.Equals(MouseClickType.Right)) + { + bool isConnectingOrConnected = _apiController.ServerState is ServerState.Connected or ServerState.Connecting or ServerState.Reconnecting; + + if (_apiController.ServerState is not (ServerState.Reconnecting or ServerState.Disconnecting)) + { + if (isConnectingOrConnected && !_serverManager.CurrentServer.FullPause) + { + _serverManager.CurrentServer.FullPause = true; + _serverManager.Save(); + } + else if (!isConnectingOrConnected && _serverManager.CurrentServer.FullPause) + { + _serverManager.CurrentServer.FullPause = false; + _serverManager.Save(); + } + + _ = _apiController.CreateConnectionsAsync(); + } + } + } private async Task RunAsync() { diff --git a/LightlessSync/UI/EventViewerUI.cs b/LightlessSync/UI/EventViewerUI.cs index 89c7083..9ce1536 100644 --- a/LightlessSync/UI/EventViewerUI.cs +++ b/LightlessSync/UI/EventViewerUI.cs @@ -110,7 +110,7 @@ internal class EventViewerUI : WindowMediatorSubscriberBase { ImGui.SameLine(); ImGui.AlignTextToFramePadding(); - UiSharedService.ColorTextWrapped("New events are available, press refresh to update", ImGuiColors.DalamudYellow); + UiSharedService.ColorTextWrapped("New events are available, press refresh to update", UIColors.Get("LightlessYellow")); } var buttonSize = _uiSharedService.GetIconTextButtonSize(FontAwesomeIcon.FolderOpen, "Open EventLog Folder"); @@ -180,7 +180,7 @@ internal class EventViewerUI : WindowMediatorSubscriberBase var iconColor = ev.EventSeverity switch { EventSeverity.Informational => new Vector4(), - EventSeverity.Warning => ImGuiColors.DalamudYellow, + EventSeverity.Warning => UIColors.Get("LightlessYellow"), EventSeverity.Error => ImGuiColors.DalamudRed, _ => new Vector4() }; diff --git a/LightlessSync/UI/Handlers/IdDisplayHandler.cs b/LightlessSync/UI/Handlers/IdDisplayHandler.cs index 2683489..e77093b 100644 --- a/LightlessSync/UI/Handlers/IdDisplayHandler.cs +++ b/LightlessSync/UI/Handlers/IdDisplayHandler.cs @@ -43,9 +43,9 @@ public class IdDisplayHandler if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) { var prevState = textIsUid; - if (_showIdForEntry.ContainsKey(group.GID)) + if (_showIdForEntry.TryGetValue(group.GID, out bool value)) { - prevState = _showIdForEntry[group.GID]; + prevState = value; } _showIdForEntry[group.GID] = !prevState; } diff --git a/LightlessSync/UI/Handlers/TagHandler.cs b/LightlessSync/UI/Handlers/TagHandler.cs index f721508..9790d89 100644 --- a/LightlessSync/UI/Handlers/TagHandler.cs +++ b/LightlessSync/UI/Handlers/TagHandler.cs @@ -17,61 +17,146 @@ public class TagHandler _serverConfigurationManager = serverConfigurationManager; } - public void AddTag(string tag) - { - _serverConfigurationManager.AddTag(tag); - } + /// + /// Creation of an pair tag + /// + /// Name of the tag + public void AddPairTag(string tag) => _serverConfigurationManager.AddPairTag(tag); - public void AddTagToPairedUid(string uid, string tagName) - { - _serverConfigurationManager.AddTagForUid(uid, tagName); - } + /// + /// Creation of an syncshell tag + /// + /// Name of the tag + public void AddSyncshellTag(string tag) => _serverConfigurationManager.AddSyncshellTag(tag); - public List GetAllTagsSorted() - { - return - [ + /// + /// Add pair to tag + /// + /// UID that will be added to tag/param> + /// Name of the tag + public void AddPairTagToPairedUid(string uid, string tagName) => _serverConfigurationManager.AddTagForUid(uid, tagName); + + /// + /// Add syncshell to tag + /// + /// Syncshell that will be added to tag/param> + /// Name of the tag + public void AddTagToSyncshell(string name, string tagName) => _serverConfigurationManager.AddTagForSyncshell(name, tagName); + + /// + /// Get all pair tags + /// + public List GetAllPairTagsSorted() => [ .. _serverConfigurationManager.GetServerAvailablePairTags() - .OrderBy(s => s, StringComparer.OrdinalIgnoreCase) + .Order(StringComparer.OrdinalIgnoreCase) , ]; - } - public HashSet GetOtherUidsForTag(string tag) - { - return _serverConfigurationManager.GetUidsForTag(tag); - } + /// + /// Get all syncshell tags + /// + public List GetAllSyncshellTagsSorted() => [ + .. _serverConfigurationManager.GetServerAvailableSyncshellTags() + .Order(StringComparer.OrdinalIgnoreCase) +, + ]; - public bool HasAnyTag(string uid) - { - return _serverConfigurationManager.HasTags(uid); - } + /// + /// Get all UIDs bound to an given tag + /// + /// Name of the tag + public HashSet GetOtherUidsForTag(string tag) => _serverConfigurationManager.GetUidsForPairTag(tag); - public bool HasTag(string uid, string tagName) - { - return _serverConfigurationManager.ContainsTag(uid, tagName); - } + /// + /// Get all syncshells bound to an given tag + /// + /// Name of the tag + public HashSet GetOtherSyncshellsForTag(string tag) => _serverConfigurationManager.GetNamesForSyncshellTag(tag); + + /// + /// Checking if the UID is connected to any tag + /// + /// Syncshell that needs to be checked + public bool HasAnyPairTag(string uid) => _serverConfigurationManager.HasPairTags(uid); + + /// + /// Checking if the syncshell is connected to the tag + /// + /// Syncshell that needs to be checked + public bool HasAnySyncshellTag(string name) => _serverConfigurationManager.HasSyncshellTags(name); + + /// + /// Checking if the UID is connected to the tag + /// + /// UID that needs to be checked + /// Name of the tag + public bool HasPairTag(string uid, string tagName) => _serverConfigurationManager.ContainsPairTag(uid, tagName); + + /// + /// Checking if the syncshell is connected to the tag + /// + /// Syncshell that needs to be checked + /// Name of the tag + public bool HasSyncshellTag(string name, string tagName) => _serverConfigurationManager.ContainsSyncshellTag(name, tagName); /// /// Is this tag opened in the paired clients UI? /// /// the tag /// open true/false - public bool IsTagOpen(string tag) + public bool IsTagOpen(string tag) => _serverConfigurationManager.ContainsOpenPairTag(tag); + + /// + /// Removal of Pair Tags from Storage + /// + /// Name of the tag + public void RemovePairTag(string tag) => _serverConfigurationManager.RemovePairTag(tag); + + /// + /// Removal of Syncshell Tags from Storage + /// + /// Name of the tag + public void RemoveSyncshellTag(string tag) => _serverConfigurationManager.RemoveSyncshellTag(tag); + + /// + /// Removal of UID in a Tag + /// + /// UID of user thats bound to the tag + /// Name of the tag + public void RemoveTagFromPairedUid(string uid, string tagName) => _serverConfigurationManager.RemoveTagForUid(uid, tagName); + + /// + /// Removal of Syncshell in a Tag + /// + /// Syncshell thats bound to the tag + /// Name of the tag + public void RemoveTagFromSyncshell(string name, string tagName) => _serverConfigurationManager.RemoveTagForSyncshell(name, tagName); + + /// + /// Rename of a pair tag + /// + /// Old pair tag name + /// New pair tag name + public void RenamePairTag(string oldName, string newName) { - return _serverConfigurationManager.ContainsOpenPairTag(tag); + _serverConfigurationManager.RenamePairTag(oldName, newName); } - public void RemoveTag(string tag) + /// + /// Rename of a syncshell tag + /// + /// Old syncshell tag name + /// New syncshell tag name + public void RenameSyncshellTag(string oldName, string newName) { - _serverConfigurationManager.RemoveTag(tag); - } - - public void RemoveTagFromPairedUid(string uid, string tagName) - { - _serverConfigurationManager.RemoveTagForUid(uid, tagName); + _serverConfigurationManager.RenameSyncshellTag(oldName, newName); } + /// + /// Changes the tag to open/close + /// + /// The Tag that will be modified + /// True/False public void SetTagOpen(string tag, bool open) { if (open) diff --git a/LightlessSync/UI/IntroUI.cs b/LightlessSync/UI/IntroUI.cs index f21e968..bc32128 100644 --- a/LightlessSync/UI/IntroUI.cs +++ b/LightlessSync/UI/IntroUI.cs @@ -78,7 +78,7 @@ public partial class IntroUi : WindowMediatorSubscriberBase UiSharedService.ColorTextWrapped("Note: Any modifications you have applied through anything but Penumbra cannot be shared and your character state on other clients " + "might look broken because of this or others players mods might not apply on your end altogether. " + - "If you want to use this plugin you will have to move your mods to Penumbra.", ImGuiColors.DalamudYellow); + "If you want to use this plugin you will have to move your mods to Penumbra.", UIColors.Get("LightlessYellow")); if (!_uiShared.DrawOtherPluginState()) return; ImGui.Separator(); if (ImGui.Button("Next##toAgreement")) @@ -172,8 +172,8 @@ public partial class IntroUi : WindowMediatorSubscriberBase "Once the storage folder is set and the scan complete, this page will automatically forward to registration at a service."); UiSharedService.TextWrapped("Note: The initial scan, depending on the amount of mods you have, might take a while. Please wait until it is completed."); UiSharedService.ColorTextWrapped("Warning: once past this step you should not delete the FileCache.csv of Lightless Sync in the Plugin Configurations folder of Dalamud. " + - "Otherwise on the next launch a full re-scan of the file cache database will be initiated.", ImGuiColors.DalamudYellow); - UiSharedService.ColorTextWrapped("Warning: if the scan is hanging and does nothing for a long time, chances are high your Penumbra folder is not set up properly.", ImGuiColors.DalamudYellow); + "Otherwise on the next launch a full re-scan of the file cache database will be initiated.", UIColors.Get("LightlessYellow")); + UiSharedService.ColorTextWrapped("Warning: if the scan is hanging and does nothing for a long time, chances are high your Penumbra folder is not set up properly.", UIColors.Get("LightlessYellow")); _uiShared.DrawCacheDirectorySetting(); } @@ -197,7 +197,7 @@ public partial class IntroUi : WindowMediatorSubscriberBase _configService.Save(); } UiSharedService.ColorTextWrapped("The File Compactor can save a tremendeous amount of space on the hard disk for downloads through Lightless. It will incur a minor CPU penalty on download but can speed up " + - "loading of other characters. It is recommended to keep it enabled. You can change this setting later anytime in the Lightless settings.", ImGuiColors.DalamudYellow); + "loading of other characters. It is recommended to keep it enabled. You can change this setting later anytime in the Lightless settings.", UIColors.Get("LightlessYellow")); } } else if (!_uiShared.ApiController.ServerAlive) @@ -255,7 +255,7 @@ public partial class IntroUi : WindowMediatorSubscriberBase ImGuiHelpers.ScaledDummy(5); UiSharedService.DrawGroupedCenteredColorText("Strongly consider to use OAuth2 to authenticate, if the server supports it (the current main server does). " + "The authentication flow is simpler and you do not require to store or maintain Secret Keys. " + - "You already implicitly register using Discord, so the OAuth2 method will be cleaner and more straight-forward to use.", ImGuiColors.DalamudYellow, 500); + "You already implicitly register using Discord, so the OAuth2 method will be cleaner and more straight-forward to use.", UIColors.Get("LightlessYellow"), 500); ImGuiHelpers.ScaledDummy(5); ImGui.AlignTextToFramePadding(); diff --git a/LightlessSync/UI/JoinSyncshellUI.cs b/LightlessSync/UI/JoinSyncshellUI.cs index cb5c376..3de9026 100644 --- a/LightlessSync/UI/JoinSyncshellUI.cs +++ b/LightlessSync/UI/JoinSyncshellUI.cs @@ -88,7 +88,7 @@ internal class JoinSyncshellUI : WindowMediatorSubscriberBase UiSharedService.ColorTextWrapped("Failed to join the Syncshell. This is due to one of following reasons:" + Environment.NewLine + "- The Syncshell does not exist or the password is incorrect" + Environment.NewLine + "- You are already in that Syncshell or are banned from that Syncshell" + Environment.NewLine + - "- The Syncshell is at capacity or has invites disabled" + Environment.NewLine, ImGuiColors.DalamudYellow); + "- The Syncshell is at capacity or has invites disabled" + Environment.NewLine, UIColors.Get("LightlessYellow")); } } else @@ -111,7 +111,7 @@ internal class JoinSyncshellUI : WindowMediatorSubscriberBase || _groupJoinInfo.GroupPermissions.IsPreferDisableAnimations() != _ownPermissions.DisableGroupAnimations) { ImGuiHelpers.ScaledDummy(2f); - UiSharedService.ColorText("Your current preferred default Syncshell permissions deviate from the suggested permissions:", ImGuiColors.DalamudYellow); + UiSharedService.ColorText("Your current preferred default Syncshell permissions deviate from the suggested permissions:", UIColors.Get("LightlessYellow")); if (_groupJoinInfo.GroupPermissions.IsPreferDisableSounds() != _ownPermissions.DisableGroupSounds) { ImGui.AlignTextToFramePadding(); diff --git a/LightlessSync/UI/PopoutProfileUi.cs b/LightlessSync/UI/PopoutProfileUi.cs index 5b28dc1..5a6557c 100644 --- a/LightlessSync/UI/PopoutProfileUi.cs +++ b/LightlessSync/UI/PopoutProfileUi.cs @@ -135,12 +135,12 @@ public class PopoutProfileUi : WindowMediatorSubscriberBase if (_pair.UserPair.OwnPermissions.IsPaused()) { ImGui.SameLine(); - UiSharedService.ColorText("You: paused", ImGuiColors.DalamudYellow); + UiSharedService.ColorText("You: paused", UIColors.Get("LightlessYellow")); } if (_pair.UserPair.OtherPermissions.IsPaused()) { ImGui.SameLine(); - UiSharedService.ColorText("They: paused", ImGuiColors.DalamudYellow); + UiSharedService.ColorText("They: paused", UIColors.Get("LightlessYellow")); } } if (_pair.UserPair.Groups.Any()) diff --git a/LightlessSync/UI/SettingsUi.cs b/LightlessSync/UI/SettingsUi.cs index 2932892..2832dd6 100644 --- a/LightlessSync/UI/SettingsUi.cs +++ b/LightlessSync/UI/SettingsUi.cs @@ -98,7 +98,7 @@ public class SettingsUi : WindowMediatorSubscriberBase _uiShared = uiShared; _nameplateService = nameplateService; AllowClickthrough = false; - AllowPinning = false; + AllowPinning = true; _validationProgress = new Progress<(int, int, FileCacheEntity)>(v => _currentProgress = v); SizeConstraints = new WindowSizeConstraints() @@ -373,8 +373,8 @@ public class SettingsUi : WindowMediatorSubscriberBase } else if (!_speedTestTask.IsCompleted) { - UiSharedService.ColorTextWrapped("Running Speedtest to File Servers...", ImGuiColors.DalamudYellow); - UiSharedService.ColorTextWrapped("Please be patient, depending on usage and load this can take a while.", ImGuiColors.DalamudYellow); + UiSharedService.ColorTextWrapped("Running Speedtest to File Servers...", UIColors.Get("LightlessYellow")); + UiSharedService.ColorTextWrapped("Please be patient, depending on usage and load this can take a while.", UIColors.Get("LightlessYellow")); if (_uiShared.IconTextButton(FontAwesomeIcon.Ban, "Cancel speedtest")) { _speedTestCts?.Cancel(); @@ -393,7 +393,7 @@ public class SettingsUi : WindowMediatorSubscriberBase } else { - UiSharedService.ColorTextWrapped("Speedtest completed with no results", ImGuiColors.DalamudYellow); + UiSharedService.ColorTextWrapped("Speedtest completed with no results", UIColors.Get("LightlessYellow")); } } } @@ -637,7 +637,7 @@ public class SettingsUi : WindowMediatorSubscriberBase ImGuiHelpers.ScaledDummy(10); - UiSharedService.ColorTextWrapped("Exporting MCDF has moved.", ImGuiColors.DalamudYellow); + UiSharedService.ColorTextWrapped("Exporting MCDF has moved.", UIColors.Get("LightlessYellow")); ImGuiHelpers.ScaledDummy(5); UiSharedService.TextWrapped("It is now found in the Main UI under \"Your User Menu\" ("); ImGui.SameLine(); @@ -720,7 +720,7 @@ public class SettingsUi : WindowMediatorSubscriberBase bool isLinux = _dalamudUtilService.IsWine; if (!useFileCompactor && !isLinux) { - UiSharedService.ColorTextWrapped("Hint: To free up space when using Lightless consider enabling the File Compactor", ImGuiColors.DalamudYellow); + UiSharedService.ColorTextWrapped("Hint: To free up space when using Lightless consider enabling the File Compactor", UIColors.Get("LightlessYellow")); } if (isLinux || !_cacheMonitor.StorageisNTFS) ImGui.BeginDisabled(); if (ImGui.Checkbox("Use file compactor", ref useFileCompactor)) @@ -756,7 +756,7 @@ public class SettingsUi : WindowMediatorSubscriberBase } else { - UiSharedService.ColorText($"File compactor currently running ({_fileCompactor.Progress})", ImGuiColors.DalamudYellow); + UiSharedService.ColorText($"File compactor currently running ({_fileCompactor.Progress})", UIColors.Get("LightlessYellow")); } if (isLinux || !_cacheMonitor.StorageisNTFS) { @@ -974,37 +974,98 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Save(); } - if (ImGui.Checkbox("Color-code the Server Info Bar entry according to status", ref useColorsInDtr)) + } + + _uiShared.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f); + ImGui.TreePop(); + } + + ImGui.Separator(); + + if (_uiShared.MediumTreeNode("Colors", UIColors.Get("LightlessPurple"))) + { + ImGui.TextUnformatted("UI Theme Colors"); + + var colorNames = new[] + { + ("LightlessPurple", "Lightless Purple", "Primary colors"), + ("LightlessBlue", "Lightless Blue", "Secondary colors"), + ("LightlessYellow", "Lightless Yellow", "Warning colors"), + ("PairBlue", "Pair Blue", "Pair UI elements"), + ("DimRed", "Dim Red", "Error and offline") + }; + + foreach (var (colorKey, displayName, description) in colorNames) + { + var currentColor = UIColors.Get(colorKey); + var colorToEdit = currentColor; + + ImGui.AlignTextToFramePadding(); + + if (ImGui.ColorEdit4($"##color_{colorKey}", ref colorToEdit, ImGuiColorEditFlags.NoInputs | ImGuiColorEditFlags.AlphaPreviewHalf)) { - _configService.Current.UseColorsInDtr = useColorsInDtr; + UIColors.Set(colorKey, colorToEdit); + } + + ImGui.SameLine(); + ImGui.TextUnformatted($"{displayName} - {description}"); + + if (UIColors.IsCustom(colorKey)) + { + ImGui.SameLine(); + if (_uiShared.IconTextButton(FontAwesomeIcon.Undo, $"Reset {colorKey}")) + { + UIColors.Reset(colorKey); + } + UiSharedService.AttachToolTip("Reset this color to default"); + } + } + + ImGui.Spacing(); + if (_uiShared.IconTextButton(FontAwesomeIcon.Undo, "Reset All Theme Colors")) + { + UIColors.ResetAll(); + } + _uiShared.DrawHelpText("This will reset all theme colors to their default values"); + + ImGui.Spacing(); + + ImGui.TextUnformatted("Server Info Bar Colors"); + + if (ImGui.Checkbox("Color-code the Server Info Bar entry according to status", ref useColorsInDtr)) + { + _configService.Current.UseColorsInDtr = useColorsInDtr; + _configService.Save(); + } + _uiShared.DrawHelpText("This will color the Server Info Bar entry based on connection status and visible pairs."); + + using (ImRaii.Disabled(!useColorsInDtr)) + { + using var indent = ImRaii.PushIndent(); + if (InputDtrColors("Default", ref dtrColorsDefault)) + { + _configService.Current.DtrColorsDefault = dtrColorsDefault; _configService.Save(); } - using (ImRaii.Disabled(!useColorsInDtr)) + ImGui.SameLine(); + if (InputDtrColors("Not Connected", ref dtrColorsNotConnected)) { - using var indent2 = ImRaii.PushIndent(); - if (InputDtrColors("Default", ref dtrColorsDefault)) - { - _configService.Current.DtrColorsDefault = dtrColorsDefault; - _configService.Save(); - } + _configService.Current.DtrColorsNotConnected = dtrColorsNotConnected; + _configService.Save(); + } - ImGui.SameLine(); - if (InputDtrColors("Not Connected", ref dtrColorsNotConnected)) - { - _configService.Current.DtrColorsNotConnected = dtrColorsNotConnected; - _configService.Save(); - } - - ImGui.SameLine(); - if (InputDtrColors("Pairs in Range", ref dtrColorsPairsInRange)) - { - _configService.Current.DtrColorsPairsInRange = dtrColorsPairsInRange; - _configService.Save(); - } + ImGui.SameLine(); + if (InputDtrColors("Pairs in Range", ref dtrColorsPairsInRange)) + { + _configService.Current.DtrColorsPairsInRange = dtrColorsPairsInRange; + _configService.Save(); } } + ImGui.Spacing(); + ImGui.TextUnformatted("Nameplate Colors"); + var nameColorsEnabled = _configService.Current.IsNameplateColorsEnabled; var nameColors = _configService.Current.NameplateColors; var isFriendOverride = _configService.Current.overrideFriendColor; @@ -1016,6 +1077,7 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Save(); _nameplateService.RequestRedraw(); } + _uiShared.DrawHelpText("This will override the nameplate colors for visible paired players in-game."); using (ImRaii.Disabled(!nameColorsEnabled)) { @@ -1119,7 +1181,6 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Current.UseFocusTarget = useFocusTarget; _configService.Save(); } - _uiShared.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f); ImGui.TreePop(); } @@ -1318,9 +1379,18 @@ public class SettingsUi : WindowMediatorSubscriberBase bool autoPause = _playerPerformanceConfigService.Current.AutoPausePlayersExceedingThresholds; bool autoPauseEveryone = _playerPerformanceConfigService.Current.AutoPausePlayersWithPreferredPermissionsExceedingThresholds; + bool autoPauseInDuty = _playerPerformanceConfigService.Current.PauseInInstanceDuty; if (_uiShared.MediumTreeNode("Auto Pause", UIColors.Get("LightlessPurple"))) { + if (ImGui.Checkbox("Auto pause sync while in instances and duties", ref autoPauseInDuty)) + { + _playerPerformanceConfigService.Current.PauseInInstanceDuty = autoPauseInDuty; + _playerPerformanceConfigService.Save(); + } + _uiShared.DrawHelpText("When enabled, it will automatically pause all players while you are in an instance, such as a dungeon or raid." + Environment.NewLine + + UiSharedService.TooltipSeparator + "Warning: You many have to leave the dungeon to resync with people again"); + if (ImGui.Checkbox("Automatically pause players exceeding thresholds", ref autoPause)) { _playerPerformanceConfigService.Current.AutoPausePlayersExceedingThresholds = autoPause; @@ -1544,7 +1614,7 @@ public class SettingsUi : WindowMediatorSubscriberBase var selectedServer = _serverConfigurationManager.GetServerByIndex(idx); if (selectedServer == _serverConfigurationManager.CurrentServer) { - UiSharedService.ColorTextWrapped("For any changes to be applied to the current service you need to reconnect to the service.", ImGuiColors.DalamudYellow); + UiSharedService.ColorTextWrapped("For any changes to be applied to the current service you need to reconnect to the service.", UIColors.Get("LightlessYellow")); } bool useOauth = selectedServer.UseOAuth2; @@ -1556,7 +1626,7 @@ public class SettingsUi : WindowMediatorSubscriberBase if (selectedServer.SecretKeys.Any() || useOauth) { UiSharedService.ColorTextWrapped("Characters listed here will automatically connect to the selected Lightless service with the settings as provided below." + - " Make sure to enter the character names correctly or use the 'Add current character' button at the bottom.", ImGuiColors.DalamudYellow); + " Make sure to enter the character names correctly or use the 'Add current character' button at the bottom.", UIColors.Get("LightlessYellow")); int i = 0; _uiShared.DrawUpdateOAuthUIDsButton(selectedServer); @@ -1577,14 +1647,14 @@ public class SettingsUi : WindowMediatorSubscriberBase } if (_secretKeysConversionTask != null && !_secretKeysConversionTask.IsCompleted) { - UiSharedService.ColorTextWrapped("Converting Secret Keys to UIDs", ImGuiColors.DalamudYellow); + UiSharedService.ColorTextWrapped("Converting Secret Keys to UIDs", UIColors.Get("LightlessYellow")); } if (_secretKeysConversionTask != null && _secretKeysConversionTask.IsCompletedSuccessfully) { Vector4? textColor = null; if (_secretKeysConversionTask.Result.PartialSuccess) { - textColor = ImGuiColors.DalamudYellow; + textColor = UIColors.Get("LightlessYellow"); } if (!_secretKeysConversionTask.Result.Success) { @@ -1618,10 +1688,10 @@ public class SettingsUi : WindowMediatorSubscriberBase if (authWithCid != null) { ImGuiHelpers.ScaledDummy(5); - UiSharedService.ColorText("A potential rename/world change from this character was detected:", ImGuiColors.DalamudYellow); + UiSharedService.ColorText("A potential rename/world change from this character was detected:", UIColors.Get("LightlessYellow")); using (ImRaii.PushIndent(10f)) UiSharedService.ColorText("Entry: " + authWithCid.CharacterName + " - " + _dalamudUtilService.WorldData.Value[(ushort)authWithCid.WorldId], UIColors.Get("LightlessBlue")); - UiSharedService.ColorText("Press the button below to adjust that entry to your current character:", ImGuiColors.DalamudYellow); + UiSharedService.ColorText("Press the button below to adjust that entry to your current character:", UIColors.Get("LightlessYellow")); using (ImRaii.PushIndent(10f)) UiSharedService.ColorText("Current: " + youName + " - " + _dalamudUtilService.WorldData.Value[(ushort)youWorld], UIColors.Get("LightlessBlue")); ImGuiHelpers.ScaledDummy(5); @@ -1767,7 +1837,7 @@ public class SettingsUi : WindowMediatorSubscriberBase } else { - UiSharedService.ColorTextWrapped("You need to add a Secret Key first before adding Characters.", ImGuiColors.DalamudYellow); + UiSharedService.ColorTextWrapped("You need to add a Secret Key first before adding Characters.", UIColors.Get("LightlessYellow")); } ImGui.EndTabItem(); @@ -1801,7 +1871,7 @@ public class SettingsUi : WindowMediatorSubscriberBase } else { - UiSharedService.ColorTextWrapped("This key is in use and cannot be deleted", ImGuiColors.DalamudYellow); + UiSharedService.ColorTextWrapped("This key is in use and cannot be deleted", UIColors.Get("LightlessYellow")); } if (item.Key != selectedServer.SecretKeys.Keys.LastOrDefault()) @@ -1981,7 +2051,7 @@ public class SettingsUi : WindowMediatorSubscriberBase else { UiSharedService.ColorTextWrapped("Default Permission Settings unavailable for this service. " + - "You need to connect to this service to change the default permissions since they are stored on the service.", ImGuiColors.DalamudYellow); + "You need to connect to this service to change the default permissions since they are stored on the service.", UIColors.Get("LightlessYellow")); } ImGui.EndTabItem(); diff --git a/LightlessSync/UI/StandaloneProfileUi.cs b/LightlessSync/UI/StandaloneProfileUi.cs index 4484013..ca7d779 100644 --- a/LightlessSync/UI/StandaloneProfileUi.cs +++ b/LightlessSync/UI/StandaloneProfileUi.cs @@ -130,12 +130,12 @@ public class StandaloneProfileUi : WindowMediatorSubscriberBase if (Pair.UserPair.OwnPermissions.IsPaused()) { ImGui.SameLine(); - UiSharedService.ColorText("You: paused", ImGuiColors.DalamudYellow); + UiSharedService.ColorText("You: paused", UIColors.Get("LightlessYellow")); } if (Pair.UserPair.OtherPermissions.IsPaused()) { ImGui.SameLine(); - UiSharedService.ColorText("They: paused", ImGuiColors.DalamudYellow); + UiSharedService.ColorText("They: paused", UIColors.Get("LightlessYellow")); } } diff --git a/LightlessSync/UI/SyncshellAdminUI.cs b/LightlessSync/UI/SyncshellAdminUI.cs index b3c70d2..4cf254b 100644 --- a/LightlessSync/UI/SyncshellAdminUI.cs +++ b/LightlessSync/UI/SyncshellAdminUI.cs @@ -13,7 +13,7 @@ using LightlessSync.WebAPI; using Microsoft.Extensions.Logging; using System.Globalization; -namespace LightlessSync.UI.Components.Popup; +namespace LightlessSync.UI; public class SyncshellAdminUI : WindowMediatorSubscriberBase { @@ -63,7 +63,7 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase using var id = ImRaii.PushId("syncshell_admin_" + GroupFullInfo.GID); using (_uiSharedService.UidFont.Push()) - ImGui.TextUnformatted(GroupFullInfo.GroupAliasOrGID + " Administrative Panel"); + _uiSharedService.UnderlinedBigText(GroupFullInfo.GroupAliasOrGID + " Administrative Panel", UIColors.Get("LightlessBlue")); ImGui.Separator(); var perm = GroupFullInfo.GroupPermissions; @@ -72,133 +72,203 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase if (tabbar) { - var inviteTab = ImRaii.TabItem("Invites"); - if (inviteTab) - { - bool isInvitesDisabled = perm.IsDisableInvites(); + DrawInvites(perm); - if (_uiSharedService.IconTextButton(isInvitesDisabled ? FontAwesomeIcon.Unlock : FontAwesomeIcon.Lock, - isInvitesDisabled ? "Unlock Syncshell" : "Lock Syncshell")) + DrawManagement(); + + DrawPermission(perm); + } + } + + private void DrawPermission(GroupPermissions perm) + { + var permissionTab = ImRaii.TabItem("Permissions"); + if (permissionTab) + { + bool isDisableAnimations = perm.IsPreferDisableAnimations(); + bool isDisableSounds = perm.IsPreferDisableSounds(); + bool isDisableVfx = perm.IsPreferDisableVFX(); + + ImGui.AlignTextToFramePadding(); + ImGui.Text("Suggest Sound Sync"); + _uiSharedService.BooleanToColoredIcon(!isDisableSounds); + ImGui.SameLine(230); + using (ImRaii.PushColor(ImGuiCol.Text, isDisableSounds ? UIColors.Get("PairBlue") : UIColors.Get("DimRed"))) + { + if (_uiSharedService.IconTextButton(isDisableSounds ? FontAwesomeIcon.VolumeUp : FontAwesomeIcon.VolumeMute, + isDisableSounds ? "Suggest to enable sound sync" : "Suggest to disable sound sync")) { - perm.SetDisableInvites(!isInvitesDisabled); + perm.SetPreferDisableSounds(!perm.IsPreferDisableSounds()); _ = _apiController.GroupChangeGroupPermissionState(new(GroupFullInfo.Group, perm)); } + } - ImGuiHelpers.ScaledDummy(2f); - - UiSharedService.TextWrapped("One-time invites work as single-use passwords. Use those if you do not want to distribute your Syncshell password."); - if (_uiSharedService.IconTextButton(FontAwesomeIcon.Envelope, "Single one-time invite")) + ImGui.AlignTextToFramePadding(); + ImGui.Text("Suggest Animation Sync"); + _uiSharedService.BooleanToColoredIcon(!isDisableAnimations); + ImGui.SameLine(230); + using (ImRaii.PushColor(ImGuiCol.Text, isDisableAnimations ? UIColors.Get("PairBlue") : UIColors.Get("DimRed"))) + { + if (_uiSharedService.IconTextButton(isDisableAnimations ? FontAwesomeIcon.Running : FontAwesomeIcon.Stop, + isDisableAnimations ? "Suggest to enable animation sync" : "Suggest to disable animation sync")) { - ImGui.SetClipboardText(_apiController.GroupCreateTempInvite(new(GroupFullInfo.Group), 1).Result.FirstOrDefault() ?? string.Empty); - } - UiSharedService.AttachToolTip("Creates a single-use password for joining the syncshell which is valid for 24h and copies it to the clipboard."); - ImGui.InputInt("##amountofinvites", ref _multiInvites); - ImGui.SameLine(); - using (ImRaii.Disabled(_multiInvites <= 1 || _multiInvites > 100)) - { - if (_uiSharedService.IconTextButton(FontAwesomeIcon.Envelope, "Generate " + _multiInvites + " one-time invites")) - { - _oneTimeInvites.AddRange(_apiController.GroupCreateTempInvite(new(GroupFullInfo.Group), _multiInvites).Result); - } - } - - if (_oneTimeInvites.Any()) - { - var invites = string.Join(Environment.NewLine, _oneTimeInvites); - ImGui.InputTextMultiline("Generated Multi Invites", ref invites, 5000, new(0, 0), ImGuiInputTextFlags.ReadOnly); - if (_uiSharedService.IconTextButton(FontAwesomeIcon.Copy, "Copy Invites to clipboard")) - { - ImGui.SetClipboardText(invites); - } + perm.SetPreferDisableAnimations(!perm.IsPreferDisableAnimations()); + _ = _apiController.GroupChangeGroupPermissionState(new(GroupFullInfo.Group, perm)); } } - inviteTab.Dispose(); - var mgmtTab = ImRaii.TabItem("User Management"); - if (mgmtTab) + ImGui.AlignTextToFramePadding(); + ImGui.Text("Suggest VFX Sync"); + _uiSharedService.BooleanToColoredIcon(!isDisableVfx); + ImGui.SameLine(230); + using (ImRaii.PushColor(ImGuiCol.Text, isDisableVfx ? UIColors.Get("PairBlue") : UIColors.Get("DimRed"))) { - var userNode = ImRaii.TreeNode("User List & Administration"); - if (userNode) + if (_uiSharedService.IconTextButton(isDisableVfx ? FontAwesomeIcon.Sun : FontAwesomeIcon.Circle, + isDisableVfx ? "Suggest to enable vfx sync" : "Suggest to disable vfx sync")) { - if (!_pairManager.GroupPairs.TryGetValue(GroupFullInfo, out var pairs)) + perm.SetPreferDisableVFX(!perm.IsPreferDisableVFX()); + _ = _apiController.GroupChangeGroupPermissionState(new(GroupFullInfo.Group, perm)); + } + } + + UiSharedService.TextWrapped("Note: those suggested permissions will be shown to users on joining the Syncshell."); + } + permissionTab.Dispose(); + + if (_isOwner) + { + var ownerTab = ImRaii.TabItem("Owner Settings"); + if (ownerTab) + { + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted("New Password"); + var availableWidth = ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X; + var buttonSize = _uiSharedService.GetIconTextButtonSize(FontAwesomeIcon.Passport, "Change Password"); + var textSize = ImGui.CalcTextSize("New Password").X; + var spacing = ImGui.GetStyle().ItemSpacing.X; + + ImGui.SameLine(); + ImGui.SetNextItemWidth(availableWidth - buttonSize - textSize - spacing * 2); + ImGui.InputTextWithHint("##changepw", "Min 10 characters", ref _newPassword, 50); + ImGui.SameLine(); + using (ImRaii.Disabled(_newPassword.Length < 10)) + { + if (_uiSharedService.IconTextButton(FontAwesomeIcon.Passport, "Change Password")) { - UiSharedService.ColorTextWrapped("No users found in this Syncshell", ImGuiColors.DalamudYellow); + _pwChangeSuccess = _apiController.GroupChangePassword(new GroupPasswordDto(GroupFullInfo.Group, _newPassword)).Result; + _newPassword = string.Empty; } - else + } + UiSharedService.AttachToolTip("Password requires to be at least 10 characters long. This action is irreversible."); + + if (!_pwChangeSuccess) + { + UiSharedService.ColorTextWrapped("Failed to change the password. Password requires to be at least 10 characters long.", ImGuiColors.DalamudYellow); + } + + if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, "Delete Syncshell") && UiSharedService.CtrlPressed() && UiSharedService.ShiftPressed()) + { + IsOpen = false; + _ = _apiController.GroupDelete(new(GroupFullInfo.Group)); + } + UiSharedService.AttachToolTip("Hold CTRL and Shift and click to delete this Syncshell." + Environment.NewLine + "WARNING: this action is irreversible."); + } + ownerTab.Dispose(); + } + } + + private void DrawManagement() + { + var mgmtTab = ImRaii.TabItem("User Management"); + if (mgmtTab) + { + if (_uiSharedService.MediumTreeNode("User List & Administration", UIColors.Get("LightlessPurple"))) + { + if (!_pairManager.GroupPairs.TryGetValue(GroupFullInfo, out var pairs)) + { + UiSharedService.ColorTextWrapped("No users found in this Syncshell", ImGuiColors.DalamudYellow); + } + else + { + var tableFlags = ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingStretchProp; + if (pairs.Count > 10) tableFlags |= ImGuiTableFlags.ScrollY; + using var table = ImRaii.Table("userList#" + GroupFullInfo.Group.GID, 3, tableFlags); + if (table) { - using var table = ImRaii.Table("userList#" + GroupFullInfo.Group.GID, 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingStretchProp | ImGuiTableFlags.ScrollY); - if (table) + ImGui.TableSetupColumn("Alias/UID/Note", ImGuiTableColumnFlags.None, 5); + ImGui.TableSetupColumn("Flags", ImGuiTableColumnFlags.None, 1); + ImGui.TableSetupColumn("Actions", ImGuiTableColumnFlags.None, 2); + 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 => { - ImGui.TableSetupColumn("Alias/UID/Note", ImGuiTableColumnFlags.None, 3); - ImGui.TableSetupColumn("Online/Name", ImGuiTableColumnFlags.None, 2); - ImGui.TableSetupColumn("Flags", ImGuiTableColumnFlags.None, 1); - ImGui.TableSetupColumn("Actions", ImGuiTableColumnFlags.None, 2); - ImGui.TableHeadersRow(); + 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); - 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 => + 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)) { - if (p.Value == null) return 10; - if (p.Value.Value.IsModerator()) return 0; - if (p.Value.Value.IsPinned()) return 1; - return 10; - }).ThenBy(p => p.Key.GetNote() ?? p.Key.UserData.AliasOrUID, StringComparer.OrdinalIgnoreCase)) + UiSharedService.AttachToolTip(pair.Key.PlayerName); + ImGui.SameLine(); + } + + ImGui.TableNextColumn(); // special flags + if (pair.Value != null && (pair.Value.Value.IsModerator() || pair.Value.Value.IsPinned() || isUserOwner)) { - using var tableId = ImRaii.PushId("userTable_" + pair.Key.UserData.UID); - - 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(); - ImGui.TextUnformatted(text); - - ImGui.TableNextColumn(); // online/name - string onlineText = pair.Key.IsOnline ? "Online" : "Offline"; - if (!string.IsNullOrEmpty(pair.Key.PlayerName)) + if (pair.Value.Value.IsModerator()) { - onlineText += " (" + pair.Key.PlayerName + ")"; + _uiSharedService.IconText(FontAwesomeIcon.UserShield, UIColors.Get("LightlessPurple")); + UiSharedService.AttachToolTip("Moderator"); } - var boolcolor = UiSharedService.GetBoolColor(pair.Key.IsOnline); - ImGui.AlignTextToFramePadding(); - UiSharedService.ColorText(onlineText, boolcolor); - - ImGui.TableNextColumn(); // special flags - if (pair.Value != null && (pair.Value.Value.IsModerator() || pair.Value.Value.IsPinned())) + if (pair.Value.Value.IsPinned() && !isUserOwner) { - if (pair.Value.Value.IsModerator()) - { - _uiSharedService.IconText(FontAwesomeIcon.UserShield); - UiSharedService.AttachToolTip("Moderator"); - } - if (pair.Value.Value.IsPinned()) - { - _uiSharedService.IconText(FontAwesomeIcon.Thumbtack); - UiSharedService.AttachToolTip("Pinned"); - } + _uiSharedService.IconText(FontAwesomeIcon.Thumbtack); + UiSharedService.AttachToolTip("Pinned"); } - else + if (isUserOwner) { - _uiSharedService.IconText(FontAwesomeIcon.None); + _uiSharedService.IconText(FontAwesomeIcon.Crown, UIColors.Get("LightlessYellow")); + UiSharedService.AttachToolTip("Owner"); } + } + else + { + _uiSharedService.IconText(FontAwesomeIcon.None); + } - ImGui.TableNextColumn(); // actions - if (_isOwner) + ImGui.TableNextColumn(); // actions + if (_isOwner) + { + if (_uiSharedService.IconButton(FontAwesomeIcon.UserShield)) { - if (_uiSharedService.IconButton(FontAwesomeIcon.UserShield)) - { - GroupPairUserInfo userInfo = pair.Value ?? GroupPairUserInfo.None; + GroupPairUserInfo userInfo = pair.Value ?? GroupPairUserInfo.None; - userInfo.SetModerator(!userInfo.IsModerator()); + 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(); + _ = _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 (_isOwner || (pair.Value == null || (pair.Value != null && !pair.Value.Value.IsModerator()))) + 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)) { @@ -208,9 +278,12 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase _ = _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(); + } + 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)) @@ -218,10 +291,13 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase _ = _apiController.GroupRemoveUser(new GroupPairDto(GroupFullInfo.Group, pair.Key.UserData)); } } - UiSharedService.AttachToolTip("Remove user from Syncshell" - + UiSharedService.TooltipSeparator + "Hold CTRL to enable this button"); + } + UiSharedService.AttachToolTip("Remove user from Syncshell" + + UiSharedService.TooltipSeparator + "Hold CTRL to enable this button"); + ImGui.SameLine(); - ImGui.SameLine(); + using (ImRaii.PushColor(ImGuiCol.Text, UIColors.Get("DimRed"))) + { using (ImRaii.Disabled(!UiSharedService.CtrlPressed())) { if (_uiSharedService.IconButton(FontAwesomeIcon.Ban)) @@ -229,224 +305,190 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase Mediator.Publish(new OpenBanUserPopupMessage(pair.Key, GroupFullInfo)); } } - UiSharedService.AttachToolTip("Ban user from Syncshell" - + UiSharedService.TooltipSeparator + "Hold CTRL to enable this button"); } - } - } - } - } - userNode.Dispose(); - var clearNode = ImRaii.TreeNode("Mass Cleanup"); - if (clearNode) - { - using (ImRaii.Disabled(!UiSharedService.CtrlPressed())) - { - if (_uiSharedService.IconTextButton(FontAwesomeIcon.Broom, "Clear Syncshell")) - { - _ = _apiController.GroupClear(new(GroupFullInfo.Group)); - } - } - UiSharedService.AttachToolTip("This will remove all non-pinned, non-moderator users from the Syncshell." - + UiSharedService.TooltipSeparator + "Hold CTRL to enable this button"); - - ImGuiHelpers.ScaledDummy(2f); - ImGui.Separator(); - ImGuiHelpers.ScaledDummy(2f); - - if (_uiSharedService.IconTextButton(FontAwesomeIcon.Unlink, "Check for Inactive Users")) - { - _pruneTestTask = _apiController.GroupPrune(new(GroupFullInfo.Group), _pruneDays, execute: false); - _pruneTask = null; - } - UiSharedService.AttachToolTip($"This will start the prune process for this Syncshell of inactive Lightless users that have not logged in in the past {_pruneDays} days." - + Environment.NewLine + "You will be able to review the amount of inactive users before executing the prune." - + UiSharedService.TooltipSeparator + "Note: this check excludes pinned users and moderators of this Syncshell."); - ImGui.SameLine(); - ImGui.SetNextItemWidth(150); - _uiSharedService.DrawCombo("Days of inactivity", [7, 14, 30, 90], (count) => - { - return count + " days"; - }, - (selected) => - { - _pruneDays = selected; - _pruneTestTask = null; - _pruneTask = null; - }, - _pruneDays); - - if (_pruneTestTask != null) - { - if (!_pruneTestTask.IsCompleted) - { - UiSharedService.ColorTextWrapped("Calculating inactive users...", ImGuiColors.DalamudYellow); - } - else - { - ImGui.AlignTextToFramePadding(); - UiSharedService.TextWrapped($"Found {_pruneTestTask.Result} user(s) that have not logged into Lightless in the past {_pruneDays} days."); - if (_pruneTestTask.Result > 0) - { - using (ImRaii.Disabled(!UiSharedService.CtrlPressed())) - { - if (_uiSharedService.IconTextButton(FontAwesomeIcon.Broom, "Prune Inactive Users")) - { - _pruneTask = _apiController.GroupPrune(new(GroupFullInfo.Group), _pruneDays, execute: true); - _pruneTestTask = null; - } - } - UiSharedService.AttachToolTip($"Pruning will remove {_pruneTestTask?.Result ?? 0} inactive user(s)." + UiSharedService.AttachToolTip("Ban user from Syncshell" + UiSharedService.TooltipSeparator + "Hold CTRL to enable this button"); } } } - if (_pruneTask != null) + } + _uiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f); + ImGui.TreePop(); + } + ImGui.Separator(); + + if (_uiSharedService.MediumTreeNode("Mass Cleanup", UIColors.Get("LightlessPurple"))) + { + using (ImRaii.Disabled(!UiSharedService.CtrlPressed())) + { + if (_uiSharedService.IconTextButton(FontAwesomeIcon.Broom, "Clear Syncshell")) { - if (!_pruneTask.IsCompleted) - { - UiSharedService.ColorTextWrapped("Pruning Syncshell...", ImGuiColors.DalamudYellow); - } - else - { - UiSharedService.TextWrapped($"Syncshell was pruned and {_pruneTask.Result} inactive user(s) have been removed."); - } + _ = _apiController.GroupClear(new(GroupFullInfo.Group)); } } - clearNode.Dispose(); + UiSharedService.AttachToolTip("This will remove all non-pinned, non-moderator users from the Syncshell." + + UiSharedService.TooltipSeparator + "Hold CTRL to enable this button"); - var banNode = ImRaii.TreeNode("User Bans"); - if (banNode) + ImGuiHelpers.ScaledDummy(2f); + ImGui.Separator(); + ImGuiHelpers.ScaledDummy(2f); + + if (_uiSharedService.IconTextButton(FontAwesomeIcon.Unlink, "Check for Inactive Users")) { - if (_uiSharedService.IconTextButton(FontAwesomeIcon.Retweet, "Refresh Banlist from Server")) + _pruneTestTask = _apiController.GroupPrune(new(GroupFullInfo.Group), _pruneDays, execute: false); + _pruneTask = null; + } + UiSharedService.AttachToolTip($"This will start the prune process for this Syncshell of inactive Lightless users that have not logged in in the past {_pruneDays} day(s)." + + Environment.NewLine + "You will be able to review the amount of inactive users before executing the prune." + + UiSharedService.TooltipSeparator + "Note: this check excludes pinned users and moderators of this Syncshell."); + ImGui.SameLine(); + ImGui.SetNextItemWidth(150); + _uiSharedService.DrawCombo("Day(s) of inactivity", [1, 3, 7, 14, 30, 90], (count) => + { + return count + " day(s)"; + }, + (selected) => + { + _pruneDays = selected; + _pruneTestTask = null; + _pruneTask = null; + }, + _pruneDays); + + if (_pruneTestTask != null) + { + if (!_pruneTestTask.IsCompleted) { - _bannedUsers = _apiController.GroupGetBannedUsers(new GroupDto(GroupFullInfo.Group)).Result; + UiSharedService.ColorTextWrapped("Calculating inactive users...", ImGuiColors.DalamudYellow); } - - if (ImGui.BeginTable("bannedusertable" + GroupFullInfo.GID, 6, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingStretchProp | ImGuiTableFlags.ScrollY)) + else { - ImGui.TableSetupColumn("UID", ImGuiTableColumnFlags.None, 1); - ImGui.TableSetupColumn("Alias", ImGuiTableColumnFlags.None, 1); - ImGui.TableSetupColumn("By", ImGuiTableColumnFlags.None, 1); - ImGui.TableSetupColumn("Date", ImGuiTableColumnFlags.None, 2); - ImGui.TableSetupColumn("Reason", ImGuiTableColumnFlags.None, 3); - ImGui.TableSetupColumn("Actions", ImGuiTableColumnFlags.None, 1); - - ImGui.TableHeadersRow(); - - foreach (var bannedUser in _bannedUsers.ToList()) + ImGui.AlignTextToFramePadding(); + UiSharedService.TextWrapped($"Found {_pruneTestTask.Result} user(s) that have not logged into Lightless in the past {_pruneDays} day(s)."); + if (_pruneTestTask.Result > 0) { - ImGui.TableNextColumn(); - ImGui.TextUnformatted(bannedUser.UID); - ImGui.TableNextColumn(); - ImGui.TextUnformatted(bannedUser.UserAlias ?? string.Empty); - ImGui.TableNextColumn(); - ImGui.TextUnformatted(bannedUser.BannedBy); - ImGui.TableNextColumn(); - ImGui.TextUnformatted(bannedUser.BannedOn.ToLocalTime().ToString(CultureInfo.CurrentCulture)); - ImGui.TableNextColumn(); - UiSharedService.TextWrapped(bannedUser.Reason); - ImGui.TableNextColumn(); - using var _ = ImRaii.PushId(bannedUser.UID); - if (_uiSharedService.IconTextButton(FontAwesomeIcon.Check, "Unban")) + using (ImRaii.Disabled(!UiSharedService.CtrlPressed())) { - _apiController.GroupUnbanUser(bannedUser); - _bannedUsers.RemoveAll(b => string.Equals(b.UID, bannedUser.UID, StringComparison.Ordinal)); + if (_uiSharedService.IconTextButton(FontAwesomeIcon.Broom, "Prune Inactive Users")) + { + _pruneTask = _apiController.GroupPrune(new(GroupFullInfo.Group), _pruneDays, execute: true); + _pruneTestTask = null; + } } + UiSharedService.AttachToolTip($"Pruning will remove {_pruneTestTask?.Result ?? 0} inactive user(s)." + + UiSharedService.TooltipSeparator + "Hold CTRL to enable this button"); } - - ImGui.EndTable(); } } - banNode.Dispose(); - } - mgmtTab.Dispose(); - - var permissionTab = ImRaii.TabItem("Permissions"); - if (permissionTab) - { - bool isDisableAnimations = perm.IsPreferDisableAnimations(); - bool isDisableSounds = perm.IsPreferDisableSounds(); - bool isDisableVfx = perm.IsPreferDisableVFX(); - - ImGui.AlignTextToFramePadding(); - ImGui.Text("Suggest Sound Sync"); - _uiSharedService.BooleanToColoredIcon(!isDisableSounds); - ImGui.SameLine(230); - if (_uiSharedService.IconTextButton(isDisableSounds ? FontAwesomeIcon.VolumeUp : FontAwesomeIcon.VolumeMute, - isDisableSounds ? "Suggest to enable sound sync" : "Suggest to disable sound sync")) + if (_pruneTask != null) { - perm.SetPreferDisableSounds(!perm.IsPreferDisableSounds()); - _ = _apiController.GroupChangeGroupPermissionState(new(GroupFullInfo.Group, perm)); - } - - ImGui.AlignTextToFramePadding(); - ImGui.Text("Suggest Animation Sync"); - _uiSharedService.BooleanToColoredIcon(!isDisableAnimations); - ImGui.SameLine(230); - if (_uiSharedService.IconTextButton(isDisableAnimations ? FontAwesomeIcon.Running : FontAwesomeIcon.Stop, - isDisableAnimations ? "Suggest to enable animation sync" : "Suggest to disable animation sync")) - { - perm.SetPreferDisableAnimations(!perm.IsPreferDisableAnimations()); - _ = _apiController.GroupChangeGroupPermissionState(new(GroupFullInfo.Group, perm)); - } - - ImGui.AlignTextToFramePadding(); - ImGui.Text("Suggest VFX Sync"); - _uiSharedService.BooleanToColoredIcon(!isDisableVfx); - ImGui.SameLine(230); - if (_uiSharedService.IconTextButton(isDisableVfx ? FontAwesomeIcon.Sun : FontAwesomeIcon.Circle, - isDisableVfx ? "Suggest to enable vfx sync" : "Suggest to disable vfx sync")) - { - perm.SetPreferDisableVFX(!perm.IsPreferDisableVFX()); - _ = _apiController.GroupChangeGroupPermissionState(new(GroupFullInfo.Group, perm)); - } - - UiSharedService.TextWrapped("Note: those suggested permissions will be shown to users on joining the Syncshell."); - } - permissionTab.Dispose(); - - if (_isOwner) - { - var ownerTab = ImRaii.TabItem("Owner Settings"); - if (ownerTab) - { - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted("New Password"); - var availableWidth = ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X; - var buttonSize = _uiSharedService.GetIconTextButtonSize(FontAwesomeIcon.Passport, "Change Password"); - var textSize = ImGui.CalcTextSize("New Password").X; - var spacing = ImGui.GetStyle().ItemSpacing.X; - - ImGui.SameLine(); - ImGui.SetNextItemWidth(availableWidth - buttonSize - textSize - spacing * 2); - ImGui.InputTextWithHint("##changepw", "Min 10 characters", ref _newPassword, 50); - ImGui.SameLine(); - using (ImRaii.Disabled(_newPassword.Length < 10)) + if (!_pruneTask.IsCompleted) { - if (_uiSharedService.IconTextButton(FontAwesomeIcon.Passport, "Change Password")) + UiSharedService.ColorTextWrapped("Pruning Syncshell...", ImGuiColors.DalamudYellow); + } + else + { + UiSharedService.TextWrapped($"Syncshell was pruned and {_pruneTask.Result} inactive user(s) have been removed."); + } + } + _uiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f); + ImGui.TreePop(); + } + ImGui.Separator(); + + if (_uiSharedService.MediumTreeNode("User Bans", UIColors.Get("LightlessPurple"))) + { + if (_uiSharedService.IconTextButton(FontAwesomeIcon.Retweet, "Refresh Banlist from Server")) + { + _bannedUsers = _apiController.GroupGetBannedUsers(new GroupDto(GroupFullInfo.Group)).Result; + } + var tableFlags = ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingStretchProp; + if (_bannedUsers.Count > 10) tableFlags |= ImGuiTableFlags.ScrollY; + if (ImGui.BeginTable("bannedusertable" + GroupFullInfo.GID, 6, tableFlags)) + { + ImGui.TableSetupColumn("UID", ImGuiTableColumnFlags.None, 1); + ImGui.TableSetupColumn("Alias", ImGuiTableColumnFlags.None, 1); + ImGui.TableSetupColumn("By", ImGuiTableColumnFlags.None, 1); + ImGui.TableSetupColumn("Date", ImGuiTableColumnFlags.None, 2); + ImGui.TableSetupColumn("Reason", ImGuiTableColumnFlags.None, 3); + ImGui.TableSetupColumn("Actions", ImGuiTableColumnFlags.None, 1); + + ImGui.TableHeadersRow(); + + foreach (var bannedUser in _bannedUsers.ToList()) + { + ImGui.TableNextColumn(); + ImGui.TextUnformatted(bannedUser.UID); + ImGui.TableNextColumn(); + ImGui.TextUnformatted(bannedUser.UserAlias ?? string.Empty); + ImGui.TableNextColumn(); + ImGui.TextUnformatted(bannedUser.BannedBy); + ImGui.TableNextColumn(); + ImGui.TextUnformatted(bannedUser.BannedOn.ToLocalTime().ToString(CultureInfo.CurrentCulture)); + ImGui.TableNextColumn(); + UiSharedService.TextWrapped(bannedUser.Reason); + ImGui.TableNextColumn(); + using var _ = ImRaii.PushId(bannedUser.UID); + if (_uiSharedService.IconTextButton(FontAwesomeIcon.Check, "Unban")) { - _pwChangeSuccess = _apiController.GroupChangePassword(new GroupPasswordDto(GroupFullInfo.Group, _newPassword)).Result; - _newPassword = string.Empty; + _apiController.GroupUnbanUser(bannedUser); + _bannedUsers.RemoveAll(b => string.Equals(b.UID, bannedUser.UID, StringComparison.Ordinal)); } } - UiSharedService.AttachToolTip("Password requires to be at least 10 characters long. This action is irreversible."); - - if (!_pwChangeSuccess) - { - UiSharedService.ColorTextWrapped("Failed to change the password. Password requires to be at least 10 characters long.", ImGuiColors.DalamudYellow); - } - - if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, "Delete Syncshell") && UiSharedService.CtrlPressed() && UiSharedService.ShiftPressed()) - { - IsOpen = false; - _ = _apiController.GroupDelete(new(GroupFullInfo.Group)); - } - UiSharedService.AttachToolTip("Hold CTRL and Shift and click to delete this Syncshell." + Environment.NewLine + "WARNING: this action is irreversible."); + ImGui.EndTable(); + } + _uiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f); + ImGui.TreePop(); + } + ImGui.Separator(); + } + mgmtTab.Dispose(); + + } + + private void DrawInvites(GroupPermissions perm) + { + var inviteTab = ImRaii.TabItem("Invites"); + if (inviteTab) + { + bool isInvitesDisabled = perm.IsDisableInvites(); + + if (_uiSharedService.IconTextButton(isInvitesDisabled ? FontAwesomeIcon.Unlock : FontAwesomeIcon.Lock, + isInvitesDisabled ? "Unlock Syncshell" : "Lock Syncshell")) + { + perm.SetDisableInvites(!isInvitesDisabled); + _ = _apiController.GroupChangeGroupPermissionState(new(GroupFullInfo.Group, perm)); + } + + ImGuiHelpers.ScaledDummy(2f); + + UiSharedService.TextWrapped("One-time invites work as single-use passwords. Use those if you do not want to distribute your Syncshell password."); + if (_uiSharedService.IconTextButton(FontAwesomeIcon.Envelope, "Single one-time invite")) + { + ImGui.SetClipboardText(_apiController.GroupCreateTempInvite(new(GroupFullInfo.Group), 1).Result.FirstOrDefault() ?? string.Empty); + } + UiSharedService.AttachToolTip("Creates a single-use password for joining the syncshell which is valid for 24h and copies it to the clipboard."); + ImGui.InputInt("##amountofinvites", ref _multiInvites); + ImGui.SameLine(); + using (ImRaii.Disabled(_multiInvites <= 1 || _multiInvites > 100)) + { + if (_uiSharedService.IconTextButton(FontAwesomeIcon.Envelope, "Generate " + _multiInvites + " one-time invites")) + { + _oneTimeInvites.AddRange(_apiController.GroupCreateTempInvite(new(GroupFullInfo.Group), _multiInvites).Result); + } + } + + if (_oneTimeInvites.Any()) + { + var invites = string.Join(Environment.NewLine, _oneTimeInvites); + ImGui.InputTextMultiline("Generated Multi Invites", ref invites, 5000, new(0, 0), ImGuiInputTextFlags.ReadOnly); + if (_uiSharedService.IconTextButton(FontAwesomeIcon.Copy, "Copy Invites to clipboard")) + { + ImGui.SetClipboardText(invites); } - ownerTab.Dispose(); } } + inviteTab.Dispose(); } public override void OnClose() diff --git a/LightlessSync/UI/UIColors.cs b/LightlessSync/UI/UIColors.cs index a93f904..1d56167 100644 --- a/LightlessSync/UI/UIColors.cs +++ b/LightlessSync/UI/UIColors.cs @@ -1,11 +1,12 @@ -using System.Globalization; +using System.Globalization; using System.Numerics; +using LightlessSync.LightlessConfiguration; namespace LightlessSync.UI { internal static class UIColors { - private static readonly Dictionary HexColors = new(StringComparer.OrdinalIgnoreCase) + private static readonly Dictionary DefaultHexColors = new(StringComparer.OrdinalIgnoreCase) { { "LightlessPurple", "#ad8af5" }, { "LightlessBlue", "#a6c2ff" }, @@ -14,14 +15,72 @@ namespace LightlessSync.UI { "DimRed", "#d44444" }, }; + private static LightlessConfigService? _configService; + + public static void Initialize(LightlessConfigService configService) + { + _configService = configService; + } + public static Vector4 Get(string name) { - if (!HexColors.TryGetValue(name, out var hex)) + if (_configService?.Current.CustomUIColors.TryGetValue(name, out var customColorHex) == true) + return HexToRgba(customColorHex); + + if (!DefaultHexColors.TryGetValue(name, out var hex)) throw new ArgumentException($"Color '{name}' not found in UIColors."); return HexToRgba(hex); } + public static void Set(string name, Vector4 color) + { + if (!DefaultHexColors.ContainsKey(name)) + throw new ArgumentException($"Color '{name}' not found in UIColors."); + + if (_configService != null) + { + _configService.Current.CustomUIColors[name] = RgbaToHex(color); + _configService.Save(); + } + } + + public static void Reset(string name) + { + if (_configService != null) + { + _configService.Current.CustomUIColors.Remove(name); + _configService.Save(); + } + } + + public static void ResetAll() + { + if (_configService != null) + { + _configService.Current.CustomUIColors.Clear(); + _configService.Save(); + } + } + + public static Vector4 GetDefault(string name) + { + if (!DefaultHexColors.TryGetValue(name, out var hex)) + throw new ArgumentException($"Color '{name}' not found in UIColors."); + + return HexToRgba(hex); + } + + public static bool IsCustom(string name) + { + return _configService?.Current.CustomUIColors.ContainsKey(name) == true; + } + + public static IEnumerable GetColorNames() + { + return DefaultHexColors.Keys; + } + public static Vector4 HexToRgba(string hexColor) { hexColor = hexColor.TrimStart('#'); @@ -31,5 +90,14 @@ namespace LightlessSync.UI int a = hexColor.Length == 8 ? int.Parse(hexColor.Substring(6, 2), NumberStyles.HexNumber) : 255; return new Vector4(r / 255f, g / 255f, b / 255f, a / 255f); } + + public static string RgbaToHex(Vector4 color) + { + int r = (int)(color.X * 255); + int g = (int)(color.Y * 255); + int b = (int)(color.Z * 255); + int a = (int)(color.W * 255); + return $"#{r:X2}{g:X2}{b:X2}{(a != 255 ? a.ToString("X2") : "")}"; + } } -} +} \ No newline at end of file diff --git a/LightlessSync/UI/UISharedService.cs b/LightlessSync/UI/UISharedService.cs index acd8c1c..ffca698 100644 --- a/LightlessSync/UI/UISharedService.cs +++ b/LightlessSync/UI/UISharedService.cs @@ -405,7 +405,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase } public static Vector4 UploadColor((long, long) data) => data.Item1 == 0 ? ImGuiColors.DalamudGrey : - data.Item1 == data.Item2 ? UIColors.Get("LightlessBlue") : ImGuiColors.DalamudYellow; + data.Item1 == data.Item2 ? UIColors.Get("LightlessBlue") : UIColors.Get("LightlessYellow"); public bool ApplyNotesFromClipboard(string notes, bool overwrite) { @@ -537,7 +537,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase public void DrawCacheDirectorySetting() { - ColorTextWrapped("Note: The storage folder should be somewhere close to root (i.e. C:\\LightlessStorage) in a new empty folder. DO NOT point this to your game folder. DO NOT point this to your Penumbra folder.", ImGuiColors.DalamudYellow); + ColorTextWrapped("Note: The storage folder should be somewhere close to root (i.e. C:\\LightlessStorage) in a new empty folder. DO NOT point this to your game folder. DO NOT point this to your Penumbra folder.", UIColors.Get("LightlessYellow")); var cacheDirectory = _configService.Current.CacheFolder; ImGui.InputText("Storage Folder##cache", ref cacheDirectory, 255, ImGuiInputTextFlags.ReadOnly); @@ -723,7 +723,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase { if (!_discordOAuthCheck.IsCompleted) { - ColorTextWrapped($"Checking OAuth2 compatibility with {selectedServer.ServerUri}", ImGuiColors.DalamudYellow); + ColorTextWrapped($"Checking OAuth2 compatibility with {selectedServer.ServerUri}", UIColors.Get("LightlessYellow")); } else { @@ -814,7 +814,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase { if (!_discordOAuthUIDs.IsCompleted) { - ColorTextWrapped("Checking UIDs on Server", ImGuiColors.DalamudYellow); + ColorTextWrapped("Checking UIDs on Server", UIColors.Get("LightlessYellow")); } else { diff --git a/LightlessSync/packages.lock.json b/LightlessSync/packages.lock.json index 629deb2..5c2c7d1 100644 --- a/LightlessSync/packages.lock.json +++ b/LightlessSync/packages.lock.json @@ -4,9 +4,9 @@ "net9.0-windows7.0": { "DalamudPackager": { "type": "Direct", - "requested": "[13.0.0, )", - "resolved": "13.0.0", - "contentHash": "Mb3cUDSK/vDPQ8gQIeuCw03EMYrej1B4J44a1AvIJ9C759p9XeqdU9Hg4WgOmlnlPe0G7ILTD32PKSUpkQNa8w==" + "requested": "[13.1.0, )", + "resolved": "13.1.0", + "contentHash": "XdoNhJGyFby5M/sdcRhnc5xTop9PHy+H50PTWpzLhJugjB19EDBiHD/AsiDF66RETM+0qKUdJBZrNuebn7qswQ==" }, "DotNet.ReproducibleBuilds": { "type": "Direct", diff --git a/PenumbraAPI b/PenumbraAPI index 953dd22..dd14131 160000 --- a/PenumbraAPI +++ b/PenumbraAPI @@ -1 +1 @@ -Subproject commit 953dd227afda6b3943b0b88cc965d8aee8a879b5 +Subproject commit dd14131793e5ae47cc8e9232f46469216017b5aa