main
Ismail TAHA JANAN 2 years ago
commit 04e0381ccd

BIN
.DS_Store vendored

Binary file not shown.

429
.gitignore vendored

@ -0,0 +1,429 @@
# Created by https://www.toptal.com/developers/gitignore/api/dotnetcore,visualstudio,visualstudiocode
# Edit at https://www.toptal.com/developers/gitignore?templates=dotnetcore,visualstudio,visualstudiocode
*.png
### DotnetCore ###
# .NET Core build folders
bin/
obj/
# Common node modules locations
/node_modules
/wwwroot/node_modules
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
### VisualStudio ###
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
*.code-workspace
# Local History for Visual Studio Code
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
# JetBrains Rider
*.sln.iml
### VisualStudio Patch ###
# Additional files built by Visual Studio
# End of https://www.toptal.com/developers/gitignore/api/dotnetcore,visualstudio,visualstudiocode

Binary file not shown.

@ -0,0 +1,453 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="CraftingController.cs" company="UCA Clermont-Ferrand">
// Copyright (c) UCA Clermont-Ferrand All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace Minecraft.Crafting.Api.Controllers
{
using Microsoft.AspNetCore.Mvc;
using Minecraft.Crafting.Api.Models;
using System.Text.Json;
using System.Text.Json.Serialization;
/// <summary>
/// The crafting controller.
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class CraftingController : ControllerBase
{
/// <summary>
/// The json serializer options.
/// </summary>
private readonly JsonSerializerOptions _jsonSerializerOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault
};
/// <summary>
/// Adds the specified item.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>The async task.</returns>
[HttpPost]
[Route("")]
public Task Add(Item item)
{
var data = JsonSerializer.Deserialize<List<Item>>(System.IO.File.ReadAllText("Data/items.json"), _jsonSerializerOptions);
if (data == null)
{
throw new Exception("Unable to get the items.");
}
// Simulate the Id
item.Id = data.Max(s => s.Id) + 1;
data.Add(item);
System.IO.File.WriteAllText("Data/items.json", JsonSerializer.Serialize(data, _jsonSerializerOptions));
return Task.CompletedTask;
}
/// <summary>
/// Get all items.
/// </summary>
/// <returns>All items.</returns>
[HttpGet]
[Route("all")]
public Task<List<Item>> All()
{
var data = JsonSerializer.Deserialize<List<Item>>(System.IO.File.ReadAllText("Data/items.json"), _jsonSerializerOptions);
if (data == null)
{
throw new Exception("Unable to get the items.");
}
return Task.FromResult(data.ToList());
}
/// <summary>
/// Count the number of items.
/// </summary>
/// <returns>The number of items.</returns>
[HttpGet]
[Route("count")]
public Task<int> Count()
{
var data = JsonSerializer.Deserialize<List<Item>>(System.IO.File.ReadAllText("Data/items.json"), _jsonSerializerOptions);
if (data == null)
{
throw new Exception("Unable to get the items.");
}
return Task.FromResult(data.Count);
}
/// <summary>
/// Deletes the specified identifier.
/// </summary>
/// <param name="id">The identifier.</param>
/// <returns>The async task.</returns>
[HttpDelete]
[Route("{id}")]
public Task Delete(int id)
{
var data = JsonSerializer.Deserialize<List<Item>>(System.IO.File.ReadAllText("Data/items.json"), _jsonSerializerOptions);
if (data == null)
{
throw new Exception("Unable to get the items.");
}
var item = data.FirstOrDefault(w => w.Id == id);
if (item == null)
{
throw new Exception($"Unable to found the item with ID: {id}");
}
data.Remove(item);
System.IO.File.WriteAllText("Data/items.json", JsonSerializer.Serialize(data, _jsonSerializerOptions));
return Task.CompletedTask;
}
/// <summary>
/// Gets the item by identifier.
/// </summary>
/// <param name="id">The identifier.</param>
/// <returns>The item.</returns>
[HttpGet]
[Route("{id}")]
public Task<Item> GetById(int id)
{
var data = JsonSerializer.Deserialize<List<Item>>(System.IO.File.ReadAllText("Data/items.json"), _jsonSerializerOptions);
if (data == null)
{
throw new Exception("Unable to get the items.");
}
var item = data.FirstOrDefault(w => w.Id == id);
if (item == null)
{
throw new Exception($"Unable to found the item with ID: {id}");
}
return Task.FromResult(item);
}
/// <summary>
/// Gets the item by name.
/// </summary>
/// <param name="name">The name.</param>
/// <returns>
/// The item.
/// </returns>
[HttpGet]
[Route("by-name/{name}")]
public Task<Item> GetByName(string name)
{
var data = JsonSerializer.Deserialize<List<Item>>(System.IO.File.ReadAllText("Data/items.json"), _jsonSerializerOptions);
if (data == null)
{
throw new Exception("Unable to get the items.");
}
var item = data.FirstOrDefault(w => w.Name.ToLowerInvariant() == name.ToLowerInvariant());
if (item == null)
{
throw new Exception($"Unable to found the item with name: {name}");
}
return Task.FromResult(item);
}
/// <summary>
/// Gets the recipes.
/// </summary>
/// <returns>The recipes.</returns>
[HttpGet]
[Route("recipe")]
public Task<List<Recipe>> GetRecipe()
{
if (!System.IO.File.Exists("Data/convert-recipes.json"))
{
ResetRecipes();
}
var data = JsonSerializer.Deserialize<List<Recipe>>(System.IO.File.ReadAllText("Data/convert-recipes.json"), _jsonSerializerOptions);
if (data == null)
{
throw new Exception("Unable to get the recipes.");
}
return Task.FromResult(data);
}
/// <summary>
/// Get the items with pagination.
/// </summary>
/// <param name="currentPage">The current page.</param>
/// <param name="pageSize">Size of the page.</param>
/// <returns>The items.</returns>
[HttpGet]
[Route("")]
public Task<List<Item>> List(int currentPage, int pageSize)
{
var data = JsonSerializer.Deserialize<List<Item>>(System.IO.File.ReadAllText("Data/items.json"), _jsonSerializerOptions);
if (data == null)
{
throw new Exception("Unable to get the items.");
}
return Task.FromResult(data.Skip((currentPage - 1) * pageSize).Take(pageSize).ToList());
}
/// <summary>
/// Resets the items.
/// </summary>
/// <returns>The async task.</returns>
[HttpGet]
[Route("reset-items")]
public Task ResetItems()
{
if (!System.IO.File.Exists("Data/items.json"))
{
System.IO.File.Delete("Data/items.json");
}
var data = JsonSerializer.Deserialize<List<Item>>(System.IO.File.ReadAllText("Data/items-original.json"), _jsonSerializerOptions);
if (data == null)
{
throw new Exception("Unable to get the items.");
}
var defaultImage = Convert.ToBase64String(System.IO.File.ReadAllBytes("Images/default.png"));
var imageTranslation = new Dictionary<string, string>
{
{ "stone_slab", "smooth_stone_slab_side" },
{ "sticky_piston", "piston_top_sticky" },
{ "mob_spawner", "spawner" },
{ "chest", "chest_minecart" },
{ "stone_stairs", "stairs" },
};
foreach (var item in data)
{
var imageFilepath = defaultImage;
if (System.IO.File.Exists($"Images/{item.Name}.png"))
{
imageFilepath = Convert.ToBase64String(System.IO.File.ReadAllBytes($"Images/{item.Name}.png"));
}
if (imageFilepath == defaultImage && System.IO.File.Exists($"Images/{item.Name}_top.png"))
{
imageFilepath = Convert.ToBase64String(System.IO.File.ReadAllBytes($"Images/{item.Name}_top.png"));
}
if (imageFilepath == defaultImage && System.IO.File.Exists($"Images/{item.Name}_front.png"))
{
imageFilepath = Convert.ToBase64String(System.IO.File.ReadAllBytes($"Images/{item.Name}_front.png"));
}
if (imageFilepath == defaultImage && System.IO.File.Exists($"Images/white_{item.Name}.png"))
{
imageFilepath = Convert.ToBase64String(System.IO.File.ReadAllBytes($"Images/white_{item.Name}.png"));
}
if (imageFilepath == defaultImage && System.IO.File.Exists($"Images/oak_{item.Name}.png"))
{
imageFilepath = Convert.ToBase64String(System.IO.File.ReadAllBytes($"Images/oak_{item.Name}.png"));
}
if (imageFilepath == defaultImage && System.IO.File.Exists($"Images/{item.DisplayName.ToLower().Replace(" ", "_")}.png"))
{
imageFilepath = Convert.ToBase64String(System.IO.File.ReadAllBytes($"Images/{item.DisplayName.ToLower().Replace(" ", "_")}.png"));
}
if (imageFilepath == defaultImage && imageTranslation.ContainsKey(item.Name))
{
imageFilepath = Convert.ToBase64String(System.IO.File.ReadAllBytes($"Images/{imageTranslation[item.Name]}.png"));
}
item.ImageBase64 = imageFilepath;
}
System.IO.File.WriteAllText("Data/items.json", JsonSerializer.Serialize(data, _jsonSerializerOptions));
return Task.FromResult(data);
}
/// <summary>
/// Resets the recipes.
/// </summary>
/// <returns>The async task.</returns>
[HttpGet]
[Route("reset-recipes")]
public Task ResetRecipes()
{
if (!System.IO.File.Exists("Data/convert-recipes.json"))
{
System.IO.File.Delete("Data/convert-recipes.json");
}
ConvertRecipes();
return Task.CompletedTask;
}
/// <summary>
/// Updates the specified identifier.
/// </summary>
/// <param name="id">The identifier.</param>
/// <param name="item">The item.</param>
/// <returns>The async task.</returns>
[HttpPut]
[Route("{id}")]
public Task Update(int id, Item item)
{
var data = JsonSerializer.Deserialize<List<Item>>(System.IO.File.ReadAllText("Data/items.json"), _jsonSerializerOptions);
var itemOriginal = data?.FirstOrDefault(w => w.Id == id);
if (itemOriginal == null)
{
throw new Exception($"Unable to found the item with ID: {id}");
}
itemOriginal.Id = item.Id;
itemOriginal.Name = item.Name;
itemOriginal.CreatedDate = item.CreatedDate;
itemOriginal.DisplayName = item.DisplayName;
itemOriginal.EnchantCategories = item.EnchantCategories;
itemOriginal.MaxDurability = item.MaxDurability;
itemOriginal.RepairWith = item.RepairWith;
itemOriginal.StackSize = item.StackSize;
itemOriginal.UpdatedDate = item.UpdatedDate;
itemOriginal.ImageBase64 = item.ImageBase64;
System.IO.File.WriteAllText("Data/items.json", JsonSerializer.Serialize(data, _jsonSerializerOptions));
return Task.CompletedTask;
}
/// <summary>
/// Gets the name of the item.
/// </summary>
/// <param name="items">The items.</param>
/// <param name="inShape">The in shape.</param>
/// <param name="line">The line.</param>
/// <param name="row">The row.</param>
/// <returns>The name of the item.</returns>
private static string GetItemName(List<Item> items, InShape[][] inShape, int line, int row)
{
if (inShape.Length < line + 1)
{
return null;
}
if (inShape[line].Length < row + 1)
{
return null;
}
var id = inShape[line][row].Integer ?? inShape[line][row].IngredientClass?.Id;
if (id == null)
{
return null;
}
return GetItemName(items, id.Value);
}
/// <summary>
/// Gets the name of the item.
/// </summary>
/// <param name="items">The items.</param>
/// <param name="id">The identifier.</param>
/// <returns>The name of the item.</returns>
private static string GetItemName(List<Item> items, long id)
{
var item = items.FirstOrDefault(w => w.Id == id);
return item?.Name;
}
/// <summary>
/// Converts the recipes.
/// </summary>
private void ConvertRecipes()
{
var data = JsonSerializer.Deserialize<List<Item>>(System.IO.File.ReadAllText("Data/items.json"), _jsonSerializerOptions);
if (data == null)
{
return;
}
var recipes = Recipes.FromJson(System.IO.File.ReadAllText("Data/recipes.json"));
var items = new List<Recipe>();
foreach (var recipe in recipes.SelectMany(s => s.Value))
{
if (recipe.InShape == null)
{
continue;
}
var giveItem = data.FirstOrDefault(w => w.Id == recipe.Result.Id);
if (giveItem == null)
{
continue;
}
items.Add(new Recipe
{
Give = giveItem,
Have = new List<List<string>>
{
new()
{
GetItemName(data, recipe.InShape, 0, 0),
GetItemName(data, recipe.InShape, 0, 1),
GetItemName(data, recipe.InShape, 0, 2)
},
new()
{
GetItemName(data, recipe.InShape, 1, 0),
GetItemName(data, recipe.InShape, 1, 1),
GetItemName(data, recipe.InShape, 1, 2)
},
new()
{
GetItemName(data, recipe.InShape, 2, 0),
GetItemName(data, recipe.InShape, 2, 1),
GetItemName(data, recipe.InShape, 2, 2)
}
}
});
}
System.IO.File.WriteAllText("Data/convert-recipes.json", JsonSerializer.Serialize(items, _jsonSerializerOptions));
}
}
}

@ -0,0 +1,143 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="InventoryController.cs" company="UCA Clermont-Ferrand">
// Copyright (c) UCA Clermont-Ferrand All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace Minecraft.Crafting.Api.Controllers
{
using Microsoft.AspNetCore.Mvc;
using Minecraft.Crafting.Api.Models;
using System.Text.Json;
using System.Text.Json.Serialization;
/// <summary>
/// The inventory controller.
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class InventoryController : ControllerBase
{
/// <summary>
/// The json serializer options.
/// </summary>
private readonly JsonSerializerOptions _jsonSerializerOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault
};
/// <summary>
/// Adds to inventory.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>The async task.</returns>
[HttpPost]
[Route("")]
public Task AddToInventory(InventoryModel item)
{
var data = JsonSerializer.Deserialize<List<InventoryModel>>(System.IO.File.ReadAllText("Data/inventory.json"), _jsonSerializerOptions);
if (data == null)
{
throw new Exception("Unable to get the inventory.");
}
data.Add(item);
System.IO.File.WriteAllText("Data/inventory.json", JsonSerializer.Serialize(data, _jsonSerializerOptions));
return Task.CompletedTask;
}
/// <summary>
/// Deletes from inventory.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>The async task.</returns>
[HttpDelete]
[Route("")]
public Task DeleteFromInventory(InventoryModel item)
{
if (!System.IO.File.Exists("Data/inventory.json"))
{
throw new Exception($"Unable to found the item with name: {item.ItemName}");
}
var data = JsonSerializer.Deserialize<List<InventoryModel>>(System.IO.File.ReadAllText("Data/inventory.json"), _jsonSerializerOptions);
if (data == null)
{
throw new Exception("Unable to get the inventory.");
}
var inventoryItem = data.FirstOrDefault(w => w.ItemName == item.ItemName && w.Position == item.Position);
if (inventoryItem == null)
{
throw new Exception($"Unable to found the item with name: {item.ItemName} at position: {item.Position}");
}
data.Remove(inventoryItem);
System.IO.File.WriteAllText("Data/inventory.json", JsonSerializer.Serialize(data, _jsonSerializerOptions));
return Task.CompletedTask;
}
/// <summary>
/// Gets the inventory.
/// </summary>
/// <returns>The inventory.</returns>
[HttpGet]
[Route("")]
public Task<List<InventoryModel>> GetInventory()
{
if (!System.IO.File.Exists("Data/inventory.json"))
{
return Task.FromResult(new List<InventoryModel>());
}
var data = JsonSerializer.Deserialize<List<InventoryModel>>(System.IO.File.ReadAllText("Data/inventory.json"), _jsonSerializerOptions);
if (data == null)
{
throw new Exception("Unable to get the inventory.");
}
return Task.FromResult(data);
}
/// <summary>
/// Updates the inventory.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>The async task.</returns>
[HttpPut]
[Route("")]
public Task UpdateInventory(InventoryModel item)
{
var data = JsonSerializer.Deserialize<List<InventoryModel>>(System.IO.File.ReadAllText("Data/inventory.json"), _jsonSerializerOptions);
if (data == null)
{
throw new Exception("Unable to get the inventory.");
}
var inventoryItem = data.FirstOrDefault(w => w.ItemName == item.ItemName && w.Position == item.Position);
if (inventoryItem == null)
{
throw new Exception($"Unable to found the item with name: {item.ItemName} at position: {item.Position}");
}
inventoryItem.ItemName = item.ItemName;
inventoryItem.Position = item.Position;
System.IO.File.WriteAllText("Data/inventory.json", JsonSerializer.Serialize(data, _jsonSerializerOptions));
return Task.CompletedTask;
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -0,0 +1,20 @@
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["src/Minecraft.Crafting.Api/Minecraft.Crafting.Api.csproj", "Minecraft.Crafting.Api/"]
RUN dotnet restore "Minecraft.Crafting.Api/Minecraft.Crafting.Api.csproj"
COPY src/. .
WORKDIR "/src/Minecraft.Crafting.Api"
RUN dotnet build "Minecraft.Crafting.Api.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "Minecraft.Crafting.Api.csproj" -c Release -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Minecraft.Crafting.Api.dll"]

@ -0,0 +1,6 @@
{
"animation": {
"interpolate": true,
"frametime": 10
}
}

@ -0,0 +1,5 @@
{
"animation": {
"frametime": 2
}
}

@ -0,0 +1,6 @@
{
"animation": {
"interpolate": true,
"frametime": 20
}
}

@ -0,0 +1,6 @@
{
"animation": {
"interpolate": true,
"frametime": 10
}
}

@ -0,0 +1,6 @@
{
"animation": {
"interpolate": true,
"frametime": 10
}
}

@ -0,0 +1,6 @@
{
"animation": {
"interpolate": true,
"frametime": 10
}
}

@ -0,0 +1,6 @@
{
"animation": {
"interpolate": true,
"frametime": 10
}
}

@ -0,0 +1,6 @@
{
"animation": {
"interpolate": true,
"frametime": 10
}
}

@ -0,0 +1,6 @@
{
"animation": {
"interpolate": true,
"frametime": 10
}
}

@ -0,0 +1,6 @@
{
"animation": {
"interpolate": true,
"frametime": 10
}
}

@ -0,0 +1,6 @@
{
"animation": {
"interpolate": true,
"frametime": 10
}
}

@ -0,0 +1,6 @@
{
"animation": {
"interpolate": true,
"frametime": 10
}
}

@ -0,0 +1,38 @@
{
"animation": {
"frames": [
16,
17,
18,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
31,
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15
]
}
}

@ -0,0 +1,5 @@
{
"animation": {
"frametime": 2
}
}

@ -0,0 +1,5 @@
{
"animation": {
"frametime": 2
}
}

@ -0,0 +1,5 @@
{
"animation": {
"frametime": 8
}
}

@ -0,0 +1,5 @@
{
"animation": {
"frametime": 3
}
}

@ -0,0 +1,45 @@
{
"animation": {
"frametime": 2,
"frames": [
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
18,
17,
16,
15,
14,
13,
12,
11,
10,
9,
8,
7,
6,
5,
4,
3,
2,
1
]
}
}

@ -0,0 +1,11 @@
{
"animation": {
"frametime": 8,
"interpolate": true,
"frames": [
0,
1,
2
]
}
}

@ -0,0 +1,30 @@
{
"animation": {
"frametime": 300,
"interpolate": true,
"frames": [
0,
1,
0,
2,
0,
3,
0,
1,
2,
1,
3,
1,
0,
2,
1,
2,
3,
2,
0,
3,
1,
3
]
}
}

@ -0,0 +1,6 @@
{
"animation": {
"interpolate": true,
"frametime": 10
}
}

@ -0,0 +1,6 @@
{
"animation": {
"interpolate": true,
"frametime": 10
}
}

@ -0,0 +1,6 @@
{
"animation": {
"interpolate": true,
"frametime": 10
}
}

@ -0,0 +1,6 @@
{
"animation": {
"interpolate": true,
"frametime": 10
}
}

@ -0,0 +1,6 @@
{
"animation": {
"frametime": 20,
"interpolate": true
}
}

@ -0,0 +1,6 @@
{
"animation": {
"frametime": 3,
"interpolate": true
}
}

@ -0,0 +1,6 @@
{
"animation": {
"frametime": 6,
"interpolate": true
}
}

@ -0,0 +1,6 @@
{
"animation": {
"frametime": 20,
"interpolate": true
}
}

@ -0,0 +1,5 @@
{
"animation": {
"frametime": 5
}
}

@ -0,0 +1,5 @@
{
"animation": {
"frametime": 2
}
}

@ -0,0 +1,6 @@
{
"animation": {
"interpolate": false,
"frametime": 4
}
}

@ -0,0 +1,6 @@
{
"animation": {
"frametime": 2
}
}

@ -0,0 +1,6 @@
{
"animation": {
"interpolate": true,
"frametime": 20
}
}

@ -0,0 +1,38 @@
{
"animation": {
"frames": [
16,
17,
18,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
31,
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15
]
}
}

@ -0,0 +1,5 @@
{
"animation": {
"frametime": 8
}
}

@ -0,0 +1,6 @@
{
"animation": {
"interpolate": false,
"frametime": 1
}
}

@ -0,0 +1,5 @@
{
"animation": {
"frametime": 2
}
}

@ -0,0 +1,5 @@
{
"animation": {
"frametime": 2
}
}

@ -0,0 +1,6 @@
{
"animation": {
"interpolate": true,
"frametime": 10
}
}

@ -0,0 +1,5 @@
{
"animation": {
"frametime": 2
}
}

@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
</ItemGroup>
<ItemGroup>
<Content Update="Data\items-original.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Data\recipes.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Content Include="Images\*.*">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

@ -0,0 +1,30 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="InventoryController.cs" company="UCA Clermont-Ferrand">
// Copyright (c) UCA Clermont-Ferrand All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace Minecraft.Crafting.Api.Models
{
/// <summary>
/// The inventory model.
/// </summary>
public class InventoryModel
{
/// <summary>
/// Gets or sets the name of the item.
/// </summary>
public string ItemName { get; set; }
/// <summary>
/// Gets or sets the number item.
/// </summary>
public int NumberItem { get; set; }
/// <summary>
/// Gets or sets the position.
/// </summary>
public int Position { get; set; }
public string? ImageBase64 { get; set; }
}
}

@ -0,0 +1,73 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="InventoryController.cs" company="UCA Clermont-Ferrand">
// Copyright (c) UCA Clermont-Ferrand All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace Minecraft.Crafting.Api.Models
{
/// <summary>
/// The item.
/// </summary>
public class Item
{
/// <summary>
/// Initializes a new instance of the <see cref="Item"/> class.
/// </summary>
public Item()
{
EnchantCategories = new List<string>();
RepairWith = new List<string>();
}
/// <summary>
/// Gets or sets the created date.
/// </summary>
public DateTime CreatedDate { get; set; }
/// <summary>
/// Gets or sets the display name.
/// </summary>
public string DisplayName { get; set; }
/// <summary>
/// Gets or sets the enchant categories.
/// </summary>
public List<string> EnchantCategories { get; set; }
/// <summary>
/// Gets or sets the identifier.
/// </summary>
public int Id { get; set; }
/// <summary>
/// Gets or sets the image base64.
/// </summary>
public string ImageBase64 { get; set; }
/// <summary>
/// Gets or sets the maximum durability.
/// </summary>
public int MaxDurability { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Gets or sets the repair with.
/// </summary>
public List<string> RepairWith { get; set; }
/// <summary>
/// Gets or sets the size of the stack.
/// </summary>
public int StackSize { get; set; }
/// <summary>
/// Gets or sets the updated date.
/// </summary>
public DateTime? UpdatedDate { get; set; }
}
}

@ -0,0 +1,26 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="InventoryController.cs" company="UCA Clermont-Ferrand">
// Copyright (c) UCA Clermont-Ferrand All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace Minecraft.Crafting.Api
{
using Minecraft.Crafting.Api.Models;
/// <summary>
/// The recipe.
/// </summary>
public class Recipe
{
/// <summary>
/// Gets or sets the give.
/// </summary>
public Item Give { get; set; }
/// <summary>
/// Gets or sets the have.
/// </summary>
public List<List<string>> Have { get; set; }
}
}

@ -0,0 +1,41 @@
namespace Minecraft.Crafting.Api
{
/// <summary>
/// The program.
/// </summary>
public class Program
{
/// <summary>
/// Defines the entry point of the application.
/// </summary>
/// <param name="args">The arguments.</param>
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
}
}
}

@ -0,0 +1,36 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:51530",
"sslPort": 44598
}
},
"profiles": {
"Minecraft.Crafting.Api": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7234;http://localhost:5234",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Docker": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger",
"environmentVariables": {}
}
}
}

@ -0,0 +1,177 @@
namespace Minecraft.Crafting.Api
{
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System;
using System.Collections.Generic;
using System.Globalization;
using JsonConverter = Newtonsoft.Json.JsonConverter;
using JsonSerializer = Newtonsoft.Json.JsonSerializer;
public struct IngredientElement
{
public IngredientClass IngredientClass;
public long? Integer;
public static implicit operator IngredientElement(IngredientClass IngredientClass) => new IngredientElement { IngredientClass = IngredientClass };
public static implicit operator IngredientElement(long Integer) => new IngredientElement { Integer = Integer };
}
public struct InShape
{
public IngredientClass IngredientClass;
public long? Integer;
public bool IsNull => IngredientClass == null && Integer == null;
public static implicit operator InShape(IngredientClass IngredientClass) => new InShape { IngredientClass = IngredientClass };
public static implicit operator InShape(long Integer) => new InShape { Integer = Integer };
}
public static class Serialize
{
public static string ToJson(this Dictionary<string, Recipes[]> self) => JsonConvert.SerializeObject(self, Converter.Settings);
}
public class IngredientClass
{
[JsonProperty("id")]
public long Id { get; set; }
[JsonProperty("metadata")]
public long Metadata { get; set; }
}
public partial class Recipes
{
[JsonProperty("ingredients", NullValueHandling = NullValueHandling.Ignore)]
public IngredientElement[] Ingredients { get; set; }
[JsonProperty("inShape", NullValueHandling = NullValueHandling.Ignore)]
public InShape[][] InShape { get; set; }
[JsonProperty("outShape", NullValueHandling = NullValueHandling.Ignore)]
public long?[][] OutShape { get; set; }
[JsonProperty("result")]
public Result Result { get; set; }
}
public partial class Recipes
{
public static Dictionary<string, Recipes[]> FromJson(string json) => JsonConvert.DeserializeObject<Dictionary<string, Recipes[]>>(json, Converter.Settings);
}
public class Result
{
[JsonProperty("count")]
public long Count { get; set; }
[JsonProperty("id")]
public long Id { get; set; }
[JsonProperty("metadata")]
public long Metadata { get; set; }
}
internal static class Converter
{
public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
{
MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
DateParseHandling = DateParseHandling.None,
Converters =
{
InShapeConverter.Singleton,
IngredientElementConverter.Singleton,
new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }
},
};
}
internal class IngredientElementConverter : JsonConverter
{
public static readonly IngredientElementConverter Singleton = new IngredientElementConverter();
public override bool CanConvert(Type t) => t == typeof(IngredientElement) || t == typeof(IngredientElement?);
public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer)
{
switch (reader.TokenType)
{
case JsonToken.Integer:
var integerValue = serializer.Deserialize<long>(reader);
return new IngredientElement { Integer = integerValue };
case JsonToken.StartObject:
var objectValue = serializer.Deserialize<IngredientClass>(reader);
return new IngredientElement { IngredientClass = objectValue };
}
throw new Exception("Cannot unmarshal type IngredientElement");
}
public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer)
{
var value = (IngredientElement)untypedValue;
if (value.Integer != null)
{
serializer.Serialize(writer, value.Integer.Value);
return;
}
if (value.IngredientClass != null)
{
serializer.Serialize(writer, value.IngredientClass);
return;
}
throw new Exception("Cannot marshal type IngredientElement");
}
}
internal class InShapeConverter : JsonConverter
{
public static readonly InShapeConverter Singleton = new InShapeConverter();
public override bool CanConvert(Type t) => t == typeof(InShape) || t == typeof(InShape?);
public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer)
{
switch (reader.TokenType)
{
case JsonToken.Null:
return new InShape { };
case JsonToken.Integer:
var integerValue = serializer.Deserialize<long>(reader);
return new InShape { Integer = integerValue };
case JsonToken.StartObject:
var objectValue = serializer.Deserialize<IngredientClass>(reader);
return new InShape { IngredientClass = objectValue };
}
throw new Exception("Cannot unmarshal type InShape");
}
public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer)
{
var value = (InShape)untypedValue;
if (value.IsNull)
{
serializer.Serialize(writer, null);
return;
}
if (value.Integer != null)
{
serializer.Serialize(writer, value.Integer.Value);
return;
}
if (value.IngredientClass != null)
{
serializer.Serialize(writer, value.IngredientClass);
return;
}
throw new Exception("Cannot marshal type InShape");
}
}
}

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

@ -0,0 +1,34 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.2.32616.157
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "blazor_lab", "blazor_lab\blazor_lab.csproj", "{7B8F9C82-6399-47FC-A996-8140F39484D6}"
ProjectSection(ProjectDependencies) = postProject
{F89D34E2-0DBC-4E98-BC77-E4CB00A33D19} = {F89D34E2-0DBC-4E98-BC77-E4CB00A33D19}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Minecraft.Crafting.Api", "Minecraft.Crafting.Api\Minecraft.Crafting.Api.csproj", "{F89D34E2-0DBC-4E98-BC77-E4CB00A33D19}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{7B8F9C82-6399-47FC-A996-8140F39484D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7B8F9C82-6399-47FC-A996-8140F39484D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7B8F9C82-6399-47FC-A996-8140F39484D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7B8F9C82-6399-47FC-A996-8140F39484D6}.Release|Any CPU.Build.0 = Release|Any CPU
{F89D34E2-0DBC-4E98-BC77-E4CB00A33D19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F89D34E2-0DBC-4E98-BC77-E4CB00A33D19}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F89D34E2-0DBC-4E98-BC77-E4CB00A33D19}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F89D34E2-0DBC-4E98-BC77-E4CB00A33D19}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {95423355-3A6F-48EB-B72A-9CD0DB0C3AC0}
EndGlobalSection
EndGlobal

BIN
blazor_lab/.DS_Store vendored

Binary file not shown.

@ -0,0 +1,14 @@
<CascadingBlazoredModal>
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
<PageTitle>404 / Not found</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">404 / Not found</p>
</LayoutView>
</NotFound>
</Router>
</CascadingBlazoredModal>

@ -0,0 +1,46 @@
<CascadingValue Value="@this">
<div class="container">
<div class="row">
<div class="col-6">
<div>Available items:</div>
<div>
<div class="css-grid">
@foreach (var item in Items)
{
<CraftingItem Item="item" NoDrop="true" />
}
</div>
</div>
</div>
<div class="col-6">
<div>Recipe</div>
<div>
<div class="css-recipe">
<CraftingItem Index="0" />
<CraftingItem Index="1" />
<CraftingItem Index="2" />
<CraftingItem Index="3" />
<CraftingItem Index="4" />
<CraftingItem Index="5" />
<CraftingItem Index="6" />
<CraftingItem Index="7" />
<CraftingItem Index="8" />
</div>
</div>
<div>Result</div>
<div>
<CraftingItem Item="RecipeResult" />
</div>
</div>
</div>
</div>
</CascadingValue>

@ -0,0 +1,80 @@
using blazor_lab.Models;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
namespace blazor_lab.Components
{
public partial class Crafting
{
private Item _recipeResult;
public Crafting()
{
Actions = new ObservableCollection<CraftingAction>();
Actions.CollectionChanged += OnActionsCollectionChanged;
this.RecipeItems = new List<Item> { null, null, null, null, null, null, null, null, null };
}
public ObservableCollection<CraftingAction> Actions { get; set; }
public Item CurrentDragItem { get; set; }
[Parameter]
public List<Item> Items { get; set; }
public List<Item> RecipeItems { get; set; }
public Item RecipeResult
{
get => this._recipeResult;
set
{
if (this._recipeResult == value)
{
return;
}
this._recipeResult = value;
this.StateHasChanged();
}
}
[Parameter]
public List<CraftingRecipe> Recipes { get; set; }
/// <summary>
/// Gets or sets the java script runtime.
/// </summary>
[Inject]
internal IJSRuntime JavaScriptRuntime { get; set; }
public void CheckRecipe()
{
RecipeResult = null;
// Get the current model
var currentModel = string.Join("|", this.RecipeItems.Select(s => s != null ? s.Name : string.Empty));
this.Actions.Add(new CraftingAction { Action = $"Items : {currentModel}" });
foreach (var craftingRecipe in Recipes)
{
// Get the recipe model
var recipeModel = string.Join("|", craftingRecipe.Have.SelectMany(s => s));
this.Actions.Add(new CraftingAction { Action = $"Recipe model : {recipeModel}" });
if (currentModel == recipeModel)
{
RecipeResult = craftingRecipe.Give;
}
}
}
private void OnActionsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
JavaScriptRuntime.InvokeVoidAsync("Crafting.AddActions", e.NewItems);
}
}
}

@ -0,0 +1,19 @@
.css-grid {
grid-template-columns: repeat(4,minmax(0,1fr));
gap: 10px;
display: grid;
width: 286px;
}
.css-recipe {
grid-template-columns: repeat(3,minmax(0,1fr));
gap: 10px;
display: grid;
width: 212px;
}
.actions {
border: 1px solid black;
height: 250px;
overflow: scroll;
}

@ -0,0 +1,16 @@
window.Crafting =
{
AddActions: function (data) {
data.forEach(element => {
const div = document.createElement('div');
div.innerHTML = 'Action: ' + element.action + ' - Index: ' + element.index;
if (element.item) {
div.innerHTML += ' - Item Name: ' + element.item.name;
}
document.getElementById('actions').appendChild(div);
});
}
}

@ -0,0 +1,11 @@
using blazor_lab.Models;
namespace blazor_lab.Components
{
public class CraftingAction
{
public string Action { get; set; }
public int Index { get; set; }
public Item Item { get; set; }
}
}

@ -0,0 +1,13 @@
<div class="item"
ondragover="event.preventDefault();"
draggable="true"
@ondragstart="@OnDragStart"
@ondrop="@OnDrop"
@ondragenter="@OnDragEnter"
@ondragleave="@OnDragLeave">
@if (Item != null)
{
@Item.DisplayName
}
</div>

@ -0,0 +1,63 @@
using blazor_lab.Models;
using Microsoft.AspNetCore.Components;
namespace blazor_lab.Components
{
public partial class CraftingItem
{
[Parameter]
public int Index { get; set; }
[Parameter]
public Item Item { get; set; }
[Parameter]
public bool NoDrop { get; set; }
[CascadingParameter]
public Crafting Parent { get; set; }
internal void OnDragEnter()
{
if (NoDrop)
{
return;
}
Parent.Actions.Add(new CraftingAction { Action = "Drag Enter", Item = this.Item, Index = this.Index });
}
internal void OnDragLeave()
{
if (NoDrop)
{
return;
}
Parent.Actions.Add(new CraftingAction { Action = "Drag Leave", Item = this.Item, Index = this.Index });
}
internal void OnDrop()
{
if (NoDrop)
{
return;
}
this.Item = Parent.CurrentDragItem;
Parent.RecipeItems[this.Index] = this.Item;
Parent.Actions.Add(new CraftingAction { Action = "Drop", Item = this.Item, Index = this.Index });
// Check recipe
Parent.CheckRecipe();
}
private void OnDragStart()
{
Parent.CurrentDragItem = this.Item;
Parent.Actions.Add(new CraftingAction { Action = "Drag Start", Item = this.Item, Index = this.Index });
}
}
}

@ -0,0 +1,11 @@
.item {
width: 64px;
height: 64px;
border: 1px solid;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
font-size: x-small;
background-color: #eee;
}

@ -0,0 +1,10 @@
using blazor_lab.Models;
namespace blazor_lab.Components
{
public class CraftingRecipe
{
public Item Give { get; set; }
public List<List<string>> Have { get; set; }
}
}

@ -0,0 +1,31 @@
<CascadingValue Value="@this">
<div class="side-by-side">
<div class="inventory-grid">
@for (int row = 0; row < 3; row++)
{
<div class="inventory-row">
@for (int col = 0; col < 6; col++)
{
<div class="inventory-slot">
@if (InventoryContent != null && InventoryContent.Count > (row * 6 + col))
{
<InventoryItem Position="(row * 6 + col)" IsInList="false" IsInInventory="true" />
}
</div>
}
</div>
}
</div>
<div>
<h2>@Localizer["list_of_items"]</h2>
<InventoryList Items="Items" />
</div>
</div>
<div class="actions" id="actions"></div>
</CascadingValue>

@ -0,0 +1,35 @@
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;
using Microsoft.JSInterop;
using Minecraft.Crafting.Api.Models;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using Item = blazor_lab.Models.Item;
namespace blazor_lab.Components
{
public partial class Inventory
{
[Inject]
public IStringLocalizer<Inventory> Localizer { get; set; }
[Inject]
internal IJSRuntime JavaScriptRuntime { get; set; }
[Parameter]
public List<Item> Items { get; set; }
public InventoryModel? CurrentDragItem { get; set; } = new();
public List<InventoryModel> InventoryContent { get; set; } = Enumerable.Range(1, 18).Select(_ => new InventoryModel()).ToList();
public Inventory()
{
Actions = new ObservableCollection<InventoryAction>();
Actions.CollectionChanged += OnActionsCollectionChanged;
}
public ObservableCollection<InventoryAction> Actions { get; set; }
private void OnActionsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
JavaScriptRuntime.InvokeVoidAsync("Inventory.AddActions", e.NewItems);
}
}
}

@ -0,0 +1,31 @@
.side-by-side {
display: flex;
flex-direction: row;
}
.inventory-grid {
display: flex;
flex-direction: column;
}
.inventory-row {
display: flex;
flex-direction: row;
}
.inventory-slot {
width: 64px;
height: 64px;
border: 1px solid;
margin: 5px;
display: flex;
flex-direction: column;
align-items: center;
overflow: hidden;
}
.actions {
border: 1px solid black;
height: 250px;
overflow: scroll;
}

@ -0,0 +1,16 @@
window.Inventory =
{
AddActions: function (data) {
data.forEach(element => {
const div = document.createElement('div');
div.innerHTML = 'Action: ' + element.action + ' - Position: ' + element.position;
if (element.inventoryModel) {
div.innerHTML += ' - Item Name: ' + element.inventoryModel.itemName;
}
document.getElementById('actions').appendChild(div);
});
}
}

@ -0,0 +1,11 @@
using Minecraft.Crafting.Api.Models;
namespace blazor_lab.Components
{
public class InventoryAction
{
public string? Action { get; set; }
public int Position { get; set; }
public InventoryModel? InventoryModel { get; set; }
}
}

@ -0,0 +1,24 @@
<div ondragover="event.preventDefault();"
draggable="true"
@ondragstart="@OnDragStart"
@ondrop="@OnDrop">
@if (Item is not null && IsInList)
{
<div class="inventory-list-item">
<img src="@($"data:image/png;base64,{Item.ImageBase64}")" alt="@Item.DisplayName" />
<div class="item-name">@Item.DisplayName</div>
</div>
}
@if (InventoryModel is not null && IsInInventory)
{
<div class="inventory-grid-item">
@if (InventoryModel.NumberItem > 0)
{
<div class="slot-image">
<img src="@($"data:image/png;base64,{InventoryModel.ImageBase64}")" alt="@InventoryModel.ItemName" />
</div>
<div class="slot-count">@InventoryModel.NumberItem</div>
}
</div>
}
</div>

@ -0,0 +1,132 @@
using Blazorise;
using Blazorise.Extensions;
using Microsoft.AspNetCore.Components;
using Minecraft.Crafting.Api.Models;
using Item = blazor_lab.Models.Item;
namespace blazor_lab.Components
{
public partial class InventoryItem
{
[Parameter]
public Item? Item { get; set; }
[Parameter]
public int Position { get; set; } = -1;
[Parameter]
public bool IsInList { get; set; }
[Parameter]
public bool IsInInventory { get; set; }
[CascadingParameter]
public InventoryList? ListParent { get; set; }
[CascadingParameter]
public Inventory? InventoryParent { get; set; }
public InventoryModel InventoryModel { get; set; } = new InventoryModel();
public InventoryItem()
{
if (IsInInventory)
{
InitInventoryModel();
}
}
internal void OnDrop()
{
if (IsInList)
{
ListParent!.Parent.CurrentDragItem = null;
ListParent.Parent.Actions.Add(new InventoryAction
{
Action = "Tried to drop on list",
InventoryModel = null,
Position = -1
});
return;
}
if (IsInInventory && InventoryParent!.CurrentDragItem is not null)
{
if (InventoryModel!.ItemName.IsNullOrEmpty()) // new inventoryModel
{
InventoryModel.ImageBase64 = InventoryParent!.CurrentDragItem!.ImageBase64;
InventoryModel.ItemName = InventoryParent!.CurrentDragItem!.ItemName;
InventoryModel.Position = Position;
InventoryModel.NumberItem = 1;
InventoryParent.InventoryContent.Insert(Position, InventoryModel);
InventoryParent.Actions.Add(new InventoryAction
{
Action = "Drop on inventory -- new -- successful",
InventoryModel = InventoryModel,
Position = Position
});
}
else if (InventoryModel.ItemName == InventoryParent!.CurrentDragItem!.ItemName) // adding to an existing stack
{
InventoryModel.NumberItem += 1;
InventoryParent.Actions.Add(new InventoryAction
{
Action = "Drop on inventory -- add -- successful",
InventoryModel = InventoryModel,
Position = Position
});
}
else
{
InventoryParent.Actions.Add(new InventoryAction
{
Action = "Drop on inventory -- unsuccessful",
InventoryModel = null,
Position = Position
});
}
InventoryParent!.CurrentDragItem = null;
}
}
private void OnDragStart()
{
if (IsInList)
{
ListParent!.Parent.CurrentDragItem = new InventoryModel
{
ImageBase64 = Item!.ImageBase64,
ItemName = Item!.DisplayName,
NumberItem = 1,
Position = -1
};
ListParent.Parent.Actions.Add(new InventoryAction
{
Action = "Drag from list",
InventoryModel = ListParent.Parent.CurrentDragItem,
Position = ListParent.Parent.CurrentDragItem.Position
});
}
else if (IsInInventory)
// delete item stack if it is dragged from inventory
{
InventoryParent!.Actions.Add(new InventoryAction
{
Action = "Drag from inventory (deleting)",
InventoryModel = InventoryParent.CurrentDragItem,
Position = Position
});
InventoryParent.CurrentDragItem = null;
InitInventoryModel();
}
}
private void InitInventoryModel()
{
InventoryModel.ImageBase64 = null;
InventoryModel.ItemName = "";
InventoryModel.NumberItem = 0;
InventoryModel.Position = Position;
}
}
}

@ -0,0 +1,39 @@
.inventory-list-item {
display: flex;
flex-direction: row;
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;
}
.inventory-grid-item {
display: flex;
flex-grow: 1;
flex-direction: column;
justify-content: center;
align-items: center;
border: 1px solid #ddd;
height: 64px;
width: 64px;
}
.inventory-grid-item .slot-image {
font-weight: bold;
font-size: small;
}
.inventory-grid-item .slot-count {
font-size: small;
}
.inventory-grid-item .item-name {
font-weight: bold;
}

@ -0,0 +1,44 @@
<CascadingValue Value="@this">
<div class="inventory-list">
<div class="search-container">
<input type="text" value="@searchQuery" @oninput="OnInputChange" placeholder="@Localizer["search_label"]" />
</div>
<div class="sort-container">
<label>@Localizer["sort_label"]</label>
<select @onchange="OnSortOptionChanged">
<option value="NameDescending">@Localizer["sort_by_name_desc"]</option>
<option value="NameAscending">@Localizer["sort_by_name_asc"]</option>
</select>
</div>
<div class="inventory-list-items">
@foreach (var item in VisibleItems)
{
<InventoryItem Item=item IsInList="true" IsInInventory="false"></InventoryItem>
}
</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>
</CascadingValue>

@ -0,0 +1,106 @@
using blazor_lab.Models;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;
namespace blazor_lab.Components
{
public enum SortOption
{
NameDescending,
NameAscending
}
public partial class InventoryList
{
[CascadingParameter]
public Inventory Parent { get; set; }
[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 readonly int pageSize = 10;
private void UpdateFilteredItems()
{
_filteredItems = string.IsNullOrEmpty(searchQuery)
? Items
: Items.Where(i => i.DisplayName.ToLower().Contains(searchQuery.ToLower())).ToList();
SortItems();
VisibleItems = _filteredItems.Skip((currentPage - 1) * pageSize).Take(pageSize).ToList();
}
private void SortItems()
{
switch (_sortOption)
{
case SortOption.NameAscending:
_filteredItems = _filteredItems.OrderBy(i => i.DisplayName).ToList();
break;
case SortOption.NameDescending:
_filteredItems = _filteredItems.OrderByDescending(i => i.DisplayName).ToList();
break;
}
}
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 SortOption _sortOption = SortOption.NameDescending;
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
currentPage = 1; // Go back to page 1 when user is searching
UpdateFilteredItems();
}
private void OnSortOptionChanged(ChangeEventArgs e)
{
if (Enum.TryParse(e.Value.ToString(), out SortOption sortOption))
{
_sortOption = sortOption;
UpdateFilteredItems();
}
}
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
UpdateFilteredItems();
}
}
}
}

@ -0,0 +1,7 @@
.inventory-list {
margin-top: 20px;
}
.search-container {
margin-bottom: 20px;
}

@ -0,0 +1,31 @@
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc;
/// <summary>
/// The culture controller.
/// </summary>
[Route("[controller]/[action]")]
public class CultureController : Controller
{
/// <summary>
/// Sets the culture.
/// </summary>
/// <param name="culture">The culture.</param>
/// <param name="redirectUri">The redirect URI.</param>
/// <returns>
/// The action result.
/// </returns>
public IActionResult SetCulture(string culture, string redirectUri)
{
if (culture != null)
{
// Define a cookie with the selected culture
this.HttpContext.Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(
new RequestCulture(culture)));
}
return this.LocalRedirect(redirectUri);
}
}

@ -0,0 +1,51 @@
using blazor_lab.Models;
namespace blazor_lab.Factories
{
public static class ItemFactory
{
public static ItemModel ToModel(Item item, byte[] imageContent)
{
return new ItemModel
{
Id = item.Id,
DisplayName = item.DisplayName,
Name = item.Name,
RepairWith = item.RepairWith,
EnchantCategories = item.EnchantCategories,
MaxDurability = item.MaxDurability,
StackSize = item.StackSize,
ImageContent = imageContent,
ImageBase64 = string.IsNullOrWhiteSpace(item.ImageBase64) ? Convert.ToBase64String(imageContent) : item.ImageBase64
};
}
public static Item Create(ItemModel model)
{
return new Item
{
Id = model.Id,
DisplayName = model.DisplayName,
Name = model.Name,
RepairWith = model.RepairWith,
EnchantCategories = model.EnchantCategories,
MaxDurability = model.MaxDurability,
StackSize = model.StackSize,
CreatedDate = DateTime.Now,
ImageBase64 = Convert.ToBase64String(model.ImageContent)
};
}
public static void Update(Item item, ItemModel model)
{
item.DisplayName = model.DisplayName;
item.Name = model.Name;
item.RepairWith = model.RepairWith;
item.EnchantCategories = model.EnchantCategories;
item.MaxDurability = model.MaxDurability;
item.StackSize = model.StackSize;
item.UpdatedDate = DateTime.Now;
item.ImageBase64 = Convert.ToBase64String(model.ImageContent);
}
}
}

@ -0,0 +1,10 @@
<div class="simple-form">
<p>
Are you sure you want to delete @item.DisplayName ?
</p>
<button @onclick="ConfirmDelete" class="btn btn-primary">Delete</button>
<button @onclick="Cancel" class="btn btn-secondary">Cancel</button>
</div>

@ -0,0 +1,38 @@
using blazor_lab.Models;
using blazor_lab.Services;
using Blazored.Modal;
using Blazored.Modal.Services;
using Microsoft.AspNetCore.Components;
namespace blazor_lab.Modals
{
public partial class DeleteConfirmation
{
[CascadingParameter]
public BlazoredModalInstance ModalInstance { get; set; }
[Inject]
public IDataService DataService { get; set; }
[Parameter]
public int Id { get; set; }
private Item item = new();
protected override async Task OnInitializedAsync()
{
// Get the item
item = await DataService.GetById(Id);
}
void ConfirmDelete()
{
ModalInstance.CloseAsync(ModalResult.Ok(true));
}
void Cancel()
{
ModalInstance.CancelAsync();
}
}
}

@ -0,0 +1,16 @@
namespace blazor_lab.Models
{
public class Item
{
public int Id { get; set; }
public string DisplayName { get; set; }
public string Name { get; set; }
public int StackSize { get; set; }
public int MaxDurability { get; set; }
public List<string> EnchantCategories { get; set; }
public List<string> RepairWith { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime? UpdatedDate { get; set; }
public string ImageBase64 { get; set; }
}
}

@ -0,0 +1,37 @@
using System.ComponentModel.DataAnnotations;
namespace blazor_lab.Models
{
public class ItemModel
{
public int Id { get; set; }
[Required]
[StringLength(50, ErrorMessage = "50ch max")]
public string DisplayName { get; set; }
[Required]
[StringLength(50, ErrorMessage = "50ch max")]
[RegularExpression(@"^[a-z''-'\s]{1,50}$", ErrorMessage = "lowercase only")]
public string Name { get; set; }
[Required]
[Range(1, 64)]
public int StackSize { get; set; }
[Required]
[Range(1, 125)]
public int MaxDurability { get; set; }
public List<string> EnchantCategories { get; set; }
public List<string> RepairWith { get; set; }
[Required]
[Range(typeof(bool), "true", "true", ErrorMessage = "You must agree to thhe terms.")]
public bool AcceptConditions { get; set; }
[Required(ErrorMessage = "img mandatory")]
public byte[] ImageContent { get; set; }
public string ImageBase64 { get; set; }
}
}

@ -0,0 +1,75 @@
@page "/add"
<h3>Add</h3>
<EditForm Model="@itemModel" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<p>
<label for="display-name">
Display name:
<InputText id="display-name" @bind-Value="itemModel.DisplayName" />
</label>
</p>
<p>
<label for="name">
Name:
<InputText id="name" @bind-Value="itemModel.Name" />
</label>
</p>
<p>
<label for="stack-size">
Stack size:
<InputNumber id="stack-size" @bind-Value="itemModel.StackSize" />
</label>
</p>
<p>
<label for="max-durability">
Max durability:
<InputNumber id="max-durability" @bind-Value="itemModel.MaxDurability" />
</label>
</p>
<p>
Enchant categories:
<div>
@foreach (var item in enchantCategories)
{
<label>
<input type="checkbox"
@onchange="@(e => OnEnchantCategoriesChange(item, e.Value))" />
@item
</label>
}
</div>
</p>
<p>
Repair with:
<div>
@foreach (var item in repairWith)
{
<label>
<input type="checkbox"
@onchange="@(e => OnRepairWithChange(item, e.Value))"
/>
@item
</label>
}
</div>
</p>
<p>
<label>
Item image:
<InputFile OnChange="@LoadImage" accept=".png" />
</label>
</p>
<p>
<label>
Accept Condition:
<InputCheckbox @bind-Value="itemModel.AcceptConditions" />
</label>
</p>
<button type="submit">Submit</button>
</EditForm>

@ -0,0 +1,88 @@
using blazor_lab.Models;
using blazor_lab.Services;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
namespace blazor_lab.Pages
{
public partial class Add
{
[Inject]
public IDataService DataService { get; set; }
[Inject]
public NavigationManager NavigationManager { get; set; }
/// <summary>
/// The default enchant categories.
/// </summary>
private List<string> enchantCategories = new() { "armor", "armor_head", "armor_chest", "weapon", "digger", "breakable", "vanishable" };
/// <summary>
/// The default repair with.
/// </summary>
private List<string> repairWith = new() { "oak_planks", "spruce_planks", "birch_planks", "jungle_planks", "acacia_planks", "dark_oak_planks", "crimson_planks", "warped_planks" };
/// <summary>
/// The current item model
/// </summary>
private ItemModel itemModel = new()
{
EnchantCategories = new List<string>(),
RepairWith = new List<string>()
};
private async void HandleValidSubmit()
{
await DataService.Add(itemModel);
NavigationManager.NavigateTo("list");
}
private async Task LoadImage(InputFileChangeEventArgs e)
{
// Set the model's image to the image saved on file
using (var memoryStream = new MemoryStream())
{
await e.File.OpenReadStream().CopyToAsync(memoryStream);
itemModel.ImageContent = memoryStream.ToArray();
}
}
private void OnEnchantCategoriesChange(string item, object checkedValue)
{
if ((bool)checkedValue)
{
if (!itemModel.EnchantCategories.Contains(item))
{
itemModel.EnchantCategories.Add(item);
}
return;
}
if (itemModel.EnchantCategories.Contains(item))
{
itemModel.EnchantCategories.Remove(item);
}
}
private void OnRepairWithChange(string item, object checkedValue)
{
if ((bool)checkedValue)
{
if (!itemModel.RepairWith.Contains(item))
{
itemModel.RepairWith.Add(item);
}
return;
}
if (itemModel.RepairWith.Contains(item))
{
itemModel.RepairWith.Remove(item);
}
}
}
}

@ -0,0 +1,82 @@
@page "/edit/{Id:int}"
<h3>Edit</h3>
<EditForm Model="@itemModel" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<p>
<label for="display-name">
Display name:
<InputText id="display-name" @bind-Value="itemModel.DisplayName" />
</label>
</p>
<p>
<label for="name">
Name:
<InputText id="name" @bind-Value="itemModel.Name" />
</label>
</p>
<p>
<label for="stack-size">
Stack size:
<InputNumber id="stack-size" @bind-Value="itemModel.StackSize" />
</label>
</p>
<p>
<label for="max-durability">
Max durability:
<InputNumber id="max-durability" @bind-Value="itemModel.MaxDurability" />
</label>
</p>
<p>
Enchant categories:
<div>
@foreach (var enchant in enchantCategories)
{
<label>
<input type="checkbox"
checked="@(itemModel.EnchantCategories.Contains(enchant) ? "checked" : null)"
@onchange="@(e => OnEnchantCategoriesChange(enchant, e.Value))" />
@enchant
</label>
}
</div>
</p>
<p>
Repair with:
<div>
@foreach (var material in repairWith)
{
<label>
<input type="checkbox"
checked="@(itemModel.EnchantCategories.Contains(material) ? "checked" : null)"
@onchange="@(e => OnRepairWithChange(material, e.Value))" />
@material
</label>
}
</div>
</p>
<p>
<label>
Current Item image:
<img src="data:image/png;base64, @(itemModel.ImageBase64)" class="img-thumbnail" title="@itemModel.DisplayName" alt="@itemModel.DisplayName" style="min-width: 50px; max-width: 150px" />
</label>
</p>
<p>
<label>
Item image:
<InputFile OnChange="@LoadImage" accept=".png" />
</label>
</p>
<p>
<label>
Accept Condition:
<InputCheckbox @bind-Value="itemModel.AcceptConditions" />
</label>
</p>
<button type="submit">Submit</button>
</EditForm>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save