Implement InventoryList #2

Merged
alexis.drai merged 4 commits from feat/implement-list into main 2 years ago

@ -1,11 +1,5 @@
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Minecraft.Crafting.Api.Models; using Minecraft.Crafting.Api.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
namespace blazor_lab.Components namespace blazor_lab.Components
@ -16,18 +10,12 @@ namespace blazor_lab.Components
[Parameter] [Parameter]
public List<InventoryModel> Inventory { get; set; } public List<InventoryModel> Inventory { get; set; }
public List<Item> Items { get; set; } = new List<Item>(); /// <summary>
/// Used by GetItemImageBase64 in this component, rather than calling our DataService every time.
[Inject] /// A very basic cache, not kept up to date in any way, but event listeners could be set up in the future
public HttpClient HttpClient { get; set; } /// </summary>
[Parameter]
[Inject] public List<Models.Item> Items { get; set; }
public IConfiguration Config { get; set; }
protected override async Task OnInitializedAsync()
{
Items = await HttpClient.GetFromJsonAsync<List<Item>>($"{Config["CraftingApi:BaseUrl"]}/api/Crafting/all");
}
public string GetItemImageBase64(string displayName) public string GetItemImageBase64(string displayName)
{ {

@ -0,0 +1,35 @@
<div class="inventory-list">
<div class="search-container">
<input type="text" value="@searchQuery" @oninput="OnInputChange" placeholder="@Localizer["search_label"]" />
</div>
<div class="inventory-list-items">
@foreach (var item in VisibleItems)
{
<div class="inventory-list-item side-by-side">
<img src="@($"data:image/png;base64,{item.ImageBase64}")" alt="@item.DisplayName" />
<div class="item-name">@item.DisplayName</div>
</div>
}
</div>
<div class="pagination-container">
@for (var i = 1; i <= TotalPages; i++)
{
var pageNumber = i; // copy the loop variable to avoid closure issues
<button @onclick="() => GoToPage(pageNumber)">@pageNumber</button>
}
</div>
<div class="item-count">
@if (VisibleItems.Any())
{
var firstItem = (currentPage - 1) * pageSize + 1;
var lastItem = Math.Min(currentPage * pageSize, TotalItems);
<span>@firstItem - @lastItem @Localizer["out_of"] @TotalItems</span>
}
else
{
<span>@Localizer["no_items_found"]</span>
}
</div>
</div>

@ -0,0 +1,71 @@
using blazor_lab.Models;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;
namespace blazor_lab.Components
{
public partial class InventoryList
{
[Inject]
public IStringLocalizer<InventoryList> Localizer { get; set; }
[Parameter]
public List<Item> Items { get; set; }
private List<Item> _filteredItems;
private string searchQuery = "";
private int currentPage = 1;
private int pageSize = 10;
private void UpdateFilteredItems()
{
_filteredItems = string.IsNullOrEmpty(searchQuery) ? Items : Items.Where(i => i.DisplayName.ToLower().Contains(searchQuery.ToLower())).ToList();
VisibleItems = _filteredItems.Skip((currentPage - 1) * pageSize).Take(pageSize).ToList();
}
private List<Item> _visibleItems;
private List<Item> VisibleItems
{
get => _visibleItems;
set
{
_visibleItems = value;
StateHasChanged();
}
}
private int TotalPages => (int)Math.Ceiling((double)_filteredItems.Count / pageSize);
private int TotalItems => _filteredItems.Count;
private void GoToPage(int page)
{
currentPage = page;
UpdateFilteredItems();
}
protected override void OnParametersSet()
{
UpdateFilteredItems();
}
private async Task OnInputChange(ChangeEventArgs e)
{
searchQuery = e.Value.ToString();
await Task.Delay(250); // debounce the search to avoid excessive API requests
UpdateFilteredItems();
}
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
UpdateFilteredItems();
}
}
}
}

@ -0,0 +1,35 @@
.inventory-list {
margin-top: 20px;
}
.search-container {
margin-bottom: 20px;
}
.inventory-list-item {
margin-bottom: 10px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}
.inventory-list-item img {
max-width: 64px;
max-height: 64px;
margin-right: 10px;
}
.item-name {
font-weight: bold;
}
.side-by-side {
display: flex;
flex-direction: row;
}
button[disabled] {
opacity: 0.5;
cursor: default;
}

@ -2,6 +2,14 @@
@using Minecraft.Crafting.Api.Models @using Minecraft.Crafting.Api.Models
@using blazor_lab.Components @using blazor_lab.Components
<h1>Inventory</h1> <div class="side-by-side">
<div>
<h2>@Localizer["my_inventory"]</h2>
<InventoryGrid Inventory="Stuff" /> <InventoryGrid Inventory="FreshInventory" Items="Items" />
</div>
<div>
<h2>@Localizer["list_of_items"]</h2>
<InventoryList Items="Items" />
</div>
</div>

@ -1,9 +1,26 @@
using Minecraft.Crafting.Api.Models; using blazor_lab.Services;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;
using Minecraft.Crafting.Api.Models;
using System.Diagnostics;
namespace blazor_lab.Pages namespace blazor_lab.Pages
{ {
public partial class Inventory public partial class Inventory
{ {
private List<InventoryModel> Stuff = Enumerable.Range(1, 18).Select(_ => new InventoryModel()).ToList(); private List<Models.Item> Items = new();
[Inject]
public IStringLocalizer<Inventory> Localizer { get; set; }
[Inject]
private DataApiService DataApiService { get; set; }
protected override async Task OnInitializedAsync()
{
Items = await DataApiService.All();
}
private List<InventoryModel> FreshInventory = Enumerable.Range(1, 18).Select(_ => new InventoryModel()).ToList();
} }
} }

@ -0,0 +1,4 @@
.side-by-side {
display: flex;
flex-direction: row;
}

@ -1,7 +1,7 @@
@page "/list" @page "/list"
@using Models @using Models
<h3>@Localizer["Title"]</h3> <h3>List</h3>
<div> <div>
<NavLink class="btn btn-primary" href="add" Match="NavLinkMatch.All"> <NavLink class="btn btn-primary" href="add" Match="NavLinkMatch.All">

@ -15,9 +15,6 @@ namespace blazor_lab.Pages
private int totalItems; private int totalItems;
[Inject]
public IStringLocalizer<List> Localizer { get; set; }
[Inject] [Inject]
public IDataService DataService { get; set; } public IDataService DataService { get; set; }

@ -43,7 +43,7 @@ builder.Services.Configure<RequestLocalizationOptions>(options =>
options.SupportedUICultures = new List<CultureInfo> { new CultureInfo("en-US"), new CultureInfo("fr-FR") }; options.SupportedUICultures = new List<CultureInfo> { new CultureInfo("en-US"), new CultureInfo("fr-FR") };
}); });
builder.Services.AddScoped<IDataService, DataApiService>(); builder.Services.AddScoped<DataApiService>();
var app = builder.Build(); var app = builder.Build();

@ -0,0 +1,129 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="no_items_found" xml:space="preserve">
<value>Pas d'objet trouvé</value>
</data>
<data name="out_of" xml:space="preserve">
<value>de</value>
</data>
<data name="search_label" xml:space="preserve">
<value>Rechercher</value>
</data>
</root>

@ -0,0 +1,129 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="no_items_found" xml:space="preserve">
<value>No items found</value>
</data>
<data name="out_of" xml:space="preserve">
<value>out of</value>
</data>
<data name="search_label" xml:space="preserve">
<value>Search</value>
</data>
</root>

@ -117,7 +117,10 @@
<resheader name="writer"> <resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<data name="Title" xml:space="preserve"> <data name="list_of_items" xml:space="preserve">
<value>Items list</value> <value>Liste des objets</value>
</data>
<data name="my_inventory" xml:space="preserve">
<value>Mon inventaire</value>
</data> </data>
</root> </root>

@ -117,7 +117,10 @@
<resheader name="writer"> <resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<data name="Title" xml:space="preserve"> <data name="list_of_items" xml:space="preserve">
<value>Liste des éléments</value> <value>List of items</value>
</data>
<data name="my_inventory" xml:space="preserve">
<value>My inventory</value>
</data> </data>
</root> </root>

@ -28,6 +28,11 @@ namespace blazor_lab.Services
return await _http.GetFromJsonAsync<int>("https://localhost:7234/api/Crafting/count"); return await _http.GetFromJsonAsync<int>("https://localhost:7234/api/Crafting/count");
} }
public async Task<List<Item>> All()
{
return await _http.GetFromJsonAsync<List<Item>>($"https://localhost:7234/api/Crafting/all");
}
public async Task<List<Item>> List(int currentPage, int pageSize) public async Task<List<Item>> List(int currentPage, int pageSize)
{ {
return await _http.GetFromJsonAsync<List<Item>>($"https://localhost:7234/api/Crafting/?currentPage={currentPage}&pageSize={pageSize}"); return await _http.GetFromJsonAsync<List<Item>>($"https://localhost:7234/api/Crafting/?currentPage={currentPage}&pageSize={pageSize}");

@ -22,4 +22,13 @@
<ProjectReference Include="..\Minecraft.Crafting.Api\Minecraft.Crafting.Api.csproj" /> <ProjectReference Include="..\Minecraft.Crafting.Api\Minecraft.Crafting.Api.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resources\Pages.Inventory.resx">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Update="Resources\Components.InventoryList.resx">
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
</Project> </Project>

Loading…
Cancel
Save