@ -1,11 +0,0 @@
|
|||||||
Copyright (c) <year> <owner> All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of the copyright holders or contributors.
|
|
@ -0,0 +1,364 @@
|
|||||||
|
// --------------------------------------------------------------------------------------------------------------------
|
||||||
|
// <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.");
|
||||||
|
}
|
||||||
|
|
||||||
|
data.Add(item);
|
||||||
|
|
||||||
|
System.IO.File.WriteAllText("Data/items.json", JsonSerializer.Serialize(data, _jsonSerializerOptions));
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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 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"));
|
||||||
|
|
||||||
|
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"));
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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 = new Item { DisplayName = giveItem.DisplayName, Name = giveItem.Name },
|
||||||
|
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,22 @@
|
|||||||
|
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
|
||||||
|
|
||||||
|
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 ["Minecraft.Crafting.Api/Minecraft.Crafting.Api.csproj", "Minecraft.Crafting.Api/"]
|
||||||
|
RUN dotnet restore "Minecraft.Crafting.Api/Minecraft.Crafting.Api.csproj"
|
||||||
|
COPY . .
|
||||||
|
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"]
|
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 5.7 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.3 KiB |