Uplink store interface searchable with a searchbar. (#24287)
* Can now search the uplink store interface with a searchbar. * Search text updates no longer send server messages. Persists listings locally. * Formatting fixes and tidying. * Added helper method to get localised name and description (or otherwise, entity name and description) of store listing items. * Update Content.Client/Store/Ui/StoreMenu.xaml * Review change; moved localisation helper functions to their own class. * Prevent thread-unsafe behaviour as-per review. * Remove dummy boxcontainer --------- Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
This commit is contained in:
@@ -1,22 +1,27 @@
|
||||
using Content.Shared.Store;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Serilog;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Store.Ui;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class StoreBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
private IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
[ViewVariables]
|
||||
private StoreMenu? _menu;
|
||||
|
||||
[ViewVariables]
|
||||
private string _windowName = Loc.GetString("store-ui-default-title");
|
||||
|
||||
[ViewVariables]
|
||||
private string _search = "";
|
||||
|
||||
[ViewVariables]
|
||||
private HashSet<ListingData> _listings = new();
|
||||
|
||||
public StoreBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
@@ -49,6 +54,12 @@ public sealed class StoreBoundUserInterface : BoundUserInterface
|
||||
SendMessage(new StoreRequestUpdateInterfaceMessage());
|
||||
};
|
||||
|
||||
_menu.SearchTextUpdated += (_, search) =>
|
||||
{
|
||||
_search = search.Trim().ToLowerInvariant();
|
||||
UpdateListingsWithSearchFilter();
|
||||
};
|
||||
|
||||
_menu.OnRefundAttempt += (_) =>
|
||||
{
|
||||
SendMessage(new StoreRequestRefundMessage());
|
||||
@@ -64,10 +75,10 @@ public sealed class StoreBoundUserInterface : BoundUserInterface
|
||||
switch (state)
|
||||
{
|
||||
case StoreUpdateState msg:
|
||||
_menu.UpdateBalance(msg.Balance);
|
||||
_menu.PopulateStoreCategoryButtons(msg.Listings);
|
||||
_listings = msg.Listings;
|
||||
|
||||
_menu.UpdateListing(msg.Listings.ToList());
|
||||
_menu.UpdateBalance(msg.Balance);
|
||||
UpdateListingsWithSearchFilter();
|
||||
_menu.SetFooterVisibility(msg.ShowFooter);
|
||||
_menu.UpdateRefund(msg.AllowRefund);
|
||||
break;
|
||||
@@ -89,4 +100,19 @@ public sealed class StoreBoundUserInterface : BoundUserInterface
|
||||
_menu?.Close();
|
||||
_menu?.Dispose();
|
||||
}
|
||||
|
||||
private void UpdateListingsWithSearchFilter()
|
||||
{
|
||||
if (_menu == null)
|
||||
return;
|
||||
|
||||
var filteredListings = new HashSet<ListingData>(_listings);
|
||||
if (!string.IsNullOrEmpty(_search))
|
||||
{
|
||||
filteredListings.RemoveWhere(listingData => !ListingLocalisationHelpers.GetLocalisedNameOrEntityName(listingData, _prototypeManager).Trim().ToLowerInvariant().Contains(_search) &&
|
||||
!ListingLocalisationHelpers.GetLocalisedDescriptionOrEntityDescription(listingData, _prototypeManager).Trim().ToLowerInvariant().Contains(_search));
|
||||
}
|
||||
_menu.PopulateStoreCategoryButtons(filteredListings);
|
||||
_menu.UpdateListing(filteredListings.ToList());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,8 @@
|
||||
HorizontalAlignment="Right"
|
||||
Text="Refund" />
|
||||
</BoxContainer>
|
||||
<PanelContainer VerticalExpand="True">
|
||||
<LineEdit Name="SearchBar" Margin="4" PlaceHolder="Search" HorizontalExpand="True"/>
|
||||
<PanelContainer VerticalExpand="True">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#000000FF" />
|
||||
</PanelContainer.PanelOverride>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Content.Client.Actions;
|
||||
using Content.Client.GameTicking.Managers;
|
||||
using Content.Client.Message;
|
||||
@@ -27,6 +26,7 @@ public sealed partial class StoreMenu : DefaultWindow
|
||||
|
||||
private StoreWithdrawWindow? _withdrawWindow;
|
||||
|
||||
public event EventHandler<string>? SearchTextUpdated;
|
||||
public event Action<BaseButton.ButtonEventArgs, ListingData>? OnListingButtonPressed;
|
||||
public event Action<BaseButton.ButtonEventArgs, string>? OnCategoryButtonPressed;
|
||||
public event Action<BaseButton.ButtonEventArgs, string, int>? OnWithdrawAttempt;
|
||||
@@ -46,6 +46,7 @@ public sealed partial class StoreMenu : DefaultWindow
|
||||
WithdrawButton.OnButtonDown += OnWithdrawButtonDown;
|
||||
RefreshButton.OnButtonDown += OnRefreshButtonDown;
|
||||
RefundButton.OnButtonDown += OnRefundButtonDown;
|
||||
SearchBar.OnTextChanged += _ => SearchTextUpdated?.Invoke(this, SearchBar.Text);
|
||||
|
||||
if (Window != null)
|
||||
Window.Title = name;
|
||||
@@ -59,7 +60,7 @@ public sealed partial class StoreMenu : DefaultWindow
|
||||
(type.Key, type.Value), type => _prototypeManager.Index<CurrencyPrototype>(type.Key));
|
||||
|
||||
var balanceStr = string.Empty;
|
||||
foreach (var ((type, amount),proto) in currency)
|
||||
foreach (var ((_, amount), proto) in currency)
|
||||
{
|
||||
balanceStr += Loc.GetString("store-ui-balance-display", ("amount", amount),
|
||||
("currency", Loc.GetString(proto.DisplayName, ("amount", 1))));
|
||||
@@ -81,7 +82,6 @@ public sealed partial class StoreMenu : DefaultWindow
|
||||
{
|
||||
var sorted = listings.OrderBy(l => l.Priority).ThenBy(l => l.Cost.Values.Sum());
|
||||
|
||||
|
||||
// should probably chunk these out instead. to-do if this clogs the internet tubes.
|
||||
// maybe read clients prototypes instead?
|
||||
ClearListings();
|
||||
@@ -129,8 +129,8 @@ public sealed partial class StoreMenu : DefaultWindow
|
||||
if (!listing.Categories.Contains(CurrentCategory))
|
||||
return;
|
||||
|
||||
var listingName = Loc.GetString(listing.Name);
|
||||
var listingDesc = Loc.GetString(listing.Description);
|
||||
var listingName = ListingLocalisationHelpers.GetLocalisedNameOrEntityName(listing, _prototypeManager);
|
||||
var listingDesc = ListingLocalisationHelpers.GetLocalisedDescriptionOrEntityDescription(listing, _prototypeManager);
|
||||
var listingPrice = listing.Cost;
|
||||
var canBuy = CanBuyListing(Balance, listingPrice);
|
||||
|
||||
@@ -144,12 +144,6 @@ public sealed partial class StoreMenu : DefaultWindow
|
||||
{
|
||||
if (texture == null)
|
||||
texture = spriteSys.GetPrototypeIcon(listing.ProductEntity).Default;
|
||||
|
||||
var proto = _prototypeManager.Index<EntityPrototype>(listing.ProductEntity);
|
||||
if (listingName == string.Empty)
|
||||
listingName = proto.Name;
|
||||
if (listingDesc == string.Empty)
|
||||
listingDesc = proto.Description;
|
||||
}
|
||||
else if (listing.ProductAction != null)
|
||||
{
|
||||
@@ -243,13 +237,16 @@ public sealed partial class StoreMenu : DefaultWindow
|
||||
|
||||
allCategories = allCategories.OrderBy(c => c.Priority).ToList();
|
||||
|
||||
// This will reset the Current Category selection if nothing matches the search.
|
||||
if (allCategories.All(category => category.ID != CurrentCategory))
|
||||
CurrentCategory = string.Empty;
|
||||
|
||||
if (CurrentCategory == string.Empty && allCategories.Count > 0)
|
||||
CurrentCategory = allCategories.First().ID;
|
||||
|
||||
if (allCategories.Count <= 1)
|
||||
return;
|
||||
|
||||
CategoryListContainer.Children.Clear();
|
||||
if (allCategories.Count < 1)
|
||||
return;
|
||||
|
||||
foreach (var proto in allCategories)
|
||||
{
|
||||
|
||||
@@ -15,6 +15,7 @@ using Content.Shared.UserInterface;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Store.Systems;
|
||||
|
||||
@@ -29,6 +30,7 @@ public sealed partial class StoreSystem
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly StackSystem _stack = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _ui = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
private void InitializeUi()
|
||||
{
|
||||
@@ -259,7 +261,7 @@ public sealed partial class StoreSystem
|
||||
|
||||
//log dat shit.
|
||||
_admin.Add(LogType.StorePurchase, LogImpact.Low,
|
||||
$"{ToPrettyString(buyer):player} purchased listing \"{Loc.GetString(listing.Name)}\" from {ToPrettyString(uid)}");
|
||||
$"{ToPrettyString(buyer):player} purchased listing \"{ListingLocalisationHelpers.GetLocalisedNameOrEntityName(listing, _prototypeManager)}\" from {ToPrettyString(uid)}");
|
||||
|
||||
listing.PurchaseAmount++; //track how many times something has been purchased
|
||||
_audio.PlayEntity(component.BuySuccessSound, msg.Session, uid); //cha-ching!
|
||||
|
||||
42
Content.Shared/Store/ListingLocalisationHelpers.cs
Normal file
42
Content.Shared/Store/ListingLocalisationHelpers.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Store;
|
||||
|
||||
public static class ListingLocalisationHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// ListingData's Name field can be either a localisation string or the actual entity's name.
|
||||
/// This function gets a localised name from the localisation string if it exists, and if not, it gets the entity's name.
|
||||
/// If neither a localised string exists, or an associated entity name, it will return the value of the "Name" field.
|
||||
/// </summary>
|
||||
public static string GetLocalisedNameOrEntityName(ListingData listingData, IPrototypeManager prototypeManager)
|
||||
{
|
||||
bool wasLocalised = Loc.TryGetString(listingData.Name, out string? listingName);
|
||||
|
||||
if (!wasLocalised && listingData.ProductEntity != null)
|
||||
{
|
||||
var proto = prototypeManager.Index<EntityPrototype>(listingData.ProductEntity);
|
||||
listingName = proto.Name;
|
||||
}
|
||||
|
||||
return listingName ?? listingData.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ListingData's Description field can be either a localisation string or the actual entity's description.
|
||||
/// This function gets a localised description from the localisation string if it exists, and if not, it gets the entity's description.
|
||||
/// If neither a localised string exists, or an associated entity description, it will return the value of the "Description" field.
|
||||
/// </summary>
|
||||
public static string GetLocalisedDescriptionOrEntityDescription(ListingData listingData, IPrototypeManager prototypeManager)
|
||||
{
|
||||
bool wasLocalised = Loc.TryGetString(listingData.Description, out string? listingDesc);
|
||||
|
||||
if (!wasLocalised && listingData.ProductEntity != null)
|
||||
{
|
||||
var proto = prototypeManager.Index<EntityPrototype>(listingData.ProductEntity);
|
||||
listingDesc = proto.Description;
|
||||
}
|
||||
|
||||
return listingDesc ?? listingData.Description;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user