using Dalamud.Bindings.ImGui; using Dalamud.Interface; using Dalamud.Interface.Colors; using Dalamud.Interface.ImGuiFileDialog; using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using LightlessSync.API.Data.Enum; using LightlessSync.API.Data.Extensions; using LightlessSync.API.Dto.Group; using LightlessSync.Services; using LightlessSync.Services.Mediator; using LightlessSync.PlayerData.Pairs; using LightlessSync.WebAPI; using Microsoft.Extensions.Logging; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; using System.Globalization; namespace LightlessSync.UI; public class SyncshellAdminUI : WindowMediatorSubscriberBase { private readonly ApiController _apiController; private readonly bool _isModerator = false; private readonly bool _isOwner = false; private readonly List _oneTimeInvites = []; private readonly PairManager _pairManager; private readonly LightlessProfileManager _lightlessProfileManager; private readonly FileDialogManager _fileDialogManager; private readonly UiSharedService _uiSharedService; private List _bannedUsers = []; private LightlessGroupProfileData? _profileData = null; private bool _adjustedForScollBarsLocalProfile = false; private bool _adjustedForScollBarsOnlineProfile = false; private string _descriptionText = string.Empty; private IDalamudTextureWrap? _pfpTextureWrap; private string _profileDescription = string.Empty; private byte[] _profileImage = []; private bool _showFileDialogError = false; private int _multiInvites; private string _newPassword; private bool _pwChangeSuccess; private Task? _pruneTestTask; private Task? _pruneTask; private int _pruneDays = 14; private List _selectedTags = []; public SyncshellAdminUI(ILogger logger, LightlessMediator mediator, ApiController apiController, UiSharedService uiSharedService, PairManager pairManager, GroupFullInfoDto groupFullInfo, PerformanceCollectorService performanceCollectorService, LightlessProfileManager lightlessProfileManager, FileDialogManager fileDialogManager) : base(logger, mediator, "Syncshell Admin Panel (" + groupFullInfo.GroupAliasOrGID + ")", performanceCollectorService) { GroupFullInfo = groupFullInfo; _apiController = apiController; _uiSharedService = uiSharedService; _pairManager = pairManager; _lightlessProfileManager = lightlessProfileManager; _fileDialogManager = fileDialogManager; _isOwner = string.Equals(GroupFullInfo.OwnerUID, _apiController.UID, StringComparison.Ordinal); _isModerator = GroupFullInfo.GroupUserInfo.IsModerator(); _newPassword = string.Empty; _multiInvites = 30; _pwChangeSuccess = true; IsOpen = true; Mediator.Subscribe(this, (msg) => { if (msg.GroupData == null || string.Equals(msg.GroupData.AliasOrGID, GroupFullInfo.Group.AliasOrGID, StringComparison.Ordinal)) { _pfpTextureWrap?.Dispose(); _pfpTextureWrap = null; } }); SizeConstraints = new WindowSizeConstraints() { MinimumSize = new(700, 500), MaximumSize = new(700, 2000), }; } public GroupFullInfoDto GroupFullInfo { get; private set; } protected override void DrawInternal() { if (!_isModerator && !_isOwner) return; _logger.LogTrace("Drawing Syncshell Admin UI for {group}", GroupFullInfo.GroupAliasOrGID); GroupFullInfo = _pairManager.Groups[GroupFullInfo.Group]; _profileData = _lightlessProfileManager.GetLightlessGroupProfile(GroupFullInfo.Group); GetTagsFromProfile(); using var id = ImRaii.PushId("syncshell_admin_" + GroupFullInfo.GID); using (_uiSharedService.UidFont.Push()) _uiSharedService.UnderlinedBigText(GroupFullInfo.GroupAliasOrGID + " Administrative Panel", UIColors.Get("LightlessBlue")); ImGui.Separator(); var perm = GroupFullInfo.GroupPermissions; using var tabbar = ImRaii.TabBar("syncshell_tab_" + GroupFullInfo.GID); if (tabbar) { DrawInvites(perm); DrawManagement(); DrawPermission(perm); DrawProfile(); } } private void DrawPermission(GroupPermissions perm) { var permissionTab = ImRaii.TabItem("Permissions"); if (permissionTab) { bool isDisableAnimations = perm.IsPreferDisableAnimations(); bool isDisableSounds = perm.IsPreferDisableSounds(); bool isDisableVfx = perm.IsPreferDisableVFX(); ImGui.AlignTextToFramePadding(); ImGui.Text("Suggest Sound Sync"); _uiSharedService.BooleanToColoredIcon(!isDisableSounds); ImGui.SameLine(230); using (ImRaii.PushColor(ImGuiCol.Text, isDisableSounds ? UIColors.Get("PairBlue") : UIColors.Get("DimRed"))) { if (_uiSharedService.IconTextButton(isDisableSounds ? FontAwesomeIcon.VolumeUp : FontAwesomeIcon.VolumeMute, isDisableSounds ? "Suggest to enable sound sync" : "Suggest to disable sound sync")) { perm.SetPreferDisableSounds(!perm.IsPreferDisableSounds()); _ = _apiController.GroupChangeGroupPermissionState(new(GroupFullInfo.Group, perm)); } } ImGui.AlignTextToFramePadding(); ImGui.Text("Suggest Animation Sync"); _uiSharedService.BooleanToColoredIcon(!isDisableAnimations); ImGui.SameLine(230); using (ImRaii.PushColor(ImGuiCol.Text, isDisableAnimations ? UIColors.Get("PairBlue") : UIColors.Get("DimRed"))) { if (_uiSharedService.IconTextButton(isDisableAnimations ? FontAwesomeIcon.Running : FontAwesomeIcon.Stop, isDisableAnimations ? "Suggest to enable animation sync" : "Suggest to disable animation sync")) { perm.SetPreferDisableAnimations(!perm.IsPreferDisableAnimations()); _ = _apiController.GroupChangeGroupPermissionState(new(GroupFullInfo.Group, perm)); } } ImGui.AlignTextToFramePadding(); ImGui.Text("Suggest VFX Sync"); _uiSharedService.BooleanToColoredIcon(!isDisableVfx); ImGui.SameLine(230); using (ImRaii.PushColor(ImGuiCol.Text, isDisableVfx ? UIColors.Get("PairBlue") : UIColors.Get("DimRed"))) { if (_uiSharedService.IconTextButton(isDisableVfx ? FontAwesomeIcon.Sun : FontAwesomeIcon.Circle, isDisableVfx ? "Suggest to enable vfx sync" : "Suggest to disable vfx sync")) { perm.SetPreferDisableVFX(!perm.IsPreferDisableVFX()); _ = _apiController.GroupChangeGroupPermissionState(new(GroupFullInfo.Group, perm)); } } UiSharedService.TextWrapped("Note: those suggested permissions will be shown to users on joining the Syncshell."); } permissionTab.Dispose(); if (_isOwner) { var ownerTab = ImRaii.TabItem("Owner Settings"); if (ownerTab) { ImGui.AlignTextToFramePadding(); ImGui.TextUnformatted("New Password"); var availableWidth = ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X; var buttonSize = _uiSharedService.GetIconTextButtonSize(FontAwesomeIcon.Passport, "Change Password"); var textSize = ImGui.CalcTextSize("New Password").X; var spacing = ImGui.GetStyle().ItemSpacing.X; ImGui.SameLine(); ImGui.SetNextItemWidth(availableWidth - buttonSize - textSize - spacing * 2); ImGui.InputTextWithHint("##changepw", "Min 10 characters", ref _newPassword, 50); ImGui.SameLine(); using (ImRaii.Disabled(_newPassword.Length < 10)) { if (_uiSharedService.IconTextButton(FontAwesomeIcon.Passport, "Change Password")) { _pwChangeSuccess = _apiController.GroupChangePassword(new GroupPasswordDto(GroupFullInfo.Group, _newPassword)).Result; _newPassword = string.Empty; } } UiSharedService.AttachToolTip("Password requires to be at least 10 characters long. This action is irreversible."); if (!_pwChangeSuccess) { UiSharedService.ColorTextWrapped("Failed to change the password. Password requires to be at least 10 characters long.", ImGuiColors.DalamudYellow); } if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, "Delete Syncshell") && UiSharedService.CtrlPressed() && UiSharedService.ShiftPressed()) { IsOpen = false; _ = _apiController.GroupDelete(new(GroupFullInfo.Group)); } UiSharedService.AttachToolTip("Hold CTRL and Shift and click to delete this Syncshell." + Environment.NewLine + "WARNING: this action is irreversible."); } ownerTab.Dispose(); } } private void DrawProfile() { var profileTab = ImRaii.TabItem("Profile"); if (profileTab) { if (_uiSharedService.MediumTreeNode("Current Profile", UIColors.Get("LightlessPurple"))) { ImGui.Dummy(new Vector2(5)); if (_profileData == null) { UiSharedService.ColorTextWrapped("Failed to load profile data.", ImGuiColors.DalamudRed); ImGui.TreePop(); return; } if (!_profileImage.SequenceEqual(_profileData.ProfileImageData.Value)) { _profileImage = _profileData.ProfileImageData.Value; _pfpTextureWrap?.Dispose(); _pfpTextureWrap = _uiSharedService.LoadImage(_profileImage); } if (!string.Equals(_profileDescription, _profileData.Description, StringComparison.OrdinalIgnoreCase)) { _profileDescription = _profileData.Description; _descriptionText = _profileDescription; } if (_pfpTextureWrap != null) { ImGui.Image(_pfpTextureWrap.Handle, ImGuiHelpers.ScaledVector2(_pfpTextureWrap.Width, _pfpTextureWrap.Height)); } var spacing = ImGui.GetStyle().ItemSpacing.X; ImGuiHelpers.ScaledRelativeSameLine(256, spacing); using (_uiSharedService.GameFont.Push()) { var descriptionTextSize = ImGui.CalcTextSize(_profileData.Description, wrapWidth: 256f); var childFrame = ImGuiHelpers.ScaledVector2(256 + ImGui.GetStyle().WindowPadding.X + ImGui.GetStyle().WindowBorderSize, 256); if (descriptionTextSize.Y > childFrame.Y) { _adjustedForScollBarsOnlineProfile = true; } else { _adjustedForScollBarsOnlineProfile = false; } childFrame = childFrame with { X = childFrame.X + (_adjustedForScollBarsOnlineProfile ? ImGui.GetStyle().ScrollbarSize : 0), }; if (ImGui.BeginChildFrame(101, childFrame)) { UiSharedService.TextWrapped(_profileData.Description); } ImGui.EndChildFrame(); ImGui.TreePop(); } var nsfw = _profileData.IsNsfw; ImGui.BeginDisabled(); ImGui.Checkbox("Is NSFW", ref nsfw); ImGui.EndDisabled(); } ImGui.Separator(); if (_uiSharedService.MediumTreeNode("Profile Settings", UIColors.Get("LightlessPurple"))) { ImGui.Dummy(new Vector2(5)); ImGui.TextUnformatted($"Profile Picture:"); if (_uiSharedService.IconTextButton(FontAwesomeIcon.FileUpload, "Upload new profile picture")) { _fileDialogManager.OpenFileDialog("Select new Profile picture", ".png", (success, file) => { if (!success) return; _ = Task.Run(async () => { var fileContent = await File.ReadAllBytesAsync(file).ConfigureAwait(false); MemoryStream ms = new(fileContent); await using (ms.ConfigureAwait(false)) { var format = await Image.DetectFormatAsync(ms).ConfigureAwait(false); if (!format.FileExtensions.Contains("png", StringComparer.OrdinalIgnoreCase)) { _showFileDialogError = true; return; } using var image = Image.Load(fileContent); if (image.Width > 512 || image.Height > 512 || (fileContent.Length > 2000 * 1024)) { _showFileDialogError = true; return; } _showFileDialogError = false; await _apiController.GroupSetProfile(new GroupProfileDto(new GroupData(GroupFullInfo.Group.AliasOrGID), Description: null, Tags: null, Convert.ToBase64String(fileContent), BannerBase64: null, IsNsfw: null, IsDisabled: null)) .ConfigureAwait(false); } }); }); } UiSharedService.AttachToolTip("Select and upload a new profile picture"); ImGui.SameLine(); if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, "Clear uploaded profile picture")) { _ = _apiController.GroupSetProfile(new GroupProfileDto(new GroupData(GroupFullInfo.Group.AliasOrGID), Description: null, Tags: null, PictureBase64: null, BannerBase64: null, IsNsfw: null, IsDisabled: null)); } UiSharedService.AttachToolTip("Clear your currently uploaded profile picture"); if (_showFileDialogError) { UiSharedService.ColorTextWrapped("The profile picture must be a PNG file with a maximum height and width of 256px and 250KiB size", ImGuiColors.DalamudRed); } ImGui.Separator(); ImGui.TextUnformatted($"Tags:"); var childFrameLocal = ImGuiHelpers.ScaledVector2(256 + ImGui.GetStyle().WindowPadding.X + ImGui.GetStyle().WindowBorderSize, 200); var allCategoryIndexes = Enum.GetValues() .Cast() .ToList(); foreach (int tag in allCategoryIndexes) { using (ImRaii.PushId($"tag-{tag}")) DrawTag(tag); } ImGui.Separator(); var widthTextBox = 400; var posX = ImGui.GetCursorPosX(); ImGui.TextUnformatted($"Description {_descriptionText.Length}/1500"); ImGui.SetCursorPosX(posX); ImGuiHelpers.ScaledRelativeSameLine(widthTextBox, ImGui.GetStyle().ItemSpacing.X); ImGui.TextUnformatted("Preview (approximate)"); using (_uiSharedService.GameFont.Push()) ImGui.InputTextMultiline("##description", ref _descriptionText, 1500, ImGuiHelpers.ScaledVector2(widthTextBox, 200)); ImGui.SameLine(); using (_uiSharedService.GameFont.Push()) { var descriptionTextSizeLocal = ImGui.CalcTextSize(_descriptionText, wrapWidth: 256f); if (descriptionTextSizeLocal.Y > childFrameLocal.Y) { _adjustedForScollBarsLocalProfile = true; } else { _adjustedForScollBarsLocalProfile = false; } childFrameLocal = childFrameLocal with { X = childFrameLocal.X + (_adjustedForScollBarsLocalProfile ? ImGui.GetStyle().ScrollbarSize : 0), }; if (ImGui.BeginChildFrame(102, childFrameLocal)) { UiSharedService.TextWrapped(_descriptionText); } ImGui.EndChildFrame(); } if (_uiSharedService.IconTextButton(FontAwesomeIcon.Save, "Save Description")) { _ = _apiController.GroupSetProfile(new GroupProfileDto(new GroupData(GroupFullInfo.Group.AliasOrGID), Description: _descriptionText, Tags: null, PictureBase64: null, BannerBase64: null, IsNsfw: null, IsDisabled: null)); } UiSharedService.AttachToolTip("Sets your profile description text"); ImGui.SameLine(); if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, "Clear Description")) { _ = _apiController.GroupSetProfile(new GroupProfileDto(new GroupData(GroupFullInfo.Group.AliasOrGID), Description: null, Tags: null, PictureBase64: null, BannerBase64: null, IsNsfw: null, IsDisabled: null)); } UiSharedService.AttachToolTip("Clears your profile description text"); ImGui.Separator(); ImGui.TextUnformatted($"Profile Options:"); var isNsfw = _profileData?.IsNsfw ?? false; if (ImGui.Checkbox("Profile is NSFW", ref isNsfw)) { _ = _apiController.GroupSetProfile(new GroupProfileDto(new GroupData(GroupFullInfo.Group.AliasOrGID), Description: null, Tags: null, PictureBase64: null, BannerBase64: null, IsNsfw: isNsfw, IsDisabled: null)); } _uiSharedService.DrawHelpText("If your profile description or image can be considered NSFW, toggle this to ON"); ImGui.TreePop(); } } profileTab.Dispose(); } private void DrawManagement() { var mgmtTab = ImRaii.TabItem("User Management"); if (mgmtTab) { if (_uiSharedService.MediumTreeNode("User List & Administration", UIColors.Get("LightlessPurple"))) { if (!_pairManager.GroupPairs.TryGetValue(GroupFullInfo, out var pairs)) { UiSharedService.ColorTextWrapped("No users found in this Syncshell", ImGuiColors.DalamudYellow); } else { var tableFlags = ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingStretchProp; if (pairs.Count > 10) tableFlags |= ImGuiTableFlags.ScrollY; using var table = ImRaii.Table("userList#" + GroupFullInfo.Group.AliasOrGID, 3, tableFlags); if (table) { ImGui.TableSetupColumn("Alias/UID/Note", ImGuiTableColumnFlags.None, 4); ImGui.TableSetupColumn("Flags", ImGuiTableColumnFlags.None, 1); ImGui.TableSetupColumn("Actions", ImGuiTableColumnFlags.None, 3); ImGui.TableHeadersRow(); var groupedPairs = new Dictionary(pairs.Select(p => new KeyValuePair(p, GroupFullInfo.GroupPairUserInfos.TryGetValue(p.UserData.UID, out GroupPairUserInfo value) ? value : null))); foreach (var pair in groupedPairs.OrderBy(p => { if (p.Value == null) return 10; if (string.Equals(p.Key.UserData.UID, GroupFullInfo.OwnerUID, StringComparison.Ordinal)) return 0; if (p.Value.Value.IsModerator()) return 1; if (p.Value.Value.IsPinned()) return 2; return 10; }).ThenBy(p => p.Key.GetNote() ?? p.Key.UserData.AliasOrUID, StringComparer.OrdinalIgnoreCase)) { using var tableId = ImRaii.PushId("userTable_" + pair.Key.UserData.UID); var isUserOwner = string.Equals(pair.Key.UserData.UID, GroupFullInfo.OwnerUID, StringComparison.Ordinal); ImGui.TableNextColumn(); // alias/uid/note var note = pair.Key.GetNote(); var text = note == null ? pair.Key.UserData.AliasOrUID : note + " (" + pair.Key.UserData.AliasOrUID + ")"; ImGui.AlignTextToFramePadding(); var boolcolor = UiSharedService.GetBoolColor(pair.Key.IsOnline); UiSharedService.ColorText(text, boolcolor); if (!string.IsNullOrEmpty(pair.Key.PlayerName)) { UiSharedService.AttachToolTip(pair.Key.PlayerName); ImGui.SameLine(); } ImGui.TableNextColumn(); // special flags if (pair.Value != null && (pair.Value.Value.IsModerator() || pair.Value.Value.IsPinned() || isUserOwner)) { if (pair.Value.Value.IsModerator()) { _uiSharedService.IconText(FontAwesomeIcon.UserShield, UIColors.Get("LightlessPurple")); UiSharedService.AttachToolTip("Moderator"); } if (pair.Value.Value.IsPinned() && !isUserOwner) { _uiSharedService.IconText(FontAwesomeIcon.Thumbtack); UiSharedService.AttachToolTip("Pinned"); } if (isUserOwner) { _uiSharedService.IconText(FontAwesomeIcon.Crown, UIColors.Get("LightlessYellow")); UiSharedService.AttachToolTip("Owner"); } } else { _uiSharedService.IconText(FontAwesomeIcon.None); } ImGui.TableNextColumn(); // actions if (_isOwner) { using (ImRaii.PushColor(ImGuiCol.Text, UIColors.Get("LightlessYellow"))) { using (ImRaii.Disabled(!UiSharedService.ShiftPressed())) { if (_uiSharedService.IconButton(FontAwesomeIcon.Crown)) { _ = _apiController.GroupChangeOwnership(new(GroupFullInfo.Group, pair.Key.UserData)); IsOpen = false; } } } UiSharedService.AttachToolTip("Hold SHIFT and click to transfer ownership of this Syncshell to " + (pair.Key.UserData.AliasOrUID) + Environment.NewLine + "WARNING: This action is irreversible and will close screen."); ImGui.SameLine(); using (ImRaii.PushColor(ImGuiCol.Text, pair.Value != null && pair.Value.Value.IsModerator() ? UIColors.Get("DimRed") : UIColors.Get("PairBlue"))) { if (_uiSharedService.IconButton(FontAwesomeIcon.UserShield)) { GroupPairUserInfo userInfo = pair.Value ?? GroupPairUserInfo.None; userInfo.SetModerator(!userInfo.IsModerator()); _ = _apiController.GroupSetUserInfo(new GroupPairUserInfoDto(GroupFullInfo.Group, pair.Key.UserData, userInfo)); } } UiSharedService.AttachToolTip(pair.Value != null && pair.Value.Value.IsModerator() ? "Demod user" : "Mod user"); ImGui.SameLine(); } if (pair.Value == null || pair.Value != null && !pair.Value.Value.IsModerator() && !isUserOwner) { using (ImRaii.PushColor(ImGuiCol.Text, pair.Value != null && pair.Value.Value.IsPinned() ? UIColors.Get("DimRed") : UIColors.Get("PairBlue"))) { if (_uiSharedService.IconButton(FontAwesomeIcon.Thumbtack)) { GroupPairUserInfo userInfo = pair.Value ?? GroupPairUserInfo.None; userInfo.SetPinned(!userInfo.IsPinned()); _ = _apiController.GroupSetUserInfo(new GroupPairUserInfoDto(GroupFullInfo.Group, pair.Key.UserData, userInfo)); } } UiSharedService.AttachToolTip(pair.Value != null && pair.Value.Value.IsPinned() ? "Unpin user" : "Pin user"); ImGui.SameLine(); using (ImRaii.PushColor(ImGuiCol.Text, UIColors.Get("DimRed"))) { using (ImRaii.Disabled(!UiSharedService.CtrlPressed())) { if (_uiSharedService.IconButton(FontAwesomeIcon.Trash)) { _ = _apiController.GroupRemoveUser(new GroupPairDto(GroupFullInfo.Group, pair.Key.UserData)); } } } UiSharedService.AttachToolTip("Remove user from Syncshell" + UiSharedService.TooltipSeparator + "Hold CTRL to enable this button"); ImGui.SameLine(); using (ImRaii.PushColor(ImGuiCol.Text, UIColors.Get("DimRed"))) { using (ImRaii.Disabled(!UiSharedService.CtrlPressed())) { if (_uiSharedService.IconButton(FontAwesomeIcon.Ban)) { Mediator.Publish(new OpenBanUserPopupMessage(pair.Key, GroupFullInfo)); } } } UiSharedService.AttachToolTip("Ban user from Syncshell" + UiSharedService.TooltipSeparator + "Hold CTRL to enable this button"); } } } } UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f); ImGui.TreePop(); } 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", [1, 3, 7, 14, 30, 90], (count) => { return count + " day(s)"; }, (selected) => { _pruneDays = selected; _pruneTestTask = null; _pruneTask = null; }, _pruneDays); if (_pruneTestTask != null) { if (!_pruneTestTask.IsCompleted) { 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 DrawInvites(GroupPermissions perm) { var inviteTab = ImRaii.TabItem("Invites"); if (inviteTab) { bool isInvitesDisabled = perm.IsDisableInvites(); if (_uiSharedService.IconTextButton(isInvitesDisabled ? FontAwesomeIcon.Unlock : FontAwesomeIcon.Lock, isInvitesDisabled ? "Unlock Syncshell" : "Lock Syncshell")) { perm.SetDisableInvites(!isInvitesDisabled); _ = _apiController.GroupChangeGroupPermissionState(new(GroupFullInfo.Group, perm)); } ImGuiHelpers.ScaledDummy(2f); UiSharedService.TextWrapped("One-time invites work as single-use passwords. Use those if you do not want to distribute your Syncshell password."); if (_uiSharedService.IconTextButton(FontAwesomeIcon.Envelope, "Single one-time invite")) { ImGui.SetClipboardText(_apiController.GroupCreateTempInvite(new(GroupFullInfo.Group), 1).Result.FirstOrDefault() ?? string.Empty); } UiSharedService.AttachToolTip("Creates a single-use password for joining the syncshell which is valid for 24h and copies it to the clipboard."); ImGui.InputInt("##amountofinvites", ref _multiInvites); ImGui.SameLine(); using (ImRaii.Disabled(_multiInvites <= 1 || _multiInvites > 100)) { if (_uiSharedService.IconTextButton(FontAwesomeIcon.Envelope, "Generate " + _multiInvites + " one-time invites")) { _oneTimeInvites.AddRange(_apiController.GroupCreateTempInvite(new(GroupFullInfo.Group), _multiInvites).Result); } } if (_oneTimeInvites.Any()) { var invites = string.Join(Environment.NewLine, _oneTimeInvites); ImGui.InputTextMultiline("Generated Multi Invites", ref invites, 5000, new(0, 0), ImGuiInputTextFlags.ReadOnly); if (_uiSharedService.IconTextButton(FontAwesomeIcon.Copy, "Copy Invites to clipboard")) { ImGui.SetClipboardText(invites); } } } inviteTab.Dispose(); } private void DrawTag(int tag) { var HasTag = _selectedTags.Contains(tag); var tagName = (ProfileTags)tag; if (ImGui.Checkbox(tagName.ToString(), ref HasTag)) { if (HasTag) { _selectedTags.Add(tag); _ = _apiController.GroupSetProfile(new GroupProfileDto(new GroupData(GroupFullInfo.Group.AliasOrGID), Description: null, Tags: _selectedTags.ToArray(), PictureBase64: null, BannerBase64: null, IsNsfw: null, IsDisabled: null)); } else { _selectedTags.Remove(tag); _ = _apiController.GroupSetProfile(new GroupProfileDto(new GroupData(GroupFullInfo.Group.AliasOrGID), Description: null, Tags: _selectedTags.ToArray(), PictureBase64: null, BannerBase64: null, IsNsfw: null, IsDisabled: null)); } } } private void GetTagsFromProfile() { if (_profileData != null) { _selectedTags = [.. _profileData.Tags]; } } public override void OnClose() { Mediator.Publish(new RemoveWindowMessage(this)); _pfpTextureWrap?.Dispose(); } }