Better notes and bans (#14228)

Co-authored-by: Chief-Engineer <119664036+Chief-Engineer@users.noreply.github.com>
This commit is contained in:
Riggle
2023-07-21 13:38:52 +02:00
committed by GitHub
parent c6cb6ad928
commit 579913b617
84 changed files with 9820 additions and 886 deletions

View File

@@ -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>

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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>

View File

@@ -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;
}
}

View File

@@ -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"/>

View File

@@ -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);

View File

@@ -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>

View File

@@ -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()
{

View 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>

View 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;
}
}