L'importation d'image fonctionne. Il manque à faire fonctionner la modifiction et le delete avec.
5
.gitignore
vendored
@ -349,3 +349,8 @@ MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
# ignore les images pour ne pas avoir pleins d'image non-lié
|
||||
GrossesMitaines/GrossesMitainesAPI/Images/*
|
||||
!GrossesMitaines/GrossesMitainesAPI/Images/default.jpg
|
||||
!GrossesMitaines/GrossesMitainesAPI/Images/default_thumbnail.jpg
|
||||
|
@ -0,0 +1,67 @@
|
||||
using GrossesMitainesAPI.Data;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace GrossesMitainesAPI.Controllers {
|
||||
[EnableCors("_myAllowSpecificOrigins"), ApiController, Route("api/[controller]"), Authorize(AuthenticationSchemes = "Identity.Application")]
|
||||
|
||||
public class ImageController : ControllerBase {
|
||||
|
||||
|
||||
private readonly InventoryContext _context;
|
||||
private readonly IWebHostEnvironment _hostEnvironment;
|
||||
private readonly ILogger<ProductController> _logger;
|
||||
|
||||
public ImageController(ILogger<ProductController> logger, InventoryContext context, IWebHostEnvironment hostEnvironment) {
|
||||
_context = context;
|
||||
_hostEnvironment = hostEnvironment;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
||||
[EnableCors("_myAllowSpecificOrigins"), HttpGet(Name = "Image"), AllowAnonymous]
|
||||
public IActionResult Get(int id, bool? thumbnail = false) {
|
||||
|
||||
string path;
|
||||
string filename;
|
||||
string filetype;
|
||||
|
||||
try {
|
||||
var prod = _context.Products.Where(x => x.Id == id).First();
|
||||
filename = prod.ImageName ?? throw new Exception("Unable to find product image name. Sending default.jpg instead...");
|
||||
}
|
||||
catch (Exception e) {
|
||||
_logger.LogError(8, e.Message);
|
||||
filename = "default.jpg";
|
||||
}
|
||||
|
||||
|
||||
path = thumbnail == true ? Path.Combine(_hostEnvironment.ContentRootPath, "Images", Path.GetFileNameWithoutExtension(filename) + "_thumbnail" + Path.GetExtension(filename))
|
||||
: Path.Combine(_hostEnvironment.ContentRootPath, "Images", filename);
|
||||
|
||||
if (!System.IO.File.Exists(path)) {
|
||||
_logger.LogError(8, "Unable to find image. Sending default image instead...");
|
||||
path = Path.Combine(_hostEnvironment.ContentRootPath, "Images", "default.jpg");
|
||||
}
|
||||
|
||||
|
||||
switch (Path.GetExtension(path)) {
|
||||
case ".jpg":
|
||||
case ".jpeg":
|
||||
filetype = "image/jpeg";
|
||||
break;
|
||||
case ".png":
|
||||
filetype = "image/png";
|
||||
break;
|
||||
default:
|
||||
filetype = "image/jpeg";
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
byte[] imageData = System.IO.File.ReadAllBytes(path);
|
||||
return File(imageData, filetype);
|
||||
}
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@ using Microsoft.Extensions.Logging;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using GrossesMitainesAPI.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
using Microsoft.Extensions.Hosting;
|
||||
#endregion
|
||||
|
||||
[EnableCors("_myAllowSpecificOrigins"), ApiController, Route("api/[controller]"),
|
||||
@ -24,14 +24,16 @@ public class InventoryController : Controller {
|
||||
private readonly ILogger<InventoryController> _logger;
|
||||
private readonly InventoryContext _context;
|
||||
private readonly DatabaseCacheService _cache;
|
||||
private readonly IWebHostEnvironment _hostEnvironment;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Ctor
|
||||
public InventoryController(ILogger<InventoryController> logger, InventoryContext context, DatabaseCacheService cache) {
|
||||
public InventoryController(ILogger<InventoryController> logger, InventoryContext context, DatabaseCacheService cache, IWebHostEnvironment hostEnvironment) {
|
||||
_context = context;
|
||||
_logger = logger;
|
||||
_cache = cache;
|
||||
_hostEnvironment = hostEnvironment;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -117,6 +119,7 @@ public class InventoryController : Controller {
|
||||
|
||||
foreach (ProductViewModel prod in ret.ToList()) {
|
||||
if (yup && add < AMOUNT_SCROLL || (all.HasValue && all == true)) {
|
||||
|
||||
lst.Add(prod);
|
||||
add++;
|
||||
}
|
||||
@ -124,13 +127,15 @@ public class InventoryController : Controller {
|
||||
yup = true;
|
||||
}
|
||||
return lst;
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (iscache)
|
||||
_logger.LogError(e, "Erreur d'appel de cache.");
|
||||
else _logger.LogError(e, "Erreur d'appel d'API.");
|
||||
return new List<ProductViewModel>();
|
||||
}
|
||||
}
|
||||
|
||||
// Inventory/Delete => Décrémenter un produit. Va aller chercher directement dans la BD.
|
||||
//[EnableCors("_myAllowSpecificOrigins"), HttpDelete(Name = "Inventory"), AllowAnonymous]
|
||||
//public ActionResult<int> Delete(int? id) {
|
||||
|
@ -10,7 +10,9 @@ using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using GrossesMitainesAPI.Services;
|
||||
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using Microsoft.EntityFrameworkCore.Storage;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
@ -57,7 +59,7 @@ public class ProductController : ControllerBase {
|
||||
}
|
||||
|
||||
[EnableCors("_myAllowSpecificOrigins"), HttpPost(Name = "Product")]
|
||||
public async Task<ActionResult<ProductModel>> Post(ProductModel prod) {
|
||||
public async Task<ActionResult<ProductModel>> Post([FromForm] ProductModel prod) {
|
||||
if (prod.Price <= prod.PromoPrice)
|
||||
prod.PromoPrice = prod.Price - 0.01M;
|
||||
try {
|
||||
@ -110,10 +112,33 @@ public class ProductController : ControllerBase {
|
||||
string imageName = new String(Path.GetFileNameWithoutExtension(imageFile.FileName).Take(10).ToArray()).Replace(' ', '-');
|
||||
imageName = imageName + DateTime.Now.ToString("yymmssfff") + Path.GetExtension(imageFile.FileName);
|
||||
var imagePath = Path.Combine(_hostEnvironment.ContentRootPath, "Images", imageName);
|
||||
|
||||
using (var fileStream = new FileStream(imagePath, FileMode.Create)) {
|
||||
await imageFile.CopyToAsync(fileStream);
|
||||
SaveImageThumbnail(fileStream, imageName);
|
||||
}
|
||||
return imageName;
|
||||
}
|
||||
|
||||
private void SaveImageThumbnail(FileStream stream, string imageName) {
|
||||
|
||||
try {
|
||||
const float maxSize = 200f;
|
||||
Image image = Image.FromStream(stream);
|
||||
|
||||
//Choisi le bon ratio de division pour ne pas dépasser le 200px ni dans height ni dans width
|
||||
float ratio = image.Width / (image.Height / maxSize) <= maxSize ? image.Height / maxSize : image.Width / maxSize;
|
||||
|
||||
Bitmap resize = new Bitmap(image, new Size((int)(image.Width / ratio), (int)(image.Height / ratio)));
|
||||
string path = Path.Combine(_hostEnvironment.ContentRootPath, "Images", Path.GetFileNameWithoutExtension(imageName) + "_thumbnail" + Path.GetExtension(imageName));
|
||||
resize.Save(path);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
_logger.LogError(8, ex.Message);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
@ -22,6 +22,7 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
BIN
GrossesMitaines/GrossesMitainesAPI/Images/default.jpg
Normal file
After Width: | Height: | Size: 5.7 KiB |
BIN
GrossesMitaines/GrossesMitainesAPI/Images/default_thumbnail.jpg
Normal file
After Width: | Height: | Size: 3.0 KiB |
@ -13,6 +13,8 @@ public class ProductViewModel {
|
||||
public States Status { get; set; } = States.Available;
|
||||
public string? ImageName { get; set; }
|
||||
|
||||
public string? ImageData { get; set; }
|
||||
|
||||
public ProductViewModel(ProductModel prod) {
|
||||
this.Id = prod.Id;
|
||||
this.Title = prod.Title;
|
||||
|
Before Width: | Height: | Size: 75 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 296 KiB |
Before Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 330 KiB |
Before Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 410 KiB |
Before Width: | Height: | Size: 61 KiB |
Before Width: | Height: | Size: 270 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 84 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 917 KiB |
Before Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 875 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 67 KiB |
@ -0,0 +1,26 @@
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
const FeaturedImage = ({ productId }) => {
|
||||
|
||||
const [imageSrc, setImageSrc] = useState("/images/default_thumbnail.jpg");
|
||||
|
||||
useEffect(() => {
|
||||
fetch(`https://localhost:7292/api/Image?id=${productId}`)
|
||||
.then(response => response.blob())
|
||||
.then(blob => {
|
||||
const imageUrl = URL.createObjectURL(blob);
|
||||
setImageSrc(imageUrl);
|
||||
})
|
||||
},[]);
|
||||
|
||||
|
||||
return (
|
||||
<img
|
||||
className="featured-img"
|
||||
src={imageSrc}
|
||||
alt={productId + "_image"}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default FeaturedImage;
|
@ -1,5 +1,7 @@
|
||||
import { Carousel } from "react-bootstrap";
|
||||
import { Link } from "react-router-dom";
|
||||
import FeaturedImage from "./FeaturedImage";
|
||||
|
||||
const FeaturedList = ({ products }) => {
|
||||
if (products === null)
|
||||
return (<></>);
|
||||
@ -9,10 +11,7 @@ const FeaturedList = ({ products }) => {
|
||||
|
||||
<Carousel.Item key={product.id} className="featured-itm">
|
||||
<Link key={product.id} to={`/morceaux/${product.id}`}>
|
||||
<img
|
||||
className="featured-img"
|
||||
src={`/images/${product.imageName}.jpg`}
|
||||
/>
|
||||
<FeaturedImage productId={product.id}/>
|
||||
<Carousel.Caption className="featured-info">
|
||||
<h3>{product.title}</h3>
|
||||
<p>{product.description}</p>
|
||||
|
@ -1,13 +1,7 @@
|
||||
import { Card } from "react-bootstrap";
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
|
||||
// public enum States {
|
||||
// Available,
|
||||
// BackOrder,
|
||||
// Unavailable,
|
||||
// Clearance,
|
||||
// Promotion,
|
||||
// Discontinued
|
||||
// }
|
||||
function renderStatus(statusCode) {
|
||||
if (statusCode !== undefined) {
|
||||
|
||||
@ -62,7 +56,7 @@ function renderPrice(price, newPrice, status) {
|
||||
if (price !== undefined) {
|
||||
|
||||
|
||||
if (status != 3 && status != 4) {
|
||||
if (status !== 3 && status !== 4) {
|
||||
return (
|
||||
<Card.Text className="item-price-container">
|
||||
<span className="item-price">
|
||||
@ -87,12 +81,25 @@ function renderPrice(price, newPrice, status) {
|
||||
|
||||
}
|
||||
|
||||
const Item = ({ imageUrl, name, price, newPrice, status }) => {
|
||||
const Item = ({ productId, name, price, newPrice, status }) => {
|
||||
|
||||
const [imageSrc, setImageSrc] = useState("/images/default_thumbnail.jpg");
|
||||
|
||||
useEffect(() => {
|
||||
fetch(`https://localhost:7292/api/Image?id=${productId}&thumbnail=true`)
|
||||
.then(response => response.blob())
|
||||
.then(blob => {
|
||||
const imageUrl = URL.createObjectURL(blob);
|
||||
setImageSrc(imageUrl);
|
||||
})
|
||||
}, []);
|
||||
|
||||
|
||||
if (name !== undefined) {
|
||||
return (
|
||||
|
||||
<Card className="item">
|
||||
<Card.Img className="item-img" variant="top" src={`/images/${imageUrl}_thumbnail.jpg`} />
|
||||
<Card.Img className="item-img" variant="top" src={imageSrc} />
|
||||
<Card.Body className="item-info">
|
||||
<div className="item-name-container">
|
||||
<Card.Title className="item-name">
|
||||
|
@ -9,7 +9,8 @@ const ItemList = ({ items }) => {
|
||||
{items.length <= 0 && <p>Aucun morceaux à montrer...</p>}
|
||||
{items.map((item) =>
|
||||
<Link key={item.id} className='item-link' to={`/morceaux/${item.id}`}>
|
||||
<Item imageUrl={item.imageName}
|
||||
<Item
|
||||
productId={item.id}
|
||||
name={item.title}
|
||||
price={item.price}
|
||||
status={item.status}
|
||||
|
@ -20,14 +20,24 @@ const Inventaire = () => {
|
||||
}, []);
|
||||
|
||||
const handleAddItem = async (morceau) => {
|
||||
|
||||
console.log(morceau);
|
||||
|
||||
let formData = new FormData();
|
||||
Object.keys(morceau).map((k) => {
|
||||
formData.set(k,morceau[k]);
|
||||
});
|
||||
|
||||
console.log(formData);
|
||||
|
||||
const response = await fetch(`https://localhost:7292/api/Product`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
mode: 'cors',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
'accept': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(morceau)
|
||||
body: formData
|
||||
})
|
||||
|
||||
const newMorceau = await response.json();
|
||||
@ -38,7 +48,7 @@ const Inventaire = () => {
|
||||
console.log("Ajout de l'item avec succès: \r\n" + newMorceau);
|
||||
}
|
||||
else
|
||||
console.log("Erreur de creation " + morceau);
|
||||
console.log("Erreur de creation " + morceau.title);
|
||||
};
|
||||
|
||||
const handleDeleteItem = async (id) => {
|
||||
|
@ -7,6 +7,7 @@ const MorceauDetail = () => {
|
||||
const { id } = useParams();
|
||||
const [item, setItem] = useState({});
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [imageSrc, setImageSrc] = useState("/images/default_thumbnail.jpg");
|
||||
|
||||
useEffect(() => {
|
||||
document.title = 'Morceaux';
|
||||
@ -16,10 +17,25 @@ const MorceauDetail = () => {
|
||||
const json = await response.json();
|
||||
setItem(json);
|
||||
}
|
||||
|
||||
|
||||
fetchData();
|
||||
|
||||
fetch(`https://localhost:7292/api/Image?id=${id}`)
|
||||
.then(response => response.blob())
|
||||
.then(blob => {
|
||||
const imageUrl = URL.createObjectURL(blob);
|
||||
setImageSrc(imageUrl);
|
||||
})
|
||||
|
||||
setIsLoading(false);
|
||||
});
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
}, []);
|
||||
|
||||
function renderPrice(price, newPrice, status) {
|
||||
if (price !== undefined) {
|
||||
if (status !== 3 && status !== 4) {
|
||||
@ -99,7 +115,7 @@ const MorceauDetail = () => {
|
||||
|
||||
<div className="detail-container">
|
||||
<div className="detail-container-left">
|
||||
<img className="detail-image" alt="" src={`/images/${item.imageName}.jpg`} />
|
||||
<img className="detail-image" alt="" src={imageSrc} />
|
||||
<p className="detail-description">{item.description}</p>
|
||||
</div>
|
||||
<div className="detail-container-right">
|
||||
|