diff --git a/AvifGenerator.csproj b/AvifGenerator.csproj
index 152f3f7..6528177 100644
--- a/AvifGenerator.csproj
+++ b/AvifGenerator.csproj
@@ -8,6 +8,14 @@
enable
+
+ Exe
+ Size
+ true
+ false
+ true
+
+
diff --git a/Models/AppJsonContext.cs b/Models/AppJsonContext.cs
new file mode 100644
index 0000000..c240e2e
--- /dev/null
+++ b/Models/AppJsonContext.cs
@@ -0,0 +1,14 @@
+using System.Text.Json.Serialization;
+using AvifGenerator.Models;
+
+namespace AvifGenerator.Utils
+{
+ [JsonSourceGenerationOptions(
+ WriteIndented = true,
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonSerializable(typeof(Dictionary))]
+ [JsonSerializable(typeof(UiResponse))]
+ internal partial class AppJsonContext : JsonSerializerContext
+ {
+ }
+}
diff --git a/Models/FieldConfig.cs b/Models/FieldConfig.cs
index 68814b1..fba4da1 100644
--- a/Models/FieldConfig.cs
+++ b/Models/FieldConfig.cs
@@ -21,4 +21,4 @@ public class FieldConfig
[JsonPropertyName("BoxWidth")] public int? BoxWidth { get; set; }
[JsonPropertyName("BoxHeight")] public int? BoxHeight { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/Models/UiResponse.cs b/Models/UiResponse.cs
new file mode 100644
index 0000000..6868b7f
--- /dev/null
+++ b/Models/UiResponse.cs
@@ -0,0 +1,17 @@
+using System.Text.Json.Serialization;
+
+namespace AvifGenerator.Models
+{
+ public class UiResponse
+ {
+ [JsonPropertyName("action")] public string Action { get; set; } = string.Empty;
+ [JsonPropertyName("success")] public bool? Success { get; set; }
+ [JsonPropertyName("name")] public string? Name { get; set; }
+ [JsonPropertyName("path")] public string? Path { get; set; }
+ [JsonPropertyName("error")] public string? Error { get; set; }
+ [JsonPropertyName("templatePath")] public string? TemplatePath { get; set; }
+ [JsonPropertyName("config")] public Dictionary? Config { get; set; }
+ [JsonPropertyName("ok")] public int? Ok { get; set; }
+ [JsonPropertyName("fail")] public int? Fail { get; set; }
+ }
+}
diff --git a/Program.cs b/Program.cs
index c5196d1..4e7bfb0 100644
--- a/Program.cs
+++ b/Program.cs
@@ -39,7 +39,7 @@ static int Main(string[] args)
});
var serviceProvider = services.BuildServiceProvider();
-
+
var window = serviceProvider.GetRequiredService();
window.WaitForClose();
diff --git a/README.md b/README.md
index e0cf16c..d9dcd8a 100644
--- a/README.md
+++ b/README.md
@@ -54,6 +54,7 @@ dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=
```
+
**For Windows (With console window for debugging):**
```bash
@@ -61,6 +62,12 @@ dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=
```
+and with native performance use:
+
+```bash
+dotnet publish -c AOT -r win-x64
+```
+
**For Linux:**
```bash
@@ -68,6 +75,12 @@ dotnet publish -c Release -r linux-x64 --self-contained true -p:PublishSingleFil
```
+and with native performance use:
+
+```bash
+dotnet publish -c AOT -r linux-x64
+```
+
## 📖 How to Use
1. **Launch the App:** Open the compiled executable.
diff --git a/Services/Bridge.cs b/Services/Bridge.cs
index f377c10..9ba1657 100644
--- a/Services/Bridge.cs
+++ b/Services/Bridge.cs
@@ -10,7 +10,7 @@ public class Bridge
{
private readonly GenerationOptions _options;
private readonly Executor _executor;
- public PhotinoWindow? Window { get; set;}
+ public PhotinoWindow? Window { get; set; }
public Bridge(GenerationOptions options, Executor executor)
{
@@ -30,30 +30,14 @@ public void HandleMessage(object? sender, string message)
switch (action)
{
- case "RUN":
- HandleRunAction(root);
- break;
- case "CHOOSE_TEMPLATE":
- HandleChooseTemplate(root);
- break;
- case "CHOOSE_CSV":
- HandleChooseCsv(root);
- break;
- case "CHOOSE_OUTPUT":
- HandleChooseOutput(root);
- break;
- case "LOAD_CONFIG":
- HandleLoadConfig(root);
- break;
- case "CHOOSE_CONFIG":
- HandleChooseConfig(root);
- break;
- case "SAVE_CONFIG":
- HandleSaveConfig(root);
- break;
- default:
- Console.WriteLine("Unknown action!");
- break;
+ case "RUN": HandleRunAction(root); break;
+ case "CHOOSE_TEMPLATE": HandleChooseTemplate(root); break;
+ case "CHOOSE_CSV": HandleChooseCsv(root); break;
+ case "CHOOSE_OUTPUT": HandleChooseOutput(root); break;
+ case "LOAD_CONFIG": HandleLoadConfig(root); break;
+ case "CHOOSE_CONFIG": HandleChooseConfig(root); break;
+ case "SAVE_CONFIG": HandleSaveConfig(root); break;
+ default: Console.WriteLine("Unknown action!"); break;
}
}
catch (Exception ex)
@@ -61,44 +45,44 @@ public void HandleMessage(object? sender, string message)
Console.WriteLine($"❌ Failed to process UI message: {ex.Message}");
}
}
+
private void HandleRunAction(JsonElement root)
{
HandleSaveConfig(root);
- var freshConfig = ConfigurationLoader.LoadConfig(_options.ConfigPath);
- if(freshConfig == null) return;
+ var freshConfig = ConfigurationLoader.LoadConfig(_options.ConfigPath ?? string.Empty);
+ if (freshConfig == null) return;
Console.WriteLine("▶ Settings updated! Starting background generation task...");
-
- Window?.SendWebMessage(JsonSerializer.Serialize(new
- {
- action = "GENERATING",
- }));
-
- _executor.OnMessageReady = (message) =>
+
+ Window?.SendWebMessage(JsonSerializer.Serialize(
+ new UiResponse { Action = "GENERATING" }, AppJsonContext.Default.UiResponse));
+
+ _executor.OnMessageReady = (msg) =>
{
Window?.Invoke(() =>
{
- Window?.SendWebMessage(message);
+ Window?.SendWebMessage(msg);
});
};
Task.Run(() => _executor.ExecuteGenerationAsync(freshConfig));
}
+
private void HandleSaveConfig(JsonElement root)
{
try
{
- var diskConfig = ConfigurationLoader.LoadConfig(_options.ConfigPath) ?? new();
-
+ var diskConfig = ConfigurationLoader.LoadConfig(_options.ConfigPath ?? string.Empty) ?? new();
+
var fields = root.GetProperty("data").GetRawText();
- var uiConfig = JsonSerializer.Deserialize>(fields);
- if(uiConfig != null)
+ var uiConfig = JsonSerializer.Deserialize(fields, AppJsonContext.Default.DictionaryStringFieldConfig);
+ if (uiConfig != null)
{
foreach (var (key, uiField) in uiConfig)
{
- if(key == "GLOBAL_SETTINGS") continue;
+ if (key == "GLOBAL_SETTINGS") continue;
if (!diskConfig.ContainsKey(key)) continue;
-
+
diskConfig[key].X = uiField.X ?? diskConfig[key].X;
diskConfig[key].Y = uiField.Y ?? diskConfig[key].Y;
diskConfig[key].BoxWidth = uiField.BoxWidth ?? diskConfig[key].BoxWidth;
@@ -107,15 +91,12 @@ private void HandleSaveConfig(JsonElement root)
diskConfig[key].Color = uiField.Color ?? diskConfig[key].Color;
}
}
-
- ConfigurationLoader.SaveConfig(_options.ConfigPath, diskConfig);
-
- Window?.SendWebMessage(JsonSerializer.Serialize(new
- {
- action = "CONFIG_SAVED",
- success = true
- }));
-
+
+ ConfigurationLoader.SaveConfig(_options.ConfigPath ?? string.Empty, diskConfig);
+
+ Window?.SendWebMessage(JsonSerializer.Serialize(
+ new UiResponse { Action = "CONFIG_SAVED", Success = true }, AppJsonContext.Default.UiResponse));
+
Console.WriteLine("✅ Config saved from UI panel");
}
catch (Exception ex)
@@ -123,98 +104,77 @@ private void HandleSaveConfig(JsonElement root)
Console.WriteLine($"❌ SaveConfig error: {ex.Message}");
}
}
+
private void HandleChooseCsv(JsonElement root)
{
var result = Dialog.FileOpen("csv");
- if(!result.IsOk) return;
+ if (!result.IsOk) return;
_options.CsvPath = result.Path;
- Window?.SendWebMessage(JsonSerializer.Serialize(new
- {
- action = "CSV_CHOSEN",
- name = Path.GetFileName(result.Path),
- }));
+ Window?.SendWebMessage(JsonSerializer.Serialize(
+ new UiResponse { Action = "CSV_CHOSEN", Name = Path.GetFileName(result.Path) }, AppJsonContext.Default.UiResponse));
}
+
private void HandleChooseTemplate(JsonElement root)
{
var result = Dialog.FileOpen("png");
- if(!result.IsOk) return;
+ if (!result.IsOk) return;
_options.TemplatePath = result.Path;
- Window?.SendWebMessage(JsonSerializer.Serialize(new
- {
- action = "TEMPLATE_CHOSEN",
- name = Path.GetFileName(result.Path),
- }));
+ Window?.SendWebMessage(JsonSerializer.Serialize(
+ new UiResponse { Action = "TEMPLATE_CHOSEN", Name = Path.GetFileName(result.Path) }, AppJsonContext.Default.UiResponse));
}
+
private void HandleChooseOutput(JsonElement root)
{
var result = Dialog.FolderPicker();
- if(!result.IsOk) return;
+ if (!result.IsOk) return;
_options.OutputDir = result.Path;
- Window?.SendWebMessage(JsonSerializer.Serialize(new
- {
- action = "OUTPUT_CHOSEN",
- name = Path.GetFileName(result.Path),
- }));
+ Window?.SendWebMessage(JsonSerializer.Serialize(
+ new UiResponse { Action = "OUTPUT_CHOSEN", Name = Path.GetFileName(result.Path) }, AppJsonContext.Default.UiResponse));
}
+
private void HandleLoadConfig(JsonElement root)
{
- if(string.IsNullOrEmpty(_options.ConfigPath) || !File.Exists(_options.ConfigPath))
+ if (string.IsNullOrEmpty(_options.ConfigPath) || !File.Exists(_options.ConfigPath))
{
- Window?.SendWebMessage(JsonSerializer.Serialize(new
- {
- action = "CONFIG_LOADED",
- error = "config.json not found",
- }));
+ Window?.SendWebMessage(JsonSerializer.Serialize(
+ new UiResponse { Action = "CONFIG_LOADED", Error = "config.json not found" }, AppJsonContext.Default.UiResponse));
+ return;
}
try
{
var config = ConfigurationLoader.LoadConfig(_options.ConfigPath);
- if(config is null)
+ if (config is null)
{
- Window?.SendWebMessage(JsonSerializer.Serialize(new
- {
- action = "CONFIG_LOADED",
- error = "Failed to parse config.json",
- }));
+ Window?.SendWebMessage(JsonSerializer.Serialize(
+ new UiResponse { Action = "CONFIG_LOADED", Error = "Failed to parse config.json" }, AppJsonContext.Default.UiResponse));
return;
}
config.Remove("GLOBAL_SETTINGS");
-
- Window?.SendWebMessage(JsonSerializer.Serialize(new
- {
- action = "CONFIG_LOADED",
- config = config,
- templatePath = _options.TemplatePath,
- }));
+ Window?.SendWebMessage(JsonSerializer.Serialize(
+ new UiResponse { Action = "CONFIG_LOADED", Config = config, TemplatePath = _options.TemplatePath }, AppJsonContext.Default.UiResponse));
Console.WriteLine($"Sent to JS: {config.Count} fields");
}
- catch(Exception ex)
+ catch (Exception ex)
{
Console.WriteLine($"LoadConfig error: {ex.Message}");
- Window?.SendWebMessage(JsonSerializer.Serialize(new
- {
- action = "CONFIG_LOADED",
- error = ex.Message,
- }));
+ Window?.SendWebMessage(JsonSerializer.Serialize(
+ new UiResponse { Action = "CONFIG_LOADED", Error = ex.Message }, AppJsonContext.Default.UiResponse));
}
}
+
private void HandleChooseConfig(JsonElement root)
{
var result = Dialog.FileOpen("json");
if (!result.IsOk) return;
_options.ConfigPath = result.Path;
- Window?.SendWebMessage(JsonSerializer.Serialize(new
- {
- action = "CONFIG_CHOSEN",
- name = Path.GetFileName(result.Path),
- path = result.Path,
- }));
+ Window?.SendWebMessage(JsonSerializer.Serialize(
+ new UiResponse { Action = "CONFIG_CHOSEN", Name = Path.GetFileName(result.Path), Path = result.Path }, AppJsonContext.Default.UiResponse));
}
}
-}
\ No newline at end of file
+}
diff --git a/Services/Executor.cs b/Services/Executor.cs
index f7b17b9..00e0356 100644
--- a/Services/Executor.cs
+++ b/Services/Executor.cs
@@ -64,12 +64,13 @@ public async Task ExecuteGenerationAsync(Dictionary config)
Console.WriteLine($"⚡ Швидкість: {imagesPerSecond:F2} зображень/сек");
Console.WriteLine($"📝 Звіт: {_options.ReportPath}");
- OnMessageReady?.Invoke(JsonSerializer.Serialize(new
- {
- action = "GENERATION_DONE",
- ok = logs.Count(l => l.IsSuccess),
- fail = logs.Count(l => !l.IsSuccess)
- }));
+ OnMessageReady?.Invoke(JsonSerializer.Serialize(
+ new UiResponse
+ {
+ Action = "GENERATION_DONE",
+ Ok = ok,
+ Fail = fail
+ }, AppJsonContext.Default.UiResponse));
}
catch (Exception ex)
{
diff --git a/Utils/ConfigurationLoader.cs b/Utils/ConfigurationLoader.cs
index 8518636..25ccb99 100644
--- a/Utils/ConfigurationLoader.cs
+++ b/Utils/ConfigurationLoader.cs
@@ -9,11 +9,12 @@ public static class ConfigurationLoader
{
public static Dictionary? LoadConfig(string path)
{
- // Your exact existing code goes here...
+ if (string.IsNullOrEmpty(path) || !File.Exists(path)) return null;
try
{
string json = File.ReadAllText(path, Encoding.UTF8);
- var result = JsonSerializer.Deserialize>(json);
+ // ВИКОРИСТОВУЄМО AOT КОНТЕКСТ
+ var result = JsonSerializer.Deserialize(json, AppJsonContext.Default.DictionaryStringFieldConfig);
if (result is null)
Console.WriteLine("❌ config.json порожній або невалідний.");
return result;
@@ -27,13 +28,34 @@ public static class ConfigurationLoader
public static List>? LoadCsv(string path)
{
+ if (string.IsNullOrEmpty(path) || !File.Exists(path)) return null;
try
{
+ var csvConfig = new CsvHelper.Configuration.CsvConfiguration(CultureInfo.InvariantCulture)
+ {
+ MissingFieldFound = null, // Відсутнє поле = null замість помилки
+ BadDataFound = null // Не падати через дрібні помилки форматування
+ };
+
using var reader = new StreamReader(path, Encoding.UTF8);
- using var csv = new CsvHelper.CsvReader(reader, CultureInfo.InvariantCulture);
- return csv.GetRecords()
- .Select(r => (IDictionary)r)
- .ToList();
+ using var csv = new CsvHelper.CsvReader(reader, csvConfig);
+
+ var records = new List>();
+
+ if (csv.Read() && csv.ReadHeader() && csv.HeaderRecord != null)
+ {
+ string[] headers = csv.HeaderRecord;
+ while (csv.Read())
+ {
+ var dict = new Dictionary();
+ foreach (var header in headers)
+ {
+ dict[header] = csv.GetField(header) ?? string.Empty;
+ }
+ records.Add(dict);
+ }
+ }
+ return records;
}
catch (Exception ex)
{
@@ -41,13 +63,13 @@ public static class ConfigurationLoader
return null;
}
}
+
public static void SaveConfig(string path, Dictionary config)
{
try
{
- // WriteIndented makes the JSON pretty and readable
- var jsonOptions = new JsonSerializerOptions { WriteIndented = true };
- string jsonString = JsonSerializer.Serialize(config, jsonOptions);
+ // ВИКОРИСТОВУЄМО AOT КОНТЕКСТ (Він вже має WriteIndented = true з атрибута)
+ string jsonString = JsonSerializer.Serialize(config, AppJsonContext.Default.DictionaryStringFieldConfig);
File.WriteAllText(path, jsonString, Encoding.UTF8);
Console.WriteLine("💾 config.json successfully overwritten with new UI coordinates.");