diff --git a/src/demo/XBase.Demo.App/App.axaml b/src/demo/XBase.Demo.App/App.axaml new file mode 100644 index 0000000..ed4fd08 --- /dev/null +++ b/src/demo/XBase.Demo.App/App.axaml @@ -0,0 +1,5 @@ + + + diff --git a/src/demo/XBase.Demo.App/App.axaml.cs b/src/demo/XBase.Demo.App/App.axaml.cs new file mode 100644 index 0000000..edc89e8 --- /dev/null +++ b/src/demo/XBase.Demo.App/App.axaml.cs @@ -0,0 +1,29 @@ +using System; +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; +using Microsoft.Extensions.DependencyInjection; +using XBase.Demo.App.Views; + +namespace XBase.Demo.App; + +public partial class App : Application +{ + public static IServiceProvider? Services { get; set; } + + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + var services = Services ?? throw new InvalidOperationException("Service provider is not initialized."); + desktop.MainWindow = services.GetRequiredService(); + } + + base.OnFrameworkInitializationCompleted(); + } +} diff --git a/src/demo/XBase.Demo.App/DependencyInjection/ServiceCollectionExtensions.cs b/src/demo/XBase.Demo.App/DependencyInjection/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..ea22e11 --- /dev/null +++ b/src/demo/XBase.Demo.App/DependencyInjection/ServiceCollectionExtensions.cs @@ -0,0 +1,24 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using XBase.Demo.App.ViewModels; +using XBase.Demo.App.Views; +using XBase.Demo.Diagnostics; +using XBase.Demo.Infrastructure; + +namespace XBase.Demo.App.DependencyInjection; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddDemoApp(this IServiceCollection services) + { + ArgumentNullException.ThrowIfNull(services); + + services.AddXBaseDemoInfrastructure(); + services.AddXBaseDemoDiagnostics(); + + services.AddSingleton(); + services.AddSingleton(); + + return services; + } +} diff --git a/src/demo/XBase.Demo.App/Program.cs b/src/demo/XBase.Demo.App/Program.cs new file mode 100644 index 0000000..0e0543e --- /dev/null +++ b/src/demo/XBase.Demo.App/Program.cs @@ -0,0 +1,44 @@ +using System; +using Avalonia; +using Avalonia.ReactiveUI; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using XBase.Demo.App.DependencyInjection; + +namespace XBase.Demo.App; + +public static class Program +{ + [STAThread] + public static int Main(string[] args) + { + using var host = CreateHost(args); + host.Start(); + + App.Services = host.Services; + + try + { + return BuildAvaloniaApp() + .AfterSetup(_ => App.Services = host.Services) + .StartWithClassicDesktopLifetime(args); + } + finally + { + host.StopAsync().GetAwaiter().GetResult(); + } + } + + private static IHost CreateHost(string[] args) + { + var builder = Host.CreateApplicationBuilder(args); + builder.Services.AddDemoApp(); + return builder.Build(); + } + + private static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure() + .UsePlatformDetect() + .LogToTrace() + .UseReactiveUI(); +} diff --git a/src/demo/XBase.Demo.App/ViewModels/ShellViewModel.cs b/src/demo/XBase.Demo.App/ViewModels/ShellViewModel.cs new file mode 100644 index 0000000..d2df08e --- /dev/null +++ b/src/demo/XBase.Demo.App/ViewModels/ShellViewModel.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Reactive; +using System.Reactive.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using ReactiveUI; +using XBase.Demo.Domain.Catalog; +using XBase.Demo.Domain.Diagnostics; +using XBase.Demo.Domain.Services; + +namespace XBase.Demo.App.ViewModels; + +public class ShellViewModel : ReactiveObject +{ + private readonly ITableCatalogService _catalogService; + private readonly ITablePageService _pageService; + private readonly IDemoTelemetrySink _telemetrySink; + private readonly ILogger _logger; + + private string? _catalogRoot; + private string? _tableSummary; + private bool _isBusy; + + public ShellViewModel( + ITableCatalogService catalogService, + ITablePageService pageService, + IDemoTelemetrySink telemetrySink, + ILogger logger) + { + _catalogService = catalogService; + _pageService = pageService; + _telemetrySink = telemetrySink; + _logger = logger; + + OpenCatalogCommand = ReactiveCommand.CreateFromTask(ExecuteOpenCatalogAsync); + OpenCatalogCommand.ThrownExceptions.Subscribe(OnOpenCatalogFault); + } + + public ReactiveCommand OpenCatalogCommand { get; } + + public string? CatalogRoot + { + get => _catalogRoot; + private set => this.RaiseAndSetIfChanged(ref _catalogRoot, value); + } + + public string? TableSummary + { + get => _tableSummary; + private set => this.RaiseAndSetIfChanged(ref _tableSummary, value); + } + + public bool IsBusy + { + get => _isBusy; + private set => this.RaiseAndSetIfChanged(ref _isBusy, value); + } + + private async Task ExecuteOpenCatalogAsync(string rootPath) + { + if (string.IsNullOrWhiteSpace(rootPath)) + { + return; + } + + try + { + IsBusy = true; + CatalogRoot = rootPath; + + var catalog = await _catalogService.LoadCatalogAsync(rootPath); + var payload = new Dictionary + { + ["root"] = catalog.RootPath, + ["tableCount"] = catalog.Tables.Count + }; + _telemetrySink.Publish(new DemoTelemetryEvent("CatalogLoaded", DateTimeOffset.UtcNow, payload)); + + if (catalog.Tables.Count > 0) + { + var firstTable = catalog.Tables[0]; + var page = await _pageService.LoadPageAsync(firstTable, new TablePageRequest(0, 25)); + TableSummary = $"{catalog.Tables.Count} tables discovered. Preview rows: {page.Rows.Count} from {firstTable.Name}."; + } + else + { + TableSummary = "Catalog scanned successfully with no tables detected."; + } + } + finally + { + IsBusy = false; + } + } + + private void OnOpenCatalogFault(Exception exception) + { + _logger.LogError(exception, "Catalog load failed"); + var payload = new Dictionary + { + ["message"] = exception.Message + }; + _telemetrySink.Publish(new DemoTelemetryEvent("CatalogLoadFailed", DateTimeOffset.UtcNow, payload)); + TableSummary = "Catalog load failed. Review diagnostics for details."; + } +} diff --git a/src/demo/XBase.Demo.App/Views/MainWindow.axaml b/src/demo/XBase.Demo.App/Views/MainWindow.axaml new file mode 100644 index 0000000..f0fce1d --- /dev/null +++ b/src/demo/XBase.Demo.App/Views/MainWindow.axaml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + diff --git a/src/demo/XBase.Demo.App/Views/MainWindow.axaml.cs b/src/demo/XBase.Demo.App/Views/MainWindow.axaml.cs new file mode 100644 index 0000000..76766da --- /dev/null +++ b/src/demo/XBase.Demo.App/Views/MainWindow.axaml.cs @@ -0,0 +1,32 @@ +using System; +using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; +using Microsoft.Extensions.DependencyInjection; +using XBase.Demo.App.ViewModels; + +namespace XBase.Demo.App.Views; + +public partial class MainWindow : ReactiveWindow +{ + public MainWindow() + : this(ResolveViewModel()) + { + } + + public MainWindow(ShellViewModel viewModel) + { + InitializeComponent(); + DataContext = viewModel; + } + + private static ShellViewModel ResolveViewModel() + { + var services = App.Services ?? throw new InvalidOperationException("Service provider is not available for MainWindow."); + return services.GetRequiredService(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } +} diff --git a/src/demo/XBase.Demo.App/XBase.Demo.App.csproj b/src/demo/XBase.Demo.App/XBase.Demo.App.csproj new file mode 100644 index 0000000..36a96a8 --- /dev/null +++ b/src/demo/XBase.Demo.App/XBase.Demo.App.csproj @@ -0,0 +1,20 @@ + + + WinExe + XBase.Demo.App + XBase.Demo.App + enable + + + + + + + + + + + + + + diff --git a/src/demo/XBase.Demo.Diagnostics/InMemoryTelemetrySink.cs b/src/demo/XBase.Demo.Diagnostics/InMemoryTelemetrySink.cs new file mode 100644 index 0000000..04737dc --- /dev/null +++ b/src/demo/XBase.Demo.Diagnostics/InMemoryTelemetrySink.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using Microsoft.Extensions.Logging; +using XBase.Demo.Domain.Diagnostics; + +namespace XBase.Demo.Diagnostics; + +/// +/// Captures telemetry events in-memory for UI consumption. +/// +public sealed class InMemoryTelemetrySink : IDemoTelemetrySink +{ + private readonly ConcurrentQueue _events = new(); + private readonly ILogger _logger; + + public InMemoryTelemetrySink(ILogger logger) + { + _logger = logger; + } + + public void Publish(DemoTelemetryEvent telemetryEvent) + { + ArgumentNullException.ThrowIfNull(telemetryEvent); + + _events.Enqueue(telemetryEvent); + while (_events.Count > 128 && _events.TryDequeue(out _)) + { + } + + _logger.LogInformation("Telemetry event {Name} captured", telemetryEvent.Name); + } + + /// + /// Returns a snapshot of the buffered events for visualization. + /// + public IReadOnlyCollection GetSnapshot() + => _events.ToArray(); +} diff --git a/src/demo/XBase.Demo.Diagnostics/ServiceCollectionExtensions.cs b/src/demo/XBase.Demo.Diagnostics/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..d12f9aa --- /dev/null +++ b/src/demo/XBase.Demo.Diagnostics/ServiceCollectionExtensions.cs @@ -0,0 +1,21 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using XBase.Demo.Domain.Diagnostics; + +namespace XBase.Demo.Diagnostics; + +/// +/// Registers diagnostics sinks for the demo experience. +/// +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddXBaseDemoDiagnostics(this IServiceCollection services) + { + ArgumentNullException.ThrowIfNull(services); + + services.AddSingleton(); + services.AddSingleton(sp => sp.GetRequiredService()); + + return services; + } +} diff --git a/src/demo/XBase.Demo.Diagnostics/XBase.Demo.Diagnostics.csproj b/src/demo/XBase.Demo.Diagnostics/XBase.Demo.Diagnostics.csproj new file mode 100644 index 0000000..0c75626 --- /dev/null +++ b/src/demo/XBase.Demo.Diagnostics/XBase.Demo.Diagnostics.csproj @@ -0,0 +1,11 @@ + + + XBase.Demo.Diagnostics + XBase.Demo.Diagnostics + + + + + + + diff --git a/src/demo/XBase.Demo.Domain/Catalog/CatalogModel.cs b/src/demo/XBase.Demo.Domain/Catalog/CatalogModel.cs new file mode 100644 index 0000000..720f0c0 --- /dev/null +++ b/src/demo/XBase.Demo.Domain/Catalog/CatalogModel.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; + +namespace XBase.Demo.Domain.Catalog; + +/// +/// Represents a catalog of xBase tables discovered under a root directory. +/// +/// The file system root the catalog was scanned from. +/// The set of tables available for browsing. +public sealed record CatalogModel(string RootPath, IReadOnlyList Tables); + +/// +/// Represents a single table, including metadata necessary for browsing. +/// +/// Logical table name. +/// Absolute path to the DBF file. +/// Associated indexes discovered for the table. +public sealed record TableModel(string Name, string Path, IReadOnlyList Indexes); + +/// +/// Represents an index that can be applied while browsing. +/// +/// Index identifier. +/// The expression used to compute the index key. +/// Optional ordering hint for display. +public sealed record IndexModel(string Name, string Expression, int Order = 0); diff --git a/src/demo/XBase.Demo.Domain/Catalog/TablePage.cs b/src/demo/XBase.Demo.Domain/Catalog/TablePage.cs new file mode 100644 index 0000000..bf59989 --- /dev/null +++ b/src/demo/XBase.Demo.Domain/Catalog/TablePage.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; + +namespace XBase.Demo.Domain.Catalog; + +/// +/// Represents a single page of table data for the browser grid. +/// +/// The row set projected for the current page. +/// Total row count matching the query. +/// Zero-based page number. +/// Maximum number of rows per page. +public sealed record TablePage(IReadOnlyList> Rows, long TotalCount, int PageNumber, int PageSize); + +/// +/// Request descriptor for loading a page of table data. +/// +/// Zero-based page number. +/// Maximum number of rows per page. +/// Optional sort expression to apply. +/// Flag indicating whether deleted rows should be included. +public sealed record TablePageRequest(int PageNumber, int PageSize, string? SortExpression = null, bool IncludeDeleted = false); diff --git a/src/demo/XBase.Demo.Domain/Diagnostics/DemoTelemetryEvent.cs b/src/demo/XBase.Demo.Domain/Diagnostics/DemoTelemetryEvent.cs new file mode 100644 index 0000000..3181677 --- /dev/null +++ b/src/demo/XBase.Demo.Domain/Diagnostics/DemoTelemetryEvent.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; + +namespace XBase.Demo.Domain.Diagnostics; + +/// +/// Represents a telemetry event produced by the demo experience. +/// +/// Event identifier. +/// UTC timestamp when the event occurred. +/// Structured payload for diagnostics dashboards. +public sealed record DemoTelemetryEvent(string Name, DateTimeOffset Timestamp, IReadOnlyDictionary Payload); + +/// +/// Abstraction for publishing telemetry events to interested listeners. +/// +public interface IDemoTelemetrySink +{ + void Publish(DemoTelemetryEvent telemetryEvent); +} diff --git a/src/demo/XBase.Demo.Domain/Services/ITableCatalogService.cs b/src/demo/XBase.Demo.Domain/Services/ITableCatalogService.cs new file mode 100644 index 0000000..2718d2a --- /dev/null +++ b/src/demo/XBase.Demo.Domain/Services/ITableCatalogService.cs @@ -0,0 +1,19 @@ +using System.Threading; +using System.Threading.Tasks; +using XBase.Demo.Domain.Catalog; + +namespace XBase.Demo.Domain.Services; + +/// +/// Provides catalog discovery and table metadata loading capabilities. +/// +public interface ITableCatalogService +{ + /// + /// Scans the provided root directory for xBase table artifacts. + /// + /// The directory containing DBF/DBT/NTX assets. + /// Cancellation token for the asynchronous operation. + /// A populated describing the catalog. + Task LoadCatalogAsync(string rootPath, CancellationToken cancellationToken = default); +} diff --git a/src/demo/XBase.Demo.Domain/Services/ITablePageService.cs b/src/demo/XBase.Demo.Domain/Services/ITablePageService.cs new file mode 100644 index 0000000..5a36c2f --- /dev/null +++ b/src/demo/XBase.Demo.Domain/Services/ITablePageService.cs @@ -0,0 +1,13 @@ +using System.Threading; +using System.Threading.Tasks; +using XBase.Demo.Domain.Catalog; + +namespace XBase.Demo.Domain.Services; + +/// +/// Provides paginated access to table data for browsing scenarios. +/// +public interface ITablePageService +{ + Task LoadPageAsync(TableModel table, TablePageRequest request, CancellationToken cancellationToken = default); +} diff --git a/src/demo/XBase.Demo.Domain/XBase.Demo.Domain.csproj b/src/demo/XBase.Demo.Domain/XBase.Demo.Domain.csproj new file mode 100644 index 0000000..8ac6e15 --- /dev/null +++ b/src/demo/XBase.Demo.Domain/XBase.Demo.Domain.csproj @@ -0,0 +1,6 @@ + + + XBase.Demo.Domain + XBase.Demo.Domain + + diff --git a/src/demo/XBase.Demo.Infrastructure/Catalog/FileSystemTableCatalogService.cs b/src/demo/XBase.Demo.Infrastructure/Catalog/FileSystemTableCatalogService.cs new file mode 100644 index 0000000..10cc9f7 --- /dev/null +++ b/src/demo/XBase.Demo.Infrastructure/Catalog/FileSystemTableCatalogService.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Microsoft.Extensions.Logging; +using XBase.Demo.Domain.Catalog; +using XBase.Demo.Domain.Services; + +namespace XBase.Demo.Infrastructure.Catalog; + +/// +/// Minimal file-system backed implementation that surfaces DBF tables and common index artifacts. +/// +public sealed class FileSystemTableCatalogService : ITableCatalogService +{ + private static readonly string[] SupportedIndexExtensions = [".ntx", ".mdx", ".ndx"]; + private readonly ILogger _logger; + + public FileSystemTableCatalogService(ILogger logger) + { + _logger = logger; + } + + public Task LoadCatalogAsync(string rootPath, CancellationToken cancellationToken = default) + { + ArgumentException.ThrowIfNullOrWhiteSpace(rootPath); + + if (!Directory.Exists(rootPath)) + { + throw new DirectoryNotFoundException($"Catalog root '{rootPath}' was not found."); + } + + var absoluteRoot = Path.GetFullPath(rootPath); + var tables = new List(); + + foreach (var tableFile in Directory.EnumerateFiles(absoluteRoot, "*.dbf", SearchOption.TopDirectoryOnly)) + { + cancellationToken.ThrowIfCancellationRequested(); + + var tableName = Path.GetFileNameWithoutExtension(tableFile); + var directory = Path.GetDirectoryName(tableFile)!; + + var indexes = new List(); + foreach (var indexFile in Directory.EnumerateFiles(directory, $"{tableName}.*", SearchOption.TopDirectoryOnly)) + { + var extension = Path.GetExtension(indexFile); + if (!SupportedIndexExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) + { + continue; + } + + indexes.Add(new IndexModel(Path.GetFileName(indexFile), extension.ToUpperInvariant())); + } + + tables.Add(new TableModel(tableName, tableFile, indexes)); + } + + _logger.LogInformation("Discovered {TableCount} tables under {RootPath}", tables.Count, absoluteRoot); + return Task.FromResult(new CatalogModel(absoluteRoot, tables)); + } +} diff --git a/src/demo/XBase.Demo.Infrastructure/Catalog/NullTablePageService.cs b/src/demo/XBase.Demo.Infrastructure/Catalog/NullTablePageService.cs new file mode 100644 index 0000000..8dd0fbd --- /dev/null +++ b/src/demo/XBase.Demo.Infrastructure/Catalog/NullTablePageService.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using XBase.Demo.Domain.Catalog; +using XBase.Demo.Domain.Services; + +namespace XBase.Demo.Infrastructure.Catalog; + +/// +/// Temporary placeholder that returns empty pages until the data engine is wired up. +/// +public sealed class NullTablePageService : ITablePageService +{ + public Task LoadPageAsync(TableModel table, TablePageRequest request, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(table); + ArgumentNullException.ThrowIfNull(request); + + var emptyRows = Array.Empty>(); + return Task.FromResult(new TablePage(emptyRows, 0, request.PageNumber, request.PageSize)); + } +} diff --git a/src/demo/XBase.Demo.Infrastructure/ServiceCollectionExtensions.cs b/src/demo/XBase.Demo.Infrastructure/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..9e3c586 --- /dev/null +++ b/src/demo/XBase.Demo.Infrastructure/ServiceCollectionExtensions.cs @@ -0,0 +1,27 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using XBase.Demo.Domain.Services; +using XBase.Demo.Infrastructure.Catalog; + +namespace XBase.Demo.Infrastructure; + +/// +/// Dependency injection helpers for the demo infrastructure layer. +/// +public static class ServiceCollectionExtensions +{ + /// + /// Registers services necessary for the Phase A demo scenarios. + /// + public static IServiceCollection AddXBaseDemoInfrastructure(this IServiceCollection services) + { + ArgumentNullException.ThrowIfNull(services); + + services.AddLogging(builder => builder.AddDebug()); + services.AddSingleton(); + services.AddSingleton(); + + return services; + } +} diff --git a/src/demo/XBase.Demo.Infrastructure/XBase.Demo.Infrastructure.csproj b/src/demo/XBase.Demo.Infrastructure/XBase.Demo.Infrastructure.csproj new file mode 100644 index 0000000..f7e3655 --- /dev/null +++ b/src/demo/XBase.Demo.Infrastructure/XBase.Demo.Infrastructure.csproj @@ -0,0 +1,14 @@ + + + XBase.Demo.Infrastructure + XBase.Demo.Infrastructure + + + + + + + + + + diff --git a/src/demo/tasks.md b/src/demo/tasks.md new file mode 100644 index 0000000..dc7cb04 --- /dev/null +++ b/src/demo/tasks.md @@ -0,0 +1,25 @@ +# xBase Demo Solution Tasks + +Source blueprint: [docs/demo/avalonia-reactiveui-demo-plan.md](../../docs/demo/avalonia-reactiveui-demo-plan.md) + +## Milestone M1 – App Shell & Catalog Browser +- [ ] Scaffold Avalonia desktop host with ReactiveUI composition root. +- [ ] Implement catalog scanning service and basic directory selection flow. +- [ ] Build table/index listing view models with paging placeholders. +- [ ] Wire diagnostics/logging sinks and sample telemetry events. +- [ ] Add integration smoke harness once basic navigation is ready. + +## Milestone M2 – DDL & Index Basics +- [ ] Generate DDL preview scripts for create/alter/drop operations. +- [ ] Implement index create/drop services with error surfacing. +- [ ] Extend ViewModels with command handlers for schema updates. + +## Milestone M3 – Rebuild & Diagnostics +- [ ] Add side-by-side index rebuild orchestration with progress observables. +- [ ] Surface performance metrics and index selection feedback in UI. +- [ ] Expand diagnostics feed for journal/index events. + +## Milestone M4 – Seed & Recovery Demo +- [ ] Implement CSV import pipeline with encoding detection. +- [ ] Simulate crash scenarios and recovery replay workflow. +- [ ] Publish diagnostic/export reports for support packages. diff --git a/xBase.sln b/xBase.sln index e785a56..34af54a 100644 --- a/xBase.sln +++ b/xBase.sln @@ -35,6 +35,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XBase.Diagnostics.Tests", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XBase.Tools.Tests", "tests\XBase.Tools.Tests\XBase.Tools.Tests.csproj", "{2B8B378E-3016-4964-89A4-539C279B0D5A}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "demo", "demo", "{8216414C-D70B-47BF-8296-90A08F02D90C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XBase.Demo.Domain", "src\demo\XBase.Demo.Domain\XBase.Demo.Domain.csproj", "{D83CDBB4-E216-4D63-8F77-8193C185366A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XBase.Demo.Infrastructure", "src\demo\XBase.Demo.Infrastructure\XBase.Demo.Infrastructure.csproj", "{25B73D78-0BB2-480D-8E6B-CAF36DE60690}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XBase.Demo.Diagnostics", "src\demo\XBase.Demo.Diagnostics\XBase.Demo.Diagnostics.csproj", "{4C647419-449C-4B34-828A-103D6B7F8AAE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XBase.Demo.App", "src\demo\XBase.Demo.App\XBase.Demo.App.csproj", "{1E80FE92-7098-4406-914A-24CE7CBED4AC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -100,6 +110,22 @@ Global {2B8B378E-3016-4964-89A4-539C279B0D5A}.Debug|Any CPU.Build.0 = Debug|Any CPU {2B8B378E-3016-4964-89A4-539C279B0D5A}.Release|Any CPU.ActiveCfg = Release|Any CPU {2B8B378E-3016-4964-89A4-539C279B0D5A}.Release|Any CPU.Build.0 = Release|Any CPU + {D83CDBB4-E216-4D63-8F77-8193C185366A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D83CDBB4-E216-4D63-8F77-8193C185366A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D83CDBB4-E216-4D63-8F77-8193C185366A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D83CDBB4-E216-4D63-8F77-8193C185366A}.Release|Any CPU.Build.0 = Release|Any CPU + {25B73D78-0BB2-480D-8E6B-CAF36DE60690}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {25B73D78-0BB2-480D-8E6B-CAF36DE60690}.Debug|Any CPU.Build.0 = Debug|Any CPU + {25B73D78-0BB2-480D-8E6B-CAF36DE60690}.Release|Any CPU.ActiveCfg = Release|Any CPU + {25B73D78-0BB2-480D-8E6B-CAF36DE60690}.Release|Any CPU.Build.0 = Release|Any CPU + {4C647419-449C-4B34-828A-103D6B7F8AAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4C647419-449C-4B34-828A-103D6B7F8AAE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4C647419-449C-4B34-828A-103D6B7F8AAE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4C647419-449C-4B34-828A-103D6B7F8AAE}.Release|Any CPU.Build.0 = Release|Any CPU + {1E80FE92-7098-4406-914A-24CE7CBED4AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1E80FE92-7098-4406-914A-24CE7CBED4AC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1E80FE92-7098-4406-914A-24CE7CBED4AC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1E80FE92-7098-4406-914A-24CE7CBED4AC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {B2E0A9D6-E39E-44E6-9762-B1A5C50D29B6} = {DB34B0F0-48E5-441D-B062-A525C37FD21D} @@ -116,5 +142,10 @@ Global {659059E0-B3CE-44D2-B68A-01227B398A1D} = {8944503E-D8CB-4870-A577-E3C76244B9F6} {ADBFA642-7C6C-43E7-A9B2-F4B3473B9FC5} = {8944503E-D8CB-4870-A577-E3C76244B9F6} {2B8B378E-3016-4964-89A4-539C279B0D5A} = {8944503E-D8CB-4870-A577-E3C76244B9F6} + {8216414C-D70B-47BF-8296-90A08F02D90C} = {DB34B0F0-48E5-441D-B062-A525C37FD21D} + {D83CDBB4-E216-4D63-8F77-8193C185366A} = {8216414C-D70B-47BF-8296-90A08F02D90C} + {25B73D78-0BB2-480D-8E6B-CAF36DE60690} = {8216414C-D70B-47BF-8296-90A08F02D90C} + {4C647419-449C-4B34-828A-103D6B7F8AAE} = {8216414C-D70B-47BF-8296-90A08F02D90C} + {1E80FE92-7098-4406-914A-24CE7CBED4AC} = {8216414C-D70B-47BF-8296-90A08F02D90C} EndGlobalSection EndGlobal