diff --git a/I18N Commander/.idea/.idea.I18N Commander/.idea/dataSources.xml b/I18N Commander/.idea/.idea.I18N Commander/.idea/dataSources.xml
new file mode 100644
index 0000000..4c26172
--- /dev/null
+++ b/I18N Commander/.idea/.idea.I18N Commander/.idea/dataSources.xml
@@ -0,0 +1,12 @@
+
+
+
+
+ sqlite.xerial
+ true
+ org.sqlite.JDBC
+ jdbc:sqlite:$PROJECT_DIR$/../../../test.i18nc
+ $ProjectFileDir$
+
+
+
\ No newline at end of file
diff --git a/I18N Commander/DataModel/Database/Common/DataContext.cs b/I18N Commander/DataModel/Database/Common/DataContext.cs
index 3d9971d..08fe633 100644
--- a/I18N Commander/DataModel/Database/Common/DataContext.cs
+++ b/I18N Commander/DataModel/Database/Common/DataContext.cs
@@ -4,13 +4,13 @@ namespace DataModel.Database.Common;
public sealed class DataContext : DbContext
{
- public DbSet? Settings { get; set; }
+ public DbSet Settings { get; set; }
- public DbSet? Sections { get; set; }
+ public DbSet Sections { get; set; }
- public DbSet? TextElements { get; set; }
+ public DbSet TextElements { get; set; }
- public DbSet? Translations { get; set; }
+ public DbSet Translations { get; set; }
public DataContext(DbContextOptions contextOptions) : base(contextOptions)
{
@@ -35,6 +35,8 @@ public sealed class DataContext : DbContext
modelBuilder.Entity().HasIndex(n => n.Id);
modelBuilder.Entity().HasIndex(n => n.Name);
+ modelBuilder.Entity().HasIndex(n => n.Depth);
+ modelBuilder.Entity().HasIndex(n => n.DataKey);
#endregion
diff --git a/I18N Commander/DataModel/Database/Section.cs b/I18N Commander/DataModel/Database/Section.cs
index b7f4c60..195bdba 100644
--- a/I18N Commander/DataModel/Database/Section.cs
+++ b/I18N Commander/DataModel/Database/Section.cs
@@ -9,7 +9,11 @@ public sealed class Section
public string Name { get; set; } = string.Empty;
- public Section Parent { get; set; }
+ public string DataKey { get; set; } = string.Empty;
- public List TextElements { get; set; }
+ public int Depth { get; set; } = 0;
+
+ public Section? Parent { get; set; }
+
+ public List TextElements { get; set; } = new();
}
\ No newline at end of file
diff --git a/I18N Commander/DataModel/Migrations/20220626195157_202206MadeParentNullable.Designer.cs b/I18N Commander/DataModel/Migrations/20220626195157_202206MadeParentNullable.Designer.cs
new file mode 100644
index 0000000..621a1c0
--- /dev/null
+++ b/I18N Commander/DataModel/Migrations/20220626195157_202206MadeParentNullable.Designer.cs
@@ -0,0 +1,184 @@
+//
+using System;
+using DataModel.Database.Common;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace DataModel.Migrations
+{
+ [DbContext(typeof(DataContext))]
+ [Migration("20220626195157_202206MadeParentNullable")]
+ partial class _202206MadeParentNullable
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "6.0.5");
+
+ modelBuilder.Entity("DataModel.Database.Section", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("ParentId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Id");
+
+ b.HasIndex("Name");
+
+ b.HasIndex("ParentId");
+
+ b.ToTable("Sections");
+ });
+
+ modelBuilder.Entity("DataModel.Database.Setting", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("BoolValue")
+ .HasColumnType("INTEGER");
+
+ b.Property("GuidValue")
+ .HasColumnType("TEXT");
+
+ b.Property("IntegerValue")
+ .HasColumnType("INTEGER");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("TextValue")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("BoolValue");
+
+ b.HasIndex("GuidValue");
+
+ b.HasIndex("Id");
+
+ b.HasIndex("IntegerValue");
+
+ b.HasIndex("Name")
+ .IsUnique();
+
+ b.HasIndex("TextValue");
+
+ b.ToTable("Settings");
+ });
+
+ modelBuilder.Entity("DataModel.Database.TextElement", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Code")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("SectionId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Code");
+
+ b.HasIndex("Id");
+
+ b.HasIndex("SectionId");
+
+ b.ToTable("TextElements");
+ });
+
+ modelBuilder.Entity("DataModel.Database.Translation", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Culture")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Text")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("TextElementId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Culture");
+
+ b.HasIndex("Id");
+
+ b.HasIndex("Text");
+
+ b.HasIndex("TextElementId");
+
+ b.ToTable("Translations");
+ });
+
+ modelBuilder.Entity("DataModel.Database.Section", b =>
+ {
+ b.HasOne("DataModel.Database.Section", "Parent")
+ .WithMany()
+ .HasForeignKey("ParentId");
+
+ b.Navigation("Parent");
+ });
+
+ modelBuilder.Entity("DataModel.Database.TextElement", b =>
+ {
+ b.HasOne("DataModel.Database.Section", "Section")
+ .WithMany("TextElements")
+ .HasForeignKey("SectionId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Section");
+ });
+
+ modelBuilder.Entity("DataModel.Database.Translation", b =>
+ {
+ b.HasOne("DataModel.Database.TextElement", "TextElement")
+ .WithMany("Translations")
+ .HasForeignKey("TextElementId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("TextElement");
+ });
+
+ modelBuilder.Entity("DataModel.Database.Section", b =>
+ {
+ b.Navigation("TextElements");
+ });
+
+ modelBuilder.Entity("DataModel.Database.TextElement", b =>
+ {
+ b.Navigation("Translations");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/I18N Commander/DataModel/Migrations/20220626195157_202206MadeParentNullable.cs b/I18N Commander/DataModel/Migrations/20220626195157_202206MadeParentNullable.cs
new file mode 100644
index 0000000..a04faba
--- /dev/null
+++ b/I18N Commander/DataModel/Migrations/20220626195157_202206MadeParentNullable.cs
@@ -0,0 +1,56 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace DataModel.Migrations
+{
+ public partial class _202206MadeParentNullable : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropForeignKey(
+ name: "FK_Sections_Sections_ParentId",
+ table: "Sections");
+
+ migrationBuilder.AlterColumn(
+ name: "ParentId",
+ table: "Sections",
+ type: "INTEGER",
+ nullable: true,
+ oldClrType: typeof(int),
+ oldType: "INTEGER");
+
+ migrationBuilder.AddForeignKey(
+ name: "FK_Sections_Sections_ParentId",
+ table: "Sections",
+ column: "ParentId",
+ principalTable: "Sections",
+ principalColumn: "Id");
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropForeignKey(
+ name: "FK_Sections_Sections_ParentId",
+ table: "Sections");
+
+ migrationBuilder.AlterColumn(
+ name: "ParentId",
+ table: "Sections",
+ type: "INTEGER",
+ nullable: false,
+ defaultValue: 0,
+ oldClrType: typeof(int),
+ oldType: "INTEGER",
+ oldNullable: true);
+
+ migrationBuilder.AddForeignKey(
+ name: "FK_Sections_Sections_ParentId",
+ table: "Sections",
+ column: "ParentId",
+ principalTable: "Sections",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ }
+ }
+}
diff --git a/I18N Commander/DataModel/Migrations/20220709094035_202207AddSectionDataKey.Designer.cs b/I18N Commander/DataModel/Migrations/20220709094035_202207AddSectionDataKey.Designer.cs
new file mode 100644
index 0000000..b0818d3
--- /dev/null
+++ b/I18N Commander/DataModel/Migrations/20220709094035_202207AddSectionDataKey.Designer.cs
@@ -0,0 +1,190 @@
+//
+using System;
+using DataModel.Database.Common;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace DataModel.Migrations
+{
+ [DbContext(typeof(DataContext))]
+ [Migration("20220709094035_202207AddSectionDataKey")]
+ partial class _202207AddSectionDataKey
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "6.0.5");
+
+ modelBuilder.Entity("DataModel.Database.Section", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("DataKey")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("ParentId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DataKey");
+
+ b.HasIndex("Id");
+
+ b.HasIndex("Name");
+
+ b.HasIndex("ParentId");
+
+ b.ToTable("Sections");
+ });
+
+ modelBuilder.Entity("DataModel.Database.Setting", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("BoolValue")
+ .HasColumnType("INTEGER");
+
+ b.Property("GuidValue")
+ .HasColumnType("TEXT");
+
+ b.Property("IntegerValue")
+ .HasColumnType("INTEGER");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("TextValue")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("BoolValue");
+
+ b.HasIndex("GuidValue");
+
+ b.HasIndex("Id");
+
+ b.HasIndex("IntegerValue");
+
+ b.HasIndex("Name")
+ .IsUnique();
+
+ b.HasIndex("TextValue");
+
+ b.ToTable("Settings");
+ });
+
+ modelBuilder.Entity("DataModel.Database.TextElement", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Code")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("SectionId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Code");
+
+ b.HasIndex("Id");
+
+ b.HasIndex("SectionId");
+
+ b.ToTable("TextElements");
+ });
+
+ modelBuilder.Entity("DataModel.Database.Translation", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Culture")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Text")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("TextElementId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Culture");
+
+ b.HasIndex("Id");
+
+ b.HasIndex("Text");
+
+ b.HasIndex("TextElementId");
+
+ b.ToTable("Translations");
+ });
+
+ modelBuilder.Entity("DataModel.Database.Section", b =>
+ {
+ b.HasOne("DataModel.Database.Section", "Parent")
+ .WithMany()
+ .HasForeignKey("ParentId");
+
+ b.Navigation("Parent");
+ });
+
+ modelBuilder.Entity("DataModel.Database.TextElement", b =>
+ {
+ b.HasOne("DataModel.Database.Section", "Section")
+ .WithMany("TextElements")
+ .HasForeignKey("SectionId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Section");
+ });
+
+ modelBuilder.Entity("DataModel.Database.Translation", b =>
+ {
+ b.HasOne("DataModel.Database.TextElement", "TextElement")
+ .WithMany("Translations")
+ .HasForeignKey("TextElementId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("TextElement");
+ });
+
+ modelBuilder.Entity("DataModel.Database.Section", b =>
+ {
+ b.Navigation("TextElements");
+ });
+
+ modelBuilder.Entity("DataModel.Database.TextElement", b =>
+ {
+ b.Navigation("Translations");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/I18N Commander/DataModel/Migrations/20220709094035_202207AddSectionDataKey.cs b/I18N Commander/DataModel/Migrations/20220709094035_202207AddSectionDataKey.cs
new file mode 100644
index 0000000..a29ef69
--- /dev/null
+++ b/I18N Commander/DataModel/Migrations/20220709094035_202207AddSectionDataKey.cs
@@ -0,0 +1,35 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace DataModel.Migrations
+{
+ public partial class _202207AddSectionDataKey : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "DataKey",
+ table: "Sections",
+ type: "TEXT",
+ nullable: false,
+ defaultValue: "");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_Sections_DataKey",
+ table: "Sections",
+ column: "DataKey");
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropIndex(
+ name: "IX_Sections_DataKey",
+ table: "Sections");
+
+ migrationBuilder.DropColumn(
+ name: "DataKey",
+ table: "Sections");
+ }
+ }
+}
diff --git a/I18N Commander/DataModel/Migrations/20220709095404_202207AddSectionDepth.Designer.cs b/I18N Commander/DataModel/Migrations/20220709095404_202207AddSectionDepth.Designer.cs
new file mode 100644
index 0000000..b3e601f
--- /dev/null
+++ b/I18N Commander/DataModel/Migrations/20220709095404_202207AddSectionDepth.Designer.cs
@@ -0,0 +1,195 @@
+//
+using System;
+using DataModel.Database.Common;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace DataModel.Migrations
+{
+ [DbContext(typeof(DataContext))]
+ [Migration("20220709095404_202207AddSectionDepth")]
+ partial class _202207AddSectionDepth
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "6.0.5");
+
+ modelBuilder.Entity("DataModel.Database.Section", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("DataKey")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Depth")
+ .HasColumnType("INTEGER");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("ParentId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DataKey");
+
+ b.HasIndex("Depth");
+
+ b.HasIndex("Id");
+
+ b.HasIndex("Name");
+
+ b.HasIndex("ParentId");
+
+ b.ToTable("Sections");
+ });
+
+ modelBuilder.Entity("DataModel.Database.Setting", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("BoolValue")
+ .HasColumnType("INTEGER");
+
+ b.Property("GuidValue")
+ .HasColumnType("TEXT");
+
+ b.Property("IntegerValue")
+ .HasColumnType("INTEGER");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("TextValue")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("BoolValue");
+
+ b.HasIndex("GuidValue");
+
+ b.HasIndex("Id");
+
+ b.HasIndex("IntegerValue");
+
+ b.HasIndex("Name")
+ .IsUnique();
+
+ b.HasIndex("TextValue");
+
+ b.ToTable("Settings");
+ });
+
+ modelBuilder.Entity("DataModel.Database.TextElement", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Code")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("SectionId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Code");
+
+ b.HasIndex("Id");
+
+ b.HasIndex("SectionId");
+
+ b.ToTable("TextElements");
+ });
+
+ modelBuilder.Entity("DataModel.Database.Translation", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Culture")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Text")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("TextElementId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Culture");
+
+ b.HasIndex("Id");
+
+ b.HasIndex("Text");
+
+ b.HasIndex("TextElementId");
+
+ b.ToTable("Translations");
+ });
+
+ modelBuilder.Entity("DataModel.Database.Section", b =>
+ {
+ b.HasOne("DataModel.Database.Section", "Parent")
+ .WithMany()
+ .HasForeignKey("ParentId");
+
+ b.Navigation("Parent");
+ });
+
+ modelBuilder.Entity("DataModel.Database.TextElement", b =>
+ {
+ b.HasOne("DataModel.Database.Section", "Section")
+ .WithMany("TextElements")
+ .HasForeignKey("SectionId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Section");
+ });
+
+ modelBuilder.Entity("DataModel.Database.Translation", b =>
+ {
+ b.HasOne("DataModel.Database.TextElement", "TextElement")
+ .WithMany("Translations")
+ .HasForeignKey("TextElementId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("TextElement");
+ });
+
+ modelBuilder.Entity("DataModel.Database.Section", b =>
+ {
+ b.Navigation("TextElements");
+ });
+
+ modelBuilder.Entity("DataModel.Database.TextElement", b =>
+ {
+ b.Navigation("Translations");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/I18N Commander/DataModel/Migrations/20220709095404_202207AddSectionDepth.cs b/I18N Commander/DataModel/Migrations/20220709095404_202207AddSectionDepth.cs
new file mode 100644
index 0000000..7829b3e
--- /dev/null
+++ b/I18N Commander/DataModel/Migrations/20220709095404_202207AddSectionDepth.cs
@@ -0,0 +1,35 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace DataModel.Migrations
+{
+ public partial class _202207AddSectionDepth : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "Depth",
+ table: "Sections",
+ type: "INTEGER",
+ nullable: false,
+ defaultValue: 0);
+
+ migrationBuilder.CreateIndex(
+ name: "IX_Sections_Depth",
+ table: "Sections",
+ column: "Depth");
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropIndex(
+ name: "IX_Sections_Depth",
+ table: "Sections");
+
+ migrationBuilder.DropColumn(
+ name: "Depth",
+ table: "Sections");
+ }
+ }
+}
diff --git a/I18N Commander/DataModel/Migrations/DataContextModelSnapshot.cs b/I18N Commander/DataModel/Migrations/DataContextModelSnapshot.cs
index fd8d8b5..6a285f4 100644
--- a/I18N Commander/DataModel/Migrations/DataContextModelSnapshot.cs
+++ b/I18N Commander/DataModel/Migrations/DataContextModelSnapshot.cs
@@ -23,15 +23,26 @@ namespace DataModel.Migrations
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
+ b.Property("DataKey")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Depth")
+ .HasColumnType("INTEGER");
+
b.Property("Name")
.IsRequired()
.HasColumnType("TEXT");
- b.Property("ParentId")
+ b.Property("ParentId")
.HasColumnType("INTEGER");
b.HasKey("Id");
+ b.HasIndex("DataKey");
+
+ b.HasIndex("Depth");
+
b.HasIndex("Id");
b.HasIndex("Name");
@@ -140,9 +151,7 @@ namespace DataModel.Migrations
{
b.HasOne("DataModel.Database.Section", "Parent")
.WithMany()
- .HasForeignKey("ParentId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
+ .HasForeignKey("ParentId");
b.Navigation("Parent");
});
diff --git a/I18N Commander/DataModel/Setup.cs b/I18N Commander/DataModel/Setup.cs
index 44e32d8..ed1388d 100644
--- a/I18N Commander/DataModel/Setup.cs
+++ b/I18N Commander/DataModel/Setup.cs
@@ -30,7 +30,7 @@ public static class Setup
///
public static void AddDatabase(this IServiceCollection serviceCollection, string path2DataFile, bool createWhenNecessary = true)
{
- serviceCollection.AddDbContext(options => options.UseSqlite($"Filename={path2DataFile};Mode={(createWhenNecessary ? DB_READ_WRITE_CREATE_MODE : DB_READ_WRITE_MODE)}"));
+ serviceCollection.AddDbContext(options => options.UseSqlite($"Filename={path2DataFile};Mode={(createWhenNecessary ? DB_READ_WRITE_CREATE_MODE : DB_READ_WRITE_MODE)}"), ServiceLifetime.Transient);
}
///
diff --git a/I18N Commander/Processor/SectionProcessor.cs b/I18N Commander/Processor/SectionProcessor.cs
new file mode 100644
index 0000000..be52f29
--- /dev/null
+++ b/I18N Commander/Processor/SectionProcessor.cs
@@ -0,0 +1,111 @@
+using DataModel.Database;
+using DataModel.Database.Common;
+using Microsoft.EntityFrameworkCore;
+
+namespace Processor;
+
+public static class SectionProcessor
+{
+ ///
+ /// Load one layer of the tree by using the specified depth:
+ ///
+ public static IAsyncEnumerable LoadLayer(DataContext db, int depth)
+ {
+ return db.Sections.Where(n => n.Depth == depth).OrderBy(n => n.Id).AsAsyncEnumerable();
+ }
+
+ ///
+ /// Determine how deep the tree is.
+ ///
+ public static async ValueTask GetDepth(DataContext db)
+ {
+ if(!await db.Sections.AnyAsync())
+ {
+ return 0;
+ }
+
+ return await db.Sections.MaxAsync(s => s.Depth);
+ }
+
+ ///
+ /// Compute the new sections key and its depth, then store the section in the database.
+ ///
+ public static async Task AddSection(DataContext db, string text, string? parentKey)
+ {
+ // Remove any whitespaces from the section name, regardless of how many e.g. spaces the user typed:
+ var key = string.Join('_', text.Split(' ', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)).ToUpperInvariant();
+
+ // Check, if this key already exists:
+ if (await db.Sections.AnyAsync(n => n.DataKey == key))
+ {
+ var rng = new Random();
+ while (await db.Sections.AnyAsync(n => n.DataKey == key))
+ {
+ // Add a random number to the end of the key:
+ key += $"_{rng.Next(1, 10_000)}";
+ }
+ }
+
+ // In the case, when the user adds a section to the root, handle the insert differently:
+ if (string.IsNullOrEmpty(parentKey))
+ {
+ var rootSection = new Section
+ {
+ Depth = 0,
+ DataKey = key,
+ Parent = null,
+ Name = text.Trim(),
+ TextElements = new(),
+ };
+
+ db.Sections.Add(rootSection);
+ await db.SaveChangesAsync();
+ return rootSection;
+ }
+
+ // Read the parent from the database:
+ var parent = await db.Sections.FirstOrDefaultAsync(n => n.DataKey == parentKey);
+ if (parent is null)
+ throw new ArgumentException($"The section's parent with key {parentKey} does not exist in the database.");
+
+ // Add the new section to the database:
+ var section = new Section
+ {
+ Name = text.Trim(),
+ DataKey = key,
+ Parent = parent,
+ TextElements = new(),
+ Depth = parent.Depth + 1,
+ };
+
+ db.Sections.Add(section);
+ await db.SaveChangesAsync();
+ return section;
+ }
+
+ public static async Task RemoveSection(DataContext db, string selectedKey)
+ {
+ // Remove the section from the database:
+ var section2Delete = await db.Sections.FirstOrDefaultAsync(n => n.DataKey == selectedKey);
+ if (section2Delete is null)
+ throw new ArgumentException($"The section with key {selectedKey} does not exist in the database.");
+
+ // Next, remove all children of the section, and the children's children, etc.:
+ var children = await db.Sections.Where(n => n.Parent == section2Delete).ToListAsync();
+ foreach (var child in children)
+ await RemoveSection(db, child.DataKey);
+
+ db.Sections.Remove(section2Delete);
+ await db.SaveChangesAsync();
+ }
+
+ public static async Task NumberChildren(DataContext db, string selectedKey)
+ {
+ // Read the section from the database:
+ var section = await db.Sections.FirstOrDefaultAsync(n => n.DataKey == selectedKey);
+ if (section is null)
+ throw new ArgumentException($"The section with key {selectedKey} does not exist in the database.");
+
+ return await db.Sections.CountAsync(n => n.Parent == section);
+ }
+}
\ 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 ad55a5f..052fe29 100644
--- a/I18N Commander/UI WinForms/Components/LoaderStart.cs
+++ b/I18N Commander/UI WinForms/Components/LoaderStart.cs
@@ -13,10 +13,10 @@ public partial class LoaderStart : UserControl
this.InitializeComponent();
}
- private static RegistryKey I18NCommanderKey => Registry.CurrentUser.OpenSubKey("Software", RegistryKeyPermissionCheck.ReadWriteSubTree)!.CreateSubKey("I18N Commander", RegistryKeyPermissionCheck.ReadWriteSubTree);
-
#region Recent Projects
+ private static RegistryKey I18NCommanderKey => Registry.CurrentUser.OpenSubKey("Software", RegistryKeyPermissionCheck.ReadWriteSubTree)!.CreateSubKey("I18N Commander", RegistryKeyPermissionCheck.ReadWriteSubTree);
+
///
/// Gets or sets the list of recent projects without any extras e.g. it does not prune the list of projects.
///
@@ -43,7 +43,15 @@ public partial class LoaderStart : UserControl
private static void UpdateRecentProjectsWithPruning(string latestUsedProjectPath)
{
var previousRecentList = LoaderStart.RecentProjects;
+
+ // Check, if the latest project is identical to the next project we open. In that case, we don't add it to the list.
+ if (previousRecentList.Any() && previousRecentList[0] == latestUsedProjectPath)
+ return;
+
+ // Add the next project to the list:
previousRecentList.Insert(0, latestUsedProjectPath);
+
+ // Prune the list:
if (previousRecentList.Count > 6)
previousRecentList = previousRecentList.Take(6).ToList();
@@ -67,7 +75,12 @@ public partial class LoaderStart : UserControl
foreach (var recentProject in recentProjects)
{
var fileInfo = new FileInfo(recentProject);
- var item = this.contextMenuRecentProjects.Items.Add($"{fileInfo.Directory.GetDirectories().Last()}/{fileInfo.Name}", Resources.Icons.icons8_document_512, (innerSender, args) => this.OpenRecentProject(innerSender));
+
+ // 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;
}
@@ -97,6 +110,12 @@ public partial class LoaderStart : UserControl
return;
var destinationFilePath = saveDialog.FileName;
+
+ // When the user chose an existing file, we delete it:
+ // (note: the user already accepts overwriting the file)
+ if (File.Exists(destinationFilePath))
+ File.Delete(destinationFilePath);
+
LoaderStart.UpdateRecentProjectsWithPruning(destinationFilePath);
this.OpenProject(destinationFilePath);
}
diff --git a/I18N Commander/UI WinForms/Components/Main.Designer.cs b/I18N Commander/UI WinForms/Components/Main.Designer.cs
new file mode 100644
index 0000000..6e3aef1
--- /dev/null
+++ b/I18N Commander/UI WinForms/Components/Main.Designer.cs
@@ -0,0 +1,114 @@
+namespace UI_WinForms.Components
+{
+ partial class Main
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Component Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ this.tableLayout = new System.Windows.Forms.TableLayoutPanel();
+ this.flowLayoutBottom = new System.Windows.Forms.FlowLayoutPanel();
+ this.splitContainer = new System.Windows.Forms.SplitContainer();
+ this.sectionTree = new UI_WinForms.Components.SectionTree();
+ this.tableLayout.SuspendLayout();
+ ((System.ComponentModel.ISupportInitialize)(this.splitContainer)).BeginInit();
+ this.splitContainer.Panel1.SuspendLayout();
+ this.splitContainer.SuspendLayout();
+ this.SuspendLayout();
+ //
+ // tableLayout
+ //
+ this.tableLayout.ColumnCount = 1;
+ this.tableLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
+ this.tableLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20F));
+ this.tableLayout.Controls.Add(this.flowLayoutBottom, 0, 1);
+ this.tableLayout.Controls.Add(this.splitContainer, 0, 0);
+ this.tableLayout.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.tableLayout.Location = new System.Drawing.Point(0, 0);
+ this.tableLayout.Name = "tableLayout";
+ this.tableLayout.RowCount = 2;
+ this.tableLayout.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
+ this.tableLayout.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 66F));
+ this.tableLayout.Size = new System.Drawing.Size(965, 603);
+ this.tableLayout.TabIndex = 0;
+ //
+ // flowLayoutBottom
+ //
+ this.flowLayoutBottom.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.flowLayoutBottom.Location = new System.Drawing.Point(0, 537);
+ this.flowLayoutBottom.Margin = new System.Windows.Forms.Padding(0);
+ this.flowLayoutBottom.Name = "flowLayoutBottom";
+ this.flowLayoutBottom.Size = new System.Drawing.Size(965, 66);
+ this.flowLayoutBottom.TabIndex = 0;
+ //
+ // splitContainer
+ //
+ this.splitContainer.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
+ this.splitContainer.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.splitContainer.FixedPanel = System.Windows.Forms.FixedPanel.Panel1;
+ this.splitContainer.Location = new System.Drawing.Point(3, 3);
+ this.splitContainer.Name = "splitContainer";
+ //
+ // splitContainer.Panel1
+ //
+ this.splitContainer.Panel1.Controls.Add(this.sectionTree);
+ this.splitContainer.Panel1MinSize = 300;
+ this.splitContainer.Size = new System.Drawing.Size(959, 531);
+ this.splitContainer.SplitterDistance = 319;
+ this.splitContainer.TabIndex = 1;
+ //
+ // sectionTree
+ //
+ this.sectionTree.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.sectionTree.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
+ this.sectionTree.Location = new System.Drawing.Point(0, 0);
+ this.sectionTree.Name = "sectionTree";
+ this.sectionTree.Size = new System.Drawing.Size(317, 529);
+ this.sectionTree.TabIndex = 0;
+ //
+ // Main
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(120F, 120F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
+ this.Controls.Add(this.tableLayout);
+ this.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
+ this.Name = "Main";
+ this.Size = new System.Drawing.Size(965, 603);
+ this.tableLayout.ResumeLayout(false);
+ this.splitContainer.Panel1.ResumeLayout(false);
+ ((System.ComponentModel.ISupportInitialize)(this.splitContainer)).EndInit();
+ this.splitContainer.ResumeLayout(false);
+ this.ResumeLayout(false);
+
+ }
+
+ #endregion
+
+ private TableLayoutPanel tableLayout;
+ private FlowLayoutPanel flowLayoutBottom;
+ private SplitContainer splitContainer;
+ private SectionTree sectionTree;
+ }
+}
diff --git a/I18N Commander/UI WinForms/Components/Main.cs b/I18N Commander/UI WinForms/Components/Main.cs
new file mode 100644
index 0000000..a25e38f
--- /dev/null
+++ b/I18N Commander/UI WinForms/Components/Main.cs
@@ -0,0 +1,9 @@
+namespace UI_WinForms.Components;
+
+public partial class Main : UserControl
+{
+ public Main()
+ {
+ this.InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/I18N Commander/UI WinForms/Components/Main.resx b/I18N Commander/UI WinForms/Components/Main.resx
new file mode 100644
index 0000000..b5ae26c
--- /dev/null
+++ b/I18N Commander/UI WinForms/Components/Main.resx
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/I18N Commander/UI WinForms/Components/SectionTree.Designer.cs b/I18N Commander/UI WinForms/Components/SectionTree.Designer.cs
new file mode 100644
index 0000000..9cf2bb7
--- /dev/null
+++ b/I18N Commander/UI WinForms/Components/SectionTree.Designer.cs
@@ -0,0 +1,131 @@
+namespace UI_WinForms.Components
+{
+ partial class SectionTree
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Component Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ this.tableLayout = new System.Windows.Forms.TableLayoutPanel();
+ this.flowLayoutBottom = new System.Windows.Forms.FlowLayoutPanel();
+ this.buttonAdd = new System.Windows.Forms.Button();
+ this.buttonRemove = new System.Windows.Forms.Button();
+ this.treeView = new System.Windows.Forms.TreeView();
+ this.tableLayout.SuspendLayout();
+ this.flowLayoutBottom.SuspendLayout();
+ this.SuspendLayout();
+ //
+ // tableLayout
+ //
+ this.tableLayout.ColumnCount = 1;
+ this.tableLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
+ this.tableLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20F));
+ this.tableLayout.Controls.Add(this.flowLayoutBottom, 0, 1);
+ this.tableLayout.Controls.Add(this.treeView, 0, 0);
+ this.tableLayout.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.tableLayout.Location = new System.Drawing.Point(0, 0);
+ this.tableLayout.Name = "tableLayout";
+ this.tableLayout.RowCount = 2;
+ this.tableLayout.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
+ this.tableLayout.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 66F));
+ this.tableLayout.Size = new System.Drawing.Size(296, 511);
+ this.tableLayout.TabIndex = 0;
+ //
+ // flowLayoutBottom
+ //
+ this.flowLayoutBottom.Controls.Add(this.buttonAdd);
+ this.flowLayoutBottom.Controls.Add(this.buttonRemove);
+ this.flowLayoutBottom.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.flowLayoutBottom.Location = new System.Drawing.Point(0, 445);
+ this.flowLayoutBottom.Margin = new System.Windows.Forms.Padding(0);
+ this.flowLayoutBottom.Name = "flowLayoutBottom";
+ this.flowLayoutBottom.Size = new System.Drawing.Size(296, 66);
+ this.flowLayoutBottom.TabIndex = 0;
+ //
+ // buttonAdd
+ //
+ this.buttonAdd.AutoSize = true;
+ this.buttonAdd.Image = global::UI_WinForms.Resources.Icons.icons8_add_folder_512;
+ this.buttonAdd.ImageAlign = System.Drawing.ContentAlignment.MiddleLeft;
+ this.buttonAdd.Location = new System.Drawing.Point(3, 3);
+ this.buttonAdd.Name = "buttonAdd";
+ this.buttonAdd.Size = new System.Drawing.Size(138, 60);
+ this.buttonAdd.TabIndex = 0;
+ this.buttonAdd.Text = "Add";
+ this.buttonAdd.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText;
+ this.buttonAdd.UseVisualStyleBackColor = true;
+ this.buttonAdd.Click += new System.EventHandler(this.buttonAdd_Click);
+ //
+ // buttonRemove
+ //
+ this.buttonRemove.AutoSize = true;
+ this.buttonRemove.Enabled = false;
+ this.buttonRemove.Image = global::UI_WinForms.Resources.Icons.icons8_delete_folder_512;
+ this.buttonRemove.ImageAlign = System.Drawing.ContentAlignment.MiddleLeft;
+ this.buttonRemove.Location = new System.Drawing.Point(147, 3);
+ this.buttonRemove.Name = "buttonRemove";
+ this.buttonRemove.Size = new System.Drawing.Size(138, 60);
+ this.buttonRemove.TabIndex = 1;
+ this.buttonRemove.Text = "Remove";
+ this.buttonRemove.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText;
+ this.buttonRemove.UseVisualStyleBackColor = true;
+ this.buttonRemove.Click += new System.EventHandler(this.buttonRemove_Click);
+ //
+ // treeView
+ //
+ this.treeView.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
+ this.treeView.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.treeView.HideSelection = false;
+ this.treeView.ItemHeight = 50;
+ this.treeView.Location = new System.Drawing.Point(3, 3);
+ this.treeView.Name = "treeView";
+ this.treeView.Size = new System.Drawing.Size(290, 439);
+ this.treeView.TabIndex = 1;
+ this.treeView.NodeMouseClick += new System.Windows.Forms.TreeNodeMouseClickEventHandler(this.treeView_NodeMouseClick);
+ //
+ // SectionTree
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(120F, 120F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
+ this.Controls.Add(this.tableLayout);
+ this.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
+ this.Name = "SectionTree";
+ this.Size = new System.Drawing.Size(296, 511);
+ this.tableLayout.ResumeLayout(false);
+ this.flowLayoutBottom.ResumeLayout(false);
+ this.flowLayoutBottom.PerformLayout();
+ this.ResumeLayout(false);
+
+ }
+
+ #endregion
+
+ private TableLayoutPanel tableLayout;
+ private FlowLayoutPanel flowLayoutBottom;
+ private Button buttonAdd;
+ private Button buttonRemove;
+ private TreeView treeView;
+ }
+}
diff --git a/I18N Commander/UI WinForms/Components/SectionTree.cs b/I18N Commander/UI WinForms/Components/SectionTree.cs
new file mode 100644
index 0000000..1ecf74a
--- /dev/null
+++ b/I18N Commander/UI WinForms/Components/SectionTree.cs
@@ -0,0 +1,184 @@
+using DataModel.Database.Common;
+using Microsoft.Extensions.DependencyInjection;
+using Processor;
+using UI_WinForms.Dialogs;
+using UI_WinForms.Resources;
+
+namespace UI_WinForms.Components;
+
+public partial class SectionTree : UserControl
+{
+ private readonly DataContext db;
+
+ public SectionTree()
+ {
+ this.InitializeComponent();
+
+ // Get the DI context from the main form:
+ this.db = Program.SERVICE_PROVIDER.GetService()!;
+
+ // Dispose of the context when the control is disposed:
+ this.Disposed += (_, _) => this.db.Dispose();
+
+ // Create an image list from a resource:
+ var imgList = new ImageList();
+ imgList.ImageSize = new Size(45, 45);
+ imgList.ColorDepth = ColorDepth.Depth32Bit;
+ imgList.Images.Add(Icons.icons8_documents_folder_512);
+
+ // Set the image list to the tree view:
+ this.treeView.ImageList = imgList;
+
+ // Subscribe to the load event:
+ this.Load += this.LoadNodes;
+ }
+
+ private async void LoadNodes(object? sender, EventArgs e)
+ {
+ // A dictionary to cache all known tree nodes:
+ var treeNodes = new Dictionary();
+
+ // Get the max. depth of the tree:
+ var maxDepth = await SectionProcessor.GetDepth(this.db);
+
+ // Store nodes, where we cannot find the parents:
+ var missingParents = new List();
+
+ // Populate the tree view out of the database, layer by layer:
+ for (var i = 0; i <= maxDepth; i++)
+ {
+ await foreach (var section in SectionProcessor.LoadLayer(this.db, i))
+ {
+ // Create the tree node:
+ var node = new TreeNode
+ {
+ Name = section.DataKey, // [sic] name is the key
+ Text = section.Name,
+ StateImageIndex = 1,
+ };
+
+ // Cache the node:
+ treeNodes.Add(section.DataKey, node);
+
+ // Is this a root node?
+ if (section.Depth is 0)
+
+ // Set the root node:
+ this.treeView.Nodes.Add(node);
+
+ // Otherwise, attach this section to its parent node:
+ else
+ {
+ // Get the parent from our cache within O(1):
+ treeNodes.TryGetValue(section.Parent?.DataKey ?? string.Empty, out var parent);
+
+ // If the parent node is not found, skip this section:
+ if (parent is null)
+ {
+ missingParents.Add(node);
+ continue;
+ }
+
+ // Add the node to the parent:
+ parent.Nodes.Add(node);
+ }
+ }
+ }
+
+ // If we found any missing parents, show a dialog:
+ if (missingParents.Any())
+ {
+ MessageBox.Show($"In {missingParents.Count} case(s) we could not found the matching parent. We added these nodes to a special root node, though.", "Parent not found", MessageBoxButtons.OK, MessageBoxIcon.Error);
+
+ // Create a root node for all missing parents:
+ var rootMissedParents = new TreeNode
+ {
+ Name = "MISSING_PARENTS",
+ Text = "Missing Parents",
+ StateImageIndex = 1,
+ };
+
+ // Add the root node to the tree:
+ this.treeView.Nodes.Add(rootMissedParents);
+
+ // Add all missing parents to the root node:
+ foreach (var node in missingParents)
+ rootMissedParents.Nodes.Add(node);
+ }
+
+ // Expand the tree:
+ this.treeView.ExpandAll();
+ }
+
+ private async void buttonAdd_Click(object sender, EventArgs e)
+ {
+ var result = InputDialog.Show(new InputDialog.Options(
+ Message: "Please type the desired section name.",
+ Title: "Add a section",
+ Placeholder: "My next section",
+ ShowQuestionCheckbox: true,
+ QuestionCheckboxText: "Add a root node (i.e. ignoring the selected node)"
+ ));
+
+ if(result.DialogResult == DialogResult.Cancel)
+ return;
+
+ var addRootNode = result.AnswerToQuestion;
+
+ // Get the currently selected section as parent:
+ var selectedNode = this.treeView.SelectedNode;
+
+ // Add the new section to the database:
+ var addedSection = await SectionProcessor.AddSection(this.db, result.Text, addRootNode ? null : selectedNode?.Name);
+
+ // Add the new section to the tree control:
+ var node = new TreeNode
+ {
+ Name = addedSection.DataKey, // [sic] name is the key
+ Text = addedSection.Name,
+ StateImageIndex = 1,
+ };
+
+ if(!addRootNode && selectedNode is not null)
+ selectedNode.Nodes.Add(node);
+ else
+ this.treeView.Nodes.Add(node);
+
+ // Ensure, that the added node is visible and gets the focus:
+ node.EnsureVisible();
+ this.treeView.SelectedNode = node;
+ }
+
+ private async void buttonRemove_Click(object sender, EventArgs e)
+ {
+ // Get the currently selected section, which will be removed:
+ var selectedNode = this.treeView.SelectedNode;
+
+ // Get the number of children:
+ // (notice, that the node's name is its key)
+ var numberChildren = await SectionProcessor.NumberChildren(this.db, selectedNode.Name);
+
+ // Ask the user, if he really wants to remove the section:
+ if(MessageBox.Show(numberChildren > 0 ? $"Are you sure, you want to remove the section '{selectedNode.Text}', its {numberChildren} children and so on?" : $"Are you sure, you want to remove the section '{selectedNode.Text}'?", "Remove section", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No)
+ return;
+
+ // Remove the section from the database:
+ // (notice, that the node's name is its key)
+ await SectionProcessor.RemoveSection(this.db, selectedNode.Name);
+
+ // Remove all nodes from the tree control:
+ this.treeView.Nodes.Clear();
+
+ // Reload the tree:
+ this.LoadNodes(this, EventArgs.Empty);
+ }
+
+ private void treeView_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)
+ {
+ // Get the currently selected section:
+ var selectedNode = this.treeView.SelectedNode;
+
+ // If the selected node is not null, enable the remove button:
+ this.buttonRemove.Enabled = selectedNode is not null;
+ }
+}
\ No newline at end of file
diff --git a/I18N Commander/UI WinForms/Components/SectionTree.resx b/I18N Commander/UI WinForms/Components/SectionTree.resx
new file mode 100644
index 0000000..b5ae26c
--- /dev/null
+++ b/I18N Commander/UI WinForms/Components/SectionTree.resx
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/I18N Commander/UI WinForms/Dialogs/InputDialog.Designer.cs b/I18N Commander/UI WinForms/Dialogs/InputDialog.Designer.cs
new file mode 100644
index 0000000..7426653
--- /dev/null
+++ b/I18N Commander/UI WinForms/Dialogs/InputDialog.Designer.cs
@@ -0,0 +1,165 @@
+namespace UI_WinForms.Dialogs
+{
+ partial class InputDialog
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ this.tableLayout = new System.Windows.Forms.TableLayoutPanel();
+ this.labelHead = new System.Windows.Forms.Label();
+ this.flowLayoutBottom = new System.Windows.Forms.FlowLayoutPanel();
+ this.buttonOk = new System.Windows.Forms.Button();
+ this.buttonCancel = new System.Windows.Forms.Button();
+ this.textBoxInput = new System.Windows.Forms.TextBox();
+ this.checkBoxQuestion = new System.Windows.Forms.CheckBox();
+ this.tableLayout.SuspendLayout();
+ this.flowLayoutBottom.SuspendLayout();
+ this.SuspendLayout();
+ //
+ // tableLayout
+ //
+ this.tableLayout.AutoSize = true;
+ this.tableLayout.ColumnCount = 1;
+ this.tableLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
+ this.tableLayout.Controls.Add(this.labelHead, 0, 0);
+ this.tableLayout.Controls.Add(this.flowLayoutBottom, 0, 2);
+ this.tableLayout.Controls.Add(this.textBoxInput, 0, 1);
+ this.tableLayout.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.tableLayout.Location = new System.Drawing.Point(0, 0);
+ this.tableLayout.Name = "tableLayout";
+ this.tableLayout.RowCount = 4;
+ this.tableLayout.RowStyles.Add(new System.Windows.Forms.RowStyle());
+ this.tableLayout.RowStyles.Add(new System.Windows.Forms.RowStyle());
+ this.tableLayout.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 66F));
+ this.tableLayout.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
+ this.tableLayout.Size = new System.Drawing.Size(800, 139);
+ this.tableLayout.TabIndex = 0;
+ //
+ // labelHead
+ //
+ this.labelHead.AutoSize = true;
+ this.labelHead.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.labelHead.Location = new System.Drawing.Point(3, 0);
+ this.labelHead.Name = "labelHead";
+ this.labelHead.Size = new System.Drawing.Size(794, 28);
+ this.labelHead.TabIndex = 0;
+ this.labelHead.Text = "header";
+ //
+ // flowLayoutBottom
+ //
+ this.flowLayoutBottom.Controls.Add(this.buttonOk);
+ this.flowLayoutBottom.Controls.Add(this.buttonCancel);
+ this.flowLayoutBottom.Controls.Add(this.checkBoxQuestion);
+ this.flowLayoutBottom.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.flowLayoutBottom.Location = new System.Drawing.Point(3, 68);
+ this.flowLayoutBottom.Margin = new System.Windows.Forms.Padding(3, 0, 0, 0);
+ this.flowLayoutBottom.Name = "flowLayoutBottom";
+ this.flowLayoutBottom.Size = new System.Drawing.Size(797, 66);
+ this.flowLayoutBottom.TabIndex = 0;
+ //
+ // buttonOk
+ //
+ this.buttonOk.AutoSize = true;
+ this.buttonOk.Image = global::UI_WinForms.Resources.Icons.icons8_ok_512;
+ this.buttonOk.ImageAlign = System.Drawing.ContentAlignment.MiddleLeft;
+ this.buttonOk.Location = new System.Drawing.Point(3, 3);
+ this.buttonOk.Name = "buttonOk";
+ this.buttonOk.Size = new System.Drawing.Size(114, 60);
+ this.buttonOk.TabIndex = 1;
+ this.buttonOk.Text = "Ok";
+ this.buttonOk.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText;
+ this.buttonOk.UseVisualStyleBackColor = true;
+ this.buttonOk.Click += new System.EventHandler(this.buttonOk_Click);
+ //
+ // buttonCancel
+ //
+ this.buttonCancel.AutoSize = true;
+ this.buttonCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
+ this.buttonCancel.Image = global::UI_WinForms.Resources.Icons.icons8_cancel_512;
+ this.buttonCancel.ImageAlign = System.Drawing.ContentAlignment.MiddleLeft;
+ this.buttonCancel.Location = new System.Drawing.Point(123, 3);
+ this.buttonCancel.Name = "buttonCancel";
+ this.buttonCancel.Size = new System.Drawing.Size(124, 60);
+ this.buttonCancel.TabIndex = 2;
+ this.buttonCancel.Text = "Cancel";
+ this.buttonCancel.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText;
+ this.buttonCancel.UseVisualStyleBackColor = true;
+ //
+ // textBoxInput
+ //
+ this.textBoxInput.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.textBoxInput.Location = new System.Drawing.Point(6, 31);
+ this.textBoxInput.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
+ this.textBoxInput.Name = "textBoxInput";
+ this.textBoxInput.Size = new System.Drawing.Size(791, 34);
+ this.textBoxInput.TabIndex = 1;
+ this.textBoxInput.KeyUp += new System.Windows.Forms.KeyEventHandler(this.textBoxInput_KeyUp);
+ //
+ // checkBoxIsRoot
+ //
+ this.checkBoxQuestion.AutoSize = true;
+ this.checkBoxQuestion.Dock = System.Windows.Forms.DockStyle.Left;
+ this.checkBoxQuestion.Location = new System.Drawing.Point(253, 3);
+ this.checkBoxQuestion.Name = "checkBoxQuestion";
+ this.checkBoxQuestion.Size = new System.Drawing.Size(458, 60);
+ this.checkBoxQuestion.TabIndex = 3;
+ this.checkBoxQuestion.UseVisualStyleBackColor = true;
+ //
+ // InputDialog
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(120F, 120F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
+ this.AutoSize = true;
+ this.CancelButton = this.buttonCancel;
+ this.ClientSize = new System.Drawing.Size(800, 139);
+ this.Controls.Add(this.tableLayout);
+ this.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
+ this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
+ this.MaximizeBox = false;
+ this.MinimizeBox = false;
+ this.Name = "InputDialog";
+ this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
+ this.Text = "InputDialog";
+ this.tableLayout.ResumeLayout(false);
+ this.tableLayout.PerformLayout();
+ this.flowLayoutBottom.ResumeLayout(false);
+ this.flowLayoutBottom.PerformLayout();
+ this.ResumeLayout(false);
+ this.PerformLayout();
+
+ }
+
+ #endregion
+
+ private TableLayoutPanel tableLayout;
+ private Label labelHead;
+ private FlowLayoutPanel flowLayoutBottom;
+ private TextBox textBoxInput;
+ private Button buttonOk;
+ private Button buttonCancel;
+ private CheckBox checkBoxQuestion;
+ }
+}
\ No newline at end of file
diff --git a/I18N Commander/UI WinForms/Dialogs/InputDialog.cs b/I18N Commander/UI WinForms/Dialogs/InputDialog.cs
new file mode 100644
index 0000000..25afee0
--- /dev/null
+++ b/I18N Commander/UI WinForms/Dialogs/InputDialog.cs
@@ -0,0 +1,59 @@
+namespace UI_WinForms.Dialogs;
+
+public partial class InputDialog : Form
+{
+ public readonly record struct Options(
+ string Message,
+ string Title,
+ string Placeholder = "",
+ string PreloadedText = "",
+ string OkButtonText = "Ok",
+ string CancelButtonText = "Cancel",
+ bool ShowQuestionCheckbox = false,
+ string QuestionCheckboxText = ""
+ );
+
+ private InputDialog()
+ {
+ this.InitializeComponent();
+ }
+
+ public static InputResult Show(Options options)
+ {
+ using var inputDialog = new InputDialog();
+ inputDialog.labelHead.Text = options.Message;
+ inputDialog.Text = options.Title;
+ inputDialog.textBoxInput.PlaceholderText = options.Placeholder;
+ inputDialog.textBoxInput.Text = options.PreloadedText;
+ inputDialog.buttonOk.Text = options.OkButtonText;
+ inputDialog.buttonCancel.Text = options.CancelButtonText;
+ inputDialog.checkBoxQuestion.Visible = options.ShowQuestionCheckbox;
+ inputDialog.checkBoxQuestion.Text = options.QuestionCheckboxText;
+
+ // Ensure, that the text box is focused on load:
+ inputDialog.textBoxInput.Select();
+
+ return new InputResult(
+ inputDialog.ShowDialog(),
+ inputDialog.textBoxInput.Text,
+ inputDialog.checkBoxQuestion.Checked
+ );
+ }
+
+ private void textBoxInput_KeyUp(object sender, KeyEventArgs e)
+ {
+ if (e.KeyCode is Keys.Enter or Keys.Return)
+ this.buttonOk.PerformClick();
+ }
+
+ private void buttonOk_Click(object sender, EventArgs e)
+ {
+ if (!string.IsNullOrWhiteSpace(this.textBoxInput.Text))
+ {
+ this.DialogResult = DialogResult.OK;
+ this.Close();
+ }
+ }
+
+ public readonly record struct InputResult(DialogResult DialogResult, string Text, bool AnswerToQuestion);
+}
\ No newline at end of file
diff --git a/I18N Commander/UI WinForms/Dialogs/InputDialog.resx b/I18N Commander/UI WinForms/Dialogs/InputDialog.resx
new file mode 100644
index 0000000..b5ae26c
--- /dev/null
+++ b/I18N Commander/UI WinForms/Dialogs/InputDialog.resx
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/I18N Commander/UI WinForms/Loader.cs b/I18N Commander/UI WinForms/Loader.cs
index b18c284..38065c7 100644
--- a/I18N Commander/UI WinForms/Loader.cs
+++ b/I18N Commander/UI WinForms/Loader.cs
@@ -2,6 +2,8 @@
public partial class Loader : Form
{
+ public string DataFile { get; set; } = string.Empty;
+
public Loader()
{
this.InitializeComponent();
@@ -9,7 +11,8 @@ public partial class Loader : Form
private void loaderStart_LoadProject(object sender, string projectFilePath)
{
- // TODO: Call the data model
+ this.DataFile = projectFilePath;
+ this.DialogResult = DialogResult.OK;
this.Close();
}
}
\ No newline at end of file
diff --git a/I18N Commander/UI WinForms/Main.Designer.cs b/I18N Commander/UI WinForms/Main.Designer.cs
index f8b002f..b15a91a 100644
--- a/I18N Commander/UI WinForms/Main.Designer.cs
+++ b/I18N Commander/UI WinForms/Main.Designer.cs
@@ -28,13 +28,24 @@
///
private void InitializeComponent()
{
+ this.mainComponent = new UI_WinForms.Components.Main();
this.SuspendLayout();
//
+ // mainComponent
+ //
+ this.mainComponent.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.mainComponent.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
+ this.mainComponent.Location = new System.Drawing.Point(0, 0);
+ this.mainComponent.Name = "mainComponent";
+ this.mainComponent.Size = new System.Drawing.Size(1071, 755);
+ this.mainComponent.TabIndex = 0;
+ //
// Main
//
this.AutoScaleDimensions = new System.Drawing.SizeF(120F, 120F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
- this.ClientSize = new System.Drawing.Size(800, 450);
+ this.ClientSize = new System.Drawing.Size(1071, 755);
+ this.Controls.Add(this.mainComponent);
this.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.Name = "Main";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
@@ -44,5 +55,7 @@
}
#endregion
+
+ private Components.Main mainComponent;
}
}
\ No newline at end of file
diff --git a/I18N Commander/UI WinForms/Program.cs b/I18N Commander/UI WinForms/Program.cs
index 50c87bb..5c21401 100644
--- a/I18N Commander/UI WinForms/Program.cs
+++ b/I18N Commander/UI WinForms/Program.cs
@@ -1,17 +1,64 @@
+using DataModel;
+using DataModel.Database.Common;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
namespace UI_WinForms;
internal static class Program
{
+ internal const string VERSION = "v0.1.0";
+ internal static IServiceProvider SERVICE_PROVIDER = null!;
+
[STAThread]
private static void Main()
{
ApplicationConfiguration.Initialize();
-
+ Application.EnableVisualStyles();
+
// Start the loader screen:
var loader = new Loader();
Application.Run(loader);
- // Start the main app:
- Application.Run(new Main());
+ // Check, if the user closes the loader screen:
+ if (loader.DialogResult != DialogResult.OK)
+ return;
+
+ //
+ // Create the DI system
+ //
+ var builder = new HostBuilder();
+
+ //
+ // Add services
+ //
+ builder.ConfigureServices((hostContext, serviceCollection) =>
+ {
+ // The main form:
+ serviceCollection.AddSingleton();
+
+ // The database:
+ serviceCollection.AddDatabase(loader.DataFile, true);
+ });
+
+ // Get the host out of the DI system:
+ var host = builder.Build();
+
+ // Create a service scope:
+ using (var scope = host.Services.CreateScope())
+ {
+ // Get a service provider:
+ SERVICE_PROVIDER = scope.ServiceProvider;
+
+ // Apply database migrations:
+ using (var database = SERVICE_PROVIDER.GetRequiredService())
+ Setup.PerformDataMigration(database).Wait();
+
+ // Create the main window:
+ var mainWindow = SERVICE_PROVIDER.GetService();
+
+ // Start the app:
+ Application.Run(mainWindow);
+ }
}
}
\ No newline at end of file
diff --git a/I18N Commander/UI WinForms/Resources/Icons.Designer.cs b/I18N Commander/UI WinForms/Resources/Icons.Designer.cs
index 32a2e64..8d0147b 100644
--- a/I18N Commander/UI WinForms/Resources/Icons.Designer.cs
+++ b/I18N Commander/UI WinForms/Resources/Icons.Designer.cs
@@ -60,6 +60,16 @@ namespace UI_WinForms.Resources {
}
}
+ ///
+ /// Looks up a localized resource of type System.Drawing.Bitmap.
+ ///
+ internal static System.Drawing.Bitmap icons8_add_folder_512 {
+ get {
+ object obj = ResourceManager.GetObject("icons8_add_folder_512", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+
///
/// Looks up a localized resource of type System.Drawing.Bitmap.
///
@@ -70,6 +80,26 @@ namespace UI_WinForms.Resources {
}
}
+ ///
+ /// Looks up a localized resource of type System.Drawing.Bitmap.
+ ///
+ internal static System.Drawing.Bitmap icons8_cancel_512 {
+ get {
+ object obj = ResourceManager.GetObject("icons8_cancel_512", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Drawing.Bitmap.
+ ///
+ internal static System.Drawing.Bitmap icons8_delete_folder_512 {
+ get {
+ object obj = ResourceManager.GetObject("icons8_delete_folder_512", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+
///
/// Looks up a localized resource of type System.Drawing.Bitmap.
///
@@ -80,6 +110,16 @@ namespace UI_WinForms.Resources {
}
}
+ ///
+ /// Looks up a localized resource of type System.Drawing.Bitmap.
+ ///
+ internal static System.Drawing.Bitmap icons8_documents_folder_512 {
+ get {
+ object obj = ResourceManager.GetObject("icons8_documents_folder_512", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+
///
/// Looks up a localized resource of type System.Drawing.Bitmap.
///
@@ -100,6 +140,16 @@ namespace UI_WinForms.Resources {
}
}
+ ///
+ /// Looks up a localized resource of type System.Drawing.Bitmap.
+ ///
+ internal static System.Drawing.Bitmap icons8_ok_512 {
+ get {
+ object obj = ResourceManager.GetObject("icons8_ok_512", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+
///
/// Looks up a localized resource of type System.Drawing.Bitmap.
///
diff --git a/I18N Commander/UI WinForms/Resources/Icons.resx b/I18N Commander/UI WinForms/Resources/Icons.resx
index 17944cf..4fb8808 100644
--- a/I18N Commander/UI WinForms/Resources/Icons.resx
+++ b/I18N Commander/UI WinForms/Resources/Icons.resx
@@ -118,9 +118,21 @@
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+ icons8-add-folder-512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
icons8-browse-folder-512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+ icons8-cancel-512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+
+ icons8-delete-folder-512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+
+ icons8-documents-folder-512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
icons8-document-512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
@@ -130,6 +142,9 @@
icons8-new-window-512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+ icons8-ok-512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
icons8-open-file-under-cursor-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-add-folder-512.png b/I18N Commander/UI WinForms/Resources/icons8-add-folder-512.png
new file mode 100644
index 0000000..3a24a0e
Binary files /dev/null and b/I18N Commander/UI WinForms/Resources/icons8-add-folder-512.png differ
diff --git a/I18N Commander/UI WinForms/Resources/icons8-cancel-512.png b/I18N Commander/UI WinForms/Resources/icons8-cancel-512.png
new file mode 100644
index 0000000..df1ba17
Binary files /dev/null and b/I18N Commander/UI WinForms/Resources/icons8-cancel-512.png differ
diff --git a/I18N Commander/UI WinForms/Resources/icons8-delete-folder-512.png b/I18N Commander/UI WinForms/Resources/icons8-delete-folder-512.png
new file mode 100644
index 0000000..6457dc5
Binary files /dev/null and b/I18N Commander/UI WinForms/Resources/icons8-delete-folder-512.png differ
diff --git a/I18N Commander/UI WinForms/Resources/icons8-documents-folder-512.png b/I18N Commander/UI WinForms/Resources/icons8-documents-folder-512.png
new file mode 100644
index 0000000..d6eebf0
Binary files /dev/null and b/I18N Commander/UI WinForms/Resources/icons8-documents-folder-512.png differ
diff --git a/I18N Commander/UI WinForms/Resources/icons8-ok-512.png b/I18N Commander/UI WinForms/Resources/icons8-ok-512.png
new file mode 100644
index 0000000..91a829f
Binary files /dev/null and b/I18N Commander/UI WinForms/Resources/icons8-ok-512.png differ
diff --git a/project for migrations.i18nc b/project for migrations.i18nc
deleted file mode 100644
index fce11e3..0000000
Binary files a/project for migrations.i18nc and /dev/null differ