mirror of
https://github.com/claunia/marechai.git
synced 2025-12-16 11:04:25 +00:00
Compare commits
5 Commits
80791a8cc9
...
4d30530ef0
| Author | SHA1 | Date | |
|---|---|---|---|
|
4d30530ef0
|
|||
|
e0689684e1
|
|||
|
cfdef93787
|
|||
|
c6cac9e04a
|
|||
|
e2f86b76db
|
@@ -2,6 +2,7 @@ using System.Net.Http;
|
||||
using Marechai.App.Presentation.ViewModels;
|
||||
using Marechai.App.Presentation.Views;
|
||||
using Marechai.App.Services;
|
||||
using Marechai.App.Services.Caching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Uno.Extensions;
|
||||
using Uno.Extensions.Configuration;
|
||||
@@ -106,6 +107,8 @@ public partial class App : Application
|
||||
.ConfigureServices((context, services) =>
|
||||
{
|
||||
// Register application services
|
||||
services.AddSingleton<FlagCache>();
|
||||
services.AddSingleton<CompanyLogoCache>();
|
||||
services.AddSingleton<NewsService>();
|
||||
services.AddSingleton<NewsViewModel>();
|
||||
services.AddSingleton<ComputersService>();
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
Navigation;
|
||||
ThemeService;
|
||||
SkiaRenderer;
|
||||
Svg;
|
||||
</UnoFeatures>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -3,13 +3,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using Marechai.App.Helpers;
|
||||
using Marechai.App.Presentation.Models;
|
||||
using Marechai.App.Services;
|
||||
using Marechai.App.Services.Caching;
|
||||
using Marechai.Data;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using Uno.Extensions.Navigation;
|
||||
|
||||
namespace Marechai.App.Presentation.ViewModels;
|
||||
@@ -17,8 +20,10 @@ namespace Marechai.App.Presentation.ViewModels;
|
||||
public partial class CompanyDetailViewModel : ObservableObject
|
||||
{
|
||||
private readonly CompanyDetailService _companyDetailService;
|
||||
private readonly FlagCache _flagCache;
|
||||
private readonly IStringLocalizer _localizer;
|
||||
private readonly ILogger<CompanyDetailViewModel> _logger;
|
||||
private readonly CompanyLogoCache _logoCache;
|
||||
private readonly INavigator _navigator;
|
||||
|
||||
[ObservableProperty]
|
||||
@@ -48,6 +53,9 @@ public partial class CompanyDetailViewModel : ObservableObject
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<CompanyDetailMachine> _filteredConsoles = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private SvgImageSource? _flagImageSource;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _hasError;
|
||||
|
||||
@@ -57,13 +65,19 @@ public partial class CompanyDetailViewModel : ObservableObject
|
||||
[ObservableProperty]
|
||||
private bool _isLoading;
|
||||
|
||||
[ObservableProperty]
|
||||
private SvgImageSource? _logoImageSource;
|
||||
|
||||
[ObservableProperty]
|
||||
private CompanyDto? _soldToCompany;
|
||||
|
||||
public CompanyDetailViewModel(CompanyDetailService companyDetailService, IStringLocalizer localizer,
|
||||
public CompanyDetailViewModel(CompanyDetailService companyDetailService, FlagCache flagCache,
|
||||
CompanyLogoCache logoCache, IStringLocalizer localizer,
|
||||
ILogger<CompanyDetailViewModel> logger, INavigator navigator)
|
||||
{
|
||||
_companyDetailService = companyDetailService;
|
||||
_flagCache = flagCache;
|
||||
_logoCache = logoCache;
|
||||
_localizer = localizer;
|
||||
_logger = logger;
|
||||
_navigator = navigator;
|
||||
@@ -82,6 +96,16 @@ public partial class CompanyDetailViewModel : ObservableObject
|
||||
/// </summary>
|
||||
public string CompanyFoundedDateDisplay => Company != null ? GetFoundedDateDisplay(Company) : string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether flag content is available
|
||||
/// </summary>
|
||||
public bool HasFlagContent => FlagImageSource != null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether logo content is available
|
||||
/// </summary>
|
||||
public bool HasLogoContent => LogoImageSource != null;
|
||||
|
||||
public IAsyncRelayCommand LoadData { get; }
|
||||
public ICommand GoBackCommand { get; }
|
||||
public IAsyncRelayCommand<CompanyDetailMachine> NavigateToMachineCommand { get; }
|
||||
@@ -94,6 +118,18 @@ public partial class CompanyDetailViewModel : ObservableObject
|
||||
OnPropertyChanged(nameof(CompanyFoundedDateDisplay));
|
||||
}
|
||||
|
||||
partial void OnFlagImageSourceChanged(SvgImageSource? oldValue, SvgImageSource? newValue)
|
||||
{
|
||||
// Notify that HasFlagContent has changed
|
||||
OnPropertyChanged(nameof(HasFlagContent));
|
||||
}
|
||||
|
||||
partial void OnLogoImageSourceChanged(SvgImageSource? oldValue, SvgImageSource? newValue)
|
||||
{
|
||||
// Notify that HasLogoContent has changed
|
||||
OnPropertyChanged(nameof(HasLogoContent));
|
||||
}
|
||||
|
||||
partial void OnComputersFilterTextChanged(string value)
|
||||
{
|
||||
FilterComputers(value);
|
||||
@@ -282,10 +318,12 @@ public partial class CompanyDetailViewModel : ObservableObject
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
ErrorMessage = string.Empty;
|
||||
HasError = false;
|
||||
IsDataLoaded = false;
|
||||
IsLoading = true;
|
||||
ErrorMessage = string.Empty;
|
||||
HasError = false;
|
||||
IsDataLoaded = false;
|
||||
FlagImageSource = null;
|
||||
LogoImageSource = null;
|
||||
|
||||
if(CompanyId <= 0)
|
||||
{
|
||||
@@ -306,13 +344,57 @@ public partial class CompanyDetailViewModel : ObservableObject
|
||||
return;
|
||||
}
|
||||
|
||||
// Load sold-to company if applicable
|
||||
// Load flag if country is available
|
||||
if(Company.CountryId is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var countryCode = (short)UntypedNodeExtractor.ExtractInt(Company.CountryId);
|
||||
Stream? flagStream = await _flagCache.GetFlagAsync(countryCode);
|
||||
|
||||
var flagSource = new SvgImageSource();
|
||||
await flagSource.SetSourceAsync(flagStream.AsRandomAccessStream());
|
||||
FlagImageSource = flagSource;
|
||||
|
||||
_logger.LogInformation("Successfully loaded flag for country code {CountryCode}", countryCode);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError("Failed to load flag for country {CountryId}: {Exception}",
|
||||
Company.CountryId,
|
||||
ex.Message);
|
||||
|
||||
// Continue without flag if loading fails
|
||||
}
|
||||
}
|
||||
|
||||
if(Company.SoldToId != null)
|
||||
{
|
||||
int soldToId = UntypedNodeExtractor.ExtractInt(Company.SoldToId);
|
||||
if(soldToId > 0) SoldToCompany = await _companyDetailService.GetSoldToCompanyAsync(soldToId);
|
||||
}
|
||||
|
||||
// Load logo if available
|
||||
if(Company.LastLogo.HasValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
Stream? logoStream = await _logoCache.GetFlagAsync(Company.LastLogo.Value);
|
||||
|
||||
var logoSource = new SvgImageSource();
|
||||
await logoSource.SetSourceAsync(logoStream.AsRandomAccessStream());
|
||||
LogoImageSource = logoSource;
|
||||
|
||||
_logger.LogInformation("Successfully loaded logo for company {CompanyId}", CompanyId);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError("Failed to load logo for company {CompanyId}: {Exception}", CompanyId, ex.Message);
|
||||
|
||||
// Continue without logo if loading fails
|
||||
}
|
||||
}
|
||||
|
||||
// Load computers and consoles made by this company
|
||||
List<MachineDto> machines = await _companyDetailService.GetComputersByCompanyAsync(CompanyId);
|
||||
Computers.Clear();
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
NavigationCacheMode="Required"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Page.Resources>
|
||||
</Page.Resources>
|
||||
|
||||
<Grid utu:SafeArea.Insets="VisibleBounds">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
@@ -29,6 +32,16 @@
|
||||
<StackPanel Padding="16"
|
||||
Spacing="16">
|
||||
|
||||
<!-- Logo Display (Top Center) -->
|
||||
<Image MaxWidth="96"
|
||||
MaxHeight="96"
|
||||
Stretch="Uniform"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="0,0,0,8"
|
||||
Source="{Binding LogoImageSource}"
|
||||
Visibility="{Binding HasLogoContent, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
|
||||
<!-- Loading State -->
|
||||
<StackPanel Visibility="{Binding IsLoading}"
|
||||
VerticalAlignment="Center"
|
||||
@@ -113,14 +126,35 @@
|
||||
<!-- Country -->
|
||||
<StackPanel Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="12"
|
||||
Spacing="8">
|
||||
<TextBlock Text="Country"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding Company.Country}"
|
||||
FontSize="14" />
|
||||
Padding="12">
|
||||
<Grid ColumnSpacing="12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Country Name and Label -->
|
||||
<StackPanel Grid.Column="0"
|
||||
Spacing="4">
|
||||
<TextBlock Text="Country"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<TextBlock Text="{Binding Company.Country}"
|
||||
FontSize="14"
|
||||
VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Country Flag -->
|
||||
<Image Grid.Column="1"
|
||||
Width="48"
|
||||
Height="32"
|
||||
Stretch="UniformToFill"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Right"
|
||||
Source="{Binding FlagImageSource}"
|
||||
Visibility="{Binding HasFlagContent, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Address -->
|
||||
|
||||
64
Marechai.App/Services/Caching/FlagCache.cs
Normal file
64
Marechai.App/Services/Caching/FlagCache.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Storage;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Marechai.App.Services.Caching;
|
||||
|
||||
public sealed class FlagCache
|
||||
{
|
||||
readonly IConfiguration _configuration;
|
||||
StorageFolder _flagsFolder;
|
||||
|
||||
public FlagCache(IConfiguration configuration)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_ = EnsureFolderExistAsync();
|
||||
}
|
||||
|
||||
async Task EnsureFolderExistAsync()
|
||||
{
|
||||
StorageFolder localFolder = ApplicationData.Current.LocalCacheFolder;
|
||||
_flagsFolder = await localFolder.CreateFolderAsync("flags", CreationCollisionOption.OpenIfExists);
|
||||
}
|
||||
|
||||
public async Task<Stream> GetFlagAsync(short countryCode)
|
||||
{
|
||||
var filename = $"{countryCode:D3}.svg";
|
||||
|
||||
Stream retStream;
|
||||
|
||||
if(await _flagsFolder.TryGetItemAsync(filename) is StorageFile file)
|
||||
{
|
||||
retStream = await file.OpenStreamForReadAsync();
|
||||
|
||||
return retStream;
|
||||
}
|
||||
|
||||
await CacheFlagAsync(countryCode);
|
||||
|
||||
file = await _flagsFolder.GetFileAsync(filename);
|
||||
|
||||
retStream = await file.OpenStreamForReadAsync();
|
||||
|
||||
return retStream;
|
||||
}
|
||||
|
||||
async Task CacheFlagAsync(short countryCode)
|
||||
{
|
||||
var filename = $"{countryCode:D3}.svg";
|
||||
string baseUrl = _configuration.GetSection("ApiClient:Url").Value;
|
||||
string flagUrl = baseUrl + $"/assets/flags/countries/{filename}";
|
||||
using var httpClient = new HttpClient();
|
||||
using HttpResponseMessage response = await httpClient.GetAsync(flagUrl);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
using Stream stream = await response.Content.ReadAsStreamAsync();
|
||||
StorageFile file = await _flagsFolder.CreateFileAsync(filename, CreationCollisionOption.ReplaceExisting);
|
||||
|
||||
using Stream fileStream = await file.OpenStreamForWriteAsync();
|
||||
await stream.CopyToAsync(fileStream);
|
||||
}
|
||||
}
|
||||
64
Marechai.App/Services/Caching/LogoCache.cs
Normal file
64
Marechai.App/Services/Caching/LogoCache.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Storage;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Marechai.App.Services.Caching;
|
||||
|
||||
public sealed class CompanyLogoCache
|
||||
{
|
||||
readonly IConfiguration _configuration;
|
||||
StorageFolder _flagsFolder;
|
||||
|
||||
public CompanyLogoCache(IConfiguration configuration)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_ = EnsureFolderExistAsync();
|
||||
}
|
||||
|
||||
async Task EnsureFolderExistAsync()
|
||||
{
|
||||
StorageFolder localFolder = ApplicationData.Current.LocalCacheFolder;
|
||||
_flagsFolder = await localFolder.CreateFolderAsync("logos", CreationCollisionOption.OpenIfExists);
|
||||
}
|
||||
|
||||
public async Task<Stream> GetFlagAsync(Guid companyLogoId)
|
||||
{
|
||||
var filename = $"{companyLogoId}.svg";
|
||||
|
||||
Stream retStream;
|
||||
|
||||
if(await _flagsFolder.TryGetItemAsync(filename) is StorageFile file)
|
||||
{
|
||||
retStream = await file.OpenStreamForReadAsync();
|
||||
|
||||
return retStream;
|
||||
}
|
||||
|
||||
await CacheFlagAsync(companyLogoId);
|
||||
|
||||
file = await _flagsFolder.GetFileAsync(filename);
|
||||
|
||||
retStream = await file.OpenStreamForReadAsync();
|
||||
|
||||
return retStream;
|
||||
}
|
||||
|
||||
async Task CacheFlagAsync(Guid companyLogoId)
|
||||
{
|
||||
var filename = $"{companyLogoId}.svg";
|
||||
string baseUrl = _configuration.GetSection("ApiClient:Url").Value;
|
||||
string flagUrl = baseUrl + $"/assets/logos/{filename}";
|
||||
using var httpClient = new HttpClient();
|
||||
using HttpResponseMessage response = await httpClient.GetAsync(flagUrl);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
using Stream stream = await response.Content.ReadAsStreamAsync();
|
||||
StorageFile file = await _flagsFolder.CreateFileAsync(filename, CreationCollisionOption.ReplaceExisting);
|
||||
|
||||
using Stream fileStream = await file.OpenStreamForWriteAsync();
|
||||
await stream.CopyToAsync(fileStream);
|
||||
}
|
||||
}
|
||||
@@ -36,11 +36,11 @@ public class Photos
|
||||
{
|
||||
public delegate Task ConversionFinished(bool result);
|
||||
|
||||
public static void EnsureCreated(string webRootPath, bool scan, string item)
|
||||
public static void EnsureCreated(string assetRootPath, bool scan, string item)
|
||||
{
|
||||
List<string> paths = [];
|
||||
|
||||
string photosRoot = Path.Combine(webRootPath, "assets", scan ? "scan" : "photos");
|
||||
string photosRoot = Path.Combine(assetRootPath, scan ? "scan" : "photos");
|
||||
string itemPhotosRoot = Path.Combine(photosRoot, item);
|
||||
string itemThumbsRoot = Path.Combine(itemPhotosRoot, "thumbs");
|
||||
string itemOriginalPhotosRoot = Path.Combine(itemPhotosRoot, "originals");
|
||||
@@ -88,14 +88,14 @@ public class Photos
|
||||
foreach(string path in paths.Where(path => !Directory.Exists(path))) Directory.CreateDirectory(path);
|
||||
}
|
||||
|
||||
public static bool Convert(string webRootPath, Guid id, string originalPath, string sourceFormat,
|
||||
public static bool Convert(string assetRootPath, Guid id, string originalPath, string sourceFormat,
|
||||
string outputFormat, string resolution, bool thumbnail, bool scan, string item)
|
||||
{
|
||||
outputFormat = outputFormat.ToLowerInvariant();
|
||||
resolution = resolution.ToLowerInvariant();
|
||||
sourceFormat = sourceFormat.ToLowerInvariant();
|
||||
|
||||
string outputPath = Path.Combine(webRootPath, "assets", scan ? "scans" : "photos", item);
|
||||
string outputPath = Path.Combine(assetRootPath, scan ? "scans" : "photos", item);
|
||||
int width, height;
|
||||
|
||||
if(thumbnail) outputPath = Path.Combine(outputPath, "thumbs");
|
||||
@@ -268,12 +268,12 @@ public class Photos
|
||||
}
|
||||
}
|
||||
|
||||
public void ConversionWorker(string webRootPath, Guid id, string originalFilePath, string sourceFormat, bool scan,
|
||||
public void ConversionWorker(string assetRootPath, Guid id, string originalFilePath, string sourceFormat, bool scan,
|
||||
string item)
|
||||
{
|
||||
List<Task> pool =
|
||||
[
|
||||
new(() => FinishedRenderingJpeg4kThumbnail?.Invoke(Convert(webRootPath,
|
||||
new(() => FinishedRenderingJpeg4kThumbnail?.Invoke(Convert(assetRootPath,
|
||||
id,
|
||||
originalFilePath,
|
||||
sourceFormat,
|
||||
@@ -282,7 +282,7 @@ public class Photos
|
||||
true,
|
||||
scan,
|
||||
item))),
|
||||
new(() => FinishedRenderingJpeg1440Thumbnail?.Invoke(Convert(webRootPath,
|
||||
new(() => FinishedRenderingJpeg1440Thumbnail?.Invoke(Convert(assetRootPath,
|
||||
id,
|
||||
originalFilePath,
|
||||
sourceFormat,
|
||||
@@ -291,7 +291,7 @@ public class Photos
|
||||
true,
|
||||
scan,
|
||||
item))),
|
||||
new(() => FinishedRenderingJpegHdThumbnail?.Invoke(Convert(webRootPath,
|
||||
new(() => FinishedRenderingJpegHdThumbnail?.Invoke(Convert(assetRootPath,
|
||||
id,
|
||||
originalFilePath,
|
||||
sourceFormat,
|
||||
@@ -300,7 +300,7 @@ public class Photos
|
||||
true,
|
||||
scan,
|
||||
item))),
|
||||
new(() => FinishedRenderingJpeg4K?.Invoke(Convert(webRootPath,
|
||||
new(() => FinishedRenderingJpeg4K?.Invoke(Convert(assetRootPath,
|
||||
id,
|
||||
originalFilePath,
|
||||
sourceFormat,
|
||||
@@ -309,7 +309,7 @@ public class Photos
|
||||
false,
|
||||
scan,
|
||||
item))),
|
||||
new(() => FinishedRenderingJpeg1440?.Invoke(Convert(webRootPath,
|
||||
new(() => FinishedRenderingJpeg1440?.Invoke(Convert(assetRootPath,
|
||||
id,
|
||||
originalFilePath,
|
||||
sourceFormat,
|
||||
@@ -318,7 +318,7 @@ public class Photos
|
||||
false,
|
||||
scan,
|
||||
item))),
|
||||
new(() => FinishedRenderingJpegHd?.Invoke(Convert(webRootPath,
|
||||
new(() => FinishedRenderingJpegHd?.Invoke(Convert(assetRootPath,
|
||||
id,
|
||||
originalFilePath,
|
||||
sourceFormat,
|
||||
@@ -327,7 +327,7 @@ public class Photos
|
||||
false,
|
||||
scan,
|
||||
item))),
|
||||
new(() => FinishedRenderingJp2k4kThumbnail?.Invoke(Convert(webRootPath,
|
||||
new(() => FinishedRenderingJp2k4kThumbnail?.Invoke(Convert(assetRootPath,
|
||||
id,
|
||||
originalFilePath,
|
||||
sourceFormat,
|
||||
@@ -336,7 +336,7 @@ public class Photos
|
||||
true,
|
||||
scan,
|
||||
item))),
|
||||
new(() => FinishedRenderingJp2k1440Thumbnail?.Invoke(Convert(webRootPath,
|
||||
new(() => FinishedRenderingJp2k1440Thumbnail?.Invoke(Convert(assetRootPath,
|
||||
id,
|
||||
originalFilePath,
|
||||
sourceFormat,
|
||||
@@ -345,7 +345,7 @@ public class Photos
|
||||
true,
|
||||
scan,
|
||||
item))),
|
||||
new(() => FinishedRenderingJp2kHdThumbnail?.Invoke(Convert(webRootPath,
|
||||
new(() => FinishedRenderingJp2kHdThumbnail?.Invoke(Convert(assetRootPath,
|
||||
id,
|
||||
originalFilePath,
|
||||
sourceFormat,
|
||||
@@ -354,7 +354,7 @@ public class Photos
|
||||
true,
|
||||
scan,
|
||||
item))),
|
||||
new(() => FinishedRenderingJp2k4k?.Invoke(Convert(webRootPath,
|
||||
new(() => FinishedRenderingJp2k4k?.Invoke(Convert(assetRootPath,
|
||||
id,
|
||||
originalFilePath,
|
||||
sourceFormat,
|
||||
@@ -363,7 +363,7 @@ public class Photos
|
||||
false,
|
||||
scan,
|
||||
item))),
|
||||
new(() => FinishedRenderingJp2k1440?.Invoke(Convert(webRootPath,
|
||||
new(() => FinishedRenderingJp2k1440?.Invoke(Convert(assetRootPath,
|
||||
id,
|
||||
originalFilePath,
|
||||
sourceFormat,
|
||||
@@ -372,7 +372,7 @@ public class Photos
|
||||
false,
|
||||
scan,
|
||||
item))),
|
||||
new(() => FinishedRenderingJp2kHd?.Invoke(Convert(webRootPath,
|
||||
new(() => FinishedRenderingJp2kHd?.Invoke(Convert(assetRootPath,
|
||||
id,
|
||||
originalFilePath,
|
||||
sourceFormat,
|
||||
@@ -381,7 +381,7 @@ public class Photos
|
||||
false,
|
||||
scan,
|
||||
item))),
|
||||
new(() => FinishedRenderingWebp4kThumbnail?.Invoke(Convert(webRootPath,
|
||||
new(() => FinishedRenderingWebp4kThumbnail?.Invoke(Convert(assetRootPath,
|
||||
id,
|
||||
originalFilePath,
|
||||
sourceFormat,
|
||||
@@ -390,7 +390,7 @@ public class Photos
|
||||
true,
|
||||
scan,
|
||||
item))),
|
||||
new(() => FinishedRenderingWebp1440Thumbnail?.Invoke(Convert(webRootPath,
|
||||
new(() => FinishedRenderingWebp1440Thumbnail?.Invoke(Convert(assetRootPath,
|
||||
id,
|
||||
originalFilePath,
|
||||
sourceFormat,
|
||||
@@ -399,7 +399,7 @@ public class Photos
|
||||
true,
|
||||
scan,
|
||||
item))),
|
||||
new(() => FinishedRenderingWebpHdThumbnail?.Invoke(Convert(webRootPath,
|
||||
new(() => FinishedRenderingWebpHdThumbnail?.Invoke(Convert(assetRootPath,
|
||||
id,
|
||||
originalFilePath,
|
||||
sourceFormat,
|
||||
@@ -408,7 +408,7 @@ public class Photos
|
||||
true,
|
||||
scan,
|
||||
item))),
|
||||
new(() => FinishedRenderingWebp4k?.Invoke(Convert(webRootPath,
|
||||
new(() => FinishedRenderingWebp4k?.Invoke(Convert(assetRootPath,
|
||||
id,
|
||||
originalFilePath,
|
||||
sourceFormat,
|
||||
@@ -417,7 +417,7 @@ public class Photos
|
||||
false,
|
||||
scan,
|
||||
item))),
|
||||
new(() => FinishedRenderingWebp1440?.Invoke(Convert(webRootPath,
|
||||
new(() => FinishedRenderingWebp1440?.Invoke(Convert(assetRootPath,
|
||||
id,
|
||||
originalFilePath,
|
||||
sourceFormat,
|
||||
@@ -426,7 +426,7 @@ public class Photos
|
||||
false,
|
||||
scan,
|
||||
item))),
|
||||
new(() => FinishedRenderingWebpHd?.Invoke(Convert(webRootPath,
|
||||
new(() => FinishedRenderingWebpHd?.Invoke(Convert(assetRootPath,
|
||||
id,
|
||||
originalFilePath,
|
||||
sourceFormat,
|
||||
@@ -435,7 +435,7 @@ public class Photos
|
||||
false,
|
||||
scan,
|
||||
item))),
|
||||
new(() => FinishedRenderingHeif4kThumbnail?.Invoke(Convert(webRootPath,
|
||||
new(() => FinishedRenderingHeif4kThumbnail?.Invoke(Convert(assetRootPath,
|
||||
id,
|
||||
originalFilePath,
|
||||
sourceFormat,
|
||||
@@ -444,7 +444,7 @@ public class Photos
|
||||
true,
|
||||
scan,
|
||||
item))),
|
||||
new(() => FinishedRenderingHeif1440Thumbnail?.Invoke(Convert(webRootPath,
|
||||
new(() => FinishedRenderingHeif1440Thumbnail?.Invoke(Convert(assetRootPath,
|
||||
id,
|
||||
originalFilePath,
|
||||
sourceFormat,
|
||||
@@ -453,7 +453,7 @@ public class Photos
|
||||
true,
|
||||
scan,
|
||||
item))),
|
||||
new(() => FinishedRenderingHeifHdThumbnail?.Invoke(Convert(webRootPath,
|
||||
new(() => FinishedRenderingHeifHdThumbnail?.Invoke(Convert(assetRootPath,
|
||||
id,
|
||||
originalFilePath,
|
||||
sourceFormat,
|
||||
@@ -462,7 +462,7 @@ public class Photos
|
||||
true,
|
||||
scan,
|
||||
item))),
|
||||
new(() => FinishedRenderingHeif4K?.Invoke(Convert(webRootPath,
|
||||
new(() => FinishedRenderingHeif4K?.Invoke(Convert(assetRootPath,
|
||||
id,
|
||||
originalFilePath,
|
||||
sourceFormat,
|
||||
@@ -471,7 +471,7 @@ public class Photos
|
||||
false,
|
||||
scan,
|
||||
item))),
|
||||
new(() => FinishedRenderingHeif1440?.Invoke(Convert(webRootPath,
|
||||
new(() => FinishedRenderingHeif1440?.Invoke(Convert(assetRootPath,
|
||||
id,
|
||||
originalFilePath,
|
||||
sourceFormat,
|
||||
@@ -480,7 +480,7 @@ public class Photos
|
||||
false,
|
||||
scan,
|
||||
item))),
|
||||
new(() => FinishedRenderingHeifHd?.Invoke(Convert(webRootPath,
|
||||
new(() => FinishedRenderingHeifHd?.Invoke(Convert(assetRootPath,
|
||||
id,
|
||||
originalFilePath,
|
||||
sourceFormat,
|
||||
@@ -489,7 +489,7 @@ public class Photos
|
||||
false,
|
||||
scan,
|
||||
item))),
|
||||
new(() => FinishedRenderingAvif4kThumbnail?.Invoke(Convert(webRootPath,
|
||||
new(() => FinishedRenderingAvif4kThumbnail?.Invoke(Convert(assetRootPath,
|
||||
id,
|
||||
originalFilePath,
|
||||
sourceFormat,
|
||||
@@ -498,7 +498,7 @@ public class Photos
|
||||
true,
|
||||
scan,
|
||||
item))),
|
||||
new(() => FinishedRenderingAvif1440Thumbnail?.Invoke(Convert(webRootPath,
|
||||
new(() => FinishedRenderingAvif1440Thumbnail?.Invoke(Convert(assetRootPath,
|
||||
id,
|
||||
originalFilePath,
|
||||
sourceFormat,
|
||||
@@ -507,7 +507,7 @@ public class Photos
|
||||
true,
|
||||
scan,
|
||||
item))),
|
||||
new(() => FinishedRenderingAvifHdThumbnail?.Invoke(Convert(webRootPath,
|
||||
new(() => FinishedRenderingAvifHdThumbnail?.Invoke(Convert(assetRootPath,
|
||||
id,
|
||||
originalFilePath,
|
||||
sourceFormat,
|
||||
@@ -516,7 +516,7 @@ public class Photos
|
||||
true,
|
||||
scan,
|
||||
item))),
|
||||
new(() => FinishedRenderingAvif4K?.Invoke(Convert(webRootPath,
|
||||
new(() => FinishedRenderingAvif4K?.Invoke(Convert(assetRootPath,
|
||||
id,
|
||||
originalFilePath,
|
||||
sourceFormat,
|
||||
@@ -525,7 +525,7 @@ public class Photos
|
||||
false,
|
||||
scan,
|
||||
item))),
|
||||
new(() => FinishedRenderingAvif1440?.Invoke(Convert(webRootPath,
|
||||
new(() => FinishedRenderingAvif1440?.Invoke(Convert(assetRootPath,
|
||||
id,
|
||||
originalFilePath,
|
||||
sourceFormat,
|
||||
@@ -534,7 +534,7 @@ public class Photos
|
||||
false,
|
||||
scan,
|
||||
item))),
|
||||
new(() => FinishedRenderingAvifHd?.Invoke(Convert(webRootPath,
|
||||
new(() => FinishedRenderingAvifHd?.Invoke(Convert(assetRootPath,
|
||||
id,
|
||||
originalFilePath,
|
||||
sourceFormat,
|
||||
|
||||
@@ -33,11 +33,11 @@ namespace Marechai.Server.Helpers;
|
||||
|
||||
public static class SvgRender
|
||||
{
|
||||
public static void RenderCountries()
|
||||
public static void RenderCountries(string assetRootPath)
|
||||
{
|
||||
if(!Directory.Exists("wwwroot/assets/flags/countries")) return;
|
||||
if(!Directory.Exists($"{assetRootPath}/flags/countries")) return;
|
||||
|
||||
foreach(string file in Directory.GetFiles("wwwroot/assets/flags/countries/",
|
||||
foreach(string file in Directory.GetFiles($"{assetRootPath}/flags/countries/",
|
||||
"*.svg",
|
||||
SearchOption.TopDirectoryOnly))
|
||||
{
|
||||
@@ -50,8 +50,8 @@ public static class SvgRender
|
||||
"png", "webp"
|
||||
})
|
||||
{
|
||||
if(!Directory.Exists(Path.Combine("wwwroot/assets/flags/countries", format)))
|
||||
Directory.CreateDirectory(Path.Combine("wwwroot/assets/flags/countries", format));
|
||||
if(!Directory.Exists(Path.Combine($"{assetRootPath}/flags/countries", format)))
|
||||
Directory.CreateDirectory(Path.Combine($"{assetRootPath}/flags/countries", format));
|
||||
|
||||
SKEncodedImageFormat skFormat;
|
||||
|
||||
@@ -72,14 +72,14 @@ public static class SvgRender
|
||||
1, 2, 3
|
||||
})
|
||||
{
|
||||
if(!Directory.Exists(Path.Combine("wwwroot/assets/flags/countries", format, $"{multiplier}x")))
|
||||
if(!Directory.Exists(Path.Combine($"{assetRootPath}/flags/countries", format, $"{multiplier}x")))
|
||||
{
|
||||
Directory.CreateDirectory(Path.Combine("wwwroot/assets/flags/countries",
|
||||
Directory.CreateDirectory(Path.Combine($"{assetRootPath}/flags/countries",
|
||||
format,
|
||||
$"{multiplier}x"));
|
||||
}
|
||||
|
||||
string rendered = Path.Combine("wwwroot/assets/flags/countries",
|
||||
string rendered = Path.Combine($"{assetRootPath}/flags/countries",
|
||||
format,
|
||||
$"{multiplier}x",
|
||||
flagName + $".{format}");
|
||||
@@ -102,11 +102,11 @@ public static class SvgRender
|
||||
}
|
||||
}
|
||||
|
||||
public static void ImportCompanyLogos(MarechaiContext context)
|
||||
public static void ImportCompanyLogos(string assetRootPath, MarechaiContext context)
|
||||
{
|
||||
if(!Directory.Exists("wwwroot/assets/incoming")) return;
|
||||
if(!Directory.Exists($"{assetRootPath}/incoming")) return;
|
||||
|
||||
foreach(string file in Directory.GetFiles("wwwroot/assets/incoming",
|
||||
foreach(string file in Directory.GetFiles($"{assetRootPath}/incoming",
|
||||
"company_*.svg",
|
||||
SearchOption.TopDirectoryOnly))
|
||||
{
|
||||
@@ -153,8 +153,8 @@ public static class SvgRender
|
||||
})
|
||||
{
|
||||
string outDir = minSize == 32
|
||||
? Path.Combine("wwwroot/assets/logos/thumbs", format)
|
||||
: Path.Combine("wwwroot/assets/logos", format);
|
||||
? Path.Combine($"{assetRootPath}/logos/thumbs", format)
|
||||
: Path.Combine($"{assetRootPath}/logos", format);
|
||||
|
||||
if(!Directory.Exists(outDir)) Directory.CreateDirectory(outDir);
|
||||
|
||||
@@ -200,7 +200,7 @@ public static class SvgRender
|
||||
}
|
||||
}
|
||||
|
||||
File.Move(file, $"wwwroot/assets/logos/{guid}.svg");
|
||||
File.Move(file, $"{assetRootPath}/logos/{guid}.svg");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
@@ -12,9 +13,11 @@ using Markdig;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.StaticFiles;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Version = Marechai.Server.Interop.Version;
|
||||
@@ -140,14 +143,36 @@ file class Program
|
||||
}
|
||||
}
|
||||
|
||||
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
string assetRootPath = builder.Configuration["AssetRootPath"];
|
||||
|
||||
if(string.IsNullOrEmpty(assetRootPath))
|
||||
{
|
||||
Console.WriteLine("\e[31;1mAsset root path not set, cannot continue, check configuration...\e[0m");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(assetRootPath);
|
||||
|
||||
DateTime start = DateTime.Now;
|
||||
Console.WriteLine("\e[31;1mRendering new country flags...\e[0m");
|
||||
SvgRender.RenderCountries();
|
||||
SvgRender.RenderCountries(assetRootPath);
|
||||
DateTime end = DateTime.Now;
|
||||
|
||||
Console.WriteLine("\e[31;1mTook \e[32;1m{0} seconds\e[31;1m...\e[0m", (end - start).TotalSeconds);
|
||||
|
||||
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
|
||||
start = DateTime.Now;
|
||||
Console.WriteLine("\e[31;1mEnsuring photo folders exist...\e[0m");
|
||||
Photos.EnsureCreated(assetRootPath, false, "machines");
|
||||
Console.WriteLine("\e[31;1mEnsuring scan folders exist...\e[0m");
|
||||
Photos.EnsureCreated(assetRootPath, true, "books");
|
||||
Photos.EnsureCreated(assetRootPath, true, "documents");
|
||||
Photos.EnsureCreated(assetRootPath, true, "magazines");
|
||||
end = DateTime.Now;
|
||||
|
||||
Console.WriteLine("\e[31;1mTook \e[32;1m{0} seconds\e[31;1m...\e[0m", (end - start).TotalSeconds);
|
||||
|
||||
// Add services to the container.
|
||||
builder.Services.AddControllers()
|
||||
@@ -210,8 +235,7 @@ file class Program
|
||||
builder.Services.AddScoped<TokenService, TokenService>();
|
||||
|
||||
// Read allowed CORS origins from configuration
|
||||
string[] allowedOrigins = builder.Configuration.GetSection("CORS:AllowedOrigins").Get<string[]>() ??
|
||||
Array.Empty<string>();
|
||||
string[] allowedOrigins = builder.Configuration.GetSection("CORS:AllowedOrigins").Get<string[]>() ?? [];
|
||||
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
@@ -241,6 +265,28 @@ file class Program
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
// Set up custom content types - associating file extension to MIME type
|
||||
var provider = new FileExtensionContentTypeProvider
|
||||
{
|
||||
Mappings =
|
||||
{
|
||||
// Add new mappings
|
||||
[".avif"] = "image/avif", // AVIF image format
|
||||
[".heic"] = "image/heic", // HEIC image format
|
||||
[".heif"] = "image/heif", // HEIF image format
|
||||
[".jxl"] = "image/jxl", // JPEG-XL image format
|
||||
[".webp"] = "image/webp", // WebP image format
|
||||
[".svg"] = "image/svg+xml" // SVG image format
|
||||
}
|
||||
};
|
||||
|
||||
app.UseStaticFiles(new StaticFileOptions
|
||||
{
|
||||
FileProvider = new PhysicalFileProvider(assetRootPath),
|
||||
RequestPath = "/assets",
|
||||
ContentTypeProvider = provider
|
||||
});
|
||||
|
||||
using(IServiceScope scope = app.Services.CreateScope())
|
||||
{
|
||||
IServiceProvider services = scope.ServiceProvider;
|
||||
@@ -257,7 +303,7 @@ file class Program
|
||||
|
||||
start = DateTime.Now;
|
||||
Console.WriteLine("\e[31;1mImporting company logos...\e[0m");
|
||||
SvgRender.ImportCompanyLogos(context);
|
||||
SvgRender.ImportCompanyLogos(assetRootPath, context);
|
||||
end = DateTime.Now;
|
||||
|
||||
Console.WriteLine("\e[31;1mTook \e[32;1m{0} seconds\e[31;1m...\e[0m", (end - start).TotalSeconds);
|
||||
@@ -278,20 +324,6 @@ file class Program
|
||||
end = DateTime.Now;
|
||||
|
||||
Console.WriteLine("\e[31;1mTook \e[32;1m{0} seconds\e[31;1m...\e[0m", (end - start).TotalSeconds);
|
||||
|
||||
start = DateTime.Now;
|
||||
Console.WriteLine("\e[31;1mEnsuring photo folders exist...\e[0m");
|
||||
Photos.EnsureCreated("wwwroot", false, "machines");
|
||||
end = DateTime.Now;
|
||||
|
||||
start = DateTime.Now;
|
||||
Console.WriteLine("\e[31;1mEnsuring scan folders exist...\e[0m");
|
||||
Photos.EnsureCreated("wwwroot", true, "books");
|
||||
Photos.EnsureCreated("wwwroot", true, "documents");
|
||||
Photos.EnsureCreated("wwwroot", true, "magazines");
|
||||
end = DateTime.Now;
|
||||
|
||||
Console.WriteLine("\e[31;1mTook \e[32;1m{0} seconds\e[31;1m...\e[0m", (end - start).TotalSeconds);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
|
||||
@@ -64,5 +64,6 @@
|
||||
"Name": "NormalUser",
|
||||
"Description": "A normal user role."
|
||||
}
|
||||
]
|
||||
],
|
||||
"AssetRootPath": "/var/marechai/assets"
|
||||
}
|
||||
Reference in New Issue
Block a user