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 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
@ -16,18 +10,12 @@ namespace blazor_lab.Components
[Parameter]
public List<InventoryModel> Inventory { get; set; }
public List<Item> Items { get; set; } = new List<Item>();
[Inject]
public HttpClient HttpClient { get; set; }
[Inject]
public IConfiguration Config { get; set; }
protected override async Task OnInitializedAsync()
{
Items = await HttpClient.GetFromJsonAsync<List<Item>>($"{Config["CraftingApi:BaseUrl"]}/api/Crafting/all");
}
/// <summary>
/// Used by GetItemImageBase64 in this component, rather than calling our DataService every time.
/// A very basic cache, not kept up to date in any way, but event listeners could be set up in the future
/// </summary>
[Parameter]
public List<Models.Item> Items { get; set; }
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 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
{
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"
@using Models
<h3>@Localizer["Title"]</h3>
<h3>List</h3>
<div>
<NavLink class="btn btn-primary" href="add" Match="NavLinkMatch.All">

@ -15,9 +15,6 @@ namespace blazor_lab.Pages
private int totalItems;
[Inject]
public IStringLocalizer<List> Localizer { get; set; }
[Inject]
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") };
});
builder.Services.AddScoped<IDataService, DataApiService>();
builder.Services.AddScoped<DataApiService>();
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">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Title" xml:space="preserve">
<value>Items list</value>
<data name="list_of_items" xml:space="preserve">
<value>Liste des objets</value>
</data>
<data name="my_inventory" xml:space="preserve">
<value>Mon inventaire</value>
</data>
</root>

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

@ -28,6 +28,11 @@ namespace blazor_lab.Services
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)
{
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" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resources\Pages.Inventory.resx">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Update="Resources\Components.InventoryList.resx">
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
</Project>

Loading…
Cancel
Save