diff --git a/Examples/Nodify.Calculator/Models/RecentProjectEntry.cs b/Examples/Nodify.Calculator/Models/RecentProjectEntry.cs
new file mode 100644
index 0000000..e32b41b
--- /dev/null
+++ b/Examples/Nodify.Calculator/Models/RecentProjectEntry.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Nodify.Calculator.Models
+{
+ public class RecentProjectEntry
+ {
+ public int Id { get; set; }
+ public string ProjectName { get; set; } = string.Empty;
+ public string ProjectPath { get; set; } = string.Empty;
+ public DateTime LastOpened { get; set; } = DateTime.Now;
+ }
+}
diff --git a/Examples/Nodify.Calculator/RecentProjectsStore.cs b/Examples/Nodify.Calculator/RecentProjectsStore.cs
new file mode 100644
index 0000000..69066fe
--- /dev/null
+++ b/Examples/Nodify.Calculator/RecentProjectsStore.cs
@@ -0,0 +1,74 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using LiteDB;
+using Nodify.Calculator.Models;
+
+namespace Nodify.Calculator
+{
+ ///
+ /// Manages a per-user database of recently opened projects.
+ /// The DB lives in %APPDATA%/APIVisualExecutor/recentprojects.db
+ ///
+ public static class RecentProjectsStore
+ {
+ private static string DbPath
+ {
+ get
+ {
+ var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
+ var dir = Path.Combine(appData, "APIVisualExecutor");
+ Directory.CreateDirectory(dir);
+ return Path.Combine(dir, "recentprojects.db");
+ }
+ }
+
+ ///
+ /// Returns all recent projects ordered by last-opened descending.
+ ///
+ public static List GetAll()
+ {
+ using var db = new LiteDatabase(DbPath);
+ var col = db.GetCollection("projects");
+ return col.FindAll().OrderByDescending(p => p.LastOpened).ToList();
+ }
+
+ ///
+ /// Adds or updates a project entry (matched by path).
+ ///
+ public static void Upsert(string projectName, string projectFilePath)
+ {
+ using var db = new LiteDatabase(DbPath);
+ var col = db.GetCollection("projects");
+ var normalized = Path.GetFullPath(projectFilePath);
+ var existing = col.FindOne(p => p.ProjectPath == normalized);
+ if (existing != null)
+ {
+ existing.ProjectName = projectName;
+ existing.LastOpened = DateTime.Now;
+ col.Update(existing);
+ }
+ else
+ {
+ col.Insert(new RecentProjectEntry
+ {
+ ProjectName = projectName,
+ ProjectPath = normalized,
+ LastOpened = DateTime.Now
+ });
+ }
+ }
+
+ ///
+ /// Removes a project entry by path.
+ ///
+ public static void Remove(string projectFilePath)
+ {
+ using var db = new LiteDatabase(DbPath);
+ var col = db.GetCollection("projects");
+ var normalized = Path.GetFullPath(projectFilePath);
+ col.DeleteMany(p => p.ProjectPath == normalized);
+ }
+ }
+}
diff --git a/Examples/Nodify.Calculator/StartupWindow.xaml b/Examples/Nodify.Calculator/StartupWindow.xaml
index ad0303f..9681629 100644
--- a/Examples/Nodify.Calculator/StartupWindow.xaml
+++ b/Examples/Nodify.Calculator/StartupWindow.xaml
@@ -2,38 +2,195 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="API Visual Executor"
- Width="500"
- Height="380"
+ Width="720"
+ Height="520"
WindowStartupLocation="CenterScreen"
- ResizeMode="NoResize"
+ ResizeMode="CanResizeWithGrip"
Background="#1E1E1E"
Foreground="White">
-
-
+
+
+
+
+
+
+
+
+
+
+ HorizontalAlignment="Center" />
+ Margin="0,4,0,0" />
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Examples/Nodify.Calculator/StartupWindow.xaml.cs b/Examples/Nodify.Calculator/StartupWindow.xaml.cs
index 527b275..eebbe29 100644
--- a/Examples/Nodify.Calculator/StartupWindow.xaml.cs
+++ b/Examples/Nodify.Calculator/StartupWindow.xaml.cs
@@ -1,14 +1,32 @@
using Microsoft.Win32;
+using Nodify.Calculator.Models;
+using System.Collections.ObjectModel;
using System.IO;
using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
namespace Nodify.Calculator
{
public partial class StartupWindow : Window
{
+ public ObservableCollection RecentProjects { get; } = new();
+
public StartupWindow()
{
InitializeComponent();
+ LoadRecentProjects();
+ }
+
+ private void LoadRecentProjects()
+ {
+ RecentProjects.Clear();
+ foreach (var entry in RecentProjectsStore.GetAll())
+ RecentProjects.Add(entry);
+
+ RecentProjectsGrid.ItemsSource = RecentProjects;
+
+ EmptyLabel.Visibility = RecentProjects.Count == 0 ? Visibility.Visible : Visibility.Collapsed;
}
private void NewProject_Click(object sender, RoutedEventArgs e)
@@ -19,7 +37,7 @@ namespace Nodify.Calculator
return;
ProjectManager.CreateProject(dialog.ProjectName, dialog.ProjectDirectory);
- OpenMainWindow();
+ TrackAndOpen();
}
private void OpenProject_Click(object sender, RoutedEventArgs e)
@@ -35,6 +53,55 @@ namespace Nodify.Calculator
return;
ProjectManager.OpenProject(ofd.FileName);
+ TrackAndOpen();
+ }
+
+ private void OpenRecentProject_Click(object sender, RoutedEventArgs e)
+ {
+ if (sender is Button btn && btn.DataContext is RecentProjectEntry entry)
+ OpenRecentEntry(entry);
+ }
+
+ private void RemoveRecentProject_Click(object sender, RoutedEventArgs e)
+ {
+ if (sender is Button btn && btn.DataContext is RecentProjectEntry entry)
+ {
+ RecentProjectsStore.Remove(entry.ProjectPath);
+ LoadRecentProjects();
+ }
+ }
+
+ private void RecentProjectsGrid_MouseDoubleClick(object sender, MouseButtonEventArgs e)
+ {
+ if (RecentProjectsGrid.SelectedItem is RecentProjectEntry entry)
+ OpenRecentEntry(entry);
+ }
+
+ private void OpenRecentEntry(RecentProjectEntry entry)
+ {
+ if (!File.Exists(entry.ProjectPath))
+ {
+ var result = MessageBox.Show(
+ $"Project file not found:\n{entry.ProjectPath}\n\nRemove it from the list?",
+ "Project Not Found",
+ MessageBoxButton.YesNo,
+ MessageBoxImage.Warning);
+
+ if (result == MessageBoxResult.Yes)
+ {
+ RecentProjectsStore.Remove(entry.ProjectPath);
+ LoadRecentProjects();
+ }
+ return;
+ }
+
+ ProjectManager.OpenProject(entry.ProjectPath);
+ TrackAndOpen();
+ }
+
+ private void TrackAndOpen()
+ {
+ RecentProjectsStore.Upsert(ProjectManager.ProjectName, ProjectManager.ProjectFilePath);
OpenMainWindow();
}