diff --git a/I18N Commander/DataModel/DataModel.csproj b/I18N Commander/DataModel/DataModel.csproj index 2b1e1d6..4210f08 100644 --- a/I18N Commander/DataModel/DataModel.csproj +++ b/I18N Commander/DataModel/DataModel.csproj @@ -1,7 +1,7 @@ - Exe + Library net6.0 enable enable diff --git a/I18N Commander/DataModel/Database/Common/DataContext.cs b/I18N Commander/DataModel/Database/Common/DataContext.cs index e63c80d..58872c3 100644 --- a/I18N Commander/DataModel/Database/Common/DataContext.cs +++ b/I18N Commander/DataModel/Database/Common/DataContext.cs @@ -167,11 +167,145 @@ public sealed class DataContext : DbContext, IDataContext }, jsonSettings); } - public static async Task ImportAndLoadAsync(string path) + /// + /// Stores data needed to resolve a parent-child relationship. + /// + /// The parent id we want to resolve. + /// The entity for which we want to resolve the parent. + /// The type of the entity. + private readonly record struct TreeResolver(Guid ParentId, T Entity); + + /// + /// Imports data from a JSON file into an empty database. + /// + /// The path to the JSON export. + /// When the database is not empty. + public async Task ImportAsync(string path) { - // We import that JSON data file into an new, empty database file - // at a temporary location. Next, we enable the auto export feature - // to keep the source file up-to-date. + if(await this.Settings.AnyAsync() || + await this.Sections.AnyAsync() || + await this.TextElements.AnyAsync() || + await this.Translations.AnyAsync()) + throw new InvalidOperationException("The database is not empty. In order to import data, the database must be empty."); + + // Start a transaction: + await using var transaction = await this.Database.BeginTransactionAsync(); + + // Configure the JSON serializer: + var jsonSettings = new JsonSerializerOptions + { + Converters = { new JsonUniqueIdConverter() }, + }; + + await using var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); + var jsonData = await JsonSerializer.DeserializeAsync(fileStream, jsonSettings); + + // -------------------- + // Import the settings: + // -------------------- + foreach (var setting in jsonData.Settings) + this.Settings.Add(Setting.FromJsonSetting(setting)); + + // -------------------- + // Import the sections: + // -------------------- + + // We must store the intermediate data in a list, because we need to resolve + // the parent-child relationships in a second step. + var allSections = new Dictionary>(); + var sectionToTextElements = new Dictionary>(); + + // Read the data from the JSON file: + foreach (var section in jsonData.Sections) + { + // Convert the next element: + var nextSection = Section.FromJsonSection(section); + + // Store the element: + allSections.Add(nextSection.UniqueId, new (section.ParentUniqueId.UniqueId, nextSection)); + sectionToTextElements.Add(nextSection.UniqueId, section.TextElements.Select(n => n.UniqueId).ToList()); + } + + // Now, resolve the parent-child relationships for the sections: + foreach (var (uniqueId, (parentId, section)) in allSections) + section.Parent = parentId == Guid.Empty ? null : allSections[parentId].Entity; + + // ------------------------- + // Import the text elements: + // ------------------------- + + // We must store the intermediate data in a list, because we need to resolve + // the parent-child relationships in a second step. + var allTextElements = new Dictionary(); + var textElementToTranslations = new Dictionary>(); + + // Read the data from the JSON file: + foreach (var textElement in jsonData.TextElements) + { + // Convert the next element: + var nextTextElement = TextElement.FromJsonTextElement(textElement); + + // We know that the section is already imported, because we imported the sections first: + nextTextElement.Section = allSections[textElement.SectionUniqueId.UniqueId].Entity; + + // Store the element in the list: + allTextElements.Add(nextTextElement.UniqueId, nextTextElement); + textElementToTranslations.Add(nextTextElement.UniqueId, textElement.Translations.Select(n => n.UniqueId).ToList()); + } + + // Now, resolve the parent-child relationships for the text elements to the sections: + foreach (var (sectionUniqueId, textElementsIds) in sectionToTextElements) + { + var section = allSections[sectionUniqueId].Entity; + section.TextElements.AddRange(textElementsIds.Select(n => allTextElements[n])); + } + + // Free the memory: + sectionToTextElements.Clear(); + + // ------------------------ + // Import the translations: + // ------------------------ + + // We must store the intermediate data in a list, because we need to resolve + // the parent-child relationships in a second step. + var allTranslations = new Dictionary(); + + // Read the data from the JSON file: + foreach (var translation in jsonData.Translations) + { + // Convert the next element: + var nextTranslation = Translation.FromJsonTranslation(translation); + + // We know that the text element is already imported, because we imported the text elements first: + nextTranslation.TextElement = allTextElements[translation.TextElementUniqueId.UniqueId]; + + // Store the element in the list: + allTranslations.Add(nextTranslation.UniqueId, nextTranslation); + } + + // Now, resolve the parent-child relationships for the translations to the text elements: + foreach (var (textElementUniqueId, translationsIds) in textElementToTranslations) + { + var textElement = allTextElements[textElementUniqueId]; + textElement.Translations.AddRange(translationsIds.Select(n => allTranslations[n])); + } + + // Free the memory: + textElementToTranslations.Clear(); + + // --------------------------------- + // Add all the data to the database: + // --------------------------------- + this.Sections.AddRange(allSections.Values.Select(n => n.Entity)); + this.TextElements.AddRange(allTextElements.Values); + this.Translations.AddRange(allTranslations.Values); + + // Save the changes: + await this.SaveChangesAsync(); + + // Commit the transaction: + await transaction.CommitAsync(); } #endregion diff --git a/I18N Commander/DataModel/Database/Section.cs b/I18N Commander/DataModel/Database/Section.cs index 23cbf8f..c963a40 100644 --- a/I18N Commander/DataModel/Database/Section.cs +++ b/I18N Commander/DataModel/Database/Section.cs @@ -31,4 +31,14 @@ public sealed class Section ParentUniqueId = this.Parent?.JsonUniqueId ?? new DataContext.JsonUniqueId("null", Guid.Empty, "Sec"), TextElements = this.TextElements.Select(n => n.JsonUniqueId).ToList() }; + + internal static Section FromJsonSection(DataContext.JsonSection jsonSection) => new() + { + UniqueId = jsonSection.UniqueId.UniqueId, + Name = jsonSection.Name, + DataKey = jsonSection.DataKey, + Depth = jsonSection.Depth, + Parent = null, + TextElements = new(), + }; } \ No newline at end of file diff --git a/I18N Commander/DataModel/Database/Setting.cs b/I18N Commander/DataModel/Database/Setting.cs index 4c5f1fc..5c5977b 100644 --- a/I18N Commander/DataModel/Database/Setting.cs +++ b/I18N Commander/DataModel/Database/Setting.cs @@ -31,4 +31,14 @@ public sealed class Setting IntegerValue = this.IntegerValue, TextValue = this.TextValue, }; + + internal static Setting FromJsonSetting(DataContext.JsonSetting jsonSetting) => new() + { + UniqueId = jsonSetting.UniqueId.UniqueId, + Code = jsonSetting.Code, + BoolValue = jsonSetting.BoolValue, + GuidValue = jsonSetting.GuidValue, + IntegerValue = jsonSetting.IntegerValue, + TextValue = jsonSetting.TextValue, + }; } \ No newline at end of file diff --git a/I18N Commander/DataModel/Database/SettingNames.cs b/I18N Commander/DataModel/Database/SettingNames.cs index 7d419cf..040b428 100644 --- a/I18N Commander/DataModel/Database/SettingNames.cs +++ b/I18N Commander/DataModel/Database/SettingNames.cs @@ -14,4 +14,8 @@ public static class SettingNames public static readonly string GENERATOR_DOTNET_DEFAULT_CULTURE = "Generator .NET Default Culture"; public static readonly string GENERATOR_GODOT_ENABLED = "Generator Godot Enabled"; public static readonly string GENERATOR_GODOT_DESTINATION_PATH = "Generator Godot Destination Path"; + public static readonly string AUTO_EXPORT_ENABLED = "Auto-Export Enabled"; + public static readonly string AUTO_EXPORT_DESTINATION_PATH = "Auto-Export Destination Path"; + public static readonly string AUTO_EXPORT_FILENAME = "Auto-Export Filename"; + public static readonly string AUTO_EXPORT_SENSITIVE_DATA = "Auto-Export Sensitive Data"; } \ No newline at end of file diff --git a/I18N Commander/DataModel/Database/TextElement.cs b/I18N Commander/DataModel/Database/TextElement.cs index adf3f86..7870238 100644 --- a/I18N Commander/DataModel/Database/TextElement.cs +++ b/I18N Commander/DataModel/Database/TextElement.cs @@ -31,4 +31,14 @@ public sealed class TextElement SectionUniqueId = this.Section.JsonUniqueId, Translations = this.Translations.Select(n => n.JsonUniqueId).ToList(), }; + + internal static TextElement FromJsonTextElement(DataContext.JsonTextElement jsonTextElement) => new() + { + UniqueId = jsonTextElement.UniqueId.UniqueId, + Code = jsonTextElement.Code, + Name = jsonTextElement.Name, + IsMultiLine = jsonTextElement.IsMultiLine, + Section = new(), + Translations = new(), + }; } \ No newline at end of file diff --git a/I18N Commander/DataModel/Database/Translation.cs b/I18N Commander/DataModel/Database/Translation.cs index 167d671..d9f428c 100644 --- a/I18N Commander/DataModel/Database/Translation.cs +++ b/I18N Commander/DataModel/Database/Translation.cs @@ -8,7 +8,7 @@ public sealed class Translation public Guid UniqueId { get; set; } - public TextElement TextElement { get; set; } + public TextElement TextElement { get; set; } = new(); public string Culture { get; set; } = "en-US"; @@ -26,4 +26,13 @@ public sealed class Translation TextElementUniqueId = this.TextElement.JsonUniqueId, Text = this.Text, }; + + internal static Translation FromJsonTranslation(DataContext.JsonTranslation jsonTranslation) => new() + { + UniqueId = jsonTranslation.UniqueId.UniqueId, + Culture = jsonTranslation.Culture, + TranslateManual = jsonTranslation.TranslateManual, + Text = jsonTranslation.Text, + TextElement = new(), + }; } \ No newline at end of file diff --git a/I18N Commander/DataModel/Setup.cs b/I18N Commander/DataModel/Setup.cs index f2a3e06..e687af1 100644 --- a/I18N Commander/DataModel/Setup.cs +++ b/I18N Commander/DataModel/Setup.cs @@ -1,4 +1,5 @@ -using DataModel.Database.Common; +using DataModel.Database; +using DataModel.Database.Common; using DataModel.MigrationScripts; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; @@ -12,9 +13,12 @@ public static class Setup private const string DB_READ_WRITE_MODE = "ReadWrite"; private const string DB_READ_WRITE_CREATE_MODE = "ReadWriteCreate"; - private static string usedDataFile = string.Empty; - - public static string DataFile => Setup.usedDataFile; + private static string USED_DATA_FILE = string.Empty; + private static SetupMaintenance SETUP_MAINTENANCE = new(); + + public static string DataFile => Setup.USED_DATA_FILE; + + public static SetupMaintenance Maintenance => Setup.SETUP_MAINTENANCE; /// /// Tries to migrate the database. @@ -37,31 +41,52 @@ public static class Setup } /// - /// Add the database to the DI system + /// Imports a JSON file into a new database. /// - public static void AddDatabase(this IServiceCollection serviceCollection, string path2DataFile, bool createWhenNecessary = true) + public static async Task ImportDataAndAddDatabase(this IServiceCollection serviceCollection, string path2JSONFile) { - Setup.usedDataFile = path2DataFile; - serviceCollection.AddDbContext(options => options.UseSqlite($"Filename={path2DataFile};Mode={(createWhenNecessary ? DB_READ_WRITE_CREATE_MODE : DB_READ_WRITE_MODE)};"), ServiceLifetime.Transient); + var tempPath = Path.GetTempFileName(); + + Setup.USED_DATA_FILE = tempPath; + Setup.SETUP_MAINTENANCE = new(path2JSONFile, true); + serviceCollection.AddDbContext(options => options.UseSqlite($"Filename={tempPath};Mode={DB_READ_WRITE_CREATE_MODE};"), ServiceLifetime.Transient); + + // Get the database service: + await using var serviceProvider = serviceCollection.BuildServiceProvider(); + var dbContext = serviceProvider.GetRequiredService(); + + // Next, we import the data from the provided JSON file: + await dbContext.ImportAsync(path2JSONFile); + + // + // Next, we enable the auto-export feature to keep the source file up to date. + // The auto-export feature might exist, but we enforce it, when we work with a + // temporary database source by a JSON file. + // + + // Enable the auto-export feature: + var autoExportEnabled = await dbContext.Settings.FirstAsync(n => n.Code == SettingNames.AUTO_EXPORT_ENABLED); + autoExportEnabled.BoolValue = true; + + // Set the auto-export path and file: + var autoExportPath = await dbContext.Settings.FirstAsync(n => n.Code == SettingNames.AUTO_EXPORT_DESTINATION_PATH); + autoExportPath.TextValue = Path.GetDirectoryName(path2JSONFile) ?? string.Empty; + + var autoExportFile = await dbContext.Settings.FirstAsync(n => n.Code == SettingNames.AUTO_EXPORT_FILENAME); + autoExportFile.TextValue = Path.GetFileName(path2JSONFile); + + // Save the changes: + await dbContext.SaveChangesAsync(); } /// - /// Create the database instance from the given path. + /// Creates and adds the database instance to the DI system (extension method). /// - public static DataContext CreateDatabaseInstance(string path2DataFile, bool createWhenNecessary = true) + public static void AddDatabase(this IServiceCollection serviceCollection, string path2DataFile, bool createWhenNecessary = true) { - // Store the path to the database: - Setup.usedDataFile = path2DataFile; - - // Create a database builder: - var builder = new DbContextOptionsBuilder(); - - // Add the database configuration to the builder: - builder.UseSqlite($"Filename={path2DataFile};Mode={(createWhenNecessary ? DB_READ_WRITE_CREATE_MODE : DB_READ_WRITE_MODE)};"); - - // Next, construct the database context: - var dbContext = new DataContext(builder.Options); - return dbContext; + Setup.USED_DATA_FILE = path2DataFile; + Setup.SETUP_MAINTENANCE = new(path2DataFile, false); + serviceCollection.AddDbContext(options => options.UseSqlite($"Filename={path2DataFile};Mode={(createWhenNecessary ? DB_READ_WRITE_CREATE_MODE : DB_READ_WRITE_MODE)};"), ServiceLifetime.Transient); } /// @@ -84,4 +109,22 @@ public static class Setup return builder; } + + public readonly record struct SetupMaintenance(string PathToDataFile = "", bool RemoveTempDatabaseAfterwards = false) : IDisposable + { + public void Dispose() + { + if (!this.RemoveTempDatabaseAfterwards) + return; + + try + { + File.Delete(this.PathToDataFile); + } + catch(Exception e) + { + Console.WriteLine($"Failed to remove the temporary database file: {e.Message} // {e.InnerException?.Message}"); + } + } + } } diff --git a/I18N Commander/Processor/ExportProcessor.cs b/I18N Commander/Processor/ExportProcessor.cs new file mode 100644 index 0000000..371f24c --- /dev/null +++ b/I18N Commander/Processor/ExportProcessor.cs @@ -0,0 +1,46 @@ +using DataModel.Database.Common; +using Microsoft.Extensions.DependencyInjection; +using Timer = System.Timers.Timer; + +namespace Processor; + +public static class ExportProcessor +{ + private static readonly Timer EXPORT_TIMER = new(); + private static readonly object EXPORT_LOCK = new(); + private static readonly SemaphoreSlim EXPORT_SEMAPHORE = new(2); + + static ExportProcessor() + { + EXPORT_TIMER.Interval = 6_000; // 6 seconds + EXPORT_TIMER.AutoReset = false; + EXPORT_TIMER.Elapsed += async (sender, args) => await ExportToJson(); + } + + private static async Task ExportToJson() + { + if(!await EXPORT_SEMAPHORE.WaitAsync(1)) + return; + + Monitor.Enter(EXPORT_LOCK); + try + { + await using var db = ProcessorMeta.ServiceProvider.GetRequiredService(); + await db.ExportAsync(Environment.ExpandEnvironmentVariables(Path.Join(await AppSettings.GetAutoExportDestinationPath(), await AppSettings.GetAutoExportFilename()))); + } + finally + { + Monitor.Exit(EXPORT_LOCK); + EXPORT_SEMAPHORE.Release(); + } + } + + public static async Task TriggerExport() + { + if (!await AppSettings.GetAutoExportEnabled()) + return; + + EXPORT_TIMER.Stop(); + EXPORT_TIMER.Start(); + } +} \ No newline at end of file diff --git a/I18N Commander/Processor/Version.cs b/I18N Commander/Processor/Version.cs index 4bff47f..62de691 100644 --- a/I18N Commander/Processor/Version.cs +++ b/I18N Commander/Processor/Version.cs @@ -2,5 +2,5 @@ public static class Version { - public static string Text => $"v0.7.1 (2022-11-06), .NET {Environment.Version}"; + public static string Text => $"v0.8.0 (2023-01-22), .NET {Environment.Version}"; } \ No newline at end of file diff --git a/I18N Commander/UI WinForms/AppEvents.cs b/I18N Commander/UI WinForms/AppEvents.cs index 16297c4..b86f556 100644 --- a/I18N Commander/UI WinForms/AppEvents.cs +++ b/I18N Commander/UI WinForms/AppEvents.cs @@ -4,12 +4,19 @@ namespace UI_WinForms; internal static class AppEvents { + static AppEvents() + { + AppEvents.AddSomethingHandlers(); + } + internal static void ResetAllSubscriptions() { WhenSettingsChanged = null; WhenSectionChanged = null; WhenTextElementChanged = null; WhenTranslationChanged = null; + + AppEvents.AddSomethingHandlers(); } #region Event: Settings changed @@ -49,4 +56,20 @@ internal static class AppEvents internal static void TranslationChanged(Translation? translation) => WhenTranslationChanged?.Invoke(null, translation); #endregion + + #region Event: Something was changed + + internal static event EventHandler? WhenSomethingChanged; + + internal static void SomethingChanged() => WhenSomethingChanged?.Invoke(null, EventArgs.Empty); + + private static void AddSomethingHandlers() + { + // Raise the something event when any of the other change-events are raised: + WhenSectionChanged += (sender, args) => WhenSomethingChanged?.Invoke(sender, EventArgs.Empty); + WhenTextElementChanged += (sender, args) => WhenSomethingChanged?.Invoke(sender, EventArgs.Empty); + WhenTranslationChanged += (sender, args) => WhenSomethingChanged?.Invoke(sender, EventArgs.Empty); + } + + #endregion } \ No newline at end of file diff --git a/I18N Commander/UI WinForms/Components/LoaderStart.cs b/I18N Commander/UI WinForms/Components/LoaderStart.cs index 523bc35..ce2b358 100644 --- a/I18N Commander/UI WinForms/Components/LoaderStart.cs +++ b/I18N Commander/UI WinForms/Components/LoaderStart.cs @@ -62,6 +62,7 @@ public partial class LoaderStart : UserControl #endregion + // Opens the recent projects dropdown menu. private void buttonOpen_Click(object sender, EventArgs e) { if(this.DesignMode) @@ -84,9 +85,19 @@ public partial class LoaderStart : UserControl // Split the file's path into each folder's name: var folderNames = fileInfo.DirectoryName!.Split(Path.DirectorySeparatorChar); - // Render this entry: - var item = this.contextMenuRecentProjects.Items.Add($"{folderNames.Last()}: {fileInfo.Name}", Resources.Icons.icons8_document_512, (innerSender, args) => this.OpenRecentProject(innerSender)); - item.Tag = recentProject; + // Distinguish between I18N Commander projects and JSON imports: + if (fileInfo.Extension == ".i18nc") + { + // Render this entry: + var item = this.contextMenuRecentProjects.Items.Add($"{folderNames.Last()}: {fileInfo.Name}", Resources.Icons.icons8_document_512, (innerSender, args) => this.OpenRecentProject(innerSender, LoaderAction.LOAD_PROJECT)); + item.Tag = recentProject; + } + else if (fileInfo.Extension == ".json") + { + // Render this entry: + var item = this.contextMenuRecentProjects.Items.Add($"{folderNames.Last()}: {fileInfo.Name}", Resources.Icons.icons8_git, (innerSender, args) => this.OpenRecentProject(innerSender, LoaderAction.IMPORT_JSON)); + item.Tag = recentProject; + } } var button = (sender as Button)!; @@ -125,7 +136,7 @@ public partial class LoaderStart : UserControl File.Delete(destinationFilePath); LoaderStart.UpdateRecentProjectsWithPruning(destinationFilePath); - this.OpenProject(destinationFilePath); + this.OpenProject(LoaderAction.CREATE_NEW_PROJECT, destinationFilePath); } private void BrowseForProject() @@ -137,10 +148,12 @@ public partial class LoaderStart : UserControl CheckFileExists = true, DereferenceLinks = true, DefaultExt = ".i18nc", - Filter = "I18N Commander Files (*.i18nc)|*.i18nc", + + // I18N Commander files (*.i18nc) or JSON files (*.json): + Filter = "I18N Commander Files (*.i18nc)|*.i18nc|JSON Files (*.json)|*.json", Multiselect = false, RestoreDirectory = true, - Title = "Open an I18N Commander file", + Title = "Open an I18N Commander file or Import a JSON file", }; var dialogResult = openDialog.ShowDialog(this); if (dialogResult != DialogResult.OK) @@ -148,23 +161,28 @@ public partial class LoaderStart : UserControl var projectFilePath = openDialog.FileName; LoaderStart.UpdateRecentProjectsWithPruning(projectFilePath); - this.OpenProject(projectFilePath); + + // Check, if the user chose an I18N Commander file or a JSON file: + if (projectFilePath.ToLowerInvariant().EndsWith(".i18nc", StringComparison.InvariantCulture)) + this.OpenProject(LoaderAction.LOAD_PROJECT, projectFilePath); + else if (projectFilePath.ToLowerInvariant().EndsWith(".json", StringComparison.InvariantCulture)) + this.OpenProject(LoaderAction.IMPORT_JSON, projectFilePath); } - private void OpenRecentProject(object? sender) + private void OpenRecentProject(object? sender, LoaderAction action) { if (sender is not ToolStripItem item) return; var path = (item.Tag as string)!; LoaderStart.UpdateRecentProjectsWithPruning(path); - this.OpenProject(path); + this.OpenProject(action, path); } - private void OpenProject(string path) + private void OpenProject(LoaderAction action, string path) { // Hint: the project file might or might not exist (new project vs. recent project) - this.LoadProject?.Invoke(this, path); + this.LoadProject?.Invoke(this, new LoaderResult(action, path)); } private void contextMenuRecentProjects_Closing(object sender, ToolStripDropDownClosingEventArgs e) @@ -173,5 +191,16 @@ public partial class LoaderStart : UserControl } [Category("Settings"), Description("When the user chooses a project to load.")] - public event EventHandler? LoadProject; + public event EventHandler? LoadProject; + + public readonly record struct LoaderResult(LoaderAction Action, string DataFile); + + public enum LoaderAction + { + NONE, + + LOAD_PROJECT, + CREATE_NEW_PROJECT, + IMPORT_JSON, + } } \ No newline at end of file diff --git a/I18N Commander/UI WinForms/Components/Main.cs b/I18N Commander/UI WinForms/Components/Main.cs index b4e1950..ec44a6e 100644 --- a/I18N Commander/UI WinForms/Components/Main.cs +++ b/I18N Commander/UI WinForms/Components/Main.cs @@ -1,4 +1,6 @@ -namespace UI_WinForms.Components; +using Processor; + +namespace UI_WinForms.Components; public partial class Main : UserControl { @@ -6,6 +8,9 @@ public partial class Main : UserControl { this.InitializeComponent(); Program.RestartMainApp = false; + + // Register the something changed event to trigger the export: + AppEvents.WhenSomethingChanged += async (_, _) => await ExportProcessor.TriggerExport(); } private void tabControl_SelectedIndexChanged(object sender, EventArgs e) diff --git a/I18N Commander/UI WinForms/Components/Setting.cs b/I18N Commander/UI WinForms/Components/Setting.cs index 5256be2..9e5a7b9 100644 --- a/I18N Commander/UI WinForms/Components/Setting.cs +++ b/I18N Commander/UI WinForms/Components/Setting.cs @@ -690,7 +690,7 @@ public sealed partial class Setting : UserControl { var currentSetting = await AppSettings.GetAutoExportEnabled(); var settingData = new SettingUIData( - Icon: Icons.icons8_git_svg, + Icon: Icons.icons8_git, SettingName: () => "Git (JSON) Auto-Export: Enabled", ChangeNeedsRestart: true, SettingExplanation: () => "When enabled, all changes are automatically exported into a Git repository as JSON file.", @@ -700,9 +700,13 @@ public sealed partial class Setting : UserControl // Set up an checkbox: var checkbox = new CheckBox(); checkbox.Checked = currentSetting; - checkbox.CheckedChanged += async (sender, args) => await AppSettings.SetAutoExportEnabled(checkbox.Checked); - checkbox.CheckedChanged += (sender, args) => changeTrigger(); - checkbox.Text = "Enable Git (JSON) Auto-Export"; + checkbox.CheckedChanged += async (sender, args) => + { + await AppSettings.SetAutoExportEnabled(checkbox.Checked); + await ExportProcessor.TriggerExport(); + changeTrigger(); + }; + checkbox.Text = "Enable Auto-Export"; // Apply the desired layout: checkbox.Dock = DockStyle.Fill; @@ -717,7 +721,7 @@ public sealed partial class Setting : UserControl { var currentSetting = await AppSettings.GetAutoExportDestinationPath(); var settingData = new SettingUIData( - Icon: Icons.icons8_git_svg, + Icon: Icons.icons8_git, SettingName: () => "Git (JSON) Auto-Export: Destination Path", ChangeNeedsRestart: true, SettingExplanation: () => "The destination path for the Git repository. You might use environment variables like %USERPROFILE%.", @@ -775,7 +779,7 @@ public sealed partial class Setting : UserControl { var currentSetting = await AppSettings.GetAutoExportFilename(); var settingData = new SettingUIData( - Icon: Icons.icons8_git_svg, + Icon: Icons.icons8_git, SettingName: () => "Git (JSON) Auto-Export: Filename", ChangeNeedsRestart: true, SettingExplanation: () => "The filename used for the Git export. You might use environment variables like %USERPROFILE%.", @@ -800,7 +804,7 @@ public sealed partial class Setting : UserControl { var currentSetting = await AppSettings.GetAutoExportSensitiveData(); var settingData = new SettingUIData( - Icon: Icons.icons8_git_svg, + Icon: Icons.icons8_git, SettingName: () => "Git (JSON) Auto-Export: Export Sensitive Data", ChangeNeedsRestart: true, SettingExplanation: () => "When enabled, sensitive data like API tokens are exported into the Git repository. This is not recommended!", diff --git a/I18N Commander/UI WinForms/Loader.Designer.cs b/I18N Commander/UI WinForms/Loader.Designer.cs index 9737b5b..c645492 100644 --- a/I18N Commander/UI WinForms/Loader.Designer.cs +++ b/I18N Commander/UI WinForms/Loader.Designer.cs @@ -1,4 +1,6 @@ -namespace UI_WinForms +using UI_WinForms.Components; + +namespace UI_WinForms { partial class Loader { @@ -57,7 +59,7 @@ this.loaderStart.Name = "loaderStart"; this.loaderStart.Size = new System.Drawing.Size(794, 444); this.loaderStart.TabIndex = 0; - this.loaderStart.LoadProject += new System.EventHandler(this.loaderStart_LoadProject); + this.loaderStart.LoadProject += new System.EventHandler(this.loaderStart_LoadProject); // // Loader // diff --git a/I18N Commander/UI WinForms/Loader.cs b/I18N Commander/UI WinForms/Loader.cs index 6a6ca87..55abd1c 100644 --- a/I18N Commander/UI WinForms/Loader.cs +++ b/I18N Commander/UI WinForms/Loader.cs @@ -1,20 +1,22 @@ -namespace UI_WinForms; +using UI_WinForms.Components; + +namespace UI_WinForms; public partial class Loader : Form { - public string DataFile { get; set; } = string.Empty; + public LoaderStart.LoaderResult Result { get; private set; } public Loader() { this.InitializeComponent(); } - private void loaderStart_LoadProject(object sender, string projectFilePath) + private void loaderStart_LoadProject(object sender, LoaderStart.LoaderResult result) { if(this.DesignMode) return; - this.DataFile = projectFilePath; + this.Result = result; this.DialogResult = DialogResult.OK; this.Close(); } diff --git a/I18N Commander/UI WinForms/Program.cs b/I18N Commander/UI WinForms/Program.cs index 054d7de..2a1296f 100644 --- a/I18N Commander/UI WinForms/Program.cs +++ b/I18N Commander/UI WinForms/Program.cs @@ -1,9 +1,12 @@ +using System.Text.Json; using DataModel; using DataModel.Database.Common; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Processor; +using static UI_WinForms.Components.LoaderStart.LoaderAction; + namespace UI_WinForms; internal static class Program @@ -25,7 +28,7 @@ internal static class Program // Check, if the user closes the loader screen: if (loader.DialogResult != DialogResult.OK) return; - + // // Create the DI system // @@ -36,10 +39,25 @@ internal static class Program // builder.ConfigureServices((hostContext, serviceCollection) => { - // The database: - serviceCollection.AddDatabase(loader.DataFile, true); + if(loader.Result.Action is LOAD_PROJECT or CREATE_NEW_PROJECT) + serviceCollection.AddDatabase(loader.Result.DataFile, true); + else if(loader.Result.Action is IMPORT_JSON) + { + try + { + serviceCollection.ImportDataAndAddDatabase(loader.Result.DataFile).ConfigureAwait(false); + } + catch (JsonException) + { + MessageBox.Show($"The JSON file '{loader.Result.DataFile}' is invalid and cannot be imported.", "Invalid JSON file", MessageBoxButtons.OK, MessageBoxIcon.Error); + Environment.Exit(100); + } + } }); + // Tear down the setup: + using var setupMaintenance = Setup.Maintenance; + // Get the host out of the DI system: var host = builder.Build(); diff --git a/I18N Commander/UI WinForms/Resources/Icons.Designer.cs b/I18N Commander/UI WinForms/Resources/Icons.Designer.cs index 98d01fb..10bf690 100644 --- a/I18N Commander/UI WinForms/Resources/Icons.Designer.cs +++ b/I18N Commander/UI WinForms/Resources/Icons.Designer.cs @@ -213,9 +213,9 @@ namespace UI_WinForms.Resources { /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// - internal static System.Drawing.Bitmap icons8_git_svg { + internal static System.Drawing.Bitmap icons8_git { get { - object obj = ResourceManager.GetObject("icons8_git_svg", resourceCulture); + object obj = ResourceManager.GetObject("icons8_git", resourceCulture); return ((System.Drawing.Bitmap)(obj)); } } diff --git a/I18N Commander/UI WinForms/Resources/Icons.resx b/I18N Commander/UI WinForms/Resources/Icons.resx index ee2bfe1..ec68fe3 100644 --- a/I18N Commander/UI WinForms/Resources/Icons.resx +++ b/I18N Commander/UI WinForms/Resources/Icons.resx @@ -163,8 +163,8 @@ icons8-folder-tree-512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - icons8-git.svg.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + icons8-git.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a icons8-increase-512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a diff --git a/I18N Commander/UI WinForms/Resources/icons8-git.png b/I18N Commander/UI WinForms/Resources/icons8-git.png new file mode 100644 index 0000000..8951825 Binary files /dev/null and b/I18N Commander/UI WinForms/Resources/icons8-git.png differ