2.0.0 #92
Submodule LightlessAPI updated: 0170ac377d...dfb0594a5b
@@ -379,7 +379,8 @@ public sealed class PairManager
|
||||
dto.GroupPermissions,
|
||||
shell.GroupFullInfo.GroupUserPermissions,
|
||||
shell.GroupFullInfo.GroupUserInfo,
|
||||
new Dictionary<string, GroupPairUserInfo>(shell.GroupFullInfo.GroupPairUserInfos, StringComparer.Ordinal));
|
||||
new Dictionary<string, GroupPairUserInfo>(shell.GroupFullInfo.GroupPairUserInfos, StringComparer.Ordinal),
|
||||
0);
|
||||
|
||||
shell.Update(updated);
|
||||
return PairOperationResult.Ok();
|
||||
@@ -514,7 +515,8 @@ public sealed class PairManager
|
||||
GroupPermissions.NoneSet,
|
||||
GroupUserPreferredPermissions.NoneSet,
|
||||
GroupPairUserInfo.None,
|
||||
new Dictionary<string, GroupPairUserInfo>(StringComparer.Ordinal));
|
||||
new Dictionary<string, GroupPairUserInfo>(StringComparer.Ordinal),
|
||||
0);
|
||||
|
||||
shell = new Syncshell(placeholder);
|
||||
_groups[group.GID] = shell;
|
||||
|
||||
@@ -23,7 +23,7 @@ public class VisibleUserDataDistributor : DisposableMediatorSubscriberBase
|
||||
private readonly List<UserData> _previouslyVisiblePlayers = [];
|
||||
private Task<CharacterData>? _fileUploadTask = null;
|
||||
private readonly HashSet<UserData> _usersToPushDataTo = new(UserDataComparer.Instance);
|
||||
private readonly SemaphoreSlim _pushDataSemaphore = new(1, 1);
|
||||
private readonly SemaphoreSlim _pushLock = new(1, 1);
|
||||
private readonly CancellationTokenSource _runtimeCts = new();
|
||||
|
||||
public VisibleUserDataDistributor(ILogger<VisibleUserDataDistributor> logger, ApiController apiController, DalamudUtilService dalamudUtil,
|
||||
@@ -108,53 +108,49 @@ public class VisibleUserDataDistributor : DisposableMediatorSubscriberBase
|
||||
private void PushCharacterData(bool forced = false)
|
||||
{
|
||||
if (_lastCreatedData == null || _usersToPushDataTo.Count == 0) return;
|
||||
_ = PushCharacterDataAsync(forced);
|
||||
}
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
private async Task PushCharacterDataAsync(bool forced = false)
|
||||
{
|
||||
await _pushLock.WaitAsync(_runtimeCts.Token).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
forced |= _uploadingCharacterData?.DataHash != _lastCreatedData.DataHash;
|
||||
if (_lastCreatedData == null || _usersToPushDataTo.Count == 0)
|
||||
return;
|
||||
|
||||
if (_fileUploadTask == null || (_fileUploadTask?.IsCompleted ?? false) || forced)
|
||||
var hashChanged = _uploadingCharacterData?.DataHash != _lastCreatedData.DataHash;
|
||||
forced |= hashChanged;
|
||||
|
||||
if (_fileUploadTask == null || _fileUploadTask.IsCompleted || forced)
|
||||
{
|
||||
_uploadingCharacterData = _lastCreatedData.DeepClone();
|
||||
var uploadTargets = _usersToPushDataTo.ToList();
|
||||
Logger.LogDebug("Starting UploadTask for {hash}, Reason: TaskIsNull: {task}, TaskIsCompleted: {taskCpl}, Forced: {frc}",
|
||||
_lastCreatedData.DataHash, _fileUploadTask == null, _fileUploadTask?.IsCompleted ?? false, forced);
|
||||
_fileUploadTask = _fileTransferManager.UploadFiles(_uploadingCharacterData, [.. _usersToPushDataTo]);
|
||||
_lastCreatedData.DataHash,
|
||||
_fileUploadTask == null,
|
||||
_fileUploadTask?.IsCompleted ?? false,
|
||||
forced);
|
||||
|
||||
_fileUploadTask = _fileTransferManager.UploadFiles(_uploadingCharacterData, uploadTargets);
|
||||
}
|
||||
|
||||
if (_fileUploadTask != null)
|
||||
{
|
||||
var dataToSend = await _fileUploadTask.ConfigureAwait(false);
|
||||
await _pushDataSemaphore.WaitAsync(_runtimeCts.Token).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (_usersToPushDataTo.Count == 0) return;
|
||||
Logger.LogDebug("Pushing {data} to {users}", dataToSend.DataHash, string.Join(", ", _usersToPushDataTo.Select(k => k.AliasOrUID)));
|
||||
await _apiController.PushCharacterData(dataToSend, [.. _usersToPushDataTo]).ConfigureAwait(false);
|
||||
_usersToPushDataTo.Clear();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_pushDataSemaphore.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) when (_runtimeCts.IsCancellationRequested)
|
||||
{
|
||||
Logger.LogDebug("PushCharacterData cancelled");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Failed to push character data");
|
||||
}
|
||||
});
|
||||
var dataToSend = await _fileUploadTask.ConfigureAwait(false);
|
||||
|
||||
var users = _usersToPushDataTo.ToList();
|
||||
if (users.Count == 0)
|
||||
return;
|
||||
|
||||
Logger.LogDebug("Pushing {data} to {users}", dataToSend.DataHash, string.Join(", ", users.Select(k => k.AliasOrUID)));
|
||||
|
||||
await _apiController.PushCharacterData(dataToSend, users).ConfigureAwait(false);
|
||||
_usersToPushDataTo.Clear();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_pushLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private List<UserData> GetVisibleUsers()
|
||||
{
|
||||
return _pairLedger.GetVisiblePairs()
|
||||
.Select(connection => connection.User)
|
||||
.ToList();
|
||||
}
|
||||
private List<UserData> GetVisibleUsers() => [.. _pairLedger.GetVisiblePairs().Select(connection => connection.User)];
|
||||
}
|
||||
|
||||
@@ -957,11 +957,10 @@ public class CompactUi : WindowMediatorSubscriberBase
|
||||
|
||||
private ImmutableList<PairUiEntry> SortEntries(IEnumerable<PairUiEntry> entries)
|
||||
{
|
||||
return entries
|
||||
return [.. entries
|
||||
.OrderByDescending(e => e.IsVisible)
|
||||
.ThenByDescending(e => e.IsOnline)
|
||||
.ThenBy(e => AlphabeticalSortKey(e), StringComparer.OrdinalIgnoreCase)
|
||||
.ToImmutableList();
|
||||
.ThenBy(e => AlphabeticalSortKey(e), StringComparer.OrdinalIgnoreCase)];
|
||||
}
|
||||
|
||||
private ImmutableList<PairUiEntry> SortVisibleEntries(IEnumerable<PairUiEntry> entries)
|
||||
@@ -972,9 +971,7 @@ public class CompactUi : WindowMediatorSubscriberBase
|
||||
VisiblePairSortMode.VramUsage => SortVisibleByMetric(entryList, e => e.LastAppliedApproximateVramBytes),
|
||||
VisiblePairSortMode.EffectiveVramUsage => SortVisibleByMetric(entryList, e => e.LastAppliedApproximateEffectiveVramBytes),
|
||||
VisiblePairSortMode.TriangleCount => SortVisibleByMetric(entryList, e => e.LastAppliedDataTris),
|
||||
VisiblePairSortMode.Alphabetical => entryList
|
||||
.OrderBy(e => AlphabeticalSortKey(e), StringComparer.OrdinalIgnoreCase)
|
||||
.ToImmutableList(),
|
||||
VisiblePairSortMode.Alphabetical => [.. entryList.OrderBy(e => AlphabeticalSortKey(e), StringComparer.OrdinalIgnoreCase)],
|
||||
VisiblePairSortMode.PreferredDirectPairs => SortVisibleByPreferred(entryList),
|
||||
_ => SortEntries(entryList),
|
||||
};
|
||||
@@ -982,31 +979,28 @@ public class CompactUi : WindowMediatorSubscriberBase
|
||||
|
||||
private ImmutableList<PairUiEntry> SortVisibleByMetric(IEnumerable<PairUiEntry> entries, Func<PairUiEntry, long> selector)
|
||||
{
|
||||
return entries
|
||||
return [.. entries
|
||||
.OrderByDescending(entry => selector(entry) >= 0)
|
||||
.ThenByDescending(selector)
|
||||
.ThenByDescending(entry => entry.IsOnline)
|
||||
.ThenBy(entry => AlphabeticalSortKey(entry), StringComparer.OrdinalIgnoreCase)
|
||||
.ToImmutableList();
|
||||
.ThenBy(entry => AlphabeticalSortKey(entry), StringComparer.OrdinalIgnoreCase)];
|
||||
}
|
||||
|
||||
private ImmutableList<PairUiEntry> SortVisibleByPreferred(IEnumerable<PairUiEntry> entries)
|
||||
{
|
||||
return entries
|
||||
return [.. entries
|
||||
.OrderByDescending(entry => entry.IsDirectlyPaired && entry.SelfPermissions.IsSticky())
|
||||
.ThenByDescending(entry => entry.IsDirectlyPaired)
|
||||
.ThenByDescending(entry => entry.IsOnline)
|
||||
.ThenBy(entry => AlphabeticalSortKey(entry), StringComparer.OrdinalIgnoreCase)
|
||||
.ToImmutableList();
|
||||
.ThenBy(entry => AlphabeticalSortKey(entry), StringComparer.OrdinalIgnoreCase)];
|
||||
}
|
||||
|
||||
private ImmutableList<PairUiEntry> SortGroupEntries(IEnumerable<PairUiEntry> entries, GroupFullInfoDto group)
|
||||
{
|
||||
return entries
|
||||
return [.. entries
|
||||
.OrderByDescending(e => e.IsOnline)
|
||||
.ThenBy(e => GroupSortWeight(e, group))
|
||||
.ThenBy(e => AlphabeticalSortKey(e), StringComparer.OrdinalIgnoreCase)
|
||||
.ToImmutableList();
|
||||
.ThenBy(e => AlphabeticalSortKey(e), StringComparer.OrdinalIgnoreCase)];
|
||||
}
|
||||
|
||||
private int GroupSortWeight(PairUiEntry entry, GroupFullInfoDto group)
|
||||
|
||||
@@ -103,7 +103,7 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
||||
|
||||
// Check if download notifications are enabled (not set to TextOverlay)
|
||||
var useNotifications = _configService.Current.UseLightlessNotifications
|
||||
? _configService.Current.LightlessDownloadNotification != NotificationLocation.TextOverlay
|
||||
? _configService.Current.LightlessDownloadNotification != NotificationLocation.LightlessUi
|
||||
: _configService.Current.UseNotificationsForDownloads;
|
||||
|
||||
if (useNotifications)
|
||||
@@ -534,6 +534,7 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
||||
if (lineSize.X > contentWidth)
|
||||
contentWidth = lineSize.X;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var lineHeight = ImGui.GetTextLineHeight();
|
||||
@@ -635,32 +636,40 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
||||
|
||||
foreach (var p in orderedPlayers)
|
||||
{
|
||||
var playerSpeedText = p.SpeedBytesPerSecond > 0
|
||||
var hasSpeed = p.SpeedBytesPerSecond > 0;
|
||||
var playerSpeedText = hasSpeed
|
||||
? $"{UiSharedService.ByteToString((long)p.SpeedBytesPerSecond)}/s"
|
||||
: "-";
|
||||
|
||||
// Label line for the player
|
||||
var labelLine =
|
||||
$"{p.Name} [W:{p.DlSlot}/Q:{p.DlQueue}/P:{p.DlProg}/D:{p.DlDecomp}] {p.TransferredFiles}/{p.TotalFiles}";
|
||||
|
||||
if (!_configService.Current.ShowPlayerSpeedBarsTransferWindow || p.DlProg <= 0)
|
||||
{
|
||||
var fullLine =
|
||||
$"{labelLine} " +
|
||||
$"({UiSharedService.ByteToString(p.TransferredBytes, addSuffix: false)}/{UiSharedService.ByteToString(p.TotalBytes)}) " +
|
||||
$"@ {playerSpeedText}";
|
||||
// State flags
|
||||
var isDownloading = p.DlProg > 0;
|
||||
var isDecompressing = p.DlDecomp > 0
|
||||
|| (!isDownloading && p.TotalBytes > 0 && p.TransferredBytes >= p.TotalBytes);
|
||||
|
||||
|
||||
var showBar = _configService.Current.ShowPlayerSpeedBarsTransferWindow
|
||||
&& (isDownloading || isDecompressing);
|
||||
|
||||
if (!showBar)
|
||||
{
|
||||
UiSharedService.DrawOutlinedFont(
|
||||
drawList,
|
||||
fullLine,
|
||||
labelLine,
|
||||
cursor,
|
||||
UiSharedService.Color(255, 255, 255, _transferBoxTransparency),
|
||||
UiSharedService.Color(0, 0, 0, _transferBoxTransparency),
|
||||
1
|
||||
);
|
||||
|
||||
cursor.Y += lineHeight + spacingY;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Top label line (only name + W/Q/P/D + files)
|
||||
UiSharedService.DrawOutlinedFont(
|
||||
drawList,
|
||||
labelLine,
|
||||
@@ -671,6 +680,7 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
||||
);
|
||||
cursor.Y += lineHeight + spacingY;
|
||||
|
||||
// Bar background
|
||||
var barBgMin = new Vector2(boxMin.X + padding, cursor.Y);
|
||||
var barBgMax = new Vector2(boxMax.X - padding, cursor.Y + perPlayerBarHeight);
|
||||
|
||||
@@ -682,8 +692,14 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
||||
);
|
||||
|
||||
float ratio = 0f;
|
||||
if (maxSpeed > 0)
|
||||
ratio = (float)(p.SpeedBytesPerSecond / maxSpeed);
|
||||
if (isDownloading && p.TotalBytes > 0)
|
||||
{
|
||||
ratio = (float)p.TransferredBytes / p.TotalBytes;
|
||||
}
|
||||
else if (isDecompressing)
|
||||
{
|
||||
ratio = 1f;
|
||||
}
|
||||
|
||||
if (ratio < 0f) ratio = 0f;
|
||||
if (ratio > 1f) ratio = 1f;
|
||||
@@ -698,24 +714,43 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
||||
3f
|
||||
);
|
||||
|
||||
var barText =
|
||||
$"{UiSharedService.ByteToString(p.TransferredBytes, addSuffix: false)}/{UiSharedService.ByteToString(p.TotalBytes)} @ {playerSpeedText}";
|
||||
string barText;
|
||||
|
||||
var barTextSize = ImGui.CalcTextSize(barText);
|
||||
if (isDownloading)
|
||||
{
|
||||
var bytesInside =
|
||||
$"{UiSharedService.ByteToString(p.TransferredBytes, addSuffix: false)}/{UiSharedService.ByteToString(p.TotalBytes)}";
|
||||
|
||||
var barTextPos = new Vector2(
|
||||
barBgMin.X + ((barBgMax.X - barBgMin.X) - barTextSize.X) / 2f - 1,
|
||||
barBgMin.Y + ((perPlayerBarHeight - barTextSize.Y) / 2f) - 1
|
||||
);
|
||||
barText = hasSpeed
|
||||
? $"{bytesInside} @ {playerSpeedText}"
|
||||
: bytesInside;
|
||||
}
|
||||
else if (isDecompressing)
|
||||
{
|
||||
barText = "Decompressing...";
|
||||
}
|
||||
else
|
||||
{
|
||||
barText = string.Empty;
|
||||
}
|
||||
|
||||
UiSharedService.DrawOutlinedFont(
|
||||
drawList,
|
||||
barText,
|
||||
barTextPos,
|
||||
UiSharedService.Color(255, 255, 255, _transferBoxTransparency),
|
||||
UiSharedService.Color(0, 0, 0, _transferBoxTransparency),
|
||||
1
|
||||
);
|
||||
if (!string.IsNullOrEmpty(barText))
|
||||
{
|
||||
var barTextSize = ImGui.CalcTextSize(barText);
|
||||
var barTextPos = new Vector2(
|
||||
barBgMin.X + ((barBgMax.X - barBgMin.X) - barTextSize.X) / 2f - 1,
|
||||
barBgMin.Y + ((perPlayerBarHeight - barTextSize.Y) / 2f) - 1
|
||||
);
|
||||
|
||||
UiSharedService.DrawOutlinedFont(
|
||||
drawList,
|
||||
barText,
|
||||
barTextPos,
|
||||
UiSharedService.Color(255, 255, 255, _transferBoxTransparency),
|
||||
UiSharedService.Color(0, 0, 0, _transferBoxTransparency),
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
cursor.Y += perPlayerBarHeight + spacingY;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ using Dalamud.Game.Gui.Dtr;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility;
|
||||
using LightlessSync.LightlessConfiguration;
|
||||
using LightlessSync.LightlessConfiguration.Configurations;
|
||||
using LightlessSync.Services;
|
||||
@@ -13,11 +12,9 @@ using LightlessSync.WebAPI;
|
||||
using LightlessSync.WebAPI.SignalR.Utils;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using LightlessSync.UI.Services;
|
||||
using LightlessSync.PlayerData.Pairs;
|
||||
using static LightlessSync.Services.PairRequestService;
|
||||
using LightlessSync.Services.LightFinder;
|
||||
|
||||
|
||||
@@ -2,26 +2,17 @@ using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Components;
|
||||
using Dalamud.Interface.ImGuiFileDialog;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using LightlessSync.API.Data;
|
||||
using LightlessSync.API.Dto.Group;
|
||||
using LightlessSync.Services;
|
||||
using LightlessSync.Services.Mediator;
|
||||
using LightlessSync.Services.Profiles;
|
||||
using LightlessSync.UI.Tags;
|
||||
using LightlessSync.Utils;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Formats;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LightlessSync.UI;
|
||||
|
||||
@@ -68,6 +59,7 @@ public partial class EditProfileUi
|
||||
_bannerTextureWrap = null;
|
||||
_showProfileImageError = false;
|
||||
_showBannerImageError = false;
|
||||
_groupVisibilityInitialized = false;
|
||||
}
|
||||
|
||||
private void DrawGroupEditor(float scale)
|
||||
@@ -376,6 +368,8 @@ public partial class EditProfileUi
|
||||
|
||||
private void DrawGroupProfileVisibilityControls()
|
||||
{
|
||||
EnsureGroupVisibilityStateInitialised();
|
||||
|
||||
bool changedNsfw = DrawCheckboxRow("Profile is NSFW", _groupIsNsfw, out var newNsfw, "Flag this profile as not safe for work.");
|
||||
if (changedNsfw)
|
||||
_groupIsNsfw = newNsfw;
|
||||
@@ -504,33 +498,36 @@ public partial class EditProfileUi
|
||||
try
|
||||
{
|
||||
var fileContent = await File.ReadAllBytesAsync(filePath).ConfigureAwait(false);
|
||||
await using var stream = new MemoryStream(fileContent);
|
||||
var format = await Image.DetectFormatAsync(stream).ConfigureAwait(false);
|
||||
if (!IsSupportedImageFormat(format))
|
||||
var stream = new MemoryStream(fileContent);
|
||||
await using (stream.ConfigureAwait(false))
|
||||
{
|
||||
_showBannerImageError = true;
|
||||
return;
|
||||
var format = await Image.DetectFormatAsync(stream).ConfigureAwait(false);
|
||||
if (!IsSupportedImageFormat(format))
|
||||
{
|
||||
_showBannerImageError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
using var image = Image.Load<Rgba32>(fileContent);
|
||||
if (image.Width > 840 || image.Height > 260 || fileContent.Length > 2000 * 1024)
|
||||
{
|
||||
_showBannerImageError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
await _apiController.GroupSetProfile(new GroupProfileDto(
|
||||
_groupInfo.Group,
|
||||
Description: null,
|
||||
Tags: null,
|
||||
PictureBase64: null,
|
||||
BannerBase64: Convert.ToBase64String(fileContent),
|
||||
IsNsfw: null,
|
||||
IsDisabled: null)).ConfigureAwait(false);
|
||||
|
||||
_showBannerImageError = false;
|
||||
_queuedBannerImage = fileContent;
|
||||
Mediator.Publish(new ClearProfileGroupDataMessage(_groupInfo.Group));
|
||||
}
|
||||
|
||||
using var image = Image.Load<Rgba32>(fileContent);
|
||||
if (image.Width > 840 || image.Height > 260 || fileContent.Length > 2000 * 1024)
|
||||
{
|
||||
_showBannerImageError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
await _apiController.GroupSetProfile(new GroupProfileDto(
|
||||
_groupInfo.Group,
|
||||
Description: null,
|
||||
Tags: null,
|
||||
PictureBase64: null,
|
||||
BannerBase64: Convert.ToBase64String(fileContent),
|
||||
IsNsfw: null,
|
||||
IsDisabled: null)).ConfigureAwait(false);
|
||||
|
||||
_showBannerImageError = false;
|
||||
_queuedBannerImage = fileContent;
|
||||
Mediator.Publish(new ClearProfileGroupDataMessage(_groupInfo.Group));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -588,6 +585,16 @@ public partial class EditProfileUi
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureGroupVisibilityStateInitialised()
|
||||
{
|
||||
if (_groupInfo == null || _groupVisibilityInitialized)
|
||||
return;
|
||||
|
||||
_groupIsNsfw = _groupServerIsNsfw;
|
||||
_groupIsDisabled = _groupServerIsDisabled;
|
||||
_groupVisibilityInitialized = true;
|
||||
}
|
||||
|
||||
private async Task SubmitGroupTagChanges(int[] payload)
|
||||
{
|
||||
if (_groupInfo is null)
|
||||
@@ -695,11 +702,15 @@ public partial class EditProfileUi
|
||||
}
|
||||
}
|
||||
|
||||
_groupIsNsfw = profile.IsNsfw;
|
||||
_groupIsDisabled = profile.IsDisabled;
|
||||
_groupServerIsNsfw = profile.IsNsfw;
|
||||
_groupServerIsDisabled = profile.IsDisabled;
|
||||
}
|
||||
|
||||
if (!_groupVisibilityInitialized)
|
||||
{
|
||||
_groupIsNsfw = _groupServerIsNsfw;
|
||||
_groupIsDisabled = _groupServerIsDisabled;
|
||||
_groupVisibilityInitialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ public partial class EditProfileUi : WindowMediatorSubscriberBase
|
||||
"<bold(1|0)> <italic(1|0)> <shadow(1|0)> <edge(1|0)> - toggle style flags.\n" +
|
||||
"<link(0x0E,...)> - create clickable links.";
|
||||
|
||||
private static readonly HashSet<string> SupportedImageExtensions = new(StringComparer.OrdinalIgnoreCase)
|
||||
private static readonly HashSet<string> _supportedImageExtensions = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"png",
|
||||
"jpg",
|
||||
@@ -52,7 +52,7 @@ public partial class EditProfileUi : WindowMediatorSubscriberBase
|
||||
"bmp"
|
||||
};
|
||||
private const string _imageFileDialogFilter = "Images{.png,.jpg,.jpeg,.webp,.bmp}";
|
||||
private readonly List<int> _tagEditorSelection = new();
|
||||
private readonly List<int> _tagEditorSelection = [];
|
||||
private int[] _profileTagIds = [];
|
||||
private readonly List<SeStringUtils.SeStringSegment> _tagPreviewSegments = new();
|
||||
private enum ProfileEditorMode
|
||||
@@ -68,6 +68,7 @@ public partial class EditProfileUi : WindowMediatorSubscriberBase
|
||||
private bool _groupIsDisabled;
|
||||
private bool _groupServerIsNsfw;
|
||||
private bool _groupServerIsDisabled;
|
||||
private bool _groupVisibilityInitialized;
|
||||
private byte[]? _queuedProfileImage;
|
||||
private byte[]? _queuedBannerImage;
|
||||
private readonly Vector4 _tagBackgroundColor = new(0.18f, 0.18f, 0.18f, 0.95f);
|
||||
@@ -85,6 +86,9 @@ public partial class EditProfileUi : WindowMediatorSubscriberBase
|
||||
private bool _showProfileImageError = false;
|
||||
private bool _showBannerImageError = false;
|
||||
private bool _wasOpen;
|
||||
private bool _userServerIsNsfw;
|
||||
private bool _isNsfwInitialized;
|
||||
private bool _isNsfw;
|
||||
|
||||
private Vector4 _currentBg = new(0.15f, 0.15f, 0.15f, 1f);
|
||||
private bool _textEnabled;
|
||||
@@ -171,6 +175,8 @@ public partial class EditProfileUi : WindowMediatorSubscriberBase
|
||||
return;
|
||||
}
|
||||
|
||||
_isNsfwInitialized = false;
|
||||
|
||||
var user = await EnsureSelfProfileUserDataAsync().ConfigureAwait(false);
|
||||
if (user is not null)
|
||||
{
|
||||
@@ -339,13 +345,12 @@ public partial class EditProfileUi : WindowMediatorSubscriberBase
|
||||
}
|
||||
|
||||
SyncProfileState(profile);
|
||||
|
||||
DrawSection("Profile Preview", scale, () => DrawProfileSnapshot(profile, scale));
|
||||
DrawSection("Profile Image", scale, () => DrawProfileImageControls(profile, scale));
|
||||
DrawSection("Profile Banner", scale, () => DrawProfileBannerControls(profile, scale));
|
||||
DrawSection("Profile Description", scale, () => DrawProfileDescriptionEditor(profile, scale));
|
||||
DrawSection("Profile Tags", scale, () => DrawProfileTagsEditor(profile, scale));
|
||||
DrawSection("Visibility", scale, () => DrawProfileVisibilityControls(profile));
|
||||
DrawSection("Visibility", scale, () => DrawProfileVisibilityControls());
|
||||
}
|
||||
|
||||
private void DrawProfileSnapshot(LightlessUserProfileData profile, float scale)
|
||||
@@ -877,21 +882,46 @@ public partial class EditProfileUi : WindowMediatorSubscriberBase
|
||||
UiSharedService.AttachToolTip(saveTooltip);
|
||||
}
|
||||
|
||||
private void DrawProfileVisibilityControls(LightlessUserProfileData profile)
|
||||
private void DrawProfileVisibilityControls()
|
||||
{
|
||||
var isNsfw = profile.IsNSFW;
|
||||
if (DrawCheckboxRow("Mark profile as NSFW", isNsfw, out var newValue, "Enable when your profile could be considered NSFW."))
|
||||
if (!_isNsfwInitialized)
|
||||
ImGui.BeginDisabled();
|
||||
|
||||
bool changed = DrawCheckboxRow("Mark profile as NSFW", _isNsfw, out var newValue, "Enable when your profile could be considered NSFW.");
|
||||
|
||||
if (changed)
|
||||
_isNsfw = newValue;
|
||||
|
||||
bool visibilityChanged = _isNsfwInitialized && (_isNsfw != _userServerIsNsfw);
|
||||
|
||||
if (!_isNsfwInitialized)
|
||||
ImGui.EndDisabled();
|
||||
|
||||
if (!_isNsfwInitialized || !visibilityChanged)
|
||||
ImGui.BeginDisabled();
|
||||
|
||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Save, "Apply Visibility Changes"))
|
||||
{
|
||||
_userServerIsNsfw = _isNsfw;
|
||||
|
||||
_ = _apiController.UserSetProfile(new UserProfileDto(
|
||||
new UserData(_apiController.UID),
|
||||
Disabled: false,
|
||||
newValue,
|
||||
ProfilePictureBase64: GetCurrentProfilePictureBase64(profile),
|
||||
IsNSFW: _isNsfw,
|
||||
ProfilePictureBase64: null,
|
||||
BannerPictureBase64: null,
|
||||
Description: null,
|
||||
BannerPictureBase64: GetCurrentProfileBannerBase64(profile),
|
||||
Tags: GetServerTagPayload()));
|
||||
Tags: null));
|
||||
}
|
||||
|
||||
UiSharedService.AttachToolTip("Apply the visibility toggles above.");
|
||||
|
||||
if (!_isNsfwInitialized || !visibilityChanged)
|
||||
ImGui.EndDisabled();
|
||||
|
||||
ImGui.SameLine();
|
||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.SyncAlt, "Reset") && _isNsfwInitialized)
|
||||
_isNsfw = _userServerIsNsfw;
|
||||
}
|
||||
|
||||
private string? GetCurrentProfilePictureBase64(LightlessUserProfileData profile)
|
||||
@@ -932,7 +962,7 @@ public partial class EditProfileUi : WindowMediatorSubscriberBase
|
||||
|
||||
foreach (var ext in format.FileExtensions)
|
||||
{
|
||||
if (SupportedImageExtensions.Contains(ext))
|
||||
if (_supportedImageExtensions.Contains(ext))
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1183,7 +1213,7 @@ public partial class EditProfileUi : WindowMediatorSubscriberBase
|
||||
});
|
||||
}
|
||||
|
||||
private void DrawSection(string title, float scale, Action body)
|
||||
private static void DrawSection(string title, float scale, Action body)
|
||||
{
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, new Vector2(6f, 4f) * scale);
|
||||
|
||||
@@ -1199,9 +1229,8 @@ public partial class EditProfileUi : WindowMediatorSubscriberBase
|
||||
}
|
||||
}
|
||||
|
||||
private bool DrawCheckboxRow(string label, bool currentValue, out bool newValue, string? tooltip = null)
|
||||
private static bool DrawCheckboxRow(string label, bool currentValue, out bool newValue, string? tooltip = null)
|
||||
{
|
||||
|
||||
bool value = currentValue;
|
||||
bool changed = UiSharedService.CheckboxWithBorder(label, ref value, UIColors.Get("LightlessPurple"), 1.5f);
|
||||
if (!string.IsNullOrEmpty(tooltip))
|
||||
@@ -1214,7 +1243,17 @@ public partial class EditProfileUi : WindowMediatorSubscriberBase
|
||||
private void SyncProfileState(LightlessUserProfileData profile)
|
||||
{
|
||||
if (string.Equals(profile.Description, LoadingProfileDescription, StringComparison.Ordinal))
|
||||
{
|
||||
_isNsfwInitialized = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_isNsfwInitialized)
|
||||
{
|
||||
_userServerIsNsfw = profile.IsNSFW;
|
||||
_isNsfw = profile.IsNSFW;
|
||||
_isNsfwInitialized = true;
|
||||
}
|
||||
|
||||
var profileBytes = profile.ImageData.Value;
|
||||
if (_pfpTextureWrap == null || !_profileImage.SequenceEqual(profileBytes))
|
||||
@@ -1239,11 +1278,11 @@ public partial class EditProfileUi : WindowMediatorSubscriberBase
|
||||
_descriptionText = _profileDescription;
|
||||
}
|
||||
|
||||
var serverTags = profile.Tags ?? Array.Empty<int>();
|
||||
var serverTags = profile.Tags ?? [];
|
||||
if (!TagsEqual(serverTags, _profileTagIds))
|
||||
{
|
||||
var previous = _profileTagIds;
|
||||
_profileTagIds = serverTags.Count == 0 ? Array.Empty<int>() : serverTags.ToArray();
|
||||
_profileTagIds = serverTags.Count == 0 ? [] : [.. serverTags];
|
||||
|
||||
if (TagsEqual(_tagEditorSelection, previous))
|
||||
{
|
||||
|
||||
@@ -184,7 +184,7 @@ public class StandaloneProfileUi : WindowMediatorSubscriberBase
|
||||
var profile = _lightlessProfileManager.GetLightlessProfile(userData);
|
||||
IReadOnlyList<ProfileTagDefinition> profileTags = profile.Tags.Count > 0
|
||||
? ProfileTagService.ResolveTags(profile.Tags)
|
||||
: Array.Empty<ProfileTagDefinition>();
|
||||
: [];
|
||||
|
||||
if (_textureWrap == null || !profile.ImageData.Value.SequenceEqual(_lastProfilePicture))
|
||||
{
|
||||
@@ -225,7 +225,7 @@ public class StandaloneProfileUi : WindowMediatorSubscriberBase
|
||||
bool directPair = false;
|
||||
bool youPaused = false;
|
||||
bool theyPaused = false;
|
||||
List<string> syncshellLines = new();
|
||||
List<string> syncshellLines = [];
|
||||
|
||||
if (!_isLightfinderContext && Pair != null)
|
||||
{
|
||||
@@ -245,7 +245,7 @@ public class StandaloneProfileUi : WindowMediatorSubscriberBase
|
||||
theyPaused = pairInfo.OtherPermissions.IsPaused();
|
||||
}
|
||||
|
||||
if (pairInfo.Groups.Any())
|
||||
if (pairInfo.Groups.Count != 0)
|
||||
{
|
||||
foreach (var gid in pairInfo.Groups)
|
||||
{
|
||||
@@ -276,8 +276,11 @@ public class StandaloneProfileUi : WindowMediatorSubscriberBase
|
||||
presenceTokens.Add(new PresenceToken("They paused syncing", true));
|
||||
}
|
||||
|
||||
if (profile.IsNSFW)
|
||||
presenceTokens.Add(new PresenceToken("NSFW", Emphasis: true));
|
||||
|
||||
if (syncshellLines.Count > 0)
|
||||
presenceTokens.Add(new PresenceToken($"Sharing Syncshells ({syncshellLines.Count})", false, syncshellLines, "Shared Syncshells"));
|
||||
presenceTokens.Add(new PresenceToken($"Sharing Syncshells ({syncshellLines.Count})", Emphasis: false, syncshellLines, "Shared Syncshells"));
|
||||
|
||||
var drawList = ImGui.GetWindowDrawList();
|
||||
var style = ImGui.GetStyle();
|
||||
@@ -780,7 +783,7 @@ public class StandaloneProfileUi : WindowMediatorSubscriberBase
|
||||
};
|
||||
|
||||
if (profile.IsNsfw)
|
||||
presenceTokens.Add(new PresenceToken("NSFW", true));
|
||||
presenceTokens.Add(new PresenceToken("NSFW", Emphasis: true));
|
||||
|
||||
int memberCount = 0;
|
||||
List<Pair>? groupMembers = null;
|
||||
|
||||
@@ -12,7 +12,6 @@ using LightlessSync.Services;
|
||||
using LightlessSync.Services.Mediator;
|
||||
using LightlessSync.Services.Profiles;
|
||||
using LightlessSync.UI.Services;
|
||||
using LightlessSync.UI.Style;
|
||||
using LightlessSync.WebAPI;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SixLabors.ImageSharp;
|
||||
@@ -42,6 +41,11 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
|
||||
private Task<int>? _pruneTask;
|
||||
private int _pruneDays = 14;
|
||||
|
||||
private Task<GroupPruneSettingsDto>? _pruneSettingsTask;
|
||||
private bool _pruneSettingsLoaded;
|
||||
private bool _autoPruneEnabled;
|
||||
private int _autoPruneDays = 14;
|
||||
|
||||
public SyncshellAdminUI(ILogger<SyncshellAdminUI> logger, LightlessMediator mediator, ApiController apiController,
|
||||
UiSharedService uiSharedService, PairUiService pairUiService, GroupFullInfoDto groupFullInfo, PerformanceCollectorService performanceCollectorService, LightlessProfileManager lightlessProfileManager)
|
||||
: base(logger, mediator, "Syncshell Admin Panel (" + groupFullInfo.GroupAliasOrGID + ")", performanceCollectorService)
|
||||
@@ -89,36 +93,147 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
|
||||
_profileData = _lightlessProfileManager.GetLightlessGroupProfile(GroupFullInfo.Group);
|
||||
using var id = ImRaii.PushId("syncshell_admin_" + GroupFullInfo.GID);
|
||||
|
||||
using (_uiSharedService.UidFont.Push())
|
||||
{
|
||||
var headerText = $"{GroupFullInfo.GroupAliasOrGID} Administrative Panel";
|
||||
_uiSharedService.UnderlinedBigText(headerText, UIColors.Get("LightlessBlue"));
|
||||
}
|
||||
DrawAdminHeader();
|
||||
|
||||
ImGui.Separator();
|
||||
var perm = GroupFullInfo.GroupPermissions;
|
||||
|
||||
DrawAdminTopBar(perm);
|
||||
}
|
||||
|
||||
private void DrawAdminHeader()
|
||||
{
|
||||
float scale = ImGuiHelpers.GlobalScale;
|
||||
var style = ImGui.GetStyle();
|
||||
|
||||
var cursorLocal = ImGui.GetCursorPos();
|
||||
var pMin = ImGui.GetCursorScreenPos();
|
||||
float width = ImGui.GetContentRegionAvail().X;
|
||||
float height = 64f * scale;
|
||||
|
||||
var pMax = new Vector2(pMin.X + width, pMin.Y + height);
|
||||
var drawList = ImGui.GetWindowDrawList();
|
||||
|
||||
var purple = UIColors.Get("LightlessPurple");
|
||||
var gradLeft = purple.WithAlpha(0.0f);
|
||||
var gradRight = purple.WithAlpha(0.85f);
|
||||
|
||||
uint colTopLeft = ImGui.ColorConvertFloat4ToU32(gradLeft);
|
||||
uint colTopRight = ImGui.ColorConvertFloat4ToU32(gradRight);
|
||||
uint colBottomRight = ImGui.ColorConvertFloat4ToU32(gradRight);
|
||||
uint colBottomLeft = ImGui.ColorConvertFloat4ToU32(gradLeft);
|
||||
|
||||
drawList.AddRectFilledMultiColor(
|
||||
pMin,
|
||||
pMax,
|
||||
colTopLeft,
|
||||
colTopRight,
|
||||
colBottomRight,
|
||||
colBottomLeft);
|
||||
|
||||
float accentHeight = 3f * scale;
|
||||
var accentMin = new Vector2(pMin.X, pMax.Y - accentHeight);
|
||||
var accentMax = new Vector2(pMax.X, pMax.Y);
|
||||
var accentColor = UIColors.Get("LightlessBlue");
|
||||
uint accentU32 = ImGui.ColorConvertFloat4ToU32(accentColor);
|
||||
drawList.AddRectFilled(accentMin, accentMax, accentU32);
|
||||
|
||||
ImGui.InvisibleButton("##adminHeaderHitbox", new Vector2(width, height));
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.BeginTooltip();
|
||||
ImGui.Text($"{GroupFullInfo.GroupAliasOrGID} is created at:");
|
||||
ImGui.Separator();
|
||||
ImGui.Text(text: GroupFullInfo.Group.CreatedAt?.ToString("yyyy-MM-dd HH:mm:ss 'UTC'"));
|
||||
ImGui.Text(GroupFullInfo.Group.CreatedAt?.ToString("yyyy-MM-dd HH:mm:ss 'UTC'") ?? "Unknown");
|
||||
ImGui.EndTooltip();
|
||||
}
|
||||
|
||||
ImGui.Separator();
|
||||
var perm = GroupFullInfo.GroupPermissions;
|
||||
var titlePos = new Vector2(pMin.X + 12f * scale, pMin.Y + 8f * scale);
|
||||
ImGui.SetCursorScreenPos(titlePos);
|
||||
|
||||
using var tabbar = ImRaii.TabBar("syncshell_tab_" + GroupFullInfo.GID);
|
||||
|
||||
if (tabbar)
|
||||
float titleHeight;
|
||||
using (_uiSharedService.UidFont.Push())
|
||||
{
|
||||
DrawInvites(perm);
|
||||
|
||||
DrawManagement();
|
||||
|
||||
DrawPermission(perm);
|
||||
|
||||
DrawProfile();
|
||||
ImGui.TextColored(UIColors.Get("LightlessBlue"), GroupFullInfo.GroupAliasOrGID);
|
||||
titleHeight = ImGui.GetTextLineHeightWithSpacing();
|
||||
}
|
||||
|
||||
var subtitlePos = new Vector2(
|
||||
pMin.X + 12f * scale,
|
||||
titlePos.Y + titleHeight - 2f * scale);
|
||||
|
||||
ImGui.SetCursorScreenPos(subtitlePos);
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey);
|
||||
ImGui.TextUnformatted("Administrative Panel");
|
||||
ImGui.PopStyleColor();
|
||||
|
||||
string roleLabel = _isOwner ? "Owner" : (_isModerator ? "Moderator" : string.Empty);
|
||||
if (!string.IsNullOrEmpty(roleLabel))
|
||||
{
|
||||
float roleTextW = ImGui.CalcTextSize(roleLabel).X;
|
||||
float pillPaddingX = 8f * scale;
|
||||
float pillPaddingY = -1f * scale;
|
||||
|
||||
float pillWidth = roleTextW + pillPaddingX * 2f;
|
||||
float pillHeight = ImGui.GetTextLineHeight() + pillPaddingY * 2f;
|
||||
|
||||
var pillMin = new Vector2(
|
||||
pMax.X - pillWidth - style.WindowPadding.X,
|
||||
subtitlePos.Y - pillPaddingY);
|
||||
var pillMax = new Vector2(pillMin.X + pillWidth, pillMin.Y + pillHeight);
|
||||
|
||||
var pillBg = _isOwner ? UIColors.Get("LightlessYellow") : UIColors.Get("LightlessOrange");
|
||||
uint pillBgU = ImGui.ColorConvertFloat4ToU32(pillBg.WithAlpha(0.9f));
|
||||
|
||||
drawList.AddRectFilled(pillMin, pillMax, pillBgU, 8f * scale);
|
||||
|
||||
ImGui.SetCursorScreenPos(new Vector2(pillMin.X + pillPaddingX, pillMin.Y + pillPaddingY));
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, UIColors.Get("FullBlack"));
|
||||
ImGui.TextUnformatted(roleLabel);
|
||||
ImGui.PopStyleColor();
|
||||
}
|
||||
|
||||
ImGui.SetCursorPos(new Vector2(cursorLocal.X, cursorLocal.Y + height + 6f * scale));
|
||||
}
|
||||
|
||||
private void DrawAdminTopBar(GroupPermissions perm)
|
||||
{
|
||||
var style = ImGui.GetStyle();
|
||||
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, new Vector2(12f, 6f) * ImGuiHelpers.GlobalScale);
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(10f, style.ItemSpacing.Y));
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.TabRounding, 6f * ImGuiHelpers.GlobalScale);
|
||||
|
||||
var baseTab = UIColors.Get("FullBlack").WithAlpha(0.0f);
|
||||
var baseTabDim = UIColors.Get("FullBlack").WithAlpha(0.1f);
|
||||
var accent = UIColors.Get("LightlessPurple");
|
||||
var accentHover = accent.WithAlpha(0.90f);
|
||||
var accentActive = accent;
|
||||
|
||||
ImGui.PushStyleColor(ImGuiCol.Tab, baseTab);
|
||||
ImGui.PushStyleColor(ImGuiCol.TabHovered, accentHover);
|
||||
ImGui.PushStyleColor(ImGuiCol.TabActive, accentActive);
|
||||
ImGui.PushStyleColor(ImGuiCol.TabUnfocused, baseTabDim);
|
||||
ImGui.PushStyleColor(ImGuiCol.TabUnfocusedActive, accentActive.WithAlpha(0.80f));
|
||||
|
||||
using (var tabbar = ImRaii.TabBar("syncshell_tab_" + GroupFullInfo.GID))
|
||||
{
|
||||
if (tabbar)
|
||||
{
|
||||
DrawInvites(perm);
|
||||
DrawManagement();
|
||||
DrawPermission(perm);
|
||||
DrawProfile();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.PopStyleColor(5);
|
||||
ImGui.PopStyleVar(3);
|
||||
|
||||
ImGuiHelpers.ScaledDummy(2f);
|
||||
UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f);
|
||||
ImGuiHelpers.ScaledDummy(2f);
|
||||
}
|
||||
|
||||
private void DrawPermission(GroupPermissions perm)
|
||||
@@ -218,6 +333,70 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawAutoPruneSettings()
|
||||
{
|
||||
ImGuiHelpers.ScaledDummy(2f);
|
||||
UiSharedService.TextWrapped("Automatic prune (server-side scheduled cleanup of inactive users).");
|
||||
|
||||
_pruneSettingsTask ??= _apiController.GroupGetPruneSettings(new GroupDto(GroupFullInfo.Group));
|
||||
|
||||
if (!_pruneSettingsLoaded)
|
||||
{
|
||||
if (!_pruneSettingsTask!.IsCompleted)
|
||||
{
|
||||
UiSharedService.ColorTextWrapped("Loading prune settings from server...", ImGuiColors.DalamudGrey);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_pruneSettingsTask.IsFaulted || _pruneSettingsTask.IsCanceled)
|
||||
{
|
||||
UiSharedService.ColorTextWrapped("Failed to load auto-prune settings.", ImGuiColors.DalamudRed);
|
||||
_pruneSettingsTask = null;
|
||||
_pruneSettingsLoaded = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var dto = _pruneSettingsTask.GetAwaiter().GetResult();
|
||||
|
||||
_autoPruneEnabled = dto.AutoPruneEnabled && dto.AutoPruneDays > 0;
|
||||
_autoPruneDays = dto.AutoPruneDays > 0 ? dto.AutoPruneDays : 14;
|
||||
|
||||
_pruneSettingsLoaded = true;
|
||||
}
|
||||
|
||||
bool enabled = _autoPruneEnabled;
|
||||
if (ImGui.Checkbox("Enable automatic pruning", ref enabled))
|
||||
{
|
||||
_autoPruneEnabled = enabled;
|
||||
SavePruneSettings();
|
||||
}
|
||||
UiSharedService.AttachToolTip("When enabled, inactive non-pinned, non-moderator users will be pruned automatically on the server.");
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.SetNextItemWidth(150);
|
||||
|
||||
using (ImRaii.Disabled(!_autoPruneEnabled))
|
||||
{
|
||||
_uiSharedService.DrawCombo(
|
||||
"Day(s) of inactivity",
|
||||
[1, 3, 7, 14, 30, 90],
|
||||
days => $"{days} day(s)",
|
||||
selected =>
|
||||
{
|
||||
_autoPruneDays = selected;
|
||||
SavePruneSettings();
|
||||
},
|
||||
_autoPruneDays);
|
||||
}
|
||||
|
||||
if (!_autoPruneEnabled)
|
||||
{
|
||||
UiSharedService.ColorTextWrapped(
|
||||
"Automatic prune is currently disabled. Enable it and choose an inactivity threshold to let the server clean up inactive users automatically.",
|
||||
ImGuiColors.DalamudGrey);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawProfile()
|
||||
{
|
||||
var profileTab = ImRaii.TabItem("Profile");
|
||||
@@ -268,167 +447,230 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
|
||||
private void DrawManagement()
|
||||
{
|
||||
var mgmtTab = ImRaii.TabItem("User Management");
|
||||
if (mgmtTab)
|
||||
if (!mgmtTab)
|
||||
return;
|
||||
|
||||
ImGuiHelpers.ScaledDummy(3f);
|
||||
|
||||
var style = ImGui.GetStyle();
|
||||
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, new Vector2(10f, 5f) * ImGuiHelpers.GlobalScale);
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(8f, style.ItemSpacing.Y));
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.TabRounding, 5f * ImGuiHelpers.GlobalScale);
|
||||
|
||||
var baseTab = UIColors.Get("FullBlack").WithAlpha(0.0f);
|
||||
var baseTabDim = UIColors.Get("FullBlack").WithAlpha(0.1f);
|
||||
var accent = UIColors.Get("LightlessPurple");
|
||||
var accentHover = accent.WithAlpha(0.90f);
|
||||
var accentActive = accent;
|
||||
|
||||
ImGui.PushStyleColor(ImGuiCol.Tab, baseTab);
|
||||
ImGui.PushStyleColor(ImGuiCol.TabHovered, accentHover);
|
||||
ImGui.PushStyleColor(ImGuiCol.TabActive, accentActive);
|
||||
ImGui.PushStyleColor(ImGuiCol.TabUnfocused, baseTabDim);
|
||||
ImGui.PushStyleColor(ImGuiCol.TabUnfocusedActive, accentActive.WithAlpha(0.80f));
|
||||
|
||||
using (var innerTabBar = ImRaii.TabBar("user_mgmt_inner_tab_" + GroupFullInfo.GID))
|
||||
{
|
||||
if (_uiSharedService.MediumTreeNode("User List & Administration", UIColors.Get("LightlessPurple")))
|
||||
if (innerTabBar)
|
||||
{
|
||||
var snapshot = _pairUiService.GetSnapshot();
|
||||
if (!snapshot.GroupPairs.TryGetValue(GroupFullInfo, out var pairs))
|
||||
// Users tab
|
||||
var usersTab = ImRaii.TabItem("Users");
|
||||
if (usersTab)
|
||||
{
|
||||
UiSharedService.ColorTextWrapped("No users found in this Syncshell", ImGuiColors.DalamudYellow);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawUserListCustom(pairs, GroupFullInfo);
|
||||
DrawUserListSection();
|
||||
}
|
||||
usersTab.Dispose();
|
||||
|
||||
ImGui.TreePop();
|
||||
// Cleanup tab
|
||||
var cleanupTab = ImRaii.TabItem("Cleanup");
|
||||
if (cleanupTab)
|
||||
{
|
||||
DrawMassCleanupSection();
|
||||
}
|
||||
cleanupTab.Dispose();
|
||||
|
||||
// Bans tab
|
||||
var bansTab = ImRaii.TabItem("Bans");
|
||||
if (bansTab)
|
||||
{
|
||||
DrawUserBansSection();
|
||||
}
|
||||
bansTab.Dispose();
|
||||
}
|
||||
ImGui.Separator();
|
||||
|
||||
if (_uiSharedService.MediumTreeNode("Mass Cleanup", UIColors.Get("DimRed")))
|
||||
{
|
||||
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");
|
||||
|
||||
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);
|
||||
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} 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",
|
||||
[0, 1, 3, 7, 14, 30, 90],
|
||||
(count) =>
|
||||
{
|
||||
return count == 0 ? "15 minute(s)" : count + " day(s)";
|
||||
},
|
||||
(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} day(s).");
|
||||
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.TooltipSeparator + "Hold CTRL to enable this button");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_pruneTask != null)
|
||||
{
|
||||
if (!_pruneTask.IsCompleted)
|
||||
{
|
||||
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("DimRed"), 1.5f);
|
||||
ImGui.TreePop();
|
||||
}
|
||||
ImGui.Separator();
|
||||
|
||||
if (_uiSharedService.MediumTreeNode("User Bans", UIColors.Get("LightlessYellow")))
|
||||
{
|
||||
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"))
|
||||
{
|
||||
_apiController.GroupUnbanUser(bannedUser);
|
||||
_bannedUsers.RemoveAll(b => string.Equals(b.UID, bannedUser.UID, StringComparison.Ordinal));
|
||||
}
|
||||
}
|
||||
ImGui.EndTable();
|
||||
}
|
||||
UiSharedService.ColoredSeparator(UIColors.Get("LightlessYellow"), 1.5f);
|
||||
ImGui.TreePop();
|
||||
}
|
||||
ImGui.Separator();
|
||||
}
|
||||
mgmtTab.Dispose();
|
||||
}
|
||||
|
||||
private void DrawUserListSection()
|
||||
{
|
||||
var snapshot = _pairUiService.GetSnapshot();
|
||||
if (!snapshot.GroupPairs.TryGetValue(GroupFullInfo, out var pairs))
|
||||
{
|
||||
UiSharedService.ColorTextWrapped("No users found in this Syncshell", ImGuiColors.DalamudYellow);
|
||||
return;
|
||||
}
|
||||
|
||||
_uiSharedService.MediumText("User List & Administration", UIColors.Get("LightlessPurple"));
|
||||
ImGuiHelpers.ScaledDummy(2f);
|
||||
DrawUserListCustom(pairs, GroupFullInfo);
|
||||
}
|
||||
|
||||
private void DrawMassCleanupSection()
|
||||
{
|
||||
_uiSharedService.MediumText("Mass Cleanup", UIColors.Get("DimRed"));
|
||||
UiSharedService.TextWrapped("Tools to bulk-clean inactive or unwanted users from this Syncshell.");
|
||||
ImGuiHelpers.ScaledDummy(3f);
|
||||
|
||||
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");
|
||||
|
||||
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);
|
||||
UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.0f);
|
||||
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} 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",
|
||||
[0, 1, 3, 7, 14, 30, 90],
|
||||
(count) => count == 0 ? "15 minute(s)" : count + " day(s)",
|
||||
(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} day(s).");
|
||||
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.TooltipSeparator + "Hold CTRL to enable this button");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_pruneTask != null)
|
||||
{
|
||||
if (!_pruneTask.IsCompleted)
|
||||
{
|
||||
UiSharedService.ColorTextWrapped("Pruning Syncshell...", ImGuiColors.DalamudYellow);
|
||||
}
|
||||
else
|
||||
{
|
||||
UiSharedService.TextWrapped($"Syncshell was pruned and {_pruneTask.Result} inactive user(s) have been removed.");
|
||||
}
|
||||
}
|
||||
|
||||
ImGuiHelpers.ScaledDummy(4f);
|
||||
UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.0f);
|
||||
ImGuiHelpers.ScaledDummy(2f);
|
||||
|
||||
DrawAutoPruneSettings();
|
||||
}
|
||||
|
||||
private void DrawUserBansSection()
|
||||
{
|
||||
_uiSharedService.MediumText("User Bans", UIColors.Get("LightlessYellow"));
|
||||
ImGuiHelpers.ScaledDummy(3f);
|
||||
|
||||
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"))
|
||||
{
|
||||
_apiController.GroupUnbanUser(bannedUser);
|
||||
_bannedUsers.RemoveAll(b => string.Equals(b.UID, bannedUser.UID, StringComparison.Ordinal));
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawInvites(GroupPermissions perm)
|
||||
{
|
||||
var inviteTab = ImRaii.TabItem("Invites");
|
||||
@@ -476,6 +718,7 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
|
||||
|
||||
private void DrawUserListCustom(IReadOnlyList<Pair> pairs, GroupFullInfoDto GroupFullInfo)
|
||||
{
|
||||
// Search bar (unchanged)
|
||||
ImGui.PushItemWidth(0);
|
||||
_uiSharedService.IconText(FontAwesomeIcon.Search, UIColors.Get("LightlessPurple"));
|
||||
ImGui.SameLine();
|
||||
@@ -511,21 +754,20 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
|
||||
if (p.Value.Value.IsPinned()) return 2;
|
||||
return 10;
|
||||
})
|
||||
.ThenBy(p => p.Key.GetNote() ?? p.Key.UserData.AliasOrUID, StringComparer.OrdinalIgnoreCase);
|
||||
.ThenBy(p => p.Key.GetNote() ?? p.Key.UserData.AliasOrUID, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
|
||||
ImGui.BeginChild("userListScroll#" + GroupFullInfo.Group.AliasOrGID, new Vector2(0, 0), true);
|
||||
|
||||
var style = ImGui.GetStyle();
|
||||
float fullW = ImGui.GetContentRegionAvail().X;
|
||||
|
||||
float colUid = fullW * 0.50f;
|
||||
float colFlags = fullW * 0.10f;
|
||||
float colActions = fullW - colUid - colFlags - style.ItemSpacing.X * 2.5f;
|
||||
float colActions = fullW - colUid - colFlags - style.ItemSpacing.X * 2.0f;
|
||||
|
||||
DrawUserListHeader(colUid, colFlags);
|
||||
|
||||
bool useScroll = pairs.Count > 10;
|
||||
float childHeight = useScroll ? 260f * ImGuiHelpers.GlobalScale : 0f;
|
||||
|
||||
ImGui.BeginChild("userListScroll#" + GroupFullInfo.Group.AliasOrGID, new Vector2(0, childHeight), true);
|
||||
|
||||
int rowIndex = 0;
|
||||
foreach (var kv in orderedPairs)
|
||||
{
|
||||
@@ -533,8 +775,8 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
|
||||
var userInfoOpt = kv.Value;
|
||||
DrawUserRowCustom(pair, userInfoOpt, GroupFullInfo, rowIndex++, colUid, colFlags, colActions);
|
||||
}
|
||||
|
||||
ImGui.EndChild();
|
||||
UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.0f);
|
||||
}
|
||||
|
||||
private static void DrawUserListHeader(float colUid, float colFlags)
|
||||
@@ -544,18 +786,18 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
|
||||
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, UIColors.Get("LightlessPurple"));
|
||||
|
||||
// Alias/UID/Note
|
||||
// Alias / UID / Note
|
||||
ImGui.SetCursorPosX(x0);
|
||||
ImGui.TextUnformatted("Alias / UID / Note");
|
||||
|
||||
// User Flags
|
||||
// Flags
|
||||
ImGui.SameLine();
|
||||
ImGui.SetCursorPosX(x0 + colUid + style.ItemSpacing.X);
|
||||
ImGui.TextUnformatted("Flags");
|
||||
|
||||
// User Actions
|
||||
// Actions
|
||||
ImGui.SameLine();
|
||||
ImGui.SetCursorPosX(x0 + colUid + colFlags + style.ItemSpacing.X * 2.5f);
|
||||
ImGui.SetCursorPosX(x0 + colUid + colFlags + style.ItemSpacing.X * 2.0f);
|
||||
ImGui.TextUnformatted("Actions");
|
||||
|
||||
ImGui.PopStyleColor();
|
||||
@@ -724,6 +966,27 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
|
||||
}
|
||||
}
|
||||
|
||||
private void SavePruneSettings()
|
||||
{
|
||||
if (_autoPruneDays <= 0)
|
||||
{
|
||||
_autoPruneEnabled = false;
|
||||
}
|
||||
|
||||
var enabled = _autoPruneEnabled && _autoPruneDays > 0;
|
||||
var dto = new GroupPruneSettingsDto(Group: GroupFullInfo.Group, AutoPruneEnabled: enabled, AutoPruneDays: enabled ? _autoPruneDays : 0);
|
||||
|
||||
try
|
||||
{
|
||||
_apiController.GroupSetPruneSettings(dto).GetAwaiter().GetResult();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to save auto prune settings for group {GID}", GroupFullInfo.Group.GID);
|
||||
UiSharedService.ColorTextWrapped("Failed to save auto-prune settings.", ImGuiColors.DalamudRed);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool MatchesUserFilter(Pair pair, string filterLower)
|
||||
{
|
||||
var note = pair.GetNote() ?? string.Empty;
|
||||
|
||||
@@ -151,6 +151,20 @@ public partial class ApiController
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<GroupPruneSettingsDto> GroupGetPruneSettings(GroupDto dto)
|
||||
{
|
||||
CheckConnection();
|
||||
return await _lightlessHub!.InvokeAsync<GroupPruneSettingsDto>(nameof(GroupGetPruneSettings), dto)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task GroupSetPruneSettings(GroupPruneSettingsDto dto)
|
||||
{
|
||||
CheckConnection();
|
||||
await _lightlessHub!.SendAsync(nameof(GroupSetPruneSettings), dto)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void CheckConnection()
|
||||
{
|
||||
if (ServerState is not (ServerState.Connected or ServerState.Connecting or ServerState.Reconnecting)) throw new InvalidDataException("Not connected");
|
||||
|
||||
@@ -71,6 +71,7 @@ public class HubFactory : MediatorSubscriberBase
|
||||
};
|
||||
|
||||
Logger.LogDebug("Building new HubConnection using transport {transport}", transportType);
|
||||
var msgpackOptions = MessagePackSerializerOptions.Standard.WithCompression(MessagePackCompression.Lz4Block).WithResolver(ContractlessStandardResolver.Instance);
|
||||
|
||||
_instance = new HubConnectionBuilder()
|
||||
.WithUrl(_serverConfigurationManager.CurrentApiUrl + ILightlessHub.Path, options =>
|
||||
@@ -80,22 +81,7 @@ public class HubFactory : MediatorSubscriberBase
|
||||
})
|
||||
.AddMessagePackProtocol(opt =>
|
||||
{
|
||||
var resolver = CompositeResolver.Create(StandardResolverAllowPrivate.Instance,
|
||||
BuiltinResolver.Instance,
|
||||
AttributeFormatterResolver.Instance,
|
||||
// replace enum resolver
|
||||
DynamicEnumAsStringResolver.Instance,
|
||||
DynamicGenericResolver.Instance,
|
||||
DynamicUnionResolver.Instance,
|
||||
DynamicObjectResolver.Instance,
|
||||
PrimitiveObjectResolver.Instance,
|
||||
// final fallback(last priority)
|
||||
StandardResolver.Instance);
|
||||
|
||||
opt.SerializerOptions =
|
||||
MessagePackSerializerOptions.Standard
|
||||
.WithCompression(MessagePackCompression.Lz4Block)
|
||||
.WithResolver(resolver);
|
||||
opt.SerializerOptions = msgpackOptions;
|
||||
})
|
||||
.WithAutomaticReconnect(new ForeverRetryPolicy(Mediator))
|
||||
.ConfigureLogging(a =>
|
||||
|
||||
Reference in New Issue
Block a user