All checks were successful
Tag and Release Lightless / tag-and-release (push) Successful in 2m27s
2.0.0 Changes: - Reworked shell finder UI with compact or list view with profile tags showing with the listing, allowing moderators to broadcast the syncshell as well to have it be used more. - Reworked user list in syncshell admin screen to have filter visible and moved away from table to its own thing, allowing to copy uid/note/alias when clicking on the name. - Reworked download bars and download box to make it look more modern, removed the jitter around, so it shouldn't vibrate around much. - Chat has been added to the top menu, working in Zone or in Syncshells to be used there. - Paired system has been revamped to make pausing and unpausing faster, and loading people should be faster as well. - Moved to the internal object table to have faster load times for users; people should load in faster - Compactor is running on a multi-threaded level instead of single-threaded; this should increase the speed of compacting files - Nameplate Service has been reworked so it wouldn't use the nameplate handler anymore. - Files can be resized when downloading to reduce load on users if they aren't compressed. (can be toggled to resize all). - Penumbra Collections are now only made when people are visible, reducing the load on boot-up when having many syncshells in your list. - Lightfinder plates have been moved away from using Nameplates, but will use an overlay. - Main UI has been changed a bit with a gradient, and on hover will glow up now. - Reworked Profile UI for Syncshell and Users to be more user-facing with more customizable items. - Reworked Settings UI to look more modern. - Performance should be better due to new systems that would dispose of the collections and better caching of items. Co-authored-by: defnotken <itsdefnotken@gmail.com> Co-authored-by: azyges <aaaaaa@aaa.aaa> Co-authored-by: choco <choco@patat.nl> Co-authored-by: cake <admin@cakeandbanana.nl> Co-authored-by: Minmoose <KennethBohr@outlook.com> Reviewed-on: #92
692 lines
26 KiB
C#
692 lines
26 KiB
C#
using Dalamud.Bindings.ImGui;
|
|
using Dalamud.Interface;
|
|
using Dalamud.Interface.Colors;
|
|
using Dalamud.Interface.Components;
|
|
using Dalamud.Interface.Utility;
|
|
using Dalamud.Interface.Utility.Raii;
|
|
using LightlessSync.API.Dto.Group;
|
|
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.PixelFormats;
|
|
using System.Numerics;
|
|
|
|
namespace LightlessSync.UI;
|
|
|
|
public partial class EditProfileUi
|
|
{
|
|
private void OpenGroupEditor(GroupFullInfoDto groupInfo)
|
|
{
|
|
_mode = ProfileEditorMode.Group;
|
|
_groupInfo = groupInfo;
|
|
|
|
var profile = _lightlessProfileManager.GetLightlessGroupProfile(groupInfo.Group);
|
|
_groupProfileData = profile;
|
|
SyncGroupProfileState(profile, resetSelection: true);
|
|
|
|
var scale = ImGuiHelpers.GlobalScale;
|
|
var viewport = ImGui.GetMainViewport();
|
|
ProfileEditorLayoutCoordinator.Enable(groupInfo.Group.GID);
|
|
ProfileEditorLayoutCoordinator.EnsureAnchor(viewport.WorkPos, scale);
|
|
Mediator.Publish(new GroupProfileOpenStandaloneMessage(groupInfo.Group));
|
|
|
|
IsOpen = true;
|
|
_wasOpen = true;
|
|
}
|
|
|
|
private void ResetGroupEditorState()
|
|
{
|
|
_groupInfo = null;
|
|
_groupProfileData = null;
|
|
_groupIsNsfw = false;
|
|
_groupIsDisabled = false;
|
|
_groupServerIsNsfw = false;
|
|
_groupServerIsDisabled = false;
|
|
_queuedProfileImage = null;
|
|
_queuedBannerImage = null;
|
|
_profileImage = Array.Empty<byte>();
|
|
_bannerImage = Array.Empty<byte>();
|
|
_profileDescription = string.Empty;
|
|
_descriptionText = string.Empty;
|
|
_profileTagIds = Array.Empty<int>();
|
|
_tagEditorSelection.Clear();
|
|
_pfpTextureWrap?.Dispose();
|
|
_pfpTextureWrap = null;
|
|
_bannerTextureWrap?.Dispose();
|
|
_bannerTextureWrap = null;
|
|
_showProfileImageError = false;
|
|
_showBannerImageError = false;
|
|
_groupVisibilityInitialized = false;
|
|
}
|
|
|
|
private void DrawGroupEditor(float scale)
|
|
{
|
|
if (_groupInfo is null)
|
|
{
|
|
UiSharedService.TextWrapped("Open the Syncshell admin panel and choose a group to edit its profile.");
|
|
return;
|
|
}
|
|
|
|
var viewport = ImGui.GetMainViewport();
|
|
var linked = ProfileEditorLayoutCoordinator.IsActive(_groupInfo.Group.GID);
|
|
|
|
if (linked)
|
|
{
|
|
ProfileEditorLayoutCoordinator.EnsureAnchor(viewport.WorkPos, scale);
|
|
|
|
var desiredSize = ProfileEditorLayoutCoordinator.GetEditorSize(scale);
|
|
if (!ProfileEditorLayoutCoordinator.NearlyEquals(ImGui.GetWindowSize(), desiredSize))
|
|
ImGui.SetWindowSize(desiredSize, ImGuiCond.Always);
|
|
|
|
var currentPos = ImGui.GetWindowPos();
|
|
if (IsWindowBeingDragged())
|
|
ProfileEditorLayoutCoordinator.UpdateAnchorFromEditor(currentPos, scale);
|
|
|
|
var desiredPos = ProfileEditorLayoutCoordinator.GetEditorPosition(scale);
|
|
if (!ProfileEditorLayoutCoordinator.NearlyEquals(currentPos, desiredPos))
|
|
ImGui.SetWindowPos(desiredPos, ImGuiCond.Always);
|
|
}
|
|
else
|
|
{
|
|
var defaultProfilePos = viewport.WorkPos + new Vector2(50f, 70f) * scale;
|
|
var defaultEditorPos = defaultProfilePos + ProfileEditorLayoutCoordinator.GetEditorOffset(scale);
|
|
ImGui.SetWindowPos(defaultEditorPos, ImGuiCond.FirstUseEver);
|
|
}
|
|
|
|
if (_queuedProfileImage is not null)
|
|
ApplyQueuedGroupProfileImage();
|
|
if (_queuedBannerImage is not null)
|
|
ApplyQueuedGroupBannerImage();
|
|
|
|
var profile = _lightlessProfileManager.GetLightlessGroupProfile(_groupInfo.Group);
|
|
_groupProfileData = profile;
|
|
SyncGroupProfileState(profile, resetSelection: false);
|
|
|
|
var accent = UIColors.Get("LightlessPurple");
|
|
var accentBg = new Vector4(accent.X, accent.Y, accent.Z, 0.015f);
|
|
var accentBorder = new Vector4(accent.X, accent.Y, accent.Z, 0.07f);
|
|
|
|
using var panelBg = ImRaii.PushColor(ImGuiCol.ChildBg, accentBg);
|
|
using var panelBorder = ImRaii.PushColor(ImGuiCol.ChildBg, accentBorder);
|
|
ImGui.PushStyleVar(ImGuiStyleVar.ChildRounding, 4f * scale);
|
|
|
|
if (ImGui.BeginChild("##GroupProfileEditorCanvas", -Vector2.One, true))
|
|
{
|
|
DrawGroupGuidelinesSection(scale);
|
|
ImGui.Dummy(new Vector2(0f, 4f * scale));
|
|
DrawGroupProfileContent(profile, scale);
|
|
}
|
|
|
|
ImGui.EndChild();
|
|
ImGui.PopStyleVar();
|
|
}
|
|
|
|
private void DrawGroupGuidelinesSection(float scale)
|
|
{
|
|
DrawSection("Guidelines", scale, () =>
|
|
{
|
|
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(1f, 1f));
|
|
|
|
_uiSharedService.DrawNoteLine("# ", UIColors.Get("LightlessBlue"), "All users that are paired and unpaused with you will be able to see your profile pictures, tags and description.");
|
|
_uiSharedService.DrawNoteLine("! ", UIColors.Get("LightlessYellow"), "Other users have the possibility to report this profile for breaking the rules.");
|
|
_uiSharedService.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.)");
|
|
_uiSharedService.DrawNoteLine("!!! ", UIColors.Get("DimRed"), "AVOID: Slurs of any kind in the description that can be considered highly offensive");
|
|
_uiSharedService.DrawNoteLine("! ", UIColors.Get("LightlessYellow"), "In case of valid reports from other users this can lead to disabling the profile forever or terminating syncshell owner's Lightless account indefinitely.");
|
|
_uiSharedService.DrawNoteLine("! ", UIColors.Get("LightlessYellow"), "Judgement of the profile validity from reports through staff is not up to debate and the decisions to disable the profile or your account permanent.");
|
|
_uiSharedService.DrawNoteLine("! ", UIColors.Get("LightlessBlue"), "If the profile picture or profile description could be considered NSFW, enable the toggle in visibility settings.");
|
|
|
|
ImGui.PopStyleVar();
|
|
});
|
|
}
|
|
|
|
private void DrawGroupProfileContent(LightlessGroupProfileData profile, float scale)
|
|
{
|
|
DrawSection("Profile Preview", scale, () => DrawGroupProfileSnapshot(profile, scale));
|
|
DrawSection("Profile Image", scale, DrawGroupProfileImageControls);
|
|
DrawSection("Profile Banner", scale, DrawGroupProfileBannerControls);
|
|
DrawSection("Profile Description", scale, DrawGroupProfileDescriptionEditor);
|
|
DrawSection("Profile Tags", scale, () => DrawGroupProfileTagsEditor(scale));
|
|
DrawSection("Visibility", scale, DrawGroupProfileVisibilityControls);
|
|
}
|
|
|
|
private void DrawGroupProfileSnapshot(LightlessGroupProfileData profile, float scale)
|
|
{
|
|
var bannerHeight = 140f * scale;
|
|
ImGui.PushStyleVar(ImGuiStyleVar.ChildRounding, 4f * scale);
|
|
if (ImGui.BeginChild("##GroupProfileBannerPreview", new Vector2(-1f, bannerHeight), true))
|
|
{
|
|
if (_bannerTextureWrap != null)
|
|
{
|
|
var childSize = ImGui.GetWindowSize();
|
|
var padding = ImGui.GetStyle().WindowPadding;
|
|
var contentSize = new Vector2(
|
|
MathF.Max(childSize.X - padding.X * 2f, 1f),
|
|
MathF.Max(childSize.Y - padding.Y * 2f, 1f));
|
|
|
|
var imageSize = ImGuiHelpers.ScaledVector2(_bannerTextureWrap.Width, _bannerTextureWrap.Height);
|
|
if (imageSize.X > contentSize.X || imageSize.Y > contentSize.Y)
|
|
{
|
|
var ratio = MathF.Min(contentSize.X / MathF.Max(imageSize.X, 1f), contentSize.Y / MathF.Max(imageSize.Y, 1f));
|
|
imageSize *= ratio;
|
|
}
|
|
|
|
var offset = new Vector2(
|
|
MathF.Max((contentSize.X - imageSize.X) * 0.5f, 0f),
|
|
MathF.Max((contentSize.Y - imageSize.Y) * 0.5f, 0f));
|
|
ImGui.SetCursorPos(padding + offset);
|
|
ImGui.Image(_bannerTextureWrap.Handle, imageSize);
|
|
}
|
|
else
|
|
{
|
|
ImGui.TextColored(UIColors.Get("LightlessPurple"), "No profile banner uploaded.");
|
|
}
|
|
}
|
|
ImGui.EndChild();
|
|
ImGui.PopStyleVar();
|
|
|
|
ImGui.Dummy(new Vector2(0f, 6f * scale));
|
|
|
|
if (_pfpTextureWrap != null)
|
|
{
|
|
var size = ImGuiHelpers.ScaledVector2(_pfpTextureWrap.Width, _pfpTextureWrap.Height);
|
|
var maxEdge = 160f * scale;
|
|
if (size.X > maxEdge || size.Y > maxEdge)
|
|
{
|
|
var ratio = MathF.Min(maxEdge / MathF.Max(size.X, 1f), maxEdge / MathF.Max(size.Y, 1f));
|
|
size *= ratio;
|
|
}
|
|
|
|
ImGui.Image(_pfpTextureWrap.Handle, size);
|
|
}
|
|
else
|
|
{
|
|
ImGui.PushStyleVar(ImGuiStyleVar.ChildRounding, 4f * scale);
|
|
if (ImGui.BeginChild("##GroupProfileImagePlaceholder", new Vector2(160f * scale, 160f * scale), true))
|
|
ImGui.TextColored(UIColors.Get("LightlessPurple"), "No profile picture uploaded.");
|
|
ImGui.EndChild();
|
|
ImGui.PopStyleVar();
|
|
}
|
|
|
|
ImGui.SameLine();
|
|
ImGui.BeginGroup();
|
|
ImGui.TextColored(UIColors.Get("LightlessBlue"), _groupInfo!.GroupAliasOrGID);
|
|
ImGui.TextDisabled($"ID: {_groupInfo.Group.GID}");
|
|
ImGui.TextDisabled($"Owner: {_groupInfo.Owner.AliasOrUID}");
|
|
ImGui.EndGroup();
|
|
|
|
ImGui.Dummy(new Vector2(0f, 4f * scale));
|
|
ImGui.PushStyleVar(ImGuiStyleVar.ChildRounding, 4f * scale);
|
|
if (ImGui.BeginChild("##GroupProfileDescriptionPreview", new Vector2(-1f, 120f * scale), true))
|
|
{
|
|
var hasDescription = !string.IsNullOrWhiteSpace(profile.Description);
|
|
ImGui.PushTextWrapPos(ImGui.GetCursorPosX() + ImGui.GetContentRegionAvail().X);
|
|
if (!hasDescription)
|
|
{
|
|
ImGui.TextDisabled("Syncshell has no description set.");
|
|
}
|
|
else if (!SeStringUtils.TryRenderSeStringMarkupAtCursor(profile.Description!))
|
|
{
|
|
UiSharedService.TextWrapped(profile.Description);
|
|
}
|
|
ImGui.PopTextWrapPos();
|
|
}
|
|
ImGui.EndChild();
|
|
ImGui.PopStyleVar();
|
|
|
|
}
|
|
|
|
private void DrawGroupProfileImageControls()
|
|
{
|
|
_uiSharedService.DrawNoteLine("# ", UIColors.Get("LightlessBlue"), "Profile pictures must be 512x512 and under 2 MiB.");
|
|
|
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.FileUpload, "Upload new profile picture"))
|
|
{
|
|
_fileDialogManager.OpenFileDialog("Select syncshell profile picture", _imageFileDialogFilter, (success, file) =>
|
|
{
|
|
if (!success || string.IsNullOrEmpty(file))
|
|
return;
|
|
_showProfileImageError = false;
|
|
_ = SubmitGroupProfilePicture(file);
|
|
});
|
|
}
|
|
UiSharedService.AttachToolTip("Select an image up to 512x512 pixels and <= 2 MiB (PNG/JPG/JPEG/WEBP/BMP).");
|
|
ImGui.SameLine();
|
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, "Clear profile picture"))
|
|
{
|
|
_ = ClearGroupProfilePicture();
|
|
}
|
|
UiSharedService.AttachToolTip("Remove the current profile picture from this syncshell.");
|
|
|
|
if (_showProfileImageError)
|
|
{
|
|
UiSharedService.ColorTextWrapped("Image must be no larger than 512x512 pixels and under 2 MiB.", ImGuiColors.DalamudRed);
|
|
}
|
|
}
|
|
|
|
private void DrawGroupProfileBannerControls()
|
|
{
|
|
_uiSharedService.DrawNoteLine("# ", UIColors.Get("LightlessBlue"), "Profile banners must be 840x260 and under 2 MiB.");
|
|
|
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.FileUpload, "Upload new profile banner"))
|
|
{
|
|
_fileDialogManager.OpenFileDialog("Select syncshell profile banner", _imageFileDialogFilter, (success, file) =>
|
|
{
|
|
if (!success || string.IsNullOrEmpty(file))
|
|
return;
|
|
_showBannerImageError = false;
|
|
_ = SubmitGroupProfileBanner(file);
|
|
});
|
|
}
|
|
UiSharedService.AttachToolTip("Select an image up to 840x260 pixels and <= 2 MiB (PNG/JPG/JPEG/WEBP/BMP).");
|
|
ImGui.SameLine();
|
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, "Clear profile banner"))
|
|
{
|
|
_ = ClearGroupProfileBanner();
|
|
}
|
|
UiSharedService.AttachToolTip("Remove the current profile banner.");
|
|
|
|
if (_showBannerImageError)
|
|
{
|
|
UiSharedService.ColorTextWrapped("Banner must be no larger than 840x260 pixels and under 2 MiB.", ImGuiColors.DalamudRed);
|
|
}
|
|
}
|
|
|
|
private void DrawGroupProfileDescriptionEditor()
|
|
{
|
|
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, new Vector2(6f, 4f) * ImGuiHelpers.GlobalScale);
|
|
var descriptionBoxSize = new Vector2(-1f, 160f * ImGuiHelpers.GlobalScale);
|
|
ImGui.InputTextMultiline("##GroupDescription", ref _descriptionText, 1500, descriptionBoxSize);
|
|
ImGui.PopStyleVar();
|
|
|
|
ImGui.TextDisabled($"{_descriptionText.Length}/1500 characters");
|
|
ImGui.SameLine();
|
|
ImGuiComponents.HelpMarker(DescriptionMacroTooltip);
|
|
|
|
bool changed = !string.Equals(_descriptionText, _profileDescription, StringComparison.Ordinal);
|
|
if (!changed)
|
|
ImGui.BeginDisabled();
|
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Save, "Save Description"))
|
|
{
|
|
_ = SubmitGroupDescription(_descriptionText);
|
|
}
|
|
UiSharedService.AttachToolTip("Apply the text above to the syncshell profile description.");
|
|
if (!changed)
|
|
ImGui.EndDisabled();
|
|
|
|
ImGui.SameLine();
|
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, "Clear Description"))
|
|
{
|
|
_ = SubmitGroupDescription(string.Empty);
|
|
}
|
|
UiSharedService.AttachToolTip("Remove the profile description.");
|
|
}
|
|
|
|
private void DrawGroupProfileTagsEditor(float scale)
|
|
{
|
|
DrawTagEditor(
|
|
scale,
|
|
contextPrefix: "group",
|
|
saveTooltip: "Apply the selected tags to this syncshell profile.",
|
|
submitAction: payload => SubmitGroupTagChanges(payload),
|
|
allowReorder: true,
|
|
sortPayloadBeforeSubmit: false,
|
|
onPayloadPrepared: payload =>
|
|
{
|
|
_tagEditorSelection.Clear();
|
|
if (payload.Length > 0)
|
|
_tagEditorSelection.AddRange(payload);
|
|
});
|
|
}
|
|
|
|
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;
|
|
|
|
bool changedDisabled = DrawCheckboxRow("Disable profile for viewers", _groupIsDisabled, out var newDisabled, "Temporarily hide this profile from members.");
|
|
if (changedDisabled)
|
|
_groupIsDisabled = newDisabled;
|
|
|
|
bool visibilityChanged = (_groupIsNsfw != _groupServerIsNsfw) || (_groupIsDisabled != _groupServerIsDisabled);
|
|
|
|
if (!visibilityChanged)
|
|
ImGui.BeginDisabled();
|
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Save, "Apply Visibility Changes"))
|
|
{
|
|
_ = SubmitGroupVisibilityChanges(_groupIsNsfw, _groupIsDisabled);
|
|
}
|
|
UiSharedService.AttachToolTip("Apply the visibility toggles above.");
|
|
if (!visibilityChanged)
|
|
ImGui.EndDisabled();
|
|
|
|
ImGui.SameLine();
|
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.SyncAlt, "Reset"))
|
|
{
|
|
_groupIsNsfw = _groupServerIsNsfw;
|
|
_groupIsDisabled = _groupServerIsDisabled;
|
|
}
|
|
}
|
|
|
|
private string? GetCurrentGroupProfileImageBase64()
|
|
{
|
|
if (_queuedProfileImage is not null && _queuedProfileImage.Length > 0)
|
|
return Convert.ToBase64String(_queuedProfileImage);
|
|
|
|
if (!string.IsNullOrWhiteSpace(_groupProfileData?.Base64ProfilePicture))
|
|
return _groupProfileData!.Base64ProfilePicture;
|
|
|
|
return _profileImage.Length > 0 ? Convert.ToBase64String(_profileImage) : null;
|
|
}
|
|
|
|
private string? GetCurrentGroupBannerBase64()
|
|
{
|
|
if (_queuedBannerImage is not null && _queuedBannerImage.Length > 0)
|
|
return Convert.ToBase64String(_queuedBannerImage);
|
|
|
|
if (!string.IsNullOrWhiteSpace(_groupProfileData?.Base64BannerPicture))
|
|
return _groupProfileData!.Base64BannerPicture;
|
|
|
|
return _bannerImage.Length > 0 ? Convert.ToBase64String(_bannerImage) : null;
|
|
}
|
|
|
|
private async Task SubmitGroupProfilePicture(string filePath)
|
|
{
|
|
if (_groupInfo is null)
|
|
return;
|
|
|
|
try
|
|
{
|
|
var fileContent = await File.ReadAllBytesAsync(filePath).ConfigureAwait(false);
|
|
var stream = new MemoryStream(fileContent);
|
|
await using (stream.ConfigureAwait(false))
|
|
{
|
|
var format = await Image.DetectFormatAsync(stream).ConfigureAwait(false);
|
|
if (!IsSupportedImageFormat(format))
|
|
{
|
|
_showProfileImageError = true;
|
|
return;
|
|
}
|
|
|
|
using var image = Image.Load<Rgba32>(fileContent);
|
|
if (image.Width > 512 || image.Height > 512 || fileContent.Length > 2000 * 1024)
|
|
{
|
|
_showProfileImageError = true;
|
|
return;
|
|
}
|
|
|
|
await _apiController.GroupSetProfile(new GroupProfileDto(
|
|
_groupInfo.Group,
|
|
Description: null,
|
|
Tags: null,
|
|
PictureBase64: Convert.ToBase64String(fileContent),
|
|
BannerBase64: null,
|
|
IsNsfw: null,
|
|
IsDisabled: null)).ConfigureAwait(false);
|
|
|
|
_showProfileImageError = false;
|
|
_queuedProfileImage = fileContent;
|
|
Mediator.Publish(new ClearProfileGroupDataMessage(_groupInfo.Group));
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Failed to upload syncshell profile picture.");
|
|
}
|
|
}
|
|
|
|
private async Task ClearGroupProfilePicture()
|
|
{
|
|
if (_groupInfo is null)
|
|
return;
|
|
|
|
try
|
|
{
|
|
await _apiController.GroupSetProfile(new GroupProfileDto(
|
|
_groupInfo.Group,
|
|
Description: null,
|
|
Tags: null,
|
|
PictureBase64: null,
|
|
BannerBase64: null,
|
|
IsNsfw: null,
|
|
IsDisabled: null)).ConfigureAwait(false);
|
|
|
|
_queuedProfileImage = Array.Empty<byte>();
|
|
Mediator.Publish(new ClearProfileGroupDataMessage(_groupInfo.Group));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Failed to clear syncshell profile picture.");
|
|
}
|
|
}
|
|
|
|
private async Task SubmitGroupProfileBanner(string filePath)
|
|
{
|
|
if (_groupInfo is null)
|
|
return;
|
|
|
|
try
|
|
{
|
|
var fileContent = await File.ReadAllBytesAsync(filePath).ConfigureAwait(false);
|
|
var stream = new MemoryStream(fileContent);
|
|
await using (stream.ConfigureAwait(false))
|
|
{
|
|
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));
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Failed to upload syncshell profile banner.");
|
|
}
|
|
}
|
|
|
|
private async Task ClearGroupProfileBanner()
|
|
{
|
|
if (_groupInfo is null)
|
|
return;
|
|
|
|
try
|
|
{
|
|
await _apiController.GroupSetProfile(new GroupProfileDto(
|
|
_groupInfo.Group,
|
|
Description: null,
|
|
Tags: null,
|
|
PictureBase64: null,
|
|
BannerBase64: null,
|
|
IsNsfw: null,
|
|
IsDisabled: null)).ConfigureAwait(false);
|
|
|
|
_queuedBannerImage = Array.Empty<byte>();
|
|
Mediator.Publish(new ClearProfileGroupDataMessage(_groupInfo.Group));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Failed to clear syncshell profile banner.");
|
|
}
|
|
}
|
|
|
|
private async Task SubmitGroupDescription(string description)
|
|
{
|
|
if (_groupInfo is null)
|
|
return;
|
|
|
|
try
|
|
{
|
|
await _apiController.GroupSetProfile(new GroupProfileDto(
|
|
_groupInfo.Group,
|
|
Description: description,
|
|
Tags: null,
|
|
PictureBase64: GetCurrentGroupProfileImageBase64(),
|
|
BannerBase64: GetCurrentGroupBannerBase64(),
|
|
IsNsfw: null,
|
|
IsDisabled: null)).ConfigureAwait(false);
|
|
|
|
_profileDescription = description;
|
|
Mediator.Publish(new ClearProfileGroupDataMessage(_groupInfo.Group));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Failed to update syncshell profile description.");
|
|
}
|
|
}
|
|
|
|
private void EnsureGroupVisibilityStateInitialised()
|
|
{
|
|
if (_groupInfo == null || _groupVisibilityInitialized)
|
|
return;
|
|
|
|
_groupIsNsfw = _groupServerIsNsfw;
|
|
_groupIsDisabled = _groupServerIsDisabled;
|
|
_groupVisibilityInitialized = true;
|
|
}
|
|
|
|
private async Task SubmitGroupTagChanges(int[] payload)
|
|
{
|
|
if (_groupInfo is null)
|
|
return;
|
|
|
|
try
|
|
{
|
|
await _apiController.GroupSetProfile(new GroupProfileDto(
|
|
_groupInfo.Group,
|
|
Description: null,
|
|
Tags: payload,
|
|
PictureBase64: GetCurrentGroupProfileImageBase64(),
|
|
BannerBase64: GetCurrentGroupBannerBase64(),
|
|
IsNsfw: null,
|
|
IsDisabled: null)).ConfigureAwait(false);
|
|
|
|
_profileTagIds = payload.Length == 0 ? [] : [.. payload];
|
|
Mediator.Publish(new ClearProfileGroupDataMessage(_groupInfo.Group));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Failed to update syncshell profile tags.");
|
|
}
|
|
}
|
|
|
|
private async Task SubmitGroupVisibilityChanges(bool isNsfw, bool isDisabled)
|
|
{
|
|
if (_groupInfo is null)
|
|
return;
|
|
|
|
try
|
|
{
|
|
await _apiController.GroupSetProfile(new GroupProfileDto(
|
|
_groupInfo.Group,
|
|
Description: null,
|
|
Tags: null,
|
|
PictureBase64: GetCurrentGroupProfileImageBase64(),
|
|
BannerBase64: GetCurrentGroupBannerBase64(),
|
|
IsNsfw: isNsfw,
|
|
IsDisabled: isDisabled)).ConfigureAwait(false);
|
|
|
|
_groupServerIsNsfw = isNsfw;
|
|
_groupServerIsDisabled = isDisabled;
|
|
Mediator.Publish(new ClearProfileGroupDataMessage(_groupInfo.Group));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Failed to update syncshell profile visibility.");
|
|
}
|
|
}
|
|
|
|
private void ApplyQueuedGroupProfileImage()
|
|
{
|
|
if (_queuedProfileImage is null)
|
|
return;
|
|
|
|
_profileImage = _queuedProfileImage;
|
|
_pfpTextureWrap?.Dispose();
|
|
_pfpTextureWrap = _profileImage.Length > 0 ? _uiSharedService.LoadImage(_profileImage) : null;
|
|
_queuedProfileImage = null;
|
|
}
|
|
|
|
private void ApplyQueuedGroupBannerImage()
|
|
{
|
|
if (_queuedBannerImage is null)
|
|
return;
|
|
|
|
_bannerImage = _queuedBannerImage;
|
|
_bannerTextureWrap?.Dispose();
|
|
_bannerTextureWrap = _bannerImage.Length > 0 ? _uiSharedService.LoadImage(_bannerImage) : null;
|
|
_queuedBannerImage = null;
|
|
}
|
|
|
|
private void SyncGroupProfileState(LightlessGroupProfileData profile, bool resetSelection)
|
|
{
|
|
if (!_profileImage.SequenceEqual(profile.ProfileImageData.Value))
|
|
{
|
|
_profileImage = profile.ProfileImageData.Value;
|
|
_pfpTextureWrap?.Dispose();
|
|
_pfpTextureWrap = _profileImage.Length > 0 ? _uiSharedService.LoadImage(_profileImage) : null;
|
|
}
|
|
|
|
if (!_bannerImage.SequenceEqual(profile.BannerImageData.Value))
|
|
{
|
|
_bannerImage = profile.BannerImageData.Value;
|
|
_bannerTextureWrap?.Dispose();
|
|
_bannerTextureWrap = _bannerImage.Length > 0 ? _uiSharedService.LoadImage(_bannerImage) : null;
|
|
}
|
|
|
|
if (!string.Equals(_profileDescription, profile.Description, StringComparison.Ordinal))
|
|
{
|
|
_profileDescription = profile.Description;
|
|
_descriptionText = _profileDescription;
|
|
}
|
|
|
|
var tags = profile.Tags ?? Array.Empty<int>();
|
|
if (!TagsEqual(tags, _profileTagIds))
|
|
{
|
|
_profileTagIds = tags.Count == 0 ? Array.Empty<int>() : tags.ToArray();
|
|
if (resetSelection)
|
|
{
|
|
_tagEditorSelection.Clear();
|
|
if (_profileTagIds.Length > 0)
|
|
_tagEditorSelection.AddRange(_profileTagIds);
|
|
}
|
|
}
|
|
|
|
_groupServerIsNsfw = profile.IsNsfw;
|
|
_groupServerIsDisabled = profile.IsDisabled;
|
|
|
|
if (!_groupVisibilityInitialized)
|
|
{
|
|
_groupIsNsfw = _groupServerIsNsfw;
|
|
_groupIsDisabled = _groupServerIsDisabled;
|
|
_groupVisibilityInitialized = true;
|
|
}
|
|
}
|
|
}
|
|
|