feat: Enhance Face AI upload functionality and UI
- Updated MainWindow.axaml to increase height and add new UI elements for SSH upload configuration. - Implemented commands for opening source and destination paths in file explorer. - Added FaceUploadPath and SSH configuration properties to DataModel and AiSettingsViewModel. - Introduced validation for FaceUploadPath format and commands for uploading face encoder output. - Enhanced PickerPreferenceService to manage SSH credentials and upload preferences. - Updated settings persistence to include FaceUploadPath and SSH preferences. - Added tests for FaceUploadPath validation and upload command enabling logic.
This commit is contained in:
parent
e68608312a
commit
e9142df97c
22 changed files with 1477 additions and 84 deletions
227
CatalogLite/Assets/Config.xml
Normal file
227
CatalogLite/Assets/Config.xml
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||
<NewDataSet>
|
||||
<Setup>
|
||||
<Nome>MiniatureModalita</Nome>
|
||||
<Valore>Text</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>DirSorgente</Nome>
|
||||
<Valore></Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>DirDestinazione</Nome>
|
||||
<Valore></Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>MiniatureCrea</Nome>
|
||||
<Valore>True</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>MiniatureSuffisso</Nome>
|
||||
<Valore>tn_</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>MiniatureAltezza</Nome>
|
||||
<Valore>350</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>MiniatureLarghezza</Nome>
|
||||
<Valore>350</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>FontDimensioneMiniatura</Nome>
|
||||
<Valore>20</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>CompressioneJpegMiniatura</Nome>
|
||||
<Valore>60</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>MiniatureAddOrario</Nome>
|
||||
<Valore>False</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>NomeMiniatura</Nome>
|
||||
<Valore>False</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>MiniatureAddScritta</Nome>
|
||||
<Valore>True</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>TempoSmall</Nome>
|
||||
<Valore>False</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>NumTempoSmall</Nome>
|
||||
<Valore>False</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>FotoCodice</Nome>
|
||||
<Valore></Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>FotoAltezza</Nome>
|
||||
<Valore>2560</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>FotoLarghezza</Nome>
|
||||
<Valore>2560</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>FotoDimOriginali</Nome>
|
||||
<Valore>False</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>CompressioneJpeg</Nome>
|
||||
<Valore>90</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>FontDimensione</Nome>
|
||||
<Valore>50</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>FontNome</Nome>
|
||||
<Valore>Verdana</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>FontBold</Nome>
|
||||
<Valore>True</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>ColoreTestoRGB</Nome>
|
||||
<Valore>#FA7B0A</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>TestoTesto</Nome>
|
||||
<Valore></Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>TestoVerticale</Nome>
|
||||
<Valore></Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>TestoTrasparente</Nome>
|
||||
<Valore>0</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>TestoMargine</Nome>
|
||||
<Valore>8</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>TestoPosizione</Nome>
|
||||
<Valore>Basso</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>TestoAllineamento</Nome>
|
||||
<Valore>Centro</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>GrandezzaVerticale</Nome>
|
||||
<Valore>18</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>MargineVerticale</Nome>
|
||||
<Valore>6</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>MarchioAltezza</Nome>
|
||||
<Valore>250</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>MarchioLarghezza</Nome>
|
||||
<Valore>250</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>MarchioMargine</Nome>
|
||||
<Valore>130</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>MarchioAllOrizzontale</Nome>
|
||||
<Valore>Destra</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>MarchioAllVerticale</Nome>
|
||||
<Valore>Alto</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>MarchioTrasparenza</Nome>
|
||||
<Valore>100</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>MarchioAggiungi</Nome>
|
||||
<Valore>True</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>ColoreTrasparente</Nome>
|
||||
<Valore>#FFFFFF</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>UsaColoreTrasparente</Nome>
|
||||
<Valore>True</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>ImageLibrary</Nome>
|
||||
<Valore>ImageSharp</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>GeneraleForzaJpg</Nome>
|
||||
<Valore>True</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>GeneraleRotazioneAutomatica</Nome>
|
||||
<Valore>True</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>DirSottoDirectory</Nome>
|
||||
<Valore>True</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>TempoGara</Nome>
|
||||
<Valore>False</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>Orario</Nome>
|
||||
<Valore>False</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>EtichettaOrario</Nome>
|
||||
<Valore></Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>DataFoto</Nome>
|
||||
<Valore>False</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>NumeroFoto</Nome>
|
||||
<Valore>False</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>GeneraleSovrascriviFile</Nome>
|
||||
<Valore>False</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>DirDividiNumFile</Nome>
|
||||
<Valore>300</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>DirDividiSuffisso</Nome>
|
||||
<Valore></Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>DirDividiNumCifre</Nome>
|
||||
<Valore>2</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>ChunkSize</Nome>
|
||||
<Valore>0</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>ThreadsCount</Nome>
|
||||
<Valore>0</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>DataPartenza</Nome>
|
||||
<Valore>17/02/2026 09:35:25</Valore>
|
||||
</Setup>
|
||||
</NewDataSet>
|
||||
BIN
CatalogLite/Assets/Logo_RUS_ETS_tricolore_OK.png
Normal file
BIN
CatalogLite/Assets/Logo_RUS_ETS_tricolore_OK.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 290 KiB |
336
CatalogLite/Assets/testConfig.xml
Normal file
336
CatalogLite/Assets/testConfig.xml
Normal file
|
|
@ -0,0 +1,336 @@
|
|||
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||
<NewDataSet>
|
||||
<Setup>
|
||||
<Nome>MiniatureModalita</Nome>
|
||||
<Valore>RaceTime</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>MiniatureCrea</Nome>
|
||||
<Valore>True</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>MiniatureSuffisso</Nome>
|
||||
<Valore>tn_</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>MiniatureAltezza</Nome>
|
||||
<Valore>350</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>MiniatureLarghezza</Nome>
|
||||
<Valore>350</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>FontDimensioneMiniatura</Nome>
|
||||
<Valore>48</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>CompressioneJpegMiniatura</Nome>
|
||||
<Valore>25</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>MiniatureAddOrario</Nome>
|
||||
<Valore>False</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>NomeMiniatura</Nome>
|
||||
<Valore>False</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>MiniatureAddScritta</Nome>
|
||||
<Valore>False</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>TempoSmall</Nome>
|
||||
<Valore>True</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>NumTempoSmall</Nome>
|
||||
<Valore>False</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>FotoCodice</Nome>
|
||||
<Valore></Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>FotoAltezza</Nome>
|
||||
<Valore>2560</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>FotoLarghezza</Nome>
|
||||
<Valore>2560</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>FotoDimOriginali</Nome>
|
||||
<Valore>False</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>CompressioneJpeg</Nome>
|
||||
<Valore>90</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>FontDimensione</Nome>
|
||||
<Valore>22</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>FontNome</Nome>
|
||||
<Valore>Verdana</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>FontBold</Nome>
|
||||
<Valore>True</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>ColoreTestoRGB</Nome>
|
||||
<Valore>#FEC005</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>TestoTesto</Nome>
|
||||
<Valore>MARATONINA DI VINCI -1 FEBBRAIO 2026</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>TestoVerticale</Nome>
|
||||
<Valore>MARATONINA DI VINCI
|
||||
1 FEBBRAIO 2026</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>TestoTrasparente</Nome>
|
||||
<Valore>0</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>TestoMargine</Nome>
|
||||
<Valore>8</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>TestoPosizione</Nome>
|
||||
<Valore>Basso</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>TestoAllineamento</Nome>
|
||||
<Valore>Centro</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>GrandezzaVerticale</Nome>
|
||||
<Valore>18</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>MargineVerticale</Nome>
|
||||
<Valore>6</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>MarchioFile</Nome>
|
||||
<Valore>K:\various\catalogtest\Logo.jpg</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>MarchioAltezza</Nome>
|
||||
<Valore>470</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>MarchioLarghezza</Nome>
|
||||
<Valore>470</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>MarchioMargine</Nome>
|
||||
<Valore>350</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>MarchioAllOrizzontale</Nome>
|
||||
<Valore>Destra</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>MarchioAllVerticale</Nome>
|
||||
<Valore>Alto</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>MarchioTrasparenza</Nome>
|
||||
<Valore>100</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>MarchioAggiungi</Nome>
|
||||
<Valore>True</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>ColoreTrasparente</Nome>
|
||||
<Valore>#FFFFFF</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>UsaColoreTrasparente</Nome>
|
||||
<Valore>True</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>ImageLibrary</Nome>
|
||||
<Valore>System.Graphics</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>GeneraleForzaJpg</Nome>
|
||||
<Valore>True</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>GeneraleRotazioneAutomatica</Nome>
|
||||
<Valore>True</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>DirSottoDirectory</Nome>
|
||||
<Valore>True</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>TempoGara</Nome>
|
||||
<Valore>True</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>Orario</Nome>
|
||||
<Valore>False</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>EtichettaOrario</Nome>
|
||||
<Valore> TEMPO :</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>DataFoto</Nome>
|
||||
<Valore>False</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>NumeroFoto</Nome>
|
||||
<Valore>False</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>GeneraleSovrascriviFile</Nome>
|
||||
<Valore>True</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>DirDividiNumFile</Nome>
|
||||
<Valore>300</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>DirDividiSuffisso</Nome>
|
||||
<Valore></Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>DirDividiNumCifre</Nome>
|
||||
<Valore>2</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>ChunkSize</Nome>
|
||||
<Valore>200</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>ThreadsCount</Nome>
|
||||
<Valore>10</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>DataPartenza</Nome>
|
||||
<Valore>01/02/2026 20:30:48</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>AI_EstraiNumeri</Nome>
|
||||
<Valore>True</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>AI_CartellaModelli</Nome>
|
||||
<Valore>K:\vs\AIFotoONLUS\models\\</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>AI_PercorsoCsv</Nome>
|
||||
<Valore>K:\various\catalogtest\aioutput\test2.csv</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>AI_UsaGpuNumeri</Nome>
|
||||
<Valore>True</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>AI_IncludiThumbnailNumeri</Nome>
|
||||
<Valore>False</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>AI_LivelloCaricoNumeri</Nome>
|
||||
<Valore>3</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>AI_FaceExecutablePath</Nome>
|
||||
<Valore>K:\various\regalamiunsorriso\bin\Face_Recognition_Windows\</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>AI_FaceOutputFolderPath</Nome>
|
||||
<Valore>K:\various\catalogtest\aioutput\</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>AI_FaceRecursive</Nome>
|
||||
<Valore>True</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>AI_FaceIncludeThumbnails</Nome>
|
||||
<Valore>False</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>AI_FaceParallelism</Nome>
|
||||
<Valore>3</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>AI_FaceMinSize</Nome>
|
||||
<Valore>35</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>AI_FaceUpsample</Nome>
|
||||
<Valore>False</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>AI_FaceMatcherExecutablePath</Nome>
|
||||
<Valore>K:\various\regalamiunsorriso\bin\Face_Recognition_Windows\face_matcher.exe</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>AI_FaceMatcherTolerance</Nome>
|
||||
<Valore>0,75</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>RaceUpload_Login</Nome>
|
||||
<Valore></Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>RaceUpload_Password</Nome>
|
||||
<Valore></Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>RaceUpload_Description</Nome>
|
||||
<Valore></Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>RaceUpload_TipoGaraId</Nome>
|
||||
<Valore>1</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>RaceUpload_StartDate</Nome>
|
||||
<Valore>12/03/2026 00:00:00</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>RaceUpload_EndDate</Nome>
|
||||
<Valore>12/03/2026 00:00:00</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>RaceUpload_PathBase</Nome>
|
||||
<Valore></Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>RaceUpload_Localita</Nome>
|
||||
<Valore></Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>RaceUpload_EventoInLinea</Nome>
|
||||
<Valore>0</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>RaceUpload_TipoIndex</Nome>
|
||||
<Valore>1</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>RaceUpload_FreeEvent</Nome>
|
||||
<Valore>0</Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>RaceUpload_LastRaceId</Nome>
|
||||
<Valore></Valore>
|
||||
</Setup>
|
||||
<Setup>
|
||||
<Nome>RaceUpload_RemoteProcessedBasePath</Nome>
|
||||
<Valore></Valore>
|
||||
</Setup>
|
||||
</NewDataSet>
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
using Avalonia.Threading;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace CatalogLite;
|
||||
|
|
@ -38,5 +39,14 @@ public sealed class AsyncCommand : ICommand
|
|||
}
|
||||
}
|
||||
|
||||
public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
|
||||
public void RaiseCanExecuteChanged()
|
||||
{
|
||||
if (Dispatcher.UIThread.CheckAccess())
|
||||
{
|
||||
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
|
||||
return;
|
||||
}
|
||||
|
||||
Dispatcher.UIThread.Post(() => CanExecuteChanged?.Invoke(this, EventArgs.Empty));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
using MaddoShared;
|
||||
using SixLabors.ImageSharp;
|
||||
|
|
@ -8,6 +9,9 @@ namespace CatalogLite;
|
|||
|
||||
public sealed class CatalogConfigurationLoader
|
||||
{
|
||||
private const string EmbeddedConfigResourceName = "CatalogLite.Assets.Config.xml";
|
||||
private const string EmbeddedLogoResourceName = "CatalogLite.Assets.Logo_RUS_ETS_tricolore_OK.png";
|
||||
|
||||
public CatalogLiteConfiguration Load(string filePath, PicSettings picSettings)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
|
|
@ -21,7 +25,7 @@ public sealed class CatalogConfigurationLoader
|
|||
}
|
||||
|
||||
var values = ConfigurationValues.Load(filePath);
|
||||
ApplyPicSettings(values, picSettings);
|
||||
ApplyPicSettings(values, picSettings);
|
||||
|
||||
var sourcePath = LiteCatalogViewModel.NormalizeDirectoryPath(values.GetString("DirSorgente"));
|
||||
var destinationPath = LiteCatalogViewModel.NormalizeDirectoryPath(values.GetString("DirDestinazione"));
|
||||
|
|
@ -41,6 +45,32 @@ public sealed class CatalogConfigurationLoader
|
|||
};
|
||||
}
|
||||
|
||||
public CatalogLiteConfiguration LoadEmbedded(PicSettings picSettings)
|
||||
{
|
||||
using var configStream = OpenEmbeddedResource(EmbeddedConfigResourceName);
|
||||
var values = ConfigurationValues.Load(configStream);
|
||||
ApplyPicSettings(values, picSettings);
|
||||
picSettings.LogoData = ReadAllBytes(OpenEmbeddedResource(EmbeddedLogoResourceName));
|
||||
picSettings.LogoNomeFile = string.Empty;
|
||||
|
||||
var sourcePath = LiteCatalogViewModel.NormalizeDirectoryPath(values.GetString("DirSorgente"));
|
||||
var destinationPath = LiteCatalogViewModel.NormalizeDirectoryPath(values.GetString("DirDestinazione"));
|
||||
|
||||
picSettings.DirectorySorgente = sourcePath;
|
||||
picSettings.DirectoryDestinazione = destinationPath;
|
||||
picSettings.DestDir = string.IsNullOrWhiteSpace(destinationPath)
|
||||
? new DirectoryInfo(Environment.CurrentDirectory)
|
||||
: new DirectoryInfo(destinationPath);
|
||||
|
||||
return new CatalogLiteConfiguration
|
||||
{
|
||||
FilePath = "Configurazione incorporata",
|
||||
SourcePath = sourcePath,
|
||||
DestinationPath = destinationPath,
|
||||
Options = BuildOptions(values, sourcePath, destinationPath)
|
||||
};
|
||||
}
|
||||
|
||||
public static ImageCreationService.Options CloneOptions(ImageCreationService.Options options, string sourcePath, string destinationPath)
|
||||
{
|
||||
return new ImageCreationService.Options
|
||||
|
|
@ -150,6 +180,22 @@ public sealed class CatalogConfigurationLoader
|
|||
return string.Equals(values.GetString("MiniatureModalita"), mode, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static Stream OpenEmbeddedResource(string resourceName)
|
||||
{
|
||||
var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName);
|
||||
return stream ?? throw new InvalidOperationException($"Risorsa incorporata non trovata: {resourceName}");
|
||||
}
|
||||
|
||||
private static byte[] ReadAllBytes(Stream stream)
|
||||
{
|
||||
using (stream)
|
||||
{
|
||||
using var memoryStream = new MemoryStream();
|
||||
stream.CopyTo(memoryStream);
|
||||
return memoryStream.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private static Rgba32 ParseColor(string value, Rgba32 fallback)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
|
|
@ -193,7 +239,13 @@ public sealed class CatalogConfigurationLoader
|
|||
|
||||
public static ConfigurationValues Load(string filePath)
|
||||
{
|
||||
var document = XDocument.Load(filePath);
|
||||
using var stream = File.OpenRead(filePath);
|
||||
return Load(stream);
|
||||
}
|
||||
|
||||
public static ConfigurationValues Load(Stream stream)
|
||||
{
|
||||
var document = XDocument.Load(stream);
|
||||
var values = document
|
||||
.Descendants("Setup")
|
||||
.Where(element => element.Element("Nome") is not null)
|
||||
|
|
|
|||
|
|
@ -25,6 +25,11 @@
|
|||
<Compile Include="$(IntermediateOutputPath)CatalogLiteExpiration.g.cs" Visible="false" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Assets\Config.xml" />
|
||||
<EmbeddedResource Include="Assets\Logo_RUS_ETS_tricolore_OK.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MaddoShared\MaddoShared.csproj" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -12,10 +12,9 @@ public sealed class LiteCatalogViewModel : ViewModelBase
|
|||
private readonly ILogger<LiteCatalogViewModel> _logger;
|
||||
private CatalogLiteConfiguration? _configuration;
|
||||
private CancellationTokenSource? _processingTokenSource;
|
||||
private string _configurationPath = string.Empty;
|
||||
private string _sourcePath = string.Empty;
|
||||
private string _destinationPath = string.Empty;
|
||||
private string _processingStatus = "Carica una configurazione XML.";
|
||||
private string _processingStatus = "Caricamento configurazione incorporata...";
|
||||
private string _speedCounter = "-";
|
||||
private int _processedImagesCount;
|
||||
private int _totalImagesCount;
|
||||
|
|
@ -36,36 +35,23 @@ public sealed class LiteCatalogViewModel : ViewModelBase
|
|||
_imageProcessingCoordinator = imageProcessingCoordinator;
|
||||
_logger = logger;
|
||||
|
||||
LoadConfigurationCommand = new AsyncCommand(RequestLoadConfigurationAsync, () => !IsProcessing);
|
||||
SelectSourceFolderCommand = new AsyncCommand(RequestSourceFolderAsync, () => !IsProcessing);
|
||||
SelectDestinationFolderCommand = new AsyncCommand(RequestDestinationFolderAsync, () => !IsProcessing);
|
||||
StartProcessingCommand = new AsyncCommand(StartProcessingAsync, CanStartProcessing);
|
||||
StopProcessingCommand = new AsyncCommand(StopProcessingAsync, () => IsProcessing);
|
||||
}
|
||||
|
||||
public event EventHandler? LoadConfigurationRequested;
|
||||
public event EventHandler? SelectSourceFolderRequested;
|
||||
public event EventHandler? SelectDestinationFolderRequested;
|
||||
public event EventHandler<LiteMessageEventArgs>? ShowMessageRequested;
|
||||
|
||||
public Action<Action>? UiInvoker { get; set; }
|
||||
|
||||
public AsyncCommand LoadConfigurationCommand { get; }
|
||||
public AsyncCommand SelectSourceFolderCommand { get; }
|
||||
public AsyncCommand SelectDestinationFolderCommand { get; }
|
||||
public AsyncCommand StartProcessingCommand { get; }
|
||||
public AsyncCommand StopProcessingCommand { get; }
|
||||
|
||||
public string ConfigurationPath
|
||||
{
|
||||
get => _configurationPath;
|
||||
private set
|
||||
{
|
||||
_configurationPath = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public string SourcePath
|
||||
{
|
||||
get => _sourcePath;
|
||||
|
|
@ -77,6 +63,26 @@ public sealed class LiteCatalogViewModel : ViewModelBase
|
|||
}
|
||||
}
|
||||
|
||||
public string HorizontalText
|
||||
{
|
||||
get => _picSettings.TestoFirmaStart ?? string.Empty;
|
||||
set
|
||||
{
|
||||
_picSettings.TestoFirmaStart = value ?? string.Empty;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public string VerticalText
|
||||
{
|
||||
get => _picSettings.TestoFirmaStartV ?? string.Empty;
|
||||
set
|
||||
{
|
||||
_picSettings.TestoFirmaStartV = value ?? string.Empty;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public string DestinationPath
|
||||
{
|
||||
get => _destinationPath;
|
||||
|
|
@ -159,25 +165,24 @@ public sealed class LiteCatalogViewModel : ViewModelBase
|
|||
}
|
||||
}
|
||||
|
||||
public async Task LoadConfigurationFromFileAsync(string filePath)
|
||||
public void LoadEmbeddedConfiguration()
|
||||
{
|
||||
try
|
||||
{
|
||||
var configuration = await Task.Run(() => _configurationLoader.Load(filePath, _picSettings)).ConfigureAwait(false);
|
||||
var configuration = _configurationLoader.LoadEmbedded(_picSettings);
|
||||
|
||||
RunOnUiThread(() =>
|
||||
{
|
||||
_configuration = configuration;
|
||||
ConfigurationPath = configuration.FilePath;
|
||||
SourcePath = configuration.SourcePath;
|
||||
DestinationPath = configuration.DestinationPath;
|
||||
ResetProgress("Configurazione caricata.");
|
||||
});
|
||||
_configuration = configuration;
|
||||
SourcePath = configuration.SourcePath;
|
||||
DestinationPath = configuration.DestinationPath;
|
||||
NotifyPropertyChanged(nameof(HorizontalText));
|
||||
NotifyPropertyChanged(nameof(VerticalText));
|
||||
ResetProgress("Configurazione incorporata caricata.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore durante il caricamento della configurazione");
|
||||
ShowMessage("Configurazione", $"Impossibile caricare la configurazione: {ex.GetBaseException().Message}");
|
||||
_logger.LogError(ex, "Errore durante il caricamento della configurazione incorporata");
|
||||
ProcessingStatus = "Errore caricamento configurazione incorporata.";
|
||||
ShowMessage("Configurazione", $"Impossibile caricare la configurazione incorporata: {ex.GetBaseException().Message}");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -192,12 +197,6 @@ public sealed class LiteCatalogViewModel : ViewModelBase
|
|||
return trimmed + Path.DirectorySeparatorChar;
|
||||
}
|
||||
|
||||
private Task RequestLoadConfigurationAsync()
|
||||
{
|
||||
LoadConfigurationRequested?.Invoke(this, EventArgs.Empty);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task RequestSourceFolderAsync()
|
||||
{
|
||||
SelectSourceFolderRequested?.Invoke(this, EventArgs.Empty);
|
||||
|
|
@ -222,7 +221,7 @@ public sealed class LiteCatalogViewModel : ViewModelBase
|
|||
{
|
||||
if (_configuration is null)
|
||||
{
|
||||
ShowMessage("Configurazione", "Carica prima una configurazione XML.");
|
||||
ShowMessage("Configurazione", "La configurazione incorporata non e' disponibile.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -340,7 +339,6 @@ public sealed class LiteCatalogViewModel : ViewModelBase
|
|||
|
||||
private void RaiseCommandStates()
|
||||
{
|
||||
LoadConfigurationCommand.RaiseCanExecuteChanged();
|
||||
SelectSourceFolderCommand.RaiseCanExecuteChanged();
|
||||
SelectDestinationFolderCommand.RaiseCanExecuteChanged();
|
||||
StartProcessingCommand.RaiseCanExecuteChanged();
|
||||
|
|
|
|||
|
|
@ -5,24 +5,11 @@
|
|||
x:CompileBindings="False"
|
||||
Title="Catalog Lite"
|
||||
Width="740"
|
||||
Height="380"
|
||||
Height="560"
|
||||
MinWidth="640"
|
||||
MinHeight="340">
|
||||
<Grid Margin="16" RowDefinitions="Auto,Auto,Auto,*,Auto" RowSpacing="12">
|
||||
<Grid ColumnDefinitions="150,*" ColumnSpacing="10">
|
||||
<Button Command="{Binding LoadConfigurationCommand}" ToolTip.Tip="Carica configurazione XML">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="7">
|
||||
<iconPacks:PackIconMaterial Kind="FolderUploadOutline" Width="16" Height="16" />
|
||||
<TextBlock Text="Carica XML" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<TextBox Grid.Column="1"
|
||||
Text="{Binding ConfigurationPath}"
|
||||
IsReadOnly="True"
|
||||
Watermark="Nessuna configurazione caricata" />
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="1" ColumnDefinitions="150,*,116" ColumnSpacing="10">
|
||||
MinHeight="500">
|
||||
<Grid Margin="16" RowDefinitions="Auto,Auto,*,Auto" RowSpacing="12">
|
||||
<Grid ColumnDefinitions="150,*,104,72" ColumnSpacing="10">
|
||||
<TextBlock Text="Sorgente" VerticalAlignment="Center" FontWeight="SemiBold" />
|
||||
<TextBox Grid.Column="1" Text="{Binding SourcePath, Mode=TwoWay}" Watermark="Cartella sorgente" />
|
||||
<Button Grid.Column="2" Command="{Binding SelectSourceFolderCommand}" ToolTip.Tip="Seleziona cartella sorgente">
|
||||
|
|
@ -31,9 +18,15 @@
|
|||
<TextBlock Text="Scegli" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Grid.Column="3" Click="OpenSourcePath_Click" ToolTip.Tip="Apri cartella sorgente in Esplora file">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="Folder" Width="16" Height="16" />
|
||||
<TextBlock Text="Apri" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="2" ColumnDefinitions="150,*,116" ColumnSpacing="10">
|
||||
<Grid Grid.Row="1" ColumnDefinitions="150,*,104,72" ColumnSpacing="10">
|
||||
<TextBlock Text="Destinazione" VerticalAlignment="Center" FontWeight="SemiBold" />
|
||||
<TextBox Grid.Column="1" Text="{Binding DestinationPath, Mode=TwoWay}" Watermark="Cartella destinazione" />
|
||||
<Button Grid.Column="2" Command="{Binding SelectDestinationFolderCommand}" ToolTip.Tip="Seleziona cartella destinazione">
|
||||
|
|
@ -42,8 +35,39 @@
|
|||
<TextBlock Text="Scegli" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Grid.Column="3" Click="OpenDestinationPath_Click" ToolTip.Tip="Apri cartella destinazione in Esplora file">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="Folder" Width="16" Height="16" />
|
||||
<TextBlock Text="Apri" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<Border Grid.Row="2"
|
||||
Background="{DynamicResource PanelBackgroundBrush}"
|
||||
BorderBrush="{DynamicResource BorderMutedBrush}"
|
||||
BorderThickness="1"
|
||||
Padding="14">
|
||||
<Grid RowDefinitions="Auto,Auto" RowSpacing="10">
|
||||
<Grid ColumnDefinitions="150,*" ColumnSpacing="10">
|
||||
<TextBlock Text="Testo orizzontale" VerticalAlignment="Center" FontWeight="SemiBold" />
|
||||
<TextBox Grid.Column="1"
|
||||
Text="{Binding HorizontalText, Mode=TwoWay}"
|
||||
Watermark="Testo applicato alle foto orizzontali" />
|
||||
</Grid>
|
||||
<Grid Grid.Row="1" ColumnDefinitions="150,*" ColumnSpacing="10">
|
||||
<TextBlock Text="Testo verticale" VerticalAlignment="Top" Margin="0,8,0,0" FontWeight="SemiBold" />
|
||||
<TextBox Grid.Column="1"
|
||||
Text="{Binding VerticalText, Mode=TwoWay}"
|
||||
AcceptsReturn="True"
|
||||
TextWrapping="Wrap"
|
||||
MinHeight="96"
|
||||
VerticalContentAlignment="Top"
|
||||
Watermark="Testo multilinea per foto verticali" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Border Grid.Row="3"
|
||||
Background="{DynamicResource PanelBackgroundBrush}"
|
||||
BorderBrush="{DynamicResource BorderMutedBrush}"
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ using Avalonia.Controls;
|
|||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.Threading;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
namespace CatalogLite;
|
||||
|
||||
|
|
@ -21,24 +23,10 @@ public partial class MainWindow : Window
|
|||
_viewModel = viewModel;
|
||||
DataContext = _viewModel;
|
||||
_viewModel.UiInvoker = action => Dispatcher.UIThread.Invoke(action);
|
||||
_viewModel.LoadConfigurationRequested += OnLoadConfigurationRequested;
|
||||
_viewModel.SelectSourceFolderRequested += OnSelectSourceFolderRequested;
|
||||
_viewModel.SelectDestinationFolderRequested += OnSelectDestinationFolderRequested;
|
||||
_viewModel.ShowMessageRequested += OnShowMessageRequested;
|
||||
}
|
||||
|
||||
private async void OnLoadConfigurationRequested(object? sender, EventArgs e)
|
||||
{
|
||||
var files = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
||||
{
|
||||
Title = "Carica configurazione XML",
|
||||
FileTypeFilter = [new FilePickerFileType("XML") { Patterns = ["*.xml"] }]
|
||||
});
|
||||
|
||||
if (files.Count > 0)
|
||||
{
|
||||
await _viewModel.LoadConfigurationFromFileAsync(files[0].Path.LocalPath);
|
||||
}
|
||||
_viewModel.LoadEmbeddedConfiguration();
|
||||
}
|
||||
|
||||
private async void OnSelectSourceFolderRequested(object? sender, EventArgs e)
|
||||
|
|
@ -67,6 +55,16 @@ public partial class MainWindow : Window
|
|||
}
|
||||
}
|
||||
|
||||
private void OpenSourcePath_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
{
|
||||
OpenInExplorer(_viewModel.SourcePath);
|
||||
}
|
||||
|
||||
private void OpenDestinationPath_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
{
|
||||
OpenInExplorer(_viewModel.DestinationPath);
|
||||
}
|
||||
|
||||
private async void OnShowMessageRequested(object? sender, LiteMessageEventArgs e)
|
||||
{
|
||||
await ShowMessageDialogAsync(e.Title, e.Message);
|
||||
|
|
@ -110,4 +108,46 @@ public partial class MainWindow : Window
|
|||
closeButton.Click += (_, _) => dialog.Close();
|
||||
await dialog.ShowDialog(this);
|
||||
}
|
||||
|
||||
private static void OpenInExplorer(string? path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var normalizedPath = path.Trim().Trim('"');
|
||||
|
||||
try
|
||||
{
|
||||
if (File.Exists(normalizedPath))
|
||||
{
|
||||
Process.Start("explorer.exe", $"/select,\"{normalizedPath}\"");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Directory.Exists(normalizedPath))
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = normalizedPath,
|
||||
UseShellExecute = true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
var containingDirectory = Path.GetDirectoryName(normalizedPath);
|
||||
if (!string.IsNullOrWhiteSpace(containingDirectory) && Directory.Exists(containingDirectory))
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = containingDirectory,
|
||||
UseShellExecute = true
|
||||
});
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue