Improve admin message seen/dismiss state. (#26223)
Fixes #26211 Admin messages now have separate "seen" and "dismissed" fields. The idea is that an admin should be able to tell whether a user pressed the "dismiss for now" button. Instead of using "seen" as "show this message to players when they join", "dismissed" is now used for this. Existing notes in the database will automatically be marked as dismissed on migration. A note cannot be dismissed without being seen (enforced via constraint in the database too, aren't I fancy). As part of this, it has become impossible for a player to play without dismissing the message in some form. Instead of a shitty popup window, the popup is now a fullscreen overlay that blocks clicks behind it, making the game unplayable. Also, if a user somehow has multiple messages they will be combined into one popup. Also I had enough respect for the codebase to make it look better and clean up the code somewhat. Yippee.
This commit is contained in:
committed by
GitHub
parent
f87480dd36
commit
d776c4b392
@@ -2,6 +2,7 @@ using Content.Client.Eui;
|
|||||||
using Content.Shared.Administration.Notes;
|
using Content.Shared.Administration.Notes;
|
||||||
using Content.Shared.Eui;
|
using Content.Shared.Eui;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
using static Content.Shared.Administration.Notes.AdminMessageEuiMsg;
|
using static Content.Shared.Administration.Notes.AdminMessageEuiMsg;
|
||||||
|
|
||||||
namespace Content.Client.Administration.UI.AdminRemarks;
|
namespace Content.Client.Administration.UI.AdminRemarks;
|
||||||
@@ -14,9 +15,8 @@ public sealed class AdminMessageEui : BaseEui
|
|||||||
public AdminMessageEui()
|
public AdminMessageEui()
|
||||||
{
|
{
|
||||||
_popup = new AdminMessagePopupWindow();
|
_popup = new AdminMessagePopupWindow();
|
||||||
_popup.OnAcceptPressed += () => SendMessage(new Accept());
|
_popup.OnAcceptPressed += () => SendMessage(new Dismiss(true));
|
||||||
_popup.OnDismissPressed += () => SendMessage(new Dismiss());
|
_popup.OnDismissPressed += () => SendMessage(new Dismiss(false));
|
||||||
_popup.OnClose += () => SendMessage(new CloseEuiMessage());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void HandleState(EuiStateBase state)
|
public override void HandleState(EuiStateBase state)
|
||||||
@@ -26,13 +26,17 @@ public sealed class AdminMessageEui : BaseEui
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_popup.SetMessage(s.Message);
|
_popup.SetState(s);
|
||||||
_popup.SetDetails(s.AdminName, s.AddedOn);
|
|
||||||
_popup.Timer = s.Time;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Opened()
|
public override void Opened()
|
||||||
{
|
{
|
||||||
_popup.OpenCentered();
|
_popup.UserInterfaceManager.WindowRoot.AddChild(_popup);
|
||||||
|
LayoutContainer.SetAnchorPreset(_popup, LayoutContainer.LayoutPreset.Wide);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Closed()
|
||||||
|
{
|
||||||
|
_popup.Orphan();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<Control xmlns="https://spacestation14.io" Margin="0 0 0 8">
|
||||||
|
<BoxContainer Orientation="Vertical">
|
||||||
|
<RichTextLabel Name="Admin" Margin="0 0 0 4" />
|
||||||
|
<RichTextLabel Name="Message" Margin="2 0 0 0" />
|
||||||
|
</BoxContainer>
|
||||||
|
</Control>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using Content.Shared.Administration.Notes;
|
||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Client.Administration.UI.AdminRemarks;
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class AdminMessagePopupMessage : Control
|
||||||
|
{
|
||||||
|
public AdminMessagePopupMessage(AdminMessageEuiState.Message message)
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
|
||||||
|
Admin.SetMessage(FormattedMessage.FromMarkup(Loc.GetString(
|
||||||
|
"admin-notes-message-admin",
|
||||||
|
("admin", message.AdminName),
|
||||||
|
("date", message.AddedOn.ToLocalTime()))));
|
||||||
|
|
||||||
|
Message.SetMessage(message.Text);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,22 +1,36 @@
|
|||||||
<ui:FancyWindow xmlns="https://spacestation14.io"
|
<Control xmlns="https://spacestation14.io"
|
||||||
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
|
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client">
|
||||||
VerticalExpand="True" HorizontalExpand="True"
|
<PanelContainer MouseFilter="Stop">
|
||||||
Title="{Loc admin-notes-message-window-title}"
|
<PanelContainer.PanelOverride>
|
||||||
MinSize="600 170">
|
<!-- semi-transparent background -->
|
||||||
<PanelContainer VerticalExpand="True" HorizontalExpand="True" StyleClasses="BackgroundDark">
|
<gfx:StyleBoxFlat BackgroundColor="#000000AA" />
|
||||||
<ScrollContainer HScrollEnabled="False" VerticalExpand="True" HorizontalExpand="True" Margin="4">
|
</PanelContainer.PanelOverride>
|
||||||
<BoxContainer Orientation="Vertical" SeparationOverride="10" VerticalAlignment="Bottom">
|
|
||||||
<Label Name="AdminLabel" Text="Loading..." />
|
<Control HorizontalAlignment="Center" VerticalAlignment="Center" MaxWidth="600">
|
||||||
<RichTextLabel Name="MessageLabel" />
|
<PanelContainer StyleClasses="AngleRect" />
|
||||||
|
|
||||||
|
<BoxContainer Orientation="Vertical" Margin="4">
|
||||||
|
<RichTextLabel Name="Description" />
|
||||||
|
|
||||||
|
<!-- Contains actual messages -->
|
||||||
|
<ScrollContainer HScrollEnabled="False" Margin="4" VerticalExpand="True" ReturnMeasure="True" MaxHeight="400">
|
||||||
|
<BoxContainer Orientation="Vertical" Name="MessageContainer" Margin="0 2 0 0" />
|
||||||
|
</ScrollContainer>
|
||||||
|
|
||||||
<Label Name="WaitLabel" />
|
<Label Name="WaitLabel" />
|
||||||
<BoxContainer Orientation="Horizontal">
|
<BoxContainer Orientation="Horizontal">
|
||||||
<Button Name="DismissButton"
|
<Button Name="DismissButton"
|
||||||
Text="{Loc 'admin-notes-message-dismiss'}" />
|
Text="{Loc 'admin-notes-message-dismiss'}"
|
||||||
|
Disabled="True"
|
||||||
|
HorizontalExpand="True"
|
||||||
|
StyleClasses="OpenRight" />
|
||||||
<Button Name="AcceptButton"
|
<Button Name="AcceptButton"
|
||||||
Text="{Loc 'admin-notes-message-accept'}"
|
Text="{Loc 'admin-notes-message-accept'}"
|
||||||
Disabled="True" />
|
Disabled="True"
|
||||||
|
HorizontalExpand="True"
|
||||||
|
StyleClasses="OpenLeft" />
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
</ScrollContainer>
|
</Control>
|
||||||
</PanelContainer>
|
</PanelContainer>
|
||||||
</ui:FancyWindow>
|
</Control>
|
||||||
|
|||||||
@@ -1,56 +1,65 @@
|
|||||||
using Content.Client.UserInterface.Controls;
|
using Content.Client.Stylesheets;
|
||||||
|
using Content.Shared.Administration.Notes;
|
||||||
using Robust.Client.AutoGenerated;
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
using Robust.Client.UserInterface.XAML;
|
using Robust.Client.UserInterface.XAML;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Client.Administration.UI.AdminRemarks;
|
namespace Content.Client.Administration.UI.AdminRemarks;
|
||||||
|
|
||||||
[GenerateTypedNameReferences]
|
[GenerateTypedNameReferences]
|
||||||
public sealed partial class AdminMessagePopupWindow : FancyWindow
|
public sealed partial class AdminMessagePopupWindow : Control
|
||||||
{
|
{
|
||||||
private float _timer = float.MaxValue;
|
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? OnDismissPressed;
|
||||||
|
|
||||||
public event Action? OnAcceptPressed;
|
public event Action? OnAcceptPressed;
|
||||||
|
|
||||||
public AdminMessagePopupWindow()
|
public AdminMessagePopupWindow()
|
||||||
{
|
{
|
||||||
RobustXamlLoader.Load(this);
|
RobustXamlLoader.Load(this);
|
||||||
|
|
||||||
|
Stylesheet = IoCManager.Resolve<IStylesheetManager>().SheetSpace;
|
||||||
|
|
||||||
AcceptButton.OnPressed += OnAcceptButtonPressed;
|
AcceptButton.OnPressed += OnAcceptButtonPressed;
|
||||||
DismissButton.OnPressed += OnDismissButtonPressed;
|
DismissButton.OnPressed += OnDismissButtonPressed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetMessage(string message)
|
public float Timer
|
||||||
{
|
{
|
||||||
MessageLabel.SetMessage(message);
|
get => _timer;
|
||||||
|
private set
|
||||||
|
{
|
||||||
|
WaitLabel.Text = Loc.GetString("admin-notes-message-wait", ("time", MathF.Floor(value)));
|
||||||
|
_timer = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetDetails(string adminName, DateTime addedOn)
|
public void SetState(AdminMessageEuiState state)
|
||||||
{
|
{
|
||||||
AdminLabel.Text = Loc.GetString("admin-notes-message-admin", ("admin", adminName), ("date", addedOn));
|
Timer = (float) state.Time.TotalSeconds;
|
||||||
|
|
||||||
|
MessageContainer.RemoveAllChildren();
|
||||||
|
|
||||||
|
foreach (var message in state.Messages)
|
||||||
|
{
|
||||||
|
MessageContainer.AddChild(new AdminMessagePopupMessage(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
Description.SetMessage(FormattedMessage.FromMarkup(Loc.GetString("admin-notes-message-desc", ("count", state.Messages.Length))));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDismissButtonPressed(BaseButton.ButtonEventArgs obj)
|
private void OnDismissButtonPressed(BaseButton.ButtonEventArgs obj)
|
||||||
{
|
{
|
||||||
OnDismissPressed?.Invoke();
|
OnDismissPressed?.Invoke();
|
||||||
Close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnAcceptButtonPressed(BaseButton.ButtonEventArgs obj)
|
private void OnAcceptButtonPressed(BaseButton.ButtonEventArgs obj)
|
||||||
{
|
{
|
||||||
OnAcceptPressed?.Invoke();
|
OnAcceptPressed?.Invoke();
|
||||||
Close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void FrameUpdate(FrameEventArgs args)
|
protected override void FrameUpdate(FrameEventArgs args)
|
||||||
@@ -70,6 +79,7 @@ public sealed partial class AdminMessagePopupWindow : FancyWindow
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
AcceptButton.Disabled = false;
|
AcceptButton.Disabled = false;
|
||||||
|
DismissButton.Disabled = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1768
Content.Server.Database/Migrations/Postgres/20240318022005_AdminMessageDismiss.Designer.cs
generated
Normal file
1768
Content.Server.Database/Migrations/Postgres/20240318022005_AdminMessageDismiss.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,40 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Content.Server.Database.Migrations.Postgres
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AdminMessageDismiss : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "dismissed",
|
||||||
|
table: "admin_messages",
|
||||||
|
type: "boolean",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
|
||||||
|
migrationBuilder.Sql("UPDATE admin_messages SET dismissed = seen");
|
||||||
|
|
||||||
|
migrationBuilder.AddCheckConstraint(
|
||||||
|
name: "NotDismissedAndSeen",
|
||||||
|
table: "admin_messages",
|
||||||
|
sql: "NOT dismissed OR seen");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropCheckConstraint(
|
||||||
|
name: "NotDismissedAndSeen",
|
||||||
|
table: "admin_messages");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "dismissed",
|
||||||
|
table: "admin_messages");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -183,6 +183,10 @@ namespace Content.Server.Database.Migrations.Postgres
|
|||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("deleted_by_id");
|
.HasColumnName("deleted_by_id");
|
||||||
|
|
||||||
|
b.Property<bool>("Dismissed")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("dismissed");
|
||||||
|
|
||||||
b.Property<DateTime?>("ExpirationTime")
|
b.Property<DateTime?>("ExpirationTime")
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasColumnName("expiration_time");
|
.HasColumnName("expiration_time");
|
||||||
@@ -232,7 +236,10 @@ namespace Content.Server.Database.Migrations.Postgres
|
|||||||
b.HasIndex("RoundId")
|
b.HasIndex("RoundId")
|
||||||
.HasDatabaseName("IX_admin_messages_round_id");
|
.HasDatabaseName("IX_admin_messages_round_id");
|
||||||
|
|
||||||
b.ToTable("admin_messages", (string)null);
|
b.ToTable("admin_messages", null, t =>
|
||||||
|
{
|
||||||
|
t.HasCheckConstraint("NotDismissedAndSeen", "NOT dismissed OR seen");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.AdminNote", b =>
|
modelBuilder.Entity("Content.Server.Database.AdminNote", b =>
|
||||||
|
|||||||
1699
Content.Server.Database/Migrations/Sqlite/20240318021959_AdminMessageDismiss.Designer.cs
generated
Normal file
1699
Content.Server.Database/Migrations/Sqlite/20240318021959_AdminMessageDismiss.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,40 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Content.Server.Database.Migrations.Sqlite
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AdminMessageDismiss : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "dismissed",
|
||||||
|
table: "admin_messages",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
|
||||||
|
migrationBuilder.Sql("UPDATE admin_messages SET dismissed = seen");
|
||||||
|
|
||||||
|
migrationBuilder.AddCheckConstraint(
|
||||||
|
name: "NotDismissedAndSeen",
|
||||||
|
table: "admin_messages",
|
||||||
|
sql: "NOT dismissed OR seen");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropCheckConstraint(
|
||||||
|
name: "NotDismissedAndSeen",
|
||||||
|
table: "admin_messages");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "dismissed",
|
||||||
|
table: "admin_messages");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -166,6 +166,10 @@ namespace Content.Server.Database.Migrations.Sqlite
|
|||||||
.HasColumnType("TEXT")
|
.HasColumnType("TEXT")
|
||||||
.HasColumnName("deleted_by_id");
|
.HasColumnName("deleted_by_id");
|
||||||
|
|
||||||
|
b.Property<bool>("Dismissed")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("dismissed");
|
||||||
|
|
||||||
b.Property<DateTime?>("ExpirationTime")
|
b.Property<DateTime?>("ExpirationTime")
|
||||||
.HasColumnType("TEXT")
|
.HasColumnType("TEXT")
|
||||||
.HasColumnName("expiration_time");
|
.HasColumnName("expiration_time");
|
||||||
@@ -215,7 +219,10 @@ namespace Content.Server.Database.Migrations.Sqlite
|
|||||||
b.HasIndex("RoundId")
|
b.HasIndex("RoundId")
|
||||||
.HasDatabaseName("IX_admin_messages_round_id");
|
.HasDatabaseName("IX_admin_messages_round_id");
|
||||||
|
|
||||||
b.ToTable("admin_messages", (string)null);
|
b.ToTable("admin_messages", null, t =>
|
||||||
|
{
|
||||||
|
t.HasCheckConstraint("NotDismissedAndSeen", "NOT dismissed OR seen");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.AdminNote", b =>
|
modelBuilder.Entity("Content.Server.Database.AdminNote", b =>
|
||||||
|
|||||||
@@ -268,6 +268,11 @@ namespace Content.Server.Database
|
|||||||
.HasPrincipalKey(author => author.UserId)
|
.HasPrincipalKey(author => author.UserId)
|
||||||
.OnDelete(DeleteBehavior.SetNull);
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
// A message cannot be "dismissed" without also being "seen".
|
||||||
|
modelBuilder.Entity<AdminMessage>().ToTable(t =>
|
||||||
|
t.HasCheckConstraint("NotDismissedAndSeen",
|
||||||
|
"NOT dismissed OR seen"));
|
||||||
|
|
||||||
modelBuilder.Entity<ServerBan>()
|
modelBuilder.Entity<ServerBan>()
|
||||||
.HasOne(ban => ban.CreatedBy)
|
.HasOne(ban => ban.CreatedBy)
|
||||||
.WithMany(author => author.AdminServerBansCreated)
|
.WithMany(author => author.AdminServerBansCreated)
|
||||||
@@ -969,6 +974,15 @@ namespace Content.Server.Database
|
|||||||
[ForeignKey("DeletedBy")] public Guid? DeletedById { get; set; }
|
[ForeignKey("DeletedBy")] public Guid? DeletedById { get; set; }
|
||||||
public Player? DeletedBy { get; set; }
|
public Player? DeletedBy { get; set; }
|
||||||
public DateTime? DeletedAt { get; set; }
|
public DateTime? DeletedAt { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the message has been seen at least once by the player.
|
||||||
|
/// </summary>
|
||||||
public bool Seen { get; set; }
|
public bool Seen { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the message has been dismissed permanently by the player.
|
||||||
|
/// </summary>
|
||||||
|
public bool Dismissed { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
|
using System.Linq;
|
||||||
using Content.Server.Database;
|
using Content.Server.Database;
|
||||||
using Content.Server.EUI;
|
using Content.Server.EUI;
|
||||||
using Content.Shared.Administration.Notes;
|
using Content.Shared.Administration.Notes;
|
||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
using Content.Shared.Eui;
|
using Content.Shared.Eui;
|
||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
using static Content.Shared.Administration.Notes.AdminMessageEuiMsg;
|
using static Content.Shared.Administration.Notes.AdminMessageEuiMsg;
|
||||||
|
|
||||||
namespace Content.Server.Administration.Notes;
|
namespace Content.Server.Administration.Notes;
|
||||||
@@ -12,32 +14,33 @@ public sealed class AdminMessageEui : BaseEui
|
|||||||
{
|
{
|
||||||
[Dependency] private readonly IAdminNotesManager _notesMan = default!;
|
[Dependency] private readonly IAdminNotesManager _notesMan = default!;
|
||||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||||
private readonly float _closeWait;
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
private AdminMessageRecord? _message;
|
|
||||||
private DateTime _startTime;
|
|
||||||
|
|
||||||
public AdminMessageEui()
|
private readonly TimeSpan _closeWait;
|
||||||
|
private readonly TimeSpan _endTime;
|
||||||
|
private readonly AdminMessageRecord[] _messages;
|
||||||
|
|
||||||
|
public AdminMessageEui(AdminMessageRecord[] messages)
|
||||||
{
|
{
|
||||||
IoCManager.InjectDependencies(this);
|
IoCManager.InjectDependencies(this);
|
||||||
_closeWait = _cfg.GetCVar(CCVars.MessageWaitTime);
|
_closeWait = TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.MessageWaitTime));
|
||||||
|
_endTime = _gameTiming.RealTime + _closeWait;
|
||||||
|
_messages = messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetMessage(AdminMessageRecord message)
|
public override void Opened()
|
||||||
{
|
{
|
||||||
_message = message;
|
|
||||||
_startTime = DateTime.UtcNow;
|
|
||||||
StateDirty();
|
StateDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override EuiStateBase GetNewState()
|
public override EuiStateBase GetNewState()
|
||||||
{
|
{
|
||||||
if (_message == null)
|
|
||||||
return new AdminMessageEuiState(float.MaxValue, "An error has occurred.", string.Empty, DateTime.MinValue);
|
|
||||||
return new AdminMessageEuiState(
|
return new AdminMessageEuiState(
|
||||||
_closeWait,
|
_closeWait,
|
||||||
_message.Message,
|
_messages.Select(x => new AdminMessageEuiState.Message(
|
||||||
_message.CreatedBy?.LastSeenUserName ?? "[System]",
|
x.Message,
|
||||||
_message.CreatedAt.UtcDateTime
|
x.CreatedBy?.LastSeenUserName ?? Loc.GetString("admin-notes-fallback-admin-name"),
|
||||||
|
x.CreatedAt.UtcDateTime)).ToArray()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,15 +50,14 @@ public sealed class AdminMessageEui : BaseEui
|
|||||||
|
|
||||||
switch (msg)
|
switch (msg)
|
||||||
{
|
{
|
||||||
case Accept:
|
case Dismiss dismiss:
|
||||||
if (_message == null)
|
if (_gameTiming.RealTime < _endTime)
|
||||||
break;
|
return;
|
||||||
// No escape
|
|
||||||
if (DateTime.UtcNow - _startTime >= TimeSpan.FromSeconds(_closeWait))
|
foreach (var message in _messages)
|
||||||
await _notesMan.MarkMessageAsSeen(_message.Id);
|
{
|
||||||
Close();
|
await _notesMan.MarkMessageAsSeen(message.Id, dismiss.Permanent);
|
||||||
break;
|
}
|
||||||
case Dismiss:
|
|
||||||
Close();
|
Close();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -331,9 +331,9 @@ public sealed class AdminNotesManager : IAdminNotesManager, IPostInjectInit
|
|||||||
return await _db.GetMessages(player);
|
return await _db.GetMessages(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task MarkMessageAsSeen(int id)
|
public async Task MarkMessageAsSeen(int id, bool dismissedToo)
|
||||||
{
|
{
|
||||||
await _db.MarkMessageAsSeen(id);
|
await _db.MarkMessageAsSeen(id, dismissedToo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PostInject()
|
public void PostInject()
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Linq;
|
||||||
using Content.Server.Administration.Commands;
|
using Content.Server.Administration.Commands;
|
||||||
using Content.Server.Chat.Managers;
|
using Content.Server.Chat.Managers;
|
||||||
using Content.Server.EUI;
|
using Content.Server.EUI;
|
||||||
@@ -52,7 +53,7 @@ public sealed class AdminNotesSystem : EntitySystem
|
|||||||
|
|
||||||
private async void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
private async void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.NewStatus != SessionStatus.Connected)
|
if (e.NewStatus != SessionStatus.InGame)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var messages = await _notes.GetNewMessages(e.Session.UserId);
|
var messages = await _notes.GetNewMessages(e.Session.UserId);
|
||||||
@@ -69,19 +70,11 @@ public sealed class AdminNotesSystem : EntitySystem
|
|||||||
_chat.SendAdminAlert(Loc.GetString("admin-notes-watchlist", ("player", username), ("message", watchlist.Message)));
|
_chat.SendAdminAlert(Loc.GetString("admin-notes-watchlist", ("player", username), ("message", watchlist.Message)));
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var message in messages)
|
var messagesToShow = messages.OrderBy(x => x.CreatedAt).Where(x => !x.Dismissed).ToArray();
|
||||||
{
|
if (messagesToShow.Length == 0)
|
||||||
var messageString = Loc.GetString("admin-notes-new-message", ("admin", message.CreatedBy?.LastSeenUserName ?? "[System]"), ("message", message.Message));
|
return;
|
||||||
// Only open the popup if the user hasn't seen it yet
|
|
||||||
if (!message.Seen)
|
|
||||||
{
|
|
||||||
var ui = new AdminMessageEui();
|
|
||||||
_euis.OpenEui(ui, e.Session);
|
|
||||||
ui.SetMessage(message);
|
|
||||||
|
|
||||||
// Only send the message if they haven't seen it yet
|
var ui = new AdminMessageEui(messagesToShow);
|
||||||
_chat.DispatchServerMessage(e.Session, messageString);
|
_euis.OpenEui(ui, e.Session);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,5 +45,13 @@ public interface IAdminNotesManager
|
|||||||
/// <param name="player">Desired player's <see cref="Guid"/></param>
|
/// <param name="player">Desired player's <see cref="Guid"/></param>
|
||||||
/// <returns>All unread messages</returns>
|
/// <returns>All unread messages</returns>
|
||||||
Task<List<AdminMessageRecord>> GetNewMessages(Guid player);
|
Task<List<AdminMessageRecord>> GetNewMessages(Guid player);
|
||||||
Task MarkMessageAsSeen(int id);
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mark an admin message as being seen by the target player.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">The database ID of the admin message.</param>
|
||||||
|
/// <param name="dismissedToo">
|
||||||
|
/// If true, the message is "permanently dismissed" and will not be shown to the player again when they join.
|
||||||
|
/// </param>
|
||||||
|
Task MarkMessageAsSeen(int id, bool dismissedToo);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,7 +111,8 @@ public sealed record AdminMessageRecord(
|
|||||||
bool Deleted,
|
bool Deleted,
|
||||||
PlayerRecord? DeletedBy,
|
PlayerRecord? DeletedBy,
|
||||||
DateTimeOffset? DeletedAt,
|
DateTimeOffset? DeletedAt,
|
||||||
bool Seen) : IAdminRemarksRecord;
|
bool Seen,
|
||||||
|
bool Dismissed) : IAdminRemarksRecord;
|
||||||
|
|
||||||
|
|
||||||
public sealed record PlayerRecord(
|
public sealed record PlayerRecord(
|
||||||
|
|||||||
@@ -1143,7 +1143,8 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
|
|||||||
entity.Deleted,
|
entity.Deleted,
|
||||||
MakePlayerRecord(entity.DeletedBy),
|
MakePlayerRecord(entity.DeletedBy),
|
||||||
NormalizeDatabaseTime(entity.DeletedAt),
|
NormalizeDatabaseTime(entity.DeletedAt),
|
||||||
entity.Seen);
|
entity.Seen,
|
||||||
|
entity.Dismissed);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ServerBanNoteRecord?> GetServerBanAsNoteAsync(int id)
|
public async Task<ServerBanNoteRecord?> GetServerBanAsNoteAsync(int id)
|
||||||
@@ -1422,11 +1423,13 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
|
|||||||
return entities.Select(MakeAdminMessageRecord).ToList();
|
return entities.Select(MakeAdminMessageRecord).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task MarkMessageAsSeen(int id)
|
public async Task MarkMessageAsSeen(int id, bool dismissedToo)
|
||||||
{
|
{
|
||||||
await using var db = await GetDb();
|
await using var db = await GetDb();
|
||||||
var message = await db.DbContext.AdminMessages.SingleAsync(m => m.Id == id);
|
var message = await db.DbContext.AdminMessages.SingleAsync(m => m.Id == id);
|
||||||
message.Seen = true;
|
message.Seen = true;
|
||||||
|
if (dismissedToo)
|
||||||
|
message.Dismissed = true;
|
||||||
await db.DbContext.SaveChangesAsync();
|
await db.DbContext.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -274,7 +274,15 @@ namespace Content.Server.Database
|
|||||||
Task DeleteAdminMessage(int id, Guid deletedBy, DateTimeOffset deletedAt);
|
Task DeleteAdminMessage(int id, Guid deletedBy, DateTimeOffset deletedAt);
|
||||||
Task HideServerBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt);
|
Task HideServerBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt);
|
||||||
Task HideServerRoleBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt);
|
Task HideServerRoleBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt);
|
||||||
Task MarkMessageAsSeen(int id);
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mark an admin message as being seen by the target player.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">The database ID of the admin message.</param>
|
||||||
|
/// <param name="dismissedToo">
|
||||||
|
/// If true, the message is "permanently dismissed" and will not be shown to the player again when they join.
|
||||||
|
/// </param>
|
||||||
|
Task MarkMessageAsSeen(int id, bool dismissedToo);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
@@ -847,10 +855,10 @@ namespace Content.Server.Database
|
|||||||
return RunDbCommand(() => _db.HideServerRoleBanFromNotes(id, deletedBy, deletedAt));
|
return RunDbCommand(() => _db.HideServerRoleBanFromNotes(id, deletedBy, deletedAt));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task MarkMessageAsSeen(int id)
|
public Task MarkMessageAsSeen(int id, bool dismissedToo)
|
||||||
{
|
{
|
||||||
DbWriteOpsMetric.Inc();
|
DbWriteOpsMetric.Inc();
|
||||||
return RunDbCommand(() => _db.MarkMessageAsSeen(id));
|
return RunDbCommand(() => _db.MarkMessageAsSeen(id, dismissedToo));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrapper functions to run DB commands from the thread pool.
|
// Wrapper functions to run DB commands from the thread pool.
|
||||||
|
|||||||
@@ -1,39 +1,28 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Content.Shared.Eui;
|
using Content.Shared.Eui;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
namespace Content.Shared.Administration.Notes;
|
namespace Content.Shared.Administration.Notes;
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
public sealed class AdminMessageEuiState : EuiStateBase
|
public sealed class AdminMessageEuiState(TimeSpan time, AdminMessageEuiState.Message[] messages) : EuiStateBase
|
||||||
{
|
{
|
||||||
public float Time { get; set; }
|
public TimeSpan Time { get; } = time;
|
||||||
public string Message { get; set; }
|
public Message[] Messages { get; } = messages;
|
||||||
public string AdminName { get; set; }
|
|
||||||
public DateTime AddedOn { get; set; }
|
|
||||||
|
|
||||||
public AdminMessageEuiState(float time, string message, string adminName, DateTime addedOn)
|
[Serializable]
|
||||||
|
public sealed class Message(string text, string adminName, DateTime addedOn)
|
||||||
{
|
{
|
||||||
Message = message;
|
public string Text = text;
|
||||||
Time = time;
|
public string AdminName = adminName;
|
||||||
AdminName = adminName;
|
public DateTime AddedOn = addedOn;
|
||||||
AddedOn = addedOn;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class AdminMessageEuiMsg
|
public static class AdminMessageEuiMsg
|
||||||
{
|
{
|
||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
public sealed class Accept : EuiMessageBase
|
public sealed class Dismiss(bool permanent) : EuiMessageBase
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
|
||||||
public sealed class Dismiss : EuiMessageBase
|
|
||||||
{
|
{
|
||||||
|
public bool Permanent { get; } = permanent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,8 +23,11 @@ admin-notes-hide = Hide
|
|||||||
admin-notes-delete-confirm = Confirm delete
|
admin-notes-delete-confirm = Confirm delete
|
||||||
admin-notes-edited = Last edit by {$author} on {$date}
|
admin-notes-edited = Last edit by {$author} on {$date}
|
||||||
admin-notes-unbanned = Unbanned by {$admin} on {$date}
|
admin-notes-unbanned = Unbanned by {$admin} on {$date}
|
||||||
admin-notes-message-window-title = Alert!
|
admin-notes-message-desc = [color=white]You have received { $count ->
|
||||||
admin-notes-message-admin = New message from {$admin}, added on {$date}
|
[1] an administrative message
|
||||||
|
*[other] administrative messages
|
||||||
|
} since the last time you played on this server.[/color]
|
||||||
|
admin-notes-message-admin = From [bold]{ $admin }[/bold], written on { TOSTRING($date, "f") }:
|
||||||
admin-notes-message-wait = The accept button will be enabled after {$time} seconds.
|
admin-notes-message-wait = The accept button will be enabled after {$time} seconds.
|
||||||
admin-notes-message-accept = Dismiss permanently
|
admin-notes-message-accept = Dismiss permanently
|
||||||
admin-notes-message-dismiss = Dismiss for now
|
admin-notes-message-dismiss = Dismiss for now
|
||||||
@@ -68,6 +71,7 @@ admin-notes-verb-text = Open Admin Notes
|
|||||||
# Watchlist and message login
|
# Watchlist and message login
|
||||||
admin-notes-watchlist = Watchlist for {$player}: {$message}
|
admin-notes-watchlist = Watchlist for {$player}: {$message}
|
||||||
admin-notes-new-message = You've received an admin message from {$admin}: {$message}
|
admin-notes-new-message = You've received an admin message from {$admin}: {$message}
|
||||||
|
admin-notes-fallback-admin-name = [System]
|
||||||
|
|
||||||
# Admin remarks
|
# Admin remarks
|
||||||
admin-remarks-command-description = Opens the admin remarks page
|
admin-remarks-command-description = Opens the admin remarks page
|
||||||
|
|||||||
Reference in New Issue
Block a user