diff --git a/Photino-Boilerplate.csproj b/BrikInstaller.csproj similarity index 85% rename from Photino-Boilerplate.csproj rename to BrikInstaller.csproj index 2b7ed89..f38a49c 100644 --- a/Photino-Boilerplate.csproj +++ b/BrikInstaller.csproj @@ -7,6 +7,7 @@ enable enable false + src/resources/build/icon.ico @@ -18,6 +19,11 @@ + + + PreserveNewest + + diff --git a/Photino-Boilerplate.sln b/BrikInstaller.sln similarity index 86% rename from Photino-Boilerplate.sln rename to BrikInstaller.sln index b092ffb..b67ed25 100644 --- a/Photino-Boilerplate.sln +++ b/BrikInstaller.sln @@ -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 diff --git a/src/main/Constants.cs b/src/main/Constants.cs new file mode 100644 index 0000000..6f490d6 --- /dev/null +++ b/src/main/Constants.cs @@ -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"; + } +} \ No newline at end of file diff --git a/src/main/Program.cs b/src/main/Program.cs index 2f6371f..8a27a43 100644 --- a/src/main/Program.cs +++ b/src/main/Program.cs @@ -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 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}"); + } + } } \ No newline at end of file diff --git a/src/main/helpers/Bash.cs b/src/main/helpers/Bash.cs new file mode 100644 index 0000000..b81bd7d --- /dev/null +++ b/src/main/helpers/Bash.cs @@ -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; + } + } + } +} \ No newline at end of file diff --git a/src/main/helpers/Http.cs b/src/main/helpers/Http.cs new file mode 100644 index 0000000..d783549 --- /dev/null +++ b/src/main/helpers/Http.cs @@ -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 FetchAsync(string url, HttpMethod? method = null, object? body = null, Dictionary? 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); + } +} \ No newline at end of file diff --git a/src/main/helpers/Os.cs b/src/main/helpers/Os.cs new file mode 100644 index 0000000..fa38fbd --- /dev/null +++ b/src/main/helpers/Os.cs @@ -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; + } +} \ No newline at end of file diff --git a/src/main/helpers/Window.cs b/src/main/helpers/Window.cs index 91ed8cb..9843b05 100644 --- a/src/main/helpers/Window.cs +++ b/src/main/helpers/Window.cs @@ -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); + } + } } \ No newline at end of file diff --git a/src/main/schemas/GenericBrikFiles.cs b/src/main/schemas/GenericBrikFiles.cs new file mode 100644 index 0000000..4a00f7f --- /dev/null +++ b/src/main/schemas/GenericBrikFiles.cs @@ -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 Root { get; set; } = new(); +} + +public class ArtifactEntry { + public ArtifactContainer Downloads { get; set; } = null!; +} + +public class ArtifactContainer { + public GenericArtifact Artifact { get; set; } = null!; +} \ No newline at end of file diff --git a/src/main/schemas/Product.cs b/src/main/schemas/Product.cs new file mode 100644 index 0000000..35262b4 --- /dev/null +++ b/src/main/schemas/Product.cs @@ -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; } +} \ No newline at end of file diff --git a/src/main/schemas/Requirement.cs b/src/main/schemas/Requirement.cs new file mode 100644 index 0000000..5f71628 --- /dev/null +++ b/src/main/schemas/Requirement.cs @@ -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; } +} \ No newline at end of file diff --git a/src/main/services/Files.cs b/src/main/services/Files.cs new file mode 100644 index 0000000..b7341d8 --- /dev/null +++ b/src/main/services/Files.cs @@ -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(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 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(); + } +} \ No newline at end of file diff --git a/src/main/services/IPC.cs b/src/main/services/IPC.cs new file mode 100644 index 0000000..a5ae4fb --- /dev/null +++ b/src/main/services/IPC.cs @@ -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)); + } +} \ No newline at end of file diff --git a/src/main/services/Installation.cs b/src/main/services/Installation.cs new file mode 100644 index 0000000..92c3065 --- /dev/null +++ b/src/main/services/Installation.cs @@ -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; + } +} \ No newline at end of file diff --git a/src/main/services/Network.cs b/src/main/services/Network.cs new file mode 100644 index 0000000..1b93996 --- /dev/null +++ b/src/main/services/Network.cs @@ -0,0 +1,30 @@ +using System.Text.Json; +using BrikInstaller; + +public static class NetworkService { + public static async Task 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? headers = null; + if (jsonPayload.TryGetProperty("headers", out var h)) { + headers = JsonSerializer.Deserialize>(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 }; + } + } +} \ No newline at end of file diff --git a/src/main/services/Requirements.cs b/src/main/services/Requirements.cs new file mode 100644 index 0000000..62642e5 --- /dev/null +++ b/src/main/services/Requirements.cs @@ -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 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>(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, $"{element.Name}"); + int state = await ProcessRequirement(element); + switch (state) { + case 0: + IPC.sendRequirementCheckConsoleLog(window, $" {element.Name}"); + IPC.sendRequirementCheckConsoleLog(window, $"{element.Name}"); + IPC.sendRequirementCheckConsoleLog(window, $""); + return new RequirementCheckingResponse{ Success = false }; + case 1: + IPC.sendRequirementCheckConsoleLog(window, $" {element.Name}"); + IPC.sendRequirementCheckConsoleLog(window, $"{element.Name}"); + return new RequirementCheckingResponse{ Success = false }; + case 2: + IPC.sendRequirementCheckConsoleLog(window, $""); + 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 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 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; + } +} \ No newline at end of file diff --git a/src/resources/build/icon.ico b/src/resources/build/icon.ico new file mode 100644 index 0000000..c09b530 Binary files /dev/null and b/src/resources/build/icon.ico differ diff --git a/src/resources/build/logo.png b/src/resources/build/logo.png new file mode 100644 index 0000000..71f71b7 Binary files /dev/null and b/src/resources/build/logo.png differ diff --git a/src/resources/renderer/assets/css/common.css b/src/resources/renderer/assets/css/common.css new file mode 100644 index 0000000..ceaf9ef --- /dev/null +++ b/src/resources/renderer/assets/css/common.css @@ -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; +} diff --git a/src/resources/renderer/assets/css/index.css b/src/resources/renderer/assets/css/index.css index 055c3c5..8b01fe0 100644 --- a/src/resources/renderer/assets/css/index.css +++ b/src/resources/renderer/assets/css/index.css @@ -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; +} \ No newline at end of file diff --git a/src/resources/renderer/assets/css/select.css b/src/resources/renderer/assets/css/select.css new file mode 100644 index 0000000..6175c4e --- /dev/null +++ b/src/resources/renderer/assets/css/select.css @@ -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; +} \ No newline at end of file diff --git a/src/resources/renderer/assets/img/logo.png b/src/resources/renderer/assets/img/logo.png new file mode 100644 index 0000000..71f71b7 Binary files /dev/null and b/src/resources/renderer/assets/img/logo.png differ diff --git a/src/resources/renderer/assets/js/common.js b/src/resources/renderer/assets/js/common.js new file mode 100644 index 0000000..65beb38 --- /dev/null +++ b/src/resources/renderer/assets/js/common.js @@ -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)); +} \ No newline at end of file diff --git a/src/resources/renderer/assets/js/frames.js b/src/resources/renderer/assets/js/frames.js new file mode 100644 index 0000000..ab228c9 --- /dev/null +++ b/src/resources/renderer/assets/js/frames.js @@ -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) +} \ No newline at end of file diff --git a/src/resources/renderer/assets/js/index.js b/src/resources/renderer/assets/js/index.js index b6e9954..22aecb8 100644 --- a/src/resources/renderer/assets/js/index.js +++ b/src/resources/renderer/assets/js/index.js @@ -1,4 +1,274 @@ -async function callCSharp() { - const result = await system.call("hello::world") - alert(result) -} \ No newline at end of file +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 += "
" +} + +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() \ No newline at end of file diff --git a/src/resources/renderer/assets/js/ipc.js b/src/resources/renderer/assets/js/ipc.js index 6f54d74..01305d0 100644 --- a/src/resources/renderer/assets/js/ipc.js +++ b/src/resources/renderer/assets/js/ipc.js @@ -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() \ No newline at end of file diff --git a/src/resources/renderer/assets/js/select.js b/src/resources/renderer/assets/js/select.js new file mode 100644 index 0000000..2890703 --- /dev/null +++ b/src/resources/renderer/assets/js/select.js @@ -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")) + } + } +} \ No newline at end of file diff --git a/src/resources/renderer/assets/locales/arab.json b/src/resources/renderer/assets/locales/arab.json new file mode 100644 index 0000000..ea4bc5f --- /dev/null +++ b/src/resources/renderer/assets/locales/arab.json @@ -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": "محاولة التثبيت التلقائي: " +} \ No newline at end of file diff --git a/src/resources/renderer/assets/locales/dz.json b/src/resources/renderer/assets/locales/dz.json new file mode 100644 index 0000000..a436567 --- /dev/null +++ b/src/resources/renderer/assets/locales/dz.json @@ -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: " +} \ No newline at end of file diff --git a/src/resources/renderer/assets/locales/en.json b/src/resources/renderer/assets/locales/en.json new file mode 100644 index 0000000..b6da80b --- /dev/null +++ b/src/resources/renderer/assets/locales/en.json @@ -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: " +} \ No newline at end of file diff --git a/src/resources/renderer/assets/locales/es.json b/src/resources/renderer/assets/locales/es.json new file mode 100644 index 0000000..56b55af --- /dev/null +++ b/src/resources/renderer/assets/locales/es.json @@ -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: " +} \ No newline at end of file diff --git a/src/resources/renderer/assets/locales/et.json b/src/resources/renderer/assets/locales/et.json new file mode 100644 index 0000000..ce115d1 --- /dev/null +++ b/src/resources/renderer/assets/locales/et.json @@ -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": "ራሱን በራሱ ለመጫን እየሞከረ ነው: " +} \ No newline at end of file diff --git a/src/resources/renderer/assets/locales/fr.json b/src/resources/renderer/assets/locales/fr.json new file mode 100644 index 0000000..f266b82 --- /dev/null +++ b/src/resources/renderer/assets/locales/fr.json @@ -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 : " +} \ No newline at end of file diff --git a/src/resources/renderer/assets/locales/it.json b/src/resources/renderer/assets/locales/it.json new file mode 100644 index 0000000..6a90b25 --- /dev/null +++ b/src/resources/renderer/assets/locales/it.json @@ -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: " +} \ No newline at end of file diff --git a/src/resources/renderer/assets/locales/jp.json b/src/resources/renderer/assets/locales/jp.json new file mode 100644 index 0000000..3144c63 --- /dev/null +++ b/src/resources/renderer/assets/locales/jp.json @@ -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": "自動インストールを試行中: " +} \ No newline at end of file diff --git a/src/resources/renderer/assets/locales/ma.json b/src/resources/renderer/assets/locales/ma.json new file mode 100644 index 0000000..6d86a1b --- /dev/null +++ b/src/resources/renderer/assets/locales/ma.json @@ -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: " +} \ No newline at end of file diff --git a/src/resources/renderer/assets/locales/ng.json b/src/resources/renderer/assets/locales/ng.json new file mode 100644 index 0000000..7968e18 --- /dev/null +++ b/src/resources/renderer/assets/locales/ng.json @@ -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á: " +} \ No newline at end of file diff --git a/src/resources/renderer/assets/locales/ru.json b/src/resources/renderer/assets/locales/ru.json new file mode 100644 index 0000000..476ae70 --- /dev/null +++ b/src/resources/renderer/assets/locales/ru.json @@ -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": "Попытка автоматической установки: " +} \ No newline at end of file diff --git a/src/resources/renderer/assets/locales/sn.json b/src/resources/renderer/assets/locales/sn.json new file mode 100644 index 0000000..e983c1d --- /dev/null +++ b/src/resources/renderer/assets/locales/sn.json @@ -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: " +} \ No newline at end of file diff --git a/src/resources/renderer/assets/locales/tamazight.json b/src/resources/renderer/assets/locales/tamazight.json new file mode 100644 index 0000000..e38a2a3 --- /dev/null +++ b/src/resources/renderer/assets/locales/tamazight.json @@ -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: " +} \ No newline at end of file diff --git a/src/resources/renderer/assets/locales/tn.json b/src/resources/renderer/assets/locales/tn.json new file mode 100644 index 0000000..ce688da --- /dev/null +++ b/src/resources/renderer/assets/locales/tn.json @@ -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: " +} \ No newline at end of file diff --git a/src/resources/renderer/assets/locales/tz.json b/src/resources/renderer/assets/locales/tz.json new file mode 100644 index 0000000..c8d8d62 --- /dev/null +++ b/src/resources/renderer/assets/locales/tz.json @@ -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: " +} \ No newline at end of file diff --git a/src/resources/renderer/assets/locales/za.json b/src/resources/renderer/assets/locales/za.json new file mode 100644 index 0000000..6fa3a2f --- /dev/null +++ b/src/resources/renderer/assets/locales/za.json @@ -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: " +} \ No newline at end of file diff --git a/src/resources/renderer/index.html b/src/resources/renderer/index.html index 0306745..2b4e3cc 100644 --- a/src/resources/renderer/index.html +++ b/src/resources/renderer/index.html @@ -3,21 +3,292 @@ + + +
-

- Bienvenue dans Photino -

- -
+ + + + + + + +