]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Uplink store interface searchable with a searchbar. (#24287)
authorJ. Brown <DrMelon@users.noreply.github.com>
Sun, 31 Mar 2024 04:09:15 +0000 (05:09 +0100)
committerGitHub <noreply@github.com>
Sun, 31 Mar 2024 04:09:15 +0000 (15:09 +1100)
* 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>
Content.Client/Store/Ui/StoreBoundUserInterface.cs
Content.Client/Store/Ui/StoreMenu.xaml
Content.Client/Store/Ui/StoreMenu.xaml.cs
Content.Server/Store/Systems/StoreSystem.Ui.cs
Content.Shared/Store/ListingLocalisationHelpers.cs [new file with mode: 0644]

index b549918d7c41b9fb7baa7f2b695d81c342b59d86..f87b92bc615ef9940eb07e3563cf9b26f888559d 100644 (file)
@@ -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());
+    }
 }
index 4b38352a44adffd1d3582632c1b2cc28caef5920..fc4cbe444fc573522dfd3183dd37f982592b67d5 100644 (file)
@@ -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>
index 5dc1ab246bd19208ee78c394d0bcafcbececfaa3..67e5d360a3a27f8bea8b420da35e7f26e8e4407c 100644 (file)
@@ -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)
         {
index 49db980451ea6b30a967074f90ce8c18f5df5a89..e6c4eb0ccea71fffcef63013ae6800da82d5b862 100644 (file)
@@ -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!
diff --git a/Content.Shared/Store/ListingLocalisationHelpers.cs b/Content.Shared/Store/ListingLocalisationHelpers.cs
new file mode 100644 (file)
index 0000000..3ac75cd
--- /dev/null
@@ -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;
+    }
+}