Merge branch '1.12.0' into ui-redesign

This commit is contained in:
choco
2025-09-28 15:17:48 +02:00
17 changed files with 624 additions and 414 deletions

View File

@@ -230,7 +230,7 @@ public sealed class Plugin : IDalamudPlugin
s.GetRequiredService<LightlessProfileManager>(), s.GetRequiredService<PerformanceCollectorService>())); s.GetRequiredService<LightlessProfileManager>(), s.GetRequiredService<PerformanceCollectorService>()));
collection.AddScoped<WindowMediatorSubscriberBase, PopupHandler>(); collection.AddScoped<WindowMediatorSubscriberBase, PopupHandler>();
collection.AddScoped<WindowMediatorSubscriberBase, BroadcastUI>((s) => new BroadcastUI(s.GetRequiredService<ILogger<BroadcastUI>>(), s.GetRequiredService<LightlessMediator>(), s.GetRequiredService<PerformanceCollectorService>(), s.GetRequiredService<BroadcastService>(), s.GetRequiredService<LightlessConfigService>(), s.GetRequiredService<UiSharedService>(), s.GetRequiredService<ApiController>(), s.GetRequiredService<BroadcastScannerService>())); collection.AddScoped<WindowMediatorSubscriberBase, BroadcastUI>((s) => new BroadcastUI(s.GetRequiredService<ILogger<BroadcastUI>>(), s.GetRequiredService<LightlessMediator>(), s.GetRequiredService<PerformanceCollectorService>(), s.GetRequiredService<BroadcastService>(), s.GetRequiredService<LightlessConfigService>(), s.GetRequiredService<UiSharedService>(), s.GetRequiredService<ApiController>(), s.GetRequiredService<BroadcastScannerService>()));
collection.AddScoped<WindowMediatorSubscriberBase, SyncshellFinderUI>((s) => new SyncshellFinderUI(s.GetRequiredService<ILogger<SyncshellFinderUI>>(), s.GetRequiredService<LightlessMediator>(), s.GetRequiredService<PerformanceCollectorService>(), s.GetRequiredService<BroadcastService>(), s.GetRequiredService<LightlessConfigService>(), s.GetRequiredService<UiSharedService>(), s.GetRequiredService<ApiController>(), s.GetRequiredService<BroadcastScannerService>())); collection.AddScoped<WindowMediatorSubscriberBase, SyncshellFinderUI>((s) => new SyncshellFinderUI(s.GetRequiredService<ILogger<SyncshellFinderUI>>(), s.GetRequiredService<LightlessMediator>(), s.GetRequiredService<PerformanceCollectorService>(), s.GetRequiredService<BroadcastService>(), s.GetRequiredService<UiSharedService>(), s.GetRequiredService<ApiController>(), s.GetRequiredService<BroadcastScannerService>(), s.GetRequiredService<PairManager>()));
collection.AddScoped<IPopupHandler, BanUserPopupHandler>(); collection.AddScoped<IPopupHandler, BanUserPopupHandler>();
collection.AddScoped<IPopupHandler, CensusPopupHandler>(); collection.AddScoped<IPopupHandler, CensusPopupHandler>();
collection.AddScoped<CacheCreationService>(); collection.AddScoped<CacheCreationService>();

View File

@@ -4,9 +4,7 @@ using LightlessSync.LightlessConfiguration;
using LightlessSync.Services.Mediator; using LightlessSync.Services.Mediator;
using LightlessSync.Utils; using LightlessSync.Utils;
using LightlessSync.WebAPI; using LightlessSync.WebAPI;
using LightlessSync.WebAPI.SignalR;
using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -16,7 +14,6 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
private readonly ILogger<BroadcastService> _logger; private readonly ILogger<BroadcastService> _logger;
private readonly ApiController _apiController; private readonly ApiController _apiController;
private readonly LightlessMediator _mediator; private readonly LightlessMediator _mediator;
private readonly HubFactory _hubFactory;
private readonly LightlessConfigService _config; private readonly LightlessConfigService _config;
private readonly DalamudUtilService _dalamudUtil; private readonly DalamudUtilService _dalamudUtil;
public LightlessMediator Mediator => _mediator; public LightlessMediator Mediator => _mediator;
@@ -29,35 +26,37 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
private TimeSpan? _remainingTtl = null; private TimeSpan? _remainingTtl = null;
private DateTime _lastTtlCheck = DateTime.MinValue; private DateTime _lastTtlCheck = DateTime.MinValue;
private DateTime _lastForcedDisableTime = DateTime.MinValue; private DateTime _lastForcedDisableTime = DateTime.MinValue;
private static readonly TimeSpan DisableCooldown = TimeSpan.FromSeconds(5); private static readonly TimeSpan _disableCooldown = TimeSpan.FromSeconds(5);
public TimeSpan? RemainingTtl => _remainingTtl; public TimeSpan? RemainingTtl => _remainingTtl;
public TimeSpan? RemainingCooldown public TimeSpan? RemainingCooldown
{ {
get get
{ {
var elapsed = DateTime.UtcNow - _lastForcedDisableTime; var elapsed = DateTime.UtcNow - _lastForcedDisableTime;
if (elapsed >= DisableCooldown) return null; if (elapsed >= _disableCooldown) return null;
return DisableCooldown - elapsed; return _disableCooldown - elapsed;
} }
} }
public BroadcastService(ILogger<BroadcastService> logger, LightlessMediator mediator, HubFactory hubFactory, LightlessConfigService config, DalamudUtilService dalamudUtil, ApiController apiController)
public BroadcastService(ILogger<BroadcastService> logger, LightlessMediator mediator, LightlessConfigService config, DalamudUtilService dalamudUtil, ApiController apiController)
{ {
_logger = logger; _logger = logger;
_mediator = mediator; _mediator = mediator;
_hubFactory = hubFactory;
_config = config; _config = config;
_dalamudUtil = dalamudUtil; _dalamudUtil = dalamudUtil;
_apiController = apiController; _apiController = apiController;
} }
private async Task RequireConnectionAsync(string context, Func<Task> action) private async Task RequireConnectionAsync(string context, Func<Task> action)
{ {
if (!_apiController.IsConnected) if (!_apiController.IsConnected)
{ {
_logger.LogDebug($"{context} skipped, not connected"); _logger.LogDebug(context + " skipped, not connected");
return; return;
} }
await action().ConfigureAwait(false); await action().ConfigureAwait(false);
} }
public async Task StartAsync(CancellationToken cancellationToken) public async Task StartAsync(CancellationToken cancellationToken)
{ {
_mediator.Subscribe<EnableBroadcastMessage>(this, OnEnableBroadcast); _mediator.Subscribe<EnableBroadcastMessage>(this, OnEnableBroadcast);
@@ -65,7 +64,7 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
_mediator.Subscribe<PriorityFrameworkUpdateMessage>(this, OnTick); _mediator.Subscribe<PriorityFrameworkUpdateMessage>(this, OnTick);
_apiController.OnConnected += () => _ = CheckLightfinderSupportAsync(cancellationToken); _apiController.OnConnected += () => _ = CheckLightfinderSupportAsync(cancellationToken);
_ = CheckLightfinderSupportAsync(cancellationToken); //_ = CheckLightfinderSupportAsync(cancellationToken);
} }
public Task StopAsync(CancellationToken cancellationToken) public Task StopAsync(CancellationToken cancellationToken)
@@ -86,13 +85,12 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
return; return;
var hub = _hubFactory.GetOrCreate(CancellationToken.None);
var dummy = "0".PadLeft(64, '0'); var dummy = "0".PadLeft(64, '0');
await hub.InvokeAsync<BroadcastStatusInfoDto?>("IsUserBroadcasting", dummy, cancellationToken); await _apiController.IsUserBroadcasting(dummy).ConfigureAwait(false);
await hub.InvokeAsync("SetBroadcastStatus", dummy, true, null, cancellationToken); await _apiController.SetBroadcastStatus(dummy, true, null).ConfigureAwait(false);
await hub.InvokeAsync<TimeSpan?>("GetBroadcastTtl", dummy, cancellationToken); await _apiController.GetBroadcastTtl(dummy).ConfigureAwait(false);
await hub.InvokeAsync<Dictionary<string, BroadcastStatusInfoDto?>>("AreUsersBroadcasting", new[] { dummy }, cancellationToken); await _apiController.AreUsersBroadcasting([dummy]).ConfigureAwait(false);
IsLightFinderAvailable = true; IsLightFinderAvailable = true;
_logger.LogInformation("Lightfinder is available."); _logger.LogInformation("Lightfinder is available.");
@@ -119,6 +117,7 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
_config.Current.BroadcastEnabled = false; _config.Current.BroadcastEnabled = false;
_config.Current.BroadcastTtl = DateTime.MinValue; _config.Current.BroadcastTtl = DateTime.MinValue;
_config.Save(); _config.Save();
_mediator.Publish(new BroadcastStatusChangedMessage(false, null)); _mediator.Publish(new BroadcastStatusChangedMessage(false, null));
} }
} }
@@ -151,13 +150,13 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
_config.Save(); _config.Save();
_mediator.Publish(new BroadcastStatusChangedMessage(false, null)); _mediator.Publish(new BroadcastStatusChangedMessage(false, null));
Mediator.Publish(new EventMessage(new Services.Events.Event(nameof(BroadcastService), Services.Events.EventSeverity.Informational,$"Disabled Lightfinder for Player: {msg.HashedCid}"))); Mediator.Publish(new EventMessage(new Services.Events.Event(nameof(BroadcastService), Services.Events.EventSeverity.Informational, $"Disabled Lightfinder for Player: {msg.HashedCid}")));
return; return;
} }
_waitingForTtlFetch = true; _waitingForTtlFetch = true;
var ttl = await GetBroadcastTtlAsync(msg.HashedCid).ConfigureAwait(false); TimeSpan? ttl = await GetBroadcastTtlAsync(msg.HashedCid).ConfigureAwait(false);
if (ttl is { } remaining && remaining > TimeSpan.Zero) if (ttl is { } remaining && remaining > TimeSpan.Zero)
{ {
@@ -325,8 +324,8 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
_syncedOnStartup = true; _syncedOnStartup = true;
try try
{ {
var hashedCid = (await _dalamudUtil.GetCIDAsync().ConfigureAwait(false)).ToString().GetHash256(); string hashedCid = (await _dalamudUtil.GetCIDAsync().ConfigureAwait(false)).ToString().GetHash256();
var ttl = await GetBroadcastTtlAsync(hashedCid).ConfigureAwait(false); TimeSpan? ttl = await GetBroadcastTtlAsync(hashedCid).ConfigureAwait(false);
if (ttl is { } if (ttl is { }
remaining && remaining > TimeSpan.Zero) remaining && remaining > TimeSpan.Zero)
{ {
@@ -357,8 +356,8 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
return; return;
} }
var expiry = _config.Current.BroadcastTtl; DateTime expiry = _config.Current.BroadcastTtl;
var remaining = expiry - DateTime.UtcNow; TimeSpan remaining = expiry - DateTime.UtcNow;
_remainingTtl = remaining > TimeSpan.Zero ? remaining : null; _remainingTtl = remaining > TimeSpan.Zero ? remaining : null;
if (_remainingTtl == null) if (_remainingTtl == null)
{ {

View File

@@ -104,7 +104,7 @@ namespace LightlessSync.UI
try try
{ {
_allSyncshells = await _apiController.GroupsGetAll(); _allSyncshells = await _apiController.GroupsGetAll().ConfigureAwait(false);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -131,7 +131,7 @@ namespace LightlessSync.UI
ImGuiHelpers.ScaledDummy(0.25f); ImGuiHelpers.ScaledDummy(0.25f);
} }
if (ImGui.BeginTabBar("##MyTabBar")) if (ImGui.BeginTabBar("##BroadcastTabs"))
{ {
if (ImGui.BeginTabItem("Lightfinder")) if (ImGui.BeginTabItem("Lightfinder"))
{ {

View File

@@ -141,7 +141,7 @@ public class CompactUi : WindowMediatorSubscriberBase
}, },
}; };
_drawFolders = [.. GetDrawFolders()]; _drawFolders = [.. DrawFolders];
#if DEBUG #if DEBUG
string dev = "Dev Build"; string dev = "Dev Build";
@@ -158,7 +158,7 @@ public class CompactUi : WindowMediatorSubscriberBase
Mediator.Subscribe<CutsceneEndMessage>(this, (_) => UiSharedService_GposeEnd()); Mediator.Subscribe<CutsceneEndMessage>(this, (_) => UiSharedService_GposeEnd());
Mediator.Subscribe<DownloadStartedMessage>(this, (msg) => _currentDownloads[msg.DownloadId] = msg.DownloadStatus); Mediator.Subscribe<DownloadStartedMessage>(this, (msg) => _currentDownloads[msg.DownloadId] = msg.DownloadStatus);
Mediator.Subscribe<DownloadFinishedMessage>(this, (msg) => _currentDownloads.TryRemove(msg.DownloadId, out _)); Mediator.Subscribe<DownloadFinishedMessage>(this, (msg) => _currentDownloads.TryRemove(msg.DownloadId, out _));
Mediator.Subscribe<RefreshUiMessage>(this, (msg) => _drawFolders = GetDrawFolders().ToList()); Mediator.Subscribe<RefreshUiMessage>(this, (msg) => _drawFolders = DrawFolders.ToList());
Flags |= ImGuiWindowFlags.NoDocking; Flags |= ImGuiWindowFlags.NoDocking;
@@ -561,6 +561,7 @@ public class CompactUi : WindowMediatorSubscriberBase
if ((isOverTriHold || isOverVRAMUsage) && _playerPerformanceConfig.Current.WarnOnExceedingThresholds) if ((isOverTriHold || isOverVRAMUsage) && _playerPerformanceConfig.Current.WarnOnExceedingThresholds)
{ {
ImGui.SameLine(); ImGui.SameLine();
ImGui.SetCursorPosY(cursorY + 15f);
_uiSharedService.IconText(FontAwesomeIcon.ExclamationTriangle, UIColors.Get("LightlessYellow")); _uiSharedService.IconText(FontAwesomeIcon.ExclamationTriangle, UIColors.Get("LightlessYellow"));
string warningMessage = ""; string warningMessage = "";
@@ -611,152 +612,151 @@ public class CompactUi : WindowMediatorSubscriberBase
} }
} }
private IEnumerable<IDrawFolder> GetDrawFolders() private IEnumerable<IDrawFolder> DrawFolders
{ {
List<IDrawFolder> drawFolders = []; get
var allPairs = _pairManager.PairsWithGroups
.ToDictionary(k => k.Key, k => k.Value);
var filteredPairs = allPairs
.Where(p =>
{ {
if (_tabMenu.Filter.IsNullOrEmpty()) return true; var drawFolders = new List<IDrawFolder>();
return p.Key.UserData.AliasOrUID.Contains(_tabMenu.Filter, StringComparison.OrdinalIgnoreCase) || var filter = _tabMenu.Filter;
(p.Key.GetNote()?.Contains(_tabMenu.Filter, StringComparison.OrdinalIgnoreCase) ?? false) ||
(p.Key.PlayerName?.Contains(_tabMenu.Filter, StringComparison.OrdinalIgnoreCase) ?? false);
})
.ToDictionary(k => k.Key, k => k.Value);
string? AlphabeticalSort(KeyValuePair<Pair, List<GroupFullInfoDto>> u)
=> (_configService.Current.ShowCharacterNameInsteadOfNotesForVisible && !string.IsNullOrEmpty(u.Key.PlayerName)
? (_configService.Current.PreferNotesOverNamesForVisible ? u.Key.GetNote() : u.Key.PlayerName)
: (u.Key.GetNote() ?? u.Key.UserData.AliasOrUID));
bool FilterOnlineOrPausedSelf(KeyValuePair<Pair, List<GroupFullInfoDto>> u)
=> (u.Key.IsOnline || (!u.Key.IsOnline && !_configService.Current.ShowOfflineUsersSeparately)
|| u.Key.UserPair.OwnPermissions.IsPaused());
Dictionary<Pair, List<GroupFullInfoDto>> BasicSortedDictionary(IEnumerable<KeyValuePair<Pair, List<GroupFullInfoDto>>> u)
=> u.OrderByDescending(u => u.Key.IsVisible)
.ThenByDescending(u => u.Key.IsOnline)
.ThenBy(AlphabeticalSort, StringComparer.OrdinalIgnoreCase)
.ToDictionary(u => u.Key, u => u.Value);
ImmutableList<Pair> ImmutablePairList(IEnumerable<KeyValuePair<Pair, List<GroupFullInfoDto>>> u)
=> u.Select(k => k.Key).ToImmutableList();
bool FilterVisibleUsers(KeyValuePair<Pair, List<GroupFullInfoDto>> u)
=> u.Key.IsVisible
&& (_configService.Current.ShowSyncshellUsersInVisible || !(!_configService.Current.ShowSyncshellUsersInVisible && !u.Key.IsDirectlyPaired));
bool FilterTagUsers(KeyValuePair<Pair, List<GroupFullInfoDto>> u, string tag)
=> u.Key.IsDirectlyPaired && !u.Key.IsOneSidedPair && _tagHandler.HasPairTag(u.Key.UserData.UID, tag);
bool FilterGroupUsers(KeyValuePair<Pair, List<GroupFullInfoDto>> u, GroupFullInfoDto group)
=> u.Value.Exists(g => string.Equals(g.GID, group.GID, StringComparison.Ordinal));
bool FilterNotTaggedUsers(KeyValuePair<Pair, List<GroupFullInfoDto>> u)
=> u.Key.IsDirectlyPaired && !u.Key.IsOneSidedPair && !_tagHandler.HasAnyPairTag(u.Key.UserData.UID);
bool FilterNotTaggedSyncshells(GroupFullInfoDto group)
=> (!_tagHandler.HasAnySyncshellTag(group.GID) && !_configService.Current.ShowGroupedSyncshellsInAll) || true;
bool FilterOfflineUsers(KeyValuePair<Pair, List<GroupFullInfoDto>> u)
=> ((u.Key.IsDirectlyPaired && _configService.Current.ShowSyncshellOfflineUsersSeparately)
|| !_configService.Current.ShowSyncshellOfflineUsersSeparately)
&& (!u.Key.IsOneSidedPair || u.Value.Any()) && !u.Key.IsOnline && !u.Key.UserPair.OwnPermissions.IsPaused();
bool FilterOfflineSyncshellUsers(KeyValuePair<Pair, List<GroupFullInfoDto>> u)
=> (!u.Key.IsDirectlyPaired && !u.Key.IsOnline && !u.Key.UserPair.OwnPermissions.IsPaused());
var allPairs = _pairManager.PairsWithGroups.ToDictionary(k => k.Key, k => k.Value);
var filteredPairs = allPairs.Where(p => PassesFilter(p.Key, filter)).ToDictionary(k => k.Key, k => k.Value);
//Filter of online/visible pairs
if (_configService.Current.ShowVisibleUsersSeparately) if (_configService.Current.ShowVisibleUsersSeparately)
{ {
var allVisiblePairs = ImmutablePairList(allPairs var allVisiblePairs = ImmutablePairList(allPairs.Where(p => FilterVisibleUsers(p.Key)));
.Where(FilterVisibleUsers)); var filteredVisiblePairs = BasicSortedDictionary(filteredPairs.Where(p => FilterVisibleUsers(p.Key)));
var filteredVisiblePairs = BasicSortedDictionary(filteredPairs
.Where(FilterVisibleUsers));
drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(TagHandler.CustomVisibleTag, filteredVisiblePairs, allVisiblePairs)); drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(TagHandler.CustomVisibleTag, filteredVisiblePairs, allVisiblePairs));
} }
List<IDrawFolder> groupFolders = new(); //Filter of not foldered syncshells
var groupFolders = new List<IDrawFolder>();
foreach (var group in _pairManager.GroupPairs.Select(g => g.Key).OrderBy(g => g.GroupAliasOrGID, StringComparer.OrdinalIgnoreCase)) foreach (var group in _pairManager.GroupPairs.Select(g => g.Key).OrderBy(g => g.GroupAliasOrGID, StringComparer.OrdinalIgnoreCase))
{ {
GetGroups(allPairs, filteredPairs, group, out ImmutableList<Pair> allGroupPairs, out Dictionary<Pair, List<GroupFullInfoDto>> filteredGroupPairs); GetGroups(allPairs, filteredPairs, group, out ImmutableList<Pair> allGroupPairs, out Dictionary<Pair, List<GroupFullInfoDto>> filteredGroupPairs);
if (FilterNotTaggedSyncshells(group)) if (FilterNotTaggedSyncshells(group))
{ {
groupFolders.Add(_drawEntityFactory.CreateDrawGroupFolder(group, filteredGroupPairs, allGroupPairs)); groupFolders.Add(_drawEntityFactory.CreateDrawGroupFolder(group, filteredGroupPairs, allGroupPairs));
} }
} }
//Filter of grouped up syncshells (All Syncshells Folder)
if (_configService.Current.GroupUpSyncshells) if (_configService.Current.GroupUpSyncshells)
drawFolders.Add(new DrawGroupedGroupFolder(groupFolders, _tagHandler, _uiSharedService, _selectSyncshellForTagUi, _renameSyncshellTagUi, "")); drawFolders.Add(new DrawGroupedGroupFolder(groupFolders, _tagHandler, _uiSharedService,
_selectSyncshellForTagUi, _renameSyncshellTagUi, ""));
else else
drawFolders.AddRange(groupFolders); drawFolders.AddRange(groupFolders);
var tags = _tagHandler.GetAllPairTagsSorted(); //Filter of grouped/foldered pairs
foreach (var tag in tags) foreach (var tag in _tagHandler.GetAllPairTagsSorted())
{ {
var allTagPairs = ImmutablePairList(allPairs var allTagPairs = ImmutablePairList(allPairs.Where(p => FilterTagUsers(p.Key, tag)));
.Where(u => FilterTagUsers(u, tag))); var filteredTagPairs = BasicSortedDictionary(filteredPairs.Where(p => FilterTagUsers(p.Key, tag) && FilterOnlineOrPausedSelf(p.Key)));
var filteredTagPairs = BasicSortedDictionary(filteredPairs
.Where(u => FilterTagUsers(u, tag) && FilterOnlineOrPausedSelf(u)));
drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(tag, filteredTagPairs, allTagPairs)); drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(tag, filteredTagPairs, allTagPairs));
} }
var syncshellTags = _tagHandler.GetAllSyncshellTagsSorted(); //Filter of grouped/foldered syncshells
foreach (var syncshelltag in syncshellTags) foreach (var syncshellTag in _tagHandler.GetAllSyncshellTagsSorted())
{ {
List<IDrawFolder> syncshellFolderTags = []; var syncshellFolderTags = new List<IDrawFolder>();
foreach (var group in _pairManager.GroupPairs.Select(g => g.Key).OrderBy(g => g.GroupAliasOrGID, StringComparer.OrdinalIgnoreCase)) foreach (var group in _pairManager.GroupPairs.Select(g => g.Key).OrderBy(g => g.GroupAliasOrGID, StringComparer.OrdinalIgnoreCase))
{ {
if (_tagHandler.HasSyncshellTag(group.GID, syncshelltag)) if (_tagHandler.HasSyncshellTag(group.GID, syncshellTag))
{ {
GetGroups(allPairs, filteredPairs, group, out ImmutableList<Pair> allGroupPairs, out Dictionary<Pair, List<GroupFullInfoDto>> filteredGroupPairs); GetGroups(allPairs, filteredPairs, group,
out ImmutableList<Pair> allGroupPairs,
out Dictionary<Pair, List<GroupFullInfoDto>> filteredGroupPairs);
syncshellFolderTags.Add(_drawEntityFactory.CreateDrawGroupFolder($"tag_{group.GID}", group, filteredGroupPairs, allGroupPairs)); syncshellFolderTags.Add(_drawEntityFactory.CreateDrawGroupFolder($"tag_{group.GID}", group, filteredGroupPairs, allGroupPairs));
} }
} }
if (syncshellFolderTags.Count > 0) drawFolders.Add(new DrawGroupedGroupFolder(syncshellFolderTags, _tagHandler, _uiSharedService, _selectSyncshellForTagUi, _renameSyncshellTagUi, syncshellTag));
{
drawFolders.Add(new DrawGroupedGroupFolder(syncshellFolderTags, _tagHandler, _uiSharedService, _selectSyncshellForTagUi, _renameSyncshellTagUi, syncshelltag));
}
} }
var allOnlineNotTaggedPairs = ImmutablePairList(allPairs //Filter of not grouped/foldered and offline pairs
.Where(FilterNotTaggedUsers)); var allOnlineNotTaggedPairs = ImmutablePairList(allPairs.Where(p => FilterNotTaggedUsers(p.Key)));
var onlineNotTaggedPairs = BasicSortedDictionary(filteredPairs var onlineNotTaggedPairs = BasicSortedDictionary(filteredPairs.Where(p => FilterNotTaggedUsers(p.Key) && FilterOnlineOrPausedSelf(p.Key)));
.Where(u => FilterNotTaggedUsers(u) && FilterOnlineOrPausedSelf(u)));
drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder((_configService.Current.ShowOfflineUsersSeparately ? TagHandler.CustomOnlineTag : TagHandler.CustomAllTag), drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder((_configService.Current.ShowOfflineUsersSeparately ? TagHandler.CustomOnlineTag : TagHandler.CustomAllTag), onlineNotTaggedPairs, allOnlineNotTaggedPairs));
onlineNotTaggedPairs, allOnlineNotTaggedPairs));
if (_configService.Current.ShowOfflineUsersSeparately) if (_configService.Current.ShowOfflineUsersSeparately)
{ {
var allOfflinePairs = ImmutablePairList(allPairs var allOfflinePairs = ImmutablePairList(allPairs.Where(p => FilterOfflineUsers(p.Key, p.Value)));
.Where(FilterOfflineUsers)); var filteredOfflinePairs = BasicSortedDictionary(filteredPairs.Where(p => FilterOfflineUsers(p.Key, p.Value)));
var filteredOfflinePairs = BasicSortedDictionary(filteredPairs
.Where(FilterOfflineUsers));
drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(TagHandler.CustomOfflineTag, filteredOfflinePairs, allOfflinePairs)); drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(TagHandler.CustomOfflineTag, filteredOfflinePairs, allOfflinePairs));
if (_configService.Current.ShowSyncshellOfflineUsersSeparately) if (_configService.Current.ShowSyncshellOfflineUsersSeparately)
{ {
var allOfflineSyncshellUsers = ImmutablePairList(allPairs var allOfflineSyncshellUsers = ImmutablePairList(allPairs.Where(p => FilterOfflineSyncshellUsers(p.Key)));
.Where(FilterOfflineSyncshellUsers)); var filteredOfflineSyncshellUsers = BasicSortedDictionary(filteredPairs.Where(p => FilterOfflineSyncshellUsers(p.Key)));
var filteredOfflineSyncshellUsers = BasicSortedDictionary(filteredPairs
.Where(FilterOfflineSyncshellUsers));
drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(TagHandler.CustomOfflineSyncshellTag, drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(TagHandler.CustomOfflineSyncshellTag, filteredOfflineSyncshellUsers, allOfflineSyncshellUsers));
filteredOfflineSyncshellUsers,
allOfflineSyncshellUsers));
} }
} }
//Unpaired
drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(TagHandler.CustomUnpairedTag, drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(TagHandler.CustomUnpairedTag,
BasicSortedDictionary(filteredPairs.Where(u => u.Key.IsOneSidedPair)), BasicSortedDictionary(filteredPairs.Where(p => p.Key.IsOneSidedPair)),
ImmutablePairList(allPairs.Where(u => u.Key.IsOneSidedPair)))); ImmutablePairList(allPairs.Where(p => p.Key.IsOneSidedPair))));
return drawFolders; return drawFolders;
}
}
void GetGroups(Dictionary<Pair, List<GroupFullInfoDto>> allPairs, Dictionary<Pair, List<GroupFullInfoDto>> filteredPairs, GroupFullInfoDto group, out ImmutableList<Pair> allGroupPairs, out Dictionary<Pair, List<GroupFullInfoDto>> filteredGroupPairs) private static bool PassesFilter(Pair pair, string filter)
{
if (string.IsNullOrEmpty(filter)) return true;
return pair.UserData.AliasOrUID.Contains(filter, StringComparison.OrdinalIgnoreCase) || (pair.GetNote()?.Contains(filter, StringComparison.OrdinalIgnoreCase) ?? false) || (pair.PlayerName?.Contains(filter, StringComparison.OrdinalIgnoreCase) ?? false);
}
private string AlphabeticalSortKey(Pair pair)
{
if (_configService.Current.ShowCharacterNameInsteadOfNotesForVisible && !string.IsNullOrEmpty(pair.PlayerName))
{
return _configService.Current.PreferNotesOverNamesForVisible ? (pair.GetNote() ?? string.Empty) : pair.PlayerName;
}
return pair.GetNote() ?? pair.UserData.AliasOrUID;
}
private bool FilterOnlineOrPausedSelf(Pair pair) => pair.IsOnline || (!pair.IsOnline && !_configService.Current.ShowOfflineUsersSeparately) || pair.UserPair.OwnPermissions.IsPaused();
private bool FilterVisibleUsers(Pair pair) => pair.IsVisible && (_configService.Current.ShowSyncshellUsersInVisible || pair.IsDirectlyPaired);
private bool FilterTagUsers(Pair pair, string tag) => pair.IsDirectlyPaired && !pair.IsOneSidedPair && _tagHandler.HasPairTag(pair.UserData.UID, tag);
private static bool FilterGroupUsers(List<GroupFullInfoDto> groups, GroupFullInfoDto group) => groups.Exists(g => string.Equals(g.GID, group.GID, StringComparison.Ordinal));
private bool FilterNotTaggedUsers(Pair pair) => pair.IsDirectlyPaired && !pair.IsOneSidedPair && !_tagHandler.HasAnyPairTag(pair.UserData.UID);
private bool FilterNotTaggedSyncshells(GroupFullInfoDto group) => !_tagHandler.HasAnySyncshellTag(group.GID) || _configService.Current.ShowGroupedSyncshellsInAll;
private bool FilterOfflineUsers(Pair pair, List<GroupFullInfoDto> groups) => ((pair.IsDirectlyPaired && _configService.Current.ShowSyncshellOfflineUsersSeparately) || !_configService.Current.ShowSyncshellOfflineUsersSeparately) && (!pair.IsOneSidedPair || groups.Count != 0) && !pair.IsOnline && !pair.UserPair.OwnPermissions.IsPaused();
private static bool FilterOfflineSyncshellUsers(Pair pair) => !pair.IsDirectlyPaired && !pair.IsOnline && !pair.UserPair.OwnPermissions.IsPaused();
private Dictionary<Pair, List<GroupFullInfoDto>> BasicSortedDictionary(IEnumerable<KeyValuePair<Pair, List<GroupFullInfoDto>>> pairs) => pairs.OrderByDescending(u => u.Key.IsVisible).ThenByDescending(u => u.Key.IsOnline).ThenBy(u => AlphabeticalSortKey(u.Key), StringComparer.OrdinalIgnoreCase).ToDictionary(u => u.Key, u => u.Value);
private static ImmutableList<Pair> ImmutablePairList(IEnumerable<KeyValuePair<Pair, List<GroupFullInfoDto>>> pairs) => [.. pairs.Select(k => k.Key)];
private void GetGroups(Dictionary<Pair, List<GroupFullInfoDto>> allPairs,
Dictionary<Pair, List<GroupFullInfoDto>> filteredPairs,
GroupFullInfoDto group,
out ImmutableList<Pair> allGroupPairs,
out Dictionary<Pair, List<GroupFullInfoDto>> filteredGroupPairs)
{ {
allGroupPairs = ImmutablePairList(allPairs allGroupPairs = ImmutablePairList(allPairs
.Where(u => FilterGroupUsers(u, group))); .Where(u => FilterGroupUsers(u.Value, group)));
filteredGroupPairs = filteredPairs filteredGroupPairs = filteredPairs
.Where(u => FilterGroupUsers(u, group) && FilterOnlineOrPausedSelf(u)) .Where(u => FilterGroupUsers( u.Value, group) && FilterOnlineOrPausedSelf(u.Key))
.OrderByDescending(u => u.Key.IsOnline) .OrderByDescending(u => u.Key.IsOnline)
.ThenBy(u => .ThenBy(u =>
{ {
@@ -768,10 +768,9 @@ public class CompactUi : WindowMediatorSubscriberBase
} }
return u.Key.IsVisible ? 3 : 4; return u.Key.IsVisible ? 3 : 4;
}) })
.ThenBy(AlphabeticalSort, StringComparer.OrdinalIgnoreCase) .ThenBy(u => AlphabeticalSortKey(u.Key), StringComparer.OrdinalIgnoreCase)
.ToDictionary(k => k.Key, k => k.Value); .ToDictionary(k => k.Key, k => k.Value);
} }
}
private string GetServerError() private string GetServerError()
{ {

View File

@@ -34,8 +34,6 @@ public class DrawGroupedGroupFolder : IDrawFolder
public void Draw() public void Draw()
{ {
if (!_groups.Any()) return;
string _id = "__folder_syncshells"; string _id = "__folder_syncshells";
if (_tag != "") if (_tag != "")
{ {

View File

@@ -4,14 +4,17 @@ using Dalamud.Interface.Colors;
using Dalamud.Interface.ImGuiFileDialog; using Dalamud.Interface.ImGuiFileDialog;
using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using LightlessSync.API.Data; using LightlessSync.API.Data;
using LightlessSync.API.Dto.User; using LightlessSync.API.Dto.User;
using LightlessSync.Services; using LightlessSync.Services;
using LightlessSync.Services.Mediator; using LightlessSync.Services.Mediator;
using LightlessSync.Utils;
using LightlessSync.WebAPI; using LightlessSync.WebAPI;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using SixLabors.ImageSharp; using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using System.Numerics;
namespace LightlessSync.UI; namespace LightlessSync.UI;
@@ -30,6 +33,15 @@ public class EditProfileUi : WindowMediatorSubscriberBase
private bool _showFileDialogError = false; private bool _showFileDialogError = false;
private bool _wasOpen; private bool _wasOpen;
private bool vanityInitialized; // useless for now
private bool textEnabled;
private bool glowEnabled;
private Vector4 textColor;
private Vector4 glowColor;
private record VanityState(bool TextEnabled, bool GlowEnabled, Vector4 TextColor, Vector4 GlowColor);
private VanityState _savedVanity;
public EditProfileUi(ILogger<EditProfileUi> logger, LightlessMediator mediator, public EditProfileUi(ILogger<EditProfileUi> logger, LightlessMediator mediator,
ApiController apiController, UiSharedService uiSharedService, FileDialogManager fileDialogManager, ApiController apiController, UiSharedService uiSharedService, FileDialogManager fileDialogManager,
LightlessProfileManager lightlessProfileManager, PerformanceCollectorService performanceCollectorService) LightlessProfileManager lightlessProfileManager, PerformanceCollectorService performanceCollectorService)
@@ -38,8 +50,8 @@ public class EditProfileUi : WindowMediatorSubscriberBase
IsOpen = false; IsOpen = false;
this.SizeConstraints = new() this.SizeConstraints = new()
{ {
MinimumSize = new(768, 512), MinimumSize = new(850, 640),
MaximumSize = new(768, 2000) MaximumSize = new(850, 700)
}; };
_apiController = apiController; _apiController = apiController;
_uiSharedService = uiSharedService; _uiSharedService = uiSharedService;
@@ -57,14 +69,61 @@ public class EditProfileUi : WindowMediatorSubscriberBase
_pfpTextureWrap = null; _pfpTextureWrap = null;
} }
}); });
Mediator.Subscribe<ConnectedMessage>(this, msg =>
{
LoadVanity();
});
}
void DrawNoteLine(string icon, Vector4 color, string text)
{
_uiSharedService.MediumText(icon, color);
ImGui.SameLine();
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 3);
ImGui.TextWrapped(text);
}
private void LoadVanity()
{
textEnabled = !string.IsNullOrEmpty(_apiController.TextColorHex);
glowEnabled = !string.IsNullOrEmpty(_apiController.TextGlowColorHex);
textColor = textEnabled ? UIColors.HexToRgba(_apiController.TextColorHex!) : Vector4.One;
glowColor = glowEnabled ? UIColors.HexToRgba(_apiController.TextGlowColorHex!) : Vector4.Zero;
_savedVanity = new VanityState(textEnabled, glowEnabled, textColor, glowColor);
vanityInitialized = true;
} }
protected override void DrawInternal() protected override void DrawInternal()
{ {
_uiSharedService.BigText("Current Profile (as saved on server)"); _uiSharedService.UnderlinedBigText("Notes and Rules for Profiles", UIColors.Get("LightlessYellow"));
ImGui.Dummy(new Vector2(5));
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(2, 2));
DrawNoteLine("# ", UIColors.Get("LightlessBlue"), "All users that are paired and unpaused with you will be able to see your profile picture and description.");
DrawNoteLine("! ", UIColors.Get("LightlessYellow"), "Other users have the possibility to report your profile for breaking the rules.");
DrawNoteLine("!!! ", UIColors.Get("DimRed"), "AVOID: Anything as profile image that can be considered highly illegal or obscene (bestiality, anything that could be considered a sexual act with a minor (that includes Lalafells), etc.)");
DrawNoteLine("!!! ", UIColors.Get("DimRed"), "AVOID: Slurs of any kind in the description that can be considered highly offensive");
DrawNoteLine("! ", UIColors.Get("LightlessYellow"), "In case of valid reports from other users this can lead to disabling your profile forever or terminating your Lightless account indefinitely.");
DrawNoteLine("! ", UIColors.Get("LightlessYellow"), "Judgement of your profile validity from reports through staff is not up to debate and the decisions to disable your profile/account permanent.");
DrawNoteLine("! ", UIColors.Get("LightlessBlue"), "If your profile picture or profile description could be considered NSFW, enable the toggle in profile settings.");
ImGui.PopStyleVar();
ImGui.Dummy(new Vector2(3));
var profile = _lightlessProfileManager.GetLightlessProfile(new UserData(_apiController.UID)); var profile = _lightlessProfileManager.GetLightlessProfile(new UserData(_apiController.UID));
if (ImGui.BeginTabBar("##EditProfileTabs"))
{
if (ImGui.BeginTabItem("Current Profile"))
{
_uiSharedService.MediumText("Current Profile (as saved on server)", UIColors.Get("LightlessPurple"));
ImGui.Dummy(new Vector2(5));
if (profile.IsFlagged) if (profile.IsFlagged)
{ {
UiSharedService.ColorTextWrapped(profile.Description, ImGuiColors.DalamudRed); UiSharedService.ColorTextWrapped(profile.Description, ImGuiColors.DalamudRed);
@@ -119,18 +178,14 @@ public class EditProfileUi : WindowMediatorSubscriberBase
ImGui.Checkbox("Is NSFW", ref nsfw); ImGui.Checkbox("Is NSFW", ref nsfw);
ImGui.EndDisabled(); ImGui.EndDisabled();
ImGui.Separator(); _uiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f);
_uiSharedService.BigText("Notes and Rules for Profiles"); ImGui.EndTabItem();
}
ImGui.TextWrapped($"- All users that are paired and unpaused with you will be able to see your profile picture and description.{Environment.NewLine}" + if (ImGui.BeginTabItem("Profile Settings"))
$"- Other users have the possibility to report your profile for breaking the rules.{Environment.NewLine}" + {
$"- !!! AVOID: anything as profile image that can be considered highly illegal or obscene (bestiality, anything that could be considered a sexual act with a minor (that includes Lalafells), etc.){Environment.NewLine}" + _uiSharedService.MediumText("Profile Settings", UIColors.Get("LightlessPurple"));
$"- !!! AVOID: slurs of any kind in the description that can be considered highly offensive{Environment.NewLine}" + ImGui.Dummy(new Vector2(5));
$"- In case of valid reports from other users this can lead to disabling your profile forever or terminating your Lightless account indefinitely.{Environment.NewLine}" +
$"- Judgement of your profile validity from reports through staff is not up to debate and the decisions to disable your profile/account permanent.{Environment.NewLine}" +
$"- If your profile picture or profile description could be considered NSFW, enable the toggle below.");
ImGui.Separator();
_uiSharedService.BigText("Profile Settings");
if (_uiSharedService.IconTextButton(FontAwesomeIcon.FileUpload, "Upload new profile picture")) if (_uiSharedService.IconTextButton(FontAwesomeIcon.FileUpload, "Upload new profile picture"))
{ {
@@ -223,6 +278,105 @@ public class EditProfileUi : WindowMediatorSubscriberBase
_ = _apiController.UserSetProfile(new UserProfileDto(new UserData(_apiController.UID), Disabled: false, IsNSFW: null, ProfilePictureBase64: null, "")); _ = _apiController.UserSetProfile(new UserProfileDto(new UserData(_apiController.UID), Disabled: false, IsNSFW: null, ProfilePictureBase64: null, ""));
} }
UiSharedService.AttachToolTip("Clears your profile description text"); UiSharedService.AttachToolTip("Clears your profile description text");
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem("Vanity Settings"))
{
_uiSharedService.MediumText("Supporter Vanity Settings", UIColors.Get("LightlessPurple"));
ImGui.Dummy(new Vector2(4));
DrawNoteLine("# ", UIColors.Get("LightlessPurple"), "Must be a supporter through Patreon/Ko-fi to access these settings.");
var hasVanity = _apiController.HasVanity;
if (!hasVanity)
{
UiSharedService.ColorTextWrapped("You do not currently have vanity access. Become a supporter to unlock these features.", UIColors.Get("DimRed"));
ImGui.Dummy(new Vector2(8));
ImGui.BeginDisabled();
}
_uiSharedService.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"), 1.5f);
_uiSharedService.MediumText("Colored UID", UIColors.Get("LightlessPurple"));
ImGui.Dummy(new Vector2(5));
var font = UiBuilder.MonoFont;
var playerUID = _apiController.UID;
var playerDisplay = _apiController.DisplayName;
var previewTextColor = textEnabled ? textColor : Vector4.One;
var previewGlowColor = glowEnabled ? glowColor : Vector4.Zero;
var seString = SeStringUtils.BuildFormattedPlayerName(playerDisplay, previewTextColor, previewGlowColor);
using (ImRaii.PushFont(font))
{
var offsetX = 10f;
var pos = ImGui.GetCursorScreenPos() + new Vector2(offsetX, 0);
var size = ImGui.CalcTextSize(seString.TextValue);
var padding = new Vector2(6, 3);
var rectMin = pos - padding;
var rectMax = pos + size + padding;
var bgColor = new Vector4(0.15f, 0.15f, 0.15f, 1f);
var borderColor = UIColors.Get("LightlessPurple");
var drawList = ImGui.GetWindowDrawList();
drawList.AddRectFilled(rectMin, rectMax, ImGui.GetColorU32(bgColor), 6.0f);
drawList.AddRect(rectMin, rectMax, ImGui.GetColorU32(borderColor), 6.0f, ImDrawFlags.None, 1.5f);
SeStringUtils.RenderSeStringWithHitbox(seString, pos, font);
}
const float colorPickAlign = 90f;
DrawNoteLine("- ", UIColors.Get("LightlessPurple"), "Text Color");
ImGui.SameLine(colorPickAlign);
ImGui.Checkbox("##toggleTextColor", ref textEnabled);
ImGui.SameLine();
ImGui.BeginDisabled(!textEnabled);
ImGui.ColorEdit4($"##color_text", ref textColor, ImGuiColorEditFlags.NoInputs | ImGuiColorEditFlags.AlphaPreviewHalf);
ImGui.EndDisabled();
DrawNoteLine("- ", UIColors.Get("LightlessPurple"), "Glow Color");
ImGui.SameLine(colorPickAlign);
ImGui.Checkbox("##toggleGlowColor", ref glowEnabled);
ImGui.SameLine();
ImGui.BeginDisabled(!glowEnabled);
ImGui.ColorEdit4($"##color_glow", ref glowColor, ImGuiColorEditFlags.NoInputs | ImGuiColorEditFlags.AlphaPreviewHalf);
ImGui.EndDisabled();
bool changed = !Equals(_savedVanity, new VanityState(textEnabled, glowEnabled, textColor, glowColor));
if (!changed)
ImGui.BeginDisabled();
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Save, "Save Changes"))
{
string? newText = textEnabled ? UIColors.RgbaToHex(textColor) : string.Empty;
string? newGlow = glowEnabled ? UIColors.RgbaToHex(glowColor) : string.Empty;
_ = _apiController.UserUpdateVanityColors(new UserVanityColorsDto(newText, newGlow));
_savedVanity = new VanityState(textEnabled, glowEnabled, textColor, glowColor);
}
if (!changed)
ImGui.EndDisabled();
ImGui.Dummy(new Vector2(5));
_uiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f);
if (!hasVanity)
ImGui.EndDisabled();
ImGui.EndTabItem();
}
ImGui.EndTabBar();
}
} }
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)

View File

@@ -98,20 +98,21 @@ public class IdDisplayHandler
var font = UiBuilder.MonoFont; var font = UiBuilder.MonoFont;
var isAdmin = pair.UserData.IsAdmin; Vector4? textColor = null;
var isModerator = pair.UserData.IsModerator; Vector4? glowColor = null;
Vector4? textColor = isAdmin if (pair.UserData.HasVanity)
? UIColors.Get("LightlessAdminText") {
: isModerator if (!string.IsNullOrWhiteSpace(pair.UserData.TextColorHex))
? UIColors.Get("LightlessModeratorText") {
: null; textColor = UIColors.HexToRgba(pair.UserData.TextColorHex);
}
Vector4? glowColor = isAdmin if (!string.IsNullOrWhiteSpace(pair.UserData.TextGlowColorHex))
? UIColors.Get("LightlessAdminGlow") {
: isModerator glowColor = UIColors.HexToRgba(pair.UserData.TextGlowColorHex);
? UIColors.Get("LightlessModeratorGlow") }
: null; }
var seString = (textColor != null || glowColor != null) var seString = (textColor != null || glowColor != null)
? SeStringUtils.BuildFormattedPlayerName(playerText, textColor, glowColor) ? SeStringUtils.BuildFormattedPlayerName(playerText, textColor, glowColor)

View File

@@ -167,7 +167,7 @@ public partial class IntroUi : WindowMediatorSubscriberBase
} }
else else
{ {
UiSharedService.TextWrapped("To not unnecessary download files already present on your computer, Lightless Sync will have to scan your Penumbra mod directory. " + UiSharedService.TextWrapped("To not unnecessarily download files already present on your computer, Lightless Sync will have to scan your Penumbra mod directory. " +
"Additionally, a local storage folder must be set where Lightless Sync will download other character files to. " + "Additionally, a local storage folder must be set where Lightless Sync will download other character files to. " +
"Once the storage folder is set and the scan complete, this page will automatically forward to registration at a service."); "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.TextWrapped("Note: The initial scan, depending on the amount of mods you have, might take a while. Please wait until it is completed.");

View File

@@ -63,7 +63,7 @@ internal class JoinSyncshellUI : WindowMediatorSubscriberBase
"Joining a Syncshell will pair you implicitly with all existing users in the Syncshell." + Environment.NewLine + "Joining a Syncshell will pair you implicitly with all existing users in the Syncshell." + Environment.NewLine +
"All permissions to all users in the Syncshell will be set to the preferred Syncshell permissions on joining, excluding prior set preferred permissions."); "All permissions to all users in the Syncshell will be set to the preferred Syncshell permissions on joining, excluding prior set preferred permissions.");
ImGui.Separator(); ImGui.Separator();
ImGui.TextUnformatted("Note: Syncshell ID and Password are case sensitive. MSS- is part of Syncshell IDs, unless using Vanity IDs."); ImGui.TextUnformatted("Note: Syncshell ID and Password are case sensitive. LLS- is part of Syncshell IDs, unless using Vanity IDs.");
ImGui.AlignTextToFramePadding(); ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted("Syncshell ID"); ImGui.TextUnformatted("Syncshell ID");

View File

@@ -1152,12 +1152,12 @@ public class SettingsUi : WindowMediatorSubscriberBase
} }
_uiShared.DrawHelpText("This will group up all Syncshells in a special 'All Syncshells' folder in the main UI."); _uiShared.DrawHelpText("This will group up all Syncshells in a special 'All Syncshells' folder in the main UI.");
//if (ImGui.Checkbox("Show grouped syncshells in main screen/all syncshells", ref groupedSyncshells)) if (ImGui.Checkbox("Show grouped syncshells in main screen/all syncshells", ref groupedSyncshells))
//{ {
// _configService.Current.ShowGroupedSyncshellsInAll = groupedSyncshells; _configService.Current.ShowGroupedSyncshellsInAll = groupedSyncshells;
// _configService.Save(); _configService.Save();
// Mediator.Publish(new RefreshUiMessage()); Mediator.Publish(new RefreshUiMessage());
//} }
_uiShared.DrawHelpText("This will show grouped syncshells in main screen or group 'All Syncshells'."); _uiShared.DrawHelpText("This will show grouped syncshells in main screen or group 'All Syncshells'.");
if (ImGui.Checkbox("Show player name for visible players", ref showNameInsteadOfNotes)) if (ImGui.Checkbox("Show player name for visible players", ref showNameInsteadOfNotes))

View File

@@ -336,7 +336,7 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
} }
ImGui.Separator(); ImGui.Separator();
if (_uiSharedService.MediumTreeNode("Mass Cleanup", UIColors.Get("LightlessPurple"))) if (_uiSharedService.MediumTreeNode("Mass Cleanup", UIColors.Get("DimRed")))
{ {
using (ImRaii.Disabled(!UiSharedService.CtrlPressed())) using (ImRaii.Disabled(!UiSharedService.CtrlPressed()))
{ {
@@ -348,6 +348,18 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
UiSharedService.AttachToolTip("This will remove all non-pinned, non-moderator users from the Syncshell." UiSharedService.AttachToolTip("This will remove all non-pinned, non-moderator users from the Syncshell."
+ UiSharedService.TooltipSeparator + "Hold CTRL to enable this button"); + UiSharedService.TooltipSeparator + "Hold CTRL to enable this button");
ImGui.SameLine();
using (ImRaii.Disabled(!UiSharedService.CtrlPressed()))
{
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Brush, "Clear Lightfinder Users"))
{
_ = _apiController.GroupClearFinder(new(GroupFullInfo.Group));
}
}
UiSharedService.AttachToolTip("This will remove all users that joined through Lightfinder from the Syncshell."
+ UiSharedService.TooltipSeparator + "Hold CTRL to enable this button");
ImGuiHelpers.ScaledDummy(2f); ImGuiHelpers.ScaledDummy(2f);
ImGui.Separator(); ImGui.Separator();
ImGuiHelpers.ScaledDummy(2f); ImGuiHelpers.ScaledDummy(2f);
@@ -410,12 +422,12 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
UiSharedService.TextWrapped($"Syncshell was pruned and {_pruneTask.Result} inactive user(s) have been removed."); UiSharedService.TextWrapped($"Syncshell was pruned and {_pruneTask.Result} inactive user(s) have been removed.");
} }
} }
_uiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f); _uiSharedService.ColoredSeparator(UIColors.Get("DimRed"), 1.5f);
ImGui.TreePop(); ImGui.TreePop();
} }
ImGui.Separator(); ImGui.Separator();
if (_uiSharedService.MediumTreeNode("User Bans", UIColors.Get("LightlessPurple"))) if (_uiSharedService.MediumTreeNode("User Bans", UIColors.Get("LightlessYellow")))
{ {
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Retweet, "Refresh Banlist from Server")) if (_uiSharedService.IconTextButton(FontAwesomeIcon.Retweet, "Refresh Banlist from Server"))
{ {
@@ -456,7 +468,7 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
} }
ImGui.EndTable(); ImGui.EndTable();
} }
_uiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f); _uiSharedService.ColoredSeparator(UIColors.Get("LightlessYellow"), 1.5f);
ImGui.TreePop(); ImGui.TreePop();
} }
ImGui.Separator(); ImGui.Separator();

View File

@@ -4,15 +4,15 @@ using Dalamud.Interface.Colors;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Utility.Raii;
using LightlessSync.API.Data.Enum; using LightlessSync.API.Data.Enum;
using LightlessSync.API.Data.Extensions;
using LightlessSync.API.Dto; using LightlessSync.API.Dto;
using LightlessSync.API.Dto.Group; using LightlessSync.API.Dto.Group;
using LightlessSync.LightlessConfiguration; using LightlessSync.PlayerData.Pairs;
using LightlessSync.Services; using LightlessSync.Services;
using LightlessSync.Services.Mediator; using LightlessSync.Services.Mediator;
using LightlessSync.Utils; using LightlessSync.Utils;
using LightlessSync.WebAPI; using LightlessSync.WebAPI;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using LightlessSync.API.Data.Extensions;
using System.Numerics; using System.Numerics;
namespace LightlessSync.UI; namespace LightlessSync.UI;
@@ -20,12 +20,13 @@ namespace LightlessSync.UI;
public class SyncshellFinderUI : WindowMediatorSubscriberBase public class SyncshellFinderUI : WindowMediatorSubscriberBase
{ {
private readonly ApiController _apiController; private readonly ApiController _apiController;
private readonly LightlessConfigService _configService;
private readonly BroadcastService _broadcastService; private readonly BroadcastService _broadcastService;
private readonly UiSharedService _uiSharedService; private readonly UiSharedService _uiSharedService;
private readonly BroadcastScannerService _broadcastScannerService; private readonly BroadcastScannerService _broadcastScannerService;
private readonly PairManager _pairManager;
private readonly List<GroupJoinDto> _nearbySyncshells = new(); private readonly List<GroupJoinDto> _nearbySyncshells = [];
private List<GroupFullInfoDto> _currentSyncshells = [];
private int _selectedNearbyIndex = -1; private int _selectedNearbyIndex = -1;
private GroupJoinDto? _joinDto; private GroupJoinDto? _joinDto;
@@ -37,17 +38,16 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
LightlessMediator mediator, LightlessMediator mediator,
PerformanceCollectorService performanceCollectorService, PerformanceCollectorService performanceCollectorService,
BroadcastService broadcastService, BroadcastService broadcastService,
LightlessConfigService configService,
UiSharedService uiShared, UiSharedService uiShared,
ApiController apiController, ApiController apiController,
BroadcastScannerService broadcastScannerService BroadcastScannerService broadcastScannerService,
) : base(logger, mediator, "Shellfinder###LightlessSyncshellFinderUI", performanceCollectorService) PairManager pairManager) : base(logger, mediator, "Shellfinder###LightlessSyncshellFinderUI", performanceCollectorService)
{ {
_broadcastService = broadcastService; _broadcastService = broadcastService;
_uiSharedService = uiShared; _uiSharedService = uiShared;
_configService = configService;
_apiController = apiController; _apiController = apiController;
_broadcastScannerService = broadcastScannerService; _broadcastScannerService = broadcastScannerService;
_pairManager = pairManager;
IsOpen = false; IsOpen = false;
SizeConstraints = new() SizeConstraints = new()
@@ -56,14 +56,14 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
MaximumSize = new(600, 550) MaximumSize = new(600, 550)
}; };
Mediator.Subscribe<SyncshellBroadcastsUpdatedMessage>(this, async _ => await RefreshSyncshellsAsync()); Mediator.Subscribe<SyncshellBroadcastsUpdatedMessage>(this, async _ => await RefreshSyncshellsAsync().ConfigureAwait(false));
Mediator.Subscribe<BroadcastStatusChangedMessage>(this, async _ => await RefreshSyncshellsAsync()); Mediator.Subscribe<BroadcastStatusChangedMessage>(this, async _ => await RefreshSyncshellsAsync().ConfigureAwait(false));
} }
public override async void OnOpen() public override async void OnOpen()
{ {
_ownPermissions = _apiController.DefaultPermissions.DeepClone()!; _ownPermissions = _apiController.DefaultPermissions.DeepClone()!;
await RefreshSyncshellsAsync(); await RefreshSyncshellsAsync().ConfigureAwait(false);
} }
protected override void DrawInternal() protected override void DrawInternal()
@@ -107,10 +107,8 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
ImGui.TableSetupColumn("Join", ImGuiTableColumnFlags.WidthFixed, 80f * ImGuiHelpers.GlobalScale); ImGui.TableSetupColumn("Join", ImGuiTableColumnFlags.WidthFixed, 80f * ImGuiHelpers.GlobalScale);
ImGui.TableHeadersRow(); ImGui.TableHeadersRow();
for (int i = 0; i < _nearbySyncshells.Count; i++) foreach (var shell in _nearbySyncshells)
{ {
var shell = _nearbySyncshells[i];
ImGui.TableNextRow(); ImGui.TableNextRow();
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.TextUnformatted(shell.Group.Alias ?? "(No Alias)"); ImGui.TextUnformatted(shell.Group.Alias ?? "(No Alias)");
@@ -123,6 +121,10 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, UIColors.Get("LightlessGreen").WithAlpha(0.85f)); ImGui.PushStyleColor(ImGuiCol.ButtonHovered, UIColors.Get("LightlessGreen").WithAlpha(0.85f));
ImGui.PushStyleColor(ImGuiCol.ButtonActive, UIColors.Get("LightlessGreen").WithAlpha(0.75f)); ImGui.PushStyleColor(ImGuiCol.ButtonActive, UIColors.Get("LightlessGreen").WithAlpha(0.75f));
if (!_currentSyncshells.Exists(g => string.Equals(g.GID, shell.GID, StringComparison.Ordinal)))
{
if (ImGui.Button(label)) if (ImGui.Button(label))
{ {
_logger.LogInformation($"Join requested for Syncshell {shell.Group.GID} ({shell.Group.Alias})"); _logger.LogInformation($"Join requested for Syncshell {shell.Group.GID} ({shell.Group.Alias})");
@@ -156,8 +158,16 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
} }
}); });
} }
}
else
{
using (ImRaii.Disabled())
{
ImGui.Button(label);
}
UiSharedService.AttachToolTip("Already a member or owner of this Syncshell.");
}
ImGui.PopStyleColor(3); ImGui.PopStyleColor(3);
} }
@@ -169,6 +179,8 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
} }
private void DrawConfirmation() private void DrawConfirmation()
{
if (_joinDto != null && _joinInfo != null)
{ {
ImGui.Separator(); ImGui.Separator();
ImGui.TextUnformatted($"Join Syncshell: {_joinDto.Group.AliasOrGID} by {_joinInfo.OwnerAliasOrUID}"); ImGui.TextUnformatted($"Join Syncshell: {_joinDto.Group.AliasOrGID} by {_joinInfo.OwnerAliasOrUID}");
@@ -194,6 +206,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
_joinInfo = null; _joinInfo = null;
} }
} }
}
private void DrawPermissionRow(string label, bool suggested, bool current, Action<bool> apply) private void DrawPermissionRow(string label, bool suggested, bool current, Action<bool> apply)
{ {
@@ -224,6 +237,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
private async Task RefreshSyncshellsAsync() private async Task RefreshSyncshellsAsync()
{ {
var syncshellBroadcasts = _broadcastScannerService.GetActiveSyncshellBroadcasts(); var syncshellBroadcasts = _broadcastScannerService.GetActiveSyncshellBroadcasts();
_currentSyncshells = _pairManager.GroupPairs.Select(g => g.Key).ToList();
if (syncshellBroadcasts.Count == 0) if (syncshellBroadcasts.Count == 0)
{ {
@@ -231,11 +245,11 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
return; return;
} }
List<GroupJoinDto> updatedList; List<GroupJoinDto> updatedList = [];
try try
{ {
var groups = await _apiController.GetBroadcastedGroups(syncshellBroadcasts); var groups = await _apiController.GetBroadcastedGroups(syncshellBroadcasts).ConfigureAwait(false);
updatedList = groups?.ToList() ?? new(); updatedList = groups?.ToList();
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -243,8 +257,8 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
return; return;
} }
var currentGids = _nearbySyncshells.Select(s => s.Group.GID).ToHashSet(); var currentGids = _nearbySyncshells.Select(s => s.Group.GID).ToHashSet(StringComparer.Ordinal);
var newGids = updatedList.Select(s => s.Group.GID).ToHashSet(); var newGids = updatedList.Select(s => s.Group.GID).ToHashSet(StringComparer.Ordinal);
if (currentGids.SetEquals(newGids)) if (currentGids.SetEquals(newGids))
return; return;
@@ -256,7 +270,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
if (previousGid != null) if (previousGid != null)
{ {
var newIndex = _nearbySyncshells.FindIndex(s => s.Group.GID == previousGid); var newIndex = _nearbySyncshells.FindIndex(s => string.Equals(s.Group.GID, previousGid, StringComparison.Ordinal));
if (newIndex >= 0) if (newIndex >= 0)
{ {
_selectedNearbyIndex = newIndex; _selectedNearbyIndex = newIndex;
@@ -290,9 +304,4 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
return _nearbySyncshells[_selectedNearbyIndex].Group.GID; return _nearbySyncshells[_selectedNearbyIndex].Group.GID;
} }
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
}
} }

View File

@@ -134,6 +134,12 @@ public partial class ApiController
await _lightlessHub!.InvokeAsync(nameof(UserSetProfile), userDescription).ConfigureAwait(false); await _lightlessHub!.InvokeAsync(nameof(UserSetProfile), userDescription).ConfigureAwait(false);
} }
public async Task UserUpdateVanityColors(UserVanityColorsDto dto)
{
if (!IsConnected) return;
await _lightlessHub!.InvokeAsync(nameof(UserUpdateVanityColors), dto).ConfigureAwait(false);
}
public async Task UserUpdateDefaultPermissions(DefaultPermissionsDto defaultPermissionsDto) public async Task UserUpdateDefaultPermissions(DefaultPermissionsDto defaultPermissionsDto)
{ {
CheckConnection(); CheckConnection();

View File

@@ -277,6 +277,12 @@ public partial class ApiController
_lightlessHub!.On(nameof(Client_GroupSendInfo), act); _lightlessHub!.On(nameof(Client_GroupSendInfo), act);
} }
public void OnGroupUpdateProfile(Action<GroupProfileDto> act)
{
if (_initialized) return;
_lightlessHub!.On(nameof(Client_GroupSendProfile), act);
}
public void OnReceiveServerMessage(Action<MessageSeverity, string> act) public void OnReceiveServerMessage(Action<MessageSeverity, string> act)
{ {
if (_initialized) return; if (_initialized) return;

View File

@@ -45,6 +45,11 @@ public partial class ApiController
CheckConnection(); CheckConnection();
await _lightlessHub!.SendAsync(nameof(GroupClear), group).ConfigureAwait(false); await _lightlessHub!.SendAsync(nameof(GroupClear), group).ConfigureAwait(false);
} }
public async Task GroupClearFinder(GroupDto group)
{
CheckConnection();
await _lightlessHub!.SendAsync(nameof(GroupClearFinder), group).ConfigureAwait(false);
}
public async Task<GroupJoinDto> GroupCreate() public async Task<GroupJoinDto> GroupCreate()
{ {

View File

@@ -2,6 +2,7 @@
using LightlessSync.API.Data; using LightlessSync.API.Data;
using LightlessSync.API.Data.Extensions; using LightlessSync.API.Data.Extensions;
using LightlessSync.API.Dto; using LightlessSync.API.Dto;
using LightlessSync.API.Dto.Group;
using LightlessSync.API.Dto.User; using LightlessSync.API.Dto.User;
using LightlessSync.API.SignalR; using LightlessSync.API.SignalR;
using LightlessSync.LightlessConfiguration; using LightlessSync.LightlessConfiguration;
@@ -76,6 +77,10 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
public DefaultPermissionsDto? DefaultPermissions => _connectionDto?.DefaultPreferredPermissions ?? null; public DefaultPermissionsDto? DefaultPermissions => _connectionDto?.DefaultPreferredPermissions ?? null;
public string DisplayName => _connectionDto?.User.AliasOrUID ?? string.Empty; public string DisplayName => _connectionDto?.User.AliasOrUID ?? string.Empty;
public bool HasVanity => _connectionDto?.HasVanity ?? false;
public string TextColorHex => _connectionDto?.TextColorHex ?? string.Empty;
public string TextGlowColorHex => _connectionDto?.TextGlowColorHex ?? string.Empty;
public bool IsConnected => ServerState == ServerState.Connected; public bool IsConnected => ServerState == ServerState.Connected;
public bool IsCurrentVersion => (Assembly.GetExecutingAssembly().GetName().Version ?? new Version(0, 0, 0, 0)) >= (_connectionDto?.CurrentClientVersion ?? new Version(0, 0, 0, 0)); public bool IsCurrentVersion => (Assembly.GetExecutingAssembly().GetName().Version ?? new Version(0, 0, 0, 0)) >= (_connectionDto?.CurrentClientVersion ?? new Version(0, 0, 0, 0));
@@ -444,6 +449,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
OnGroupPairLeft((dto) => _ = Client_GroupPairLeft(dto)); OnGroupPairLeft((dto) => _ = Client_GroupPairLeft(dto));
OnGroupSendFullInfo((dto) => _ = Client_GroupSendFullInfo(dto)); OnGroupSendFullInfo((dto) => _ = Client_GroupSendFullInfo(dto));
OnGroupSendInfo((dto) => _ = Client_GroupSendInfo(dto)); OnGroupSendInfo((dto) => _ = Client_GroupSendInfo(dto));
OnGroupUpdateProfile((dto) => _ = Client_GroupSendProfile(dto));
OnGroupChangeUserPairPermissions((dto) => _ = Client_GroupChangeUserPairPermissions(dto)); OnGroupChangeUserPairPermissions((dto) => _ = Client_GroupChangeUserPairPermissions(dto));
OnGposeLobbyJoin((dto) => _ = Client_GposeLobbyJoin(dto)); OnGposeLobbyJoin((dto) => _ = Client_GposeLobbyJoin(dto));
@@ -596,5 +602,20 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
ServerState = state; ServerState = state;
} }
public Task Client_GroupSendProfile(GroupProfileDto groupInfo)
{
throw new NotImplementedException();
}
public Task<GroupProfileDto> GroupGetProfile(GroupDto dto)
{
throw new NotImplementedException();
}
public Task GroupSetProfile(GroupProfileDto dto)
{
throw new NotImplementedException();
}
} }
#pragma warning restore MA0040 #pragma warning restore MA0040