News UI overhaul and PDA notifications (#19610)
This commit is contained in:
@@ -1,50 +0,0 @@
|
||||
using Content.Client.UserInterface.Fragments;
|
||||
using Content.Shared.CartridgeLoader.Cartridges;
|
||||
using Content.Shared.CartridgeLoader;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.CartridgeLoader.Cartridges;
|
||||
|
||||
public sealed partial class NewsReadUi : UIFragment
|
||||
{
|
||||
private NewsReadUiFragment? _fragment;
|
||||
|
||||
public override Control GetUIFragmentRoot()
|
||||
{
|
||||
return _fragment!;
|
||||
}
|
||||
|
||||
public override void Setup(BoundUserInterface userInterface, EntityUid? fragmentOwner)
|
||||
{
|
||||
_fragment = new NewsReadUiFragment();
|
||||
|
||||
_fragment.OnNextButtonPressed += () =>
|
||||
{
|
||||
SendNewsReadMessage(NewsReadUiAction.Next, userInterface);
|
||||
};
|
||||
_fragment.OnPrevButtonPressed += () =>
|
||||
{
|
||||
SendNewsReadMessage(NewsReadUiAction.Prev, userInterface);
|
||||
};
|
||||
_fragment.OnNotificationSwithPressed += () =>
|
||||
{
|
||||
SendNewsReadMessage(NewsReadUiAction.NotificationSwith, userInterface);
|
||||
};
|
||||
}
|
||||
|
||||
public override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
if (state is NewsReadBoundUserInterfaceState cast)
|
||||
_fragment?.UpdateState(cast.Article, cast.TargetNum, cast.TotalNum, cast.NotificationOn);
|
||||
else if (state is NewsReadEmptyBoundUserInterfaceState empty)
|
||||
_fragment?.UpdateEmptyState(empty.NotificationOn);
|
||||
}
|
||||
|
||||
private void SendNewsReadMessage(NewsReadUiAction action, BoundUserInterface userInterface)
|
||||
{
|
||||
var newsMessage = new NewsReadUiMessageEvent(action);
|
||||
var message = new CartridgeUiMessage(newsMessage);
|
||||
userInterface.SendMessage(message);
|
||||
}
|
||||
}
|
||||
54
Content.Client/CartridgeLoader/Cartridges/NewsReaderUi.cs
Normal file
54
Content.Client/CartridgeLoader/Cartridges/NewsReaderUi.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using Content.Client.UserInterface.Fragments;
|
||||
using Content.Shared.CartridgeLoader.Cartridges;
|
||||
using Content.Shared.CartridgeLoader;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.CartridgeLoader.Cartridges;
|
||||
|
||||
public sealed partial class NewsReaderUi : UIFragment
|
||||
{
|
||||
private NewsReaderUiFragment? _fragment;
|
||||
|
||||
public override Control GetUIFragmentRoot()
|
||||
{
|
||||
return _fragment!;
|
||||
}
|
||||
|
||||
public override void Setup(BoundUserInterface userInterface, EntityUid? fragmentOwner)
|
||||
{
|
||||
_fragment = new NewsReaderUiFragment();
|
||||
|
||||
_fragment.OnNextButtonPressed += () =>
|
||||
{
|
||||
SendNewsReaderMessage(NewsReaderUiAction.Next, userInterface);
|
||||
};
|
||||
_fragment.OnPrevButtonPressed += () =>
|
||||
{
|
||||
SendNewsReaderMessage(NewsReaderUiAction.Prev, userInterface);
|
||||
};
|
||||
_fragment.OnNotificationSwithPressed += () =>
|
||||
{
|
||||
SendNewsReaderMessage(NewsReaderUiAction.NotificationSwitch, userInterface);
|
||||
};
|
||||
}
|
||||
|
||||
public override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case NewsReaderBoundUserInterfaceState cast:
|
||||
_fragment?.UpdateState(cast.Article, cast.TargetNum, cast.TotalNum, cast.NotificationOn);
|
||||
break;
|
||||
case NewsReaderEmptyBoundUserInterfaceState empty:
|
||||
_fragment?.UpdateEmptyState(empty.NotificationOn);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void SendNewsReaderMessage(NewsReaderUiAction action, BoundUserInterface userInterface)
|
||||
{
|
||||
var newsMessage = new NewsReaderUiMessageEvent(action);
|
||||
var message = new CartridgeUiMessage(newsMessage);
|
||||
userInterface.SendMessage(message);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,30 @@
|
||||
<cartridges:NewsReadUiFragment xmlns:cartridges="clr-namespace:Content.Client.CartridgeLoader.Cartridges"
|
||||
<cartridges:NewsReaderUiFragment xmlns:cartridges="clr-namespace:Content.Client.CartridgeLoader.Cartridges"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns="https://spacestation14.io" Margin="1 0 2 0">
|
||||
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
xmlns="https://spacestation14.io"
|
||||
Margin="1 0 2 0"
|
||||
Orientation="Vertical"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True">
|
||||
<PanelContainer StyleClasses="BackgroundDark"></PanelContainer>
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="5,5,5,5">
|
||||
<Button
|
||||
Name="Prev"
|
||||
MinWidth="64"
|
||||
Disabled="True"
|
||||
HorizontalAlignment="Left"
|
||||
Text="{Loc 'news-read-ui-past-text'}"
|
||||
Text="{Loc 'news-read-ui-prev-text'}"
|
||||
ToolTip="{Loc 'news-read-ui-prev-tooltip'}"
|
||||
Access="Public"
|
||||
HorizontalExpand="True"/>
|
||||
<Button
|
||||
Name="Next"
|
||||
MinWidth="64"
|
||||
Disabled="True"
|
||||
HorizontalAlignment="Right"
|
||||
Text="{Loc 'news-read-ui-next-text'}" />
|
||||
Text="{Loc 'news-read-ui-next-text'}"
|
||||
ToolTip="{Loc 'news-read-ui-next-tooltip'}"/>
|
||||
</BoxContainer>
|
||||
<controls:StripeBack Name="АrticleNameContainer">
|
||||
<PanelContainer>
|
||||
@@ -46,9 +55,11 @@
|
||||
</PanelContainer>
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="5,5,5,5">
|
||||
<Button
|
||||
Name="NotificationSwith"
|
||||
Name="NotificationSwitch"
|
||||
ToolTip="{Loc news-reader-ui-mute-tooltip}"
|
||||
MinWidth="20"/>
|
||||
<RichTextLabel Margin="5,2,2,2" Name="ShareTime" VerticalAlignment="Top"/>
|
||||
<customControls:VSeparator Margin="2 0"/>
|
||||
<RichTextLabel Margin="5,2,2,2" Name="Author" VerticalAlignment="Top" HorizontalAlignment="Right"/>
|
||||
</BoxContainer>
|
||||
</cartridges:NewsReadUiFragment>
|
||||
</cartridges:NewsReaderUiFragment>
|
||||
@@ -7,23 +7,20 @@ using Robust.Client.UserInterface.XAML;
|
||||
namespace Content.Client.CartridgeLoader.Cartridges;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class NewsReadUiFragment : BoxContainer
|
||||
public sealed partial class NewsReaderUiFragment : BoxContainer
|
||||
{
|
||||
public event Action? OnNextButtonPressed;
|
||||
public event Action? OnPrevButtonPressed;
|
||||
|
||||
public event Action? OnNotificationSwithPressed;
|
||||
|
||||
public NewsReadUiFragment()
|
||||
public NewsReaderUiFragment()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
Orientation = LayoutOrientation.Vertical;
|
||||
HorizontalExpand = true;
|
||||
VerticalExpand = true;
|
||||
|
||||
Next.OnPressed += _ => OnNextButtonPressed?.Invoke();
|
||||
Prev.OnPressed += _ => OnPrevButtonPressed?.Invoke();
|
||||
NotificationSwith.OnPressed += _ => OnNotificationSwithPressed?.Invoke();
|
||||
NotificationSwitch.OnPressed += _ => OnNotificationSwithPressed?.Invoke();
|
||||
}
|
||||
|
||||
public void UpdateState(NewsArticle article, int targetNum, int totalNum, bool notificationOn)
|
||||
@@ -33,17 +30,20 @@ public sealed partial class NewsReadUiFragment : BoxContainer
|
||||
ShareTime.Visible = true;
|
||||
Author.Visible = true;
|
||||
|
||||
PageName.Text = article.Name;
|
||||
PageName.Text = article.Title;
|
||||
PageText.SetMarkup(article.Content);
|
||||
|
||||
PageNum.Text = $"{targetNum}/{totalNum}";
|
||||
|
||||
NotificationSwith.Text = Loc.GetString(notificationOn ? "news-read-ui-notification-on" : "news-read-ui-notification-off");
|
||||
NotificationSwitch.Text = Loc.GetString(notificationOn ? "news-read-ui-notification-on" : "news-read-ui-notification-off");
|
||||
|
||||
string shareTime = article.ShareTime.ToString("hh\\:mm\\:ss");
|
||||
string shareTime = article.ShareTime.ToString(@"hh\:mm\:ss");
|
||||
ShareTime.SetMarkup(Loc.GetString("news-read-ui-time-prefix-text") + " " + shareTime);
|
||||
|
||||
Author.SetMarkup(Loc.GetString("news-read-ui-author-prefix") + " " + (article.Author != null ? article.Author : Loc.GetString("news-read-ui-no-author")));
|
||||
|
||||
Prev.Disabled = targetNum <= 1;
|
||||
Next.Disabled = targetNum >= totalNum;
|
||||
}
|
||||
|
||||
public void UpdateEmptyState(bool notificationOn)
|
||||
@@ -55,6 +55,6 @@ public sealed partial class NewsReadUiFragment : BoxContainer
|
||||
|
||||
PageName.Text = Loc.GetString("news-read-ui-not-found-text");
|
||||
|
||||
NotificationSwith.Text = Loc.GetString(notificationOn ? "news-read-ui-notification-on" : "news-read-ui-notification-off");
|
||||
NotificationSwitch.Text = Loc.GetString(notificationOn ? "news-read-ui-notification-on" : "news-read-ui-notification-off");
|
||||
}
|
||||
}
|
||||
71
Content.Client/MassMedia/Ui/ArticleEditorPanel.xaml
Normal file
71
Content.Client/MassMedia/Ui/ArticleEditorPanel.xaml
Normal file
@@ -0,0 +1,71 @@
|
||||
<Control xmlns="https://spacestation14.io"
|
||||
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
MouseFilter="Stop">
|
||||
<PanelContainer StyleClasses="BackgroundOpenLeft"/>
|
||||
<PanelContainer>
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxFlat BorderColor="#25252A" BorderThickness="0 0 0 3"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
</PanelContainer>
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
|
||||
<Control Margin="0 0 1 0">
|
||||
<PanelContainer StyleClasses="WindowHeadingBackground" />
|
||||
<BoxContainer Margin="4 2 8 0" Orientation="Horizontal">
|
||||
<Label Name="Title" Text="{Loc news-write-ui-new-article}"
|
||||
HorizontalExpand="True" VAlign="Center" StyleClasses="FancyWindowTitle" />
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
<PanelContainer StyleClasses="LowDivider" Margin="0 0 1 0"/>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="Title:" Margin="17 10 0 9" VerticalAlignment="Center"/>
|
||||
<LineEdit Name="TitleField" Margin="6 10 0 9" MinWidth="260" MinHeight="23" Access="Public"/>
|
||||
<Control HorizontalExpand="True" />
|
||||
<Label Name="RichTextInfoLabel" Text="?" MouseFilter="Pass" Margin="14 0" StyleClasses="LabelSecondaryColor"/>
|
||||
</BoxContainer>
|
||||
<Control Name="TextEditPanel" VerticalExpand="True" Margin="11 0 11 0">
|
||||
<PanelContainer>
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxFlat BackgroundColor="#202023" BorderThickness="1" BorderColor="#3B3E56"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
</PanelContainer>
|
||||
<TextEdit Name="ContentField" Margin="0 1" Access="Public"/>
|
||||
</Control>
|
||||
<Control Name="PreviewPanel" Visible="False" VerticalExpand="True" Margin="11 0 11 0">
|
||||
<PanelContainer>
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxFlat BorderThickness="1" BorderColor="#3B3E56"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
</PanelContainer>
|
||||
<ScrollContainer HScrollEnabled="True">
|
||||
<RichTextLabel Name="PreviewLabel" VerticalAlignment="Top" Margin="9 3" MaxWidth="360"/>
|
||||
</ScrollContainer>
|
||||
</Control>
|
||||
<BoxContainer Orientation="Horizontal" Margin="12 5 12 8">
|
||||
<Control>
|
||||
<Button Name="ButtonCancel" SetHeight="32" SetWidth="85"
|
||||
StyleClasses="ButtonColorRed" Text="{Loc news-write-ui-cancel-text}"/>
|
||||
</Control>
|
||||
<Control HorizontalExpand="True"/>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Button Name="ButtonPreview" SetHeight="32" SetWidth="85"
|
||||
StyleClasses="OpenRight" Text="{Loc news-write-ui-preview-text}"/>
|
||||
<Button Name="ButtonPublish" SetHeight="32" SetWidth="85" Text="{Loc news-write-ui-publish-text}" Access="Public"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
<PanelContainer>
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxFlat BorderThickness="2 0 0 0" BorderColor="#1d1d22"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
</PanelContainer>
|
||||
<PanelContainer HorizontalAlignment="Left" VerticalAlignment="Top" SetHeight="27" SetWidth="2">
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxFlat BorderColor="#2a2a2d" BorderThickness="0 0 0 2"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
</PanelContainer>
|
||||
<PanelContainer HorizontalAlignment="Left" VerticalAlignment="Top" SetHeight="25" SetWidth="2">
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxFlat BackgroundColor="#1b1b1f"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
</PanelContainer>
|
||||
</Control>
|
||||
109
Content.Client/MassMedia/Ui/ArticleEditorPanel.xaml.cs
Normal file
109
Content.Client/MassMedia/Ui/ArticleEditorPanel.xaml.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
using Content.Client.Message;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.MassMedia.Systems;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.MassMedia.Ui;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class ArticleEditorPanel : Control
|
||||
{
|
||||
public event Action? PublishButtonPressed;
|
||||
|
||||
private bool _preview;
|
||||
|
||||
public ArticleEditorPanel()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
ButtonPublish.StyleClasses.Add(StyleBase.ButtonOpenLeft);
|
||||
ButtonPublish.StyleClasses.Add(StyleNano.StyleClassButtonColorGreen);
|
||||
|
||||
ContentField.GetChild(0).Margin = new Thickness(9, 3);
|
||||
// Customize scrollbar width and margin. This is not possible in xaml
|
||||
var scrollbar = ContentField.GetChild(1);
|
||||
scrollbar.SetWidth = 6f;
|
||||
scrollbar.Margin = new Thickness(9, 0, 2 , 0);
|
||||
|
||||
RichTextInfoLabel.TooltipSupplier = sender =>
|
||||
{
|
||||
var label = new RichTextLabel();
|
||||
label.SetMarkup(Loc.GetString("news-write-ui-richtext-tooltip"));
|
||||
|
||||
var tooltip = new Tooltip();
|
||||
tooltip.GetChild(0).Children.Clear();
|
||||
tooltip.GetChild(0).Children.Add(label);
|
||||
|
||||
return tooltip;
|
||||
};
|
||||
|
||||
ButtonPreview.OnPressed += OnPreview;
|
||||
ButtonCancel.OnPressed += OnCancel;
|
||||
ButtonPublish.OnPressed += OnPublish;
|
||||
|
||||
TitleField.OnTextChanged += args => OnTextChanged(args.Text.Length, args.Control, SharedNewsSystem.MaxTitleLength);
|
||||
ContentField.OnTextChanged += args => OnTextChanged(Rope.CalcTotalLength(args.TextRope), args.Control, SharedNewsSystem.MaxContentLength);
|
||||
}
|
||||
|
||||
private void OnTextChanged(long length, Control control, long maxLength)
|
||||
{
|
||||
if (length > maxLength)
|
||||
{
|
||||
control.ModulateSelfOverride = Color.Red;
|
||||
control.ToolTip = Loc.GetString("news-writer-text-length-exceeded");
|
||||
}
|
||||
else
|
||||
{
|
||||
control.ModulateSelfOverride = null;
|
||||
control.ToolTip = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPreview(BaseButton.ButtonEventArgs eventArgs)
|
||||
{
|
||||
_preview = !_preview;
|
||||
|
||||
TextEditPanel.Visible = !_preview;
|
||||
PreviewPanel.Visible = _preview;
|
||||
PreviewLabel.SetMarkup(Rope.Collapse(ContentField.TextRope));
|
||||
}
|
||||
|
||||
private void OnCancel(BaseButton.ButtonEventArgs eventArgs)
|
||||
{
|
||||
Reset();
|
||||
Visible = false;
|
||||
}
|
||||
|
||||
private void OnPublish(BaseButton.ButtonEventArgs eventArgs)
|
||||
{
|
||||
PublishButtonPressed?.Invoke();
|
||||
Reset();
|
||||
Visible = false;
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
_preview = false;
|
||||
TextEditPanel.Visible = true;
|
||||
PreviewPanel.Visible = false;
|
||||
PreviewLabel.SetMarkup("");
|
||||
TitleField.Text = "";
|
||||
ContentField.TextRope = Rope.Leaf.Empty;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
ButtonPreview.OnPressed -= OnPreview;
|
||||
ButtonCancel.OnPressed -= OnCancel;
|
||||
ButtonPublish.OnPressed -= OnPublish;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
<Control xmlns="https://spacestation14.io"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client">
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" Margin="0 0 0 12">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="True">
|
||||
<PanelContainer HorizontalExpand="True" VerticalExpand="True">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#4c6530"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
<Label Name="NameLabel" Margin="6 6 6 6" HorizontalAlignment="Center"/>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<PanelContainer HorizontalExpand="True" VerticalExpand="True">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#33333f"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
<RichTextLabel Name="Author" HorizontalExpand="True" VerticalAlignment="Bottom" Margin="6 6 6 6"/>
|
||||
<Button Name="Delete"
|
||||
Text="{Loc 'news-write-ui-delete-text'}"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="8 6 6 6"
|
||||
Access="Public"/>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
@@ -1,27 +0,0 @@
|
||||
using Content.Client.Message;
|
||||
using Content.Shared.Research.Prototypes;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.MassMedia.Ui;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class MiniArticleCardControl : Control
|
||||
{
|
||||
public Action? OnDeletePressed;
|
||||
public int ArticleNum;
|
||||
|
||||
public MiniArticleCardControl(string name, string author)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
NameLabel.Text = name;
|
||||
Author.SetMarkup(author);
|
||||
|
||||
Delete.OnPressed += _ => OnDeletePressed?.Invoke();
|
||||
}
|
||||
}
|
||||
29
Content.Client/MassMedia/Ui/NewsArticleCard.xaml
Normal file
29
Content.Client/MassMedia/Ui/NewsArticleCard.xaml
Normal file
@@ -0,0 +1,29 @@
|
||||
<Control xmlns="https://spacestation14.io"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
xmlns:system="clr-namespace:System;assembly=System.Runtime"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Margin="0 0 0 8">
|
||||
<PanelContainer StyleClasses="AngleRect" ModulateSelfOverride="#2b2b31"/>
|
||||
<BoxContainer Orientation="Vertical" SetHeight="60">
|
||||
<Control HorizontalExpand="True" SetHeight="27">
|
||||
<PanelContainer>
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BorderColor="#3B3E56" BorderThickness="0 0 0 1"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
</PanelContainer>
|
||||
<Label Name="TitleLabel" Margin="12 0 6 0" HorizontalAlignment="Left"/>
|
||||
</Control>
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<Label FontColorOverride="#b1b1b2" StyleClasses="LabelSmall" Name="AuthorLabel" Margin="14 6 6 6"/>
|
||||
<Control HorizontalExpand="True"/>
|
||||
<Label FontColorOverride="#b1b1b2" StyleClasses="LabelSmall" Name="PublishTimeLabel" Margin="6 6 6 6"/>
|
||||
<controls:ConfirmButton Name="DeleteButton" Text="{Loc news-write-ui-delete-text}"
|
||||
HorizontalAlignment="Right" Margin="8 6 6 6" SetHeight="19" SetWidth="52" Access="Public">
|
||||
<Button.StyleClasses>
|
||||
<system:String>ButtonSmall</system:String>
|
||||
<system:String>ButtonColorRed</system:String>
|
||||
</Button.StyleClasses>
|
||||
</controls:ConfirmButton>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
47
Content.Client/MassMedia/Ui/NewsArticleCard.xaml.cs
Normal file
47
Content.Client/MassMedia/Ui/NewsArticleCard.xaml.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.MassMedia.Ui;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class NewsArticleCard : Control
|
||||
{
|
||||
private string? _authorMarkup;
|
||||
private TimeSpan? _publicationTime;
|
||||
|
||||
public Action? OnDeletePressed;
|
||||
public int ArtcileNumber;
|
||||
|
||||
public string? Title
|
||||
{
|
||||
get => TitleLabel.Text;
|
||||
set => TitleLabel.Text = value?.Length <= 30 ? value : $"{value?[..30]}...";
|
||||
}
|
||||
|
||||
public string? Author
|
||||
{
|
||||
get => _authorMarkup;
|
||||
set
|
||||
{
|
||||
_authorMarkup = value;
|
||||
AuthorLabel.Text = _authorMarkup ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
public TimeSpan? PublicationTime
|
||||
{
|
||||
get => _publicationTime;
|
||||
set
|
||||
{
|
||||
_publicationTime = value;
|
||||
PublishTimeLabel.Text = value?.ToString(@"hh\:mm\:ss") ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
public NewsArticleCard()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
DeleteButton.OnPressed += _ => OnDeletePressed?.Invoke();
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
using JetBrains.Annotations;
|
||||
using Content.Shared.MassMedia.Components;
|
||||
using Content.Shared.MassMedia.Systems;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.MassMedia.Ui
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class NewsWriteBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[ViewVariables]
|
||||
private NewsWriteMenu? _menu;
|
||||
|
||||
[ViewVariables]
|
||||
private string _windowName = Loc.GetString("news-read-ui-default-title");
|
||||
|
||||
public NewsWriteBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
_menu = new NewsWriteMenu(_windowName);
|
||||
|
||||
_menu.OpenCentered();
|
||||
_menu.OnClose += Close;
|
||||
|
||||
_menu.ShareButtonPressed += OnShareButtonPressed;
|
||||
_menu.DeleteButtonPressed += OnDeleteButtonPressed;
|
||||
|
||||
SendMessage(new NewsWriteArticlesRequestMessage());
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
_menu?.Close();
|
||||
_menu?.Dispose();
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
if (_menu == null || state is not NewsWriteBoundUserInterfaceState cast)
|
||||
return;
|
||||
|
||||
_menu.UpdateUI(cast.Articles, cast.ShareAvalible);
|
||||
}
|
||||
|
||||
private void OnShareButtonPressed()
|
||||
{
|
||||
if (_menu == null || _menu.NameInput.Text.Length == 0)
|
||||
return;
|
||||
|
||||
var stringContent = Rope.Collapse(_menu.ContentInput.TextRope);
|
||||
|
||||
if (stringContent.Length == 0)
|
||||
return;
|
||||
|
||||
var stringName = _menu.NameInput.Text.Trim();
|
||||
var name = stringName[..Math.Min(stringName.Length, (SharedNewsSystem.MaxNameLength))];
|
||||
var content = stringContent[..Math.Min(stringContent.Length, (SharedNewsSystem.MaxArticleLength))];
|
||||
_menu.ContentInput.TextRope = new Rope.Leaf(string.Empty);
|
||||
_menu.NameInput.Text = string.Empty;
|
||||
SendMessage(new NewsWriteShareMessage(name, content));
|
||||
}
|
||||
|
||||
private void OnDeleteButtonPressed(int articleNum)
|
||||
{
|
||||
if (_menu == null)
|
||||
return;
|
||||
|
||||
SendMessage(new NewsWriteDeleteMessage(articleNum));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
<DefaultWindow
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
Title="{Loc 'news-write-ui-default-title'}"
|
||||
MinSize="680 512"
|
||||
SetSize="680 512">
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True">
|
||||
<BoxContainer Orientation="Vertical"
|
||||
VerticalExpand="True"
|
||||
SizeFlagsStretchRatio="2"
|
||||
Margin="10 0 10 10"
|
||||
MinWidth="350">
|
||||
<Label Text="{Loc 'news-write-ui-articles-label'}" HorizontalAlignment="Center"/>
|
||||
<customControls:HSeparator StyleClasses="LowDivider" Margin="0 0 0 10"/>
|
||||
<PanelContainer VerticalExpand="True">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#1B1B1E" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<ScrollContainer
|
||||
HScrollEnabled="False"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True">
|
||||
<BoxContainer
|
||||
Name="ArticleCardsContainer"
|
||||
Orientation="Vertical"
|
||||
VerticalExpand="True">
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Vertical"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True"
|
||||
Margin="15 0 0 0">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<Label Text="{Loc 'news-write-ui-article-name-label'}"/>
|
||||
<LineEdit Name="NameInput"
|
||||
MinSize="60 0"
|
||||
VerticalAlignment="Top"
|
||||
Margin="4 0 0 0"
|
||||
Access="Public"
|
||||
HorizontalExpand="True"/>
|
||||
</BoxContainer>
|
||||
<customControls:HSeparator StyleClasses="LowDivider" Margin="0 5 0 5"/>
|
||||
<Label Text="{Loc 'news-write-ui-article-content-label'}" Margin="0 0 0 5"/>
|
||||
<PanelContainer Name="InputContainer"
|
||||
VerticalAlignment="Stretch"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#333237"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
<TextEdit Name="ContentInput" Access="Public" />
|
||||
</PanelContainer>
|
||||
<Button Name="Share"
|
||||
MinWidth="30"
|
||||
HorizontalAlignment="Left"
|
||||
Text="{Loc 'news-write-ui-share-text'}"
|
||||
Access="Public"
|
||||
Margin="0 4 4 4">
|
||||
</Button>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
@@ -1,42 +0,0 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Shared.MassMedia.Systems;
|
||||
|
||||
namespace Content.Client.MassMedia.Ui;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class NewsWriteMenu : DefaultWindow
|
||||
{
|
||||
public event Action? ShareButtonPressed;
|
||||
public event Action<int>? DeleteButtonPressed;
|
||||
|
||||
public NewsWriteMenu(string name)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
if (Window != null)
|
||||
Window.Title = name;
|
||||
|
||||
Share.OnPressed += _ => ShareButtonPressed?.Invoke();
|
||||
}
|
||||
|
||||
public void UpdateUI(NewsArticle[] articles, bool shareAvalible)
|
||||
{
|
||||
ArticleCardsContainer.Children.Clear();
|
||||
|
||||
for (int i = 0; i < articles.Length; i++)
|
||||
{
|
||||
var article = articles[i];
|
||||
var mini = new MiniArticleCardControl(article.Name, (article.Author != null ? article.Author : Loc.GetString("news-read-ui-no-author")));
|
||||
mini.ArticleNum = i;
|
||||
mini.OnDeletePressed += () => DeleteButtonPressed?.Invoke(mini.ArticleNum);
|
||||
|
||||
ArticleCardsContainer.AddChild(mini);
|
||||
}
|
||||
|
||||
Share.Disabled = !shareAvalible;
|
||||
}
|
||||
}
|
||||
84
Content.Client/MassMedia/Ui/NewsWriterBoundUserInterface.cs
Normal file
84
Content.Client/MassMedia/Ui/NewsWriterBoundUserInterface.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using JetBrains.Annotations;
|
||||
using Content.Shared.MassMedia.Systems;
|
||||
using Content.Shared.MassMedia.Components;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.MassMedia.Ui;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class NewsWriterBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
[ViewVariables]
|
||||
private NewsWriterMenu? _menu;
|
||||
|
||||
public NewsWriterBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
_menu = new NewsWriterMenu(_gameTiming);
|
||||
|
||||
_menu.OpenCentered();
|
||||
_menu.OnClose += Close;
|
||||
|
||||
_menu.ArticleEditorPanel.PublishButtonPressed += OnPublishButtonPressed;
|
||||
_menu.DeleteButtonPressed += OnDeleteButtonPressed;
|
||||
|
||||
SendMessage(new NewsWriterArticlesRequestMessage());
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
_menu?.Close();
|
||||
_menu?.Dispose();
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
if (state is not NewsWriterBoundUserInterfaceState cast)
|
||||
return;
|
||||
|
||||
_menu?.UpdateUI(cast.Articles, cast.PublishEnabled, cast.NextPublish);
|
||||
}
|
||||
|
||||
private void OnPublishButtonPressed()
|
||||
{
|
||||
var title = _menu?.ArticleEditorPanel.TitleField.Text.Trim() ?? "";
|
||||
if (_menu == null || title.Length == 0)
|
||||
return;
|
||||
|
||||
var stringContent = Rope.Collapse(_menu.ArticleEditorPanel.ContentField.TextRope).Trim();
|
||||
|
||||
if (stringContent.Length == 0)
|
||||
return;
|
||||
|
||||
var name = title.Length <= SharedNewsSystem.MaxTitleLength
|
||||
? title
|
||||
: $"{title[..(SharedNewsSystem.MaxTitleLength - 3)]}...";
|
||||
|
||||
var content = stringContent.Length <= SharedNewsSystem.MaxContentLength
|
||||
? stringContent
|
||||
: $"{stringContent[..(SharedNewsSystem.MaxContentLength - 3)]}...";
|
||||
|
||||
|
||||
SendMessage(new NewsWriterPublishMessage(name, content));
|
||||
}
|
||||
|
||||
private void OnDeleteButtonPressed(int articleNum)
|
||||
{
|
||||
if (_menu == null)
|
||||
return;
|
||||
|
||||
SendMessage(new NewsWriterDeleteMessage(articleNum));
|
||||
}
|
||||
}
|
||||
45
Content.Client/MassMedia/Ui/NewsWriterMenu.xaml
Normal file
45
Content.Client/MassMedia/Ui/NewsWriterMenu.xaml
Normal file
@@ -0,0 +1,45 @@
|
||||
<controls:FancyWindow
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:ui="clr-namespace:Content.Client.MassMedia.Ui"
|
||||
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
Title="{Loc 'news-write-ui-default-title'}"
|
||||
MinSize="348 443"
|
||||
SetSize="348 443">
|
||||
|
||||
<ui:ArticleEditorPanel Name="ArticleEditorPanel" HorizontalAlignment="Left" VerticalExpand="True"
|
||||
MinWidth="410" MinHeight="370" Margin="0 0 0 30" Access="Public" Visible="False"/>
|
||||
|
||||
<BoxContainer Orientation="Vertical" VerticalExpand="True">
|
||||
<Control VerticalExpand="True" HorizontalExpand="True" Margin="10 10 10 0">
|
||||
<PanelContainer Name="MainPanel" HorizontalExpand="False" VerticalExpand="True">
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxFlat BackgroundColor="#202023" />
|
||||
</PanelContainer.PanelOverride>
|
||||
</PanelContainer>
|
||||
<ScrollContainer Name="ArticleListScrollbar" HorizontalExpand="True" VerticalExpand="True" HScrollEnabled="True">
|
||||
<BoxContainer Name="ArticlesContainer" Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True" Margin="6 6 6 6">
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
</Control>
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="12 7 12 9">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Name="ArticleCount" Text="{Loc news-write-ui-article-count-0}"/>
|
||||
</BoxContainer>
|
||||
<Control HorizontalExpand="True"/>
|
||||
<Control>
|
||||
<Button Name="ButtonCreate" SetHeight="26" MinWidth="83" Text="{Loc news-write-ui-create-text}"/>
|
||||
</Control>
|
||||
</BoxContainer>
|
||||
<Control SetHeight="30" Margin="2 0 0 0">
|
||||
<PanelContainer Name="FooterPanel">
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxFlat BorderColor="#5A5A5A" BorderThickness="0 2 0 0" />
|
||||
</PanelContainer.PanelOverride>
|
||||
</PanelContainer>
|
||||
<BoxContainer Name="ContentFooter" HorizontalExpand="True" SetHeight="28">
|
||||
<Label Text="{Loc news-write-ui-footer-text}" VerticalAlignment="Center" Margin="6 0" StyleClasses="PdaContentFooterText"/>
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
97
Content.Client/MassMedia/Ui/NewsWriterMenu.xaml.cs
Normal file
97
Content.Client/MassMedia/Ui/NewsWriterMenu.xaml.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Content.Shared.MassMedia.Systems;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.MassMedia.Ui;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class NewsWriterMenu : FancyWindow
|
||||
{
|
||||
private readonly IGameTiming _gameTiming;
|
||||
|
||||
private TimeSpan? _nextPublish;
|
||||
|
||||
public event Action<int>? DeleteButtonPressed;
|
||||
|
||||
public NewsWriterMenu(IGameTiming gameTiming)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
_gameTiming = gameTiming;
|
||||
ContentsContainer.RectClipContent = false;
|
||||
|
||||
// Customize scrollbar width and margin. This is not possible in xaml
|
||||
var scrollbar = ArticleListScrollbar.GetChild(1);
|
||||
scrollbar.SetWidth = 6f;
|
||||
scrollbar.Margin = new Thickness(0, 0, 2 , 0);
|
||||
|
||||
ButtonCreate.OnPressed += OnCreate;
|
||||
}
|
||||
|
||||
public void UpdateUI(NewsArticle[] articles, bool publishEnabled, TimeSpan nextPublish)
|
||||
{
|
||||
ArticlesContainer.Children.Clear();
|
||||
ArticleCount.Text = Loc.GetString("news-write-ui-article-count-text", ("count", articles.Length));
|
||||
|
||||
//Iterate backwards to have the newest article at the top
|
||||
for (var i = articles.Length - 1; i >= 0 ; i--)
|
||||
{
|
||||
var article = articles[i];
|
||||
var control = new NewsArticleCard
|
||||
{
|
||||
Title = article.Title,
|
||||
Author = article.Author ?? Loc.GetString("news-read-ui-no-author"),
|
||||
PublicationTime = article.ShareTime,
|
||||
ArtcileNumber = i
|
||||
};
|
||||
control.OnDeletePressed += () => DeleteButtonPressed?.Invoke(control.ArtcileNumber);
|
||||
|
||||
ArticlesContainer.AddChild(control);
|
||||
}
|
||||
|
||||
ButtonCreate.Disabled = !publishEnabled;
|
||||
_nextPublish = nextPublish;
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
if (!_nextPublish.HasValue)
|
||||
return;
|
||||
|
||||
var remainingTime = _nextPublish.Value.Subtract(_gameTiming.CurTime);
|
||||
if (remainingTime.TotalSeconds <= 0)
|
||||
{
|
||||
_nextPublish = null;
|
||||
ButtonCreate.Text = Loc.GetString("news-write-ui-create-text");
|
||||
return;
|
||||
}
|
||||
|
||||
ButtonCreate.Text = remainingTime.Seconds.ToString("D2");
|
||||
}
|
||||
|
||||
protected override void Resized()
|
||||
{
|
||||
base.Resized();
|
||||
var margin = ArticleEditorPanel.Margin;
|
||||
// Bandaid for the funny 1 pixel margin differences
|
||||
ArticleEditorPanel.Margin = new Thickness(Width - 1, margin.Top, margin.Right, margin.Bottom);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
ButtonCreate.OnPressed -= OnCreate;
|
||||
}
|
||||
|
||||
private void OnCreate(BaseButton.ButtonEventArgs buttonEventArgs)
|
||||
{
|
||||
ArticleEditorPanel.Visible = true;
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ public sealed class PaperBoundUserInterface : BoundUserInterface
|
||||
_window.OnClose += Close;
|
||||
_window.Input.OnKeyBindDown += args => // Solution while TextEdit don't have events
|
||||
{
|
||||
if (args.Function == EngineKeyFunctions.TextSubmit)
|
||||
if (args.Function == EngineKeyFunctions.MultilineTextSubmit)
|
||||
{
|
||||
var text = Rope.Collapse(_window.Input.TextRope);
|
||||
Input_OnTextEntered(text);
|
||||
|
||||
@@ -74,6 +74,7 @@ namespace Content.Client.Stylesheets
|
||||
public const string StyleClassLabelKeyText = "LabelKeyText";
|
||||
public const string StyleClassLabelSecondaryColor = "LabelSecondaryColor";
|
||||
public const string StyleClassLabelBig = "LabelBig";
|
||||
public const string StyleClassLabelSmall = "LabelSmall";
|
||||
public const string StyleClassButtonBig = "ButtonBig";
|
||||
|
||||
public const string StyleClassPopupMessageSmall = "PopupMessageSmall";
|
||||
@@ -329,6 +330,12 @@ namespace Content.Client.Stylesheets
|
||||
chatFilterButton.SetPatchMargin(StyleBox.Margin.All, 5);
|
||||
chatFilterButton.SetPadding(StyleBox.Margin.All, 2);
|
||||
|
||||
var smallButtonTex = resCache.GetTexture("/Textures/Interface/Nano/button_small.svg.96dpi.png");
|
||||
var smallButtonBase = new StyleBoxTexture
|
||||
{
|
||||
Texture = smallButtonTex,
|
||||
};
|
||||
|
||||
var textureInvertedTriangle = resCache.GetTexture("/Textures/Interface/Nano/inverted_triangle.svg.png");
|
||||
|
||||
var lineEditTex = resCache.GetTexture("/Textures/Interface/Nano/lineedit.png");
|
||||
@@ -646,6 +653,23 @@ namespace Content.Client.Stylesheets
|
||||
.Pseudo(ContainerButton.StylePseudoClassDisabled)
|
||||
.Prop(Control.StylePropertyModulateSelf, ButtonColorCautionDisabled),
|
||||
|
||||
// Colors for confirm buttons confirm states.
|
||||
Element<ConfirmButton>()
|
||||
.Pseudo(ConfirmButton.ConfirmPrefix + ContainerButton.StylePseudoClassNormal)
|
||||
.Prop(Control.StylePropertyModulateSelf, ButtonColorCautionDefault),
|
||||
|
||||
Element<ConfirmButton>()
|
||||
.Pseudo(ConfirmButton.ConfirmPrefix + ContainerButton.StylePseudoClassHover)
|
||||
.Prop(Control.StylePropertyModulateSelf, ButtonColorCautionHovered),
|
||||
|
||||
Element<ConfirmButton>()
|
||||
.Pseudo(ConfirmButton.ConfirmPrefix + ContainerButton.StylePseudoClassPressed)
|
||||
.Prop(Control.StylePropertyModulateSelf, ButtonColorCautionPressed),
|
||||
|
||||
Element<ConfirmButton>()
|
||||
.Pseudo(ConfirmButton.ConfirmPrefix + ContainerButton.StylePseudoClassDisabled)
|
||||
.Prop(Control.StylePropertyModulateSelf, ButtonColorCautionDisabled),
|
||||
|
||||
new StyleRule(new SelectorChild(
|
||||
new SelectorElement(typeof(Button), null, null, new[] {ContainerButton.StylePseudoClassDisabled}),
|
||||
new SelectorElement(typeof(Label), null, null, null)),
|
||||
@@ -1189,14 +1213,6 @@ namespace Content.Client.Stylesheets
|
||||
new StyleProperty(StripeBack.StylePropertyBackground, stripeBack),
|
||||
}),
|
||||
|
||||
// StyleClassLabelBig
|
||||
new StyleRule(
|
||||
SelectorElement.Class(StyleClassLabelBig),
|
||||
new[]
|
||||
{
|
||||
new StyleProperty("font", notoSans16),
|
||||
}),
|
||||
|
||||
// StyleClassItemStatus
|
||||
new StyleRule(SelectorElement.Class(StyleClassItemStatus), new[]
|
||||
{
|
||||
@@ -1303,10 +1319,29 @@ namespace Content.Client.Stylesheets
|
||||
new StyleProperty(PanelContainer.StylePropertyPanel, new StyleBoxFlat { BackgroundColor = NanoGold, ContentMarginBottomOverride = 2, ContentMarginLeftOverride = 2}),
|
||||
}),
|
||||
|
||||
// Labels ---
|
||||
Element<Label>().Class(StyleClassLabelBig)
|
||||
.Prop(Label.StylePropertyFont, notoSans16),
|
||||
|
||||
Element<Label>().Class(StyleClassLabelSmall)
|
||||
.Prop(Label.StylePropertyFont, notoSans10),
|
||||
// ---
|
||||
|
||||
// Different Background shapes ---
|
||||
Element<PanelContainer>().Class(ClassAngleRect)
|
||||
.Prop(PanelContainer.StylePropertyPanel, BaseAngleRect)
|
||||
.Prop(Control.StylePropertyModulateSelf, Color.FromHex("#25252A")),
|
||||
|
||||
Element<PanelContainer>().Class("BackgroundOpenRight")
|
||||
.Prop(PanelContainer.StylePropertyPanel, BaseButtonOpenRight)
|
||||
.Prop(Control.StylePropertyModulateSelf, Color.FromHex("#25252A")),
|
||||
|
||||
Element<PanelContainer>().Class("BackgroundOpenLeft")
|
||||
.Prop(PanelContainer.StylePropertyPanel, BaseButtonOpenLeft)
|
||||
.Prop(Control.StylePropertyModulateSelf, Color.FromHex("#25252A")),
|
||||
// ---
|
||||
|
||||
// Dividers
|
||||
Element<PanelContainer>().Class(ClassLowDivider)
|
||||
.Prop(PanelContainer.StylePropertyPanel, new StyleBoxFlat
|
||||
{
|
||||
@@ -1393,6 +1428,15 @@ namespace Content.Client.Stylesheets
|
||||
.Prop(Control.StylePropertyModulateSelf, ButtonColorGoodHovered),
|
||||
// ---
|
||||
|
||||
// Small Button ---
|
||||
Element<Button>().Class("ButtonSmall")
|
||||
.Prop(ContainerButton.StylePropertyStyleBox, smallButtonBase),
|
||||
|
||||
Child().Parent(Element<Button>().Class("ButtonSmall"))
|
||||
.Child(Element<Label>())
|
||||
.Prop(Label.StylePropertyFont, notoSans8),
|
||||
// ---
|
||||
|
||||
Element<Label>().Class("StatusFieldTitle")
|
||||
.Prop("font-color", NanoGold),
|
||||
|
||||
@@ -1490,7 +1534,6 @@ namespace Content.Client.Stylesheets
|
||||
{
|
||||
BackgroundColor = FancyTreeSelectedRowColor,
|
||||
}),
|
||||
|
||||
}).ToList());
|
||||
}
|
||||
}
|
||||
|
||||
142
Content.Client/UserInterface/Controls/ConfirmButton.cs
Normal file
142
Content.Client/UserInterface/Controls/ConfirmButton.cs
Normal file
@@ -0,0 +1,142 @@
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.UserInterface.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// A Button that requires a second click to actually invoke its OnPressed action. <br/>
|
||||
/// When clicked once it will change rendering modes to be prefixed by <see cref="ConfirmPrefix"/>
|
||||
/// and displays <see cref="ConfirmationText"/> on the button instead of <see cref="Text"/>.<br/>
|
||||
/// <br/>
|
||||
/// After the first click <see cref="CooldownTime"/> needs to elapse before it can be clicked again to confirm.<br/>
|
||||
/// When the button doesn't get clicked a second time before <see cref="ResetTime"/> passes it changes back to its normal state.<br/>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Colors for the different states need to be set in the stylesheet
|
||||
/// </remarks>
|
||||
public sealed class ConfirmButton : Button
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
public const string ConfirmPrefix = "confirm-";
|
||||
|
||||
private TimeSpan? _nextReset;
|
||||
private TimeSpan? _nextCooldown;
|
||||
private string? _confirmationText;
|
||||
private string? _text;
|
||||
|
||||
/// <summary>
|
||||
/// Fired when the button was pressed and confirmed
|
||||
/// </summary>
|
||||
public new event Action<ButtonEventArgs>? OnPressed;
|
||||
|
||||
/// <inheritdoc cref="Button.Text"/>
|
||||
/// <remarks>
|
||||
/// Hides the buttons text property to be able to sanely replace the button text with
|
||||
/// <see cref="_confirmationText"/> when asking for confirmation
|
||||
/// </remarks>
|
||||
public new string? Text
|
||||
{
|
||||
get => _text;
|
||||
set
|
||||
{
|
||||
_text = value;
|
||||
base.Text = IsConfirming ? _confirmationText : value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The text displayed on the button when waiting for a second click
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string ConfirmationText
|
||||
{
|
||||
get => _confirmationText ?? Loc.GetString("generic-confirm");
|
||||
set => _confirmationText = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The time until the button reverts to normal
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public TimeSpan ResetTime { get; set; } = TimeSpan.FromSeconds(2);
|
||||
|
||||
/// <summary>
|
||||
/// The time until the button accepts a second click. This is to prevent accidentally confirming the button
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public TimeSpan CooldownTime { get; set; } = TimeSpan.FromSeconds(.5);
|
||||
|
||||
[ViewVariables]
|
||||
public bool IsConfirming = false;
|
||||
|
||||
public ConfirmButton()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
base.OnPressed += HandleOnPressed;
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
if (IsConfirming && _gameTiming.CurTime > _nextReset)
|
||||
{
|
||||
IsConfirming = false;
|
||||
base.Text = Text;
|
||||
DrawModeChanged();
|
||||
}
|
||||
|
||||
if (Disabled && _gameTiming.CurTime > _nextCooldown)
|
||||
Disabled = false;
|
||||
}
|
||||
|
||||
protected override void DrawModeChanged()
|
||||
{
|
||||
if (IsConfirming)
|
||||
{
|
||||
switch (DrawMode)
|
||||
{
|
||||
case DrawModeEnum.Normal:
|
||||
SetOnlyStylePseudoClass(ConfirmPrefix + StylePseudoClassNormal);
|
||||
break;
|
||||
case DrawModeEnum.Pressed:
|
||||
SetOnlyStylePseudoClass(ConfirmPrefix + StylePseudoClassPressed);
|
||||
break;
|
||||
case DrawModeEnum.Hover:
|
||||
SetOnlyStylePseudoClass(ConfirmPrefix + StylePseudoClassHover);
|
||||
break;
|
||||
case DrawModeEnum.Disabled:
|
||||
SetOnlyStylePseudoClass(ConfirmPrefix + StylePseudoClassDisabled);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
base.DrawModeChanged();
|
||||
}
|
||||
|
||||
private void HandleOnPressed(ButtonEventArgs buttonEvent)
|
||||
{
|
||||
//Prevent accidental confirmations from double clicking
|
||||
if (IsConfirming && _nextCooldown > _gameTiming.CurTime)
|
||||
return;
|
||||
|
||||
switch (IsConfirming)
|
||||
{
|
||||
case false:
|
||||
_nextCooldown = _gameTiming.CurTime + CooldownTime;
|
||||
_nextReset = _gameTiming.CurTime + ResetTime;
|
||||
Disabled = true;
|
||||
break;
|
||||
case true:
|
||||
OnPressed?.Invoke(buttonEvent);
|
||||
break;
|
||||
}
|
||||
|
||||
base.Text = IsConfirming ? Text : ConfirmationText;
|
||||
|
||||
IsConfirming = !IsConfirming;
|
||||
}
|
||||
}
|
||||
@@ -488,11 +488,12 @@ public sealed class ChatUIController : UIController
|
||||
|
||||
if (_state.CurrentState is GameplayStateBase)
|
||||
{
|
||||
// can always hear local / radio / emote when in the game
|
||||
// can always hear local / radio / emote / notifications when in the game
|
||||
FilterableChannels |= ChatChannel.Local;
|
||||
FilterableChannels |= ChatChannel.Whisper;
|
||||
FilterableChannels |= ChatChannel.Radio;
|
||||
FilterableChannels |= ChatChannel.Emotes;
|
||||
FilterableChannels |= ChatChannel.Notifications;
|
||||
|
||||
// Can only send local / radio / emote when attached to a non-ghost entity.
|
||||
// TODO: this logic is iffy (checking if controlling something that's NOT a ghost), is there a better way to check this?
|
||||
|
||||
@@ -16,6 +16,7 @@ public sealed partial class ChannelFilterPopup : Popup
|
||||
ChatChannel.Whisper,
|
||||
ChatChannel.Emotes,
|
||||
ChatChannel.Radio,
|
||||
ChatChannel.Notifications,
|
||||
ChatChannel.LOOC,
|
||||
ChatChannel.OOC,
|
||||
ChatChannel.Dead,
|
||||
|
||||
@@ -199,9 +199,13 @@ public sealed class CartridgeLoaderSystem : SharedCartridgeLoaderSystem
|
||||
return false;
|
||||
|
||||
var installedProgram = Spawn(prototype, new EntityCoordinates(loaderUid, 0, 0));
|
||||
if (!TryComp(installedProgram, out CartridgeComponent? cartridge))
|
||||
return false;
|
||||
|
||||
_containerSystem.Insert(installedProgram, container);
|
||||
|
||||
UpdateCartridgeInstallationStatus(installedProgram, deinstallable ? InstallationStatus.Installed : InstallationStatus.Readonly);
|
||||
UpdateCartridgeInstallationStatus(installedProgram, deinstallable ? InstallationStatus.Installed : InstallationStatus.Readonly, cartridge);
|
||||
cartridge.LoaderUid = loaderUid;
|
||||
|
||||
RaiseLocalEvent(installedProgram, new CartridgeAddedEvent(loaderUid));
|
||||
UpdateUserInterfaceState(loaderUid, loader);
|
||||
@@ -223,11 +227,14 @@ public sealed class CartridgeLoaderSystem : SharedCartridgeLoaderSystem
|
||||
if (!GetInstalled(loaderUid).Contains(programUid))
|
||||
return false;
|
||||
|
||||
if (TryComp(programUid, out CartridgeComponent? cartridge))
|
||||
cartridge.LoaderUid = null;
|
||||
|
||||
if (loader.ActiveProgram == programUid)
|
||||
loader.ActiveProgram = null;
|
||||
|
||||
loader.BackgroundPrograms.Remove(programUid);
|
||||
EntityManager.QueueDeleteEntity(programUid);
|
||||
QueueDel(programUid);
|
||||
UpdateUserInterfaceState(loaderUid, loader);
|
||||
return true;
|
||||
}
|
||||
@@ -308,6 +315,18 @@ public sealed class CartridgeLoaderSystem : SharedCartridgeLoaderSystem
|
||||
loader.BackgroundPrograms.Remove(cartridgeUid);
|
||||
}
|
||||
|
||||
public void SendNotification(EntityUid loaderUid, string header, string message, CartridgeLoaderComponent? loader = default!)
|
||||
{
|
||||
if (!Resolve(loaderUid, ref loader))
|
||||
return;
|
||||
|
||||
if (!loader.NotificationsEnabled)
|
||||
return;
|
||||
|
||||
var args = new CartridgeLoaderNotificationSentEvent(header, message);
|
||||
RaiseLocalEvent(loaderUid, ref args);
|
||||
}
|
||||
|
||||
protected override void OnItemInserted(EntityUid uid, CartridgeLoaderComponent loader, EntInsertedIntoContainerMessage args)
|
||||
{
|
||||
if (args.Container.ID != InstalledContainerId && args.Container.ID != loader.CartridgeSlot.ID)
|
||||
@@ -434,13 +453,10 @@ public sealed class CartridgeLoaderSystem : SharedCartridgeLoaderSystem
|
||||
UpdateUiState(loaderUid, null, loader);
|
||||
}
|
||||
|
||||
private void UpdateCartridgeInstallationStatus(EntityUid cartridgeUid, InstallationStatus installationStatus, CartridgeComponent? cartridgeComponent = default!)
|
||||
private void UpdateCartridgeInstallationStatus(EntityUid cartridgeUid, InstallationStatus installationStatus, CartridgeComponent cartridgeComponent)
|
||||
{
|
||||
if (Resolve(cartridgeUid, ref cartridgeComponent))
|
||||
{
|
||||
cartridgeComponent.InstallationStatus = installationStatus;
|
||||
Dirty(cartridgeUid, cartridgeComponent);
|
||||
}
|
||||
cartridgeComponent.InstallationStatus = installationStatus;
|
||||
Dirty(cartridgeUid, cartridgeComponent);
|
||||
}
|
||||
|
||||
private bool HasProgram(EntityUid loader, EntityUid program, CartridgeLoaderComponent component)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
namespace Content.Server.CartridgeLoader.Cartridges;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class NewsReadCartridgeComponent : Component
|
||||
public sealed partial class NewsReaderCartridgeComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int ArticleNum;
|
||||
public int ArticleNumber;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField]
|
||||
public bool NotificationOn = true;
|
||||
}
|
||||
@@ -19,6 +19,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Content.Packaging\Content.Packaging.csproj" />
|
||||
<ProjectReference Include="..\Content.Server.Database\Content.Server.Database.csproj" />
|
||||
<ProjectReference Include="..\Content.Shared.Database\Content.Shared.Database.csproj" />
|
||||
<ProjectReference Include="..\RobustToolbox\Lidgren.Network\Lidgren.Network.csproj" />
|
||||
<ProjectReference Include="..\RobustToolbox\Robust.Shared.Maths\Robust.Shared.Maths.csproj" />
|
||||
<ProjectReference Include="..\RobustToolbox\Robust.Shared\Robust.Shared.csproj" />
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Server.MassMedia.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed partial class NewsWriteComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool ShareAvalible = false;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("nextShare", customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
public TimeSpan NextShare;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("shareCooldown")]
|
||||
public float ShareCooldown = 60f;
|
||||
|
||||
[DataField("noAccessSound")]
|
||||
public SoundSpecifier NoAccessSound = new SoundPathSpecifier("/Audio/Machines/airlock_deny.ogg");
|
||||
[DataField("confirmSound")]
|
||||
public SoundSpecifier ConfirmSound = new SoundPathSpecifier("/Audio/Machines/scan_finish.ogg");
|
||||
}
|
||||
}
|
||||
25
Content.Server/MassMedia/Components/NewsWriterComponent.cs
Normal file
25
Content.Server/MassMedia/Components/NewsWriterComponent.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using Content.Server.MassMedia.Systems;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Server.MassMedia.Components;
|
||||
|
||||
[RegisterComponent, AutoGenerateComponentPause]
|
||||
[Access(typeof(NewsSystem))]
|
||||
public sealed partial class NewsWriterComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField]
|
||||
public bool PublishEnabled;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
|
||||
public TimeSpan NextPublish;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField]
|
||||
public float PublishCooldown = 20f;
|
||||
|
||||
[DataField]
|
||||
public SoundSpecifier NoAccessSound = new SoundPathSpecifier("/Audio/Machines/airlock_deny.ogg");
|
||||
|
||||
[DataField]
|
||||
public SoundSpecifier ConfirmSound = new SoundPathSpecifier("/Audio/Machines/scan_finish.ogg");
|
||||
}
|
||||
@@ -3,285 +3,321 @@ using Content.Server.Administration.Logs;
|
||||
using Content.Server.CartridgeLoader;
|
||||
using Content.Server.CartridgeLoader.Cartridges;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.MassMedia.Components;
|
||||
using Content.Server.PDA.Ringer;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Server.Access.Systems;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.StationRecords.Systems;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.CartridgeLoader;
|
||||
using Content.Shared.CartridgeLoader.Cartridges;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.MassMedia.Components;
|
||||
using Content.Shared.MassMedia.Systems;
|
||||
using Content.Shared.PDA;
|
||||
using Robust.Server.GameObjects;
|
||||
using Content.Server.MassMedia.Components;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.StationRecords;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.MassMedia.Systems;
|
||||
|
||||
public sealed class NewsSystem : SharedNewsSystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _ui = default!;
|
||||
[Dependency] private readonly RingerSystem _ringer = default!;
|
||||
[Dependency] private readonly CartridgeLoaderSystem _cartridgeLoaderSystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
[Dependency] private readonly GameTicker _ticker = default!;
|
||||
[Dependency] private readonly AccessReaderSystem _accessReader = default!;
|
||||
[Dependency] private readonly StationRecordsSystem _stationRecords = default!;
|
||||
|
||||
// TODO remove this. Dont store data on systems
|
||||
// Honestly NewsSystem just needs someone to rewrite it entirely.
|
||||
private readonly List<NewsArticle> _articles = new List<NewsArticle>();
|
||||
[Dependency] private readonly IdCardSystem _idCardSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<NewsWriteComponent, NewsWriteShareMessage>(OnWriteUiShareMessage);
|
||||
SubscribeLocalEvent<NewsWriteComponent, NewsWriteDeleteMessage>(OnWriteUiDeleteMessage);
|
||||
SubscribeLocalEvent<NewsWriteComponent, NewsWriteArticlesRequestMessage>(OnRequestWriteUiMessage);
|
||||
// News writer
|
||||
SubscribeLocalEvent<NewsWriterComponent, MapInitEvent>(OnMapInit);
|
||||
|
||||
SubscribeLocalEvent<NewsReadCartridgeComponent, CartridgeUiReadyEvent>(OnReadUiReady);
|
||||
SubscribeLocalEvent<NewsReadCartridgeComponent, CartridgeMessageEvent>(OnReadUiMessage);
|
||||
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundRestart);
|
||||
}
|
||||
|
||||
private void OnRoundRestart(RoundRestartCleanupEvent ev)
|
||||
{
|
||||
_articles.Clear();
|
||||
}
|
||||
|
||||
public void ToggleUi(EntityUid user, EntityUid deviceEnt, NewsWriteComponent? component)
|
||||
{
|
||||
if (!Resolve(deviceEnt, ref component))
|
||||
return;
|
||||
|
||||
if (!TryComp<ActorComponent>(user, out var actor))
|
||||
return;
|
||||
|
||||
_ui.TryToggleUi(deviceEnt, NewsWriteUiKey.Key, actor.PlayerSession);
|
||||
}
|
||||
|
||||
public void OnReadUiReady(EntityUid uid, NewsReadCartridgeComponent component, CartridgeUiReadyEvent args)
|
||||
{
|
||||
UpdateReadUi(uid, args.Loader, component);
|
||||
}
|
||||
|
||||
public void UpdateWriteUi(EntityUid uid, NewsWriteComponent component)
|
||||
{
|
||||
if (!_ui.TryGetUi(uid, NewsWriteUiKey.Key, out _))
|
||||
return;
|
||||
|
||||
var state = new NewsWriteBoundUserInterfaceState(_articles.ToArray(), component.ShareAvalible);
|
||||
_ui.TrySetUiState(uid, NewsWriteUiKey.Key, state);
|
||||
}
|
||||
|
||||
public void UpdateReadUi(EntityUid uid, EntityUid loaderUid, NewsReadCartridgeComponent? component)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
NewsReadLeafArticle(component, 0);
|
||||
|
||||
if (_articles.Any())
|
||||
_cartridgeLoaderSystem?.UpdateCartridgeUiState(loaderUid, new NewsReadBoundUserInterfaceState(_articles[component.ArticleNum], component.ArticleNum + 1, _articles.Count, component.NotificationOn));
|
||||
else
|
||||
_cartridgeLoaderSystem?.UpdateCartridgeUiState(loaderUid, new NewsReadEmptyBoundUserInterfaceState(component.NotificationOn));
|
||||
}
|
||||
|
||||
private void OnReadUiMessage(EntityUid uid, NewsReadCartridgeComponent component, CartridgeMessageEvent args)
|
||||
{
|
||||
if (args is not NewsReadUiMessageEvent message)
|
||||
return;
|
||||
|
||||
if (message.Action == NewsReadUiAction.Next)
|
||||
NewsReadLeafArticle(component, 1);
|
||||
if (message.Action == NewsReadUiAction.Prev)
|
||||
NewsReadLeafArticle(component, -1);
|
||||
if (message.Action == NewsReadUiAction.NotificationSwith)
|
||||
component.NotificationOn = !component.NotificationOn;
|
||||
|
||||
UpdateReadUi(uid, GetEntity(args.LoaderUid), component);
|
||||
}
|
||||
|
||||
public void OnWriteUiShareMessage(EntityUid uid, NewsWriteComponent component, NewsWriteShareMessage msg)
|
||||
{
|
||||
// dont blindly trust input from clients.
|
||||
if (msg.Session.AttachedEntity is not {} author)
|
||||
return;
|
||||
|
||||
if (!_accessReader.FindAccessItemsInventory(author, out var items))
|
||||
return;
|
||||
|
||||
if (!_accessReader.FindStationRecordKeys(author, out _, items))
|
||||
return;
|
||||
|
||||
string? authorName = null;
|
||||
|
||||
// TODO: There is a dedicated helper for this.
|
||||
foreach (var item in items)
|
||||
// New writer bui messages
|
||||
Subs.BuiEvents<NewsWriterComponent>(NewsWriterUiKey.Key, subs =>
|
||||
{
|
||||
// ID Card
|
||||
if (TryComp(item, out IdCardComponent? id))
|
||||
{
|
||||
authorName = id.FullName;
|
||||
break;
|
||||
}
|
||||
subs.Event<NewsWriterDeleteMessage>(OnWriteUiDeleteMessage);
|
||||
subs.Event<NewsWriterArticlesRequestMessage>(OnRequestArticlesUiMessage);
|
||||
subs.Event<NewsWriterPublishMessage>(OnWriteUiPublishMessage);
|
||||
});
|
||||
|
||||
if (TryComp(item, out PdaComponent? pda)
|
||||
&& pda.ContainedId != null
|
||||
&& TryComp(pda.ContainedId, out id))
|
||||
{
|
||||
authorName = id.FullName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var trimmedName = msg.Name.Trim();
|
||||
var trimmedContent = msg.Content.Trim();
|
||||
|
||||
var article = new NewsArticle
|
||||
{
|
||||
Author = authorName,
|
||||
Name = trimmedName.Length <= MaxNameLength ? trimmedName : $"{trimmedName[..MaxNameLength]}...",
|
||||
Content = trimmedContent.Length <= MaxArticleLength ? trimmedContent : $"{trimmedContent[..MaxArticleLength]}...",
|
||||
ShareTime = _ticker.RoundDuration()
|
||||
};
|
||||
|
||||
_audio.PlayPvs(component.ConfirmSound, uid);
|
||||
_adminLogger.Add(LogType.Chat, LogImpact.Medium, $"{ToPrettyString(author):actor} created news article {article.Name} by {article.Author}: {article.Content}");
|
||||
_articles.Add(article);
|
||||
|
||||
component.ShareAvalible = false;
|
||||
component.NextShare = _timing.CurTime + TimeSpan.FromSeconds(component.ShareCooldown);
|
||||
|
||||
UpdateReadDevices();
|
||||
UpdateWriteDevices();
|
||||
TryNotify();
|
||||
}
|
||||
|
||||
public void OnWriteUiDeleteMessage(EntityUid uid, NewsWriteComponent component, NewsWriteDeleteMessage msg)
|
||||
{
|
||||
if (msg.ArticleNum > _articles.Count)
|
||||
return;
|
||||
|
||||
var articleDeleter = msg.Session.AttachedEntity;
|
||||
if (CheckDeleteAccess(_articles[msg.ArticleNum], uid, articleDeleter))
|
||||
{
|
||||
if (articleDeleter != null)
|
||||
_adminLogger.Add(LogType.Chat, LogImpact.Medium, $"{ToPrettyString(articleDeleter.Value):actor} deleted news article {_articles[msg.ArticleNum].Name} by {_articles[msg.ArticleNum].Author}: {_articles[msg.ArticleNum].Content}");
|
||||
else
|
||||
_adminLogger.Add(LogType.Chat, LogImpact.Medium, $"{msg.Session.Name:actor} created news article {_articles[msg.ArticleNum].Name}: {_articles[msg.ArticleNum].Content}");
|
||||
_articles.RemoveAt(msg.ArticleNum);
|
||||
_audio.PlayPvs(component.ConfirmSound, uid);
|
||||
}
|
||||
else
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("news-write-no-access-popup"), uid);
|
||||
_audio.PlayPvs(component.NoAccessSound, uid);
|
||||
}
|
||||
|
||||
UpdateReadDevices();
|
||||
UpdateWriteDevices();
|
||||
}
|
||||
|
||||
public void OnRequestWriteUiMessage(EntityUid uid, NewsWriteComponent component, NewsWriteArticlesRequestMessage msg)
|
||||
{
|
||||
UpdateWriteUi(uid, component);
|
||||
}
|
||||
|
||||
private void NewsReadLeafArticle(NewsReadCartridgeComponent component, int leafDir)
|
||||
{
|
||||
component.ArticleNum += leafDir;
|
||||
|
||||
if (component.ArticleNum >= _articles.Count) component.ArticleNum = 0;
|
||||
if (component.ArticleNum < 0) component.ArticleNum = _articles.Count - 1;
|
||||
}
|
||||
|
||||
private void TryNotify()
|
||||
{
|
||||
var query = EntityQueryEnumerator<CartridgeLoaderComponent, RingerComponent, ContainerManagerComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var comp, out var ringer, out var cont))
|
||||
{
|
||||
if (!_cartridgeLoaderSystem.TryGetProgram<NewsReadCartridgeComponent>(uid, out _, out var newsReadCartridgeComponent, false, comp, cont)
|
||||
|| !newsReadCartridgeComponent.NotificationOn)
|
||||
continue;
|
||||
|
||||
_ringer.RingerPlayRingtone(uid, ringer);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateReadDevices()
|
||||
{
|
||||
var query = EntityQueryEnumerator<CartridgeLoaderComponent>();
|
||||
|
||||
while (query.MoveNext(out var owner, out var comp))
|
||||
{
|
||||
if (EntityManager.TryGetComponent<NewsReadCartridgeComponent>(comp.ActiveProgram, out var cartridge))
|
||||
UpdateReadUi(comp.ActiveProgram.Value, owner, cartridge);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateWriteDevices()
|
||||
{
|
||||
var query = EntityQueryEnumerator<NewsWriteComponent>();
|
||||
|
||||
while (query.MoveNext(out var owner, out var comp))
|
||||
{
|
||||
UpdateWriteUi(owner, comp);
|
||||
}
|
||||
}
|
||||
|
||||
private bool CheckDeleteAccess(NewsArticle articleToDelete, EntityUid device, EntityUid? user)
|
||||
{
|
||||
if (EntityManager.TryGetComponent<AccessReaderComponent>(device, out var accessReader) &&
|
||||
user.HasValue &&
|
||||
_accessReader.IsAllowed(user.Value, device, accessReader))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (articleToDelete.AuthorStationRecordKeyIds == null ||
|
||||
!articleToDelete.AuthorStationRecordKeyIds.Any())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var conv = _stationRecords.Convert(articleToDelete.AuthorStationRecordKeyIds);
|
||||
if (user.HasValue
|
||||
&& _accessReader.FindStationRecordKeys(user.Value, out var recordKeys)
|
||||
&& recordKeys.Intersect(conv).Any())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
// News reader
|
||||
SubscribeLocalEvent<NewsReaderCartridgeComponent, NewsArticlePublishedEvent>(OnArticlePublished);
|
||||
SubscribeLocalEvent<NewsReaderCartridgeComponent, NewsArticleDeletedEvent>(OnArticleDeleted);
|
||||
SubscribeLocalEvent<NewsReaderCartridgeComponent, CartridgeMessageEvent>(OnReaderUiMessage);
|
||||
SubscribeLocalEvent<NewsReaderCartridgeComponent, CartridgeUiReadyEvent>(OnReaderUiReady);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = EntityQueryEnumerator<NewsWriteComponent>();
|
||||
var query = EntityQueryEnumerator<NewsWriterComponent>();
|
||||
while (query.MoveNext(out var uid, out var comp))
|
||||
{
|
||||
if (comp.ShareAvalible || _timing.CurTime < comp.NextShare)
|
||||
if (comp.PublishEnabled || _timing.CurTime < comp.NextPublish)
|
||||
continue;
|
||||
|
||||
comp.ShareAvalible = true;
|
||||
|
||||
UpdateWriteUi(uid, comp);
|
||||
comp.PublishEnabled = true;
|
||||
UpdateWriterUi((uid, comp));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Writer Event Handlers
|
||||
|
||||
private void OnMapInit(Entity<NewsWriterComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
var station = _station.GetOwningStation(ent);
|
||||
if (!station.HasValue)
|
||||
return;
|
||||
|
||||
EnsureComp<StationNewsComponent>(station.Value);
|
||||
}
|
||||
|
||||
private void OnWriteUiDeleteMessage(Entity<NewsWriterComponent> ent, ref NewsWriterDeleteMessage msg)
|
||||
{
|
||||
if (!TryGetArticles(ent, out var articles))
|
||||
return;
|
||||
|
||||
if (msg.ArticleNum >= articles.Count)
|
||||
return;
|
||||
|
||||
if (msg.Session.AttachedEntity is not { } actor)
|
||||
return;
|
||||
|
||||
var article = articles[msg.ArticleNum];
|
||||
if (CheckDeleteAccess(article, ent, actor))
|
||||
{
|
||||
_adminLogger.Add(
|
||||
LogType.Chat, LogImpact.Medium,
|
||||
$"{ToPrettyString(actor):actor} deleted news article {article.Title} by {article.Author}: {article.Content}"
|
||||
);
|
||||
|
||||
articles.RemoveAt(msg.ArticleNum);
|
||||
_audio.PlayPvs(ent.Comp.ConfirmSound, ent);
|
||||
}
|
||||
else
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("news-write-no-access-popup"), ent, PopupType.SmallCaution);
|
||||
_audio.PlayPvs(ent.Comp.NoAccessSound, ent);
|
||||
}
|
||||
|
||||
var args = new NewsArticleDeletedEvent();
|
||||
var query = EntityQueryEnumerator<NewsReaderCartridgeComponent>();
|
||||
while (query.MoveNext(out var readerUid, out _))
|
||||
{
|
||||
RaiseLocalEvent(readerUid, ref args);
|
||||
}
|
||||
|
||||
UpdateWriterDevices();
|
||||
}
|
||||
|
||||
private void OnRequestArticlesUiMessage(Entity<NewsWriterComponent> ent, ref NewsWriterArticlesRequestMessage msg)
|
||||
{
|
||||
UpdateWriterUi(ent);
|
||||
}
|
||||
|
||||
private void OnWriteUiPublishMessage(Entity<NewsWriterComponent> ent, ref NewsWriterPublishMessage msg)
|
||||
{
|
||||
if (!ent.Comp.PublishEnabled)
|
||||
return;
|
||||
|
||||
ent.Comp.PublishEnabled = false;
|
||||
ent.Comp.NextPublish = _timing.CurTime + TimeSpan.FromSeconds(ent.Comp.PublishCooldown);
|
||||
|
||||
if (!TryGetArticles(ent, out var articles))
|
||||
return;
|
||||
|
||||
if (msg.Session.AttachedEntity is not { } author)
|
||||
return;
|
||||
|
||||
if (!_accessReader.FindStationRecordKeys(author, out _))
|
||||
return;
|
||||
|
||||
string? authorName = null;
|
||||
if (_idCardSystem.TryFindIdCard(author, out var idCard))
|
||||
authorName = idCard.Comp.FullName;
|
||||
|
||||
var title = msg.Title.Trim();
|
||||
var content = msg.Content.Trim();
|
||||
|
||||
var article = new NewsArticle
|
||||
{
|
||||
Title = title.Length <= MaxTitleLength ? title : $"{title[..MaxTitleLength]}...",
|
||||
Content = content.Length <= MaxContentLength ? content : $"{content[..MaxContentLength]}...",
|
||||
Author = authorName,
|
||||
ShareTime = _ticker.RoundDuration()
|
||||
};
|
||||
|
||||
_audio.PlayPvs(ent.Comp.ConfirmSound, ent);
|
||||
|
||||
_adminLogger.Add(
|
||||
LogType.Chat,
|
||||
LogImpact.Medium,
|
||||
$"{ToPrettyString(author):actor} created news article {article.Title} by {article.Author}: {article.Content}"
|
||||
);
|
||||
|
||||
articles.Add(article);
|
||||
|
||||
var args = new NewsArticlePublishedEvent(article);
|
||||
var query = EntityQueryEnumerator<NewsReaderCartridgeComponent>();
|
||||
while (query.MoveNext(out var readerUid, out _))
|
||||
{
|
||||
RaiseLocalEvent(readerUid, ref args);
|
||||
}
|
||||
|
||||
UpdateWriterDevices();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Reader Event Handlers
|
||||
|
||||
private void OnArticlePublished(Entity<NewsReaderCartridgeComponent> ent, ref NewsArticlePublishedEvent args)
|
||||
{
|
||||
if (Comp<CartridgeComponent>(ent).LoaderUid is not { } loaderUid)
|
||||
return;
|
||||
|
||||
UpdateReaderUi(ent, loaderUid);
|
||||
|
||||
if (!ent.Comp.NotificationOn)
|
||||
return;
|
||||
|
||||
_cartridgeLoaderSystem.SendNotification(
|
||||
loaderUid,
|
||||
Loc.GetString("news-pda-notification-header"),
|
||||
args.Article.Title);
|
||||
}
|
||||
|
||||
private void OnArticleDeleted(Entity<NewsReaderCartridgeComponent> ent, ref NewsArticleDeletedEvent args)
|
||||
{
|
||||
if (Comp<CartridgeComponent>(ent).LoaderUid is not { } loaderUid)
|
||||
return;
|
||||
|
||||
UpdateReaderUi(ent, loaderUid);
|
||||
}
|
||||
|
||||
private void OnReaderUiMessage(Entity<NewsReaderCartridgeComponent> ent, ref CartridgeMessageEvent args)
|
||||
{
|
||||
if (args is not NewsReaderUiMessageEvent message)
|
||||
return;
|
||||
|
||||
switch (message.Action)
|
||||
{
|
||||
case NewsReaderUiAction.Next:
|
||||
NewsReaderLeafArticle(ent, 1);
|
||||
break;
|
||||
case NewsReaderUiAction.Prev:
|
||||
NewsReaderLeafArticle(ent, -1);
|
||||
break;
|
||||
case NewsReaderUiAction.NotificationSwitch:
|
||||
ent.Comp.NotificationOn = !ent.Comp.NotificationOn;
|
||||
break;
|
||||
}
|
||||
|
||||
UpdateReaderUi(ent, GetEntity(args.LoaderUid));
|
||||
}
|
||||
|
||||
private void OnReaderUiReady(Entity<NewsReaderCartridgeComponent> ent, ref CartridgeUiReadyEvent args)
|
||||
{
|
||||
UpdateReaderUi(ent, args.Loader);
|
||||
}
|
||||
#endregion
|
||||
|
||||
private bool TryGetArticles(EntityUid uid, [NotNullWhen(true)] out List<NewsArticle>? articles)
|
||||
{
|
||||
if (_station.GetOwningStation(uid) is not { } station ||
|
||||
!TryComp<StationNewsComponent>(station, out var stationNews))
|
||||
{
|
||||
articles = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
articles = stationNews.Articles;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void UpdateWriterUi(Entity<NewsWriterComponent> ent)
|
||||
{
|
||||
if (!_ui.TryGetUi(ent, NewsWriterUiKey.Key, out var ui))
|
||||
return;
|
||||
|
||||
if (!TryGetArticles(ent, out var articles))
|
||||
return;
|
||||
|
||||
var state = new NewsWriterBoundUserInterfaceState(articles.ToArray(), ent.Comp.PublishEnabled, ent.Comp.NextPublish);
|
||||
_ui.SetUiState(ui, state);
|
||||
}
|
||||
|
||||
private void UpdateReaderUi(Entity<NewsReaderCartridgeComponent> ent, EntityUid loaderUid)
|
||||
{
|
||||
if (!TryGetArticles(ent, out var articles))
|
||||
return;
|
||||
|
||||
NewsReaderLeafArticle(ent, 0);
|
||||
|
||||
if (articles.Count == 0)
|
||||
{
|
||||
_cartridgeLoaderSystem.UpdateCartridgeUiState(loaderUid, new NewsReaderEmptyBoundUserInterfaceState(ent.Comp.NotificationOn));
|
||||
return;
|
||||
}
|
||||
|
||||
var state = new NewsReaderBoundUserInterfaceState(
|
||||
articles[ent.Comp.ArticleNumber],
|
||||
ent.Comp.ArticleNumber + 1,
|
||||
articles.Count,
|
||||
ent.Comp.NotificationOn);
|
||||
|
||||
_cartridgeLoaderSystem.UpdateCartridgeUiState(loaderUid, state);
|
||||
}
|
||||
|
||||
private void NewsReaderLeafArticle(Entity<NewsReaderCartridgeComponent> ent, int leafDir)
|
||||
{
|
||||
if (!TryGetArticles(ent, out var articles))
|
||||
return;
|
||||
|
||||
ent.Comp.ArticleNumber += leafDir;
|
||||
|
||||
if (ent.Comp.ArticleNumber >= articles.Count)
|
||||
ent.Comp.ArticleNumber = 0;
|
||||
|
||||
if (ent.Comp.ArticleNumber < 0)
|
||||
ent.Comp.ArticleNumber = articles.Count - 1;
|
||||
}
|
||||
|
||||
private void UpdateWriterDevices()
|
||||
{
|
||||
var query = EntityQueryEnumerator<NewsWriterComponent>();
|
||||
while (query.MoveNext(out var owner, out var comp))
|
||||
{
|
||||
UpdateWriterUi((owner, comp));
|
||||
}
|
||||
}
|
||||
|
||||
private bool CheckDeleteAccess(NewsArticle articleToDelete, EntityUid device, EntityUid user)
|
||||
{
|
||||
if (TryComp<AccessReaderComponent>(device, out var accessReader) &&
|
||||
_accessReader.IsAllowed(user, device, accessReader))
|
||||
return true;
|
||||
|
||||
if (articleToDelete.AuthorStationRecordKeyIds == null || articleToDelete.AuthorStationRecordKeyIds.Count == 0)
|
||||
return true;
|
||||
|
||||
return _accessReader.FindStationRecordKeys(user, out var recordKeys)
|
||||
&& StationRecordsToNetEntities(recordKeys).Intersect(articleToDelete.AuthorStationRecordKeyIds).Any();
|
||||
}
|
||||
|
||||
private ICollection<(NetEntity, uint)> StationRecordsToNetEntities(IEnumerable<StationRecordKey> records)
|
||||
{
|
||||
return records.Select(record => (GetNetEntity(record.OriginStation), record.Id)).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Server.AlertLevel;
|
||||
using Content.Server.CartridgeLoader;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.DeviceNetwork.Components;
|
||||
using Content.Server.Instruments;
|
||||
using Content.Server.Light.EntitySystems;
|
||||
@@ -10,10 +11,14 @@ using Content.Server.Store.Components;
|
||||
using Content.Server.Store.Systems;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.CartridgeLoader;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.PDA;
|
||||
using Robust.Server.Containers;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.PDA
|
||||
{
|
||||
@@ -24,8 +29,10 @@ namespace Content.Server.PDA
|
||||
[Dependency] private readonly RingerSystem _ringer = default!;
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
[Dependency] private readonly StoreSystem _store = default!;
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _ui = default!;
|
||||
[Dependency] private readonly UnpoweredFlashlightSystem _unpoweredFlashlight = default!;
|
||||
[Dependency] private readonly ContainerSystem _containerSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -41,6 +48,8 @@ namespace Content.Server.PDA
|
||||
SubscribeLocalEvent<PdaComponent, PdaShowUplinkMessage>(OnUiMessage);
|
||||
SubscribeLocalEvent<PdaComponent, PdaLockUplinkMessage>(OnUiMessage);
|
||||
|
||||
SubscribeLocalEvent<PdaComponent, CartridgeLoaderNotificationSentEvent>(OnNotification);
|
||||
|
||||
SubscribeLocalEvent<StationRenamedEvent>(OnStationRenamed);
|
||||
SubscribeLocalEvent<AlertLevelChangedEvent>(OnAlertLevelChanged);
|
||||
}
|
||||
@@ -106,6 +115,28 @@ namespace Content.Server.PDA
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNotification(Entity<PdaComponent> ent, ref CartridgeLoaderNotificationSentEvent args)
|
||||
{
|
||||
_ringer.RingerPlayRingtone(ent.Owner);
|
||||
|
||||
if (!_containerSystem.TryGetContainingContainer(ent, out var container)
|
||||
|| !TryComp<ActorComponent>(container.Owner, out var actor))
|
||||
return;
|
||||
|
||||
var message = FormattedMessage.EscapeText(args.Message);
|
||||
var wrappedMessage = Loc.GetString("pda-notification-message",
|
||||
("header", args.Header),
|
||||
("message", message));
|
||||
|
||||
_chatManager.ChatMessageToOne(
|
||||
ChatChannel.Notifications,
|
||||
message,
|
||||
wrappedMessage,
|
||||
EntityUid.Invalid,
|
||||
false,
|
||||
actor.PlayerSession.Channel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send new UI state to clients, call if you modify something like uplink.
|
||||
/// </summary>
|
||||
|
||||
@@ -63,13 +63,16 @@ namespace Content.Server.PDA.Ringer
|
||||
UpdateRingerUserInterface(uid, ringer, true);
|
||||
}
|
||||
|
||||
public void RingerPlayRingtone(EntityUid uid, RingerComponent ringer)
|
||||
public void RingerPlayRingtone(Entity<RingerComponent?> ent)
|
||||
{
|
||||
EnsureComp<ActiveRingerComponent>(uid);
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return;
|
||||
|
||||
_popupSystem.PopupEntity(Loc.GetString("comp-ringer-vibration-popup"), uid, Filter.Pvs(uid, 0.05f), false, PopupType.Small);
|
||||
EnsureComp<ActiveRingerComponent>(ent);
|
||||
|
||||
UpdateRingerUserInterface(uid, ringer, true);
|
||||
_popupSystem.PopupEntity(Loc.GetString("comp-ringer-vibration-popup"), ent, Filter.Pvs(ent, 0.05f), false, PopupType.Medium);
|
||||
|
||||
UpdateRingerUserInterface(ent, ent.Comp, true);
|
||||
}
|
||||
|
||||
private void UpdateRingerUserInterfaceDriver(EntityUid uid, RingerComponent ringer, RingerRequestUpdateInterfaceMessage args)
|
||||
|
||||
@@ -10,6 +10,9 @@ namespace Content.Shared.CartridgeLoader;
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class CartridgeComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public EntityUid? LoaderUid;
|
||||
|
||||
[DataField(required: true)]
|
||||
public LocId ProgramName = "default-program-name";
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ public sealed partial class CartridgeLoaderComponent : Component
|
||||
{
|
||||
public const string CartridgeSlotId = "Cartridge-Slot";
|
||||
|
||||
[DataField("cartridgeSlot")]
|
||||
[DataField]
|
||||
public ItemSlot CartridgeSlot = new();
|
||||
|
||||
/// <summary>
|
||||
@@ -32,9 +32,16 @@ public sealed partial class CartridgeLoaderComponent : Component
|
||||
/// <summary>
|
||||
/// The maximum amount of programs that can be installed on the cartridge loader entity
|
||||
/// </summary>
|
||||
[DataField("diskSpace")]
|
||||
[DataField]
|
||||
public int DiskSpace = 5;
|
||||
|
||||
[DataField("uiKey", required: true)]
|
||||
/// <summary>
|
||||
/// Controls whether the cartridge loader will play notifications if it supports it at all
|
||||
/// TODO: Add an option for this to the PDA
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool NotificationsEnabled = true;
|
||||
|
||||
[DataField(required: true)]
|
||||
public Enum UiKey = default!;
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.CartridgeLoader.Cartridges;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class NewsReadUiMessageEvent : CartridgeMessageEvent
|
||||
{
|
||||
public readonly NewsReadUiAction Action;
|
||||
|
||||
public NewsReadUiMessageEvent(NewsReadUiAction action)
|
||||
{
|
||||
Action = action;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum NewsReadUiAction
|
||||
{
|
||||
Next,
|
||||
Prev,
|
||||
NotificationSwith
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.CartridgeLoader.Cartridges;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class NewsReaderUiMessageEvent : CartridgeMessageEvent
|
||||
{
|
||||
public readonly NewsReaderUiAction Action;
|
||||
|
||||
public NewsReaderUiMessageEvent(NewsReaderUiAction action)
|
||||
{
|
||||
Action = action;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum NewsReaderUiAction
|
||||
{
|
||||
Next,
|
||||
Prev,
|
||||
NotificationSwitch
|
||||
}
|
||||
@@ -4,14 +4,14 @@ using Content.Shared.MassMedia.Systems;
|
||||
namespace Content.Shared.CartridgeLoader.Cartridges;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class NewsReadBoundUserInterfaceState : BoundUserInterfaceState
|
||||
public sealed class NewsReaderBoundUserInterfaceState : BoundUserInterfaceState
|
||||
{
|
||||
public NewsArticle Article;
|
||||
public int TargetNum;
|
||||
public int TotalNum;
|
||||
public bool NotificationOn;
|
||||
|
||||
public NewsReadBoundUserInterfaceState(NewsArticle article, int targetNum, int totalNum, bool notificationOn)
|
||||
public NewsReaderBoundUserInterfaceState(NewsArticle article, int targetNum, int totalNum, bool notificationOn)
|
||||
{
|
||||
Article = article;
|
||||
TargetNum = targetNum;
|
||||
@@ -21,11 +21,11 @@ public sealed class NewsReadBoundUserInterfaceState : BoundUserInterfaceState
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class NewsReadEmptyBoundUserInterfaceState : BoundUserInterfaceState
|
||||
public sealed class NewsReaderEmptyBoundUserInterfaceState : BoundUserInterfaceState
|
||||
{
|
||||
public bool NotificationOn;
|
||||
|
||||
public NewsReadEmptyBoundUserInterfaceState(bool notificationOn)
|
||||
public NewsReaderEmptyBoundUserInterfaceState(bool notificationOn)
|
||||
{
|
||||
NotificationOn = notificationOn;
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Content.Shared.CartridgeLoader;
|
||||
|
||||
@@ -124,3 +123,11 @@ public sealed class CartridgeUiReadyEvent : EntityEventArgs
|
||||
Loader = loader;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets sent by the cartridge loader system to the cartridge loader entity so another system
|
||||
/// can handle displaying the notification
|
||||
/// </summary>
|
||||
/// <param name="Message">The message to be displayed</param>
|
||||
[ByRefEvent]
|
||||
public record struct CartridgeLoaderNotificationSentEvent(string Header, string Message);
|
||||
|
||||
@@ -49,40 +49,46 @@ namespace Content.Shared.Chat
|
||||
/// </summary>
|
||||
Visual = 1 << 7,
|
||||
|
||||
/// <summary>
|
||||
/// Notifications from things like the PDA.
|
||||
/// Receiving a PDA message will send a notification to this channel for example
|
||||
/// </summary>
|
||||
Notifications = 1 << 8,
|
||||
|
||||
/// <summary>
|
||||
/// Emotes
|
||||
/// </summary>
|
||||
Emotes = 1 << 8,
|
||||
Emotes = 1 << 9,
|
||||
|
||||
/// <summary>
|
||||
/// Deadchat
|
||||
/// </summary>
|
||||
Dead = 1 << 9,
|
||||
Dead = 1 << 10,
|
||||
|
||||
/// <summary>
|
||||
/// Misc admin messages
|
||||
/// </summary>
|
||||
Admin = 1 << 10,
|
||||
Admin = 1 << 11,
|
||||
|
||||
/// <summary>
|
||||
/// Admin alerts, messages likely of elevated importance to admins
|
||||
/// </summary>
|
||||
AdminAlert = 1 << 11,
|
||||
AdminAlert = 1 << 12,
|
||||
|
||||
/// <summary>
|
||||
/// Admin chat
|
||||
/// </summary>
|
||||
AdminChat = 1 << 12,
|
||||
AdminChat = 1 << 13,
|
||||
|
||||
/// <summary>
|
||||
/// Unspecified.
|
||||
/// </summary>
|
||||
Unspecified = 1 << 13,
|
||||
Unspecified = 1 << 14,
|
||||
|
||||
/// <summary>
|
||||
/// Channels considered to be IC.
|
||||
/// </summary>
|
||||
IC = Local | Whisper | Radio | Dead | Emotes | Damage | Visual,
|
||||
IC = Local | Whisper | Radio | Dead | Emotes | Damage | Visual | Notifications,
|
||||
|
||||
AdminRelated = Admin | AdminAlert | AdminChat,
|
||||
}
|
||||
|
||||
55
Content.Shared/MassMedia/Components/NewsWriterBuiMessages.cs
Normal file
55
Content.Shared/MassMedia/Components/NewsWriterBuiMessages.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using Content.Shared.MassMedia.Systems;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.MassMedia.Components;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum NewsWriterUiKey : byte
|
||||
{
|
||||
Key
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class NewsWriterBoundUserInterfaceState : BoundUserInterfaceState
|
||||
{
|
||||
public readonly NewsArticle[] Articles;
|
||||
public readonly bool PublishEnabled;
|
||||
public readonly TimeSpan NextPublish;
|
||||
|
||||
public NewsWriterBoundUserInterfaceState(NewsArticle[] articles, bool publishEnabled, TimeSpan nextPublish)
|
||||
{
|
||||
Articles = articles;
|
||||
PublishEnabled = publishEnabled;
|
||||
NextPublish = nextPublish;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class NewsWriterPublishMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public readonly string Title;
|
||||
public readonly string Content;
|
||||
|
||||
|
||||
public NewsWriterPublishMessage(string title, string content)
|
||||
{
|
||||
Title = title;
|
||||
Content = content;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class NewsWriterDeleteMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public readonly int ArticleNum;
|
||||
|
||||
public NewsWriterDeleteMessage(int num)
|
||||
{
|
||||
ArticleNum = num;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class NewsWriterArticlesRequestMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
using Content.Shared.MassMedia.Systems;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.MassMedia.Components;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum NewsWriteUiKey : byte
|
||||
{
|
||||
Key
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class NewsWriteBoundUserInterfaceState : BoundUserInterfaceState
|
||||
{
|
||||
public NewsArticle[] Articles;
|
||||
public bool ShareAvalible;
|
||||
|
||||
public NewsWriteBoundUserInterfaceState(NewsArticle[] articles, bool shareAvalible)
|
||||
{
|
||||
Articles = articles;
|
||||
ShareAvalible = shareAvalible;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class NewsWriteShareMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly string Content;
|
||||
public NewsWriteShareMessage(string name, string content)
|
||||
{
|
||||
Name = name;
|
||||
Content = content;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class NewsWriteDeleteMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public int ArticleNum;
|
||||
|
||||
public NewsWriteDeleteMessage(int num)
|
||||
{
|
||||
ArticleNum = num;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class NewsWriteArticlesRequestMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public NewsWriteArticlesRequestMessage()
|
||||
{
|
||||
}
|
||||
}
|
||||
10
Content.Shared/MassMedia/Components/StationNewsComponent.cs
Normal file
10
Content.Shared/MassMedia/Components/StationNewsComponent.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using Content.Shared.MassMedia.Systems;
|
||||
|
||||
namespace Content.Shared.MassMedia.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class StationNewsComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public List<NewsArticle> Articles = new();
|
||||
}
|
||||
@@ -4,16 +4,31 @@ namespace Content.Shared.MassMedia.Systems;
|
||||
|
||||
public abstract class SharedNewsSystem : EntitySystem
|
||||
{
|
||||
public const int MaxNameLength = 25;
|
||||
public const int MaxArticleLength = 2048;
|
||||
public const int MaxTitleLength = 25;
|
||||
public const int MaxContentLength = 2048;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public struct NewsArticle
|
||||
{
|
||||
public string Name;
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string Title;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string Content;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string? Author;
|
||||
|
||||
[ViewVariables]
|
||||
public ICollection<(NetEntity, uint)>? AuthorStationRecordKeyIds;
|
||||
|
||||
[ViewVariables]
|
||||
public TimeSpan ShareTime;
|
||||
}
|
||||
|
||||
[ByRefEvent]
|
||||
public record struct NewsArticlePublishedEvent(NewsArticle Article);
|
||||
|
||||
[ByRefEvent]
|
||||
public record struct NewsArticleDeletedEvent;
|
||||
|
||||
@@ -26,6 +26,7 @@ hud-chatbox-channel-Whisper = Whisper
|
||||
hud-chatbox-channel-LOOC = LOOC
|
||||
hud-chatbox-channel-OOC = OOC
|
||||
hud-chatbox-channel-Radio = Radio
|
||||
hud-chatbox-channel-Notifications = Notifications
|
||||
hud-chatbox-channel-Server = Server
|
||||
hud-chatbox-channel-Visual = Actions
|
||||
hud-chatbox-channel-Damage = Damage
|
||||
|
||||
@@ -12,3 +12,5 @@ generic-invalid = invalid
|
||||
generic-hours = hours
|
||||
|
||||
generic-playtime-title = Playtime
|
||||
|
||||
generic-confirm = Confirm
|
||||
|
||||
@@ -1,17 +1,37 @@
|
||||
news-read-ui-next-text = Next
|
||||
news-read-ui-past-text = Past
|
||||
news-read-ui-next-text = ▶
|
||||
news-read-ui-prev-text = ◀
|
||||
news-read-ui-next-tooltip = Next
|
||||
news-read-ui-prev-tooltip = Prev
|
||||
news-read-ui-default-title = Station News
|
||||
news-read-ui-not-found-text = No articles found
|
||||
news-read-ui-time-prefix-text = Publication time:
|
||||
news-reader-ui-mute-tooltip = Mute notifications
|
||||
news-read-ui-notification-off = ̶♫̶
|
||||
news-read-ui-notification-on = ♫
|
||||
news-read-ui-no-author = Anonymous
|
||||
news-read-ui-author-prefix = Author:
|
||||
news-write-ui-default-title = Mass-media Management
|
||||
news-read-ui-author-prefix = Author:
|
||||
news-write-ui-default-title = News Management
|
||||
news-write-ui-articles-label = Articles:
|
||||
news-write-ui-delete-text = Delete
|
||||
news-write-ui-share-text = Publish
|
||||
news-write-ui-publish-text = Publish
|
||||
news-write-ui-create-text = Create
|
||||
news-write-ui-cancel-text = Cancel
|
||||
news-write-ui-preview-text = Preview
|
||||
news-write-ui-article-count-0 = 0 Articles
|
||||
news-write-ui-article-count-text = {$count} Articles
|
||||
news-write-ui-footer-text = News#Manager™ Authoring System
|
||||
news-write-ui-new-article = New Article
|
||||
news-write-ui-article-name-label = Heading:
|
||||
news-write-ui-article-content-label = Content:
|
||||
news-write-no-access-popup = No access
|
||||
news-writer-text-length-exceeded = Text exceeds maximum length
|
||||
news-write-ui-richtext-tooltip = News articles support rich text
|
||||
The following rich text tags are supported:
|
||||
{"[color=Gray][bullet/]heading \\[size=1-3\\]"}
|
||||
{"[bullet/]bold"}
|
||||
{"[bullet/]italic"}
|
||||
{"[bullet/]bolditalic"}
|
||||
{"[bullet/]color"}
|
||||
{"[bullet/]bullet[/color]"}
|
||||
|
||||
news-pda-notification-header = New news article
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
# For the PDA Ringer screen
|
||||
|
||||
comp-ringer-vibration-popup = PDA vibrates
|
||||
comp-ringer-vibration-popup = Your PDA vibrates
|
||||
|
||||
comp-ringer-ui-menu-title = Ringtone
|
||||
|
||||
|
||||
@@ -51,3 +51,6 @@ pda-bound-user-interface-music-button-description = Play music on your PDA
|
||||
comp-pda-ui-unknown = Unknown
|
||||
|
||||
comp-pda-ui-unassigned = Unassigned
|
||||
|
||||
pda-notification-message = [font size=12][bold]PDA[/bold] { $header }: [/font]
|
||||
"{ $message }"
|
||||
|
||||
@@ -364,7 +364,7 @@
|
||||
- type: entity
|
||||
parent: BaseComputerCircuitboard
|
||||
id: ComputerMassMediaCircuitboard
|
||||
name: mass-media console board
|
||||
name: news manager console board
|
||||
description: Write your message to the world!
|
||||
components:
|
||||
- type: Sprite
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
- type: entity
|
||||
parent: BaseItem
|
||||
id: NewsReadCartridge
|
||||
id: NewsReaderCartridge
|
||||
name: news cartridge
|
||||
description: A program for reading news
|
||||
components:
|
||||
@@ -26,13 +26,13 @@
|
||||
sprite: Objects/Devices/cartridge.rsi
|
||||
state: cart-y
|
||||
- type: UIFragment
|
||||
ui: !type:NewsReadUi
|
||||
ui: !type:NewsReaderUi
|
||||
- type: Cartridge
|
||||
programName: news-read-program-name
|
||||
icon:
|
||||
sprite: Interface/Misc/program_icons.rsi
|
||||
state: news_read
|
||||
- type: NewsReadCartridge
|
||||
- type: NewsReaderCartridge
|
||||
|
||||
- type: entity
|
||||
parent: BaseItem
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
preinstalled:
|
||||
- CrewManifestCartridge
|
||||
- NotekeeperCartridge
|
||||
- NewsReadCartridge
|
||||
- NewsReaderCartridge
|
||||
cartridgeSlot:
|
||||
priority: -1
|
||||
name: device-pda-slot-component-slot-name-cartridge
|
||||
@@ -669,7 +669,7 @@
|
||||
preinstalled:
|
||||
- CrewManifestCartridge
|
||||
- NotekeeperCartridge
|
||||
- NewsReadCartridge
|
||||
- NewsReaderCartridge
|
||||
- LogProbeCartridge
|
||||
|
||||
- type: entity
|
||||
|
||||
@@ -109,6 +109,12 @@
|
||||
- type: SiliconLawProvider
|
||||
laws: Crewsimov
|
||||
|
||||
- type: entity
|
||||
id: BaseStationNews
|
||||
abstract: true
|
||||
components:
|
||||
- type: StationNews
|
||||
|
||||
- type: entity
|
||||
id: BaseStationAllEventsEligible
|
||||
abstract: true
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
id: StandardNanotrasenStation
|
||||
parent:
|
||||
- BaseStation
|
||||
- BaseStationNews
|
||||
- BaseStationCargo
|
||||
- BaseStationJobsSpawning
|
||||
- BaseStationRecords
|
||||
|
||||
@@ -984,7 +984,7 @@
|
||||
- type: entity
|
||||
parent: BaseComputer
|
||||
id: ComputerMassMedia
|
||||
name: mass-media console
|
||||
name: news manager console
|
||||
description: Write your message to the world!
|
||||
components:
|
||||
- type: Sprite
|
||||
@@ -1000,18 +1000,18 @@
|
||||
- type: Computer
|
||||
board: ComputerMassMediaCircuitboard
|
||||
- type: DeviceNetworkRequiresPower
|
||||
- type: NewsWrite
|
||||
- type: NewsWriter
|
||||
- type: AccessReader
|
||||
access: [[ "Command" ]]
|
||||
- type: ActivatableUI
|
||||
key: enum.NewsWriteUiKey.Key
|
||||
key: enum.NewsWriterUiKey.Key
|
||||
- type: ActivatableUIRequiresVision
|
||||
- type: Transform
|
||||
anchored: true
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
- key: enum.NewsWriteUiKey.Key
|
||||
type: NewsWriteBoundUserInterface
|
||||
- key: enum.NewsWriterUiKey.Key
|
||||
type: NewsWriterBoundUserInterface
|
||||
|
||||
- type: entity
|
||||
parent: BaseComputer
|
||||
|
||||
54
Resources/Textures/Interface/Nano/button_small.svg
Normal file
54
Resources/Textures/Interface/Nano/button_small.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 24 KiB |
BIN
Resources/Textures/Interface/Nano/button_small.svg.96dpi.png
Normal file
BIN
Resources/Textures/Interface/Nano/button_small.svg.96dpi.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 278 B |
@@ -372,18 +372,24 @@ binds:
|
||||
type: State
|
||||
key: Return
|
||||
canRepeat: true
|
||||
mod1: Shift
|
||||
- function: TextNewline
|
||||
type: State
|
||||
key: NumpadEnter
|
||||
canRepeat: true
|
||||
mod1: Shift
|
||||
- function: TextSubmit
|
||||
type: State
|
||||
key: Return
|
||||
- function: TextSubmit
|
||||
type: State
|
||||
key: NumpadEnter
|
||||
- function: MultilineTextSubmit
|
||||
type: State
|
||||
key: Return
|
||||
mod1: Control
|
||||
- function: MultilineTextSubmit
|
||||
type: State
|
||||
key: NumpadEnter
|
||||
mod1: Control
|
||||
- function: TextSelectAll
|
||||
type: State
|
||||
key: A
|
||||
|
||||
Reference in New Issue
Block a user