mirror of
https://github.com/wega-team/ss14-wega.git
synced 2026-02-15 03:31:44 +01:00
@@ -0,0 +1,15 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
Title="{Loc 'nano-chat-ui-create-group-title'}"
|
||||
MinWidth="500">
|
||||
|
||||
<BoxContainer Orientation="Vertical" Margin="4">
|
||||
<Label Text="{Loc 'nano-chat-ui-group-name'}" StyleClasses="LabelHeading"/>
|
||||
<LineEdit Name="GroupNameInput" PlaceHolder="{Loc 'nano-chat-ui-group-name-placeholder'}"/>
|
||||
|
||||
<BoxContainer Orientation="Horizontal" HorizontalAlignment="Right" Margin="0 8 0 0">
|
||||
<Button Name="CancelButton" Text="{Loc 'nano-chat-ui-cancel'}"/>
|
||||
<Button Name="CreateButton" Text="{Loc 'nano-chat-ui-create'}"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
|
||||
</DefaultWindow>
|
||||
@@ -0,0 +1,30 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client._Wega.CartridgeLoader.Cartridges;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class NanoChatCreateGroupPopup : DefaultWindow
|
||||
{
|
||||
public Action<string>? OnGroupCreated;
|
||||
|
||||
public NanoChatCreateGroupPopup()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
CancelButton.OnPressed += _ => Close();
|
||||
CreateButton.OnPressed += _ =>
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(GroupNameInput.Text))
|
||||
{
|
||||
var groupName = GroupNameInput.Text.Length <= 16
|
||||
? GroupNameInput.Text
|
||||
: GroupNameInput.Text[..16];
|
||||
|
||||
OnGroupCreated?.Invoke(groupName);
|
||||
Close();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Content.Shared.CartridgeLoader.Cartridges;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client._Wega.CartridgeLoader.Cartridges;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class NanoChatGroupControl : Control
|
||||
{
|
||||
public Action? OnPressed;
|
||||
|
||||
private readonly ChatGroup _group;
|
||||
|
||||
public NanoChatGroupControl(ChatGroup group)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
_group = group;
|
||||
|
||||
var groupName = group.GroupName;
|
||||
if (group.HasUnread)
|
||||
groupName += Loc.GetString("nano-chat-ui-unread-indicator");
|
||||
|
||||
GroupName.Text = groupName;
|
||||
MemberCount.Text = group.MemberCount.ToString();
|
||||
|
||||
MainButton.OnPressed += _ => OnPressed?.Invoke();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<Control xmlns="https://spacestation14.io">
|
||||
<Button Name="MainButton" StyleClasses="ButtonSquare">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<Label Name="GroupName" HorizontalExpand="True"/>
|
||||
<Label Name="MemberCount" StyleClasses="LabelSubText" Margin="4 0 0 0"/>
|
||||
</BoxContainer>
|
||||
</Button>
|
||||
</Control>
|
||||
@@ -0,0 +1,15 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
Title="{Loc 'nano-chat-ui-join-group-title'}"
|
||||
MinWidth="500">
|
||||
|
||||
<BoxContainer Orientation="Vertical" Margin="4">
|
||||
<Label Text="{Loc 'nano-chat-ui-group-id'}" StyleClasses="LabelHeading"/>
|
||||
<LineEdit Name="GroupIdInput" PlaceHolder="{Loc 'nano-chat-ui-group-id-placeholder'}"/>
|
||||
|
||||
<BoxContainer Orientation="Horizontal" HorizontalAlignment="Right" Margin="0 8 0 0">
|
||||
<Button Name="CancelButton" Text="{Loc 'nano-chat-ui-cancel'}"/>
|
||||
<Button Name="JoinButton" Text="{Loc 'nano-chat-ui-join'}"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
|
||||
</DefaultWindow>
|
||||
@@ -0,0 +1,41 @@
|
||||
using System.Linq;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client._Wega.CartridgeLoader.Cartridges;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class NanoChatJoinGroupPopup : DefaultWindow
|
||||
{
|
||||
public Action<string>? OnGroupJoined;
|
||||
|
||||
public NanoChatJoinGroupPopup()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
CancelButton.OnPressed += _ => Close();
|
||||
JoinButton.OnPressed += _ =>
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(GroupIdInput.Text))
|
||||
{
|
||||
var normalizedId = NormalizeGroupId(GroupIdInput.Text.Trim());
|
||||
var groupId = normalizedId.Length <= 5
|
||||
? normalizedId
|
||||
: normalizedId[..5];
|
||||
|
||||
OnGroupJoined?.Invoke(groupId);
|
||||
Close();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private string NormalizeGroupId(string contactId)
|
||||
{
|
||||
var cleanedId = contactId.Trim().Replace(" ", "");
|
||||
if (!cleanedId.StartsWith("G") && cleanedId.All(char.IsDigit))
|
||||
return "G" + cleanedId;
|
||||
|
||||
return cleanedId;
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@ public sealed partial class NanoChatUi : UIFragment
|
||||
{
|
||||
private NanoChatUiFragment? _fragment;
|
||||
private NanoChatAddContactPopup? _addContactPopup;
|
||||
private NanoChatJoinGroupPopup? _joinGroupPopup;
|
||||
private NanoChatCreateGroupPopup? _createGroupPopup;
|
||||
|
||||
public override Control GetUIFragmentRoot() => _fragment!;
|
||||
|
||||
@@ -16,25 +18,36 @@ public sealed partial class NanoChatUi : UIFragment
|
||||
{
|
||||
_fragment = new NanoChatUiFragment();
|
||||
_addContactPopup = new NanoChatAddContactPopup();
|
||||
_joinGroupPopup = new NanoChatJoinGroupPopup();
|
||||
_createGroupPopup = new NanoChatCreateGroupPopup();
|
||||
|
||||
_fragment.InitializeEmojiPicker();
|
||||
|
||||
_fragment.OpenAddContact += () => _addContactPopup.OpenCentered();
|
||||
_fragment.JoinGroup += () => _joinGroupPopup.OpenCentered();
|
||||
_fragment.CreateGroup += () => _createGroupPopup.OpenCentered();
|
||||
|
||||
_fragment.EraseChat += contactId =>
|
||||
{
|
||||
userInterface.SendMessage(new CartridgeUiMessage(
|
||||
new NanoChatUiMessageEvent(new NanoChatEraseContact(contactId))));
|
||||
};
|
||||
|
||||
_fragment.LeaveChat += groupId =>
|
||||
{
|
||||
userInterface.SendMessage(new CartridgeUiMessage(
|
||||
new NanoChatUiMessageEvent(new NanoChatLeaveGroup(groupId))));
|
||||
};
|
||||
|
||||
_fragment.OpenEmojiPicker += () => _fragment.OpenEmojiPickerInternal();
|
||||
|
||||
_fragment.OnMutePressed += () =>
|
||||
userInterface.SendMessage(new CartridgeUiMessage(
|
||||
new NanoChatUiMessageEvent(new NanoChatMuted())));
|
||||
|
||||
_fragment.SetActiveChat += contactId =>
|
||||
_fragment.SetActiveChat += chatId =>
|
||||
userInterface.SendMessage(new CartridgeUiMessage(
|
||||
new NanoChatUiMessageEvent(new NanoChatSetActiveChat(contactId))));
|
||||
new NanoChatUiMessageEvent(new NanoChatSetActiveChat(chatId))));
|
||||
|
||||
_fragment.SendMessage += message =>
|
||||
{
|
||||
@@ -51,6 +64,18 @@ public sealed partial class NanoChatUi : UIFragment
|
||||
userInterface.SendMessage(new CartridgeUiMessage(
|
||||
new NanoChatUiMessageEvent(new NanoChatAddContact(contactId, contactName))));
|
||||
};
|
||||
|
||||
_joinGroupPopup.OnGroupJoined += groupId =>
|
||||
{
|
||||
userInterface.SendMessage(new CartridgeUiMessage(
|
||||
new NanoChatUiMessageEvent(new NanoChatJoinGroup(groupId))));
|
||||
};
|
||||
|
||||
_createGroupPopup.OnGroupCreated += groupName =>
|
||||
{
|
||||
userInterface.SendMessage(new CartridgeUiMessage(
|
||||
new NanoChatUiMessageEvent(new NanoChatCreateGroup(groupName))));
|
||||
};
|
||||
}
|
||||
|
||||
public override void UpdateState(BoundUserInterfaceState state)
|
||||
|
||||
@@ -22,6 +22,10 @@
|
||||
<TextureButton Name="MuteChatButton" MaxSize="24 24" Margin="0 0 4 0"/>
|
||||
<TextureButton Name="AddContactButton" MaxSize="24 24" ToolTip="{Loc 'nano-chat-ui-add-contact-tooltip'}"
|
||||
TexturePath="/Textures/Interface/VerbIcons/plus.svg.192dpi.png" VerticalAlignment="Center"/>
|
||||
<TextureButton Name="JoinGroupButton" MaxSize="24 24" ToolTip="{Loc 'nano-chat-ui-join-group-tooltip'}"
|
||||
TexturePath="/Textures/Interface/VerbIcons/in.svg.192dpi.png" VerticalAlignment="Center" Margin="4 0 0 0"/>
|
||||
<TextureButton Name="CreateGroupButton" MaxSize="24 24" ToolTip="{Loc 'nano-chat-ui-create-group-tooltip'}"
|
||||
TexturePath="/Textures/Interface/VerbIcons/group.svg.192dpi.png" VerticalAlignment="Center" Margin="4 0 0 0"/>
|
||||
</BoxContainer>
|
||||
</controls:StripeBack>
|
||||
</BoxContainer>
|
||||
@@ -29,15 +33,23 @@
|
||||
<!-- Main content -->
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="True">
|
||||
|
||||
<!-- Contacts list -->
|
||||
<!-- Contacts/Groups panel -->
|
||||
<PanelContainer StyleClasses="AngleRect" MinWidth="150" MaxWidth="200" VerticalExpand="True">
|
||||
<BoxContainer Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True">
|
||||
<!-- Tabs -->
|
||||
<TabContainer Name="ChatTypeTabs" VerticalExpand="True">
|
||||
<ScrollContainer VerticalExpand="True" HorizontalExpand="True">
|
||||
<BoxContainer Name="ContactsContainer" Orientation="Vertical" HorizontalExpand="True"/>
|
||||
</ScrollContainer>
|
||||
<ScrollContainer VerticalExpand="True" HorizontalExpand="True">
|
||||
<BoxContainer Name="GroupsContainer" Orientation="Vertical" HorizontalExpand="True"/>
|
||||
</ScrollContainer>
|
||||
</TabContainer>
|
||||
|
||||
<Label Name="NoContactsLabel" Text="{Loc 'nano-chat-ui-no-contacts'}" StyleClasses="LabelSubText"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" Visible="False"/>
|
||||
|
||||
<ScrollContainer VerticalExpand="True" HorizontalExpand="True">
|
||||
<BoxContainer Name="ContactsContainer" Orientation="Vertical" HorizontalExpand="True"/>
|
||||
</ScrollContainer>
|
||||
<Label Name="NoGroupsLabel" Text="{Loc 'nano-chat-ui-no-groups'}" StyleClasses="LabelSubText"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" Visible="False"/>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
|
||||
@@ -54,6 +66,8 @@
|
||||
</PanelContainer.PanelOverride>
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="0 0 0 4">
|
||||
<Label Name="ChatTitle" StyleClasses="LabelHeading" Margin="4 0 0 0" VerticalAlignment="Center" HorizontalExpand="True"/>
|
||||
<TextureButton Name="LeaveChatButton" MaxSize="24 24" Margin="0 0 4 0" Visible="False" ToolTip="{Loc 'nano-chat-ui-leave-chat-tooltip'}"
|
||||
TexturePath="/Textures/Interface/VerbIcons/close.svg.192dpi.png"/>
|
||||
<TextureButton Name="EraseChatButton" MaxSize="24 24" Margin="0 0 4 0" Visible="False" ToolTip="{Loc 'nano-chat-ui-erase-chat-tooltip'}"
|
||||
TexturePath="/Textures/Interface/eraser.svg.png"/>
|
||||
<TextureButton Name="CloseChatButton" MaxSize="24 24" Visible="False" ToolTip="{Loc 'nano-chat-ui-close-chat-tooltip'}"
|
||||
|
||||
@@ -19,18 +19,27 @@ public sealed partial class NanoChatUiFragment : BoxContainer
|
||||
public Action<string>? SendMessage;
|
||||
public Action<string>? EraseChat;
|
||||
public Action<string>? CloseChat;
|
||||
public Action<string>? LeaveChat;
|
||||
public Action? JoinGroup;
|
||||
public Action? CreateGroup;
|
||||
|
||||
private NanoChatEmojiPopup? _emojiPopup;
|
||||
|
||||
public string? ActiveChatId;
|
||||
public Dictionary<string, ChatContact> Contacts = new();
|
||||
public Dictionary<string, ChatGroup> Groups = new();
|
||||
|
||||
public NanoChatUiFragment()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
Orientation = LayoutOrientation.Vertical;
|
||||
|
||||
ChatTypeTabs.SetTabTitle(0, Loc.GetString("nano-chat-ui-tab-contacts"));
|
||||
ChatTypeTabs.SetTabTitle(1, Loc.GetString("nano-chat-ui-tab-groups"));
|
||||
|
||||
AddContactButton.OnPressed += _ => OpenAddContact?.Invoke();
|
||||
JoinGroupButton.OnPressed += _ => JoinGroup?.Invoke();
|
||||
CreateGroupButton.OnPressed += _ => CreateGroup?.Invoke();
|
||||
MuteChatButton.OnPressed += _ => OnMutePressed?.Invoke();
|
||||
EmojiButton.OnPressed += _ => OpenEmojiPicker?.Invoke();
|
||||
|
||||
@@ -52,6 +61,16 @@ public sealed partial class NanoChatUiFragment : BoxContainer
|
||||
}
|
||||
};
|
||||
|
||||
LeaveChatButton.OnPressed += _ =>
|
||||
{
|
||||
if (ActiveChatId != null && ActiveChatId.StartsWith("G"))
|
||||
{
|
||||
LeaveChat?.Invoke(ActiveChatId);
|
||||
ActiveChatId = null;
|
||||
UpdateUiState();
|
||||
}
|
||||
};
|
||||
|
||||
CloseChatButton.OnPressed += _ =>
|
||||
{
|
||||
if (ActiveChatId != null)
|
||||
@@ -71,16 +90,24 @@ public sealed partial class NanoChatUiFragment : BoxContainer
|
||||
}
|
||||
};
|
||||
|
||||
ChatTypeTabs.OnTabChanged += _ => OnTabChanged();
|
||||
|
||||
EmojiButton.AddStyleClass(StyleNano.ButtonOpenBoth);
|
||||
SendButton.AddStyleClass(StyleNano.ButtonOpenLeft);
|
||||
|
||||
UpdateUiState();
|
||||
}
|
||||
|
||||
private void OnTabChanged()
|
||||
{
|
||||
UpdateContactsList();
|
||||
}
|
||||
|
||||
public void UpdateState(NanoChatUiState state)
|
||||
{
|
||||
OwnIdLabel.Text = state.ChatId;
|
||||
Contacts = state.Contacts;
|
||||
Groups = state.Groups;
|
||||
ActiveChatId = state.ActiveChat;
|
||||
|
||||
MuteChatButton.TexturePath = state.Muted
|
||||
@@ -90,7 +117,7 @@ public sealed partial class NanoChatUiFragment : BoxContainer
|
||||
? Loc.GetString("nano-chat-ui-unmute-tooltip")
|
||||
: Loc.GetString("nano-chat-ui-mute-tooltip");
|
||||
|
||||
// Update contacts list
|
||||
// Update contacts and groups lists
|
||||
UpdateContactsList();
|
||||
|
||||
// Update messages if we have active chat
|
||||
@@ -130,13 +157,28 @@ public sealed partial class NanoChatUiFragment : BoxContainer
|
||||
private void UpdateContactsList()
|
||||
{
|
||||
ContactsContainer.RemoveAllChildren();
|
||||
NoContactsLabel.Visible = Contacts.Count == 0;
|
||||
GroupsContainer.RemoveAllChildren();
|
||||
|
||||
foreach (var contact in Contacts.Values.OrderBy(c => c.ContactName))
|
||||
NoContactsLabel.Visible = Contacts.Count == 0 && ChatTypeTabs.CurrentTab == 0;
|
||||
NoGroupsLabel.Visible = Groups.Count == 0 && ChatTypeTabs.CurrentTab == 1;
|
||||
|
||||
if (ChatTypeTabs.CurrentTab == 0)
|
||||
{
|
||||
var control = new NanoChatContactControl(contact);
|
||||
control.OnPressed += () => SetActiveChat?.Invoke(contact.ContactId);
|
||||
ContactsContainer.AddChild(control);
|
||||
foreach (var contact in Contacts.Values.OrderBy(c => c.ContactName))
|
||||
{
|
||||
var control = new NanoChatContactControl(contact);
|
||||
control.OnPressed += () => SetActiveChat?.Invoke(contact.ContactId);
|
||||
ContactsContainer.AddChild(control);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var group in Groups.Values.OrderBy(g => g.GroupName))
|
||||
{
|
||||
var control = new NanoChatGroupControl(group);
|
||||
control.OnPressed += () => SetActiveChat?.Invoke(group.GroupId);
|
||||
GroupsContainer.AddChild(control);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,6 +199,7 @@ public sealed partial class NanoChatUiFragment : BoxContainer
|
||||
private void UpdateUiState()
|
||||
{
|
||||
var hasActiveChat = ActiveChatId != null;
|
||||
var isGroupChat = hasActiveChat && ActiveChatId?.StartsWith("G") == true;
|
||||
|
||||
// Update input state
|
||||
MessageInput.Editable = hasActiveChat;
|
||||
@@ -165,13 +208,27 @@ public sealed partial class NanoChatUiFragment : BoxContainer
|
||||
: Loc.GetString("nano-chat-ui-select-chat-input");
|
||||
EmojiButton.Disabled = !hasActiveChat;
|
||||
SendButton.Disabled = !hasActiveChat;
|
||||
EraseChatButton.Visible = hasActiveChat;
|
||||
|
||||
// Update button visibility
|
||||
EraseChatButton.Visible = hasActiveChat && !isGroupChat;
|
||||
LeaveChatButton.Visible = hasActiveChat && isGroupChat;
|
||||
CloseChatButton.Visible = hasActiveChat;
|
||||
|
||||
// Update chat title
|
||||
if (ActiveChatId != null && hasActiveChat && Contacts.TryGetValue(ActiveChatId, out var contact))
|
||||
if (ActiveChatId != null)
|
||||
{
|
||||
ChatTitle.Text = contact.ContactName;
|
||||
if (isGroupChat && Groups.TryGetValue(ActiveChatId, out var group))
|
||||
{
|
||||
ChatTitle.Text = $"{group.GroupName} {ActiveChatId}";
|
||||
}
|
||||
else if (!isGroupChat && Contacts.TryGetValue(ActiveChatId, out var contact))
|
||||
{
|
||||
ChatTitle.Text = contact.ContactName;
|
||||
}
|
||||
else
|
||||
{
|
||||
ChatTitle.Text = Loc.GetString("nano-chat-ui-unknown-chat");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -379,6 +379,9 @@ namespace Content.Server.Forensics
|
||||
|
||||
foreach (var (contactId, messages) in chatComp.Messages)
|
||||
{
|
||||
if (contactId.StartsWith("G"))
|
||||
continue;
|
||||
|
||||
if (chatComp.Contacts.TryGetValue(contactId, out var contact))
|
||||
{
|
||||
text.AppendLine(Loc.GetString("forensic-scanner-chat-with-contact",
|
||||
@@ -410,6 +413,9 @@ namespace Content.Server.Forensics
|
||||
|
||||
foreach (var contact in chatComp.Contacts.Values)
|
||||
{
|
||||
if (contact.ContactId.StartsWith("G"))
|
||||
continue;
|
||||
|
||||
if (!chatComp.Messages.ContainsKey(contact.ContactId))
|
||||
{
|
||||
text.AppendLine(Loc.GetString("forensic-scanner-chat-contact-no-messages",
|
||||
@@ -418,6 +424,68 @@ namespace Content.Server.Forensics
|
||||
text.AppendLine();
|
||||
}
|
||||
}
|
||||
|
||||
if (chatComp.Groups.Count > 0)
|
||||
{
|
||||
text.AppendLine(Loc.GetString("forensic-scanner-chat-groups-header"));
|
||||
text.AppendLine(new string('=', 36));
|
||||
|
||||
foreach (var (groupId, group) in chatComp.Groups)
|
||||
{
|
||||
text.AppendLine(Loc.GetString("forensic-scanner-chat-group-info",
|
||||
("name", group.GroupName),
|
||||
("id", groupId),
|
||||
("members", group.MemberCount)));
|
||||
|
||||
if (chatComp.Messages.TryGetValue(groupId, out var groupMessages))
|
||||
{
|
||||
text.AppendLine(Loc.GetString("forensic-scanner-chat-group-messages"));
|
||||
text.AppendLine(new string('-', 30));
|
||||
|
||||
foreach (var message in groupMessages)
|
||||
{
|
||||
var time = $"{(int)message.Timestamp.TotalHours:00}:{message.Timestamp.Minutes:00}";
|
||||
var sender = message.IsOwnMessage ?
|
||||
Loc.GetString("forensic-scanner-chat-you") :
|
||||
message.SenderName;
|
||||
|
||||
var status = message.Delivered ? "✓" : "✗";
|
||||
|
||||
text.AppendLine($"[{time}] {sender} ({status}): {message.Message}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
text.AppendLine(Loc.GetString("forensic-scanner-chat-group-no-messages"));
|
||||
}
|
||||
|
||||
text.AppendLine();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (groupId, messages) in chatComp.Messages)
|
||||
{
|
||||
if (groupId.StartsWith("G") && !chatComp.Groups.ContainsKey(groupId))
|
||||
{
|
||||
text.AppendLine(Loc.GetString("forensic-scanner-chat-archived-group",
|
||||
("id", groupId)));
|
||||
text.AppendLine(new string('-', 30));
|
||||
|
||||
foreach (var message in messages)
|
||||
{
|
||||
var time = $"{(int)message.Timestamp.TotalHours:00}:{message.Timestamp.Minutes:00}";
|
||||
var sender = message.IsOwnMessage ?
|
||||
Loc.GetString("forensic-scanner-chat-you") :
|
||||
message.SenderName;
|
||||
|
||||
var status = message.Delivered ? "✓" : "✗";
|
||||
|
||||
text.AppendLine($"[{time}] {sender} ({status}): {message.Message}");
|
||||
}
|
||||
|
||||
text.AppendLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Corvax-Wega-NanoChat-end
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ public sealed class NanoChatCartridgeSystem : SharedNanoChatCartridgeSystem
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
private readonly Dictionary<string, EntityUid> _activeChats = new();
|
||||
private readonly Dictionary<string, ChatGroupData> _groups = new();
|
||||
private const int MessageRange = 2000;
|
||||
|
||||
public override void Initialize()
|
||||
@@ -46,7 +47,7 @@ public sealed class NanoChatCartridgeSystem : SharedNanoChatCartridgeSystem
|
||||
SubscribeLocalEvent<NanoChatCartridgeComponent, EmpDisabledRemoved>(OnEmpFinished);
|
||||
}
|
||||
|
||||
private void OnRoundRestart(RoundRestartCleanupEvent args) { _activeChats.Clear(); }
|
||||
private void OnRoundRestart(RoundRestartCleanupEvent args) { _activeChats.Clear(); _groups.Clear(); }
|
||||
|
||||
private void OnOwnerNameChanged(Entity<PdaComponent> ent, ref OwnerNameChangedEvent args)
|
||||
{
|
||||
@@ -83,6 +84,17 @@ public sealed class NanoChatCartridgeSystem : SharedNanoChatCartridgeSystem
|
||||
return id;
|
||||
}
|
||||
|
||||
private string GenerateUniqueGroupId()
|
||||
{
|
||||
string id;
|
||||
do
|
||||
{
|
||||
id = "G" + _random.Next(10000).ToString("D4");
|
||||
} while (_groups.ContainsKey(id));
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
private void OnUiReady(Entity<NanoChatCartridgeComponent> ent, ref CartridgeUiReadyEvent args)
|
||||
{
|
||||
UpdateUiState(ent);
|
||||
@@ -91,6 +103,8 @@ public sealed class NanoChatCartridgeSystem : SharedNanoChatCartridgeSystem
|
||||
private void OnCartridgeRemoved(Entity<NanoChatCartridgeComponent> ent, ref CartridgeRemovedEvent args)
|
||||
{
|
||||
_activeChats.Remove(ent.Comp.ChatId);
|
||||
foreach (var group in _groups.Values.Where(g => g.Members.Contains(ent.Comp.ChatId)).ToList())
|
||||
group.Members.Remove(ent.Comp.ChatId);
|
||||
}
|
||||
|
||||
private void OnEmpPulse(Entity<NanoChatCartridgeComponent> ent, ref EmpPulseEvent args)
|
||||
@@ -130,6 +144,15 @@ public sealed class NanoChatCartridgeSystem : SharedNanoChatCartridgeSystem
|
||||
case NanoChatSetActiveChat setActiveChat:
|
||||
SetActiveChat(ent, setActiveChat.ContactId);
|
||||
break;
|
||||
case NanoChatCreateGroup createGroup:
|
||||
CreateGroup(ent, createGroup.GroupName);
|
||||
break;
|
||||
case NanoChatJoinGroup joinGroup:
|
||||
JoinGroup(ent, joinGroup.GroupId);
|
||||
break;
|
||||
case NanoChatLeaveGroup leaveGroup:
|
||||
LeaveGroup(ent, leaveGroup.GroupId);
|
||||
break;
|
||||
}
|
||||
|
||||
UpdateUiState(ent);
|
||||
@@ -146,9 +169,7 @@ public sealed class NanoChatCartridgeSystem : SharedNanoChatCartridgeSystem
|
||||
private void EraseContact(Entity<NanoChatCartridgeComponent> ent, string contactId)
|
||||
{
|
||||
if (ent.Comp.Contacts.ContainsKey(contactId))
|
||||
{
|
||||
ent.Comp.Contacts.Remove(contactId);
|
||||
}
|
||||
|
||||
ent.Comp.ActiveChat = null;
|
||||
UpdateUiState(ent);
|
||||
@@ -160,17 +181,86 @@ public sealed class NanoChatCartridgeSystem : SharedNanoChatCartridgeSystem
|
||||
UpdateUiState(ent);
|
||||
}
|
||||
|
||||
private void SetActiveChat(Entity<NanoChatCartridgeComponent> ent, string contactId)
|
||||
private void SetActiveChat(Entity<NanoChatCartridgeComponent> ent, string chatId)
|
||||
{
|
||||
ent.Comp.ActiveChat = contactId;
|
||||
if (ent.Comp.Contacts.TryGetValue(contactId, out var contact))
|
||||
ent.Comp.ActiveChat = chatId;
|
||||
if (ent.Comp.Contacts.TryGetValue(chatId, out var contact))
|
||||
{
|
||||
ent.Comp.Contacts[contactId] = new ChatContact(contactId, contact.ContactName, false);
|
||||
ent.Comp.Contacts[chatId] = new ChatContact(chatId, contact.ContactName, false);
|
||||
}
|
||||
|
||||
if (ent.Comp.Groups.TryGetValue(chatId, out var group))
|
||||
{
|
||||
ent.Comp.Groups[chatId] = new ChatGroup(chatId, group.GroupName, false, group.MemberCount);
|
||||
}
|
||||
|
||||
UpdateUiState(ent);
|
||||
}
|
||||
|
||||
private void CreateGroup(Entity<NanoChatCartridgeComponent> ent, string groupName)
|
||||
{
|
||||
var groupId = GenerateUniqueGroupId();
|
||||
var groupData = new ChatGroupData
|
||||
{
|
||||
GroupId = groupId,
|
||||
GroupName = groupName
|
||||
};
|
||||
|
||||
_groups[groupId] = groupData;
|
||||
|
||||
var systemMessage = new ChatMessage(
|
||||
"system", "System",
|
||||
Loc.GetString("nano-chat-create-group-message", ("name", ent.Comp.OwnerName), ("groupName", groupName)),
|
||||
_timing.CurTime,
|
||||
false,
|
||||
true
|
||||
);
|
||||
|
||||
groupData.Messages.Add(systemMessage);
|
||||
|
||||
JoinGroup(ent, groupId);
|
||||
|
||||
_admin.Add(LogType.Action, LogImpact.Low,
|
||||
$"Group created: '{groupName}' (ID: {groupId}) by {ent.Comp.ChatId}");
|
||||
}
|
||||
|
||||
private void JoinGroup(Entity<NanoChatCartridgeComponent> ent, string groupId)
|
||||
{
|
||||
if (_groups.TryGetValue(groupId, out var group))
|
||||
{
|
||||
group.Members.Add(ent.Comp.ChatId);
|
||||
|
||||
ent.Comp.Groups[groupId] = new ChatGroup(
|
||||
groupId,
|
||||
group.GroupName,
|
||||
false,
|
||||
group.Members.Count
|
||||
);
|
||||
|
||||
NotifyGroupMembers(groupId, Loc.GetString("nano-chat-join-message", ("name", ent.Comp.OwnerName)));
|
||||
|
||||
UpdateAllGroupMembersUi(groupId);
|
||||
}
|
||||
}
|
||||
|
||||
private void LeaveGroup(Entity<NanoChatCartridgeComponent> ent, string groupId)
|
||||
{
|
||||
if (_groups.TryGetValue(groupId, out var group))
|
||||
{
|
||||
group.Members.Remove(ent.Comp.ChatId);
|
||||
ent.Comp.Groups.Remove(groupId);
|
||||
|
||||
NotifyGroupMembers(groupId, Loc.GetString("nano-chat-leave-message", ("name", ent.Comp.OwnerName)));
|
||||
|
||||
if (ent.Comp.ActiveChat == groupId)
|
||||
{
|
||||
ent.Comp.ActiveChat = null;
|
||||
}
|
||||
|
||||
UpdateAllGroupMembersUi(groupId);
|
||||
}
|
||||
}
|
||||
|
||||
private void SendMessage(Entity<NanoChatCartridgeComponent> sender, string recipientId, string message)
|
||||
{
|
||||
if (_timing.CurTime < sender.Comp.NextMessageAllowedAfter)
|
||||
@@ -208,6 +298,12 @@ public sealed class NanoChatCartridgeSystem : SharedNanoChatCartridgeSystem
|
||||
return;
|
||||
}
|
||||
|
||||
if (recipientId.StartsWith("G") && _groups.TryGetValue(recipientId, out var group))
|
||||
{
|
||||
SendGroupMessage(sender, group, message, originalMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_activeChats.TryGetValue(recipientId, out var recipientEntity))
|
||||
{
|
||||
AddUndeliveredMessage(sender, recipientId, originalMessage);
|
||||
@@ -271,21 +367,107 @@ public sealed class NanoChatCartridgeSystem : SharedNanoChatCartridgeSystem
|
||||
UpdateUiState(sender);
|
||||
}
|
||||
|
||||
private void SendGroupMessage(Entity<NanoChatCartridgeComponent> sender, ChatGroupData group, string message, string originalMessage)
|
||||
{
|
||||
var timestamp = _timing.CurTime;
|
||||
var groupMessage = new ChatMessage(sender.Comp.ChatId, sender.Comp.OwnerName,
|
||||
message, timestamp, true, true);
|
||||
|
||||
group.Messages.Add(groupMessage);
|
||||
|
||||
if (!sender.Comp.Messages.ContainsKey(group.GroupId))
|
||||
sender.Comp.Messages[group.GroupId] = new List<ChatMessage>();
|
||||
sender.Comp.Messages[group.GroupId].Add(groupMessage);
|
||||
|
||||
foreach (var memberId in group.Members)
|
||||
{
|
||||
if (memberId == sender.Comp.ChatId)
|
||||
continue;
|
||||
|
||||
if (!_activeChats.TryGetValue(memberId, out var memberEntity))
|
||||
continue;
|
||||
|
||||
if (!TryComp<NanoChatCartridgeComponent>(memberEntity, out var memberComp))
|
||||
continue;
|
||||
|
||||
if (!IsWithinRange(sender.Owner, memberEntity))
|
||||
continue;
|
||||
|
||||
if (!memberComp.Messages.ContainsKey(group.GroupId))
|
||||
memberComp.Messages[group.GroupId] = new List<ChatMessage>();
|
||||
|
||||
var memberMessage = new ChatMessage(sender.Comp.ChatId, sender.Comp.OwnerName,
|
||||
message, timestamp, false, true);
|
||||
memberComp.Messages[group.GroupId].Add(memberMessage);
|
||||
|
||||
if (memberComp.Groups.TryGetValue(group.GroupId, out var memberGroup))
|
||||
{
|
||||
memberComp.Groups[group.GroupId] = new ChatGroup(
|
||||
memberGroup.GroupId,
|
||||
memberGroup.GroupName,
|
||||
true,
|
||||
group.Members.Count
|
||||
);
|
||||
}
|
||||
|
||||
if (TryComp<CartridgeComponent>(memberEntity, out var cartridge)
|
||||
&& cartridge.LoaderUid.HasValue && !memberComp.MutedSound)
|
||||
_audio.PlayPvs(memberComp.Sound, memberEntity);
|
||||
|
||||
UpdateUiState((memberEntity, memberComp));
|
||||
}
|
||||
|
||||
_admin.Add(LogType.Action, LogImpact.Low,
|
||||
$"Group message: '{originalMessage}' by {sender.Comp.ChatId} in group {group.GroupId}.");
|
||||
|
||||
UpdateUiState(sender);
|
||||
}
|
||||
|
||||
private void UpdateUiState(Entity<NanoChatCartridgeComponent> ent)
|
||||
{
|
||||
List<ChatMessage>? activeMessages = null;
|
||||
if (ent.Comp.ActiveChat != null && ent.Comp.Messages.TryGetValue(ent.Comp.ActiveChat, out var messages))
|
||||
if (ent.Comp.ActiveChat != null)
|
||||
{
|
||||
activeMessages = messages;
|
||||
if (ent.Comp.ActiveChat.StartsWith("G") && _groups.TryGetValue(ent.Comp.ActiveChat, out var group))
|
||||
{
|
||||
activeMessages = group.Messages;
|
||||
}
|
||||
else if (ent.Comp.Messages.TryGetValue(ent.Comp.ActiveChat, out var messages))
|
||||
{
|
||||
activeMessages = messages;
|
||||
}
|
||||
}
|
||||
|
||||
if (!TryComp<CartridgeComponent>(ent, out var cartridge) || !cartridge.LoaderUid.HasValue)
|
||||
return;
|
||||
|
||||
var state = new NanoChatUiState(ent.Comp.ChatId, ent.Comp.ActiveChat, ent.Comp.MutedSound, ent.Comp.Contacts, activeMessages);
|
||||
var state = new NanoChatUiState(ent.Comp.ChatId, ent.Comp.ActiveChat, ent.Comp.MutedSound, ent.Comp.Contacts, ent.Comp.Groups, activeMessages);
|
||||
_cartridgeLoader.UpdateCartridgeUiState(cartridge.LoaderUid.Value, state);
|
||||
}
|
||||
|
||||
private void UpdateAllGroupMembersUi(string groupId)
|
||||
{
|
||||
if (_groups.TryGetValue(groupId, out var group))
|
||||
{
|
||||
foreach (var memberId in group.Members)
|
||||
{
|
||||
if (_activeChats.TryGetValue(memberId, out var memberEntity) &&
|
||||
TryComp<NanoChatCartridgeComponent>(memberEntity, out var memberComp))
|
||||
{
|
||||
|
||||
memberComp.Groups[groupId] = new ChatGroup(
|
||||
groupId,
|
||||
group.GroupName,
|
||||
memberComp.Groups.TryGetValue(groupId, out var currentGroup) ? currentGroup.HasUnread : false,
|
||||
group.Members.Count
|
||||
);
|
||||
|
||||
UpdateUiState((memberEntity, memberComp));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsWithinRange(EntityUid sender, EntityUid recipient)
|
||||
{
|
||||
if (!Transform(sender).Coordinates.IsValid(EntityManager) ||
|
||||
@@ -300,6 +482,27 @@ public sealed class NanoChatCartridgeSystem : SharedNanoChatCartridgeSystem
|
||||
return senderCoords.InRange(recipientCoords, MessageRange);
|
||||
}
|
||||
|
||||
private void NotifyGroupMembers(string groupId, string message)
|
||||
{
|
||||
if (_groups.TryGetValue(groupId, out var group))
|
||||
{
|
||||
var timestamp = _timing.CurTime;
|
||||
var groupMessage = new ChatMessage("system", "System", message, timestamp, false, true);
|
||||
group.Messages.Add(groupMessage);
|
||||
|
||||
foreach (var memberId in group.Members)
|
||||
{
|
||||
if (_activeChats.TryGetValue(memberId, out var memberEntity) &&
|
||||
TryComp<NanoChatCartridgeComponent>(memberEntity, out var memberComp) &&
|
||||
TryComp<CartridgeComponent>(memberEntity, out var cartridge) &&
|
||||
cartridge.LoaderUid.HasValue && !memberComp.MutedSound)
|
||||
{
|
||||
_audio.PlayPvs(memberComp.Sound, memberEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddUndeliveredMessage(Entity<NanoChatCartridgeComponent> sender, string recipientId, string message)
|
||||
{
|
||||
var timestamp = _timing.CurTime;
|
||||
@@ -375,6 +578,19 @@ public sealed class NanoChatCartridgeSystem : SharedNanoChatCartridgeSystem
|
||||
contact.HasUnread
|
||||
);
|
||||
}
|
||||
|
||||
foreach (var groupKey in comp.Groups.Keys.ToList())
|
||||
{
|
||||
var group = comp.Groups[groupKey];
|
||||
var distortedName = DistortMessage(group.GroupName);
|
||||
|
||||
comp.Groups[groupKey] = new ChatGroup(
|
||||
group.GroupId,
|
||||
distortedName,
|
||||
group.HasUnread,
|
||||
group.MemberCount
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private string DistortMessage(string originalMessage)
|
||||
@@ -400,4 +616,12 @@ public sealed class NanoChatCartridgeSystem : SharedNanoChatCartridgeSystem
|
||||
|
||||
return new string(result);
|
||||
}
|
||||
|
||||
private sealed class ChatGroupData
|
||||
{
|
||||
public string GroupId = string.Empty;
|
||||
public string GroupName = string.Empty;
|
||||
public HashSet<string> Members = new();
|
||||
public List<ChatMessage> Messages = new();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using Robust.Shared.Audio;
|
||||
namespace Content.Shared.CartridgeLoader.Cartridges;
|
||||
|
||||
[RegisterComponent, AutoGenerateComponentPause]
|
||||
// [Access(typeof(SharedNanoChatCartridgeSystem))]
|
||||
public sealed partial class NanoChatCartridgeComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
@@ -26,6 +27,9 @@ public sealed partial class NanoChatCartridgeComponent : Component
|
||||
[DataField]
|
||||
public Dictionary<string, List<ChatMessage>> Messages = new();
|
||||
|
||||
[DataField]
|
||||
public Dictionary<string, ChatGroup> Groups = new();
|
||||
|
||||
[DataField, AutoPausedField]
|
||||
public TimeSpan NextMessageAllowedAfter = TimeSpan.Zero;
|
||||
|
||||
|
||||
@@ -57,6 +57,39 @@ public sealed class NanoChatSetActiveChat : INanoChatUiMessagePayload
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class NanoChatCreateGroup : INanoChatUiMessagePayload
|
||||
{
|
||||
public string GroupName { get; }
|
||||
|
||||
public NanoChatCreateGroup(string groupName)
|
||||
{
|
||||
GroupName = groupName;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class NanoChatJoinGroup : INanoChatUiMessagePayload
|
||||
{
|
||||
public string GroupId { get; }
|
||||
|
||||
public NanoChatJoinGroup(string groupId)
|
||||
{
|
||||
GroupId = groupId;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class NanoChatLeaveGroup : INanoChatUiMessagePayload
|
||||
{
|
||||
public string GroupId { get; }
|
||||
|
||||
public NanoChatLeaveGroup(string groupId)
|
||||
{
|
||||
GroupId = groupId;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class NanoChatUiMessageEvent : CartridgeMessageEvent
|
||||
{
|
||||
|
||||
@@ -9,14 +9,21 @@ public sealed class NanoChatUiState : BoundUserInterfaceState
|
||||
public string? ActiveChat;
|
||||
public bool Muted;
|
||||
public Dictionary<string, ChatContact> Contacts;
|
||||
public Dictionary<string, ChatGroup> Groups;
|
||||
public List<ChatMessage>? ActiveChatMessages;
|
||||
|
||||
public NanoChatUiState(string chatId, string? activeChat, bool muted, Dictionary<string, ChatContact> contacts, List<ChatMessage>? activeChatMessages)
|
||||
public NanoChatUiState(
|
||||
string chatId, string? activeChat, bool muted,
|
||||
Dictionary<string, ChatContact> contacts,
|
||||
Dictionary<string, ChatGroup> groups,
|
||||
List<ChatMessage>? activeChatMessages
|
||||
)
|
||||
{
|
||||
ChatId = chatId;
|
||||
ActiveChat = activeChat;
|
||||
Muted = muted;
|
||||
Contacts = contacts;
|
||||
Groups = groups;
|
||||
ActiveChatMessages = activeChatMessages;
|
||||
}
|
||||
}
|
||||
@@ -36,6 +43,23 @@ public sealed class ChatContact
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class ChatGroup
|
||||
{
|
||||
public string GroupId { get; }
|
||||
public string GroupName { get; }
|
||||
public bool HasUnread { get; }
|
||||
public int MemberCount { get; }
|
||||
|
||||
public ChatGroup(string groupId, string groupName, bool hasUnread, int memberCount)
|
||||
{
|
||||
GroupId = groupId;
|
||||
GroupName = groupName;
|
||||
HasUnread = hasUnread;
|
||||
MemberCount = memberCount;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class ChatMessage
|
||||
{
|
||||
|
||||
@@ -18,4 +18,10 @@ forensic-scanner-chat-with-contact = Чат с: { $name } ({ $id })
|
||||
forensic-scanner-chat-with-unknown = Чат с: Неизвестный ({ $id })
|
||||
forensic-scanner-chat-contact-no-messages = Контакт: { $name } ({ $id }) - нет сообщений
|
||||
|
||||
forensic-scanner-chat-groups-header = ГРУППОВЫЕ ЧАТЫ
|
||||
forensic-scanner-chat-group-info = Группа: { $name } (ID: { $id }, Участников: { $members })
|
||||
forensic-scanner-chat-group-messages = Сообщения группы:
|
||||
forensic-scanner-chat-group-no-messages = Нет сообщений в группе
|
||||
forensic-scanner-chat-archived-group = Архивная группа: { $id }
|
||||
|
||||
forensic-scanner-chat-you = Вы
|
||||
|
||||
@@ -1,22 +1,60 @@
|
||||
# Ui
|
||||
nano-chat-ui-add = Добавить
|
||||
nano-chat-ui-add-contact-title = Добавить контакт
|
||||
nano-chat-ui-add-contact-tooltip = Добавить контакт
|
||||
|
||||
nano-chat-ui-cancel = Отмена
|
||||
|
||||
nano-chat-ui-close-chat-tooltip = Закрыть чат
|
||||
|
||||
nano-chat-ui-contact-id = ID контакта
|
||||
nano-chat-ui-contact-id-placeholder = Введите ID контакта(5 символов)...
|
||||
nano-chat-ui-contact-name = Имя контакта
|
||||
nano-chat-ui-contact-name-placeholder = Введите имя контакта(9 символов)...
|
||||
nano-chat-ui-cancel = Отмена
|
||||
nano-chat-ui-add = Добавить
|
||||
nano-chat-ui-emoji-title = Выбор эмодзи
|
||||
|
||||
nano-chat-ui-create = Создать
|
||||
nano-chat-ui-create-group-title = Создать группу
|
||||
nano-chat-ui-create-group-tooltip = Создать новую группу
|
||||
|
||||
nano-chat-ui-emoji-select = Выберите эмодзи:
|
||||
nano-chat-ui-emoji-title = Выбор эмодзи
|
||||
nano-chat-ui-emoji-tooltip = Эмодзи
|
||||
nano-chat-ui-unread-indicator = (У)
|
||||
nano-chat-ui-title = NanoChat
|
||||
nano-chat-ui-mute-tooltip = Отключить звук
|
||||
nano-chat-ui-unmute-tooltip = Включить звук
|
||||
nano-chat-ui-add-contact-tooltip = Добавить контакт
|
||||
nano-chat-ui-no-contacts = Нет контактов
|
||||
|
||||
nano-chat-ui-erase-chat-tooltip = Стереть контакт
|
||||
nano-chat-ui-close-chat-tooltip = Закрыть чат
|
||||
|
||||
nano-chat-ui-group-id = ID группы
|
||||
nano-chat-ui-group-id-placeholder = Введите ID группы(5 символов)...
|
||||
nano-chat-ui-group-name = Название группы
|
||||
nano-chat-ui-group-name-placeholder = Введите название группы(16 символов)...
|
||||
|
||||
nano-chat-ui-join = Присоединиться
|
||||
nano-chat-ui-join-group-title = Присоединиться к группе
|
||||
nano-chat-ui-join-group-tooltip = Присоединиться к группе
|
||||
|
||||
nano-chat-ui-leave-chat-tooltip = Покинуть группу
|
||||
|
||||
nano-chat-ui-message-placeholder = Введите сообщение...
|
||||
|
||||
nano-chat-ui-mute-tooltip = Отключить звук
|
||||
|
||||
nano-chat-ui-no-contacts = Нет контактов
|
||||
nano-chat-ui-no-groups = Нет групп
|
||||
|
||||
nano-chat-ui-select-chat = Выберите чат
|
||||
nano-chat-ui-select-chat-input = Выберите чат для сообщения
|
||||
nano-chat-ui-message-placeholder = Введите сообщение...
|
||||
|
||||
nano-chat-ui-send-tooltip = Отправить
|
||||
|
||||
nano-chat-ui-tab-contacts = Конт.
|
||||
nano-chat-ui-tab-groups = Групп.
|
||||
|
||||
nano-chat-ui-title = NanoChat
|
||||
|
||||
nano-chat-ui-unmute-tooltip = Включить звук
|
||||
|
||||
nano-chat-ui-unread-indicator = (У)
|
||||
|
||||
# System
|
||||
nano-chat-create-group-message = {$name} создал группу '{$groupName}'.
|
||||
nano-chat-join-message = -> {$name} присоединился!
|
||||
nano-chat-leave-message = <- {$name} покинул группу.
|
||||
Reference in New Issue
Block a user