Merge Common to Desktop
This commit is contained in:
parent
26915defe1
commit
e9c6e14965
40 changed files with 447 additions and 282 deletions
|
|
@ -1,5 +0,0 @@
|
||||||
namespace InkForge.Common.Controllers;
|
|
||||||
|
|
||||||
public class WorkspaceController
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
using InkForge.Data;
|
|
||||||
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
|
|
||||||
namespace InkForge.Common.Data;
|
|
||||||
|
|
||||||
public class NoteDbContextFactory : IDbContextFactory<NoteDbContext>
|
|
||||||
{
|
|
||||||
|
|
||||||
public NoteDbContext CreateDbContext()
|
|
||||||
{
|
|
||||||
return new NoteDbContext(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
|
||||||
<RootNamespace>InkForge</RootNamespace>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Avalonia.Controls.DataGrid" />
|
|
||||||
<PackageReference Include="Avalonia.Fonts.Inter" />
|
|
||||||
<PackageReference Include="Avalonia.ReactiveUI" />
|
|
||||||
<PackageReference Include="Avalonia.Themes.Fluent" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" />
|
|
||||||
<PackageReference Include="Splat.Microsoft.Extensions.DependencyInjection" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\..\shared\InkForge.Data\InkForge.Data.csproj" />
|
|
||||||
<ProjectReference Include="..\..\shared\migrations\InkForge.Sqlite\InkForge.Sqlite.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<EmbeddedResource Include="Properties\Settings.json" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
{}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
using InkForge.Common.Controllers;
|
|
||||||
|
|
||||||
using ReactiveUI;
|
|
||||||
|
|
||||||
namespace InkForge.Common.ViewModels;
|
|
||||||
|
|
||||||
public class AppViewModel : ReactiveObject
|
|
||||||
{
|
|
||||||
private object _view;
|
|
||||||
|
|
||||||
public object View
|
|
||||||
{
|
|
||||||
get => _view;
|
|
||||||
set => this.RaiseAndSetIfChanged(ref _view, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AppViewModel(WorkspaceController workspace, LandingViewModel landingViewModel)
|
|
||||||
{
|
|
||||||
View = landingViewModel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
using InkForge.Common.Controllers;
|
|
||||||
using InkForge.Common.ReactiveUI;
|
|
||||||
|
|
||||||
using ReactiveUI;
|
|
||||||
|
|
||||||
namespace InkForge.Common.ViewModels.Landing;
|
|
||||||
|
|
||||||
public class CreateWorkspaceViewModel : LandingViewModelBase
|
|
||||||
{
|
|
||||||
public override string? UrlPathSegment => null;
|
|
||||||
|
|
||||||
private string workspaceName;
|
|
||||||
|
|
||||||
public string WorkspaceName
|
|
||||||
{
|
|
||||||
get => workspaceName;
|
|
||||||
set => this.RaiseAndSetIfChanged(ref workspaceName, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CreateWorkspaceViewModel(LandingViewModel landing, WorkspaceController workspace) : base(landing)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
using InkForge.Common.ReactiveUI;
|
|
||||||
|
|
||||||
namespace InkForge.Common.ViewModels.Landing;
|
|
||||||
|
|
||||||
public abstract class LandingViewModelBase(LandingViewModel landing) : RoutableReactiveObject(landing)
|
|
||||||
{
|
|
||||||
public LandingViewModel Landing => landing;
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
|
|
||||||
namespace InkForge.Common.ViewModels.Landing;
|
|
||||||
|
|
||||||
public class LandingViewModelFactory(IServiceProvider serviceProvider)
|
|
||||||
{
|
|
||||||
public T Create<T>(LandingViewModel landing) where T : LandingViewModelBase
|
|
||||||
{
|
|
||||||
LandingViewModelsObjectParameters objectParameters = new(landing);
|
|
||||||
return TypeFactory<LandingViewModelsObjectParameters, T>.Create(objectParameters, serviceProvider);
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly record struct LandingViewModelsObjectParameters(
|
|
||||||
LandingViewModel Landing
|
|
||||||
) : IObjectParameters<LandingViewModelsObjectParameters>
|
|
||||||
{
|
|
||||||
public static Type[] Types => [typeof(LandingViewModel)];
|
|
||||||
|
|
||||||
public static implicit operator object[](in LandingViewModelsObjectParameters self) => [self.Landing];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
|
|
||||||
using InkForge.Common.ReactiveUI;
|
|
||||||
|
|
||||||
namespace InkForge.Common.ViewModels.Landing;
|
|
||||||
|
|
||||||
public class OpenRecentViewModel : LandingViewModelBase
|
|
||||||
{
|
|
||||||
public override string? UrlPathSegment => null;
|
|
||||||
private readonly ReadOnlyObservableCollection<RecentItemViewModel> recentItems;
|
|
||||||
|
|
||||||
public ReadOnlyObservableCollection<RecentItemViewModel> RecentItems => recentItems;
|
|
||||||
|
|
||||||
public OpenRecentViewModel(LandingViewModel landing) : base(landing)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
using System.Reactive.Linq;
|
|
||||||
|
|
||||||
using InkForge.Common.ViewModels.Landing;
|
|
||||||
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
|
|
||||||
using ReactiveUI;
|
|
||||||
|
|
||||||
namespace InkForge.Common.ViewModels;
|
|
||||||
|
|
||||||
public class LandingViewModel : ReactiveObject, IScreen
|
|
||||||
{
|
|
||||||
private readonly LandingViewModelFactory _factory;
|
|
||||||
|
|
||||||
public RoutingState Router { get; } = new();
|
|
||||||
|
|
||||||
public LandingViewModel(LandingViewModelFactory factory)
|
|
||||||
{
|
|
||||||
_factory = factory;
|
|
||||||
|
|
||||||
Router.CurrentViewModel.Where(x => x is null)
|
|
||||||
.SelectMany(Observable.Return(factory.Create<OpenRecentViewModel>(this)))
|
|
||||||
.InvokeCommand<IRoutableViewModel>(Router.NavigateAndReset);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Navigate<T>() where T : LandingViewModelBase
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
<UserControl xmlns="https://github.com/avaloniaui"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
xmlns:reactiveui="http://reactiveui.net"
|
|
||||||
xmlns:ifcvm="using:InkForge.Common.ViewModels"
|
|
||||||
mc:Ignorable="d"
|
|
||||||
d:DesignWidth="800"
|
|
||||||
d:DesignHeight="450"
|
|
||||||
x:Class="InkForge.Common.Views.LandingView"
|
|
||||||
x:DataType="ifcvm:LandingViewModel">
|
|
||||||
<Grid RowDefinitions="Auto, *, Auto">
|
|
||||||
<Label Content="InkForge"
|
|
||||||
Grid.Row="0" />
|
|
||||||
|
|
||||||
<reactiveui:RoutedViewHost Router="{CompiledBinding Router}"
|
|
||||||
Grid.Row="1" />
|
|
||||||
</Grid>
|
|
||||||
</UserControl>
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
using Avalonia.ReactiveUI;
|
|
||||||
|
|
||||||
using InkForge.Common.ViewModels.Landing;
|
|
||||||
|
|
||||||
namespace InkForge.Common.Views.LandingViews;
|
|
||||||
|
|
||||||
public partial class CreateWorkspaceView : ReactiveUserControl<CreateWorkspaceViewModel>
|
|
||||||
{
|
|
||||||
public CreateWorkspaceView()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
using Avalonia.ReactiveUI;
|
|
||||||
|
|
||||||
using InkForge.Common.ViewModels.Landing;
|
|
||||||
|
|
||||||
namespace InkForge.Common.Views.LandingViews;
|
|
||||||
|
|
||||||
public partial class OpenRecentView : ReactiveUserControl<OpenRecentViewModel>
|
|
||||||
{
|
|
||||||
public OpenRecentView()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<Application xmlns="https://github.com/avaloniaui"
|
<Application xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
x:Class="InkForge.Common.App"
|
x:Class="InkForge.Desktop.App"
|
||||||
RequestedThemeVariant="Default">
|
RequestedThemeVariant="Default">
|
||||||
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
|
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
|
||||||
|
|
||||||
|
|
@ -3,7 +3,7 @@ using Avalonia.Controls;
|
||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
|
|
||||||
using InkForge.Common.ViewModels;
|
using InkForge.Desktop.ViewModels;
|
||||||
|
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
@ -14,7 +14,7 @@ using ReactiveUI;
|
||||||
using Splat;
|
using Splat;
|
||||||
using Splat.Microsoft.Extensions.DependencyInjection;
|
using Splat.Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace InkForge.Common;
|
namespace InkForge.Desktop;
|
||||||
|
|
||||||
public partial class App : Application
|
public partial class App : Application
|
||||||
{
|
{
|
||||||
|
|
@ -30,15 +30,15 @@ public partial class App : Application
|
||||||
configuration.SetBasePath(AppContext.BaseDirectory);
|
configuration.SetBasePath(AppContext.BaseDirectory);
|
||||||
configuration.AddJsonFile(
|
configuration.AddJsonFile(
|
||||||
new ManifestEmbeddedFileProvider(typeof(App).Assembly),
|
new ManifestEmbeddedFileProvider(typeof(App).Assembly),
|
||||||
"Properties/Settings.json", false, false);
|
"Properties/appsettings.json", false, false);
|
||||||
configuration.AddJsonFile(
|
configuration.AddJsonFile(
|
||||||
Path.Combine(
|
Path.Combine(
|
||||||
Environment.GetFolderPath(
|
Environment.GetFolderPath(
|
||||||
Environment.SpecialFolder.ApplicationData,
|
Environment.SpecialFolder.ApplicationData,
|
||||||
Environment.SpecialFolderOption.DoNotVerify),
|
Environment.SpecialFolderOption.DoNotVerify),
|
||||||
"InkForge",
|
"InkForge",
|
||||||
"UserSettings.json"), true, true);
|
"usersettings.json"), true, true);
|
||||||
configuration.AddJsonFile("Settings.json", true, true);
|
configuration.AddJsonFile("appsettings.json", true, true);
|
||||||
|
|
||||||
services.UseMicrosoftDependencyResolver();
|
services.UseMicrosoftDependencyResolver();
|
||||||
Locator.CurrentMutable.InitializeSplat();
|
Locator.CurrentMutable.InitializeSplat();
|
||||||
68
app/InkForge.Desktop/Controllers/WorkspaceController.cs
Normal file
68
app/InkForge.Desktop/Controllers/WorkspaceController.cs
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
using InkForge.Data;
|
||||||
|
using InkForge.Desktop.Models;
|
||||||
|
using InkForge.Desktop.Services;
|
||||||
|
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
|
namespace InkForge.Desktop.Controllers;
|
||||||
|
|
||||||
|
public class WorkspaceController : ReactiveObject
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
private Workspace _workspace;
|
||||||
|
|
||||||
|
public Workspace Workspace
|
||||||
|
{
|
||||||
|
get => _workspace;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _workspace, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WorkspaceController(IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task OpenWorkspace(string path, bool createFile = false)
|
||||||
|
{
|
||||||
|
if (await CreateWorkspace(path, createFile) is { } workspace)
|
||||||
|
{
|
||||||
|
Workspace = workspace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ValueTask<Workspace?> CreateWorkspace(string path, bool createFile)
|
||||||
|
{
|
||||||
|
FileInfo file = new(path);
|
||||||
|
if (!(createFile || file.Exists))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
file.Directory!.Create();
|
||||||
|
var scope = _serviceProvider.CreateScope();
|
||||||
|
var scopeServiceProvider = scope.ServiceProvider;
|
||||||
|
var context = scope.ServiceProvider.GetRequiredService<WorkspaceContext>();
|
||||||
|
context.DbPath = path;
|
||||||
|
|
||||||
|
var db = scopeServiceProvider.GetRequiredService<NoteDbContext>();
|
||||||
|
await using (var transaction = await db.Database.BeginTransactionAsync().ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await db.Database.MigrateAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
await transaction.RollbackAsync().ConfigureAwait(false);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
await transaction.CommitAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new(scope);
|
||||||
|
}
|
||||||
|
}
|
||||||
27
app/InkForge.Desktop/Data/NoteDbContextFactory.cs
Normal file
27
app/InkForge.Desktop/Data/NoteDbContextFactory.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
using InkForge.Desktop.Services;
|
||||||
|
using InkForge.Data;
|
||||||
|
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
|
||||||
|
using SmartFormat;
|
||||||
|
|
||||||
|
namespace InkForge.Desktop.Data;
|
||||||
|
|
||||||
|
public class NoteDbContextFactory(WorkspaceContext context, IConfiguration configuration) : IDbContextFactory<NoteDbContext>
|
||||||
|
{
|
||||||
|
private string? _connectionString;
|
||||||
|
|
||||||
|
public NoteDbContext CreateDbContext()
|
||||||
|
{
|
||||||
|
_connectionString ??= Smart.Format(configuration.GetConnectionString("DefaultConnection")!, new
|
||||||
|
{
|
||||||
|
WorkspaceFile = context.DbPath
|
||||||
|
});
|
||||||
|
|
||||||
|
DbContextOptionsBuilder<NoteDbContext> builder = new();
|
||||||
|
builder.UseSqlite(_connectionString, o => o.MigrationsAssembly("InkForge.Sqlite"));
|
||||||
|
|
||||||
|
return new NoteDbContext(builder.Options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,16 +6,32 @@
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<AssemblyName>InkForge</AssemblyName>
|
<AssemblyName>InkForge</AssemblyName>
|
||||||
|
<RootNamespace>InkForge.Desktop</RootNamespace>
|
||||||
|
<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Avalonia.Controls.DataGrid" />
|
||||||
<PackageReference Include="Avalonia.Desktop" />
|
<PackageReference Include="Avalonia.Desktop" />
|
||||||
|
<PackageReference Include="Avalonia.Fonts.Inter" />
|
||||||
|
<PackageReference Include="Avalonia.ReactiveUI" />
|
||||||
|
<PackageReference Include="Avalonia.Themes.Fluent" />
|
||||||
<PackageReference Include="Dock.Avalonia" />
|
<PackageReference Include="Dock.Avalonia" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Http" />
|
||||||
|
<PackageReference Include="SmartFormat" />
|
||||||
|
<PackageReference Include="Splat.Microsoft.Extensions.DependencyInjection" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\InkForge.Common\InkForge.Common.csproj" />
|
<ProjectReference Include="..\..\shared\InkForge.Data\InkForge.Data.csproj" />
|
||||||
|
<ProjectReference Include="..\..\shared\migrations\InkForge.Sqlite\InkForge.Sqlite.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Include="Properties\appsettings.json" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
using InkForge.Common.Controllers;
|
using InkForge.Desktop.Controllers;
|
||||||
using InkForge.Common.Data;
|
using InkForge.Desktop.Data;
|
||||||
using InkForge.Common.ViewModels;
|
using InkForge.Desktop.Services;
|
||||||
using InkForge.Common.ViewModels.Landing;
|
using InkForge.Desktop.ViewModels;
|
||||||
using InkForge.Data;
|
using InkForge.Data;
|
||||||
|
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
using Splat;
|
using Splat;
|
||||||
|
|
@ -16,10 +18,12 @@ public static class InkForgeServiceCollections
|
||||||
{
|
{
|
||||||
services.AddHttpClient();
|
services.AddHttpClient();
|
||||||
|
|
||||||
services.AddDbContextFactory<NoteDbContext, NoteDbContextFactory>();
|
services.AddScoped<IDbContextFactory<NoteDbContext>, NoteDbContextFactory>();
|
||||||
|
services.AddScoped(s => s.GetRequiredService<IDbContextFactory<NoteDbContext>>().CreateDbContext());
|
||||||
|
|
||||||
|
services.AddScoped<WorkspaceContext>();
|
||||||
|
|
||||||
services.AddSingleton<LandingViewModel>();
|
services.AddSingleton<LandingViewModel>();
|
||||||
services.AddSingleton<LandingViewModelFactory>();
|
|
||||||
services.AddSingleton<WorkspaceController>();
|
services.AddSingleton<WorkspaceController>();
|
||||||
|
|
||||||
Locator.CurrentMutable.RegisterViewsForViewModels(typeof(InkForgeServiceCollections).Assembly);
|
Locator.CurrentMutable.RegisterViewsForViewModels(typeof(InkForgeServiceCollections).Assembly);
|
||||||
8
app/InkForge.Desktop/Models/Workspace.cs
Normal file
8
app/InkForge.Desktop/Models/Workspace.cs
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace InkForge.Desktop.Models;
|
||||||
|
|
||||||
|
public class Workspace(IServiceScope scope)
|
||||||
|
{
|
||||||
|
public IServiceProvider ServiceProvider => scope.ServiceProvider;
|
||||||
|
}
|
||||||
|
|
@ -3,29 +3,18 @@ using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
|
|
||||||
using InkForge.Common;
|
using InkForge.Desktop;
|
||||||
using InkForge.Common.ViewModels;
|
|
||||||
using InkForge.Desktop.Views;
|
|
||||||
|
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
using ReactiveUI;
|
|
||||||
|
|
||||||
static class Program
|
static class Program
|
||||||
{
|
{
|
||||||
private static readonly ConfigurationManager Configuration = new();
|
|
||||||
|
|
||||||
[STAThread]
|
[STAThread]
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
=> BuildAvaloniaApp()
|
=> BuildAvaloniaApp()
|
||||||
.UseMicrosoftDependencyInjection()
|
.UseMicrosoftDependencyInjection(out var configuration)
|
||||||
.StartWithClassicDesktopLifetime(args, WithMicrosoftDependencyInjection);
|
.StartWithClassicDesktopLifetime(args, configuration.WithMicrosoftDependencyInjection);
|
||||||
|
|
||||||
private static void WithMicrosoftDependencyInjection(IClassicDesktopStyleApplicationLifetime lifetime)
|
|
||||||
{
|
|
||||||
Configuration.AddCommandLine(lifetime.Args ?? []);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AppBuilder BuildAvaloniaApp()
|
public static AppBuilder BuildAvaloniaApp()
|
||||||
=> AppBuilder.Configure<App>()
|
=> AppBuilder.Configure<App>()
|
||||||
|
|
@ -34,12 +23,7 @@ static class Program
|
||||||
.WithInterFont()
|
.WithInterFont()
|
||||||
.LogToTrace();
|
.LogToTrace();
|
||||||
|
|
||||||
private static void ConfigureServices(IServiceCollection services)
|
private static void SetupApp(this IServiceCollection services, AppBuilder appBuilder)
|
||||||
{
|
|
||||||
services.AddTransient<IViewFor<AppViewModel>, MainWindow>();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void OnSetup(this IServiceCollection services, AppBuilder appBuilder)
|
|
||||||
{
|
{
|
||||||
var dispatcher = Dispatcher.UIThread;
|
var dispatcher = Dispatcher.UIThread;
|
||||||
var app = appBuilder.Instance!;
|
var app = appBuilder.Instance!;
|
||||||
|
|
@ -49,19 +33,52 @@ static class Program
|
||||||
.AddSingleton(app.PlatformSettings!)
|
.AddSingleton(app.PlatformSettings!)
|
||||||
.AddSingleton(dispatcher);
|
.AddSingleton(dispatcher);
|
||||||
|
|
||||||
ConfigureServices(services);
|
|
||||||
|
|
||||||
var serviceProvider = services.BuildServiceProvider();
|
var serviceProvider = services.BuildServiceProvider();
|
||||||
app.SetValue(App.ServiceProviderProperty, serviceProvider);
|
app.SetValue(App.ServiceProviderProperty, serviceProvider);
|
||||||
dispatcher.ShutdownFinished += (_, _) => serviceProvider.Dispose();
|
_ = new ServiceProviderDisposer(serviceProvider, dispatcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static AppBuilder UseMicrosoftDependencyInjection(this AppBuilder builder)
|
private static AppBuilder UseMicrosoftDependencyInjection(this AppBuilder builder, out ConfigurationManager configuration)
|
||||||
{
|
{
|
||||||
|
configuration = new();
|
||||||
ServiceCollection services = [];
|
ServiceCollection services = [];
|
||||||
App.Configure(services, Configuration);
|
services.AddSingleton<IConfiguration>(configuration);
|
||||||
|
App.Configure(services, configuration);
|
||||||
|
|
||||||
builder.AfterSetup(services.OnSetup);
|
builder.AfterSetup(services.SetupApp);
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void WithMicrosoftDependencyInjection(this ConfigurationManager configuration, IClassicDesktopStyleApplicationLifetime lifetime)
|
||||||
|
{
|
||||||
|
configuration.AddCommandLine(lifetime.Args ?? []);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ServiceProviderDisposer
|
||||||
|
{
|
||||||
|
private readonly ServiceProvider _serviceProvider;
|
||||||
|
private readonly TaskCompletionSource<ValueTask> _shutdownTask = new();
|
||||||
|
|
||||||
|
public ServiceProviderDisposer(ServiceProvider serviceProvider, Dispatcher dispatcher)
|
||||||
|
{
|
||||||
|
dispatcher.ShutdownStarted += OnShutdownStarted;
|
||||||
|
dispatcher.ShutdownFinished += OnShutdownFinished;
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnShutdownFinished(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (_shutdownTask.Task.Result is { IsCompleted: false } disposeTask)
|
||||||
|
{
|
||||||
|
disposeTask.GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnShutdownStarted(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
#pragma warning disable CA2012 // This will only ever be awaited once in ShutdownFinished
|
||||||
|
_shutdownTask.SetResult(_serviceProvider.DisposeAsync());
|
||||||
|
#pragma warning restore CA2012
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
namespace InkForge.Common.Properties;
|
namespace InkForge.Desktop.Properties;
|
||||||
|
|
||||||
public class ApplicationSettings
|
public class ApplicationSettings
|
||||||
{
|
{
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace InkForge.Common.Properties;
|
namespace InkForge.Desktop.Properties;
|
||||||
|
|
||||||
[JsonSerializable(typeof(ApplicationSettings))]
|
[JsonSerializable(typeof(ApplicationSettings))]
|
||||||
[JsonSerializable(typeof(IDictionary<string, object>))]
|
[JsonSerializable(typeof(IDictionary<string, object>))]
|
||||||
5
app/InkForge.Desktop/Properties/appsettings.json
Normal file
5
app/InkForge.Desktop/Properties/appsettings.json
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"DefaultConnection": "Data Source={WorkspaceFile}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace InkForge.Common.ReactiveUI;
|
namespace InkForge.Desktop.ReactiveUI;
|
||||||
|
|
||||||
public abstract class RoutableReactiveObject(IScreen screen) : ReactiveObject, IRoutableViewModel
|
public abstract class RoutableReactiveObject(IScreen screen) : ReactiveObject, IRoutableViewModel
|
||||||
{
|
{
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace InkForge.Common.ReactiveUI;
|
namespace InkForge.Desktop.ReactiveUI;
|
||||||
|
|
||||||
public interface IViewModelFactory<T, TCreator>
|
public interface IViewModelFactory<T, TCreator>
|
||||||
{
|
{
|
||||||
13
app/InkForge.Desktop/Services/DialogHelper.cs
Normal file
13
app/InkForge.Desktop/Services/DialogHelper.cs
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
using Avalonia.Platform.Storage;
|
||||||
|
|
||||||
|
namespace InkForge.Desktop.Services;
|
||||||
|
|
||||||
|
public static class StorageProviderExtensions
|
||||||
|
{
|
||||||
|
public static IStorageProvider? GetStorageProvider(this object? context)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(context);
|
||||||
|
|
||||||
|
return TopLevels.GetTopLevelForContext(context)?.StorageProvider;
|
||||||
|
}
|
||||||
|
}
|
||||||
54
app/InkForge.Desktop/Services/TopLevels.cs
Normal file
54
app/InkForge.Desktop/Services/TopLevels.cs
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
|
namespace InkForge.Desktop.Services;
|
||||||
|
|
||||||
|
public class TopLevels
|
||||||
|
{
|
||||||
|
public static readonly AttachedProperty<object?> RegisterProperty
|
||||||
|
= AvaloniaProperty.RegisterAttached<TopLevels, Visual, object?>("Register");
|
||||||
|
|
||||||
|
private static readonly Dictionary<object, Visual> RegistrationMapper = [];
|
||||||
|
|
||||||
|
static TopLevels()
|
||||||
|
{
|
||||||
|
RegisterProperty.Changed.AddClassHandler<Visual>(RegisterChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static object? GetRegister(AvaloniaObject element)
|
||||||
|
{
|
||||||
|
return element.GetValue(RegisterProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TopLevel? GetTopLevelForContext(object context)
|
||||||
|
{
|
||||||
|
return TopLevel.GetTopLevel(GetVisualForContext(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Visual? GetVisualForContext(object context)
|
||||||
|
{
|
||||||
|
return RegistrationMapper.TryGetValue(context, out var result) ? result : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetRegister(AvaloniaObject element, object value)
|
||||||
|
{
|
||||||
|
element.SetValue(RegisterProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RegisterChanged(Visual sender, AvaloniaPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(sender);
|
||||||
|
|
||||||
|
// Unregister any old registered context
|
||||||
|
if (e.OldValue != null)
|
||||||
|
{
|
||||||
|
RegistrationMapper.Remove(e.OldValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register any new context
|
||||||
|
if (e.NewValue != null)
|
||||||
|
{
|
||||||
|
RegistrationMapper.Add(e.NewValue, sender);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
app/InkForge.Desktop/Services/WorkspaceContext.cs
Normal file
6
app/InkForge.Desktop/Services/WorkspaceContext.cs
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace InkForge.Desktop.Services;
|
||||||
|
|
||||||
|
public class WorkspaceContext
|
||||||
|
{
|
||||||
|
public string DbPath { get; set; }
|
||||||
|
}
|
||||||
36
app/InkForge.Desktop/ViewModels/AppViewModel.cs
Normal file
36
app/InkForge.Desktop/ViewModels/AppViewModel.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
using InkForge.Desktop.Controllers;
|
||||||
|
using InkForge.Desktop.Models;
|
||||||
|
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
|
namespace InkForge.Desktop.ViewModels;
|
||||||
|
|
||||||
|
public class AppViewModel : ReactiveObject
|
||||||
|
{
|
||||||
|
private readonly LandingViewModel _landingViewModel;
|
||||||
|
private readonly WorkspaceController _workspace;
|
||||||
|
private object _view;
|
||||||
|
|
||||||
|
public object View
|
||||||
|
{
|
||||||
|
get => _view;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _view, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppViewModel(WorkspaceController workspace, LandingViewModel landingViewModel)
|
||||||
|
{
|
||||||
|
_workspace = workspace;
|
||||||
|
_landingViewModel = landingViewModel;
|
||||||
|
|
||||||
|
this.WhenAnyValue(v => v._workspace.Workspace).Subscribe(OnWorkspaceChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnWorkspaceChanged(Workspace workspace)
|
||||||
|
{
|
||||||
|
View = workspace switch
|
||||||
|
{
|
||||||
|
null => _landingViewModel,
|
||||||
|
{ } => new WorkspaceViewModel(workspace) // scoped?
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
89
app/InkForge.Desktop/ViewModels/LandingViewModel.cs
Normal file
89
app/InkForge.Desktop/ViewModels/LandingViewModel.cs
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Reactive;
|
||||||
|
|
||||||
|
using Avalonia.Platform.Storage;
|
||||||
|
|
||||||
|
using InkForge.Desktop.Controllers;
|
||||||
|
using InkForge.Desktop.Services;
|
||||||
|
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
|
namespace InkForge.Desktop.ViewModels;
|
||||||
|
|
||||||
|
public class LandingViewModel : ReactiveObject
|
||||||
|
{
|
||||||
|
private ReadOnlyObservableCollection<RecentItemViewModel> _recentItems;
|
||||||
|
private readonly WorkspaceController _workspaceController;
|
||||||
|
|
||||||
|
public ReactiveCommand<Unit, Unit> CreateNew { get; }
|
||||||
|
|
||||||
|
public ReactiveCommand<Unit, Unit> OpenNew { get; }
|
||||||
|
|
||||||
|
public ReadOnlyObservableCollection<RecentItemViewModel> RecentItems => _recentItems;
|
||||||
|
|
||||||
|
public LandingViewModel(WorkspaceController workspaceController)
|
||||||
|
{
|
||||||
|
_workspaceController = workspaceController;
|
||||||
|
CreateNew = ReactiveCommand.CreateFromTask(OnCreateNew);
|
||||||
|
OpenNew = ReactiveCommand.CreateFromTask(OnOpenNew);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnCreateNew()
|
||||||
|
{
|
||||||
|
var storageProvider = this.GetStorageProvider()!;
|
||||||
|
|
||||||
|
var documents = await storageProvider.TryGetWellKnownFolderAsync(WellKnownFolder.Documents);
|
||||||
|
var file = await storageProvider.SaveFilePickerAsync(new FilePickerSaveOptions()
|
||||||
|
{
|
||||||
|
DefaultExtension = ".ifdb",
|
||||||
|
FileTypeChoices =
|
||||||
|
[
|
||||||
|
new FilePickerFileType("InkForge Database File")
|
||||||
|
{
|
||||||
|
Patterns = [ "*.ifdb" ],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
SuggestedStartLocation = documents,
|
||||||
|
Title = "Select InkForge Database Name",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (file?.TryGetLocalPath() is not { } filePath)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _workspaceController.OpenWorkspace(filePath, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnOpenNew()
|
||||||
|
{
|
||||||
|
var storageProvider = this.GetStorageProvider()!;
|
||||||
|
|
||||||
|
var documents = await storageProvider.TryGetWellKnownFolderAsync(WellKnownFolder.Documents);
|
||||||
|
var files = await storageProvider.OpenFilePickerAsync(new FilePickerOpenOptions()
|
||||||
|
{
|
||||||
|
AllowMultiple = false,
|
||||||
|
SuggestedStartLocation = documents,
|
||||||
|
FileTypeFilter =
|
||||||
|
[
|
||||||
|
new FilePickerFileType("InkForge Database File")
|
||||||
|
{
|
||||||
|
Patterns = [ "*.ifdb" ]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
Title = "Open InkForge Database file"
|
||||||
|
});
|
||||||
|
|
||||||
|
if (files.Count != 1)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (files[0].TryGetLocalPath() is not { } filePath)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _workspaceController.OpenWorkspace(filePath, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace InkForge.Common.ViewModels.Landing;
|
namespace InkForge.Desktop.ViewModels;
|
||||||
|
|
||||||
public record class RecentItemViewModel(
|
public record class RecentItemViewModel(
|
||||||
DateTimeOffset Created,
|
DateTimeOffset Created,
|
||||||
15
app/InkForge.Desktop/ViewModels/WorkspaceViewModel.cs
Normal file
15
app/InkForge.Desktop/ViewModels/WorkspaceViewModel.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
using InkForge.Desktop.Models;
|
||||||
|
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
|
namespace InkForge.Desktop.ViewModels;
|
||||||
|
|
||||||
|
public class WorkspaceViewModel : ReactiveObject
|
||||||
|
{
|
||||||
|
private readonly Workspace _workspace;
|
||||||
|
|
||||||
|
public WorkspaceViewModel(Workspace workspace)
|
||||||
|
{
|
||||||
|
_workspace = workspace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,17 +2,21 @@
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:vm="using:InkForge.Common.ViewModels.Landing"
|
xmlns:reactiveui="http://reactiveui.net"
|
||||||
|
xmlns:vm="using:InkForge.Desktop.ViewModels"
|
||||||
|
xmlns:services="using:InkForge.Desktop.Services"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
d:DesignWidth="800"
|
d:DesignWidth="800"
|
||||||
d:DesignHeight="450"
|
d:DesignHeight="450"
|
||||||
x:Class="InkForge.Common.Views.LandingViews.OpenRecentView"
|
x:Class="InkForge.Desktop.Views.LandingView"
|
||||||
x:DataType="vm:OpenRecentViewModel">
|
x:DataType="vm:LandingViewModel"
|
||||||
<Grid RowDefinitions="Auto, *">
|
services:TopLevels.Register="{CompiledBinding}">
|
||||||
|
<Grid RowDefinitions="Auto, *, Auto">
|
||||||
<Label Content="Open Recent"
|
<Label Content="Open Recent"
|
||||||
Grid.Row="0" />
|
Grid.Row="0" />
|
||||||
|
|
||||||
<DataGrid IsReadOnly="true"
|
<DataGrid IsEnabled="False"
|
||||||
|
IsReadOnly="True"
|
||||||
ItemsSource="{CompiledBinding RecentItems}"
|
ItemsSource="{CompiledBinding RecentItems}"
|
||||||
Grid.Row="1">
|
Grid.Row="1">
|
||||||
<DataGrid.Columns>
|
<DataGrid.Columns>
|
||||||
|
|
@ -25,5 +29,14 @@
|
||||||
Binding="{CompiledBinding LastUsed, StringFormat={}{0:d}}" />
|
Binding="{CompiledBinding LastUsed, StringFormat={}{0:d}}" />
|
||||||
</DataGrid.Columns>
|
</DataGrid.Columns>
|
||||||
</DataGrid>
|
</DataGrid>
|
||||||
|
|
||||||
|
<Menu Grid.Row="2">
|
||||||
|
<MenuItem Header="Create New"
|
||||||
|
Command="{CompiledBinding CreateNew}" />
|
||||||
|
<MenuItem Header="Open"
|
||||||
|
IsEnabled="False" />
|
||||||
|
<MenuItem Header="Open File"
|
||||||
|
Command="{CompiledBinding OpenNew}" />
|
||||||
|
</Menu>
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
|
|
||||||
using InkForge.Common.ViewModels;
|
using InkForge.Desktop.ViewModels;
|
||||||
|
|
||||||
namespace InkForge.Common.Views;
|
namespace InkForge.Desktop.Views;
|
||||||
|
|
||||||
public partial class LandingView : ReactiveUserControl<LandingViewModel>
|
public partial class LandingView : ReactiveUserControl<LandingViewModel>
|
||||||
{
|
{
|
||||||
|
|
@ -3,19 +3,13 @@
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:reactiveui="http://reactiveui.net"
|
xmlns:reactiveui="http://reactiveui.net"
|
||||||
xmlns:ifcvm="using:InkForge.Common.ViewModels"
|
xmlns:vm="using:InkForge.Desktop.ViewModels"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
d:DesignWidth="800"
|
d:DesignWidth="800"
|
||||||
d:DesignHeight="450"
|
d:DesignHeight="450"
|
||||||
x:Class="InkForge.Desktop.Views.MainWindow"
|
x:Class="InkForge.Desktop.Views.MainWindow"
|
||||||
x:DataType="ifcvm:AppViewModel"
|
x:DataType="vm:AppViewModel"
|
||||||
Title="InkForge">
|
Title="MainWindow">
|
||||||
<NativeMenu.Menu>
|
|
||||||
<NativeMenu>
|
|
||||||
<NativeMenuItem Header="Test" />
|
|
||||||
</NativeMenu>
|
|
||||||
</NativeMenu.Menu>
|
|
||||||
|
|
||||||
<DockPanel>
|
<DockPanel>
|
||||||
<NativeMenuBar />
|
<NativeMenuBar />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
|
|
||||||
using InkForge.Common.ViewModels;
|
using InkForge.Desktop.ViewModels;
|
||||||
|
|
||||||
namespace InkForge.Desktop.Views;
|
namespace InkForge.Desktop.Views;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,12 @@
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:reactiveui="http://reactiveui.net"
|
||||||
|
xmlns:vm="using:InkForge.Desktop.ViewModels"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
d:DesignWidth="800"
|
d:DesignWidth="800"
|
||||||
d:DesignHeight="450"
|
d:DesignHeight="450"
|
||||||
x:Class="InkForge.Common.Views.LandingViews.CreateWorkspaceView">
|
x:Class="InkForge.Desktop.Views.WorkspaceView"
|
||||||
|
x:DataType="vm:WorkspaceViewModel">
|
||||||
Welcome to Avalonia!
|
Welcome to Avalonia!
|
||||||
</UserControl>
|
</UserControl>
|
||||||
13
app/InkForge.Desktop/Views/WorkspaceView.axaml.cs
Normal file
13
app/InkForge.Desktop/Views/WorkspaceView.axaml.cs
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
|
||||||
|
using InkForge.Desktop.ViewModels;
|
||||||
|
|
||||||
|
namespace InkForge.Desktop.Views;
|
||||||
|
|
||||||
|
public partial class WorkspaceView : ReactiveUserControl<WorkspaceViewModel>
|
||||||
|
{
|
||||||
|
public WorkspaceView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue