Better notes and bans (#14228)
Co-authored-by: Chief-Engineer <119664036+Chief-Engineer@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
using Content.Client.Eui;
|
||||
using Content.Shared.Administration.Notes;
|
||||
using Content.Shared.Eui;
|
||||
using JetBrains.Annotations;
|
||||
using static Content.Shared.Administration.Notes.AdminMessageEuiMsg;
|
||||
|
||||
namespace Content.Client.Administration.UI.AdminRemarks;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class AdminMessageEui : BaseEui
|
||||
{
|
||||
private readonly AdminMessagePopupWindow _popup;
|
||||
|
||||
public AdminMessageEui()
|
||||
{
|
||||
_popup = new AdminMessagePopupWindow();
|
||||
_popup.OnAcceptPressed += () => SendMessage(new Accept());
|
||||
_popup.OnDismissPressed += () => SendMessage(new Dismiss());
|
||||
_popup.OnClose += () => SendMessage(new CloseEuiMessage());
|
||||
}
|
||||
|
||||
public override void HandleState(EuiStateBase state)
|
||||
{
|
||||
if (state is not AdminMessageEuiState s)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_popup.SetMessage(s.Message);
|
||||
_popup.SetDetails(s.AdminName, s.AddedOn);
|
||||
_popup.Timer = s.Time;
|
||||
}
|
||||
|
||||
public override void Opened()
|
||||
{
|
||||
_popup.OpenCentered();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<ui:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
VerticalExpand="True" HorizontalExpand="True"
|
||||
Title="{Loc admin-notes-message-window-title}"
|
||||
MinSize="600 170">
|
||||
<PanelContainer VerticalExpand="True" HorizontalExpand="True">
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxFlat BackgroundColor="#25252A" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<ScrollContainer HScrollEnabled="False" VerticalExpand="True" HorizontalExpand="True" Margin="4">
|
||||
<BoxContainer Orientation="Vertical" SeparationOverride="10" VerticalAlignment="Bottom">
|
||||
<Label Name="AdminLabel" Text="Loading..." />
|
||||
<RichTextLabel Name="MessageLabel" />
|
||||
<Label Name="WaitLabel" />
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Button Name="DismissButton"
|
||||
Text="{Loc 'admin-notes-message-dismiss'}" />
|
||||
<Button Name="AcceptButton"
|
||||
Text="{Loc 'admin-notes-message-accept'}"
|
||||
Disabled="True" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
</PanelContainer>
|
||||
</ui:FancyWindow>
|
||||
@@ -0,0 +1,75 @@
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Administration.UI.AdminRemarks;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class AdminMessagePopupWindow : FancyWindow
|
||||
{
|
||||
private float _timer = float.MaxValue;
|
||||
public float Timer
|
||||
{
|
||||
get => _timer;
|
||||
set
|
||||
{
|
||||
WaitLabel.Text = Loc.GetString("admin-notes-message-wait", ("time", MathF.Floor(value)));
|
||||
_timer = value;
|
||||
}
|
||||
}
|
||||
|
||||
public event Action? OnDismissPressed;
|
||||
public event Action? OnAcceptPressed;
|
||||
|
||||
public AdminMessagePopupWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
AcceptButton.OnPressed += OnAcceptButtonPressed;
|
||||
DismissButton.OnPressed += OnDismissButtonPressed;
|
||||
}
|
||||
|
||||
public void SetMessage(string message)
|
||||
{
|
||||
MessageLabel.SetMessage(message);
|
||||
}
|
||||
|
||||
public void SetDetails(string adminName, DateTime addedOn)
|
||||
{
|
||||
AdminLabel.Text = Loc.GetString("admin-notes-message-admin", ("admin", adminName), ("date", addedOn));
|
||||
}
|
||||
|
||||
private void OnDismissButtonPressed(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
OnDismissPressed?.Invoke();
|
||||
Close();
|
||||
}
|
||||
|
||||
private void OnAcceptButtonPressed(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
OnAcceptPressed?.Invoke();
|
||||
Close();
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
if (!AcceptButton.Disabled)
|
||||
return;
|
||||
|
||||
if (Timer > 0.0)
|
||||
{
|
||||
if (Timer - args.DeltaSeconds < 0)
|
||||
Timer = 0;
|
||||
else
|
||||
Timer -= args.DeltaSeconds;
|
||||
}
|
||||
else
|
||||
{
|
||||
AcceptButton.Disabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<ui:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
VerticalExpand="True" HorizontalExpand="True"
|
||||
Title="{Loc admin-remarks-title}"
|
||||
SetSize="600 400">
|
||||
<PanelContainer>
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxFlat BackgroundColor="#25252A" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<BoxContainer Orientation="Vertical" Margin="4">
|
||||
<ScrollContainer VerticalExpand="True" HorizontalExpand="True" HScrollEnabled="False">
|
||||
<BoxContainer Orientation="Vertical" Name="NotesContainer" Access="Public" VerticalExpand="True" />
|
||||
</ScrollContainer>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</ui:FancyWindow>
|
||||
@@ -0,0 +1,49 @@
|
||||
using System.Linq;
|
||||
using Content.Client.Administration.UI.Notes;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Administration.Notes;
|
||||
using Content.Shared.Database;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.Administration.UI.AdminRemarks;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class AdminRemarksWindow : FancyWindow
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystem = default!;
|
||||
private readonly SpriteSystem _sprites;
|
||||
private readonly Dictionary<(int, NoteType), AdminNotesLine> _inputs = new();
|
||||
|
||||
public AdminRemarksWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
_sprites = _entitySystem.GetEntitySystem<SpriteSystem>();
|
||||
}
|
||||
|
||||
public void SetNotes(Dictionary<(int, NoteType), SharedAdminNote> notes)
|
||||
{
|
||||
foreach (var (id, input) in _inputs)
|
||||
{
|
||||
if (notes.ContainsKey(id))
|
||||
continue;
|
||||
NotesContainer.RemoveChild(input);
|
||||
_inputs.Remove(id);
|
||||
}
|
||||
|
||||
foreach (var note in notes.Values.OrderByDescending(note => note.CreatedAt))
|
||||
{
|
||||
if (_inputs.TryGetValue((note.Id, note.NoteType), out var input))
|
||||
{
|
||||
input.UpdateNote(note);
|
||||
continue;
|
||||
}
|
||||
|
||||
input = new AdminNotesLine(_sprites, note);
|
||||
NotesContainer.AddChild(input);
|
||||
_inputs[(note.Id, note.NoteType)] = input;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using Content.Client.Administration.UI.Notes;
|
||||
using Content.Client.Eui;
|
||||
using Content.Shared.Administration.Notes;
|
||||
using Content.Shared.Eui;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Client.Administration.UI.AdminRemarks;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class UserNotesEui : BaseEui
|
||||
{
|
||||
public UserNotesEui()
|
||||
{
|
||||
NoteWindow = new AdminRemarksWindow();
|
||||
NoteWindow.OnClose += () => SendMessage(new CloseEuiMessage());
|
||||
}
|
||||
|
||||
private AdminRemarksWindow NoteWindow { get; }
|
||||
|
||||
public override void HandleState(EuiStateBase state)
|
||||
{
|
||||
if (state is not UserNotesEuiState s)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NoteWindow.SetNotes(s.Notes);
|
||||
}
|
||||
|
||||
public override void Opened()
|
||||
{
|
||||
NoteWindow.OpenCentered();
|
||||
}
|
||||
}
|
||||
49
Content.Client/Administration/UI/BanPanel/BanPanel.xaml
Normal file
49
Content.Client/Administration/UI/BanPanel/BanPanel.xaml
Normal file
@@ -0,0 +1,49 @@
|
||||
<DefaultWindow
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
Title="{Loc ban-panel-title}" MinSize="350 500">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<TabContainer Name="Tabs" VerticalExpand="True">
|
||||
<!-- Basic info -->
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
||||
<BoxContainer Orientation="Horizontal" Margin="2">
|
||||
<CheckBox Name="PlayerCheckbox" MinWidth="100" Text="{Loc ban-panel-player}" Pressed="True" />
|
||||
<Control MinWidth="50" />
|
||||
<LineEdit Name="PlayerNameLine" MinWidth="100" HorizontalExpand="True" PlaceHolder="{Loc ban-panel-player}" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" Margin="2">
|
||||
<CheckBox Name="IpCheckbox" MinWidth="100" Text="{Loc ban-panel-ip}" Pressed="False" />
|
||||
<Control MinWidth="50" />
|
||||
<LineEdit Name="IpLine" MinWidth="100" HorizontalExpand="True" PlaceHolder="{Loc ban-panel-ip}" ToolTip="{Loc ban-panel-ip-hwid-tooltip}" Editable="False" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" Margin="2">
|
||||
<CheckBox Name="HwidCheckbox" MinWidth="100" Text="{Loc ban-panel-hwid}" Pressed="True" />
|
||||
<Control MinWidth="50" />
|
||||
<LineEdit Name="HwidLine" MinWidth="100" HorizontalExpand="True" PlaceHolder="{Loc ban-panel-hwid}" ToolTip="{Loc ban-panel-ip-hwid-tooltip}" />
|
||||
</BoxContainer>
|
||||
<CheckBox Name="LastConnCheckbox" Margin="2" Text="{Loc ban-panel-last-conn}" Pressed="True" />
|
||||
<BoxContainer Orientation="Horizontal" Margin="2">
|
||||
<LineEdit Name="TimeLine" MaxWidth="150" MinWidth="70" PlaceHolder="0" />
|
||||
<OptionButton Name="MultiplierOption" />
|
||||
<Control MinWidth="50" />
|
||||
<Label Name="ExpiresLabel" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" Margin="4">
|
||||
<OptionButton Name="TypeOption" />
|
||||
<Control MinWidth="30"></Control>
|
||||
<Label Text="{Loc ban-panel-severity}" />
|
||||
<OptionButton Name="SeverityOption" />
|
||||
</BoxContainer>
|
||||
<cc:HSeparator Margin="1"/>
|
||||
<TextEdit Name="ReasonTextEdit" MinHeight="100" VerticalExpand="True" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<!-- Player List -->
|
||||
<cc:PlayerListControl Name="PlayerList" VerticalExpand="True" />
|
||||
<!-- Role list (auto-generated) -->
|
||||
<ScrollContainer>
|
||||
<BoxContainer Name="RolesContainer" Orientation="Vertical" />
|
||||
</ScrollContainer>
|
||||
</TabContainer>
|
||||
<Button Name="SubmitButton" Text="{Loc ban-panel-submit}" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
459
Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs
Normal file
459
Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs
Normal file
@@ -0,0 +1,459 @@
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text.RegularExpressions;
|
||||
using Content.Client.Administration.UI.CustomControls;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Administration.UI.BanPanel;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class BanPanel : DefaultWindow
|
||||
{
|
||||
public event Action<string?, (IPAddress, int)?, bool, byte[]?, bool, uint, string, NoteSeverity, string[]?>? BanSubmitted;
|
||||
public event Action<string>? PlayerChanged;
|
||||
private string? PlayerUsername { get; set; }
|
||||
private (IPAddress, int)? IpAddress { get; set; }
|
||||
private byte[]? Hwid { get; set; }
|
||||
private double TimeEntered { get; set; }
|
||||
private uint Multiplier { get; set; }
|
||||
private bool HasBanFlag { get; set; }
|
||||
private TimeSpan? ButtonResetOn { get; set; }
|
||||
// This is less efficient than just holding a reference to the root control and enumerating children, but you
|
||||
// have to know how the controls are nested, which makes the code more complicated.
|
||||
private readonly List<CheckBox> _roleCheckboxes = new();
|
||||
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
private enum TabNumbers
|
||||
{
|
||||
BasicInfo,
|
||||
//Text,
|
||||
Players,
|
||||
Roles
|
||||
}
|
||||
|
||||
private enum Multipliers
|
||||
{
|
||||
Minutes,
|
||||
Hours,
|
||||
Days,
|
||||
Weeks,
|
||||
Months,
|
||||
Years,
|
||||
Permanent
|
||||
}
|
||||
|
||||
private enum Types
|
||||
{
|
||||
None,
|
||||
Server,
|
||||
Role
|
||||
}
|
||||
|
||||
public BanPanel()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
PlayerList.OnSelectionChanged += OnPlayerSelectionChanged;
|
||||
PlayerNameLine.OnFocusExit += _ => OnPlayerNameChanged();
|
||||
PlayerCheckbox.OnPressed += _ =>
|
||||
{
|
||||
PlayerNameLine.Editable = PlayerCheckbox.Pressed;
|
||||
PlayerNameLine.ModulateSelfOverride = null;
|
||||
};
|
||||
TimeLine.OnTextChanged += OnMinutesChanged;
|
||||
MultiplierOption.OnItemSelected += args =>
|
||||
{
|
||||
MultiplierOption.SelectId(args.Id);
|
||||
OnMultiplierChanged();
|
||||
};
|
||||
IpLine.OnFocusExit += _ => OnIpChanged();
|
||||
IpCheckbox.OnPressed += _ =>
|
||||
{
|
||||
IpLine.Editable = IpCheckbox.Pressed;
|
||||
OnIpChanged();
|
||||
};
|
||||
HwidLine.OnFocusExit += _ => OnHwidChanged();
|
||||
HwidCheckbox.OnPressed += _ =>
|
||||
{
|
||||
HwidLine.Editable = HwidCheckbox.Pressed;
|
||||
OnHwidChanged();
|
||||
};
|
||||
TypeOption.OnItemSelected += args =>
|
||||
{
|
||||
TypeOption.SelectId(args.Id);
|
||||
OnTypeChanged();
|
||||
};
|
||||
LastConnCheckbox.OnPressed += args =>
|
||||
{
|
||||
IpLine.ModulateSelfOverride = null;
|
||||
HwidLine.ModulateSelfOverride = null;
|
||||
OnIpChanged();
|
||||
OnHwidChanged();
|
||||
};
|
||||
SubmitButton.OnPressed += SubmitButtonOnOnPressed;
|
||||
|
||||
SeverityOption.AddItem(Loc.GetString("admin-note-editor-severity-none"), (int) NoteSeverity.None);
|
||||
SeverityOption.AddItem(Loc.GetString("admin-note-editor-severity-low"), (int) NoteSeverity.Minor);
|
||||
SeverityOption.AddItem(Loc.GetString("admin-note-editor-severity-medium"), (int) NoteSeverity.Medium);
|
||||
SeverityOption.AddItem(Loc.GetString("admin-note-editor-severity-high"), (int) NoteSeverity.High);
|
||||
SeverityOption.SelectId((int) NoteSeverity.Medium);
|
||||
SeverityOption.OnItemSelected += args => SeverityOption.SelectId(args.Id);
|
||||
|
||||
MultiplierOption.AddItem(Loc.GetString("ban-panel-minutes"), (int) Multipliers.Minutes);
|
||||
MultiplierOption.AddItem(Loc.GetString("ban-panel-hours"), (int) Multipliers.Hours);
|
||||
MultiplierOption.AddItem(Loc.GetString("ban-panel-days"), (int) Multipliers.Days);
|
||||
MultiplierOption.AddItem(Loc.GetString("ban-panel-weeks"), (int) Multipliers.Weeks);
|
||||
MultiplierOption.AddItem(Loc.GetString("ban-panel-months"), (int) Multipliers.Months);
|
||||
MultiplierOption.AddItem(Loc.GetString("ban-panel-years"), (int) Multipliers.Years);
|
||||
MultiplierOption.AddItem(Loc.GetString("ban-panel-permanent"), (int) Multipliers.Permanent);
|
||||
MultiplierOption.SelectId((int) Multipliers.Minutes);
|
||||
OnMultiplierChanged();
|
||||
|
||||
Tabs.SetTabTitle((int) TabNumbers.BasicInfo, Loc.GetString("ban-panel-tabs-basic"));
|
||||
//Tabs.SetTabTitle((int) TabNumbers.Text, Loc.GetString("ban-panel-tabs-reason"));
|
||||
Tabs.SetTabTitle((int) TabNumbers.Players, Loc.GetString("ban-panel-tabs-players"));
|
||||
Tabs.SetTabTitle((int) TabNumbers.Roles, Loc.GetString("ban-panel-tabs-role"));
|
||||
Tabs.SetTabVisible((int) TabNumbers.Roles, false);
|
||||
|
||||
TypeOption.AddItem(Loc.GetString("ban-panel-select"), (int) Types.None);
|
||||
TypeOption.AddItem(Loc.GetString("ban-panel-server"), (int) Types.Server);
|
||||
TypeOption.AddItem(Loc.GetString("ban-panel-role"), (int) Types.Role);
|
||||
|
||||
ReasonTextEdit.Placeholder = new Rope.Leaf(Loc.GetString("ban-panel-reason"));
|
||||
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
foreach (var proto in prototypeManager.EnumeratePrototypes<DepartmentPrototype>())
|
||||
{
|
||||
CreateRoleGroup(proto.ID, proto.Roles, proto.Color);
|
||||
}
|
||||
|
||||
CreateRoleGroup("Antagonist", prototypeManager.EnumeratePrototypes<AntagPrototype>().Select(p => p.ID), Color.Red);
|
||||
}
|
||||
|
||||
private void CreateRoleGroup(string roleName, IEnumerable<string> roleList, Color color)
|
||||
{
|
||||
var outerContainer = new BoxContainer
|
||||
{
|
||||
Name = $"{roleName}GroupOuterBox",
|
||||
HorizontalExpand = true,
|
||||
VerticalExpand = true,
|
||||
Orientation = BoxContainer.LayoutOrientation.Vertical,
|
||||
Margin = new Thickness(4)
|
||||
};
|
||||
var departmentCheckbox = new CheckBox
|
||||
{
|
||||
Name = $"{roleName}GroupCheckbox",
|
||||
Text = roleName,
|
||||
Modulate = color,
|
||||
HorizontalAlignment = HAlignment.Left
|
||||
};
|
||||
outerContainer.AddChild(departmentCheckbox);
|
||||
var innerContainer = new BoxContainer
|
||||
{
|
||||
Name = $"{roleName}GroupInnerBox",
|
||||
HorizontalExpand = true,
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal
|
||||
};
|
||||
departmentCheckbox.OnToggled += args =>
|
||||
{
|
||||
foreach (var child in innerContainer.Children)
|
||||
{
|
||||
if (child is CheckBox c)
|
||||
{
|
||||
c.Pressed = args.Pressed;
|
||||
}
|
||||
}
|
||||
};
|
||||
outerContainer.AddChild(innerContainer);
|
||||
foreach (var role in roleList)
|
||||
{
|
||||
AddRoleCheckbox(role, innerContainer, departmentCheckbox);
|
||||
}
|
||||
RolesContainer.AddChild(new PanelContainer
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat
|
||||
{
|
||||
BackgroundColor = color
|
||||
}
|
||||
});
|
||||
RolesContainer.AddChild(outerContainer);
|
||||
RolesContainer.AddChild(new HSeparator());
|
||||
}
|
||||
|
||||
private void AddRoleCheckbox(string role, Control container, CheckBox header)
|
||||
{
|
||||
var roleCheckbox = new CheckBox
|
||||
{
|
||||
Name = $"{role}RoleCheckbox",
|
||||
Text = role
|
||||
};
|
||||
roleCheckbox.OnToggled += args =>
|
||||
{
|
||||
if (args is { Pressed: true, Button.Parent: { } } && args.Button.Parent.Children.Where(e => e is CheckBox).All(e => ((CheckBox) e).Pressed))
|
||||
header.Pressed = args.Pressed;
|
||||
else
|
||||
header.Pressed = false;
|
||||
};
|
||||
container.AddChild(roleCheckbox);
|
||||
_roleCheckboxes.Add(roleCheckbox);
|
||||
}
|
||||
|
||||
public void UpdateBanFlag(bool newFlag)
|
||||
{
|
||||
HasBanFlag = newFlag;
|
||||
SubmitButton.Visible = HasBanFlag;
|
||||
ModulateSelfOverride = HasBanFlag ? Color.Red : null;
|
||||
}
|
||||
|
||||
public void UpdatePlayerData(string playerName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(playerName))
|
||||
{
|
||||
PlayerNameLine.ModulateSelfOverride = Color.Red;
|
||||
ErrorLevel |= ErrorLevelEnum.PlayerName;
|
||||
UpdateSubmitEnabled();
|
||||
return;
|
||||
}
|
||||
PlayerNameLine.ModulateSelfOverride = null;
|
||||
ErrorLevel &= ~ErrorLevelEnum.PlayerName;
|
||||
UpdateSubmitEnabled();
|
||||
PlayerUsername = playerName;
|
||||
PlayerNameLine.Text = playerName;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
private enum ErrorLevelEnum : byte
|
||||
{
|
||||
None = 0,
|
||||
Minutes = 1 << 0,
|
||||
PlayerName = 1 << 1,
|
||||
IpAddress = 1 << 2,
|
||||
Hwid = 1 << 3,
|
||||
}
|
||||
|
||||
private ErrorLevelEnum ErrorLevel { get; set; }
|
||||
|
||||
private void OnMinutesChanged(LineEdit.LineEditEventArgs args)
|
||||
{
|
||||
TimeLine.Text = args.Text;
|
||||
if (!double.TryParse(args.Text, out var result))
|
||||
{
|
||||
ExpiresLabel.Text = "err";
|
||||
ErrorLevel |= ErrorLevelEnum.Minutes;
|
||||
TimeLine.ModulateSelfOverride = Color.Red;
|
||||
UpdateSubmitEnabled();
|
||||
return;
|
||||
}
|
||||
|
||||
ErrorLevel &= ~ErrorLevelEnum.Minutes;
|
||||
TimeLine.ModulateSelfOverride = null;
|
||||
TimeEntered = result;
|
||||
UpdateSubmitEnabled();
|
||||
UpdateExpiresLabel();
|
||||
}
|
||||
|
||||
private void OnMultiplierChanged()
|
||||
{
|
||||
TimeLine.Editable = MultiplierOption.SelectedId != (int) Multipliers.Permanent;
|
||||
Multiplier = MultiplierOption.SelectedId switch
|
||||
{
|
||||
(int) Multipliers.Minutes => 1,
|
||||
(int) Multipliers.Hours => 60,
|
||||
(int) Multipliers.Days => 60 * 24,
|
||||
(int) Multipliers.Weeks => 60 * 24 * 7,
|
||||
(int) Multipliers.Months => 60 * 24 * 30,
|
||||
(int) Multipliers.Years => 60 * 24 * 365,
|
||||
(int) Multipliers.Permanent => 0,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(MultiplierOption.SelectedId), "Multiplier out of range")
|
||||
};
|
||||
UpdateExpiresLabel();
|
||||
}
|
||||
|
||||
private void UpdateExpiresLabel()
|
||||
{
|
||||
var minutes = (uint) (TimeEntered * Multiplier);
|
||||
ExpiresLabel.Text = minutes == 0
|
||||
? $"{Loc.GetString("admin-note-editor-expiry-label")} {Loc.GetString("server-ban-string-never")}"
|
||||
: $"{Loc.GetString("admin-note-editor-expiry-label")} {DateTime.Now + TimeSpan.FromMinutes(minutes):yyyy/MM/dd HH:mm:ss}";
|
||||
}
|
||||
|
||||
private void OnIpChanged()
|
||||
{
|
||||
if (LastConnCheckbox.Pressed && IpAddress is null || !IpCheckbox.Pressed)
|
||||
{
|
||||
IpAddress = null;
|
||||
ErrorLevel &= ~ErrorLevelEnum.IpAddress;
|
||||
IpLine.ModulateSelfOverride = null;
|
||||
UpdateSubmitEnabled();
|
||||
return;
|
||||
}
|
||||
var ip = IpLine.Text;
|
||||
var hid = "0";
|
||||
if (ip.Contains('/'))
|
||||
{
|
||||
var split = ip.Split('/');
|
||||
ip = split[0];
|
||||
hid = split[1];
|
||||
}
|
||||
|
||||
if (!IPAddress.TryParse(ip, out var parsedIp) || !byte.TryParse(hid, out var hidInt) || hidInt > 128 || hidInt > 32 && parsedIp.AddressFamily == AddressFamily.InterNetwork)
|
||||
{
|
||||
ErrorLevel |= ErrorLevelEnum.IpAddress;
|
||||
IpLine.ModulateSelfOverride = Color.Red;
|
||||
UpdateSubmitEnabled();
|
||||
return;
|
||||
}
|
||||
|
||||
if (hidInt == 0)
|
||||
hidInt = (byte) (parsedIp.AddressFamily == AddressFamily.InterNetworkV6 ? 128 : 32);
|
||||
IpAddress = (parsedIp, hidInt);
|
||||
ErrorLevel &= ~ErrorLevelEnum.IpAddress;
|
||||
IpLine.ModulateSelfOverride = null;
|
||||
UpdateSubmitEnabled();
|
||||
}
|
||||
|
||||
private void OnHwidChanged()
|
||||
{
|
||||
var hwidString = HwidLine.Text;
|
||||
var length = 3 * (hwidString.Length / 4) - hwidString.TakeLast(2).Count(c => c == '=');
|
||||
Hwid = new byte[length];
|
||||
if (HwidCheckbox.Pressed && !(string.IsNullOrEmpty(hwidString) && LastConnCheckbox.Pressed) && !Convert.TryFromBase64String(hwidString, Hwid, out _))
|
||||
{
|
||||
ErrorLevel |= ErrorLevelEnum.Hwid;
|
||||
HwidLine.ModulateSelfOverride = Color.Red;
|
||||
UpdateSubmitEnabled();
|
||||
return;
|
||||
}
|
||||
|
||||
ErrorLevel &= ~ErrorLevelEnum.Hwid;
|
||||
HwidLine.ModulateSelfOverride = null;
|
||||
UpdateSubmitEnabled();
|
||||
|
||||
if (LastConnCheckbox.Pressed || !HwidCheckbox.Pressed)
|
||||
{
|
||||
Hwid = null;
|
||||
return;
|
||||
}
|
||||
Hwid = Convert.FromHexString(hwidString);
|
||||
}
|
||||
|
||||
private void OnTypeChanged()
|
||||
{
|
||||
TypeOption.ModulateSelfOverride = null;
|
||||
Tabs.SetTabVisible((int) TabNumbers.Roles, TypeOption.SelectedId == (int) Types.Role);
|
||||
}
|
||||
|
||||
private void UpdateSubmitEnabled()
|
||||
{
|
||||
SubmitButton.Disabled = ErrorLevel != ErrorLevelEnum.None;
|
||||
}
|
||||
|
||||
private void OnPlayerNameChanged()
|
||||
{
|
||||
if (PlayerUsername == PlayerNameLine.Text)
|
||||
return;
|
||||
PlayerUsername = PlayerNameLine.Text;
|
||||
if (!PlayerCheckbox.Pressed)
|
||||
return;
|
||||
if (string.IsNullOrWhiteSpace(PlayerUsername))
|
||||
ErrorLevel |= ErrorLevelEnum.PlayerName;
|
||||
else
|
||||
ErrorLevel &= ~ErrorLevelEnum.PlayerName;
|
||||
|
||||
UpdateSubmitEnabled();
|
||||
PlayerChanged?.Invoke(PlayerUsername);
|
||||
}
|
||||
|
||||
public void OnPlayerSelectionChanged(PlayerInfo? player)
|
||||
{
|
||||
PlayerNameLine.Text = player?.Username ?? string.Empty;
|
||||
OnPlayerNameChanged();
|
||||
}
|
||||
|
||||
private void ResetTextEditor(GUIBoundKeyEventArgs _)
|
||||
{
|
||||
ReasonTextEdit.ModulateSelfOverride = null;
|
||||
ReasonTextEdit.OnKeyBindDown -= ResetTextEditor;
|
||||
}
|
||||
|
||||
private void SubmitButtonOnOnPressed(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
string[]? roles = null;
|
||||
if (TypeOption.SelectedId == (int) Types.Role)
|
||||
{
|
||||
var rolesList = new List<string>();
|
||||
if (_roleCheckboxes.Count == 0)
|
||||
throw new DebugAssertException("RoleCheckboxes was empty");
|
||||
|
||||
rolesList.AddRange(_roleCheckboxes.Where(c => c is { Pressed: true, Text: { } }).Select(c => c.Text!));
|
||||
|
||||
if (rolesList.Count == 0)
|
||||
{
|
||||
Tabs.CurrentTab = (int) TabNumbers.Roles;
|
||||
return;
|
||||
}
|
||||
|
||||
roles = rolesList.ToArray();
|
||||
}
|
||||
|
||||
if (TypeOption.SelectedId == (int) Types.None)
|
||||
{
|
||||
TypeOption.ModulateSelfOverride = Color.Red;
|
||||
Tabs.CurrentTab = (int) TabNumbers.BasicInfo;
|
||||
return;
|
||||
}
|
||||
|
||||
var reason = Rope.Collapse(ReasonTextEdit.TextRope);
|
||||
if (string.IsNullOrWhiteSpace(reason))
|
||||
{
|
||||
//Tabs.CurrentTab = (int) TabNumbers.Text;
|
||||
Tabs.CurrentTab = (int) TabNumbers.BasicInfo;
|
||||
ReasonTextEdit.GrabKeyboardFocus();
|
||||
ReasonTextEdit.ModulateSelfOverride = Color.Red;
|
||||
ReasonTextEdit.OnKeyBindDown += ResetTextEditor;
|
||||
return;
|
||||
}
|
||||
|
||||
if (ButtonResetOn is null)
|
||||
{
|
||||
ButtonResetOn = _gameTiming.CurTime.Add(TimeSpan.FromSeconds(3));
|
||||
SubmitButton.ModulateSelfOverride = Color.Red;
|
||||
SubmitButton.Text = Loc.GetString("ban-panel-confirm");
|
||||
return;
|
||||
}
|
||||
|
||||
var player = PlayerCheckbox.Pressed ? PlayerUsername : null;
|
||||
var useLastIp = IpCheckbox.Pressed && LastConnCheckbox.Pressed && IpAddress is null;
|
||||
var useLastHwid = HwidCheckbox.Pressed && LastConnCheckbox.Pressed && Hwid is null;
|
||||
var severity = (NoteSeverity) SeverityOption.SelectedId;
|
||||
BanSubmitted?.Invoke(player, IpAddress, useLastIp, Hwid, useLastHwid, (uint) (TimeEntered * Multiplier), reason, severity, roles);
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
// This checks for null for free, do not invert it as null always produces a false value
|
||||
if (_gameTiming.CurTime > ButtonResetOn)
|
||||
{
|
||||
ButtonResetOn = null;
|
||||
SubmitButton.ModulateSelfOverride = null;
|
||||
SubmitButton.Text = Loc.GetString("ban-panel-submit");
|
||||
}
|
||||
}
|
||||
}
|
||||
48
Content.Client/Administration/UI/BanPanel/BanPanelEui.cs
Normal file
48
Content.Client/Administration/UI/BanPanel/BanPanelEui.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Client.Eui;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Eui;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Client.Administration.UI.BanPanel;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class BanPanelEui : BaseEui
|
||||
{
|
||||
private BanPanel BanPanel { get; }
|
||||
|
||||
public BanPanelEui()
|
||||
{
|
||||
BanPanel = new BanPanel();
|
||||
BanPanel.OnClose += () => SendMessage(new CloseEuiMessage());
|
||||
BanPanel.BanSubmitted += (player, ip, useLastIp, hwid, useLastHwid, minutes, reason, severity, roles)
|
||||
=> SendMessage(new BanPanelEuiStateMsg.CreateBanRequest(player, ip, useLastIp, hwid, useLastHwid, minutes, reason, severity, roles));
|
||||
BanPanel.PlayerChanged += player => SendMessage(new BanPanelEuiStateMsg.GetPlayerInfoRequest(player));
|
||||
}
|
||||
|
||||
public override void HandleState(EuiStateBase state)
|
||||
{
|
||||
if (state is not BanPanelEuiState s)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
BanPanel.UpdateBanFlag(s.HasBan);
|
||||
BanPanel.UpdatePlayerData(s.PlayerName);
|
||||
}
|
||||
|
||||
public override void Opened()
|
||||
{
|
||||
BanPanel.OpenCentered();
|
||||
}
|
||||
|
||||
public override void Closed()
|
||||
{
|
||||
BanPanel.Close();
|
||||
BanPanel.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using Content.Client.Administration.Managers;
|
||||
@@ -123,12 +123,10 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
_console.ExecuteCommand($"adminnotes \"{_currentPlayer.SessionId}\"");
|
||||
};
|
||||
|
||||
// ew
|
||||
Ban.OnPressed += _ =>
|
||||
{
|
||||
var bw = new BanWindow();
|
||||
bw.OnPlayerSelectionChanged(_currentPlayer);
|
||||
bw.Open();
|
||||
if (_currentPlayer is not null)
|
||||
_console.ExecuteCommand($"banpanel \"{_currentPlayer.SessionId}\"");
|
||||
};
|
||||
|
||||
Kick.OnPressed += _ =>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Control xmlns="https://spacestation14.io"
|
||||
<Control xmlns="https://spacestation14.io"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client">
|
||||
<PanelContainer>
|
||||
<PanelContainer.PanelOverride>
|
||||
@@ -8,8 +8,8 @@
|
||||
<ScrollContainer VerticalExpand="True" HorizontalExpand="True" HScrollEnabled="False">
|
||||
<BoxContainer Orientation="Vertical" Name="Notes" Access="Public" VerticalExpand="True"/>
|
||||
</ScrollContainer>
|
||||
<Label Name="NewNoteLabel" Text="{Loc admin-notes-new-note}" />
|
||||
<HistoryLineEdit Name="NewNote"/>
|
||||
<Button Name="ShowMoreButton" Text="{Loc admin-notes-show-more}" Visible="False" HorizontalAlignment="Center" />
|
||||
<Button Name="NewNoteButton" Text="{Loc admin-notes-new-note}" Disabled="True" />
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</Control>
|
||||
|
||||
@@ -1,119 +1,167 @@
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Shared.Administration.Notes;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Database;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using static Robust.Client.UserInterface.Controls.LineEdit;
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
namespace Content.Client.Administration.UI.Notes;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class AdminNotesControl : Control
|
||||
{
|
||||
public event Action<int, string>? OnNoteChanged;
|
||||
public event Action<string>? OnNewNoteEntered;
|
||||
public event Action<int>? OnNoteDeleted;
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystem = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
public event Action<int, NoteType, string, NoteSeverity?, bool, DateTime?>? NoteChanged;
|
||||
public event Action<NoteType, string, NoteSeverity?, bool, DateTime?>? NewNoteEntered;
|
||||
public event Action<int, NoteType>? NoteDeleted;
|
||||
|
||||
private AdminNotesLinePopup? _popup;
|
||||
private readonly SpriteSystem _sprites;
|
||||
private readonly double _noteFreshDays;
|
||||
private readonly double _noteStaleDays;
|
||||
|
||||
public AdminNotesControl()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
_sprites = _entitySystem.GetEntitySystem<SpriteSystem>();
|
||||
|
||||
NewNote.OnTextEntered += NewNoteEntered;
|
||||
// There should be a warning somewhere if fresh > stale
|
||||
// I thought about putting it here but then it would spam you every time you open notes
|
||||
_noteFreshDays = _cfg.GetCVar(CCVars.NoteFreshDays);
|
||||
_noteStaleDays = _cfg.GetCVar(CCVars.NoteStaleDays);
|
||||
|
||||
NewNoteButton.OnPressed += OnNewNoteButtonPressed;
|
||||
ShowMoreButton.OnPressed += OnShowMoreButtonPressed;
|
||||
}
|
||||
|
||||
private Dictionary<int, AdminNotesLine> Inputs { get; } = new();
|
||||
private Dictionary<(int noteId, NoteType noteType), AdminNotesLine> Inputs { get; } = new();
|
||||
private bool CanCreate { get; set; }
|
||||
private bool CanDelete { get; set; }
|
||||
private bool CanEdit { get; set; }
|
||||
private string PlayerName { get; set; } = "<Error>";
|
||||
|
||||
private void NewNoteEntered(LineEditEventArgs args)
|
||||
public void SetPlayerName(string playerName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(args.Text))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NewNote.Clear();
|
||||
OnNewNoteEntered?.Invoke(args.Text);
|
||||
PlayerName = playerName;
|
||||
}
|
||||
|
||||
private void NoteSubmitted(AdminNotesLine input)
|
||||
private void OnNewNoteButtonPressed(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
var text = input.EditText.Trim();
|
||||
if (input.OriginalMessage == text)
|
||||
var noteEdit = new NoteEdit(null, PlayerName, CanCreate, CanEdit);
|
||||
noteEdit.SubmitPressed += OnNoteSubmitted;
|
||||
noteEdit.OpenCentered();
|
||||
}
|
||||
|
||||
private void OnNoteSubmitted(int id, NoteType type, string message, NoteSeverity? severity, bool secret, DateTime? expiryTime)
|
||||
{
|
||||
if (id == 0)
|
||||
{
|
||||
NewNoteEntered?.Invoke(type, message, severity, secret, expiryTime);
|
||||
return;
|
||||
}
|
||||
|
||||
OnNoteChanged?.Invoke(input.Id, text);
|
||||
NoteChanged?.Invoke(id, type, message, severity, secret, expiryTime);
|
||||
}
|
||||
|
||||
private bool NoteClicked(AdminNotesLine line)
|
||||
{
|
||||
ClosePopup();
|
||||
|
||||
_popup = new AdminNotesLinePopup(line.Note, CanDelete, CanEdit);
|
||||
_popup.OnEditPressed += noteId =>
|
||||
_popup = new AdminNotesLinePopup(line.Note, PlayerName, CanDelete, CanEdit);
|
||||
_popup.OnEditPressed += (noteId, noteType) =>
|
||||
{
|
||||
if (!Inputs.TryGetValue(noteId, out var input))
|
||||
if (!Inputs.TryGetValue((noteId, noteType), out var input))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
input.SetEditable(true);
|
||||
var noteEdit = new NoteEdit(input.Note, PlayerName, CanCreate, CanEdit);
|
||||
noteEdit.SubmitPressed += OnNoteSubmitted;
|
||||
noteEdit.OpenCentered();
|
||||
};
|
||||
_popup.OnDeletePressed += noteId => OnNoteDeleted?.Invoke(noteId);
|
||||
|
||||
_popup.OnDeletePressed += (noteId, noteType) => NoteDeleted?.Invoke(noteId, noteType);
|
||||
var box = UIBox2.FromDimensions(UserInterfaceManager.MousePositionScaled.Position, Vector2.One);
|
||||
_popup.Open(box);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ClosePopup()
|
||||
public void SetNotes(Dictionary<(int, NoteType), SharedAdminNote> notes)
|
||||
{
|
||||
_popup?.Close();
|
||||
_popup = null;
|
||||
}
|
||||
|
||||
public void SetNotes(Dictionary<int, SharedAdminNote> notes)
|
||||
{
|
||||
foreach (var (id, input) in Inputs)
|
||||
foreach (var (key, input) in Inputs)
|
||||
{
|
||||
if (!notes.ContainsKey(id))
|
||||
if (!notes.ContainsKey(key))
|
||||
{
|
||||
Notes.RemoveChild(input);
|
||||
Inputs.Remove(id);
|
||||
// Yes this is slower than just updating, but new notes get added at the bottom. The user won't notice.
|
||||
Notes.RemoveAllChildren();
|
||||
Inputs.Clear();
|
||||
break;
|
||||
}
|
||||
Notes.RemoveChild(input);
|
||||
Inputs.Remove(key);
|
||||
}
|
||||
|
||||
foreach (var note in notes.Values.OrderBy(note => note.Id))
|
||||
var showMoreButtonVisible = false;
|
||||
foreach (var note in notes.Values.OrderByDescending(note => note.CreatedAt))
|
||||
{
|
||||
if (Inputs.TryGetValue(note.Id, out var input))
|
||||
if (Inputs.TryGetValue((note.Id, note.NoteType), out var input))
|
||||
{
|
||||
input.UpdateNote(note);
|
||||
continue;
|
||||
}
|
||||
|
||||
input = new AdminNotesLine(note);
|
||||
input.OnSubmitted += NoteSubmitted;
|
||||
input = new AdminNotesLine(_sprites, note);
|
||||
input.OnClicked += NoteClicked;
|
||||
|
||||
var timeDiff = DateTime.UtcNow - note.CreatedAt;
|
||||
float alpha;
|
||||
if (_noteFreshDays == 0 || timeDiff.TotalDays <= _noteFreshDays)
|
||||
{
|
||||
alpha = 1f;
|
||||
}
|
||||
else if (_noteStaleDays == 0 || timeDiff.TotalDays > _noteStaleDays)
|
||||
{
|
||||
alpha = 0f;
|
||||
input.Visible = false;
|
||||
showMoreButtonVisible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
alpha = (float) (1 - Math.Clamp((timeDiff.TotalDays - _noteFreshDays) / (_noteStaleDays - _noteFreshDays), 0, 1));
|
||||
}
|
||||
|
||||
input.Modulate = input.Modulate.WithAlpha(alpha);
|
||||
Notes.AddChild(input);
|
||||
Inputs[note.Id] = input;
|
||||
Inputs[(note.Id, note.NoteType)] = input;
|
||||
ShowMoreButton.Visible = showMoreButtonVisible;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnShowMoreButtonPressed(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
foreach (var input in Inputs.Values)
|
||||
{
|
||||
input.Modulate = input.Modulate.WithAlpha(1f);
|
||||
input.Visible = true;
|
||||
}
|
||||
|
||||
ShowMoreButton.Visible = false;
|
||||
}
|
||||
|
||||
public void SetPermissions(bool create, bool delete, bool edit)
|
||||
{
|
||||
CanCreate = create;
|
||||
CanDelete = delete;
|
||||
CanEdit = edit;
|
||||
NewNoteLabel.Visible = create;
|
||||
NewNote.Visible = create;
|
||||
NewNoteButton.Visible = create;
|
||||
NewNoteButton.Disabled = !create;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
@@ -125,21 +173,14 @@ public sealed partial class AdminNotesControl : Control
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var input in Inputs.Values)
|
||||
{
|
||||
input.OnSubmitted -= NoteSubmitted;
|
||||
}
|
||||
|
||||
Inputs.Clear();
|
||||
NewNote.OnTextEntered -= NewNoteEntered;
|
||||
NewNoteButton.OnPressed -= OnNewNoteButtonPressed;
|
||||
|
||||
if (_popup != null)
|
||||
{
|
||||
UserInterfaceManager.PopupRoot.RemoveChild(_popup);
|
||||
}
|
||||
|
||||
OnNoteChanged = null;
|
||||
OnNewNoteEntered = null;
|
||||
OnNoteDeleted = null;
|
||||
NoteDeleted = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Client.Eui;
|
||||
using Content.Client.Eui;
|
||||
using Content.Shared.Administration.Notes;
|
||||
using Content.Shared.Eui;
|
||||
using JetBrains.Annotations;
|
||||
@@ -14,15 +14,10 @@ public sealed class AdminNotesEui : BaseEui
|
||||
NoteWindow = new AdminNotesWindow();
|
||||
NoteControl = NoteWindow.Notes;
|
||||
|
||||
NoteControl.OnNoteChanged += (id, text) => SendMessage(new EditNoteRequest(id, text));
|
||||
NoteControl.OnNewNoteEntered += text => SendMessage(new CreateNoteRequest(text));
|
||||
NoteControl.OnNoteDeleted += id => SendMessage(new DeleteNoteRequest(id));
|
||||
NoteWindow.OnClose += OnClosed;
|
||||
}
|
||||
|
||||
private void OnClosed()
|
||||
{
|
||||
SendMessage(new CloseEuiMessage());
|
||||
NoteControl.NoteChanged += (id, type, text, severity, secret, expiryTime) => SendMessage(new EditNoteRequest(id, type, text, severity, secret, expiryTime));
|
||||
NoteControl.NewNoteEntered += (type, text, severity, secret, expiryTime) => SendMessage(new CreateNoteRequest(type, text, severity, secret, expiryTime));
|
||||
NoteControl.NoteDeleted += (id, type) => SendMessage(new DeleteNoteRequest(id, type));
|
||||
NoteWindow.OnClose += () => SendMessage(new CloseEuiMessage());
|
||||
}
|
||||
|
||||
public override void Closed()
|
||||
@@ -43,6 +38,7 @@ public sealed class AdminNotesEui : BaseEui
|
||||
}
|
||||
|
||||
NoteWindow.SetTitlePlayer(s.NotedPlayerName);
|
||||
NoteControl.SetPlayerName(s.NotedPlayerName);
|
||||
NoteControl.SetNotes(s.Notes);
|
||||
NoteControl.SetPermissions(s.CanCreate, s.CanDelete, s.CanEdit);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
<BoxContainer xmlns="https://spacestation14.io"
|
||||
<BoxContainer xmlns="https://spacestation14.io"
|
||||
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
Orientation="Vertical">
|
||||
<cc:HSeparator Name="Separator"/>
|
||||
<BoxContainer Name ="MetadataContainer" Orientation="Horizontal">
|
||||
<TextureRect Name="SeverityRect" Margin="2"/>
|
||||
<Label Name="TimeLabel" Margin="4 0" />
|
||||
<cc:HSeparator Margin="4 0" />
|
||||
<Label Name="ServerLabel" />
|
||||
<cc:HSeparator Margin="4 0" />
|
||||
<Label Name="RoundLabel" />
|
||||
<cc:HSeparator Margin="4 0" />
|
||||
<Label Name="AdminLabel" />
|
||||
<cc:HSeparator Margin="4 0" />
|
||||
<Label Name="PlaytimeLabel" />
|
||||
<cc:HSeparator Name="SecretSeparator" Visible="False" Margin="4 0" />
|
||||
<Label Name="SecretLabel" Text="{Loc admin-notes-secret} " Visible="False" />
|
||||
</BoxContainer>
|
||||
<RichTextLabel Name="NoteLabel" />
|
||||
<Label Name="ExpiresLabel" Text="{Loc admin-note-editor-expiry-label}" Visible="False" />
|
||||
<Label Name="ExtraLabel" Visible="False" Modulate="#1AA7EC" />
|
||||
<Label Name="EditedLabel" Visible="False" />
|
||||
<cc:HSeparator Name="Separator" />
|
||||
</BoxContainer>
|
||||
|
||||
@@ -1,85 +1,172 @@
|
||||
using Content.Shared.Administration.Notes;
|
||||
using System.Text;
|
||||
using Content.Client.Resources;
|
||||
using Content.Shared.Administration.Notes;
|
||||
using Content.Shared.Database;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Input;
|
||||
using static Robust.Client.UserInterface.Controls.LineEdit;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Administration.UI.Notes;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class AdminNotesLine : BoxContainer
|
||||
{
|
||||
private RichTextLabel? _label;
|
||||
private LineEdit? _edit;
|
||||
private readonly SpriteSystem _sprites;
|
||||
|
||||
public AdminNotesLine(SharedAdminNote note)
|
||||
private const string AdminNotesTextureBase = "/Textures/Interface/AdminNotes/";
|
||||
private static readonly Dictionary<NoteSeverity, string> SeverityIcons = new()
|
||||
{
|
||||
{ NoteSeverity.None, AdminNotesTextureBase + "none_button.png" },
|
||||
{ NoteSeverity.Minor, AdminNotesTextureBase + "minor_button.png" },
|
||||
{ NoteSeverity.Medium, AdminNotesTextureBase + "medium_button.png" },
|
||||
{ NoteSeverity.High, AdminNotesTextureBase + "high_button.png" },
|
||||
};
|
||||
private static readonly Dictionary<NoteType, string> NoteTypeIcons = new()
|
||||
{
|
||||
{ NoteType.Message, AdminNotesTextureBase + "message.png" },
|
||||
{ NoteType.Watchlist, AdminNotesTextureBase + "watchlist.png" },
|
||||
};
|
||||
|
||||
public AdminNotesLine(SpriteSystem sprites, SharedAdminNote note)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
_sprites = sprites;
|
||||
|
||||
Note = note;
|
||||
MouseFilter = MouseFilterMode.Pass;
|
||||
|
||||
AddLabel();
|
||||
Separator.Visible = true;
|
||||
Refresh();
|
||||
}
|
||||
|
||||
public SharedAdminNote Note { get; private set; }
|
||||
public int Id => Note.Id;
|
||||
public string OriginalMessage => Note.Message;
|
||||
public string EditText => _edit?.Text ?? OriginalMessage;
|
||||
|
||||
public event Action<AdminNotesLine>? OnSubmitted;
|
||||
public event Func<AdminNotesLine, bool>? OnClicked;
|
||||
|
||||
private void AddLabel()
|
||||
/// <summary>
|
||||
/// Attempts to refresh the current note line with new data. The note it draws data on is stored in <see cref="Note"/>
|
||||
/// </summary>
|
||||
private void Refresh()
|
||||
{
|
||||
if (_edit != null)
|
||||
{
|
||||
_edit.OnTextEntered -= Submitted;
|
||||
_edit.OnFocusExit -= Submitted;
|
||||
string? iconPath;
|
||||
if(Note.NoteSeverity is not null)
|
||||
SeverityIcons.TryGetValue(Note.NoteSeverity.Value, out iconPath);
|
||||
else
|
||||
NoteTypeIcons.TryGetValue(Note.NoteType, out iconPath);
|
||||
|
||||
RemoveChild(_edit);
|
||||
_edit = null;
|
||||
if (iconPath is null)
|
||||
{
|
||||
SeverityRect.Visible = false;
|
||||
Logger.WarningS("admin.notes", $"Could not find an icon for note ID {Note.Id}");
|
||||
}
|
||||
else
|
||||
{
|
||||
SeverityRect.Texture = _sprites.Frame0(new SpriteSpecifier.Texture(new ResPath(iconPath)));
|
||||
}
|
||||
|
||||
_label = new RichTextLabel();
|
||||
_label.SetMessage(Note.Message);
|
||||
TimeLabel.Text = Note.CreatedAt.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
ServerLabel.Text = Note.ServerName ?? "Unknown";
|
||||
RoundLabel.Text = Note.Round == null ? "Unknown round" : "Round " + Note.Round;
|
||||
AdminLabel.Text = Note.CreatedByName;
|
||||
PlaytimeLabel.Text = $"{Note.PlaytimeAtNote.TotalHours: 0.0}h";
|
||||
|
||||
AddChild(_label);
|
||||
_label.SetPositionFirst();
|
||||
|
||||
Separator.Visible = true;
|
||||
}
|
||||
|
||||
private void AddLineEdit()
|
||||
{
|
||||
if (_label != null)
|
||||
if (Note.Secret)
|
||||
{
|
||||
RemoveChild(_label);
|
||||
_label = null;
|
||||
SecretSeparator.Visible = true;
|
||||
SecretLabel.Visible = true;
|
||||
}
|
||||
|
||||
_edit = new LineEdit {Text = Note.Message};
|
||||
_edit.OnTextEntered += Submitted;
|
||||
_edit.OnFocusExit += Submitted;
|
||||
if (Note.UnbannedTime is not null)
|
||||
{
|
||||
ExtraLabel.Text = Loc.GetString("admin-notes-unbanned", ("admin", Note.UnbannedByName ?? "[error]"), ("date", Note.UnbannedTime));
|
||||
ExtraLabel.Visible = true;
|
||||
}
|
||||
else if (Note.ExpiryTime is not null)
|
||||
{
|
||||
// Notes should never be visible when expired, bans should
|
||||
if (Note.ExpiryTime.Value > DateTime.UtcNow)
|
||||
{
|
||||
ExpiresLabel.Text = Loc.GetString("admin-note-editor-expiry-label-params",
|
||||
("date", Note.ExpiryTime.Value.ToString("yyyy-MM-dd HH:mm:ss")),
|
||||
("expiresIn", (Note.ExpiryTime.Value - DateTime.UtcNow).ToString("d'd 'hh':'mm")));
|
||||
ExpiresLabel.Modulate = Color.FromHex("#86DC3D");
|
||||
}
|
||||
else
|
||||
{
|
||||
ExpiresLabel.Text = Loc.GetString("admin-note-editor-expiry-label-expired");
|
||||
}
|
||||
ExpiresLabel.Visible = true;
|
||||
}
|
||||
|
||||
AddChild(_edit);
|
||||
_edit.SetPositionFirst();
|
||||
_edit.GrabKeyboardFocus();
|
||||
_edit.CursorPosition = _edit.Text.Length;
|
||||
if (Note.LastEditedAt > Note.CreatedAt)
|
||||
{
|
||||
EditedLabel.Text = Loc.GetString("admin-notes-edited", ("author", Note.EditedByName), ("date", Note.LastEditedAt));
|
||||
EditedLabel.Visible = true;
|
||||
}
|
||||
|
||||
Separator.Visible = false;
|
||||
switch (Note.NoteType)
|
||||
{
|
||||
case NoteType.ServerBan:
|
||||
NoteLabel.SetMessage(FormatBanMessage());
|
||||
break;
|
||||
case NoteType.RoleBan:
|
||||
NoteLabel.SetMessage(FormatRoleBanMessage());
|
||||
break;
|
||||
case NoteType.Note:
|
||||
case NoteType.Watchlist:
|
||||
case NoteType.Message:
|
||||
default:
|
||||
NoteLabel.SetMessage(Note.Message);
|
||||
break;
|
||||
}
|
||||
|
||||
if (Note.Seen == true)
|
||||
{
|
||||
ExtraLabel.Text = Loc.GetString("admin-notes-message-seen");
|
||||
ExtraLabel.Visible = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void Submitted(LineEditEventArgs args)
|
||||
private string FormatBanMessage()
|
||||
{
|
||||
OnSubmitted?.Invoke(this);
|
||||
var banMessage = new StringBuilder($"{Loc.GetString("admin-notes-banned-from")} {Loc.GetString("admin-notes-the-server")} ");
|
||||
return FormatBanMessageCommon(banMessage);
|
||||
}
|
||||
|
||||
AddLabel();
|
||||
private string FormatRoleBanMessage()
|
||||
{
|
||||
var banMessage = new StringBuilder($"{Loc.GetString("admin-notes-banned-from")} {string.Join(", ", Note.BannedRoles ?? new []{"unknown"})} ");
|
||||
return FormatBanMessageCommon(banMessage);
|
||||
}
|
||||
|
||||
var note = Note with {Message = args.Text};
|
||||
UpdateNote(note);
|
||||
private string FormatBanMessageCommon(StringBuilder sb)
|
||||
{
|
||||
if (Note.ExpiryTime is null)
|
||||
{
|
||||
sb.Append(Loc.GetString("admin-notes-permanently"));
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append("for ");
|
||||
var banLength = Note.ExpiryTime.Value - Note.CreatedAt;
|
||||
if (banLength.Days > 0)
|
||||
sb.Append(Loc.GetString("admin-notes-days", ("days", banLength.TotalDays.ToString(".00"))));
|
||||
else if (banLength.Hours > 0)
|
||||
sb.Append(Loc.GetString("admin-notes-hours", ("hours", banLength.TotalHours.ToString(".00"))));
|
||||
else
|
||||
sb.Append(Loc.GetString("admin-notes-minutes", ("minutes", banLength.TotalMinutes.ToString(".00"))));
|
||||
}
|
||||
|
||||
sb.Append(" - ");
|
||||
sb.Append(Note.Message);
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
protected override void KeyBindDown(GUIBoundKeyEventArgs args)
|
||||
@@ -101,24 +188,7 @@ public sealed partial class AdminNotesLine : BoxContainer
|
||||
public void UpdateNote(SharedAdminNote note)
|
||||
{
|
||||
Note = note;
|
||||
_label?.SetMessage(note.Message);
|
||||
|
||||
if (_edit != null && _edit.Text != note.Message)
|
||||
{
|
||||
_edit.Text = note.Message;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetEditable(bool editable)
|
||||
{
|
||||
if (editable)
|
||||
{
|
||||
AddLineEdit();
|
||||
}
|
||||
else
|
||||
{
|
||||
AddLabel();
|
||||
}
|
||||
Refresh();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
@@ -130,13 +200,6 @@ public sealed partial class AdminNotesLine : BoxContainer
|
||||
return;
|
||||
}
|
||||
|
||||
if (_edit != null)
|
||||
{
|
||||
_edit.OnTextEntered -= Submitted;
|
||||
_edit.OnFocusExit -= Submitted;
|
||||
}
|
||||
|
||||
OnSubmitted = null;
|
||||
OnClicked = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
<Popup xmlns="https://spacestation14.io"
|
||||
<Popup xmlns="https://spacestation14.io"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client">
|
||||
<PanelContainer>
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#25252A"/>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#25252A" BorderThickness="1" BorderColor="#18181B"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<Label Name="PlayerNameLabel"/>
|
||||
<Label Name="IdLabel"/>
|
||||
<Label Name="TypeLabel"/>
|
||||
<Label Name="SeverityLabel"/>
|
||||
<Label Name="RoundIdLabel"/>
|
||||
<Label Name="CreatedByLabel"/>
|
||||
<Label Name="CreatedAtLabel"/>
|
||||
<Label Name="EditedByLabel"/>
|
||||
<Label Name="EditedAtLabel"/>
|
||||
<Label Name="ExpiryTimeLabel"/>
|
||||
<TextEdit Name="NoteTextEdit" Editable="False" MinHeight="24" />
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Button Name="EditButton" Text="{Loc admin-notes-edit}"/>
|
||||
<Control HorizontalExpand="True"/>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using Content.Shared.Administration.Notes;
|
||||
using Content.Shared.Administration.Notes;
|
||||
using Content.Shared.Database;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Timing;
|
||||
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||
|
||||
namespace Content.Client.Administration.UI.Notes;
|
||||
@@ -9,57 +11,89 @@ namespace Content.Client.Administration.UI.Notes;
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class AdminNotesLinePopup : Popup
|
||||
{
|
||||
public event Action<int>? OnEditPressed;
|
||||
public event Action<int>? OnDeletePressed;
|
||||
public event Action<int, NoteType>? OnEditPressed;
|
||||
public event Action<int, NoteType>? OnDeletePressed;
|
||||
|
||||
public AdminNotesLinePopup(SharedAdminNote note, bool showDelete, bool showEdit)
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
public AdminNotesLinePopup(SharedAdminNote note, string playerName, bool showDelete, bool showEdit)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
NoteId = note.Id;
|
||||
NoteType = note.NoteType;
|
||||
DeleteButton.Visible = showDelete;
|
||||
EditButton.Visible = showEdit;
|
||||
|
||||
UserInterfaceManager.ModalRoot.AddChild(this);
|
||||
|
||||
PlayerNameLabel.Text = Loc.GetString("admin-notes-for", ("player", playerName));
|
||||
IdLabel.Text = Loc.GetString("admin-notes-id", ("id", note.Id));
|
||||
TypeLabel.Text = Loc.GetString("admin-notes-type", ("type", note.NoteType));
|
||||
SeverityLabel.Text = Loc.GetString("admin-notes-severity", ("severity", note.NoteSeverity ?? NoteSeverity.None));
|
||||
RoundIdLabel.Text = note.Round == null
|
||||
? Loc.GetString("admin-notes-round-id-unknown")
|
||||
: Loc.GetString("admin-notes-round-id", ("id", note.Round));
|
||||
CreatedByLabel.Text = Loc.GetString("admin-notes-created-by", ("author", note.CreatedByName));
|
||||
CreatedAtLabel.Text = Loc.GetString("admin-notes-created-at", ("date", note.CreatedAt.ToString("dd MMM yyyy HH:mm:ss")));
|
||||
CreatedAtLabel.Text = Loc.GetString("admin-notes-created-at", ("date", note.CreatedAt.ToString("yyyy-MM-dd HH:mm:ss")));
|
||||
EditedByLabel.Text = Loc.GetString("admin-notes-last-edited-by", ("author", note.EditedByName));
|
||||
EditedAtLabel.Text = Loc.GetString("admin-notes-last-edited-at", ("date", note.LastEditedAt.ToString("dd MMM yyyy HH:mm:ss")));
|
||||
EditedAtLabel.Text = Loc.GetString("admin-notes-last-edited-at", ("date", note.LastEditedAt?.ToString("yyyy-MM-dd HH:mm:ss") ?? Loc.GetString("admin-notes-edited-never")));
|
||||
ExpiryTimeLabel.Text = note.ExpiryTime == null
|
||||
? Loc.GetString("admin-notes-expires-never")
|
||||
: Loc.GetString("admin-notes-expires", ("expires", note.ExpiryTime.Value.ToString("yyyy-MM-dd HH:mm:ss")));
|
||||
NoteTextEdit.InsertAtCursor(note.Message);
|
||||
|
||||
if (note.NoteType is NoteType.ServerBan or NoteType.RoleBan)
|
||||
{
|
||||
DeleteButton.Text = Loc.GetString("admin-notes-hide");
|
||||
}
|
||||
|
||||
EditButton.OnPressed += EditPressed;
|
||||
DeleteButton.OnPressed += DeletePressed;
|
||||
}
|
||||
|
||||
private int NoteId { get; }
|
||||
private bool ConfirmingDelete { get; set; }
|
||||
private NoteType NoteType { get; }
|
||||
private TimeSpan? DeleteResetOn { get; set; }
|
||||
|
||||
private void EditPressed(ButtonEventArgs args)
|
||||
{
|
||||
OnEditPressed?.Invoke(NoteId);
|
||||
OnEditPressed?.Invoke(NoteId, NoteType);
|
||||
Close();
|
||||
}
|
||||
|
||||
private void DeletePressed(ButtonEventArgs args)
|
||||
{
|
||||
if (!ConfirmingDelete)
|
||||
if (DeleteResetOn is null)
|
||||
{
|
||||
ConfirmingDelete = true;
|
||||
DeleteResetOn = _gameTiming.CurTime.Add(TimeSpan.FromSeconds(3));
|
||||
DeleteButton.Text = Loc.GetString("admin-notes-delete-confirm");
|
||||
DeleteButton.ModulateSelfOverride = Color.Red;
|
||||
return;
|
||||
}
|
||||
|
||||
ConfirmingDelete = false;
|
||||
DeleteButton.ModulateSelfOverride = null;
|
||||
OnDeletePressed?.Invoke(NoteId);
|
||||
ResetDeleteButton();
|
||||
OnDeletePressed?.Invoke(NoteId, NoteType);
|
||||
Close();
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
// This checks for null for free, do not invert it as null always produces a false value
|
||||
if (DeleteResetOn < _gameTiming.CurTime)
|
||||
{
|
||||
ResetDeleteButton();
|
||||
DeleteResetOn = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetDeleteButton()
|
||||
{
|
||||
DeleteButton.Text = Loc.GetString("admin-notes-delete");
|
||||
DeleteButton.ModulateSelfOverride = null;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
<ui:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:notes="clr-namespace:Content.Client.Administration.UI.Notes"
|
||||
SetSize="400 400">
|
||||
<notes:AdminNotesControl Name="Notes" Access="Public"/>
|
||||
</DefaultWindow>
|
||||
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
SetSize="600 400"
|
||||
Title="Loading...">
|
||||
<notes:AdminNotesControl Name="Notes" Access="Public" Margin="4"/>
|
||||
</ui:FancyWindow>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.Administration.UI.Notes;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class AdminNotesWindow : DefaultWindow
|
||||
public sealed partial class AdminNotesWindow : FancyWindow
|
||||
{
|
||||
public AdminNotesWindow()
|
||||
{
|
||||
|
||||
22
Content.Client/Administration/UI/Notes/NoteEdit.xaml
Normal file
22
Content.Client/Administration/UI/Notes/NoteEdit.xaml
Normal file
@@ -0,0 +1,22 @@
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Title="Loading..."
|
||||
MinSize="400 200">
|
||||
<BoxContainer Orientation="Vertical" Margin="4">
|
||||
<TextEdit Name="NoteTextEdit" HorizontalExpand="True" VerticalExpand="True" />
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<Label Name="ExpiryLabel" Text="{Loc admin-note-editor-expiry-label}" Visible="False" />
|
||||
<HistoryLineEdit Name="ExpiryLineEdit" PlaceHolder="{Loc admin-note-editor-expiry-placeholder}"
|
||||
Visible="False" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<OptionButton Name="TypeOption" HorizontalAlignment="Center" />
|
||||
<OptionButton Name="SeverityOption" HorizontalAlignment="Center" />
|
||||
<CheckBox Name="SecretCheckBox" Text="{Loc admin-note-editor-secret}"
|
||||
ToolTip="{Loc admin-note-editor-secret-tooltip}" />
|
||||
<CheckBox Name="PermanentCheckBox" Pressed="True" Text="{Loc admin-note-editor-expiry-checkbox}"
|
||||
ToolTip="{Loc admin-note-editor-expiry-checkbox-tooltip}" />
|
||||
<Button Name="SubmitButton" Text="{Loc admin-note-editor-submit}" HorizontalAlignment="Right" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
242
Content.Client/Administration/UI/Notes/NoteEdit.xaml.cs
Normal file
242
Content.Client/Administration/UI/Notes/NoteEdit.xaml.cs
Normal file
@@ -0,0 +1,242 @@
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Administration.Notes;
|
||||
using Content.Shared.Database;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Administration.UI.Notes;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class NoteEdit : FancyWindow
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IClientConsoleHost _console = default!;
|
||||
|
||||
public event Action<int, NoteType, string, NoteSeverity?, bool, DateTime?>? SubmitPressed;
|
||||
|
||||
public NoteEdit(SharedAdminNote? note, string playerName, bool canCreate, bool canEdit)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
PlayerName = playerName;
|
||||
Title = Loc.GetString("admin-note-editor-title-new", ("player", PlayerName));
|
||||
|
||||
ResetSubmitButton();
|
||||
|
||||
TypeOption.AddItem(Loc.GetString("admin-note-editor-type-note"), (int) NoteType.Note);
|
||||
TypeOption.AddItem(Loc.GetString("admin-note-editor-type-message"), (int) NoteType.Message);
|
||||
TypeOption.AddItem(Loc.GetString("admin-note-editor-type-watchlist"), (int) NoteType.Watchlist);
|
||||
TypeOption.OnItemSelected += OnTypeChanged;
|
||||
|
||||
|
||||
SeverityOption.AddItem(Loc.GetString("admin-note-editor-severity-none"), (int) Shared.Database.NoteSeverity.None);
|
||||
SeverityOption.AddItem(Loc.GetString("admin-note-editor-severity-low"), (int) Shared.Database.NoteSeverity.Minor);
|
||||
SeverityOption.AddItem(Loc.GetString("admin-note-editor-severity-medium"), (int) Shared.Database.NoteSeverity.Medium);
|
||||
SeverityOption.AddItem(Loc.GetString("admin-note-editor-severity-high"), (int) Shared.Database.NoteSeverity.High);
|
||||
SeverityOption.OnItemSelected += OnSeverityChanged;
|
||||
|
||||
PermanentCheckBox.OnPressed += OnPermanentPressed;
|
||||
SecretCheckBox.OnPressed += OnSecretPressed;
|
||||
SubmitButton.OnPressed += OnSubmitButtonPressed;
|
||||
|
||||
if (note is null && !canCreate)
|
||||
{
|
||||
SubmitButton.Disabled = true;
|
||||
TypeOption.Disabled = true;
|
||||
SeverityOption.Disabled = true;
|
||||
}
|
||||
|
||||
if (note is not null)
|
||||
{
|
||||
Title = Loc.GetString("admin-note-editor-title-existing", ("id", note.Id), ("player", PlayerName), ("author", note.CreatedByName));
|
||||
NoteId = note.Id;
|
||||
|
||||
NoteType = note.NoteType;
|
||||
TypeOption.AddItem(Loc.GetString("admin-note-editor-type-server-ban"), (int) NoteType.ServerBan);
|
||||
TypeOption.AddItem(Loc.GetString("admin-note-editor-type-role-ban"), (int) NoteType.RoleBan);
|
||||
TypeOption.SelectId((int)NoteType);
|
||||
TypeOption.Disabled = true;
|
||||
|
||||
NoteTextEdit.InsertAtCursor(note.Message);
|
||||
|
||||
NoteSeverity = note.NoteSeverity ?? Shared.Database.NoteSeverity.Minor;
|
||||
SeverityOption.SelectId((int)NoteSeverity);
|
||||
SeverityOption.Disabled = note.NoteType is not (NoteType.Note or NoteType.ServerBan or NoteType.RoleBan);
|
||||
|
||||
IsSecret = note.Secret;
|
||||
SecretCheckBox.Pressed = note.Secret;
|
||||
SecretCheckBox.Disabled = note.NoteType is not NoteType.Note;
|
||||
ExpiryTime = note.ExpiryTime;
|
||||
if (ExpiryTime is not null)
|
||||
{
|
||||
PermanentCheckBox.Pressed = false;
|
||||
UpdatePermanentCheckboxFields();
|
||||
ExpiryLineEdit.Text = ExpiryTime.Value.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
}
|
||||
|
||||
if (!canEdit)
|
||||
{
|
||||
SubmitButton.Disabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string PlayerName { get; }
|
||||
private int NoteId { get; }
|
||||
private bool IsSecret { get; set; }
|
||||
private NoteType NoteType { get; set; }
|
||||
private NoteSeverity? NoteSeverity { get; set; } = Shared.Database.NoteSeverity.None;
|
||||
private DateTime? ExpiryTime { get; set; }
|
||||
private TimeSpan? DeleteResetOn { get; set; }
|
||||
|
||||
private void OnTypeChanged(OptionButton.ItemSelectedEventArgs args)
|
||||
{
|
||||
// We should be resetting the underlying values too but the server handles that anyway
|
||||
switch (args.Id)
|
||||
{
|
||||
case (int) NoteType.Note: // Note: your standard note, does nothing special
|
||||
NoteType = NoteType.Note;
|
||||
SecretCheckBox.Disabled = false;
|
||||
SecretCheckBox.Pressed = false;
|
||||
SeverityOption.Disabled = false;
|
||||
PermanentCheckBox.Pressed = true;
|
||||
UpdatePermanentCheckboxFields();
|
||||
break;
|
||||
case (int) NoteType.Message: // Message: these are shown to the player when they log on
|
||||
NoteType = NoteType.Message;
|
||||
SecretCheckBox.Disabled = true;
|
||||
SecretCheckBox.Pressed = false;
|
||||
SeverityOption.Disabled = true;
|
||||
SeverityOption.SelectId((int) Shared.Database.NoteSeverity.None);
|
||||
NoteSeverity = null;
|
||||
PermanentCheckBox.Pressed = false;
|
||||
UpdatePermanentCheckboxFields();
|
||||
break;
|
||||
case (int) NoteType.Watchlist: // Watchlist: these are always secret and only shown to admins when the player logs on
|
||||
NoteType = NoteType.Watchlist;
|
||||
SecretCheckBox.Disabled = true;
|
||||
SecretCheckBox.Pressed = true;
|
||||
SeverityOption.Disabled = true;
|
||||
SeverityOption.SelectId((int) Shared.Database.NoteSeverity.None);
|
||||
NoteSeverity = null;
|
||||
PermanentCheckBox.Pressed = false;
|
||||
UpdatePermanentCheckboxFields();
|
||||
break;
|
||||
default: // Wuh oh
|
||||
throw new ArgumentOutOfRangeException(nameof(args.Id), args.Id, "Unknown note type");
|
||||
}
|
||||
|
||||
TypeOption.SelectId(args.Id);
|
||||
}
|
||||
|
||||
private void OnPermanentPressed(BaseButton.ButtonEventArgs _)
|
||||
{
|
||||
UpdatePermanentCheckboxFields();
|
||||
}
|
||||
|
||||
private void UpdatePermanentCheckboxFields()
|
||||
{
|
||||
ExpiryLabel.Visible = !PermanentCheckBox.Pressed;
|
||||
ExpiryLineEdit.Visible = !PermanentCheckBox.Pressed;
|
||||
|
||||
ExpiryLineEdit.Text = !PermanentCheckBox.Pressed ? DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss") : string.Empty;
|
||||
}
|
||||
|
||||
private void OnSecretPressed(BaseButton.ButtonEventArgs _)
|
||||
{
|
||||
IsSecret = SecretCheckBox.Pressed;
|
||||
}
|
||||
|
||||
private void OnSeverityChanged(OptionButton.ItemSelectedEventArgs args)
|
||||
{
|
||||
NoteSeverity = (NoteSeverity) args.Id;
|
||||
SeverityOption.SelectId(args.Id);
|
||||
}
|
||||
|
||||
private void OnSubmitButtonPressed(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
if (!ParseExpiryTime())
|
||||
return;
|
||||
if (DeleteResetOn is null)
|
||||
{
|
||||
DeleteResetOn = _gameTiming.CurTime.Add(TimeSpan.FromSeconds(3));
|
||||
SubmitButton.Text = Loc.GetString("admin-note-editor-submit-confirm");
|
||||
SubmitButton.ModulateSelfOverride = Color.Red;
|
||||
// Task.Delay(3000).ContinueWith(_ => ResetSubmitButton()); // TODO: fix
|
||||
return;
|
||||
}
|
||||
|
||||
ResetSubmitButton();
|
||||
|
||||
SubmitPressed?.Invoke(NoteId, NoteType, Rope.Collapse(NoteTextEdit.TextRope), NoteSeverity, IsSecret, ExpiryTime);
|
||||
|
||||
if (Parent is null)
|
||||
{
|
||||
_console.ExecuteCommand($"adminnotes \"{PlayerName}\"");
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
// This checks for null for free, do not invert it as null always produces a false value
|
||||
if (DeleteResetOn > _gameTiming.CurTime)
|
||||
{
|
||||
ResetSubmitButton();
|
||||
DeleteResetOn = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetSubmitButton()
|
||||
{
|
||||
SubmitButton.Text = Loc.GetString("admin-note-editor-submit");
|
||||
SubmitButton.ModulateSelfOverride = null;
|
||||
UpdateDraw();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to parse the currently entered expiry time. As a side effect this function
|
||||
/// will colour its respective line edit to indicate an error
|
||||
/// </summary>
|
||||
/// <returns>True if parsing was successful, false if not</returns>
|
||||
private bool ParseExpiryTime()
|
||||
{
|
||||
// If the checkbox is pressed the note is permanent, so expiry is null
|
||||
if (PermanentCheckBox.Pressed)
|
||||
{
|
||||
ExpiryTime = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ExpiryLineEdit.Text) || !DateTime.TryParse(ExpiryLineEdit.Text, out var result) || DateTime.UtcNow > result)
|
||||
{
|
||||
ExpiryLineEdit.ModulateSelfOverride = Color.Red;
|
||||
return false;
|
||||
}
|
||||
|
||||
ExpiryTime = result;
|
||||
ExpiryLineEdit.ModulateSelfOverride = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (!disposing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PermanentCheckBox.OnPressed -= OnPermanentPressed;
|
||||
SecretCheckBox.OnPressed -= OnSecretPressed;
|
||||
SubmitButton.OnPressed -= OnSubmitButtonPressed;
|
||||
|
||||
SubmitPressed = null;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<Control
|
||||
<Control
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
@@ -8,7 +8,7 @@
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<GridContainer Columns="3">
|
||||
<cc:UICommandButton Command="kick" Text="{Loc admin-player-actions-window-title}" WindowType="{x:Type at:PlayerActionsWindow}" />
|
||||
<cc:UICommandButton Command="ban" Text="{Loc admin-player-actions-window-ban}" WindowType="{x:Type at:BanWindow}" />
|
||||
<cc:CommandButton Command="banpanel" Text="{Loc admin-player-actions-window-ban}" />
|
||||
<cc:CommandButton Command="aghost" Text="{Loc admin-player-actions-window-admin-ghost}" />
|
||||
<cc:UICommandButton Command="tpto" Text="{Loc admin-player-actions-window-teleport}" WindowType="{x:Type at:TeleportWindow}" />
|
||||
<cc:CommandButton Command="permissions" Text="{Loc admin-player-actions-window-permissions}" />
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
<DefaultWindow
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
Title="{Loc Ban}" MinSize="425 325">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc Player}" MinWidth="100" />
|
||||
<Control MinWidth="50" />
|
||||
<LineEdit Name="PlayerNameLine" MinWidth="100" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc Reason}" MinSize="100 0" />
|
||||
<Control MinSize="50 0" />
|
||||
<LineEdit Name="ReasonLine" MinSize="100 0" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc Minutes}" MinWidth="100" />
|
||||
<Control MinWidth="50" />
|
||||
<LineEdit Name="MinutesLine" MinWidth="100" HorizontalExpand="True" PlaceHolder="{Loc 0 minutes for a permanent ban}" />
|
||||
<Button Name="HourButton" Text="+1h (0)"/>
|
||||
<Button Name="DayButton" Text="+1d (0)"/>
|
||||
<Button Name="WeekButton" Text="+1w (0)"/>
|
||||
<Button Name="MonthButton" Text="+1M (0)"/>
|
||||
</BoxContainer>
|
||||
<cc:PlayerListControl Name="PlayerList" VerticalExpand="True" />
|
||||
<Control MinWidth="50" />
|
||||
<Button Name="SubmitButton" Text="{Loc Ban}" />
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
@@ -1,84 +0,0 @@
|
||||
using Content.Shared.Administration;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Utility;
|
||||
using static Robust.Client.UserInterface.Controls.LineEdit;
|
||||
|
||||
namespace Content.Client.Administration.UI.Tabs.AdminTab
|
||||
{
|
||||
[GenerateTypedNameReferences]
|
||||
[UsedImplicitly]
|
||||
public sealed partial class BanWindow : DefaultWindow
|
||||
{
|
||||
public BanWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
PlayerNameLine.OnTextChanged += _ => OnPlayerNameChanged();
|
||||
PlayerList.OnSelectionChanged += OnPlayerSelectionChanged;
|
||||
SubmitButton.OnPressed += SubmitButtonOnOnPressed;
|
||||
MinutesLine.OnTextChanged += UpdateButtonsText;
|
||||
HourButton.OnPressed += _ => AddMinutes(60);
|
||||
DayButton.OnPressed += _ => AddMinutes(1440);
|
||||
WeekButton.OnPressed += _ => AddMinutes(10080);
|
||||
MonthButton.OnPressed += _ => AddMinutes(43200);
|
||||
}
|
||||
|
||||
private bool TryGetMinutes(string str, out uint minutes)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(str))
|
||||
{
|
||||
minutes = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
return uint.TryParse(str, out minutes);
|
||||
}
|
||||
|
||||
private void AddMinutes(uint add)
|
||||
{
|
||||
if (!TryGetMinutes(MinutesLine.Text, out var minutes))
|
||||
return;
|
||||
|
||||
MinutesLine.Text = $"{minutes + add}";
|
||||
UpdateButtons(minutes+add);
|
||||
}
|
||||
|
||||
private void UpdateButtonsText(LineEditEventArgs obj)
|
||||
{
|
||||
if (!TryGetMinutes(obj.Text, out var minutes))
|
||||
return;
|
||||
UpdateButtons(minutes);
|
||||
}
|
||||
|
||||
private void UpdateButtons(uint minutes)
|
||||
{
|
||||
HourButton.Text = $"+1h ({minutes / 60})";
|
||||
DayButton.Text = $"+1d ({minutes / 1440})";
|
||||
WeekButton.Text = $"+1w ({minutes / 10080})";
|
||||
MonthButton.Text = $"+1M ({minutes / 43200})";
|
||||
}
|
||||
|
||||
private void OnPlayerNameChanged()
|
||||
{
|
||||
SubmitButton.Disabled = string.IsNullOrEmpty(PlayerNameLine.Text);
|
||||
}
|
||||
|
||||
public void OnPlayerSelectionChanged(PlayerInfo? player)
|
||||
{
|
||||
PlayerNameLine.Text = player?.Username ?? string.Empty;
|
||||
OnPlayerNameChanged();
|
||||
}
|
||||
|
||||
private void SubmitButtonOnOnPressed(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
// Small verification if Player Name exists
|
||||
IoCManager.Resolve<IClientConsoleHost>().ExecuteCommand(
|
||||
$"ban \"{PlayerNameLine.Text}\" \"{CommandParsing.Escape(ReasonLine.Text)}\" {MinutesLine.Text}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user