Allow to add a new company logo in admin view.

This commit is contained in:
2020-05-28 22:38:52 +01:00
parent a7d44c8940
commit c922cbcdd1
10 changed files with 503 additions and 491 deletions

View File

@@ -1,395 +0,0 @@
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using Marechai.Areas.Admin.Models;
using Marechai.Database.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query;
using SkiaSharp;
using Svg.Skia;
namespace Marechai.Areas.Admin.Controllers
{
[Area("Admin"), Authorize]
public class CompanyLogosController : Controller
{
readonly MarechaiContext _context;
readonly IWebHostEnvironment hostingEnvironment;
public CompanyLogosController(MarechaiContext context, IWebHostEnvironment env)
{
_context = context;
hostingEnvironment = env;
}
// GET: CompanyLogos
public async Task<IActionResult> Index()
{
IIncludableQueryable<CompanyLogo, Company> marechaiContext = _context.CompanyLogos.Include(c => c.Company);
return View(await marechaiContext.OrderBy(l => l.Company.Name).ThenBy(l => l.Year).
Select(l => new CompanyLogoViewModel
{
Company = l.Company.Name, Id = l.Id, Year = l.Year
}).ToListAsync());
}
// GET: CompanyLogos/Details/5
public async Task<IActionResult> Details(int? id)
{
if(id == null)
return NotFound();
CompanyLogo companyLogo =
await _context.CompanyLogos.Include(c => c.Company).FirstOrDefaultAsync(m => m.Id == id);
if(companyLogo == null)
return NotFound();
return View(companyLogo);
}
// GET: CompanyLogos/Create
// TODO: Upload
public IActionResult Create()
{
ViewData["CompanyId"] = new SelectList(_context.Companies.Select(c => new CompanyViewModel
{
Name = c.Name, Id = c.Id
}).OrderBy(c => c.Name), "Id", "Name");
return View();
}
// POST: CompanyLogos/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
// TODO: Upload
[HttpPost, ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,CompanyId,Year,SvgLogo")] CompanyLogo companyLogo)
{
if(!ModelState.IsValid)
{
ViewData["CompanyId"] = new SelectList(_context.Companies.Select(c => new CompanyViewModel
{
Name = c.Name, Id = c.Id
}).OrderBy(c => c.Name), "Id", "Name", companyLogo.CompanyId);
return View(companyLogo);
}
using(var svgMs = new MemoryStream())
{
await companyLogo.SvgLogo.CopyToAsync(svgMs);
svgMs.Position = 0;
try
{
var sr = new StreamReader(svgMs, Encoding.UTF8);
string svgStr = await sr.ReadToEndAsync();
var xml = new XmlDocument();
xml.LoadXml(svgStr);
}
catch(XmlException)
{
companyLogo.SvgLogo = null;
companyLogo.ErrorMessage = "Not a valid SVG file.";
ViewData["CompanyId"] = new SelectList(_context.Companies.Select(c => new CompanyViewModel
{
Name = c.Name, Id = c.Id
}).OrderBy(c => c.Name), "Id", "Name", companyLogo.CompanyId);
return View(companyLogo);
}
svgMs.Position = 0;
companyLogo.Guid = Guid.NewGuid();
string vectorial = Path.Combine(hostingEnvironment.WebRootPath, "assets/logos",
companyLogo.Guid + ".svg");
if(System.IO.File.Exists(vectorial))
{
companyLogo.SvgLogo = null;
companyLogo.ErrorMessage = "GUID clash, please retry and report to the administrator.";
ViewData["CompanyId"] = new SelectList(_context.Companies.Select(c => new CompanyViewModel
{
Name = c.Name, Id = c.Id
}).OrderBy(c => c.Name), "Id", "Name", companyLogo.CompanyId);
return View(companyLogo);
}
var outSvg = new FileStream(vectorial, FileMode.CreateNew);
await svgMs.CopyToAsync(outSvg);
svgMs.Position = 0;
SKSvg svg = null;
try
{
foreach(string format in new[]
{
"png", "webp"
})
{
if(!Directory.Exists(Path.Combine(hostingEnvironment.WebRootPath, "assets/logos", format)))
Directory.CreateDirectory(Path.Combine(hostingEnvironment.WebRootPath, "assets/logos",
format));
SKEncodedImageFormat skFormat;
switch(format)
{
case"webp":
skFormat = SKEncodedImageFormat.Webp;
break;
default:
skFormat = SKEncodedImageFormat.Png;
break;
}
foreach(int multiplier in new[]
{
1, 2, 3
})
{
if(!Directory.Exists(Path.Combine(hostingEnvironment.WebRootPath, "assets/logos", format,
$"{multiplier}x")))
Directory.CreateDirectory(Path.Combine(hostingEnvironment.WebRootPath, "assets/logos",
format, $"{multiplier}x"));
string rendered = Path.Combine(hostingEnvironment.WebRootPath, "assets/logos", format,
$"{multiplier}x", companyLogo.Guid + $".{format}");
if(System.IO.File.Exists(rendered))
{
companyLogo.SvgLogo = null;
companyLogo.ErrorMessage = "GUID clash, please retry and report to the administrator.";
ViewData["CompanyId"] =
new SelectList(_context.Companies.Select(c => new CompanyViewModel
{
Name = c.Name, Id = c.Id
}).OrderBy(c => c.Name), "Id", "Name", companyLogo.CompanyId);
return View(companyLogo);
}
Console.WriteLine("Rendering {0}", rendered);
if(svg == null)
{
svg = new SKSvg();
svg.Load(svgMs);
}
SKRect svgSize = svg.Picture.CullRect;
var matrix = SKMatrix.MakeScale(multiplier, multiplier);
var bitmap = new SKBitmap((int)(svgSize.Width * multiplier),
(int)(svgSize.Height * multiplier));
var canvas = new SKCanvas(bitmap);
canvas.DrawPicture(svg.Picture, ref matrix);
canvas.Flush();
var image = SKImage.FromBitmap(bitmap);
SKData data = image.Encode(skFormat, 100);
var outfs = new FileStream(rendered, FileMode.CreateNew);
data.SaveTo(outfs);
outfs.Close();
svgMs.Position = 0;
}
}
foreach(string format in new[]
{
"png", "webp"
})
{
if(!Directory.Exists(Path.Combine(hostingEnvironment.WebRootPath, "assets/logos/thumbs",
format)))
Directory.CreateDirectory(Path.Combine(hostingEnvironment.WebRootPath,
"assets/logos/thumbs", format));
SKEncodedImageFormat skFormat;
switch(format)
{
case"webp":
skFormat = SKEncodedImageFormat.Webp;
break;
default:
skFormat = SKEncodedImageFormat.Png;
break;
}
foreach(int multiplier in new[]
{
1, 2, 3
})
{
if(!Directory.Exists(Path.Combine(hostingEnvironment.WebRootPath, "assets/logos/thumbs",
format, $"{multiplier}x")))
Directory.CreateDirectory(Path.Combine(hostingEnvironment.WebRootPath,
"assets/logos/thumbs", format,
$"{multiplier}x"));
string rendered = Path.Combine(hostingEnvironment.WebRootPath, "assets/logos/thumbs",
format, $"{multiplier}x", companyLogo.Guid + $".{format}");
if(System.IO.File.Exists(rendered))
{
companyLogo.SvgLogo = null;
companyLogo.ErrorMessage = "GUID clash, please retry and report to the administrator.";
ViewData["CompanyId"] =
new SelectList(_context.Companies.Select(c => new CompanyViewModel
{
Name = c.Name, Id = c.Id
}).OrderBy(c => c.Name), "Id", "Name", companyLogo.CompanyId);
return View(companyLogo);
}
Console.WriteLine("Rendering {0}", rendered);
if(svg == null)
{
svg = new SKSvg();
svg.Load(svgMs);
}
SKRect svgSize = svg.Picture.CullRect;
float svgMax = Math.Max(svgSize.Width, svgSize.Height);
float canvasMin = 32 * multiplier;
float scale = canvasMin / svgMax;
var matrix = SKMatrix.MakeScale(scale, scale);
var bitmap = new SKBitmap((int)(svgSize.Width * scale), (int)(svgSize.Height * scale));
var canvas = new SKCanvas(bitmap);
canvas.DrawPicture(svg.Picture, ref matrix);
canvas.Flush();
var image = SKImage.FromBitmap(bitmap);
SKData data = image.Encode(skFormat, 100);
var outfs = new FileStream(rendered, FileMode.CreateNew);
data.SaveTo(outfs);
outfs.Close();
svgMs.Position = 0;
}
}
}
catch(IOException e)
{
Console.WriteLine(e);
throw;
}
}
_context.Add(companyLogo);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
// GET: CompanyLogos/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if(id == null)
return NotFound();
CompanyLogo companyLogo = await _context.CompanyLogos.FirstOrDefaultAsync(c => c.Id == id);
if(companyLogo == null)
return NotFound();
ViewData["CompanyId"] =
new SelectList(_context.Companies.OrderBy(l => l.Name), "Id", "Name", companyLogo.CompanyId);
return View(companyLogo);
}
// POST: CompanyLogos/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost, ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,CompanyId,Year,Guid")] CompanyLogo companyLogo)
{
if(id != companyLogo.Id)
return NotFound();
if(ModelState.IsValid)
{
try
{
_context.Update(companyLogo);
await _context.SaveChangesAsync();
}
catch(DbUpdateConcurrencyException)
{
if(!CompanyLogoExists(companyLogo.Id))
return NotFound();
throw;
}
return RedirectToAction(nameof(Index));
}
ViewData["CompanyId"] =
new SelectList(_context.Companies.OrderBy(l => l.Name), "Id", "Name", companyLogo.CompanyId);
return View(companyLogo);
}
// GET: CompanyLogos/Delete/5
public async Task<IActionResult> Delete(int? id)
{
if(id == null)
return NotFound();
CompanyLogo companyLogo =
await _context.CompanyLogos.Include(c => c.Company).FirstOrDefaultAsync(m => m.Id == id);
if(companyLogo == null)
return NotFound();
return View(companyLogo);
}
// POST: CompanyLogos/Delete/5
[HttpPost, ActionName("Delete"), ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
CompanyLogo companyLogo = await _context.CompanyLogos.FirstOrDefaultAsync(m => m.Id == id);
if(companyLogo == null)
return NotFound();
_context.CompanyLogos.Remove(companyLogo);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
bool CompanyLogoExists(int id) => _context.CompanyLogos.Any(e => e.Id == id);
}
}

View File

@@ -1,51 +0,0 @@
@model Marechai.Database.Models.CompanyLogo
@{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>Company logo</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create" enctype="multipart/form-data">
<div asp-validation-summary="ModelOnly" class="text-danger">
</div>
<div class="form-group">
<label asp-for="Company" class="control-label">
</label>
<select asp-for="CompanyId" class="form-control" asp-items="ViewBag.CompanyId">
</select>
</div>
<div class="form-group">
<label asp-for="Year" class="control-label">
</label>
<input asp-for="Year" class="form-control" />
<span asp-validation-for="Year" class="text-danger">
</span>
</div>
<div class="form-group">
<div class="col-md-10">
<label asp-for="SvgLogo" class="control-label">
</label>
<input asp-for="SvgLogo" class="form-control" />
<span asp-validation-for="SvgLogo" class="text-danger">
</span>
@if (Model?.ErrorMessage != null)
{
<span class="text-danger">@Model.ErrorMessage</span>
}
</div>
</div>
<div class="form-group">
<input class="btn btn-primary" type="submit" value="Create" />
<a asp-action="Index" class="btn btn-secondary">
Back to List
</a>
</div>
</form>
</div>
</div>
@section Scripts {
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}

View File

@@ -95,20 +95,8 @@ namespace Marechai.Helpers
svg.Load(file);
}
SKRect svgSize = svg.Picture.CullRect;
float svgMax = Math.Max(svgSize.Width, svgSize.Height);
float canvasMin = 32 * multiplier;
float scale = canvasMin / svgMax;
var matrix = SKMatrix.MakeScale(scale, scale);
var bitmap = new SKBitmap((int)(svgSize.Width * scale), (int)(svgSize.Height * scale));
var canvas = new SKCanvas(bitmap);
canvas.Clear();
canvas.DrawPicture(svg.Picture, ref matrix);
canvas.Flush();
var image = SKImage.FromBitmap(bitmap);
SKData data = image.Encode(skFormat, 100);
var outFs = new FileStream(rendered, FileMode.CreateNew);
data.SaveTo(outFs);
var outFs = new FileStream(rendered, FileMode.CreateNew);
RenderSvg(svg, outFs, skFormat, 32, multiplier);
outFs.Close();
}
}
@@ -210,20 +198,8 @@ namespace Marechai.Helpers
svg.Load(file);
}
SKRect svgSize = svg.Picture.CullRect;
float svgMax = Math.Max(svgSize.Width, svgSize.Height);
float canvasMin = minSize * multiplier;
float scale = canvasMin / svgMax;
var matrix = SKMatrix.MakeScale(scale, scale);
var bitmap = new SKBitmap((int)(svgSize.Width * scale), (int)(svgSize.Height * scale));
var canvas = new SKCanvas(bitmap);
canvas.Clear();
canvas.DrawPicture(svg.Picture, ref matrix);
canvas.Flush();
var image = SKImage.FromBitmap(bitmap);
SKData data = image.Encode(skFormat, 100);
var outFs = new FileStream(rendered, FileMode.CreateNew);
data.SaveTo(outFs);
var outFs = new FileStream(rendered, FileMode.CreateNew);
RenderSvg(svg, outFs, skFormat, minSize, multiplier);
outFs.Close();
}
}
@@ -232,5 +208,89 @@ namespace Marechai.Helpers
File.Move(file, $"wwwroot/assets/logos/{guid}.svg");
}
}
public static void RenderSvg(SKSvg svg, Stream outStream, SKEncodedImageFormat skFormat, int minSize,
int multiplier)
{
SKRect svgSize = svg.Picture.CullRect;
float svgMax = Math.Max(svgSize.Width, svgSize.Height);
float canvasMin = minSize * multiplier;
float scale = canvasMin / svgMax;
var matrix = SKMatrix.MakeScale(scale, scale);
var bitmap = new SKBitmap((int)(svgSize.Width * scale), (int)(svgSize.Height * scale));
var canvas = new SKCanvas(bitmap);
canvas.Clear();
canvas.DrawPicture(svg.Picture, ref matrix);
canvas.Flush();
var image = SKImage.FromBitmap(bitmap);
SKData data = image.Encode(skFormat, 100);
data.SaveTo(outStream);
}
// TODO: Reduce code duplication
public static void RenderCompanyLogo(Guid guid, Stream svgStream, string wwwroot)
{
SKSvg svg = null;
foreach(int minSize in new[]
{
256, 32
})
{
foreach(string format in new[]
{
"png", "webp"
})
{
string outDir = minSize == 32 ? Path.Combine(wwwroot, "assets/logos/thumbs", format)
: Path.Combine(wwwroot, "assets/logos", format);
if(!Directory.Exists(outDir))
Directory.CreateDirectory(outDir);
SKEncodedImageFormat skFormat;
switch(format)
{
case"webp":
skFormat = SKEncodedImageFormat.Webp;
break;
default:
skFormat = SKEncodedImageFormat.Png;
break;
}
foreach(int multiplier in new[]
{
1, 2, 3
})
{
string outPath = Path.Combine(outDir, $"{multiplier}x");
if(!Directory.Exists(outPath))
Directory.CreateDirectory(outPath);
string rendered = Path.Combine(outPath, $"{guid}.{format}");
if(File.Exists(rendered))
continue;
Console.WriteLine("Rendering {0}", rendered);
if(svg == null)
{
svg = new SKSvg();
svg.Load(svgStream);
}
var outFs = new FileStream(rendered, FileMode.CreateNew);
RenderSvg(svg, outFs, skFormat, minSize, multiplier);
outFs.Close();
}
}
}
}
}
}

View File

@@ -2,7 +2,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<Version>3.0.99.1275</Version>
<Version>3.0.99.1364</Version>
<Company>Canary Islands Computer Museum</Company>
<Copyright>Copyright © 2003-2020 Natalia Portillo</Copyright>
<Product>Canary Islands Computer Museum Website</Product>
@@ -31,6 +31,7 @@
<PackageReference Include="SkiaSharp.Extended" Version="1.60.0" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="1.68.3" />
<PackageReference Include="Svg.Skia" Version="0.3.0" />
<PackageReference Include="Tewr.Blazor.FileReader" Version="1.5.0.20109" />
<PackageReference Include="Unclassified.NetRevisionTask" Version="0.3.0" />
</ItemGroup>
<ItemGroup>
@@ -142,5 +143,6 @@
<_ContentIncludedByDefault Remove="Areas\Admin\Views\Processors\Create.cshtml" />
<_ContentIncludedByDefault Remove="Areas\Admin\Views\Screens\Create.cshtml" />
<_ContentIncludedByDefault Remove="Areas\Admin\Views\SoundSynths\Create.cshtml" />
<_ContentIncludedByDefault Remove="Areas\Admin\Views\CompanyLogos\Create.cshtml" />
</ItemGroup>
</Project>

View File

@@ -40,7 +40,10 @@
@inject NavigationManager NavigationManager
@inject CompanyLogosService CompanyLogosService
@inject IWebHostEnvironment Host
@inject IFileReaderService FileReaderService;
@attribute [Authorize(Roles = "UberAdmin, Admin")]
<h3>@L["Company details"]</h3>
<hr />
@@ -316,7 +319,7 @@
<a href="/admin/companies" class="btn btn-secondary">@L["Back to list"]</a>
@if (!_editing)
{
<a class="btn btn-success">@L["Upload new logo"]</a>
<Button Color="Color.Success" Clicked="@ShowUploadModal">@L["Upload new logo"]</Button>
}
</div>
@if (_logos.Count > 0)
@@ -385,7 +388,14 @@
<CloseButton Clicked="@HideDeleteModal"/>
</ModalHeader>
<ModalBody>
<Text>@string.Format(@L["Are you sure you want to delete the company logo introduced in {0}?"], _currentLogo?.Year)</Text>
@if (_currentLogo?.Year != null)
{
<Text>@string.Format(L["Are you sure you want to delete the company logo introduced in {0}?"], _currentLogo?.Year)</Text>
}
else
{
<Text>@L["Are you sure you want to delete the company logo you selected?"]</Text>
}
</ModalBody>
<ModalFooter>
<Button Color="Color.Primary" Clicked="@HideDeleteModal" Disabled="@_deleteInProgress">@L["Cancel"]</Button>
@@ -436,3 +446,89 @@
</ModalFooter>
</ModalContent>
</Modal>
<Modal @ref="_frmUpload" IsCentered="true" Closing="@UploadModalClosing">
<ModalBackdrop/>
<ModalContent Centered="true" Size="ModalSize.ExtraLarge">
<ModalHeader>
<ModalTitle>@L["Upload new logo"]</ModalTitle>
<CloseButton Clicked="@HideUploadModal"/>
</ModalHeader>
<ModalBody>
<Field>
@if (!_uploaded)
{
@if (!_uploading)
{
<FieldLabel>@L["Choose SVG (version 1.1 or lower) logo file"]</FieldLabel>
<input type="file" @ref=_inputUpload Accept=".svg" /><br/>
<Button Color="Color.Success" Clicked="@UploadFile" Disabled="@_uploading">@L["Upload"]</Button><br/>
}
else
{
@L["Uploading..."]
<div class="progress">
<div class="progress-bar" role="progressbar" style="width: @(_progressValue)%;" aria-valuenow="@_progressValue" aria-valuemin="0" aria-valuemax="100">@($"{_progressValue:F2}")%</div>
</div>
}
@if (_uploadError)
{
<span class="text-danger">@_uploadErrorMessage</span>
}
}
else
{
<table class="table table-dark">
<thead>
<tr>
<th>@L["SVG"]</th>
<th>@L["PNG"]</th>
<th>@L["WebP"]</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<span class="text-warning">@L["May not show properly"]</span>
</td>
<td></td>
<td>
<span class="text-warning">@L["May not show properly"]</span>
</td>
</tr>
<tr>
<td>
<img src="@_uploadedSvgData" alt="" height="auto" width="auto" style="max-height: 256px; max-width: 256px" />
</td>
<td>
<img src="@_uploadedPngData" alt="" height="auto" width="auto" style="max-height: 256px; max-width: 256px" />
</td>
<td>
<img src="@_uploadedWebpData" alt="" height="auto" width="auto" style="max-height: 256px; max-width: 256px" />
</td>
</tr>
</tbody>
</table>
<Field>
<FieldLabel>@L["Year logo came in use"]</FieldLabel>
<Check TValue="bool" @bind-Checked="@_unknownLogoYear">@L["Unknown (logo year)"]</Check>
@if (!_unknownLogoYear)
{
<Validation Validator="@ValidateLogoYear">
<NumericEdit TValue="int?" Decimals="0" @bind-Value="@_currentLogoYear">
<Feedback>
<ValidationError>@L["Please enter a valid year."]</ValidationError>
</Feedback>
</NumericEdit>
</Validation>
}
</Field>
}
</Field>
</ModalBody>
<ModalFooter>
<Button Color="Color.Primary" Clicked="@HideUploadModal" Disabled="_uploading || _savingLogo">@L["Cancel"]</Button>
<Button Color="Color.Success" Clicked="@ConfirmUpload" Disabled="!_uploaded || _savingLogo">@L["Save"]</Button>
</ModalFooter>
</ModalContent>
</Modal>

View File

@@ -1,18 +1,25 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Blazor.FileReader;
using Blazorise;
using Marechai.Database;
using Marechai.Database.Models;
using Marechai.Helpers;
using Marechai.Shared;
using Marechai.ViewModels;
using Microsoft.AspNetCore.Components;
using SkiaSharp;
using Svg.Skia;
namespace Marechai.Pages.Admin.Details
{
public partial class Company
{
const int _maxUploadSize = 5 * 1048576;
List<CompanyViewModel> _companies;
List<Iso31661Numeric> _countries;
bool _creating;
@@ -22,23 +29,35 @@ namespace Marechai.Pages.Admin.Details
bool _editing;
Modal _frmDelete;
Modal _frmLogoYear;
Modal _frmUpload;
ElementReference _inputUpload;
bool _loaded;
List<CompanyLogo> _logos;
CompanyViewModel _model;
bool _unknownAddress;
bool _unknownCity;
bool _unknownCountry;
bool _unknownFacebook;
bool _unknownFounded;
bool _unknownLogoYear;
bool _unknownPostalCode;
bool _unknownProvince;
bool _unknownSold;
bool _unknownSoldTo;
bool _unknownTwitter;
bool _unknownWebsite;
double _progressValue;
bool _yearChangeInProgress;
bool _savingLogo;
bool _unknownAddress;
bool _unknownCity;
bool _unknownCountry;
bool _unknownFacebook;
bool _unknownFounded;
bool _unknownLogoYear;
bool _unknownPostalCode;
bool _unknownProvince;
bool _unknownSold;
bool _unknownSoldTo;
bool _unknownTwitter;
bool _unknownWebsite;
bool _uploaded;
string _uploadedPngData;
string _uploadedSvgData;
string _uploadedWebpData;
bool _uploadError;
string _uploadErrorMessage;
bool _uploading;
MemoryStream _uploadMs;
bool _yearChangeInProgress;
[Parameter]
public int Id { get; set; }
@@ -323,5 +342,216 @@ namespace Marechai.Pages.Admin.Details
else
e.Status = ValidationStatus.Success;
}
void ShowUploadModal()
{
_uploadError = false;
_uploadErrorMessage = "";
_uploading = false;
_uploadMs = null;
_uploaded = false;
_progressValue = 0;
_uploadedSvgData = "";
_uploadedPngData = "";
_uploadedWebpData = "";
_savingLogo = false;
_frmUpload.Show();
}
void HideUploadModal() => _frmUpload.Hide();
void UploadModalClosing(ModalClosingEventArgs e)
{
_uploadError = false;
_uploadErrorMessage = "";
_uploading = false;
_uploadMs = null;
_uploaded = false;
_progressValue = 0;
_uploadedSvgData = "";
_uploadedPngData = "";
_uploadedWebpData = "";
_savingLogo = false;
}
async Task UploadFile()
{
IFileReference file = (await FileReaderService.CreateReference(_inputUpload).EnumerateFilesAsync()).
FirstOrDefault();
if(file is null)
return;
IFileInfo fileInfo = await file.ReadFileInfoAsync();
if(fileInfo.Size > _maxUploadSize)
{
_uploadError = true;
_uploadErrorMessage = L["The selected file is too big."];
return;
}
_uploading = true;
await using AsyncDisposableStream fs = await file.OpenReadAsync();
byte[] buffer = new byte[20480];
try
{
double lastProgress = 0;
int count;
_uploadMs = new MemoryStream();
while((count = await fs.ReadAsync(buffer, 0, buffer.Length)) != 0)
{
await _uploadMs.WriteAsync(buffer, 0, count);
double progress = ((double)fs.Position * 100) / fs.Length;
if(!(progress > lastProgress + 0.01))
continue;
_progressValue = progress;
await InvokeAsync(StateHasChanged);
await Task.Yield();
lastProgress = progress;
}
}
catch(Exception)
{
_uploading = false;
_uploadError = true;
_uploadErrorMessage = L["There was an error uploading the file."];
return;
}
_uploading = false;
await InvokeAsync(StateHasChanged);
await Task.Yield();
bool validSvg = true;
buffer = new byte[6];
_uploadMs.Position = 0;
_uploadMs.Read(buffer, 0, 6);
if(!Encoding.UTF8.GetString(buffer).StartsWith("<?xml", StringComparison.InvariantCulture) &&
!Encoding.UTF8.GetString(buffer).StartsWith("<svg", StringComparison.InvariantCulture))
validSvg = false;
_uploadMs.Seek(-6, SeekOrigin.End);
_uploadMs.Read(buffer, 0, 6);
// LF
if(buffer[^1] == 0x0A)
{
_uploadMs.Seek(-7, SeekOrigin.End);
_uploadMs.Read(buffer, 0, 6);
}
// CR
if(buffer[^1] == 0x0D)
{
_uploadMs.Seek(-8, SeekOrigin.End);
_uploadMs.Read(buffer, 0, 6);
}
if(!Encoding.UTF8.GetString(buffer).StartsWith("</svg>", StringComparison.InvariantCulture))
validSvg = false;
if(!validSvg)
{
_uploadMs = null;
_uploadError = true;
_uploadErrorMessage = L["The uploaded file is not a SVG file."];
return;
}
_uploadMs.Position = 0;
var svg = new SKSvg();
try
{
svg.Load(_uploadMs);
}
catch(Exception)
{
_uploadMs = null;
_uploadError = true;
_uploadErrorMessage = L["The uploaded file could not be loaded. Is it a correct SVG file?"];
return;
}
var pngStream = new MemoryStream();
var webpStream = new MemoryStream();
try
{
SvgRender.RenderSvg(svg, pngStream, SKEncodedImageFormat.Png, 256, 1);
SvgRender.RenderSvg(svg, webpStream, SKEncodedImageFormat.Webp, 256, 1);
}
catch(Exception)
{
_uploadMs = null;
_uploadError = true;
_uploadErrorMessage = L["An error occuring rendering the uploaded file. Is it a correct SVG file?"];
return;
}
_uploadMs.Position = 0;
pngStream.Position = 0;
webpStream.Position = 0;
_uploadedSvgData = $"data:image/svg+xml;base64,{Convert.ToBase64String(_uploadMs.ToArray())}";
_uploadedPngData = $"data:image/png;base64,{Convert.ToBase64String(pngStream.ToArray())}";
_uploadedWebpData = $"data:image/webp;base64,{Convert.ToBase64String(webpStream.ToArray())}";
_unknownLogoYear = true;
_uploaded = true;
}
async Task ConfirmUpload()
{
_savingLogo = true;
_uploadMs.Position = 0;
var guid = Guid.NewGuid();
try
{
SvgRender.RenderCompanyLogo(guid, _uploadMs, Host.WebRootPath);
var fs = new FileStream(Path.Combine(Host.WebRootPath, "assets/logos", $"{guid}.svg"), FileMode.OpenOrCreate, FileAccess.ReadWrite);
_uploadMs.Position = 0;
_uploadMs.WriteTo(fs);
fs.Close();
}
catch(Exception)
{
_savingLogo = false;
_uploadError = true;
_uploadErrorMessage = L["An error occuring rendering the uploaded file. Is it a correct SVG file?"];
return;
}
await CompanyLogosService.CreateAsync(Id, guid, _unknownLogoYear ? null : _currentLogoYear);
_logos = await CompanyLogosService.GetByCompany(Id);
_frmUpload.Hide();
// Yield thread to let UI to update
await Task.Yield();
// Tell we finished loading
StateHasChanged();
}
}
}

View File

@@ -446,4 +446,56 @@
<value>Por favor introduce un año válido.</value>
<comment>Please enter a valid year.</comment>
</data>
<data name="Are you sure you want to delete the company logo you selected?" xml:space="preserve">
<value>¿Estás seguro de que quieres borrar el logo de la compañía que has seleccionado?</value>
<comment>Are you sure you want to delete the company logo you selected?</comment>
</data>
<data name="Choose SVG (version 1.1 or lower) logo file" xml:space="preserve">
<value>Elige un archivo con el logo en format SVG (versión 1.1 o inferior)</value>
<comment>Choose SVG (version 1.1 or lower) logo file</comment>
</data>
<data name="Upload" xml:space="preserve">
<value>Subir</value>
<comment>Upload</comment>
</data>
<data name="Uploading..." xml:space="preserve">
<value>Subiendo...</value>
<comment>Uploading...</comment>
</data>
<data name="SVG" xml:space="preserve">
<value>SVG</value>
<comment>SVG</comment>
</data>
<data name="PNG" xml:space="preserve">
<value>PNG</value>
<comment>PNG</comment>
</data>
<data name="WebP" xml:space="preserve">
<value>WebP</value>
<comment>WebP</comment>
</data>
<data name="May not show properly" xml:space="preserve">
<value>Puede que no se muestre correctamente</value>
<comment>May not show properly</comment>
</data>
<data name="The selected file is too big." xml:space="preserve">
<value>El archivo seleccionado es demasiado grande.</value>
<comment>The selected file is too big.</comment>
</data>
<data name="There was an error uploading the file." xml:space="preserve">
<value>Hubo un error subiendo el archivo.</value>
<comment>There was an error uploading the file.</comment>
</data>
<data name="The uploaded file is not a SVG file." xml:space="preserve">
<value>El archivo que has subido no es un archivo SVG.</value>
<comment>The uploaded file is not a SVG file.</comment>
</data>
<data name="The uploaded file could not be loaded. Is it a correct SVG file?" xml:space="preserve">
<value>No se pudo cargar el archivo que has subido. ¿Es un SVG correcto?</value>
<comment>The uploaded file could not be loaded. Is it a correct SVG file?</comment>
</data>
<data name="An error occuring rendering the uploaded file. Is it a correct SVG file?" xml:space="preserve">
<value>Ocurrió un error renderizando el archivo subido. ¿Es un SVG correcto?</value>
<comment>An error occuring rendering the uploaded file. Is it a correct SVG file?</comment>
</data>
</root>

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -84,5 +85,18 @@ namespace Marechai.Services
logo.Year = year;
await _context.SaveChangesAsync();
}
public async Task<int> CreateAsync(int companyId, Guid guid, int? year)
{
var logo = new CompanyLogo
{
Guid = guid, Year = year, CompanyId = companyId
};
await _context.CompanyLogos.AddAsync(logo);
await _context.SaveChangesAsync();
return logo.Id;
}
}
}

View File

@@ -29,6 +29,7 @@
*******************************************************************************/
using System.Globalization;
using Blazor.FileReader;
using Blazorise;
using Blazorise.Bootstrap;
using Blazorise.Icons.FontAwesome;
@@ -83,6 +84,8 @@ namespace Marechai
services.AddLocalization(options => options.ResourcesPath = "Resources");
services.AddFileReaderService();
Register.RegisterServices(services);
}

View File

@@ -12,4 +12,5 @@
@using Microsoft.Extensions.Localization
@using System.IO
@using Microsoft.AspNetCore.Hosting
@using Blazorise
@using Blazorise
@using Blazor.FileReader