Various Improvements to NanoChat (#2922)
* Port improvements to NanoChat from Einstein-Engines and Goob-Station * fix duplicate translation key * add missing space * Properly mark DeltaV changes * Allow muting individual NanoChat users, NanoChat UI to put per-chat buttons next to the message box * remove leftover from testing stuff * cycle through inputs with tab, confirm with enter; for new and edit chat * Add channel switching with (Shift+)Alt+Up/Down; Discord-Style * better null check * another better null check * Implement changes from ImpStation PR * Rename ContactControl -> ContactContainer * Requested changes * Move Loc to _DV, don't register system as manager * I'm so smart :) --------- Co-authored-by: Alex C <alex91905@yahoo.com>
This commit is contained in:
parent
4d81b48dd3
commit
4ecf2aaca2
|
|
@ -40,6 +40,12 @@ namespace Content.Client.Input
|
||||||
common.AddFunction(ContentKeyFunctions.ResetZoom);
|
common.AddFunction(ContentKeyFunctions.ResetZoom);
|
||||||
common.AddFunction(ContentKeyFunctions.InspectEntity);
|
common.AddFunction(ContentKeyFunctions.InspectEntity);
|
||||||
common.AddFunction(ContentKeyFunctions.ToggleRoundEndSummaryWindow);
|
common.AddFunction(ContentKeyFunctions.ToggleRoundEndSummaryWindow);
|
||||||
|
// DeltaV - Begin NanoChat keybinds
|
||||||
|
common.AddFunction(ContentKeyFunctions.NanoChatNavigateUp);
|
||||||
|
common.AddFunction(ContentKeyFunctions.NanoChatNavigateDown);
|
||||||
|
common.AddFunction(ContentKeyFunctions.NanoChatNavigateUpUnread);
|
||||||
|
common.AddFunction(ContentKeyFunctions.NanoChatNavigateDownUnread);
|
||||||
|
// DeltaV - End NanoChat keybinds
|
||||||
|
|
||||||
// Not in engine, because engine cannot check for sanbox/admin status before starting placement.
|
// Not in engine, because engine cannot check for sanbox/admin status before starting placement.
|
||||||
common.AddFunction(ContentKeyFunctions.EditorCopyObject);
|
common.AddFunction(ContentKeyFunctions.EditorCopyObject);
|
||||||
|
|
|
||||||
|
|
@ -240,6 +240,14 @@ namespace Content.Client.Options.UI.Tabs
|
||||||
AddButton(EngineKeyFunctions.EscapeMenu);
|
AddButton(EngineKeyFunctions.EscapeMenu);
|
||||||
AddButton(ContentKeyFunctions.EscapeContext);
|
AddButton(ContentKeyFunctions.EscapeContext);
|
||||||
|
|
||||||
|
// DeltaV - Begin NanoChat keybinds
|
||||||
|
AddHeader("ui-options-header-nano-chat");
|
||||||
|
AddButton(ContentKeyFunctions.NanoChatNavigateUp);
|
||||||
|
AddButton(ContentKeyFunctions.NanoChatNavigateDown);
|
||||||
|
AddButton(ContentKeyFunctions.NanoChatNavigateUpUnread);
|
||||||
|
AddButton(ContentKeyFunctions.NanoChatNavigateDownUnread);
|
||||||
|
// DeltaV - End NanoChat keybinds
|
||||||
|
|
||||||
// Shitmed Change Start - TODO: Add hands, feet and groin targeting.
|
// Shitmed Change Start - TODO: Add hands, feet and groin targeting.
|
||||||
AddHeader("ui-options-header-targeting");
|
AddHeader("ui-options-header-targeting");
|
||||||
AddButton(ContentKeyFunctions.TargetHead);
|
AddButton(ContentKeyFunctions.TargetHead);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
<DefaultWindow xmlns="https://spacestation14.io"
|
||||||
|
Title="{Loc nano-chat-edit-title}"
|
||||||
|
MinSize="300 200">
|
||||||
|
<PanelContainer StyleClasses="AngleRect">
|
||||||
|
<BoxContainer Orientation="Vertical" Margin="4">
|
||||||
|
<!-- Number input -->
|
||||||
|
<BoxContainer Orientation="Vertical" Margin="0 4">
|
||||||
|
<Label Text="{Loc nano-chat-number-label}"
|
||||||
|
StyleClasses="LabelHeading" />
|
||||||
|
<PanelContainer StyleClasses="ButtonSquare">
|
||||||
|
<LineEdit Name="NumberInput"
|
||||||
|
PlaceHolder="{Loc nano-chat-number-placeholder}"
|
||||||
|
Editable="False" />
|
||||||
|
</PanelContainer>
|
||||||
|
</BoxContainer>
|
||||||
|
|
||||||
|
<!-- Name input -->
|
||||||
|
<BoxContainer Orientation="Vertical" Margin="0 4">
|
||||||
|
<Label Text="{Loc nano-chat-name-label}"
|
||||||
|
StyleClasses="LabelHeading" />
|
||||||
|
<PanelContainer StyleClasses="ButtonSquare">
|
||||||
|
<LineEdit Name="NameInput"
|
||||||
|
PlaceHolder="{Loc nano-chat-name-placeholder}" />
|
||||||
|
</PanelContainer>
|
||||||
|
</BoxContainer>
|
||||||
|
|
||||||
|
<!-- Job input (optional) -->
|
||||||
|
<BoxContainer Orientation="Vertical" Margin="0 4">
|
||||||
|
<Label Text="{Loc nano-chat-job-label}"
|
||||||
|
StyleClasses="LabelHeading" />
|
||||||
|
<PanelContainer StyleClasses="ButtonSquare">
|
||||||
|
<LineEdit Name="JobInput"
|
||||||
|
PlaceHolder="{Loc nano-chat-job-placeholder}" />
|
||||||
|
</PanelContainer>
|
||||||
|
</BoxContainer>
|
||||||
|
|
||||||
|
<!-- Action buttons -->
|
||||||
|
<BoxContainer Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Margin="0 8 0 0">
|
||||||
|
<Button Name="CancelButton"
|
||||||
|
Text="{Loc nano-chat-cancel}"
|
||||||
|
StyleClasses="OpenRight"
|
||||||
|
MinSize="80 0" />
|
||||||
|
<Button Name="ConfirmButton"
|
||||||
|
Text="{Loc nano-chat-confirm}"
|
||||||
|
StyleClasses="OpenLeft"
|
||||||
|
MinSize="80 0"
|
||||||
|
Disabled="True" />
|
||||||
|
</BoxContainer>
|
||||||
|
</BoxContainer>
|
||||||
|
</PanelContainer>
|
||||||
|
</DefaultWindow>
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
using Content.Shared.Access.Components;
|
||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
|
||||||
|
namespace Content.Client._DV.CartridgeLoader.Cartridges;
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class EditChatPopup : DefaultWindow
|
||||||
|
{
|
||||||
|
private const int MaxNumberLength = 4;
|
||||||
|
|
||||||
|
// Used to see if the user input is different from the original data
|
||||||
|
// to check if the user can submit
|
||||||
|
private string _originalNumber = "";
|
||||||
|
private string _originalName = "";
|
||||||
|
private string _originalJob = "";
|
||||||
|
|
||||||
|
public event Action<uint, string, string?>? OnContactEdited;
|
||||||
|
|
||||||
|
public EditChatPopup()
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
|
||||||
|
// margins trolling
|
||||||
|
ContentsContainer.Margin = new Thickness(3);
|
||||||
|
|
||||||
|
// Button handlers
|
||||||
|
CancelButton.OnPressed += _ => Close();
|
||||||
|
ConfirmButton.OnPressed += _ => EditChat();
|
||||||
|
|
||||||
|
NameInput.OnTabComplete += _ => JobInput.GrabKeyboardFocus();
|
||||||
|
NameInput.OnTextEntered += _ => EditChat();
|
||||||
|
|
||||||
|
JobInput.OnTabComplete += _ => NameInput.GrabKeyboardFocus();
|
||||||
|
JobInput.OnTextEntered += _ => EditChat();
|
||||||
|
|
||||||
|
// Input validation
|
||||||
|
NameInput.OnTextChanged += args =>
|
||||||
|
{
|
||||||
|
if (args.Text.Length > IdCardConsoleComponent.MaxFullNameLength)
|
||||||
|
NameInput.Text = args.Text[..IdCardConsoleComponent.MaxFullNameLength];
|
||||||
|
ValidateInputs();
|
||||||
|
};
|
||||||
|
|
||||||
|
JobInput.OnTextChanged += args =>
|
||||||
|
{
|
||||||
|
if (args.Text.Length > IdCardConsoleComponent.MaxJobTitleLength)
|
||||||
|
JobInput.Text = args.Text[..IdCardConsoleComponent.MaxJobTitleLength];
|
||||||
|
ValidateInputs();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ValidateInputs()
|
||||||
|
{
|
||||||
|
var isValid = !string.IsNullOrWhiteSpace(NumberInput.Text) &&
|
||||||
|
!string.IsNullOrWhiteSpace(NameInput.Text) &&
|
||||||
|
NumberInput.Text.Length == MaxNumberLength &&
|
||||||
|
uint.TryParse(NumberInput.Text, out _) &&
|
||||||
|
// Only valid if there are any changes
|
||||||
|
(NumberInput.Text != _originalNumber ||
|
||||||
|
NameInput.Text != _originalName ||
|
||||||
|
JobInput.Text != _originalJob);
|
||||||
|
|
||||||
|
ConfirmButton.Disabled = !isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EditChat()
|
||||||
|
{
|
||||||
|
if (!uint.TryParse(NumberInput.Text, out var number))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var name = NameInput.Text.Trim();
|
||||||
|
var job = string.IsNullOrWhiteSpace(JobInput.Text) ? null : JobInput.Text.Trim();
|
||||||
|
|
||||||
|
OnContactEdited?.Invoke(number, name, job);
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearInputs()
|
||||||
|
{
|
||||||
|
NameInput.Text = string.Empty;
|
||||||
|
JobInput.Text = string.Empty;
|
||||||
|
ValidateInputs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetNumberInput(string newNumber)
|
||||||
|
{
|
||||||
|
NumberInput.Text = newNumber.PadLeft(MaxNumberLength, '0');
|
||||||
|
_originalNumber = newNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetNameInput(string newName)
|
||||||
|
{
|
||||||
|
NameInput.Text = newName;
|
||||||
|
_originalName = newName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetJobInput(string newJob)
|
||||||
|
{
|
||||||
|
JobInput.Text = newJob;
|
||||||
|
_originalJob = newJob;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
using Content.Shared._DV.CartridgeLoader.Cartridges;
|
using Content.Shared._DV.CartridgeLoader.Cartridges;
|
||||||
|
using Content.Shared._DV.NanoChat;
|
||||||
|
using Content.Shared.Access.Components;
|
||||||
using Robust.Client.AutoGenerated;
|
using Robust.Client.AutoGenerated;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
using Robust.Client.UserInterface.XAML;
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
|
@ -29,8 +31,8 @@ public sealed partial class NanoChatEntry : BoxContainer
|
||||||
_pressHandler = _ => OnPressed?.Invoke(_number);
|
_pressHandler = _ => OnPressed?.Invoke(_number);
|
||||||
ChatButton.OnPressed += _pressHandler;
|
ChatButton.OnPressed += _pressHandler;
|
||||||
|
|
||||||
NameLabel.Text = recipient.Name;
|
NameLabel.Text = SharedNanoChatSystem.Truncate(recipient.Name, IdCardConsoleComponent.MaxFullNameLength);
|
||||||
JobLabel.Text = recipient.JobTitle ?? "";
|
JobLabel.Text = SharedNanoChatSystem.Truncate(recipient.JobTitle ?? "", IdCardConsoleComponent.MaxJobTitleLength);
|
||||||
JobLabel.Visible = !string.IsNullOrEmpty(recipient.JobTitle);
|
JobLabel.Visible = !string.IsNullOrEmpty(recipient.JobTitle);
|
||||||
UnreadIndicator.Visible = recipient.HasUnread;
|
UnreadIndicator.Visible = recipient.HasUnread;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Content.Shared._DV.CartridgeLoader.Cartridges;
|
using Content.Shared._DV.CartridgeLoader.Cartridges;
|
||||||
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;
|
||||||
|
|
||||||
|
|
@ -28,6 +29,19 @@ public sealed partial class NanoChatLookupView : PanelContainer
|
||||||
for (var idx = 0; idx < contacts.Count; idx++)
|
for (var idx = 0; idx < contacts.Count; idx++)
|
||||||
{
|
{
|
||||||
var contact = contacts[idx];
|
var contact = contacts[idx];
|
||||||
|
var isEvenRow = idx % 2 == 0;
|
||||||
|
var contactControl = new ContactContainer(contact, state, isEvenRow, OnStartChat);
|
||||||
|
ContactsList.AddChild(contactControl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class ContactContainer : PanelContainer
|
||||||
|
{
|
||||||
|
public ContactContainer(NanoChatRecipient contact, NanoChatUiState state, bool isEvenRow, Action<NanoChatRecipient>? onStartChat)
|
||||||
|
{
|
||||||
|
HorizontalExpand = true;
|
||||||
|
StyleClasses.Add(isEvenRow ? "PanelBackgroundBaseDark" : "PanelBackgroundLight");
|
||||||
|
|
||||||
var nameLabel = new Label()
|
var nameLabel = new Label()
|
||||||
{
|
{
|
||||||
Text = contact.Name,
|
Text = contact.Name,
|
||||||
|
|
@ -36,7 +50,7 @@ public sealed partial class NanoChatLookupView : PanelContainer
|
||||||
};
|
};
|
||||||
var numberLabel = new Label()
|
var numberLabel = new Label()
|
||||||
{
|
{
|
||||||
Text = $"#{contacts[idx].Number:D4}",
|
Text = $"#{contact.Number:D4}",
|
||||||
HorizontalAlignment = HAlignment.Right,
|
HorizontalAlignment = HAlignment.Right,
|
||||||
Margin = new Thickness(0, 0, 36, 0),
|
Margin = new Thickness(0, 0, 36, 0),
|
||||||
};
|
};
|
||||||
|
|
@ -49,25 +63,17 @@ public sealed partial class NanoChatLookupView : PanelContainer
|
||||||
ToolTip = Loc.GetString("nano-chat-new-chat"),
|
ToolTip = Loc.GetString("nano-chat-new-chat"),
|
||||||
};
|
};
|
||||||
startChatButton.AddStyleClass("OpenBoth");
|
startChatButton.AddStyleClass("OpenBoth");
|
||||||
|
|
||||||
if (contact.Number == state.OwnNumber || state.Recipients.ContainsKey(contact.Number) || state.MaxRecipients <= state.Recipients.Count)
|
if (contact.Number == state.OwnNumber || state.Recipients.ContainsKey(contact.Number) || state.MaxRecipients <= state.Recipients.Count)
|
||||||
{
|
{
|
||||||
startChatButton.Disabled = true;
|
startChatButton.Disabled = true;
|
||||||
}
|
}
|
||||||
startChatButton.OnPressed += _ => OnStartChat?.Invoke(contact);
|
|
||||||
|
|
||||||
var panel = new PanelContainer()
|
startChatButton.OnPressed += _ => onStartChat?.Invoke(contact);
|
||||||
{
|
|
||||||
HorizontalExpand = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
panel.AddChild(nameLabel);
|
AddChild(nameLabel);
|
||||||
panel.AddChild(numberLabel);
|
AddChild(numberLabel);
|
||||||
panel.AddChild(startChatButton);
|
AddChild(startChatButton);
|
||||||
|
|
||||||
var styleClass = idx % 2 == 0 ? "PanelBackgroundBaseDark" : "PanelBackgroundLight";
|
|
||||||
panel.StyleClasses.Add(styleClass);
|
|
||||||
|
|
||||||
ContactsList.AddChild(panel);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,17 +34,6 @@
|
||||||
StyleClasses="LabelSubText"
|
StyleClasses="LabelSubText"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Margin="0 0 8 0" />
|
Margin="0 0 8 0" />
|
||||||
<Button Name="DeleteChatButton"
|
|
||||||
MaxSize="32 32"
|
|
||||||
Visible="False"
|
|
||||||
StyleClasses="OpenBoth"
|
|
||||||
Margin="0 0 4 0"
|
|
||||||
ToolTip="{Loc nano-chat-delete}">
|
|
||||||
<TextureRect StyleClasses="ButtonSquare"
|
|
||||||
TexturePath="/Textures/Interface/VerbIcons/delete_transparent.svg.192dpi.png"
|
|
||||||
Stretch="KeepAspectCentered"
|
|
||||||
MinSize="18 18" />
|
|
||||||
</Button>
|
|
||||||
<Button Name="MuteButton"
|
<Button Name="MuteButton"
|
||||||
MaxSize="32 32"
|
MaxSize="32 32"
|
||||||
StyleClasses="OpenBoth"
|
StyleClasses="OpenBoth"
|
||||||
|
|
@ -155,18 +144,59 @@
|
||||||
</ScrollContainer>
|
</ScrollContainer>
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
|
|
||||||
|
<!-- Character count -->
|
||||||
|
<Label Name="CharacterCount"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
StyleClasses="LabelSubText"
|
||||||
|
Margin="0 0 4 2"
|
||||||
|
Visible="False" />
|
||||||
<!-- Message input -->
|
<!-- Message input -->
|
||||||
<BoxContainer Name="MessageInputContainer"
|
<BoxContainer Name="MessageInputContainer"
|
||||||
Orientation="Horizontal"
|
Orientation="Horizontal"
|
||||||
HorizontalExpand="True"
|
HorizontalExpand="True"
|
||||||
Margin="0 4 0 0"
|
Margin="0 4 0 0"
|
||||||
Visible="False">
|
Visible="False">
|
||||||
<!-- Character count -->
|
<Button Name="EditChatButton"
|
||||||
<Label Name="CharacterCount"
|
MaxSize="32 32"
|
||||||
HorizontalAlignment="Right"
|
Visible="False"
|
||||||
StyleClasses="LabelSubText"
|
StyleClasses="OpenBoth"
|
||||||
Margin="0 0 4 2"
|
Margin="0 0 4 0"
|
||||||
Visible="False" />
|
ToolTip="{Loc nano-chat-edit}">
|
||||||
|
<TextureRect StyleClasses="ButtonSquare"
|
||||||
|
TexturePath="/Textures/_DV/Interface/VerbIcons/edit.svg.png"
|
||||||
|
Stretch="KeepAspectCentered"
|
||||||
|
MinSize="18 18" />
|
||||||
|
</Button>
|
||||||
|
<Button Name="DeleteChatButton"
|
||||||
|
MaxSize="32 32"
|
||||||
|
Visible="False"
|
||||||
|
StyleClasses="OpenBoth"
|
||||||
|
Margin="0 0 4 0"
|
||||||
|
ToolTip="{Loc nano-chat-delete}">
|
||||||
|
<TextureRect StyleClasses="ButtonSquare"
|
||||||
|
TexturePath="/Textures/Interface/VerbIcons/delete_transparent.svg.192dpi.png"
|
||||||
|
Stretch="KeepAspectCentered"
|
||||||
|
MinSize="18 18" />
|
||||||
|
</Button>
|
||||||
|
<Button Name="MuteChatButton"
|
||||||
|
MaxSize="32 32"
|
||||||
|
StyleClasses="OpenBoth"
|
||||||
|
Margin="0 0 4 0"
|
||||||
|
ToolTip="{Loc nano-chat-toggle-mute-chat}">
|
||||||
|
<Control HorizontalExpand="True" VerticalExpand="True">
|
||||||
|
<TextureRect Name="BellIconContact"
|
||||||
|
StyleClasses="ButtonSquare"
|
||||||
|
TexturePath="/Textures/_DV/Interface/VerbIcons/bell.svg.png"
|
||||||
|
Stretch="KeepAspectCentered"
|
||||||
|
MinSize="18 18" />
|
||||||
|
<TextureRect Name="BellMutedIconContact"
|
||||||
|
StyleClasses="ButtonSquare"
|
||||||
|
TexturePath="/Textures/_DV/Interface/VerbIcons/bell_muted.png"
|
||||||
|
Stretch="KeepAspectCentered"
|
||||||
|
Visible="False"
|
||||||
|
MinSize="18 18" />
|
||||||
|
</Control>
|
||||||
|
</Button>
|
||||||
<!-- Input row -->
|
<!-- Input row -->
|
||||||
<LineEdit Name="MessageInput"
|
<LineEdit Name="MessageInput"
|
||||||
PlaceHolder="{Loc nano-chat-message-placeholder}"
|
PlaceHolder="{Loc nano-chat-message-placeholder}"
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using Robust.Client.UserInterface.Controls;
|
||||||
using Robust.Client.UserInterface.XAML;
|
using Robust.Client.UserInterface.XAML;
|
||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
|
using Content.Shared.Input;
|
||||||
|
|
||||||
namespace Content.Client._DV.CartridgeLoader.Cartridges;
|
namespace Content.Client._DV.CartridgeLoader.Cartridges;
|
||||||
|
|
||||||
|
|
@ -14,16 +15,16 @@ public sealed partial class NanoChatUiFragment : BoxContainer
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IGameTiming _timing = default!;
|
[Dependency] private readonly IGameTiming _timing = default!;
|
||||||
|
|
||||||
private const int MaxMessageLength = 256;
|
|
||||||
|
|
||||||
private readonly NewChatPopup _newChatPopup;
|
private readonly NewChatPopup _newChatPopup;
|
||||||
|
private readonly EditChatPopup _editChatPopup;
|
||||||
private uint? _currentChat;
|
private uint? _currentChat;
|
||||||
private uint? _pendingChat;
|
private uint? _pendingChat;
|
||||||
private uint _ownNumber;
|
private uint _ownNumber;
|
||||||
private bool _notificationsMuted;
|
private bool _notificationsMuted;
|
||||||
private bool _listNumber = true;
|
private bool _listNumber = true;
|
||||||
private Dictionary<uint, NanoChatRecipient> _recipients = new();
|
private Dictionary<uint, NanoChatRecipient> _recipients = [];
|
||||||
private Dictionary<uint, List<NanoChatMessage>> _messages = new();
|
private Dictionary<uint, List<NanoChatMessage>> _messages = [];
|
||||||
|
private HashSet<uint> _mutedChats = [];
|
||||||
|
|
||||||
public event Action<NanoChatUiMessageType, uint?, string?, string?>? OnMessageSent;
|
public event Action<NanoChatUiMessageType, uint?, string?, string?>? OnMessageSent;
|
||||||
|
|
||||||
|
|
@ -32,7 +33,8 @@ public sealed partial class NanoChatUiFragment : BoxContainer
|
||||||
IoCManager.InjectDependencies(this);
|
IoCManager.InjectDependencies(this);
|
||||||
RobustXamlLoader.Load(this);
|
RobustXamlLoader.Load(this);
|
||||||
|
|
||||||
_newChatPopup = new NewChatPopup();
|
_newChatPopup = new();
|
||||||
|
_editChatPopup = new();
|
||||||
SetupEventHandlers();
|
SetupEventHandlers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -43,12 +45,30 @@ public sealed partial class NanoChatUiFragment : BoxContainer
|
||||||
OnMessageSent?.Invoke(NanoChatUiMessageType.NewChat, number, name, job);
|
OnMessageSent?.Invoke(NanoChatUiMessageType.NewChat, number, name, job);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_editChatPopup.OnContactEdited += (number, name, job) =>
|
||||||
|
{
|
||||||
|
OnMessageSent?.Invoke(NanoChatUiMessageType.EditChat, number, name, job);
|
||||||
|
};
|
||||||
|
|
||||||
NewChatButton.OnPressed += _ =>
|
NewChatButton.OnPressed += _ =>
|
||||||
{
|
{
|
||||||
_newChatPopup.ClearInputs();
|
_newChatPopup.ClearInputs();
|
||||||
_newChatPopup.OpenCentered();
|
_newChatPopup.OpenCentered();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
MuteChatButton.OnPressed += _ =>
|
||||||
|
{
|
||||||
|
if (_currentChat is not uint currentChat)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Remove if muted, otherwise add
|
||||||
|
if (!_mutedChats.Remove(currentChat))
|
||||||
|
_mutedChats.Add(currentChat);
|
||||||
|
|
||||||
|
UpdateMuteChatButton();
|
||||||
|
OnMessageSent?.Invoke(NanoChatUiMessageType.ToggleMuteChat, currentChat, null, null);
|
||||||
|
};
|
||||||
|
|
||||||
MuteButton.OnPressed += _ =>
|
MuteButton.OnPressed += _ =>
|
||||||
{
|
{
|
||||||
_notificationsMuted = !_notificationsMuted;
|
_notificationsMuted = !_notificationsMuted;
|
||||||
|
|
@ -56,22 +76,33 @@ public sealed partial class NanoChatUiFragment : BoxContainer
|
||||||
OnMessageSent?.Invoke(NanoChatUiMessageType.ToggleMute, null, null, null);
|
OnMessageSent?.Invoke(NanoChatUiMessageType.ToggleMute, null, null, null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
MessageInput.OnKeyBindDown += args =>
|
||||||
|
{
|
||||||
|
if (args.Function == ContentKeyFunctions.NanoChatNavigateUpUnread)
|
||||||
|
CycleChannel(CycleDirection.Up, true);
|
||||||
|
else if (args.Function == ContentKeyFunctions.NanoChatNavigateDownUnread)
|
||||||
|
CycleChannel(CycleDirection.Down, true);
|
||||||
|
else if (args.Function == ContentKeyFunctions.NanoChatNavigateUp)
|
||||||
|
CycleChannel(CycleDirection.Up, false);
|
||||||
|
else if (args.Function == ContentKeyFunctions.NanoChatNavigateDown)
|
||||||
|
CycleChannel(CycleDirection.Down, false);
|
||||||
|
};
|
||||||
MessageInput.OnTextChanged += args =>
|
MessageInput.OnTextChanged += args =>
|
||||||
{
|
{
|
||||||
var length = args.Text.Length;
|
var length = args.Text.Length;
|
||||||
var isValid = !string.IsNullOrWhiteSpace(args.Text) &&
|
var isValid = !string.IsNullOrWhiteSpace(args.Text) &&
|
||||||
length <= MaxMessageLength &&
|
length <= NanoChatMessage.MaxContentLength &&
|
||||||
(_currentChat != null || _pendingChat != null);
|
(_currentChat != null || _pendingChat != null);
|
||||||
|
|
||||||
SendButton.Disabled = !isValid;
|
SendButton.Disabled = !isValid;
|
||||||
|
|
||||||
// Show character count when over limit
|
// Show character count when over limit
|
||||||
CharacterCount.Visible = length > MaxMessageLength;
|
CharacterCount.Visible = length > NanoChatMessage.MaxContentLength;
|
||||||
if (length > MaxMessageLength)
|
if (length > NanoChatMessage.MaxContentLength)
|
||||||
{
|
{
|
||||||
CharacterCount.Text = Loc.GetString("nano-chat-message-too-long",
|
CharacterCount.Text = Loc.GetString("nano-chat-message-too-long",
|
||||||
("current", length),
|
("current", length),
|
||||||
("max", MaxMessageLength));
|
("max", NanoChatMessage.MaxContentLength));
|
||||||
CharacterCount.StyleClasses.Add("LabelDanger");
|
CharacterCount.StyleClasses.Add("LabelDanger");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -93,7 +124,9 @@ public sealed partial class NanoChatUiFragment : BoxContainer
|
||||||
OnMessageSent?.Invoke(NanoChatUiMessageType.ToggleListNumber, null, null, null);
|
OnMessageSent?.Invoke(NanoChatUiMessageType.ToggleListNumber, null, null, null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
MessageInput.OnTextEntered += _ => SendMessage();
|
||||||
SendButton.OnPressed += _ => SendMessage();
|
SendButton.OnPressed += _ => SendMessage();
|
||||||
|
EditChatButton.OnPressed += _ => BeginEditChat();
|
||||||
DeleteChatButton.OnPressed += _ => DeleteCurrentChat();
|
DeleteChatButton.OnPressed += _ => DeleteCurrentChat();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,6 +137,44 @@ public sealed partial class NanoChatUiFragment : BoxContainer
|
||||||
LookupButton.Pressed = LookupView.Visible;
|
LookupButton.Pressed = LookupView.Visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum CycleDirection : byte
|
||||||
|
{
|
||||||
|
Up,
|
||||||
|
Down,
|
||||||
|
};
|
||||||
|
|
||||||
|
private void CycleChannel(CycleDirection direction, bool onlyUnread)
|
||||||
|
{
|
||||||
|
if (_recipients.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var orderedRecipients = _recipients.OrderBy(r => r.Value.Name).Select(r => r.Key).ToArray();
|
||||||
|
var currentChatIndex = (direction, _currentChat) switch
|
||||||
|
{
|
||||||
|
(CycleDirection.Up, null) => _recipients.Count,
|
||||||
|
(CycleDirection.Down, null) => 0,
|
||||||
|
(_, uint currentChat) => Array.IndexOf(orderedRecipients, currentChat),
|
||||||
|
_ => 0
|
||||||
|
};
|
||||||
|
var newChatIndex = currentChatIndex;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
newChatIndex = direction switch
|
||||||
|
{
|
||||||
|
CycleDirection.Up => newChatIndex - 1,
|
||||||
|
CycleDirection.Down => newChatIndex + 1,
|
||||||
|
_ => currentChatIndex,
|
||||||
|
};
|
||||||
|
if (newChatIndex < 0)
|
||||||
|
newChatIndex = _recipients.Count - 1;
|
||||||
|
else if (newChatIndex >= _recipients.Count)
|
||||||
|
newChatIndex = 0;
|
||||||
|
} while (onlyUnread && newChatIndex != currentChatIndex && !_recipients[orderedRecipients[newChatIndex]].HasUnread);
|
||||||
|
|
||||||
|
SelectChat(orderedRecipients[newChatIndex]);
|
||||||
|
}
|
||||||
|
|
||||||
private void SendMessage()
|
private void SendMessage()
|
||||||
{
|
{
|
||||||
var activeChat = _pendingChat ?? _currentChat;
|
var activeChat = _pendingChat ?? _currentChat;
|
||||||
|
|
@ -111,6 +182,12 @@ public sealed partial class NanoChatUiFragment : BoxContainer
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var messageContent = MessageInput.Text;
|
var messageContent = MessageInput.Text;
|
||||||
|
if (!string.IsNullOrWhiteSpace(messageContent))
|
||||||
|
{
|
||||||
|
messageContent = messageContent.Trim();
|
||||||
|
if (messageContent.Length > NanoChatMessage.MaxContentLength)
|
||||||
|
messageContent = messageContent[..NanoChatMessage.MaxContentLength];
|
||||||
|
}
|
||||||
|
|
||||||
// Add predicted message
|
// Add predicted message
|
||||||
var predictedMessage = new NanoChatMessage(
|
var predictedMessage = new NanoChatMessage(
|
||||||
|
|
@ -167,6 +244,20 @@ public sealed partial class NanoChatUiFragment : BoxContainer
|
||||||
OnMessageSent?.Invoke(NanoChatUiMessageType.DeleteChat, activeChat, null, null);
|
OnMessageSent?.Invoke(NanoChatUiMessageType.DeleteChat, activeChat, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void BeginEditChat()
|
||||||
|
{
|
||||||
|
if (_currentChat is not uint currentChat)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var recipient = _recipients[currentChat];
|
||||||
|
|
||||||
|
_editChatPopup.ClearInputs();
|
||||||
|
_editChatPopup.SetNumberInput(recipient.Number.ToString());
|
||||||
|
_editChatPopup.SetNameInput(recipient.Name);
|
||||||
|
_editChatPopup.SetJobInput(recipient.JobTitle ?? string.Empty);
|
||||||
|
_editChatPopup.OpenCentered();
|
||||||
|
}
|
||||||
|
|
||||||
private void UpdateChatList(Dictionary<uint, NanoChatRecipient> recipients)
|
private void UpdateChatList(Dictionary<uint, NanoChatRecipient> recipients)
|
||||||
{
|
{
|
||||||
ChatList.RemoveAllChildren();
|
ChatList.RemoveAllChildren();
|
||||||
|
|
@ -200,6 +291,7 @@ public sealed partial class NanoChatUiFragment : BoxContainer
|
||||||
CurrentChatName.Visible = !hasActiveChat;
|
CurrentChatName.Visible = !hasActiveChat;
|
||||||
MessageInputContainer.Visible = hasActiveChat;
|
MessageInputContainer.Visible = hasActiveChat;
|
||||||
DeleteChatButton.Visible = hasActiveChat;
|
DeleteChatButton.Visible = hasActiveChat;
|
||||||
|
EditChatButton.Visible = hasActiveChat;
|
||||||
DeleteChatButton.Disabled = !hasActiveChat;
|
DeleteChatButton.Disabled = !hasActiveChat;
|
||||||
|
|
||||||
if (activeChat != null && _recipients.TryGetValue(activeChat.Value, out var recipient))
|
if (activeChat != null && _recipients.TryGetValue(activeChat.Value, out var recipient))
|
||||||
|
|
@ -245,6 +337,12 @@ public sealed partial class NanoChatUiFragment : BoxContainer
|
||||||
BellMutedIcon.Visible = _notificationsMuted;
|
BellMutedIcon.Visible = _notificationsMuted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateMuteChatButton()
|
||||||
|
{
|
||||||
|
if (BellMutedIconContact != null)
|
||||||
|
BellMutedIconContact.Visible = _currentChat is uint currentChat && _mutedChats.Contains(currentChat);
|
||||||
|
}
|
||||||
|
|
||||||
private void UpdateListNumber()
|
private void UpdateListNumber()
|
||||||
{
|
{
|
||||||
if (ListNumberButton != null)
|
if (ListNumberButton != null)
|
||||||
|
|
@ -256,6 +354,7 @@ public sealed partial class NanoChatUiFragment : BoxContainer
|
||||||
_ownNumber = state.OwnNumber;
|
_ownNumber = state.OwnNumber;
|
||||||
_notificationsMuted = state.NotificationsMuted;
|
_notificationsMuted = state.NotificationsMuted;
|
||||||
_listNumber = state.ListNumber;
|
_listNumber = state.ListNumber;
|
||||||
|
_mutedChats = state.MutedChats;
|
||||||
OwnNumberLabel.Text = $"#{state.OwnNumber:D4}";
|
OwnNumberLabel.Text = $"#{state.OwnNumber:D4}";
|
||||||
UpdateMuteButton();
|
UpdateMuteButton();
|
||||||
UpdateListNumber();
|
UpdateListNumber();
|
||||||
|
|
@ -281,6 +380,7 @@ public sealed partial class NanoChatUiFragment : BoxContainer
|
||||||
_currentChat = state.CurrentChat;
|
_currentChat = state.CurrentChat;
|
||||||
|
|
||||||
UpdateCurrentChat();
|
UpdateCurrentChat();
|
||||||
|
UpdateMuteChatButton();
|
||||||
UpdateChatList(state.Recipients);
|
UpdateChatList(state.Recipients);
|
||||||
UpdateMessages(state.Messages);
|
UpdateMessages(state.Messages);
|
||||||
LookupView.UpdateContactList(state);
|
LookupView.UpdateContactList(state);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Content.Shared.Access.Components;
|
||||||
using Robust.Client.AutoGenerated;
|
using Robust.Client.AutoGenerated;
|
||||||
using Robust.Client.UserInterface.CustomControls;
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
using Robust.Client.UserInterface.XAML;
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
|
@ -8,7 +9,6 @@ namespace Content.Client._DV.CartridgeLoader.Cartridges;
|
||||||
[GenerateTypedNameReferences]
|
[GenerateTypedNameReferences]
|
||||||
public sealed partial class NewChatPopup : DefaultWindow
|
public sealed partial class NewChatPopup : DefaultWindow
|
||||||
{
|
{
|
||||||
private const int MaxInputLength = 16;
|
|
||||||
private const int MaxNumberLength = 4; // i hardcoded it to be 4 so suffer
|
private const int MaxNumberLength = 4; // i hardcoded it to be 4 so suffer
|
||||||
|
|
||||||
public event Action<uint, string, string?>? OnChatCreated;
|
public event Action<uint, string, string?>? OnChatCreated;
|
||||||
|
|
@ -24,9 +24,14 @@ public sealed partial class NewChatPopup : DefaultWindow
|
||||||
CancelButton.OnPressed += _ => Close();
|
CancelButton.OnPressed += _ => Close();
|
||||||
CreateButton.OnPressed += _ => CreateChat();
|
CreateButton.OnPressed += _ => CreateChat();
|
||||||
|
|
||||||
// Input validation
|
NumberInput.OnTabComplete += _ => NameInput.GrabKeyboardFocus();
|
||||||
NumberInput.OnTextChanged += _ => ValidateInputs();
|
NumberInput.OnTextEntered += _ => CreateChat();
|
||||||
NameInput.OnTextChanged += _ => ValidateInputs();
|
|
||||||
|
NameInput.OnTabComplete += _ => JobInput.GrabKeyboardFocus();
|
||||||
|
NameInput.OnTextEntered += _ => CreateChat();
|
||||||
|
|
||||||
|
JobInput.OnTabComplete += _ => NumberInput.GrabKeyboardFocus();
|
||||||
|
JobInput.OnTextEntered += _ => CreateChat();
|
||||||
|
|
||||||
// Input validation
|
// Input validation
|
||||||
NumberInput.OnTextChanged += args =>
|
NumberInput.OnTextChanged += args =>
|
||||||
|
|
@ -44,15 +49,15 @@ public sealed partial class NewChatPopup : DefaultWindow
|
||||||
|
|
||||||
NameInput.OnTextChanged += args =>
|
NameInput.OnTextChanged += args =>
|
||||||
{
|
{
|
||||||
if (args.Text.Length > MaxInputLength)
|
if (args.Text.Length > IdCardConsoleComponent.MaxFullNameLength)
|
||||||
NameInput.Text = args.Text[..MaxInputLength];
|
NameInput.Text = args.Text[..IdCardConsoleComponent.MaxFullNameLength];
|
||||||
ValidateInputs();
|
ValidateInputs();
|
||||||
};
|
};
|
||||||
|
|
||||||
JobInput.OnTextChanged += args =>
|
JobInput.OnTextChanged += args =>
|
||||||
{
|
{
|
||||||
if (args.Text.Length > MaxInputLength)
|
if (args.Text.Length > IdCardConsoleComponent.MaxJobTitleLength)
|
||||||
JobInput.Text = args.Text[..MaxInputLength];
|
JobInput.Text = args.Text[..IdCardConsoleComponent.MaxJobTitleLength];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -60,6 +65,7 @@ public sealed partial class NewChatPopup : DefaultWindow
|
||||||
{
|
{
|
||||||
var isValid = !string.IsNullOrWhiteSpace(NumberInput.Text) &&
|
var isValid = !string.IsNullOrWhiteSpace(NumberInput.Text) &&
|
||||||
!string.IsNullOrWhiteSpace(NameInput.Text) &&
|
!string.IsNullOrWhiteSpace(NameInput.Text) &&
|
||||||
|
NumberInput.Text.Length == MaxNumberLength &&
|
||||||
uint.TryParse(NumberInput.Text, out _);
|
uint.TryParse(NumberInput.Text, out _);
|
||||||
|
|
||||||
CreateButton.Disabled = !isValid;
|
CreateButton.Disabled = !isValid;
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,9 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
||||||
// no point in storing it on the comp
|
// no point in storing it on the comp
|
||||||
private const int NotificationMaxLength = 64;
|
private const int NotificationMaxLength = 64;
|
||||||
|
|
||||||
|
// The max length of the name and job title on the notification before being truncated.
|
||||||
|
private const int NotificationTitleMaxLength = 32;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
@ -105,12 +108,18 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
||||||
case NanoChatUiMessageType.SelectChat:
|
case NanoChatUiMessageType.SelectChat:
|
||||||
HandleSelectChat(card, msg);
|
HandleSelectChat(card, msg);
|
||||||
break;
|
break;
|
||||||
|
case NanoChatUiMessageType.EditChat:
|
||||||
|
HandleEditChat(card, msg);
|
||||||
|
break;
|
||||||
case NanoChatUiMessageType.CloseChat:
|
case NanoChatUiMessageType.CloseChat:
|
||||||
HandleCloseChat(card);
|
HandleCloseChat(card);
|
||||||
break;
|
break;
|
||||||
case NanoChatUiMessageType.ToggleMute:
|
case NanoChatUiMessageType.ToggleMute:
|
||||||
HandleToggleMute(card);
|
HandleToggleMute(card);
|
||||||
break;
|
break;
|
||||||
|
case NanoChatUiMessageType.ToggleMuteChat:
|
||||||
|
HandleToggleMuteChat(card, msg);
|
||||||
|
break;
|
||||||
case NanoChatUiMessageType.DeleteChat:
|
case NanoChatUiMessageType.DeleteChat:
|
||||||
HandleDeleteChat(card, msg);
|
HandleDeleteChat(card, msg);
|
||||||
break;
|
break;
|
||||||
|
|
@ -155,17 +164,33 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
||||||
if (msg.RecipientNumber == null || msg.Content == null || msg.RecipientNumber == card.Comp.Number)
|
if (msg.RecipientNumber == null || msg.Content == null || msg.RecipientNumber == card.Comp.Number)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
var name = msg.Content;
|
||||||
|
if (!string.IsNullOrWhiteSpace(name))
|
||||||
|
{
|
||||||
|
name = name.Trim();
|
||||||
|
if (name.Length > IdCardConsoleComponent.MaxFullNameLength)
|
||||||
|
name = name[..IdCardConsoleComponent.MaxFullNameLength];
|
||||||
|
}
|
||||||
|
|
||||||
|
var jobTitle = msg.RecipientJob;
|
||||||
|
if (!string.IsNullOrWhiteSpace(jobTitle))
|
||||||
|
{
|
||||||
|
jobTitle = jobTitle.Trim();
|
||||||
|
if (jobTitle.Length > IdCardConsoleComponent.MaxJobTitleLength)
|
||||||
|
jobTitle = jobTitle[..IdCardConsoleComponent.MaxJobTitleLength];
|
||||||
|
}
|
||||||
|
|
||||||
// Add new recipient
|
// Add new recipient
|
||||||
var recipient = new NanoChatRecipient(msg.RecipientNumber.Value,
|
var recipient = new NanoChatRecipient(msg.RecipientNumber.Value,
|
||||||
msg.Content,
|
name,
|
||||||
msg.RecipientJob);
|
jobTitle);
|
||||||
|
|
||||||
// Initialize or update recipient
|
// Initialize or update recipient
|
||||||
_nanoChat.SetRecipient((card, card.Comp), msg.RecipientNumber.Value, recipient);
|
_nanoChat.SetRecipient((card, card.Comp), msg.RecipientNumber.Value, recipient);
|
||||||
|
|
||||||
_adminLogger.Add(LogType.Action,
|
_adminLogger.Add(LogType.Action,
|
||||||
LogImpact.Low,
|
LogImpact.Low,
|
||||||
$"{ToPrettyString(msg.Actor):user} created new NanoChat conversation with #{msg.RecipientNumber:D4} ({msg.Content})");
|
$"{ToPrettyString(msg.Actor):user} created new NanoChat conversation with #{msg.RecipientNumber:D4} ({name})");
|
||||||
|
|
||||||
var recipientEv = new NanoChatRecipientUpdatedEvent(card);
|
var recipientEv = new NanoChatRecipientUpdatedEvent(card);
|
||||||
RaiseLocalEvent(ref recipientEv);
|
RaiseLocalEvent(ref recipientEv);
|
||||||
|
|
@ -191,6 +216,42 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles editing the current chat conversation.
|
||||||
|
/// </summary>
|
||||||
|
private void HandleEditChat(Entity<NanoChatCardComponent> card, NanoChatUiMessageEvent msg)
|
||||||
|
{
|
||||||
|
if (msg.RecipientNumber == null || msg.Content == null || msg.RecipientNumber == card.Comp.Number ||
|
||||||
|
_nanoChat.GetRecipient((card, card.Comp), msg.RecipientNumber.Value) is not { } recipient)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var name = msg.Content;
|
||||||
|
if (!string.IsNullOrWhiteSpace(name))
|
||||||
|
{
|
||||||
|
name = name.Trim();
|
||||||
|
if (name.Length > IdCardConsoleComponent.MaxFullNameLength)
|
||||||
|
name = name[..IdCardConsoleComponent.MaxFullNameLength];
|
||||||
|
}
|
||||||
|
|
||||||
|
var jobTitle = msg.RecipientJob;
|
||||||
|
if (!string.IsNullOrWhiteSpace(jobTitle))
|
||||||
|
{
|
||||||
|
jobTitle = jobTitle.Trim();
|
||||||
|
if (jobTitle.Length > IdCardConsoleComponent.MaxJobTitleLength)
|
||||||
|
jobTitle = jobTitle[..IdCardConsoleComponent.MaxJobTitleLength];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update recipient
|
||||||
|
recipient.Name = name;
|
||||||
|
recipient.JobTitle = jobTitle;
|
||||||
|
|
||||||
|
_nanoChat.SetRecipient((card, card.Comp), msg.RecipientNumber.Value, recipient);
|
||||||
|
|
||||||
|
var recipientEv = new NanoChatRecipientUpdatedEvent(card);
|
||||||
|
RaiseLocalEvent(ref recipientEv);
|
||||||
|
UpdateUIForCard(card);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles closing the current chat conversation.
|
/// Handles closing the current chat conversation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -229,6 +290,14 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
||||||
UpdateUIForCard(card);
|
UpdateUIForCard(card);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void HandleToggleMuteChat(Entity<NanoChatCardComponent> card, NanoChatUiMessageEvent msg)
|
||||||
|
{
|
||||||
|
if (msg.RecipientNumber is not uint chat)
|
||||||
|
return;
|
||||||
|
_nanoChat.ToggleChatMuted((card, card.Comp), chat);
|
||||||
|
UpdateUIForCard(card);
|
||||||
|
}
|
||||||
|
|
||||||
private void HandleToggleListNumber(Entity<NanoChatCardComponent> card)
|
private void HandleToggleListNumber(Entity<NanoChatCardComponent> card)
|
||||||
{
|
{
|
||||||
_nanoChat.SetListNumber((card, card.Comp), !_nanoChat.GetListNumber((card, card.Comp)));
|
_nanoChat.SetListNumber((card, card.Comp), !_nanoChat.GetListNumber((card, card.Comp)));
|
||||||
|
|
@ -248,10 +317,18 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
||||||
if (!EnsureRecipientExists(card, msg.RecipientNumber.Value))
|
if (!EnsureRecipientExists(card, msg.RecipientNumber.Value))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
var content = msg.Content;
|
||||||
|
if (!string.IsNullOrWhiteSpace(content))
|
||||||
|
{
|
||||||
|
content = content.Trim();
|
||||||
|
if (content.Length > NanoChatMessage.MaxContentLength)
|
||||||
|
content = content[..NanoChatMessage.MaxContentLength];
|
||||||
|
}
|
||||||
|
|
||||||
// Create and store message for sender
|
// Create and store message for sender
|
||||||
var message = new NanoChatMessage(
|
var message = new NanoChatMessage(
|
||||||
_timing.CurTime,
|
_timing.CurTime,
|
||||||
msg.Content,
|
content,
|
||||||
(uint)card.Comp.Number
|
(uint)card.Comp.Number
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -271,7 +348,7 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
||||||
|
|
||||||
_adminLogger.Add(LogType.Chat,
|
_adminLogger.Add(LogType.Chat,
|
||||||
LogImpact.Low,
|
LogImpact.Low,
|
||||||
$"{ToPrettyString(card):user} sent NanoChat message to {recipientsText}: {msg.Content}{(deliveryFailed ? " [DELIVERY FAILED]" : "")}");
|
$"{ToPrettyString(card):user} sent NanoChat message to {recipientsText}: {content}{(deliveryFailed ? " [DELIVERY FAILED]" : "")}");
|
||||||
|
|
||||||
var msgEv = new NanoChatMessageReceivedEvent(card);
|
var msgEv = new NanoChatMessageReceivedEvent(card);
|
||||||
RaiseLocalEvent(ref msgEv);
|
RaiseLocalEvent(ref msgEv);
|
||||||
|
|
@ -398,18 +475,17 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
||||||
Entity<NanoChatCardComponent> recipient,
|
Entity<NanoChatCardComponent> recipient,
|
||||||
NanoChatMessage message)
|
NanoChatMessage message)
|
||||||
{
|
{
|
||||||
var senderNumber = sender.Comp.Number;
|
if (sender.Comp.Number is not uint senderNumber)
|
||||||
if (senderNumber == null)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Always try to get and add sender info to recipient's contacts
|
// Always try to get and add sender info to recipient's contacts
|
||||||
if (!EnsureRecipientExists(recipient, senderNumber.Value))
|
if (!EnsureRecipientExists(recipient, senderNumber))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_nanoChat.AddMessage((recipient, recipient.Comp), senderNumber.Value, message with { DeliveryFailed = false });
|
_nanoChat.AddMessage((recipient, recipient.Comp), senderNumber, message with { DeliveryFailed = false });
|
||||||
|
|
||||||
if (recipient.Comp.IsClosed || _nanoChat.GetCurrentChat((recipient, recipient.Comp)) != senderNumber)
|
if (recipient.Comp.IsClosed || _nanoChat.GetCurrentChat((recipient, recipient.Comp)) != senderNumber)
|
||||||
HandleUnreadNotification(recipient, message);
|
HandleUnreadNotification(recipient, message, senderNumber);
|
||||||
|
|
||||||
var msgEv = new NanoChatMessageReceivedEvent(recipient);
|
var msgEv = new NanoChatMessageReceivedEvent(recipient);
|
||||||
RaiseLocalEvent(ref msgEv);
|
RaiseLocalEvent(ref msgEv);
|
||||||
|
|
@ -419,33 +495,49 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles unread message notifications and updates unread status.
|
/// Handles unread message notifications and updates unread status.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void HandleUnreadNotification(Entity<NanoChatCardComponent> recipient, NanoChatMessage message)
|
private void HandleUnreadNotification(Entity<NanoChatCardComponent> recipient,
|
||||||
|
NanoChatMessage message,
|
||||||
|
uint senderNumber)
|
||||||
{
|
{
|
||||||
// Get sender name from contacts or fall back to number
|
// Get sender name from contacts or fall back to number
|
||||||
var recipients = _nanoChat.GetRecipients((recipient, recipient.Comp));
|
var recipients = _nanoChat.GetRecipients((recipient, recipient.Comp));
|
||||||
var senderName = recipients.TryGetValue(message.SenderId, out var existingRecipient)
|
var senderName = recipients.TryGetValue(message.SenderId, out var senderRecipient)
|
||||||
? existingRecipient.Name
|
? senderRecipient.Name
|
||||||
: $"#{message.SenderId:D4}";
|
: $"#{message.SenderId:D4}";
|
||||||
|
var hasSelectedCurrentChat = _nanoChat.GetCurrentChat((recipient, recipient.Comp)) == senderNumber;
|
||||||
if (!recipient.Comp.Recipients[message.SenderId].HasUnread && !recipient.Comp.NotificationsMuted)
|
|
||||||
{
|
|
||||||
var pdaQuery = EntityQueryEnumerator<PdaComponent>();
|
|
||||||
while (pdaQuery.MoveNext(out var pdaUid, out var pdaComp))
|
|
||||||
{
|
|
||||||
if (pdaComp.ContainedId != recipient)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
_cartridge.SendNotification(pdaUid,
|
|
||||||
Loc.GetString("nano-chat-new-message-title", ("sender", senderName)),
|
|
||||||
Loc.GetString("nano-chat-new-message-body", ("message", TruncateMessage(message.Content))));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update unread status
|
// Update unread status
|
||||||
|
if (!hasSelectedCurrentChat)
|
||||||
_nanoChat.SetRecipient((recipient, recipient.Comp),
|
_nanoChat.SetRecipient((recipient, recipient.Comp),
|
||||||
message.SenderId,
|
message.SenderId,
|
||||||
existingRecipient with { HasUnread = true });
|
senderRecipient with { HasUnread = true });
|
||||||
|
|
||||||
|
// Temporary local to avoid trouble with read-only access; Contains doesn't modify the collection
|
||||||
|
HashSet<uint> mutedChats = recipient.Comp.MutedChats;
|
||||||
|
if (recipient.Comp.NotificationsMuted ||
|
||||||
|
mutedChats.Contains(message.SenderId) ||
|
||||||
|
recipient.Comp.PdaUid is not { } pdaUid ||
|
||||||
|
!TryComp<CartridgeLoaderComponent>(pdaUid, out var loader) ||
|
||||||
|
// Don't notify if the recipient has the NanoChat program open with this chat selected.
|
||||||
|
(hasSelectedCurrentChat &&
|
||||||
|
_ui.IsUiOpen(pdaUid, PdaUiKey.Key) &&
|
||||||
|
HasComp<NanoChatCartridgeComponent>(loader.ActiveProgram)))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var title = "";
|
||||||
|
if (!string.IsNullOrEmpty(senderRecipient.JobTitle))
|
||||||
|
{
|
||||||
|
var titleRecipient = SharedNanoChatSystem.Truncate(Loc.GetString("nano-chat-new-message-title-recipient",
|
||||||
|
("sender", senderName), ("jobTitle", senderRecipient.JobTitle)), NotificationTitleMaxLength, " \\[...\\]");
|
||||||
|
title = Loc.GetString("nano-chat-new-message-title", ("sender", titleRecipient));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
title = Loc.GetString("nano-chat-new-message-title", ("sender", senderName));
|
||||||
|
|
||||||
|
_cartridge.SendNotification(pdaUid,
|
||||||
|
title,
|
||||||
|
Loc.GetString("nano-chat-new-message-body", ("message", SharedNanoChatSystem.Truncate(message.Content, NotificationMaxLength, " [...]"))),
|
||||||
|
loader);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -505,16 +597,6 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Truncates a message to the notification maximum length.
|
|
||||||
/// </summary>
|
|
||||||
private static string TruncateMessage(string message)
|
|
||||||
{
|
|
||||||
return message.Length <= NotificationMaxLength
|
|
||||||
? message
|
|
||||||
: message[..(NotificationMaxLength - 4)] + " [...]";
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnUiReady(Entity<NanoChatCartridgeComponent> ent, ref CartridgeUiReadyEvent args)
|
private void OnUiReady(Entity<NanoChatCartridgeComponent> ent, ref CartridgeUiReadyEvent args)
|
||||||
{
|
{
|
||||||
_cartridge.RegisterBackgroundProgram(args.Loader, ent);
|
_cartridge.RegisterBackgroundProgram(args.Loader, ent);
|
||||||
|
|
@ -547,6 +629,7 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
||||||
|
|
||||||
var recipients = new Dictionary<uint, NanoChatRecipient>();
|
var recipients = new Dictionary<uint, NanoChatRecipient>();
|
||||||
var messages = new Dictionary<uint, List<NanoChatMessage>>();
|
var messages = new Dictionary<uint, List<NanoChatMessage>>();
|
||||||
|
var mutedChats = new HashSet<uint>();
|
||||||
uint? currentChat = null;
|
uint? currentChat = null;
|
||||||
uint ownNumber = 0;
|
uint ownNumber = 0;
|
||||||
var maxRecipients = 50;
|
var maxRecipients = 50;
|
||||||
|
|
@ -557,6 +640,7 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
||||||
{
|
{
|
||||||
recipients = card.Recipients;
|
recipients = card.Recipients;
|
||||||
messages = card.Messages;
|
messages = card.Messages;
|
||||||
|
mutedChats = card.MutedChats;
|
||||||
currentChat = card.CurrentChat;
|
currentChat = card.CurrentChat;
|
||||||
ownNumber = card.Number ?? 0;
|
ownNumber = card.Number ?? 0;
|
||||||
maxRecipients = card.MaxRecipients;
|
maxRecipients = card.MaxRecipients;
|
||||||
|
|
@ -566,6 +650,7 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
||||||
|
|
||||||
var state = new NanoChatUiState(recipients,
|
var state = new NanoChatUiState(recipients,
|
||||||
messages,
|
messages,
|
||||||
|
mutedChats,
|
||||||
contacts,
|
contacts,
|
||||||
currentChat,
|
currentChat,
|
||||||
ownNumber,
|
ownNumber,
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ using Content.Shared._DV.CartridgeLoader.Cartridges;
|
||||||
using Content.Shared._DV.NanoChat;
|
using Content.Shared._DV.NanoChat;
|
||||||
using Content.Shared.Kitchen.Components;
|
using Content.Shared.Kitchen.Components;
|
||||||
using Content.Shared.NameIdentifier;
|
using Content.Shared.NameIdentifier;
|
||||||
|
using Content.Shared.PDA;
|
||||||
|
using Robust.Shared.Containers;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
|
|
@ -26,10 +28,32 @@ public sealed class NanoChatSystem : SharedNanoChatSystem
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<NanoChatCardComponent, EntGotInsertedIntoContainerMessage>(OnInserted);
|
||||||
|
SubscribeLocalEvent<NanoChatCardComponent, EntGotRemovedFromContainerMessage>(OnRemoved);
|
||||||
|
|
||||||
SubscribeLocalEvent<NanoChatCardComponent, MapInitEvent>(OnCardInit);
|
SubscribeLocalEvent<NanoChatCardComponent, MapInitEvent>(OnCardInit);
|
||||||
SubscribeLocalEvent<NanoChatCardComponent, BeingMicrowavedEvent>(OnMicrowaved, after: [typeof(IdCardSystem)]);
|
SubscribeLocalEvent<NanoChatCardComponent, BeingMicrowavedEvent>(OnMicrowaved, after: [typeof(IdCardSystem)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnInserted(Entity<NanoChatCardComponent> ent, ref EntGotInsertedIntoContainerMessage args)
|
||||||
|
{
|
||||||
|
if (args.Container.ID != PdaComponent.PdaIdSlotId)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ent.Comp.PdaUid = args.Container.Owner;
|
||||||
|
Dirty(ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnRemoved(Entity<NanoChatCardComponent> ent, ref EntGotRemovedFromContainerMessage args)
|
||||||
|
{
|
||||||
|
if (args.Container.ID != PdaComponent.PdaIdSlotId)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ent.Comp.PdaUid = null;
|
||||||
|
Dirty(ent);
|
||||||
|
}
|
||||||
|
|
||||||
private void OnMicrowaved(Entity<NanoChatCardComponent> ent, ref BeingMicrowavedEvent args)
|
private void OnMicrowaved(Entity<NanoChatCardComponent> ent, ref BeingMicrowavedEvent args)
|
||||||
{
|
{
|
||||||
// Skip if the entity was deleted (e.g., by ID card system burning it)
|
// Skip if the entity was deleted (e.g., by ID card system burning it)
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,12 @@ namespace Content.Shared.Input
|
||||||
public static readonly BoundKeyFunction CycleChatChannelForward = "CycleChatChannelForward";
|
public static readonly BoundKeyFunction CycleChatChannelForward = "CycleChatChannelForward";
|
||||||
public static readonly BoundKeyFunction CycleChatChannelBackward = "CycleChatChannelBackward";
|
public static readonly BoundKeyFunction CycleChatChannelBackward = "CycleChatChannelBackward";
|
||||||
public static readonly BoundKeyFunction EscapeContext = "EscapeContext";
|
public static readonly BoundKeyFunction EscapeContext = "EscapeContext";
|
||||||
|
// DeltaV - Begin NanoChat keybinds
|
||||||
|
public static readonly BoundKeyFunction NanoChatNavigateUp = "NanoChatNavigateUp";
|
||||||
|
public static readonly BoundKeyFunction NanoChatNavigateDown = "NanoChatNavigateDown";
|
||||||
|
public static readonly BoundKeyFunction NanoChatNavigateUpUnread = "NanoChatNavigateUpUnread";
|
||||||
|
public static readonly BoundKeyFunction NanoChatNavigateDownUnread = "NanoChatNavigateDownUnread";
|
||||||
|
// DeltaV - End NanoChat keybinds
|
||||||
public static readonly BoundKeyFunction OpenCharacterMenu = "OpenCharacterMenu";
|
public static readonly BoundKeyFunction OpenCharacterMenu = "OpenCharacterMenu";
|
||||||
public static readonly BoundKeyFunction OpenEmotesMenu = "OpenEmotesMenu";
|
public static readonly BoundKeyFunction OpenEmotesMenu = "OpenEmotesMenu";
|
||||||
public static readonly BoundKeyFunction OpenCraftingMenu = "OpenCraftingMenu";
|
public static readonly BoundKeyFunction OpenCraftingMenu = "OpenCraftingMenu";
|
||||||
|
|
|
||||||
|
|
@ -50,10 +50,12 @@ public enum NanoChatUiMessageType : byte
|
||||||
{
|
{
|
||||||
NewChat,
|
NewChat,
|
||||||
SelectChat,
|
SelectChat,
|
||||||
|
EditChat,
|
||||||
CloseChat,
|
CloseChat,
|
||||||
SendMessage,
|
SendMessage,
|
||||||
DeleteChat,
|
DeleteChat,
|
||||||
ToggleMute,
|
ToggleMute,
|
||||||
|
ToggleMuteChat,
|
||||||
ToggleListNumber,
|
ToggleListNumber,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,6 +102,8 @@ public struct NanoChatRecipient
|
||||||
[Serializable, NetSerializable, DataRecord]
|
[Serializable, NetSerializable, DataRecord]
|
||||||
public struct NanoChatMessage
|
public struct NanoChatMessage
|
||||||
{
|
{
|
||||||
|
public const int MaxContentLength = 256;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// When the message was sent.
|
/// When the message was sent.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,9 @@ namespace Content.Shared._DV.CartridgeLoader.Cartridges;
|
||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
public sealed class NanoChatUiState : BoundUserInterfaceState
|
public sealed class NanoChatUiState : BoundUserInterfaceState
|
||||||
{
|
{
|
||||||
public readonly Dictionary<uint, NanoChatRecipient> Recipients = new();
|
public readonly Dictionary<uint, NanoChatRecipient> Recipients = [];
|
||||||
public readonly Dictionary<uint, List<NanoChatMessage>> Messages = new();
|
public readonly Dictionary<uint, List<NanoChatMessage>> Messages = [];
|
||||||
|
public readonly HashSet<uint> MutedChats = [];
|
||||||
public readonly List<NanoChatRecipient>? Contacts;
|
public readonly List<NanoChatRecipient>? Contacts;
|
||||||
public readonly uint? CurrentChat;
|
public readonly uint? CurrentChat;
|
||||||
public readonly uint OwnNumber;
|
public readonly uint OwnNumber;
|
||||||
|
|
@ -17,6 +18,7 @@ public sealed class NanoChatUiState : BoundUserInterfaceState
|
||||||
public NanoChatUiState(
|
public NanoChatUiState(
|
||||||
Dictionary<uint, NanoChatRecipient> recipients,
|
Dictionary<uint, NanoChatRecipient> recipients,
|
||||||
Dictionary<uint, List<NanoChatMessage>> messages,
|
Dictionary<uint, List<NanoChatMessage>> messages,
|
||||||
|
HashSet<uint> mutedChats,
|
||||||
List<NanoChatRecipient>? contacts,
|
List<NanoChatRecipient>? contacts,
|
||||||
uint? currentChat,
|
uint? currentChat,
|
||||||
uint ownNumber,
|
uint ownNumber,
|
||||||
|
|
@ -26,6 +28,7 @@ public sealed class NanoChatUiState : BoundUserInterfaceState
|
||||||
{
|
{
|
||||||
Recipients = recipients;
|
Recipients = recipients;
|
||||||
Messages = messages;
|
Messages = messages;
|
||||||
|
MutedChats = mutedChats;
|
||||||
Contacts = contacts;
|
Contacts = contacts;
|
||||||
CurrentChat = currentChat;
|
CurrentChat = currentChat;
|
||||||
OwnNumber = ownNumber;
|
OwnNumber = ownNumber;
|
||||||
|
|
|
||||||
|
|
@ -25,13 +25,19 @@ public sealed partial class NanoChatCardComponent : Component
|
||||||
/// All chat recipients stored on this card.
|
/// All chat recipients stored on this card.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
public Dictionary<uint, NanoChatRecipient> Recipients = new();
|
public Dictionary<uint, NanoChatRecipient> Recipients = [];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All messages stored on this card, keyed by recipient number.
|
/// All messages stored on this card, keyed by recipient number.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
public Dictionary<uint, List<NanoChatMessage>> Messages = new();
|
public Dictionary<uint, List<NanoChatMessage>> Messages = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The NanoChat numbers that should not give a notification, even when notifications are enabled.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public HashSet<uint> MutedChats = [];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The currently selected chat recipient number.
|
/// The currently selected chat recipient number.
|
||||||
|
|
@ -62,4 +68,10 @@ public sealed partial class NanoChatCardComponent : Component
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
public bool ListNumber = true;
|
public bool ListNumber = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The PDA that this card is currently inserted to.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public EntityUid? PdaUid = null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,16 @@ public abstract class SharedNanoChatSystem : EntitySystem
|
||||||
args.PushMarkup(Loc.GetString("nanochat-card-examine-number", ("number", $"{ent.Comp.Number:D4}")));
|
args.PushMarkup(Loc.GetString("nanochat-card-examine-number", ("number", $"{ent.Comp.Number:D4}")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper Method for truncating a string to maximum length
|
||||||
|
/// </summary>
|
||||||
|
public static string Truncate(string text, int maxLength, string overflowText = "...")
|
||||||
|
{
|
||||||
|
return text.Length > maxLength
|
||||||
|
? text[..(maxLength - overflowText.Length)] + overflowText
|
||||||
|
: text;
|
||||||
|
}
|
||||||
|
|
||||||
#region Public API Methods
|
#region Public API Methods
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -188,6 +198,19 @@ public abstract class SharedNanoChatSystem : EntitySystem
|
||||||
Dirty(card);
|
Dirty(card);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets whether notifications are muted for a specific chat.
|
||||||
|
/// </summary>
|
||||||
|
public void ToggleChatMuted(Entity<NanoChatCardComponent?> card, uint chat)
|
||||||
|
{
|
||||||
|
if (!Resolve(card, ref card.Comp))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!card.Comp.MutedChats.Remove(chat))
|
||||||
|
card.Comp.MutedChats.Add(chat);
|
||||||
|
Dirty(card);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets whether NanoChat number is listed.
|
/// Gets whether NanoChat number is listed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -170,13 +170,16 @@ nano-chat-no-chats = No active chats
|
||||||
nano-chat-select-chat = Select a chat to begin
|
nano-chat-select-chat = Select a chat to begin
|
||||||
nano-chat-message-placeholder = Type a message...
|
nano-chat-message-placeholder = Type a message...
|
||||||
nano-chat-send = Send
|
nano-chat-send = Send
|
||||||
|
nano-chat-edit = Edit Contact
|
||||||
nano-chat-delete = Delete
|
nano-chat-delete = Delete
|
||||||
nano-chat-loading = Loading...
|
nano-chat-loading = Loading...
|
||||||
nano-chat-message-too-long = Message too long ({$current}/{$max} characters)
|
nano-chat-message-too-long = Message too long ({$current}/{$max} characters)
|
||||||
nano-chat-max-recipients = Maximum number of chats reached
|
nano-chat-max-recipients = Maximum number of chats reached
|
||||||
nano-chat-new-message-title = Message from {$sender}
|
nano-chat-new-message-title = Message from {$sender}
|
||||||
|
nano-chat-new-message-title-recipient = {$sender} ({$jobTitle})
|
||||||
nano-chat-new-message-body = {$message}
|
nano-chat-new-message-body = {$message}
|
||||||
nano-chat-toggle-mute = Mute notifications
|
nano-chat-toggle-mute = Mute notifications
|
||||||
|
nano-chat-toggle-mute-chat = Mute chat
|
||||||
nano-chat-delivery-failed = Failed to deliver
|
nano-chat-delivery-failed = Failed to deliver
|
||||||
nano-chat-look-up-no-server = No valid telecommunications server found
|
nano-chat-look-up-no-server = No valid telecommunications server found
|
||||||
nano-chat-look-up = Look up numbers
|
nano-chat-look-up = Look up numbers
|
||||||
|
|
@ -193,6 +196,10 @@ nano-chat-job-placeholder = Enter a job title (optional)
|
||||||
nano-chat-cancel = Cancel
|
nano-chat-cancel = Cancel
|
||||||
nano-chat-create = Create
|
nano-chat-create = Create
|
||||||
|
|
||||||
|
# Edit chat popup
|
||||||
|
nano-chat-edit-title = Edit a contact
|
||||||
|
nano-chat-confirm = Confirm
|
||||||
|
|
||||||
# LogProbe additions
|
# LogProbe additions
|
||||||
log-probe-scan-nanochat = Scanned {$card}'s NanoChat logs
|
log-probe-scan-nanochat = Scanned {$card}'s NanoChat logs
|
||||||
log-probe-header-access = Access Log Scanner
|
log-probe-header-access = Access Log Scanner
|
||||||
|
|
|
||||||
|
|
@ -3,3 +3,10 @@ ui-options-general-forknotice = Note: These settings are fork-specific and might
|
||||||
|
|
||||||
ui-options-no-filters = Disable species vision filters
|
ui-options-no-filters = Disable species vision filters
|
||||||
ui-options-function-swap-hands-reversed = Swap hands (reversed)
|
ui-options-function-swap-hands-reversed = Swap hands (reversed)
|
||||||
|
|
||||||
|
## DeltaV NanoChat keybinds
|
||||||
|
ui-options-header-nano-chat = NanoChat
|
||||||
|
ui-options-function-nano-chat-navigate-up = Navigate up
|
||||||
|
ui-options-function-nano-chat-navigate-down = Navigate down
|
||||||
|
ui-options-function-nano-chat-navigate-up-unread = Navigate up to next unread
|
||||||
|
ui-options-function-nano-chat-navigate-down-unread = Navigate down to next unread
|
||||||
|
|
|
||||||
|
|
@ -3,3 +3,6 @@ Licensed under CC BY 4.0
|
||||||
|
|
||||||
hamburger_icon.svg.png
|
hamburger_icon.svg.png
|
||||||
Released into the public domain
|
Released into the public domain
|
||||||
|
|
||||||
|
edit.svg.png by H2D2 Design under MIT License
|
||||||
|
https://www.svgrepo.com/svg/453928/edit
|
||||||
|
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 924 B |
|
|
@ -165,12 +165,30 @@ binds:
|
||||||
- function: SwapHands
|
- function: SwapHands
|
||||||
type: State
|
type: State
|
||||||
key: X
|
key: X
|
||||||
# DeltaV - Swap Hands Reversed Start
|
# DeltaV - Begin custom keybinds
|
||||||
- function: SwapHandsReversed
|
- function: SwapHandsReversed
|
||||||
type: State
|
type: State
|
||||||
key: X
|
key: X
|
||||||
mod1: Shift
|
mod1: Shift
|
||||||
# DeltaV - Swap Hands Reversed End
|
- function: NanoChatNavigateUp
|
||||||
|
type: State
|
||||||
|
key: Up
|
||||||
|
mod1: Alt
|
||||||
|
- function: NanoChatNavigateDown
|
||||||
|
type: State
|
||||||
|
key: Down
|
||||||
|
mod1: Alt
|
||||||
|
- function: NanoChatNavigateUpUnread
|
||||||
|
type: State
|
||||||
|
key: Up
|
||||||
|
mod1: Alt
|
||||||
|
mod2: Shift
|
||||||
|
- function: NanoChatNavigateDownUnread
|
||||||
|
type: State
|
||||||
|
key: Down
|
||||||
|
mod1: Alt
|
||||||
|
mod2: Shift
|
||||||
|
# DeltaV - End custom keybinds
|
||||||
- function: MoveStoredItem
|
- function: MoveStoredItem
|
||||||
type: State
|
type: State
|
||||||
key: MouseLeft
|
key: MouseLeft
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue