Merge branch '10-component-section-tree' into 'main'

Resolve "Component: Section Tree"

Closes #10, #17, #18, #16, #19, #21, #22, #20, and #15

See merge request open-source/dotnet/i18n-commander!5
This commit is contained in:
Thorsten 2022-07-09 20:40:59 +00:00
commit 34fb693d6b
33 changed files with 1841 additions and 19 deletions

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="test.i18nc" uuid="85bb5f37-723c-416e-bc82-e7c22635d5b1">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/../../../test.i18nc</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

View File

@ -4,13 +4,13 @@ namespace DataModel.Database.Common;
public sealed class DataContext : DbContext
{
public DbSet<Setting>? Settings { get; set; }
public DbSet<Setting> Settings { get; set; }
public DbSet<Section>? Sections { get; set; }
public DbSet<Section> Sections { get; set; }
public DbSet<TextElement>? TextElements { get; set; }
public DbSet<TextElement> TextElements { get; set; }
public DbSet<Translation>? Translations { get; set; }
public DbSet<Translation> Translations { get; set; }
public DataContext(DbContextOptions<DataContext> contextOptions) : base(contextOptions)
{
@ -35,6 +35,8 @@ public sealed class DataContext : DbContext
modelBuilder.Entity<Section>().HasIndex(n => n.Id);
modelBuilder.Entity<Section>().HasIndex(n => n.Name);
modelBuilder.Entity<Section>().HasIndex(n => n.Depth);
modelBuilder.Entity<Section>().HasIndex(n => n.DataKey);
#endregion

View File

@ -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<TextElement> TextElements { get; set; }
public int Depth { get; set; } = 0;
public Section? Parent { get; set; }
public List<TextElement> TextElements { get; set; } = new();
}

View File

@ -0,0 +1,184 @@
// <auto-generated />
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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int?>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("BoolValue")
.HasColumnType("INTEGER");
b.Property<Guid>("GuidValue")
.HasColumnType("TEXT");
b.Property<int>("IntegerValue")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Code")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Culture")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Text")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("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
}
}
}

View File

@ -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<int>(
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<int>(
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);
}
}
}

View File

@ -0,0 +1,190 @@
// <auto-generated />
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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("DataKey")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int?>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("BoolValue")
.HasColumnType("INTEGER");
b.Property<Guid>("GuidValue")
.HasColumnType("TEXT");
b.Property<int>("IntegerValue")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Code")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Culture")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Text")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("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
}
}
}

View File

@ -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<string>(
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");
}
}
}

View File

@ -0,0 +1,195 @@
// <auto-generated />
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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("DataKey")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("Depth")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int?>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("BoolValue")
.HasColumnType("INTEGER");
b.Property<Guid>("GuidValue")
.HasColumnType("TEXT");
b.Property<int>("IntegerValue")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Code")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Culture")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Text")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("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
}
}
}

View File

@ -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<int>(
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");
}
}
}

View File

@ -23,15 +23,26 @@ namespace DataModel.Migrations
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("DataKey")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("Depth")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("ParentId")
b.Property<int?>("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");
});

View File

@ -30,7 +30,7 @@ public static class Setup
/// </summary>
public static void AddDatabase(this IServiceCollection serviceCollection, string path2DataFile, bool createWhenNecessary = true)
{
serviceCollection.AddDbContext<DataContext>(options => options.UseSqlite($"Filename={path2DataFile};Mode={(createWhenNecessary ? DB_READ_WRITE_CREATE_MODE : DB_READ_WRITE_MODE)}"));
serviceCollection.AddDbContext<DataContext>(options => options.UseSqlite($"Filename={path2DataFile};Mode={(createWhenNecessary ? DB_READ_WRITE_CREATE_MODE : DB_READ_WRITE_MODE)}"), ServiceLifetime.Transient);
}
/// <summary>

View File

@ -0,0 +1,111 @@
using DataModel.Database;
using DataModel.Database.Common;
using Microsoft.EntityFrameworkCore;
namespace Processor;
public static class SectionProcessor
{
/// <summary>
/// Load one layer of the tree by using the specified depth:
/// </summary>
public static IAsyncEnumerable<Section> LoadLayer(DataContext db, int depth)
{
return db.Sections.Where(n => n.Depth == depth).OrderBy(n => n.Id).AsAsyncEnumerable();
}
/// <summary>
/// Determine how deep the tree is.
/// </summary>
public static async ValueTask<int> GetDepth(DataContext db)
{
if(!await db.Sections.AnyAsync())
{
return 0;
}
return await db.Sections.MaxAsync(s => s.Depth);
}
/// <summary>
/// Compute the new sections key and its depth, then store the section in the database.
/// </summary>
public static async Task<Section> 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<int> 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);
}
}

View File

@ -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);
/// <summary>
/// Gets or sets the list of recent projects without any extras e.g. it does not prune the list of projects.
/// </summary>
@ -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);
}

View File

@ -0,0 +1,114 @@
namespace UI_WinForms.Components
{
partial class Main
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
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;
}
}

View File

@ -0,0 +1,9 @@
namespace UI_WinForms.Components;
public partial class Main : UserControl
{
public Main()
{
this.InitializeComponent();
}
}

View File

@ -0,0 +1,60 @@
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -0,0 +1,131 @@
namespace UI_WinForms.Components
{
partial class SectionTree
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
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;
}
}

View File

@ -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<DataContext>()!;
// 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<string, TreeNode>();
// 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<TreeNode>();
// 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;
}
}

View File

@ -0,0 +1,60 @@
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -0,0 +1,165 @@
namespace UI_WinForms.Dialogs
{
partial class InputDialog
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
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;
}
}

View File

@ -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);
}

View File

@ -0,0 +1,60 @@
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -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();
}
}

View File

@ -28,13 +28,24 @@
/// </summary>
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;
}
}

View File

@ -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<Main>();
// 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<DataContext>())
Setup.PerformDataMigration(database).Wait();
// Create the main window:
var mainWindow = SERVICE_PROVIDER.GetService<Main>();
// Start the app:
Application.Run(mainWindow);
}
}
}

View File

@ -60,6 +60,16 @@ namespace UI_WinForms.Resources {
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap icons8_add_folder_512 {
get {
object obj = ResourceManager.GetObject("icons8_add_folder_512", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
@ -70,6 +80,26 @@ namespace UI_WinForms.Resources {
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap icons8_cancel_512 {
get {
object obj = ResourceManager.GetObject("icons8_cancel_512", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap icons8_delete_folder_512 {
get {
object obj = ResourceManager.GetObject("icons8_delete_folder_512", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
@ -80,6 +110,16 @@ namespace UI_WinForms.Resources {
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap icons8_documents_folder_512 {
get {
object obj = ResourceManager.GetObject("icons8_documents_folder_512", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
@ -100,6 +140,16 @@ namespace UI_WinForms.Resources {
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap icons8_ok_512 {
get {
object obj = ResourceManager.GetObject("icons8_ok_512", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>

View File

@ -118,9 +118,21 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="icons8_add_folder_512" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>icons8-add-folder-512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="icons8_browse_folder_512" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>icons8-browse-folder-512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="icons8_cancel_512" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>icons8-cancel-512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="icons8_delete_folder_512" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>icons8-delete-folder-512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="icons8_documents_folder_512" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>icons8-documents-folder-512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="icons8_document_512" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>icons8-document-512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
@ -130,6 +142,9 @@
<data name="icons8_new_window_512" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>icons8-new-window-512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="icons8_ok_512" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>icons8-ok-512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="icons8_open_file_under_cursor_512" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>icons8-open-file-under-cursor-512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.