Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions AvifGenerator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@
<Nullable>enable</Nullable>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)' == 'AOT'">
<OutputType>Exe</OutputType>
<OptimizationPreference>Size</OptimizationPreference>
<PublishAot>true</PublishAot>
<SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="CsvHelper" Version="33.1.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.8" />
Expand Down
14 changes: 14 additions & 0 deletions Models/AppJsonContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Text.Json.Serialization;
using AvifGenerator.Models;

namespace AvifGenerator.Utils
{
[JsonSourceGenerationOptions(
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
[JsonSerializable(typeof(Dictionary<string, FieldConfig>))]
[JsonSerializable(typeof(UiResponse))]
internal partial class AppJsonContext : JsonSerializerContext
{
}
}
2 changes: 1 addition & 1 deletion Models/FieldConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ public class FieldConfig
[JsonPropertyName("BoxWidth")] public int? BoxWidth { get; set; }
[JsonPropertyName("BoxHeight")] public int? BoxHeight { get; set; }
}
}
}
17 changes: 17 additions & 0 deletions Models/UiResponse.cs
Original file line number Diff line number Diff line change
@@ -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<string, FieldConfig>? Config { get; set; }
[JsonPropertyName("ok")] public int? Ok { get; set; }
[JsonPropertyName("fail")] public int? Fail { get; set; }
}
}
2 changes: 1 addition & 1 deletion Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ static int Main(string[] args)
});

var serviceProvider = services.BuildServiceProvider();

var window = serviceProvider.GetRequiredService<PhotinoWindow>();
window.WaitForClose();

Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,20 +54,33 @@ dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=

```


**For Windows (With console window for debugging):**

```bash
dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true -p:OutputType=Exe

```

and with native performance use:

```bash
dotnet publish -c AOT -r win-x64
```

**For Linux:**

```bash
dotnet publish -c Release -r linux-x64 --self-contained true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true

```

and with native performance use:

```bash
dotnet publish -c AOT -r linux-x64
```

## 📖 How to Use

1. **Launch the App:** Open the compiled executable.
Expand Down
160 changes: 60 additions & 100 deletions Services/Bridge.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -30,75 +30,59 @@ 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)
{
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<Dictionary<string, FieldConfig>>(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;
Expand All @@ -107,114 +91,90 @@ 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)
{
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));
}
}
}
}
13 changes: 7 additions & 6 deletions Services/Executor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,13 @@ public async Task ExecuteGenerationAsync(Dictionary<string, FieldConfig> 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)
{
Expand Down
Loading