diff --git a/Content.Client/Administration/UI/Tabs/AdminTab/AdminTab.xaml b/Content.Client/Administration/UI/Tabs/AdminTab/AdminTab.xaml
index 8b68487547..0d4668d5bf 100644
--- a/Content.Client/Administration/UI/Tabs/AdminTab/AdminTab.xaml
+++ b/Content.Client/Administration/UI/Tabs/AdminTab/AdminTab.xaml
@@ -16,6 +16,7 @@
+
diff --git a/Content.Client/UserInterface/Systems/Chat/ChatWindow.xaml b/Content.Client/UserInterface/Systems/Chat/ChatWindow.xaml
new file mode 100644
index 0000000000..49777850cf
--- /dev/null
+++ b/Content.Client/UserInterface/Systems/Chat/ChatWindow.xaml
@@ -0,0 +1,7 @@
+
+
+
diff --git a/Content.Client/UserInterface/Systems/Chat/ChatWindow.xaml.cs b/Content.Client/UserInterface/Systems/Chat/ChatWindow.xaml.cs
new file mode 100644
index 0000000000..abf421cf0c
--- /dev/null
+++ b/Content.Client/UserInterface/Systems/Chat/ChatWindow.xaml.cs
@@ -0,0 +1,44 @@
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Chat;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.UserInterface.Systems.Chat;
+
+///
+/// Window which only holds a single chatbox, useful for monitoring multiple chats simultaneously.
+///
+[GenerateTypedNameReferences]
+public sealed partial class ChatWindow : FancyWindow
+{
+ public ChatWindow()
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+
+ // These are necessary for the controls inside the chatbox to initialize correctly:
+ Chatbox.Repopulate();
+ var controller = UserInterfaceManager.GetUIController();
+ controller.UpdateSelectedChannel(Chatbox);
+ }
+
+ ///
+ /// Helper method to configure this window to be useful for admins.
+ /// Sets incoming filters to only admin chats and output to admin channel
+ ///
+ public void ConfigureForAdminChat()
+ {
+ Chatbox.ChatInput.ChannelSelector.Select(ChatSelectChannel.Admin);
+
+ var filter = Chatbox.ChatInput.FilterButton.Popup;
+ foreach (var c in Enum.GetValues(typeof(ChatChannel)))
+ {
+ var channel = (ChatChannel)c;
+ var isAdminInterest = channel == ChatChannel.Admin
+ || channel == ChatChannel.AdminChat
+ || channel == ChatChannel.AdminAlert
+ || channel == ChatChannel.AdminRelated;
+ filter.SetActive(channel, isAdminInterest);
+ }
+ }
+}
diff --git a/Content.Client/UserInterface/Systems/Chat/ChatWindowCommand.cs b/Content.Client/UserInterface/Systems/Chat/ChatWindowCommand.cs
new file mode 100644
index 0000000000..8c3aea8907
--- /dev/null
+++ b/Content.Client/UserInterface/Systems/Chat/ChatWindowCommand.cs
@@ -0,0 +1,35 @@
+using JetBrains.Annotations;
+using Robust.Shared.Console;
+
+namespace Content.Client.UserInterface.Systems.Chat;
+
+///
+/// Command which creates a window containing a chatbox
+///
+[UsedImplicitly]
+public sealed class ChatWindowCommand : LocalizedCommands
+{
+ public override string Command => "chatwindow";
+
+ public override void Execute(IConsoleShell shell, string argStr, string[] args)
+ {
+ var window = new ChatWindow();
+ window.OpenCentered();
+ }
+}
+
+///
+/// Command which creates a window containing a chatbox configured for admin use
+///
+[UsedImplicitly]
+public sealed class AdminChatWindowCommand : LocalizedCommands
+{
+ public override string Command => "achatwindow";
+
+ public override void Execute(IConsoleShell shell, string argStr, string[] args)
+ {
+ var window = new ChatWindow();
+ window.ConfigureForAdminChat();
+ window.OpenCentered();
+ }
+}
diff --git a/Content.Client/UserInterface/Systems/Chat/Controls/ChannelFilterPopup.xaml.cs b/Content.Client/UserInterface/Systems/Chat/Controls/ChannelFilterPopup.xaml.cs
index df4f56cb27..33a82db335 100644
--- a/Content.Client/UserInterface/Systems/Chat/Controls/ChannelFilterPopup.xaml.cs
+++ b/Content.Client/UserInterface/Systems/Chat/Controls/ChannelFilterPopup.xaml.cs
@@ -1,4 +1,4 @@
-using Content.Shared.Chat;
+using Content.Shared.Chat;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
@@ -40,6 +40,15 @@ public sealed partial class ChannelFilterPopup : Popup
return _filterStates.TryGetValue(channel, out var checkbox) && checkbox.Pressed;
}
+ public void SetActive(ChatChannel channel, bool isActive)
+ {
+ if (_filterStates.TryGetValue(channel, out var checkbox) && checkbox.Pressed != isActive)
+ {
+ checkbox.Pressed = isActive;
+ OnChannelFilter?.Invoke(checkbox.Channel, checkbox.Pressed);
+ }
+ }
+
public ChatChannel GetActive()
{
ChatChannel active = 0;
diff --git a/Resources/Locale/en-US/administration/ui/tabs/admin-tab/player-actions-window.ftl b/Resources/Locale/en-US/administration/ui/tabs/admin-tab/player-actions-window.ftl
index 6af6a3ae20..62b253e3f5 100644
--- a/Resources/Locale/en-US/administration/ui/tabs/admin-tab/player-actions-window.ftl
+++ b/Resources/Locale/en-US/administration/ui/tabs/admin-tab/player-actions-window.ftl
@@ -8,3 +8,4 @@ admin-player-actions-window-shuttle = (Re)call Shuttle
admin-player-actions-window-admin-logs = Admin Logs
admin-player-actions-window-admin-notes = Admin Notes
admin-player-actions-window-admin-fax = Admin Fax
+admin-player-actions-window-admin-chat = Admin Chat
diff --git a/Resources/Locale/en-US/chat/ui/chat-window.ftl b/Resources/Locale/en-US/chat/ui/chat-window.ftl
new file mode 100644
index 0000000000..024bed6efa
--- /dev/null
+++ b/Resources/Locale/en-US/chat/ui/chat-window.ftl
@@ -0,0 +1,7 @@
+chat-window-title = Chat
+
+cmd-chatwindow-desc = Additional Chat Window
+cmd-chatwindow-help = Usage: chatwindow
+
+cmd-achatwindow-desc = Admin Chat Window
+cmd-achatwindow-help = Usage: achatwindow
diff --git a/Resources/clientCommandPerms.yml b/Resources/clientCommandPerms.yml
index cbe654e557..9d183a3f8c 100644
--- a/Resources/clientCommandPerms.yml
+++ b/Resources/clientCommandPerms.yml
@@ -38,6 +38,7 @@
- replay_stop
- replay_load
- saveconfig
+ - chatwindow
- Flags: DEBUG
Commands:
@@ -84,3 +85,7 @@
- uploadfile
- loadprototype
- uploadfolder
+
+- Flags: ADMINCHAT
+ Commands:
+ - achatwindow