Added button to jump to template editing from profile tab, made TemplateManager IDisposable
This commit is contained in:
33
CustomizePlus/Templates/Events/TemplateEditorEvent.cs
Normal file
33
CustomizePlus/Templates/Events/TemplateEditorEvent.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
using CustomizePlus.Templates.Data;
|
||||||
|
using OtterGui.Classes;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CustomizePlus.Templates.Events;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triggered when something related to template editor happens
|
||||||
|
/// </summary>
|
||||||
|
public class TemplateEditorEvent() : EventWrapper<TemplateEditorEvent.Type, Template?, TemplateEditorEvent.Priority>(nameof(TemplateEditorEvent))
|
||||||
|
{
|
||||||
|
public enum Type
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Called when something requests editor to be enabled.
|
||||||
|
/// </summary>
|
||||||
|
EditorEnableRequested,
|
||||||
|
/// <summary>
|
||||||
|
/// Called when something requests editor to be enabled. Stage 2 - logic after tab has been switched.
|
||||||
|
/// </summary>
|
||||||
|
EditorEnableRequestedStage2
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Priority
|
||||||
|
{
|
||||||
|
MainWindow = -1,
|
||||||
|
TemplatePanel
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ using System.Linq;
|
|||||||
|
|
||||||
namespace CustomizePlus.Templates;
|
namespace CustomizePlus.Templates;
|
||||||
|
|
||||||
public class TemplateManager
|
public class TemplateManager : IDisposable
|
||||||
{
|
{
|
||||||
private readonly SaveService _saveService;
|
private readonly SaveService _saveService;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
@@ -41,6 +41,11 @@ public class TemplateManager
|
|||||||
LoadTemplates();
|
LoadTemplates();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_reloadEvent.Unsubscribe(OnReload);
|
||||||
|
}
|
||||||
|
|
||||||
public Template? GetTemplate(Guid templateId) => _templates.FirstOrDefault(d => d.UniqueId == templateId);
|
public Template? GetTemplate(Guid templateId) => _templates.FirstOrDefault(d => d.UniqueId == templateId);
|
||||||
|
|
||||||
public void LoadTemplates()
|
public void LoadTemplates()
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ using CustomizePlus.Templates;
|
|||||||
using ECommons.ImGuiMethods;
|
using ECommons.ImGuiMethods;
|
||||||
using static System.Windows.Forms.AxHost;
|
using static System.Windows.Forms.AxHost;
|
||||||
using Dalamud.Interface.Colors;
|
using Dalamud.Interface.Colors;
|
||||||
|
using CustomizePlus.Templates.Events;
|
||||||
|
using CustomizePlus.Templates.Data;
|
||||||
|
using ECommons.Schedulers;
|
||||||
|
|
||||||
namespace CustomizePlus.UI.Windows.MainWindow;
|
namespace CustomizePlus.UI.Windows.MainWindow;
|
||||||
|
|
||||||
@@ -34,6 +37,15 @@ public class MainWindow : Window, IDisposable
|
|||||||
private readonly PluginConfiguration _configuration;
|
private readonly PluginConfiguration _configuration;
|
||||||
private readonly HookingService _hookingService;
|
private readonly HookingService _hookingService;
|
||||||
|
|
||||||
|
private readonly TemplateEditorEvent _templateEditorEvent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used to force the main window to switch to specific tab
|
||||||
|
/// </summary>
|
||||||
|
private string? _switchToTab = null;
|
||||||
|
|
||||||
|
private Action? _actionAfterTabSwitch = null;
|
||||||
|
|
||||||
public MainWindow(
|
public MainWindow(
|
||||||
DalamudPluginInterface pluginInterface,
|
DalamudPluginInterface pluginInterface,
|
||||||
SettingsTab settingsTab,
|
SettingsTab settingsTab,
|
||||||
@@ -45,7 +57,8 @@ public class MainWindow : Window, IDisposable
|
|||||||
PluginStateBlock pluginStateBlock,
|
PluginStateBlock pluginStateBlock,
|
||||||
TemplateEditorManager templateEditorManager,
|
TemplateEditorManager templateEditorManager,
|
||||||
PluginConfiguration configuration,
|
PluginConfiguration configuration,
|
||||||
HookingService hookingService
|
HookingService hookingService,
|
||||||
|
TemplateEditorEvent templateEditorEvent
|
||||||
) : base($"Customize+ v{Plugin.Version}###CPlusMainWindow")
|
) : base($"Customize+ v{Plugin.Version}###CPlusMainWindow")
|
||||||
{
|
{
|
||||||
_settingsTab = settingsTab;
|
_settingsTab = settingsTab;
|
||||||
@@ -61,6 +74,10 @@ public class MainWindow : Window, IDisposable
|
|||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
_hookingService = hookingService;
|
_hookingService = hookingService;
|
||||||
|
|
||||||
|
_templateEditorEvent = templateEditorEvent;
|
||||||
|
|
||||||
|
_templateEditorEvent.Subscribe(OnTemplateEditorEvent, TemplateEditorEvent.Priority.MainWindow);
|
||||||
|
|
||||||
pluginInterface.UiBuilder.DisableGposeUiHide = true;
|
pluginInterface.UiBuilder.DisableGposeUiHide = true;
|
||||||
SizeConstraints = new WindowSizeConstraints()
|
SizeConstraints = new WindowSizeConstraints()
|
||||||
{
|
{
|
||||||
@@ -73,7 +90,7 @@ public class MainWindow : Window, IDisposable
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
//throw new NotImplementedException();
|
_templateEditorEvent.Unsubscribe(OnTemplateEditorEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Draw()
|
public override void Draw()
|
||||||
@@ -83,13 +100,21 @@ public class MainWindow : Window, IDisposable
|
|||||||
using (var disabled = ImRaii.Disabled(_hookingService.RenderHookFailed || _hookingService.MovementHookFailed))
|
using (var disabled = ImRaii.Disabled(_hookingService.RenderHookFailed || _hookingService.MovementHookFailed))
|
||||||
{
|
{
|
||||||
LockWindowClosureIfNeeded();
|
LockWindowClosureIfNeeded();
|
||||||
ImGuiEx.EzTabBar("##tabs", [
|
ImGuiEx.EzTabBar("##tabs", null, _switchToTab, [
|
||||||
("Settings", _settingsTab.Draw, null, true),
|
("Settings", _settingsTab.Draw, null, true),
|
||||||
("Templates", _templatesTab.Draw, null, true),
|
("Templates", _templatesTab.Draw, null, true),
|
||||||
("Profiles", _profilesTab.Draw, null, true),
|
("Profiles", _profilesTab.Draw, null, true),
|
||||||
(_configuration.DebuggingModeEnabled ? "IPC Test" : null, _ipcTestTab.Draw, ImGuiColors.DalamudGrey, true),
|
(_configuration.DebuggingModeEnabled ? "IPC Test" : null, _ipcTestTab.Draw, ImGuiColors.DalamudGrey, true),
|
||||||
(_configuration.DebuggingModeEnabled ? "State monitoring" : null, _stateMonitoringTab.Draw, ImGuiColors.DalamudGrey, true),
|
(_configuration.DebuggingModeEnabled ? "State monitoring" : null, _stateMonitoringTab.Draw, ImGuiColors.DalamudGrey, true),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
_switchToTab = null;
|
||||||
|
|
||||||
|
if (_actionAfterTabSwitch != null)
|
||||||
|
{
|
||||||
|
_actionAfterTabSwitch();
|
||||||
|
_actionAfterTabSwitch = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_pluginStateBlock.Draw(yPos);
|
_pluginStateBlock.Draw(yPos);
|
||||||
@@ -108,4 +133,24 @@ public class MainWindow : Window, IDisposable
|
|||||||
RespectCloseHotkey = true;
|
RespectCloseHotkey = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnTemplateEditorEvent(TemplateEditorEvent.Type type, Template? template)
|
||||||
|
{
|
||||||
|
if (type != TemplateEditorEvent.Type.EditorEnableRequested)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (template == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!template.IsWriteProtected && !_templateEditorManager.IsEditorActive)
|
||||||
|
{
|
||||||
|
new TickScheduler(() =>
|
||||||
|
{
|
||||||
|
_switchToTab = "Templates";
|
||||||
|
|
||||||
|
//To make sure the tab has switched, ugly but imgui is shit and I don't trust it.
|
||||||
|
_actionAfterTabSwitch = () => { _templateEditorEvent.Invoke(TemplateEditorEvent.Type.EditorEnableRequestedStage2, template); };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,15 +7,12 @@ using System;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using CustomizePlus.Profiles;
|
using CustomizePlus.Profiles;
|
||||||
using CustomizePlus.Game.Services;
|
|
||||||
using CustomizePlus.Configuration.Data;
|
using CustomizePlus.Configuration.Data;
|
||||||
using CustomizePlus.Profiles.Data;
|
using CustomizePlus.Profiles.Data;
|
||||||
using CustomizePlus.UI.Windows.Controls;
|
using CustomizePlus.UI.Windows.Controls;
|
||||||
using CustomizePlus.Templates;
|
using CustomizePlus.Templates;
|
||||||
using CustomizePlus.Core.Helpers;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
using CustomizePlus.Core.Data;
|
using CustomizePlus.Core.Data;
|
||||||
using static System.Windows.Forms.VisualStyles.VisualStyleElement;
|
using CustomizePlus.Templates.Events;
|
||||||
|
|
||||||
namespace CustomizePlus.UI.Windows.MainWindow.Tabs.Profiles;
|
namespace CustomizePlus.UI.Windows.MainWindow.Tabs.Profiles;
|
||||||
|
|
||||||
@@ -26,6 +23,7 @@ public class ProfilePanel
|
|||||||
private readonly PluginConfiguration _configuration;
|
private readonly PluginConfiguration _configuration;
|
||||||
private readonly TemplateCombo _templateCombo;
|
private readonly TemplateCombo _templateCombo;
|
||||||
private readonly TemplateEditorManager _templateEditorManager;
|
private readonly TemplateEditorManager _templateEditorManager;
|
||||||
|
private readonly TemplateEditorEvent _templateEditorEvent;
|
||||||
|
|
||||||
private string? _newName;
|
private string? _newName;
|
||||||
private string? _newCharacterName;
|
private string? _newCharacterName;
|
||||||
@@ -43,13 +41,15 @@ public class ProfilePanel
|
|||||||
ProfileManager manager,
|
ProfileManager manager,
|
||||||
PluginConfiguration configuration,
|
PluginConfiguration configuration,
|
||||||
TemplateCombo templateCombo,
|
TemplateCombo templateCombo,
|
||||||
TemplateEditorManager templateEditorManager)
|
TemplateEditorManager templateEditorManager,
|
||||||
|
TemplateEditorEvent templateEditorEvent)
|
||||||
{
|
{
|
||||||
_selector = selector;
|
_selector = selector;
|
||||||
_manager = manager;
|
_manager = manager;
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
_templateCombo = templateCombo;
|
_templateCombo = templateCombo;
|
||||||
_templateEditorManager = templateEditorManager;
|
_templateEditorManager = templateEditorManager;
|
||||||
|
_templateEditorEvent = templateEditorEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Draw()
|
public void Draw()
|
||||||
@@ -247,7 +247,7 @@ public class ProfilePanel
|
|||||||
|
|
||||||
private void DrawTemplateArea()
|
private void DrawTemplateArea()
|
||||||
{
|
{
|
||||||
using var table = ImRaii.Table("SetTable", 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollX | ImGuiTableFlags.ScrollY);
|
using var table = ImRaii.Table("SetTable", 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollX | ImGuiTableFlags.ScrollY);
|
||||||
if (!table)
|
if (!table)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -256,6 +256,8 @@ public class ProfilePanel
|
|||||||
|
|
||||||
ImGui.TableSetupColumn("Template", ImGuiTableColumnFlags.WidthFixed, 220 * ImGuiHelpers.GlobalScale);
|
ImGui.TableSetupColumn("Template", ImGuiTableColumnFlags.WidthFixed, 220 * ImGuiHelpers.GlobalScale);
|
||||||
|
|
||||||
|
ImGui.TableSetupColumn("##editbtn", ImGuiTableColumnFlags.WidthFixed, 120 * ImGuiHelpers.GlobalScale);
|
||||||
|
|
||||||
ImGui.TableHeadersRow();
|
ImGui.TableHeadersRow();
|
||||||
|
|
||||||
//warn: .ToList() might be performance critical at some point
|
//warn: .ToList() might be performance critical at some point
|
||||||
@@ -277,6 +279,24 @@ public class ProfilePanel
|
|||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
_templateCombo.Draw(_selector.Selected!, template, idx);
|
_templateCombo.Draw(_selector.Selected!, template, idx);
|
||||||
DrawDragDrop(_selector.Selected!, idx);
|
DrawDragDrop(_selector.Selected!, idx);
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
|
||||||
|
var disabledCondition = _templateEditorManager.IsEditorActive || template.IsWriteProtected;
|
||||||
|
using (var disabled = ImRaii.Disabled(disabledCondition))
|
||||||
|
{
|
||||||
|
if (ImGui.Button("Open in editor"))
|
||||||
|
_templateEditorEvent.Invoke(TemplateEditorEvent.Type.EditorEnableRequested, template);
|
||||||
|
ImGuiUtil.HoverTooltip("Open this template in the template editor");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(disabledCondition)
|
||||||
|
{
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.Text, Constants.Colors.Warning);
|
||||||
|
ImGuiUtil.PrintIcon(FontAwesomeIcon.ExclamationTriangle);
|
||||||
|
ImGui.PopStyleColor();
|
||||||
|
ImGuiUtil.HoverTooltip("Can not be edited because this template is either write protected or template editor is already enabled.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
|
|||||||
@@ -15,10 +15,12 @@ using CustomizePlus.Configuration.Data;
|
|||||||
using CustomizePlus.Core.Helpers;
|
using CustomizePlus.Core.Helpers;
|
||||||
using CustomizePlus.Templates.Data;
|
using CustomizePlus.Templates.Data;
|
||||||
using OtterGui.Log;
|
using OtterGui.Log;
|
||||||
|
using CustomizePlus.Templates.Events;
|
||||||
|
using ECommons.Schedulers;
|
||||||
|
|
||||||
namespace CustomizePlus.UI.Windows.MainWindow.Tabs.Templates;
|
namespace CustomizePlus.UI.Windows.MainWindow.Tabs.Templates;
|
||||||
|
|
||||||
public class TemplatePanel
|
public class TemplatePanel : IDisposable
|
||||||
{
|
{
|
||||||
private readonly TemplateFileSystemSelector _selector;
|
private readonly TemplateFileSystemSelector _selector;
|
||||||
private readonly TemplateManager _manager;
|
private readonly TemplateManager _manager;
|
||||||
@@ -28,9 +30,16 @@ public class TemplatePanel
|
|||||||
private readonly PopupSystem _popupSystem;
|
private readonly PopupSystem _popupSystem;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
private readonly TemplateEditorEvent _editorEvent;
|
||||||
|
|
||||||
private string? _newName;
|
private string? _newName;
|
||||||
private Template? _changedTemplate;
|
private Template? _changedTemplate;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set to true if we received OnEditorEvent EditorEnableRequested and waiting for selector value to be changed.
|
||||||
|
/// </summary>
|
||||||
|
private bool _isEditorEnablePending = false;
|
||||||
|
|
||||||
private string SelectionName
|
private string SelectionName
|
||||||
=> _selector.Selected == null ? "No Selection" : _selector.IncognitoMode ? _selector.Selected.Incognito : _selector.Selected.Name.Text;
|
=> _selector.Selected == null ? "No Selection" : _selector.IncognitoMode ? _selector.Selected.Incognito : _selector.Selected.Name.Text;
|
||||||
|
|
||||||
@@ -41,7 +50,8 @@ public class TemplatePanel
|
|||||||
PluginConfiguration configuration,
|
PluginConfiguration configuration,
|
||||||
MessageService messageService,
|
MessageService messageService,
|
||||||
PopupSystem popupSystem,
|
PopupSystem popupSystem,
|
||||||
Logger logger)
|
Logger logger,
|
||||||
|
TemplateEditorEvent editorEvent)
|
||||||
{
|
{
|
||||||
_selector = selector;
|
_selector = selector;
|
||||||
_manager = manager;
|
_manager = manager;
|
||||||
@@ -51,6 +61,11 @@ public class TemplatePanel
|
|||||||
_popupSystem = popupSystem;
|
_popupSystem = popupSystem;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
|
||||||
|
_editorEvent = editorEvent;
|
||||||
|
|
||||||
|
_editorEvent.Subscribe(OnEditorEvent, TemplateEditorEvent.Priority.TemplatePanel);
|
||||||
|
|
||||||
|
_selector.SelectionChanged += SelectorSelectionChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Draw()
|
public void Draw()
|
||||||
@@ -67,6 +82,11 @@ public class TemplatePanel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_editorEvent.Unsubscribe(OnEditorEvent);
|
||||||
|
}
|
||||||
|
|
||||||
private HeaderDrawer.Button LockButton()
|
private HeaderDrawer.Button LockButton()
|
||||||
=> _selector.Selected == null
|
=> _selector.Selected == null
|
||||||
? HeaderDrawer.Button.Invisible
|
? HeaderDrawer.Button.Invisible
|
||||||
@@ -167,17 +187,23 @@ public class TemplatePanel
|
|||||||
|
|
||||||
private void DrawEditorToggle()
|
private void DrawEditorToggle()
|
||||||
{
|
{
|
||||||
|
(bool isEditorAllowed, bool isEditorActive) = CanToggleEditor();
|
||||||
|
|
||||||
if (ImGuiUtil.DrawDisabledButton($"{(_boneEditor.IsEditorActive ? "Finish" : "Start")} bone editing", Vector2.Zero,
|
if (ImGuiUtil.DrawDisabledButton($"{(_boneEditor.IsEditorActive ? "Finish" : "Start")} bone editing", Vector2.Zero,
|
||||||
"Toggle the bone editor for this template",
|
"Toggle the bone editor for this template", !isEditorAllowed))
|
||||||
(_selector.Selected?.IsWriteProtected ?? true) || !_configuration.PluginEnabled))
|
|
||||||
{
|
{
|
||||||
if (!_boneEditor.IsEditorActive)
|
if (!isEditorActive)
|
||||||
_boneEditor.EnableEditor(_selector.Selected!);
|
_boneEditor.EnableEditor(_selector.Selected!);
|
||||||
else
|
else
|
||||||
_boneEditor.DisableEditor();
|
_boneEditor.DisableEditor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private (bool isEditorAllowed, bool isEditorActive) CanToggleEditor()
|
||||||
|
{
|
||||||
|
return ((!_selector.Selected?.IsWriteProtected ?? false) || _configuration.PluginEnabled, _boneEditor.IsEditorActive);
|
||||||
|
}
|
||||||
|
|
||||||
private void DrawBasicSettings()
|
private void DrawBasicSettings()
|
||||||
{
|
{
|
||||||
using (var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(0, 0.5f)))
|
using (var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(0, 0.5f)))
|
||||||
@@ -214,11 +240,6 @@ public class TemplatePanel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*private void SetFromClipboard()
|
|
||||||
{
|
|
||||||
|
|
||||||
}*/
|
|
||||||
|
|
||||||
private void ExportToClipboard()
|
private void ExportToClipboard()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -232,4 +253,38 @@ public class TemplatePanel
|
|||||||
_popupSystem.ShowPopup(PopupSystem.Messages.ActionError);
|
_popupSystem.ShowPopup(PopupSystem.Messages.ActionError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void SelectorSelectionChanged(Template? oldSelection, Template? newSelection, in TemplateFileSystemSelector.TemplateState state)
|
||||||
|
{
|
||||||
|
if (!_isEditorEnablePending)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_isEditorEnablePending = false;
|
||||||
|
|
||||||
|
_boneEditor.EnableEditor(_selector.Selected!);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEditorEvent(TemplateEditorEvent.Type type, Template? template)
|
||||||
|
{
|
||||||
|
if (type != TemplateEditorEvent.Type.EditorEnableRequestedStage2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(template == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
(bool isEditorAllowed, bool isEditorActive) = CanToggleEditor();
|
||||||
|
|
||||||
|
if (!isEditorAllowed || isEditorActive)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(_selector.Selected != template)
|
||||||
|
{
|
||||||
|
_selector.SelectByValue(template);
|
||||||
|
|
||||||
|
_isEditorEnablePending = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
_boneEditor.EnableEditor(_selector.Selected!);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user