Big commit

This commit is contained in:
Gilles Lazures 2026-02-09 04:24:33 +01:00
parent 2d7e543163
commit fe85d2dcc4
44 changed files with 2120 additions and 71 deletions

View File

@ -7,6 +7,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishTrimmed>false</PublishTrimmed>
<ApplicationIcon>src/resources/build/icon.ico</ApplicationIcon>
</PropertyGroup>
<Target Name="PackRenderer" BeforeTargets="DispatchToInnerBuild;BeforeBuild">
<PropertyGroup>
@ -18,6 +19,11 @@
<Exec Command="dotnet &quot;$(BrikPackagerDll)&quot; --action pack --input &quot;$(InputPath)&quot; --output &quot;$(OutputPathFile)&quot; --key 66" />
<Message Importance="high" Text="[] Renderer packed at: $(OutputPathFile)" />
</Target>
<ItemGroup>
<None Update="src\resources\build\**">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="BrikPackager" Version="0.0.1" GeneratePathProperty="true" />
<PackageReference Include="Photino.NET" Version="4.0.16" />

View File

@ -2,7 +2,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.2.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Photino-Boilerplate", "Photino-Boilerplate.csproj", "{5FF73963-D009-62ED-7350-FF17EC625A26}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BrikInstaller", "BrikInstaller.csproj", "{5FF73963-D009-62ED-7350-FF17EC625A26}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution

19
src/main/Constants.cs Normal file
View File

@ -0,0 +1,19 @@
using System.Reflection;
using System.Text.Json;
using BrikInstaller.Utils;
namespace BrikInstaller;
public static class Constants {
public static readonly JsonSerializerOptions _jsonOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
public static readonly string os = OsHelper.getOperatingSystem();
public static readonly string arch = OsHelper.getArchitecture();
public static readonly string env = "DEV";
public static readonly string __dirname = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
public static readonly string BaseDirectory = AppDomain.CurrentDomain.BaseDirectory;
public static class URLs {
public static readonly string basePoint = "https://brik.azures.fr/products";
}
}

View File

@ -1,28 +1,29 @@
using System.Reflection;
using System.Text.Json;
using Photino.NET;
using BrikPackager;
using PhotinoBoilerplate.Utils;
using Photino.NET;
using System.Text.Json;
using BrikInstaller.Utils;
using BrikInstaller.Schemas;
using BrikInstaller.Services;
namespace PhotinoBoilerplate;
namespace BrikInstaller;
class Program{
public static readonly JsonSerializerOptions _jsonOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
private static readonly string __dirname = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
[STAThread]
static void Main(string[] args) {
CreateMainWindow().WaitForClose();
}
public static PhotinoWindow CreateMainWindow() {
BrikPackage bpkg = new BrikPackage(Path.Combine(__dirname, "root.dat"), 66);
BrikPackage bpkg = new BrikPackage(Path.Combine(Constants.__dirname, "root.dat"), 66);
List<string> entries = bpkg.ListEntries();
foreach (string entry in entries) {
Console.WriteLine(entry);
}
var window = new PhotinoWindow()
.SetTitle("Photino Boilerplate")
.SetTitle("Brik Installer")
.SetUseOsDefaultLocation(false)
.SetUseOsDefaultSize(false)
.SetChromeless(true)
.RegisterCustomSchemeHandler("http", (object sender, string scheme, string url, out string contentType) =>{
Uri uri = new Uri(url);
switch (uri.Host){
@ -38,41 +39,112 @@ class Program{
});
window.WindowCreated += (sender, e) => {
string iconPath = Path.Combine(Constants.BaseDirectory, "src", "resources", "build", "icon.ico");
SetupBridge(window);
WindowHelper.setSize(window, 900, 600);
WindowHelper.setSize(window, 465, 290);
WindowHelper.RemoveTitleBar(window);
WindowHelper.ForceTaskbarIcon(window, iconPath);
window.Center();
};
window.Load("http://internal/index.html");
LoadPage(window, "index.html");
return window;
}
public static void SetupBridge(PhotinoWindow window) {
public static void SetupBridge(PhotinoWindow window) {
window.RegisterWebMessageReceivedHandler(async (object? sender, string message) => {
try {
var json = JsonDocument.Parse(message);
string requestId = json.RootElement.GetProperty("requestId").GetString()!;
string method = json.RootElement.GetProperty("functionName").GetString()!;
var jsonPayload = json.RootElement.GetProperty("payload");
object? responsePayload = null;
var pw = (PhotinoWindow)sender!;
object? payload = null;
switch (method) {
case "hello::world":
payload = "Hello from C#";
case "network::fetch":
responsePayload = await NetworkService.HandleFetchRequest(jsonPayload);
break;
case "check::requirements":
if (jsonPayload.TryGetProperty("soft", out var soft)) {
RequirementCheckingResponse requirementChecking = await RequirementsService.CheckRequirementsAsync(pw, soft.GetString()!);
if (!requirementChecking.Success) {
IPC.sendToRenderer(window, "requirement::failed", null!);
} else {
IPC.sendToRenderer(window, "requirement::success", null!);
}
} else {
responsePayload = new { success = false, error = "Soft name missing, wtf?" };
}
break;
case "browser::open":
if (jsonPayload.TryGetProperty("url", out var url)) {
BashUtils.OpenUrl(url.ToString());
} else {
responsePayload = new { success = false, error = "Soft name missing, wtf?" };
}
break;
case "installer::elevate":
InstallationService.ElevateAndRestart();
responsePayload = new { success = true };
break;
case "installer::testInstallationPath":
if (jsonPayload.TryGetProperty("path", out var installPath)) {
bool canWrite = InstallationService.CanCreateInPath(installPath.ToString());
if (!canWrite) {
InstallationService.ElevateAndRestart();
responsePayload = new { success = canWrite };
} else {
responsePayload = new { success = canWrite };
}
} else {
responsePayload = new { success = false };
}
break;
case "dialog::selectDirectory":
string[] selectedFolders = window.ShowOpenFolder();
if (selectedFolders != null && selectedFolders.Length > 0) {
responsePayload = new { success = true, path = selectedFolders[0] };
} else {
responsePayload = new { success = false };
}
break;
case "installer::defaultInstallPath":
if (jsonPayload.TryGetProperty("soft", out var productName)) {
string defaultInstallPath = InstallationService.GetDefaultInstallationPath(productName.ToString());
responsePayload = new { success = true, path = defaultInstallPath };
} else {
responsePayload = new { success = false };
}
break;
case "installer::install":
if (jsonPayload.TryGetProperty("soft", out var softName) && jsonPayload.TryGetProperty("path", out var softInstallPath)) {
try {
await GenericFilesService.SyncFilesAsync($"{Constants.URLs.basePoint}/{softName}/{Constants.os}-{Constants.arch}/download/", $"{Constants.URLs.basePoint}/{softName}/{Constants.os}-{Constants.arch}/manifest", softInstallPath.ToString());
responsePayload = new { success = true };
} catch (Exception) {
responsePayload = new { success = false };
}
}
break;
}
var response = new { requestId, payload };
window.SendWebMessage(JsonSerializer.Serialize(response, _jsonOptions));
var finalResponse = new { requestId, payload = responsePayload };
window.SendWebMessage(JsonSerializer.Serialize(finalResponse, Constants._jsonOptions));
} catch (Exception ex) {
Console.WriteLine($"Error stacktrace: {ex.StackTrace}");
Console.WriteLine($"Bridge error: {ex.Message}");
}
});
}
public static void LoadPage(PhotinoWindow window, string url) {
if (Constants.env == "PROD") {
window.Load($"http://internal/{url}");
} else {
window.Load($"src/resources/renderer/{url}");
}
}
}

70
src/main/helpers/Bash.cs Normal file
View File

@ -0,0 +1,70 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace BrikInstaller.Utils;
public static class BashUtils {
public static string GetCommandOutput(string command) {
string shell = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "cmd.exe" : "/bin/bash";
string argPrefix = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "/c" : "-c";
string formattedCommand = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? command
: $"\"{command.Replace("\"", "\\\"")}\"";
using var proc = new Process {
StartInfo = new ProcessStartInfo {
FileName = shell,
Arguments = $"{argPrefix} {formattedCommand}",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
StandardOutputEncoding = System.Text.Encoding.UTF8
}
};
try {
proc.Start();
string output = proc.StandardOutput.ReadToEnd();
string error = proc.StandardError.ReadToEnd();
proc.WaitForExit();
return string.IsNullOrEmpty(output) ? error : output;
} catch (Exception ex) {
return $"Execution error : {ex.Message}";
}
}
public static string GetBashCommandOutput(string command) {
using var proc = new Process {
StartInfo = new ProcessStartInfo {
FileName = "/bin/bash",
Arguments = $"-c \"{command}\"",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};
proc.Start();
return proc.StandardOutput.ReadToEnd();
}
public static void OpenUrl(string url) {
try {
Process.Start(url);
}
catch {
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
url = url.Replace("&", "^&");
Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });
} else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) {
Process.Start("xdg-open", url);
} else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) {
Process.Start("open", url);
} else {
throw;
}
}
}
}

51
src/main/helpers/Http.cs Normal file
View File

@ -0,0 +1,51 @@
using System.Net.Http.Json;
using BrikInstaller;
public static class HttpHelper {
private static readonly HttpClient Http = new HttpClient();
public static async Task<HttpResponseMessage> FetchAsync(string url, HttpMethod? method = null, object? body = null, Dictionary<string, string>? headers = null) {
Console.WriteLine($"Fetching {url} using {method} method");
var request = new HttpRequestMessage(method ?? HttpMethod.Get, url);
if (headers != null) {
foreach (var header in headers) {
request.Headers.TryAddWithoutValidation(header.Key, header.Value);
}
}
if (body != null) {
if (body is HttpContent httpContent) {
request.Content = httpContent;
} else if (body is string s) {
request.Content = new StringContent(s, System.Text.Encoding.UTF8, "application/json");
} else {
request.Content = JsonContent.Create(body, options: Constants._jsonOptions);
}
}
request.Headers.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
return await Http.SendAsync(request);
}
public static async Task DownloadFileAsync(string url, string destinationPath) {
Console.WriteLine($"Downloading file from {url} to {destinationPath}");
string? directory = Path.GetDirectoryName(destinationPath);
if (!string.IsNullOrEmpty(directory)) {
Directory.CreateDirectory(directory);
}
if (File.Exists(destinationPath)) {
File.Delete(destinationPath);
}
using var response = await HttpHelper.FetchAsync(url);
response.EnsureSuccessStatusCode();
using var streamToReadFrom = await response.Content.ReadAsStreamAsync();
using var streamToWriteTo = File.Create(destinationPath);
await streamToReadFrom.CopyToAsync(streamToWriteTo);
}
}

31
src/main/helpers/Os.cs Normal file
View File

@ -0,0 +1,31 @@
using System.Runtime.InteropServices;
namespace BrikInstaller.Utils;
public static class OsHelper {
public static string getOperatingSystem() {
string os = "unknown";
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
os = "win";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) {
os = "linux";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) {
os = "macos";
}
return os;
}
public static string getArchitecture() {
string arch = RuntimeInformation.OSArchitecture switch {
Architecture.X64 => "x64",
Architecture.X86 => "x86",
Architecture.Arm64 => "arm64",
Architecture.Arm => "arm",
_ => RuntimeInformation.OSArchitecture.ToString().ToLower()
};
return arch;
}
}

View File

@ -1,13 +1,39 @@
using Photino.NET;
using System.Drawing;
using System.Runtime.InteropServices;
namespace PhotinoBoilerplate.Utils;
namespace BrikInstaller.Utils;
public static class WindowHelper {
private const float StandardDpi = 96.0f;
[DllImport("user32.dll")]
static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
[DllImport("user32.dll")]
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr LoadImage(IntPtr hinst, string lpszName, uint uType, int cxDesired, int cyDesired, uint fuLoad);
const int GWL_STYLE = -16;
const int WS_CAPTION = 0x00C00000;
const int WS_THICKFRAME = 0x00040000;
const int WS_MINIMIZEBOX = 0x00020000;
const int WS_MAXIMIZEBOX = 0x00010000;
const int WS_SYSMENU = 0x00080000;
const uint SWP_FRAMECHANGED = 0x0020;
const uint SWP_NOMOVE = 0x0002;
const uint SWP_NOSIZE = 0x0001;
const uint SWP_NOZORDER = 0x0004;
const uint WM_SETICON = 0x0080;
const IntPtr ICON_SMALL = 0;
const IntPtr ICON_BIG = 1;
const uint IMAGE_ICON = 1;
const uint LR_LOADFROMFILE = 0x00000010;
public static Size GetScaledSize(PhotinoWindow window, int logicalWidth, int logicalHeight) {
float currentDpi = window.ScreenDpi > 0 ? window.ScreenDpi : StandardDpi;
@ -22,8 +48,27 @@ public static class WindowHelper {
public static void setSize(PhotinoWindow window, int width, int height) {
Size targetSize = GetScaledSize(window, width, height);
window
.SetResizable(true)
.SetResizable(false)
.SetSize(targetSize)
.SetMinSize(targetSize.Width, targetSize.Height);
}
public static void RemoveTitleBar(PhotinoWindow window) {
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
IntPtr hWnd = window.WindowHandle;
int style = GetWindowLong(hWnd, GWL_STYLE);
style &= ~(WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU);
SetWindowLong(hWnd, GWL_STYLE, style);
SetWindowPos(hWnd, IntPtr.Zero, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER);
}
}
public static void ForceTaskbarIcon(PhotinoWindow window, string iconPath) {
IntPtr hWnd = window.WindowHandle;
IntPtr hIcon = LoadImage(IntPtr.Zero, iconPath, IMAGE_ICON, 0, 0, LR_LOADFROMFILE);
if (hIcon != IntPtr.Zero) {
SendMessage(hWnd, WM_SETICON, ICON_SMALL, hIcon);
SendMessage(hWnd, WM_SETICON, ICON_BIG, hIcon);
}
}
}

View File

@ -0,0 +1,20 @@
namespace BrikInstaller.Schemas;
public class GenericArtifact {
public string Path { get; set; } = string.Empty;
public string Sha1 { get; set; } = string.Empty;
public long Size { get; set; }
public string Url { get; set; } = string.Empty;
}
public class BrikFilesIndex {
public List<ArtifactEntry> Root { get; set; } = new();
}
public class ArtifactEntry {
public ArtifactContainer Downloads { get; set; } = null!;
}
public class ArtifactContainer {
public GenericArtifact Artifact { get; set; } = null!;
}

View File

@ -0,0 +1,10 @@
namespace BrikInstaller.Schemas;
public class ProductRequirement {
public string? Id { get; set; }
public string? Name { get; set; }
public string? CheckCommand { get; set; }
public string? ExpectedOutputPattern { get; set; }
public string? InstallationMode { get; set; }
public string? InstallationUrl { get; set; }
}

View File

@ -0,0 +1,9 @@
namespace BrikInstaller.Schemas;
public class RequirementCheckingResponse {
public bool Success { get; set; }
public string? Error { get; set; }
public int? Status { get; set; }
public bool? hasNoDeps { get; set; }
}

View File

@ -0,0 +1,56 @@
using System.Security.Cryptography;
using System.Net.Http.Json;
using BrikInstaller.Schemas;
namespace BrikInstaller.Services;
public static class GenericFilesService {
private static readonly HttpClient _httpClient = new();
public static async Task SyncFilesAsync(string rootEndpoint, string apiUrl, string destinationDir) {
try {
var index = await _httpClient.GetFromJsonAsync<BrikFilesIndex>(apiUrl);
if (index?.Root == null) return;
var artifacts = index.Root.Select(r => r.Downloads.Artifact).ToList();
await ProcessDownloadsAsync(rootEndpoint, artifacts, destinationDir);
}
catch (Exception ex) {
Console.WriteLine($"[Sync Error] {ex.Message}");
}
}
private static async Task ProcessDownloadsAsync(string rootEndpoint, List<GenericArtifact> artifacts, string baseDir) {
var queue = artifacts.Where(a => !IsFileValid(Path.Combine(baseDir, a.Path), a)).ToList();
if (queue.Count == 0) return;
await Parallel.ForEachAsync(queue, new ParallelOptions { MaxDegreeOfParallelism = 4 }, async (artifact, token) => {
string localPath = Path.Combine(baseDir, artifact.Path);
try {
await HttpHelper.DownloadFileAsync(rootEndpoint + artifact.Url, localPath);
if (!IsFileValid(localPath, artifact)) {
Console.WriteLine($"[Validation Failed] {artifact.Path}");
}
}
catch (Exception ex) {
Console.WriteLine($"[Download Error] {artifact.Path}: {ex.Message}");
}
});
}
public static bool IsFileValid(string filePath, GenericArtifact expected) {
if (!File.Exists(filePath)) return false;
var info = new FileInfo(filePath);
if (info.Length != expected.Size) return false;
using var stream = File.OpenRead(filePath);
byte[] hashBytes = SHA1.HashData(stream);
string hash = BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
return hash == expected.Sha1.ToLowerInvariant();
}
}

19
src/main/services/IPC.cs Normal file
View File

@ -0,0 +1,19 @@
using System.Text.Json;
using Photino.NET;
namespace BrikInstaller.Services;
public static class IPC {
public static void sendRequirementCheckConsoleLog(PhotinoWindow window, string log) {
sendToRenderer(window, "requirement::log", log);
}
public static void sendToRenderer(PhotinoWindow window, string requestId, object data) {
var logMessage = new {
requestId,
payload = new { message = data }
};
window.SendWebMessage(JsonSerializer.Serialize(logMessage, Constants._jsonOptions));
}
}

View File

@ -0,0 +1,76 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace BrikInstaller.Services;
public static class InstallationService {
public static bool CanCreateInPath(string folderPath) {
if (string.IsNullOrWhiteSpace(folderPath)) return false;
try {
string fullPath = Path.GetFullPath(folderPath);
DirectoryInfo di = new DirectoryInfo(fullPath);
while (di != null && !di.Exists) {
di = di.Parent!;
}
if (di == null) return false;
string testPath = Path.Combine(di.FullName, Guid.NewGuid().ToString());
Directory.CreateDirectory(testPath);
Directory.Delete(testPath);
return true;
} catch (UnauthorizedAccessException) {
return false;
} catch (Exception) {
return false;
}
}
public static void ElevateAndRestart() {
string exePath = Process.GetCurrentProcess().MainModule!.FileName;
ProcessStartInfo startInfo = new ProcessStartInfo();
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
startInfo.FileName = exePath;
startInfo.Verb = "runas";
startInfo.UseShellExecute = true;
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) {
startInfo.FileName = "pkexec";
startInfo.Arguments = $"\"{exePath}\"";
startInfo.UseShellExecute = false;
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) {
startInfo.FileName = "osascript";
startInfo.Arguments = $"-e \"do shell script \\\"{exePath}\\\" with administrator privileges\"";
startInfo.UseShellExecute = false;
}
try {
Process.Start(startInfo);
Environment.Exit(0);
} catch (Exception ex) {
Console.WriteLine($"Elevation failed : {ex.Message}");
}
}
public static string GetDefaultInstallationPath(string appName) {
string final = "";
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
final = Path.Combine(localAppData, "Programs", appName);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) {
string home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
final = Path.Combine(home, ".local", "share", appName.ToLower().Replace(" ", "_"));
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) {
final = $"/Applications/{appName}.app";
}
return final;
}
}

View File

@ -0,0 +1,30 @@
using System.Text.Json;
using BrikInstaller;
public static class NetworkService {
public static async Task<object> HandleFetchRequest(JsonElement jsonPayload) {
try {
string url = jsonPayload.GetProperty("url").GetString()!;
string httpMethodStr = jsonPayload.TryGetProperty("method", out var m) ? m.GetString()! : "GET";
string? body = jsonPayload.TryGetProperty("body", out var b) ? b.GetString() : null;
Dictionary<string, string>? headers = null;
if (jsonPayload.TryGetProperty("headers", out var h)) {
headers = JsonSerializer.Deserialize<Dictionary<string, string>>(h.GetRawText(), Constants._jsonOptions);
}
var httpMethod = new HttpMethod(httpMethodStr.ToUpper());
var response = await HttpHelper.FetchAsync(url, httpMethod, body, headers);
string content = await response.Content.ReadAsStringAsync();
return new {
status = (int)response.StatusCode,
success = response.IsSuccessStatusCode,
data = content
};
} catch (Exception ex) {
return new { success = false, error = ex.Message };
}
}
}

View File

@ -0,0 +1,96 @@
using System.Runtime.InteropServices;
using System.Text.Json;
using System.Text.RegularExpressions;
using BrikInstaller.Schemas;
using BrikInstaller.Utils;
using Photino.NET;
namespace BrikInstaller.Services;
public static class RequirementsService {
public static async Task<RequirementCheckingResponse> CheckRequirementsAsync(PhotinoWindow window, string softName) {
try {
string requirementsUrl = $"https://brik.azures.fr/products/{softName}/{Constants.os}-{Constants.arch}/requirements";
var response = await HttpHelper.FetchAsync(requirementsUrl, HttpMethod.Get);
if (!response.IsSuccessStatusCode) {
return new RequirementCheckingResponse { Success = false, Status = (int)response.StatusCode };
}
string jsonResponse = await response.Content.ReadAsStringAsync();
var requirements = JsonSerializer.Deserialize<List<ProductRequirement>>(jsonResponse, Constants._jsonOptions);
if (requirements != null) {
if (requirements.Count == 0) {
return new RequirementCheckingResponse{ Success = true, hasNoDeps = true };
}
foreach (var element in requirements) {
IPC.sendRequirementCheckConsoleLog(window, $"<span data-i18n=\"log_checking_requirement\"></span><b>{element.Name}</b>");
int state = await ProcessRequirement(element);
switch (state) {
case 0:
IPC.sendRequirementCheckConsoleLog(window, $"<span data-i18n=\"log_checking_requirement_error\"></span> <b>{element.Name}</b>");
IPC.sendRequirementCheckConsoleLog(window, $"<span data-i18n=\"log_checking_requirement_to_manual_install\"></span><b>{element.Name}</b>");
IPC.sendRequirementCheckConsoleLog(window, $"<span data-i18n=\"log_checking_requirement_to_manual_install_1\"></span>");
return new RequirementCheckingResponse{ Success = false };
case 1:
IPC.sendRequirementCheckConsoleLog(window, $"<span data-i18n=\"log_checking_requirement_error\"></span> <b>{element.Name}</b>");
IPC.sendRequirementCheckConsoleLog(window, $"<span data-i18n=\"log_checking_requirement_attempt_to_auto_install\"></span><b>{element.Name}</b>");
return new RequirementCheckingResponse{ Success = false };
case 2:
IPC.sendRequirementCheckConsoleLog(window, $"<span data-i18n=\"log_checking_requirement_success\"></span>");
break;
}
}
}
return new RequirementCheckingResponse{ Success = true };
} catch (Exception ex) {
Console.WriteLine($"[RequirementsService] Error: {ex.Message}");
return new RequirementCheckingResponse{ Success = false, Error = ex.Message };
}
}
public static async Task<int> ProcessRequirement(ProductRequirement requirement) {
string checkOutput = BashUtils.GetCommandOutput(requirement.CheckCommand!);
bool isPresent = Regex.IsMatch(checkOutput, requirement.ExpectedOutputPattern!, RegexOptions.IgnoreCase);
if (!isPresent) {
bool isAutoinstalled = await RequirementInstallationService.InstallAsync(requirement);
return isAutoinstalled == true ? 1 : 0;
} else {
return 2;
}
}
}
public static class RequirementInstallationService {
public static async Task<bool> InstallAsync(ProductRequirement req) {
if (string.IsNullOrEmpty(req.InstallationUrl)) return false;
if (req.InstallationMode == "manual") {
BashUtils.OpenUrl(req.InstallationUrl);
return false;
}
if (req.InstallationMode == "auto") {
try {
var response = await HttpHelper.FetchAsync(req.InstallationUrl);
if (!response.IsSuccessStatusCode) return false;
string extension = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".ps1" : ".sh";
string tempPath = Path.Combine(Path.GetTempPath(), $"brik_install_{req.Id}{extension}");
await File.WriteAllBytesAsync(tempPath, await response.Content.ReadAsByteArrayAsync());
string cmd = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? $"powershell -ExecutionPolicy Bypass -File \"{tempPath}\""
: $"chmod +x \"{tempPath}\" && \"{tempPath}\"";
BashUtils.GetCommandOutput(cmd);
return true;
} catch { return false; }
}
return false;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,166 @@
@import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap");
* {
margin: 0px;
padding: 0px;
user-select: none;
box-sizing: border-box;
}
main {
position: absolute;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
overflow: hidden;
}
aside {
width: 100%;
height: 100%;
}
aside > article.frame {
width: 100%;
height: 100%;
color: #ffffff;
padding: 17px 17px 17px 17px;
font-family: "Poppins", sans-serif;
background-color: #212121;
}
img {
pointer-events: none;
}
button {
font-family: "Poppins", sans-serif;
}
p {
line-height: 20px;
}
a {
color: #dfa615;
cursor: pointer;
font-size: 12px;
transition: .3s color;
}
a:hover {
color: #c99001;
}
a:active {
color: #ffb700;
}
input[type="checkbox"] {
appearance: none;
-webkit-appearance: none;
width: 16px;
height: 16px;
background-color: #2a2a2a;
border: 1px solid #333;
border-radius: 4px;
cursor: pointer;
position: relative;
}
input[type="checkbox"]:checked {
background-color: #ffb700;
border-color: #ffb700;
}
input[type="checkbox"]:checked::after {
content: "";
position: absolute;
top: 1px;
left: 4px;
width: 4px;
height: 8px;
border: solid white;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
input[type=text] {
width: 100%;
height: 24px;
border: none;
outline: none;
padding: 0px 7px;
border-radius: 7px;
}
div.loader {
overflow: hidden;
position: absolute;
left: 0px;
right: 0px;
bottom: 0px;
width: 100%;
height: 6px;
}
div.loader > div.full {
height: 6px;
width: 100%;
}
div.loader > div.full > div.loading {
height: 6px;
width: 50%;
transition: 1s width;
background-color: #dfa615;
}
div.loader > div.full > div.loading {
animation: animateLoadingEffect 0.7s linear infinite;
}
@keyframes animateLoadingEffect {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(200%);
}
}
.fi-es {
transform: scale(1.02);
transform-origin: center;
image-rendering: -webkit-optimize-contrast;
}
.fi {
backface-visibility: hidden;
-webkit-font-smoothing: antialiased;
}
[hidden] {
display: none !important;
visibility: hidden !important;
}
::-webkit-scrollbar {
width: 5px;
height: 5px;
}
::-webkit-scrollbar-track {
background-color: transparent;
}
::-webkit-scrollbar-thumb {
background-color: #ffffff3d;
border-radius: 5px;
}
::-webkit-scrollbar-thumb:hover {
background-color: #ffffff5e;
}

View File

@ -1,45 +1,151 @@
@import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap");
* {
margin: 0px;
padding: 0px;
box-sizing: border-box;
h1 {
font-weight: 550;
}
main {
display: flex;
position: absolute;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
flex-wrap: wrap;
flex-direction: column;
gap: 10px;
color: #cecece;
background-color: #404551;
details > summary {
font-size: small;
font-weight: 500px;
}
details {
width: 100%;
}
details.language {
margin-top: 10px;
}
aside > article.frame > section.author {
display: inline-flex;
align-items: center;
justify-content: center;
font-family: "Poppins", sans-serif;
position: fixed;
bottom: 10px;
left: 10px;
}
aside > article.frame > section.author > img {
width: 25px;
height: 25px;
margin-right: 5px;
border-radius: 5px;
}
aside > article.frame > section.author > p {
font-size: 12px;
}
aside > article.frame > section.buttons,
aside > article.frame > section.extra_buttons {
display: inline-flex;
align-items: center;
flex-direction: row-reverse;
position: fixed;
bottom: 10px;
right: 10px;
}
button {
height: 25px;
min-width: 50px;
padding-left: 10px;
padding-right: 10px;
cursor: pointer;
padding: 7px 7px 7px 7px;
color: #cecece;
font-weight: 500;
border: none;
outline: none;
border-radius: 5px;
background-color: #c8850a;
transition: .3s all;
font-family: "Poppins", sans-serif;
color: #ffffff;
border-radius: 3px;
font-weight: 500;
transition: .3s background-color;
margin-left: 4px;
margin-right: 4px;
background-color: #303030;
}
button:hover {
filter: brightness(0.95);
background-color: #3a3a3a;
}
button:active {
filter: brightness(1.10);
button.primary {
background-color: #dfa615;
}
button.primary:hover {
background-color: #c99001;
}
div.console {
background-color: #1b1b1b;
width: 100%;
height: 120px;
font-size: small;
padding: 7px 7px 7px 7px;
margin-top: 14px;
border-radius: .3s;
border-radius: 7px;
overflow-y: auto;
border: 1px solid #333;
}
article.frame.eula > div.console {
white-space: pre-wrap;
word-wrap: break-word;
font-family: monospace;
}
input[type=text] {
color: #cecece;
background-color: #1b1b1b;
}
article.frame.choosePath > div.installation_path {
display: flex;
align-items: center;
width: 100%;
height: 25px;
margin-top: 10px;
box-sizing: border-box;
}
article.frame.choosePath > div.installation_path > input[type=text] {
height: 100%;
font-size: 12px;
border-radius: 3px;
border: 1px solid #333;
flex: 1;
margin-top: 0;
box-sizing: border-box;
}
article.frame.choosePath > div.installation_path > button {
height: 100%;
margin-left: 5px;
margin-right: 0;
width: auto;
box-sizing: border-box;
}
div.title {
display: inline-flex;
align-items: center;
width: 100%;
height: 32px;
}
div.title > img {
width: 32px;
height: 32px;
margin-right: 6px;
}
form > div {
display: inline-flex;
align-items: center;
flex-wrap: wrap;
margin-top: 10px;
}
form > div > label {
font-size: 14px;
margin-left: 6px;
margin-top: 1px;
}

View File

@ -0,0 +1,76 @@
::marker {
content: "";
}
::-webkit-details-marker {
display: none;
}
details {
font-family: "Poppins", sans-serif;
width: 200px;
height: 30px;
cursor: pointer;
background-color: #303030;
border-radius: 5px;
outline: none;
}
details > summary {
display: flex;
flex-wrap: wrap;
align-items: center;
height: 30px;
padding-left: 10px;
outline: none;
}
details > section.options {
width: auto;
margin-top: 5px;
background-color: #303030;
border-radius: 5px;
overflow-y: auto;
max-height: 90px;
outline: none;
}
details > section.options > ul > li {
display: flex;
align-items: center;
padding-left: 7px;
height: 30px;
border-radius: 5px;
transition: .3s all;
}
details > section.options > ul > li > input[type=radio] {
visibility: hidden;
display: none;
}
details > section.options > ul > li > label {
display: block;
width: 100%;
font-size: 14px;
}
details > section.options > ul > li > label > img,
details > summary > img {
height: 26px;
margin-right: 7px;
margin-left: 2px;
vertical-align: middle;
}
span.fi {
margin-right: 5px;
}
details > section.options > ul > li:hover {
background-color: #3b3b3b8f;
}
details > section.options > ul > li:has(input:checked) {
background-color: #3b3b3bc9;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,43 @@
function getPathSeparator() {
const isWindows = navigator.platform.toLowerCase() == "win32"
return isWindows ? "\\" : "/"
}
const separator = getPathSeparator()
function toggleLoadingBar() {
const loader = document.querySelector("article.frame:not([hidden]) > div.loader")
if (loader.hasAttribute("hidden")) {
loader.removeAttribute("hidden")
} else {
loader.setAttribute("hidden", "")
}
}
function showLoadingBar() {
const loader = document.querySelector("article.frame:not([hidden]) > div.loader")
if (loader.hasAttribute("hidden")) {
loader.removeAttribute("hidden")
}
}
function hideLoadingBar() {
const loader = document.querySelector("article.frame:not([hidden]) > div.loader")
if (!loader.hasAttribute("hidden")) {
loader.setAttribute("hidden", "")
}
}
window.addEventListener("wheel", (e) => {
if (e.ctrlKey) {
e.preventDefault()
}
}, { passive: false })
window.onpopstate = () => {
window.history.pushState(null, document.title, window.location.href)
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

View File

@ -0,0 +1,32 @@
const frames = document.querySelectorAll("article.frame")
const frameCallbacks = {}
function showFrame(frameIdentifier) {
for (const frame of frames) {
frame.setAttribute("hidden", "")
if (frame.classList.contains(frameIdentifier)) {
frame.removeAttribute("hidden")
if (Array.isArray(frameCallbacks[frameIdentifier])) {
for (const callback of frameCallbacks[frameIdentifier]) {
callback()
}
}
}
}
}
function showFrameNavigationButtons() {
document.querySelector("article.frame:not([hidden]) > section.buttons").removeAttribute("hidden")
}
function hideFrameNavigationButtons() {
document.querySelector("article.frame:not([hidden]) > section.buttons").setAttribute("hidden", "")
}
function onFrameShowed(frameIdentifier, cb) {
if (!frameCallbacks[frameIdentifier]) {
frameCallbacks[frameIdentifier] = []
}
frameCallbacks[frameIdentifier].push(cb)
}

View File

@ -1,4 +1,274 @@
async function callCSharp() {
const result = await system.call("hello::world")
alert(result)
}
let products = []
i18next
.use(i18nextHttpBackend)
.init({
lng: "fr",
fallbackLng: "fr",
backend: {
loadPath: "./assets/locales/{{lng}}.json",
}
}, (err, t) => {
updatePageContent()
})
function updatePageContent() {
document.querySelectorAll("[data-i18n]").forEach(el => {
let key = el.getAttribute("data-i18n")
if (key.startsWith("[")) {
const parts = key.split("]")
const attr = parts[0].substring(1)
const actualKey = parts[1]
el.setAttribute(attr, i18next.t(actualKey))
} else {
el.innerText = i18next.t(key)
}
})
}
async function chooseLanguage(dropdownSelector, option) {
const select = document.querySelector(`details.${dropdownSelector}`)
await choose(dropdownSelector, option)
i18next.changeLanguage(select.getAttribute("value"), () => {
updatePageContent()
})
}
async function fetchProducts() {
const result = await system.call("network::fetch", {
url: "https://brik.azures.fr/products/",
method: "GET",
headers: {
"Accept": "application/json"
}
})
if (!result.success) {
return console.error("[N::F>(GP)] Error !")
}
const data = JSON.parse(result.data)
for (const productName of data) {
products.push({
name: productName,
...await fetchInstallerResourcesFor(productName)
})
}
await makeProductList()
}
async function fetchInstallerResourcesFor(productName) {
const result = await system.call("network::fetch", {
url: `https://brik.azures.fr/products/${productName}/installerResource`,
method: "GET",
headers: {
"Accept": "application/json"
}
})
if (!result.success) {
return console.error("[N::F>(GIR)] Error !")
}
return JSON.parse(result.data)
}
async function makeProductList() {
const productDropdown = document.querySelector("details.product")
const ul = document.createElement("ul")
for (const product of products) {
const index = products.indexOf(product)
const li = document.createElement("li")
const radio = document.createElement("input")
const label = document.createElement("label")
const logo = document.createElement("img")
const title = document.createElement("span")
radio.setAttribute("type", "radio")
radio.setAttribute("name", "radio")
radio.setAttribute("value", index)
radio.setAttribute("id", `box_soft_${index}`)
radio.setAttribute("onclick", "choose('product', this)")
logo.setAttribute("src", product.logo)
title.innerText = product.name
label.setAttribute("for", `box_soft_${index}`)
label.appendChild(logo)
label.appendChild(title)
li.appendChild(radio)
li.appendChild(label)
ul.appendChild(li)
}
const options = productDropdown.querySelector("section.options > ul")
options.innerHTML = ul.innerHTML
}
function clearLogIntoRequirementConsole() {
const logsContainer = document.querySelector("article.frame.requirements > div.console")
logsContainer.innerHTML = ""
}
function putLogIntoRequirementConsole(log) {
const logsContainer = document.querySelector("article.frame.requirements > div.console")
const code = document.createElement("code")
code.innerHTML = log
logsContainer.appendChild(code)
logsContainer.innerHTML += "<br>"
}
function showFrameExtraButtons() {
document.querySelector("article.frame:not([hidden]) > section.extra_buttons").removeAttribute("hidden")
}
function hideFrameExtraButtons() {
document.querySelector("article.frame:not([hidden]) > section.extra_buttons").setAttribute("hidden", "")
}
function getSelectedSoftware() {
const productDropdown = document.querySelector("details.product")
const productIndex = productDropdown.getAttribute("value") + 1
const productName = productDropdown.querySelector(`li:nth-of-type(${productIndex})`).children[1].innerText
return productName
}
function filterProductArray(productName) {
products = products.filter(product => product.name == productName)
}
function writeEula(eula) {
const eulaContainer = document.querySelector("article.frame.eula > div.console")
const pre = document.createElement("pre")
pre.innerText = eula
eulaContainer.appendChild(pre)
}
function clearEula() {
const eulaContainer = document.querySelector("article.frame.eula > div.console")
eulaContainer.innerHTML = ""
}
async function fetchEula() {
const eulaUrl = products[0].eula
const result = await system.call("network::fetch", {
url: eulaUrl,
method: "GET",
headers: {
"Accept": "application/json"
}
})
if (!result.success) {
return console.error("[N::F>(GE)] Error !")
}
return result
}
async function openEULA() {
await system.call("browser::open", { url: products[0].eula })
}
async function selectDirectory() {
const productName = getSelectedSoftware()
const result = await system.call("dialog::selectDirectory")
if (result.path != null) {
if (!result.path.endsWith(productName)) {
result.path = result.path + separator + productName
}
installationPath.setAttribute("value", result.path)
}
}
async function checkInstallationPath(path) {
const result = await system.call("installer::testInstallationPath", { path })
if (result.success) {
showFrameNavigationButtons()
}
}
onFrameShowed("onboarding", () => {
const languageDropdown = document.querySelector("details.language")
if (languageDropdown.getAttribute("value") == null) {
hideFrameNavigationButtons()
}
})
onFrameShowed("chooseProduct", () => {
const productsDropdown = document.querySelector("details.product")
const productIndex = productsDropdown.getAttribute("value")
if (productIndex != null) {
const product = productsDropdown.querySelector(`li:nth-of-type(${productIndex + 1})`).children[0]
choose("product", product)
} else {
hideFrameNavigationButtons()
showFrameExtraButtons()
}
})
onFrameShowed("requirements", async () => {
const productName = getSelectedSoftware()
clearLogIntoRequirementConsole()
hideFrameNavigationButtons()
hideFrameExtraButtons()
showLoadingBar()
filterProductArray(productName)
await system.call("check::requirements", { soft: productName })
})
onFrameShowed("eula", async () => {
clearEula()
const eula = await fetchEula()
writeEula(eula.data)
})
onFrameShowed("choosePath", async () => {
hideFrameNavigationButtons()
const result = await system.call("installer::defaultInstallPath", { soft: getSelectedSoftware() })
if (result.success) {
installationPath.setAttribute("value", result.path)
installationPath.onchange()
}
})
onFrameShowed("download", async () => {
hideFrameExtraButtons()
hideFrameNavigationButtons()
showLoadingBar()
const result = await system.call("installer::install", { soft: getSelectedSoftware(), path: installationPath.getAttribute("value") })
hideLoadingBar()
if (result.success) {
showFrame("end")
} else {
showFrameExtraButtons()
}
})
onSelected("language", () => {
showFrameNavigationButtons()
})
onSelected("product", () => {
hideFrameExtraButtons()
showFrameNavigationButtons()
})
system.result("requirement::log", (data) => {
putLogIntoRequirementConsole(data.message)
updatePageContent()
})
system.result("requirement::failed", () => {
hideLoadingBar()
showFrameExtraButtons()
})
system.result("requirement::success", () => {
hideLoadingBar()
showFrameNavigationButtons()
})
showFrame("onboarding")
fetchProducts()

View File

@ -1,14 +1,19 @@
const system = {
_pendingRequests: new Map(),
_listeners: new Map(),
init: function() {
window.external.receiveMessage(response => {
try {
const res = JSON.parse(response)
if (res.requestId && this._pendingRequests.has(res.requestId)) {
const { resolve } = this._pendingRequests.get(res.requestId)
this._pendingRequests.delete(res.requestId)
resolve(res.payload)
const { resolve } = this._pendingRequests.get(res.requestId);
this._pendingRequests.delete(res.requestId);
resolve(res.payload);
}
else if (res.requestId && this._listeners.has(res.requestId)) {
const callback = this._listeners.get(res.requestId);
callback(res.payload);
}
} catch (e) {
console.error("Erreur format message :", e)
@ -28,7 +33,35 @@ const system = {
payload: data
}))
})
},
result: function(eventName, callback) {
this._listeners.set(eventName, callback);
}
}
window.external.receiveMessage(message => {
try {
const data = JSON.parse(message)
switch (data.requestId) {
case "game::log":
const logMessage = data.payload.message
if (logMessage != "GAME_CLOSED") {
console.log("MC:", logMessage)
gamelog.put(logMessage)
logsContainer.scrollTop = logsContainer.scrollHeight
} else {
playButton.removeAttribute("disabled")
}
break;
default:
break;
}
} catch (e) {
console.error("Erreur réception message Photino:", e)
}
})
system.init()

View File

@ -0,0 +1,26 @@
const selectCallbacks = {}
function onSelected(dropdownSelector, cb) {
if (!selectCallbacks[dropdownSelector]) {
selectCallbacks[dropdownSelector] = []
}
selectCallbacks[dropdownSelector].push(cb)
}
async function choose(dropdownSelector, option) {
const select = document.querySelector(`details.${dropdownSelector}`)
const title = document.querySelector(`details.${dropdownSelector}>summary`)
const value = document.querySelector(`label[for=${option.id}]`)
title.innerHTML = value.innerHTML
if (select.getAttribute("value") != option.getAttribute("value")) {
await sleep(115)
}
select.setAttribute("value", option.getAttribute("value"))
select.removeAttribute("open")
if (Array.isArray(selectCallbacks[dropdownSelector])) {
for (const callback of selectCallbacks[dropdownSelector]) {
callback(option.getAttribute("value"))
}
}
}

View File

@ -0,0 +1,26 @@
{
"layout_direction": "rtl",
"description": "مرحباً بك في مساعد تثبيت Brik",
"next": "التالي",
"eula": "يرجى قراءة والموافقة على شروط الاستخدام",
"exit": "خروج / إنهاء",
"retry": "إعادة المحاولة",
"accept": "أوافق",
"browse": "تصفح",
"install": "تثبيت",
"previous": "السابق",
"downloading": "جاري تحميل ملفات البرنامج",
"choose_path": "اختر موقع التثبيت",
"create_shortcut": "إنشاء اختصار على سطح المكتب",
"launch_after_exit": "تشغيل البرنامج بعد إغلاق المثبت",
"installation_path": "اختر مجلد التثبيت",
"check_eula_online": "عرض الترخيص في المتصفح",
"select_soft_to_install": "اختر البرنامج الذي تود تثبيته",
"requirements": "التحقق من المكونات المطلوبة",
"log_checking_requirement": "جاري فحص ",
"log_checking_requirement_success": "✅ المكون موجود",
"log_checking_requirement_error": "❌ مكون ناقص ",
"log_checking_requirement_to_manual_install": "يرجى تثبيت المكون الناقص ",
"log_checking_requirement_to_manual_install_1": "ستفتح صفحة التحميل الآن",
"log_checking_requirement_attempt_to_auto_install": "محاولة التثبيت التلقائي: "
}

View File

@ -0,0 +1,26 @@
{
"layout_direction": "ltr",
"description": "Merhba bik fi moussa3id installation Brik",
"next": "Suivant",
"eula": "Y3aychek iqra w wafeq 3la chorout el-isti3mal",
"exit": "Khroj / Kmel",
"retry": "3awed jareb",
"accept": "Nwafeq",
"browse": "Parcourir",
"install": "Installi",
"previous": "Lback / Précédent",
"downloading": "Rani n-téléchargi fel-programme",
"choose_path": "Khayer win t-installi el-programme",
"create_shortcut": "Dir raccourci f-el-bureau",
"launch_after_exit": "7el el-programme ki t-oghlaq l-installateur",
"installation_path": "Khayer el-dossier ta3 l-installation",
"check_eula_online": "Chouf el-contrat f-el-navigateur",
"select_soft_to_install": "Khayer el-logiciel li rak hab t-installih",
"requirements": "N-verifyiw f-el-7ajet li lazem bech yekhdem",
"log_checking_requirement": "N-verifyiw fi ",
"log_checking_requirement_success": "✅ Kolchi mriguel",
"log_checking_requirement_error": "❌ Kayna 7aja naqsa ",
"log_checking_requirement_to_manual_install": "Y3aychek installi el-7aja li naqsa ",
"log_checking_requirement_to_manual_install_1": "Taw tet7al la page ta3 el-téléchargement",
"log_checking_requirement_attempt_to_auto_install": "N-sayiw n-installouha wa7edna: "
}

View File

@ -0,0 +1,26 @@
{
"layout_direction": "ltr",
"description": "Welcome to the Brik installation wizard",
"next": "Next",
"eula": "Please read and accept the terms of use.",
"exit": "Quit",
"retry": "Retry",
"accept": "I accept",
"browse": "Browse",
"install": "Install",
"previous": "Previous",
"downloading": "Program files download in progress",
"choose_path": "Select the installation location",
"create_shortcut": "Create a shortcut on the desktop",
"launch_after_exit": "Launch program after closing the installer",
"installation_path": "Select the installation location",
"check_eula_online": "View the licence in the browser",
"select_soft_to_install": "Select the software you wish to install?",
"requirements": "Verification of required components",
"log_checking_requirement": "Checking ",
"log_checking_requirement_success": "✅ Component present ",
"log_checking_requirement_error": "❌ Missing component ",
"log_checking_requirement_to_manual_install": "Please install the missing component ",
"log_checking_requirement_to_manual_install_1": "The download page will open",
"log_checking_requirement_attempt_to_auto_install": "Attempted automatic installation: "
}

View File

@ -0,0 +1,26 @@
{
"layout_direction": "ltr",
"description": "Bienvenido al asistente de instalación de Brik",
"next": "Siguiente",
"eula": "Por favor, lea y acepte las condiciones de uso.",
"exit": "Salir",
"retry": "Reintentar",
"accept": "Acepto",
"browse": "Examinar",
"install": "Instalar",
"previous": "Anterior",
"downloading": "Descarga de archivos del programa en curso",
"choose_path": "Seleccione la ubicación de instalación",
"create_shortcut": "Crear un acceso directo en el escritorio",
"launch_after_exit": "Ejecutar al finalizar",
"installation_path": "Seleccione la ruta de instalación",
"check_eula_online": "Ver la licencia en el navegador",
"select_soft_to_install": "¿Seleccione el software que desea instalar?",
"requirements": "Verificación de los componentes requeridos",
"log_checking_requirement": "Comprobando ",
"log_checking_requirement_success": "✅ Componente presente ",
"log_checking_requirement_error": "❌ Componente faltante ",
"log_checking_requirement_to_manual_install": "Por favor, instale el componente faltante ",
"log_checking_requirement_to_manual_install_1": "Se abrirá la página de descarga",
"log_checking_requirement_attempt_to_auto_install": "Intento de instalación automática: "
}

View File

@ -0,0 +1,26 @@
{
"layout_direction": "ltr",
"description": "ወደ Brik የመጫኛ ረዳት በደህና መጡ",
"next": "ቀጣይ",
"eula": "እባክዎ የአጠቃቀም ደንቦችን ያንብቡ እና ይስማሙ",
"exit": "ውጣ / ጨርስ",
"retry": "እንደገና ሞክር",
"accept": "እስማማለሁ",
"browse": "ፈልግ",
"install": "ጫን",
"previous": "ተመለስ",
"downloading": "የፕሮግራም ፋይሎች በመውረድ ላይ ናቸው",
"choose_path": "የመጫኛ ቦታውን ይምረጡ",
"create_shortcut": "በዴስክቶፕ ላይ አቋራጭ ፍጠር",
"launch_after_exit": "መጫኛው ሲዘጋ ፕሮግራሙን ክፈት",
"installation_path": "የመጫኛ ማህደሩን ይምረጡ",
"check_eula_online": "ፈቃዱን በአሳሽ ላይ ይመልከቱ",
"select_soft_to_install": "ሊጭኑት የሚፈልጉትን ሶፍትዌር ይምረጡ",
"requirements": "አስፈላጊ አካላትን በማረጋገጥ ላይ",
"log_checking_requirement": "በመፈተሽ ላይ: ",
"log_checking_requirement_success": "✅ አካሉ ተገኝቷል",
"log_checking_requirement_error": "❌ የጎደለ አካል አለ",
"log_checking_requirement_to_manual_install": "እባክዎ የጎደለውን አካል ይጫኑ: ",
"log_checking_requirement_to_manual_install_1": "የማውረጃው ገጽ አሁን ይከፈታል",
"log_checking_requirement_attempt_to_auto_install": "ራሱን በራሱ ለመጫን እየሞከረ ነው: "
}

View File

@ -0,0 +1,26 @@
{
"layout_direction": "ltr",
"description": "Bienvenue dans l'assistant d'installation Brik",
"next": "Suivant",
"eula": "Veuillez lire et accepter les conditions d'utilisation",
"exit": "Quitter / Terminer",
"retry": "Réessayer",
"accept": "J'accepte",
"install": "Installer",
"browse": "Parcourir",
"previous": "Précédent",
"downloading": "Téléchargement du programme en cours",
"choose_path": "Choisissez l'emplacement d'installation",
"create_shortcut": "Créer un raccourci sur le bureau",
"launch_after_exit": "Lancer le programme après la fermeture de l'installateur",
"installation_path": "Sélectionnez le dossier d'installation",
"check_eula_online": "Consulter la licence dans le navigateur",
"select_soft_to_install": "Choisissez le logiciel que vous souhaitez installer",
"requirements": "Vérification des composants requis",
"log_checking_requirement": "Vérification de ",
"log_checking_requirement_success": "✅ Composant présent",
"log_checking_requirement_error": "❌ Composant manquant ",
"log_checking_requirement_to_manual_install": "Veuillez installer le composant manquant ",
"log_checking_requirement_to_manual_install_1": "La page de téléchargement va s'ouvrir",
"log_checking_requirement_attempt_to_auto_install": "Tentative d'installation automatique : "
}

View File

@ -0,0 +1,26 @@
{
"layout_direction": "ltr",
"description": "Benvenuto nella procedura guidata di installazione di Brik",
"next": "Successivo",
"eula": "Leggi e accetta i termini di utilizzo.",
"exit": "Esci",
"retry": "Retry",
"accept": "Riprova",
"browse": "Sfoglia",
"install": "Installare",
"previous": "Precedente",
"downloading": "Download dei file di programma in corso",
"choose_path": "Selezionare la posizione di installazione",
"create_shortcut": "Creare un collegamento sul desktop",
"launch_after_exit": "Apri dopo l'installazione",
"installation_path": "Selezionare la posizione di installazione",
"check_eula_online": "Visualizza la licenza nel browser",
"select_soft_to_install": "Selezionare il software che si desidera installare?",
"requirements": "Verifica dei componenti richiesti",
"log_checking_requirement": "Controllo ",
"log_checking_requirement_success": "✅ Componente presente ",
"log_checking_requirement_error": "❌ Componente mancante ",
"log_checking_requirement_to_manual_install": "Si prega di installare il componente mancante ",
"log_checking_requirement_to_manual_install_1": "Si aprirà la pagina di download",
"log_checking_requirement_attempt_to_auto_install": "Tentativo di installazione automatica: "
}

View File

@ -0,0 +1,26 @@
{
"layout_direction": "ltr",
"description": "Brik インストールウィザードへようこそ",
"next": "次へ",
"eula": "利用規約を読み、同意してください。",
"exit": "終了",
"retry": "再試行",
"accept": "同意する",
"browse": "参照",
"install": "インストール",
"previous": "戻る",
"downloading": "プログラムファイルをダウンロード中...",
"choose_path": "インストール先を選択してください",
"create_shortcut": "デスクトップにショートカットを作成する",
"launch_after_exit": "終了後にプログラムを起動する",
"installation_path": "インストール先のフォルダを選択",
"check_eula_online": "ブラウザでライセンスを確認する",
"select_soft_to_install": "インストールするソフトウェアを選択してください",
"requirements": "必須コンポーネントの確認中",
"log_checking_requirement": "確認中: ",
"log_checking_requirement_success": "✅ コンポーネントが見つかりました",
"log_checking_requirement_error": "❌ コンポーネントが不足しています",
"log_checking_requirement_to_manual_install": "不足しているコンポーネントをインストールしてください: ",
"log_checking_requirement_to_manual_install_1": "ダウンロードページを開きます",
"log_checking_requirement_attempt_to_auto_install": "自動インストールを試行中: "
}

View File

@ -0,0 +1,26 @@
{
"layout_direction": "ltr",
"description": "Merhba bik f mosa3id tatbit Brik",
"next": "Tali",
"eula": "3afak iqra w wafeq 3la chourout l-istis3mal",
"exit": "Khrouj / Salina",
"retry": "3awed jereb",
"accept": "Kanwafeq",
"browse": "Parcourir",
"install": "Installi",
"previous": "L-pari li fat",
"downloading": "Jarri l-tahmil dyal l-programme",
"choose_path": "Khtar fin t-installi l-programme",
"create_shortcut": "Dir raccourci f l-bureau",
"launch_after_exit": "Khdem l-programme melli tsed l-installateur",
"installation_path": "Khtar l-dossier dyal l-installation",
"check_eula_online": "Chouf l-license f l-navigateur",
"select_soft_to_install": "Khtar l-logiciel li bghiti t-installi",
"requirements": "Kan-verifiw l-7ajat li daroria bach ykhdem",
"log_checking_requirement": "Verificassion dyal ",
"log_checking_requirement_success": "✅ Kolchi houwa hadak",
"log_checking_requirement_error": "❌ Kayna 7aja naqsa ",
"log_checking_requirement_to_manual_install": "3afak installi had l-7aja li naqsa ",
"log_checking_requirement_to_manual_install_1": "Ghat-t-fte7 l-pari dyal l-tahmil",
"log_checking_requirement_attempt_to_auto_install": "Ghan-jerrbo n-installiwha rousna: "
}

View File

@ -0,0 +1,26 @@
{
"layout_direction": "ltr",
"description": "Ẹ kú àbọ̀ sí abẹ́yọrí ìfilọ́lẹ̀ Brik",
"next": "Tẹ̀síwájú",
"eula": "Jọ̀wọ́ ka kí o sì gbà fún àwọn òfin ì lò.",
"exit": "Jáde / Parí",
"retry": "Ìdánwò lẹ́ẹ̀kan sí i",
"accept": "Mo gbà",
"browse": "Wá kiri",
"install": "Fi bẹ̀rẹ̀",
"previous": "Padà sẹ́yìn",
"downloading": "Ìgbàsílẹ̀ àwọn fáìlì ètò ń lọ lọ́wọ́...",
"choose_path": "Yan ibi tí o fẹ́ fi ètò sí",
"create_shortcut": "Ṣẹ̀dá ọ̀nà ìkékúrú sórí ojú iṣẹ́ (desktop)",
"launch_after_exit": "Ṣí ètò lẹ́yìn tí o bá ti ti olùfilọ́lẹ̀",
"installation_path": "Yan ibi tí ètò yóò wà",
"check_eula_online": "Wo ìwé àṣẹ nínú ẹ̀rọ ìṣíwò (browser)",
"select_soft_to_install": "Yan ètò (software) tí o fẹ́ fi bẹ̀rẹ̀",
"requirements": "Ìmúdájú àwọn nǹkan tí ó ṣe dandan",
"log_checking_requirement": "Ìyẹ̀wò lọ́wọ́: ",
"log_checking_requirement_success": "✅ Ohun èlò ti wà",
"log_checking_requirement_error": "❌ Ohun èlò kan dín bù ",
"log_checking_requirement_to_manual_install": "Jọ̀wọ́ fi ohun èlò tí ó dín bù bẹ̀rẹ̀ ",
"log_checking_requirement_to_manual_install_1": "Ojú-ewé ìgbàsílẹ̀ yóò ṣí nísìnyí",
"log_checking_requirement_attempt_to_auto_install": "Ìgbìnyànjú láti fi bẹ̀rẹ̀ fúnrawá: "
}

View File

@ -0,0 +1,26 @@
{
"layout_direction": "ltr",
"description": "Добро пожаловать в мастер установки Brik",
"next": "Далее",
"eula": "Пожалуйста, прочтите и примите условия использования",
"exit": "Выход",
"retry": "Повторить",
"accept": "Я принимаю",
"browse": "Обзор",
"install": "Установить",
"previous": "Назад",
"downloading": "Загрузка файлов программы...",
"choose_path": "Выберите путь установки",
"create_shortcut": "Создать ярлык на рабочем столе",
"launch_after_exit": "Запустить программу после завершения",
"installation_path": "Выберите папку для установки",
"check_eula_online": "Посмотреть лицензию в браузере",
"select_soft_to_install": "Выберите компоненты для установки",
"requirements": "Проверка системных требований",
"log_checking_requirement": "Проверка: ",
"log_checking_requirement_success": "✅ Компонент найден",
"log_checking_requirement_error": "❌ Компонент отсутствует",
"log_checking_requirement_to_manual_install": "Пожалуйста, установите недостающий компонент: ",
"log_checking_requirement_to_manual_install_1": "Сейчас откроется страница загрузки",
"log_checking_requirement_attempt_to_auto_install": "Попытка автоматической установки: "
}

View File

@ -0,0 +1,26 @@
{
"layout_direction": "ltr",
"description": "Dalal jàmm ci sarsaralub instalasiyoŋu Brik",
"next": "Kanami",
"eula": "Nanga gëstu te nangu sàrti jëfandiku gi",
"exit": "Génn / Pare",
"retry": "Baraat ko",
"accept": "Nangu naa",
"browse": "Seet",
"install": "Instale",
"previous": "Ginnaaw",
"downloading": "Yebum kàggi porogaraam bi mu ngi nekk ci yoon",
"choose_path": "Tànnal bërëb bi ñuy instale porogaraam bi",
"create_shortcut": "Sudar ab ràccu ci tablo bi",
"launch_after_exit": "Ubi porogaraam bi su instalatër bi tëjoo",
"installation_path": "Tànnal kàggub instalasiyoŋ bi",
"check_eula_online": "Gëstu sàrt yi ci internet",
"select_soft_to_install": "Tànnal porogaraam bi nga bëggë instale",
"requirements": "Xayma kàggi yi ñu laaj ngir mu liggéey",
"log_checking_requirement": "Xayma bu ",
"log_checking_requirement_success": "✅ Lépp mu ngi dox",
"log_checking_requirement_error": "❌ Am na kàggi lu manke ",
"log_checking_requirement_to_manual_install": "Nanga instale kàggi li manke ",
"log_checking_requirement_to_manual_install_1": "Xëtub yeb bi dina ubeeku léegi",
"log_checking_requirement_attempt_to_auto_install": "Njeemeb instalasiyoŋ bu boppam: "
}

View File

@ -0,0 +1,26 @@
{
"layout_direction": "ltr",
"description": "Ansuf yiswen ɣer imawan n ussebded n Brik",
"next": "Afus d-iteddun",
"eula": "Ɛafak ɣer ukan tqebleḍ timentilin n usemres",
"exit": "Ffeɣ / Kmel",
"retry": "Ɛawed jareb",
"accept": "Qebleɣ",
"browse": "Snubbjet",
"install": "Ssebded",
"previous": "Amezwaru",
"downloading": "Agmar n yifuyla n uselkim",
"choose_path": "Fren amkan n ussebded",
"create_shortcut": "Snerni rracoursi di ttabla",
"launch_after_exit": "Ssekker aselkim madi tmedleḍ amezen n ussebded",
"installation_path": "Fren akaram n ussebded",
"check_eula_online": "Ẓer tasiregt di ttanfult",
"select_soft_to_install": "Fren aselkim i tebɣid ad tsebdeded",
"requirements": "Aselken n yiferdisen i d-ittwasutren",
"log_checking_requirement": "Aselken n ",
"log_checking_requirement_success": "✅ Kulci yella",
"log_checking_requirement_error": "❌ Yella wayen i khussen ",
"log_checking_requirement_to_manual_install": "Ɛafak ssebded ayen i khussen ",
"log_checking_requirement_to_manual_install_1": "Tasaft n ugmar a d-teldi tura",
"log_checking_requirement_attempt_to_auto_install": "Armud n ussebded n yiman-is: "
}

View File

@ -0,0 +1,26 @@
{
"layout_direction": "ltr",
"description": "Marhba bik fi moussa3id installation Brik",
"next": "Tali",
"eula": "Y3aychek iqra w wafeq 3ala chourout el-istis3mal",
"exit": "Okhrjou / Kmal",
"retry": "3awed jareb",
"accept": "Nwafeq",
"browse": "Lawwej",
"install": "Installi",
"previous": "Litali",
"downloading": "Qa3ed ncharji fel-programme",
"choose_path": "Ekhtar win tseb el-programme",
"create_shortcut": "A3mel raccourci 3al-bureau",
"launch_after_exit": "7el el-programme ki tsekker l-installateur",
"installation_path": "Ekter el-dossier mta3 l-installation",
"check_eula_online": "Chouf el-license fel-navigateur",
"select_soft_to_install": "Ekhtar el-logiciel elli t7eb tsebbo",
"requirements": "Nthabtou fel-7ajet elli lezmin bech yekhdem",
"log_checking_requirement": "Nthabtou fi ",
"log_checking_requirement_success": "✅ Kol chay mriguel",
"log_checking_requirement_error": "❌ Famma 7aja naqsa ",
"log_checking_requirement_to_manual_install": "Y3aychek sab el-7aja elli naqsa ",
"log_checking_requirement_to_manual_install_1": "Taw tet7al saf7et el-telechargement",
"log_checking_requirement_attempt_to_auto_install": "Taw njarbou nsebboha wa7edna: "
}

View File

@ -0,0 +1,26 @@
{
"layout_direction": "ltr",
"description": "Karibu kwenye programu ya usakinishaji ya Brik",
"next": "Endelea",
"eula": "Tafadhali soma na ukubali masharti ya matumizi",
"exit": "Ondoka / Maliza",
"retry": "Jaribu tena",
"accept": "Ninakubali",
"browse": "Vinjari",
"install": "Sakinisha",
"previous": "Rudi nyuma",
"downloading": "Upakuaji wa faili za programu unaendelea",
"choose_path": "Chagua mahali pa kusakinisha",
"create_shortcut": "Weka njia ya mkato kwenye desktop",
"launch_after_exit": "Fungua programu baada ya kufunga kisakinishi",
"installation_path": "Chagua folda ya usakinishaji",
"check_eula_online": "Angalia leseni kwenye kivinjari",
"select_soft_to_install": "Chagua programu unayotaka kusakinisha",
"requirements": "Uhakiki wa vipengele vinavyohitajika",
"log_checking_requirement": "Inakagua ",
"log_checking_requirement_success": "✅ Kipengele kipo",
"log_checking_requirement_error": "❌ Kipengele kinakosekana ",
"log_checking_requirement_to_manual_install": "Tafadhali sakinisha kipengele kinachokosekana ",
"log_checking_requirement_to_manual_install_1": "Ukurasa wa upakuaji utafunguka",
"log_checking_requirement_attempt_to_auto_install": "Jaribio la usakinishaji wa kiotomatiki: "
}

View File

@ -0,0 +1,26 @@
{
"layout_direction": "ltr",
"description": "Siyakwamukela kusisizi sokufaka se-Brik",
"next": "Okulandelayo",
"eula": "Sicela ufunde futhi ulande imigomo yokusetshenziswa",
"exit": "Phuma / Qeda",
"retry": "Zama futhi",
"accept": "Ngiyavuma",
"browse": "Bhekisisa",
"install": "Faka",
"previous": "Okwedlule",
"downloading": "Kulandwa amafayela ohlelo...",
"choose_path": "Khetha lapho uzofaka khona uhlelo",
"create_shortcut": "Yenza isinqamuleli kudeskithophu",
"launch_after_exit": "Vula uhlelo ngemuva kokuvala isifaki",
"installation_path": "Khetha ifolda yokufaka",
"check_eula_online": "Buka ilayisensi kusiphequluli",
"select_soft_to_install": "Khetha isofthiwe ofuna ukuyifaka",
"requirements": "Ukuhlola izinto ezidingekayo",
"log_checking_requirement": "Kuhlolwa ",
"log_checking_requirement_success": "✅ Konke kulungile",
"log_checking_requirement_error": "❌ Kukhona okushortayo ",
"log_checking_requirement_to_manual_install": "Sicela ufake lokhu okushortayo ",
"log_checking_requirement_to_manual_install_1": "Ikhasi lokulanda lizovuleka manje",
"log_checking_requirement_attempt_to_auto_install": "Kuzanywa ukufaka ngokuzenzakalelayo: "
}

View File

@ -3,21 +3,292 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="./assets/css/common.css">
<link rel="stylesheet" href="./assets/css/select.css">
<link rel="stylesheet" href="./assets/css/index.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/lipis/flag-icons@7.3.2/css/flag-icons.min.css" crossorigin="anonymous"/>
<title></title>
</head>
<body>
<main>
<h2>
Bienvenue dans Photino
</h2>
<button onclick="callCSharp()">
Appellez une fonction C#
</button>
</main>
<aside>
<article class="frame onboarding">
<div class="title">
<img src="./assets/img/logo.png" alt="">
<h1>
Brik Installer
</h1>
</div>
<p data-i18n="description">
</p>
<details class="language">
<summary>
Sélectionner une langue
</summary>
<section class="options">
<ul>
<li>
<input type="radio" value="fr" name="radio" id="box_lang_fr" onclick="chooseLanguage('language', this)"/>
<label for="box_lang_fr">
<span class="fi fi-fr"></span>
Français
</label>
</li>
<li>
<input type="radio" value="tn" name="radio" id="box_lang_tn" onclick="chooseLanguage('language', this)"/>
<label for="box_lang_tn">
<span class="fi fi-tn"></span>
Darja Tounsi
</label>
</li>
<li>
<input type="radio" value="dz" name="radio" id="box_lang_dz" onclick="chooseLanguage('language', this)"/>
<label for="box_lang_dz">
<span class="fi fi-dz"></span>
Dziria
</label>
</li>
<li>
<input type="radio" value="ma" name="radio" id="box_lang_ma" onclick="chooseLanguage('language', this)"/>
<label for="box_lang_ma">
<span class="fi fi-ma"></span>
Darija Maghribia
</label>
</li>
<li>
<input type="radio" value="tamazight" name="radio" id="box_lang_tamazight" onclick="chooseLanguage('language', this)"/>
<label for="box_lang_tamazight">
ⵣ Tamazight (Tifinagh)
</label>
</li>
<li>
<input type="radio" value="it" name="radio" id="box_lang_it" onclick="chooseLanguage('language', this)"/>
<label for="box_lang_it">
<span class="fi fi-it"></span>
Italiano
</label>
</li>
<li>
<input type="radio" value="en" name="radio" id="box_lang_en" onclick="chooseLanguage('language', this)"/>
<label for="box_lang_en">
<span class="fi fi-gb"></span>
English
</label>
</li>
<li>
<input type="radio" value="es" name="radio" id="box_lang_es" onclick="chooseLanguage('language', this)"/>
<label for="box_lang_es">
<span class="fi fi-es"></span>
Espanol
</label>
</li>
<li>
<input type="radio" value="ru" name="radio" id="box_lang_ru" onclick="chooseLanguage('language', this)"/>
<label for="box_lang_ru">
<span class="fi fi-ru"></span>
Русский
</label>
</li>
<li>
<input type="radio" value="jp" name="radio" id="box_lang_jp" onclick="chooseLanguage('language', this)"/>
<label for="box_lang_jp">
<span class="fi fi-jp"></span>
日本語
</label>
</li>
<li>
<input type="radio" value="arab" name="radio" id="box_lang_arab" onclick="chooseLanguage('language', this)"/>
<label for="box_lang_arab">
<span class="fi fi-arab"></span>
ع
</label>
</li>
<li>
<input type="radio" value="tz" name="radio" id="box_lang_tz" onclick="chooseLanguage('language', this)"/>
<label for="box_lang_tz">
<span class="fi fi-tz"></span>
Swahili
</label>
</li>
<li>
<input type="radio" value="sn" name="radio" id="box_lang_sn" onclick="chooseLanguage('language', this)"/>
<label for="box_lang_sn">
<span class="fi fi-sn"></span>
Wolof
</label>
</li>
<li>
<input type="radio" value="ng" name="radio" id="box_lang_ng" onclick="chooseLanguage('language', this)"/>
<label for="box_lang_ng">
<span class="fi fi-ng"></span>
Yoruba
</label>
</li>
<li>
<input type="radio" value="et" name="radio" id="box_lang_et" onclick="chooseLanguage('language', this)"/>
<label for="box_lang_et">
<span class="fi fi-et"></span>
Amharique
</label>
</li>
<li>
<input type="radio" value="za" name="radio" id="box_lang_za" onclick="chooseLanguage('language', this)"/>
<label for="box_lang_za">
<span class="fi fi-za"></span>
Zulu
</label>
</li>
</ul>
</section>
</details>
<section class="author">
<img src="https://github.com/azures04.png?size=1024" alt="">
<p>
azures04
</p>
</section>
<section class="buttons">
<button class="primary" data-i18n="next" onclick="showFrame('chooseProduct')">
</button>
</section>
</article>
<article class="frame chooseProduct">
<div class="title">
<img src="./assets/img/logo.png" alt="">
<h1>
Brik Installer
</h1>
</div>
<details class="product">
<summary data-i18n="select_soft_to_install">
</summary>
<section class="options">
<ul>
</ul>
</section>
</details>
<section class="extra_buttons" hidden>
<button data-i18n="previous" onclick="showFrame('onboarding')"></button>
</section>
<section class="buttons">
<button class="primary" data-i18n="next" onclick="showFrame('requirements')"></button>
<button data-i18n="previous" onclick="showFrame('onboarding')"></button>
</section>
</article>
<article class="frame requirements">
<div class="title">
<img src="./assets/img/logo.png" alt="">
<h1>
Brik Installer
</h1>
</div>
<p data-i18n="requirements"></p>
<div class="console">
</div>
<section class="buttons">
<button class="primary" data-i18n="next" onclick="showFrame('eula')"></button>
<button data-i18n="previous" onclick="showFrame('chooseProduct')"></button>
</section>
<section class="extra_buttons" hidden>
<button class="primary" data-i18n="retry" onclick="showFrame('requirements')"></button>
</section>
<div class="loader" hidden>
<div class="full">
<div class="loading">
</div>
</div>
</div>
</article>
<article class="frame eula">
<div class="title">
<img src="./assets/img/logo.png" alt="">
<h1>
Brik Installer
</h1>
</div>
<p data-i18n="eula"></p>
<div class="console">
</div>
<a onclick="openEULA()" data-i18n="check_eula_online"></a>
<section class="buttons">
<button class="primary" data-i18n="accept" onclick="showFrame('choosePath')"></button>
<button data-i18n="previous" onclick="showFrame('requirements')"></button>
</section>
</article>
<article class="frame choosePath">
<div class="title">
<img src="./assets/img/logo.png" alt="">
<h1>
Brik Installer
</h1>
</div>
<p data-i18n="choose_path"></p>
<div class="installation_path">
<input type="text" id="installationPath" onchange="checkInstallationPath(this.value)" data-i18n="[placeholder]installation_path">
<button onclick="selectDirectory()" data-i18n="browse"></button>
</div>
<section class="buttons">
<button class="primary" data-i18n="install" onclick="showFrame('download')"></button>
<button data-i18n="previous" onclick="showFrame('eula')"></button>
</section>
</article>
<article class="frame download">
<div class="title">
<img src="./assets/img/logo.png" alt="">
<h1>
Brik Installer
</h1>
</div>
<p data-i18n="downloading"></p>
<div class="loader" hidden>
<div class="full">
<div class="loading">
</div>
</div>
</div>
<section class="extra_buttons">
<button class="primary" data-i18n="retry" onclick="showFrame('download')"></button>
<button data-i18n="previous" onclick="showFrame('choosePath')"></button>
</section>
<section class="buttons">
<button class="primary" data-i18n="exit" onclick="window.close()"></button>
</section>
</article>
<article class="frame end">
<div class="title">
<img src="./assets/img/logo.png" alt="">
<h1>
Brik Installer
</h1>
</div>
<form>
<div>
<input type="checkbox" name="createShortcut" id="createShortcut" value="createShortcut" checked>
<label for="createShortcut" data-i18n="create_shortcut">createShortcut</label>
</div>
<div>
<input type="checkbox" name="launchAfterExit" id="launchAfterExit" value="launchAfterExit">
<label for="launchAfterExit" data-i18n="launch_after_exit"></label>
</div>
</form>
<section class="buttons">
<button class="primary" data-i18n="exit" onclick="window.close()"></button>
</section>
</article>
</aside>
</main>
<script src="https://unpkg.com/i18next@25.8.4/dist/umd/i18next.min.js" crossorigin="anonymous"></script>
<script src="https://unpkg.com/i18next-http-backend@2.5.2/i18nextHttpBackend.min.js" crossorigin="anonymous"></script>
<script src="./assets/js/ipc.js"></script>
<script src="./assets/js/common.js"></script>
<script src="./assets/js/select.js"></script>
<script src="./assets/js/frames.js"></script>
<script src="./assets/js/index.js"></script>
</body>