Remove .Common-project

Currently of no use
This commit is contained in:
Jöran Malek 2024-02-21 02:17:33 +01:00
parent 232231d20d
commit b1d3ec73c9
31 changed files with 16020 additions and 109 deletions

View file

@ -10,9 +10,13 @@
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="$(AvaloniaVersion)" /> <PackageVersion Include="Avalonia.Controls.DataGrid" Version="$(AvaloniaVersion)" />
<PackageVersion Include="Avalonia.Desktop" Version="$(AvaloniaVersion)" /> <PackageVersion Include="Avalonia.Desktop" Version="$(AvaloniaVersion)" />
<PackageVersion Include="Avalonia.Fonts.Inter" Version="$(AvaloniaVersion)" /> <PackageVersion Include="Avalonia.Fonts.Inter" Version="$(AvaloniaVersion)" />
<PackageVersion Include="Avalonia.Labs.Controls" Version="11.0.3" />
<PackageVersion Include="Avalonia.Labs.Panels" Version="11.0.3" />
<PackageVersion Include="Avalonia.ReactiveUI" Version="$(AvaloniaVersion)" /> <PackageVersion Include="Avalonia.ReactiveUI" Version="$(AvaloniaVersion)" />
<PackageVersion Include="Avalonia.Themes.Fluent" Version="$(AvaloniaVersion)" /> <PackageVersion Include="Avalonia.Themes.Fluent" Version="$(AvaloniaVersion)" />
<PackageVersion Include="Avalonia.Themes.Simple" Version="11.0.9" />
<PackageVersion Include="Dock.Avalonia" Version="11.0.0.5" /> <PackageVersion Include="Dock.Avalonia" Version="11.0.0.5" />
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.5" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="$(DotNetVersion)" /> <PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="$(DotNetVersion)" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.UI" Version="$(DotNetVersion)" /> <PackageVersion Include="Microsoft.AspNetCore.Identity.UI" Version="$(DotNetVersion)" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="$(DotNetVersion)" /> <PackageVersion Include="Microsoft.EntityFrameworkCore" Version="$(DotNetVersion)" />

View file

@ -27,8 +27,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "design", "design", "{C78684
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InkForge.Migrations", "design\InkForge.Migrations\InkForge.Migrations.csproj", "{8DF3397E-2717-49F0-9592-82ABE9327A73}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InkForge.Migrations", "design\InkForge.Migrations\InkForge.Migrations.csproj", "{8DF3397E-2717-49F0-9592-82ABE9327A73}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InkForge.Common", "app\InkForge.Common\InkForge.Common.csproj", "{DCE2DCD6-D15C-4F0D-8D7F-22FF82F62F6F}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -66,10 +64,6 @@ Global
{8DF3397E-2717-49F0-9592-82ABE9327A73}.Debug|Any CPU.Build.0 = Debug|Any CPU {8DF3397E-2717-49F0-9592-82ABE9327A73}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8DF3397E-2717-49F0-9592-82ABE9327A73}.Release|Any CPU.ActiveCfg = Release|Any CPU {8DF3397E-2717-49F0-9592-82ABE9327A73}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8DF3397E-2717-49F0-9592-82ABE9327A73}.Release|Any CPU.Build.0 = Release|Any CPU {8DF3397E-2717-49F0-9592-82ABE9327A73}.Release|Any CPU.Build.0 = Release|Any CPU
{DCE2DCD6-D15C-4F0D-8D7F-22FF82F62F6F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DCE2DCD6-D15C-4F0D-8D7F-22FF82F62F6F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DCE2DCD6-D15C-4F0D-8D7F-22FF82F62F6F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DCE2DCD6-D15C-4F0D-8D7F-22FF82F62F6F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(NestedProjects) = preSolution GlobalSection(NestedProjects) = preSolution
{DD595B76-5FDE-4C37-822E-CB58BBB02C8C} = {C73D8E17-EA0A-4206-91D4-9E5BD63B3DB0} {DD595B76-5FDE-4C37-822E-CB58BBB02C8C} = {C73D8E17-EA0A-4206-91D4-9E5BD63B3DB0}
@ -78,6 +72,5 @@ Global
{5AFA8AD9-9230-4218-BBFD-BD75F1E752DC} = {84CBD204-9573-4472-9334-68FB360BD6ED} {5AFA8AD9-9230-4218-BBFD-BD75F1E752DC} = {84CBD204-9573-4472-9334-68FB360BD6ED}
{F8A7563F-2647-4623-88E7-470D20F25E93} = {A9F8087F-F148-47A5-94AE-F7B6E1D33096} {F8A7563F-2647-4623-88E7-470D20F25E93} = {A9F8087F-F148-47A5-94AE-F7B6E1D33096}
{8DF3397E-2717-49F0-9592-82ABE9327A73} = {C7868400-84D7-45C5-B594-C30777EE5191} {8DF3397E-2717-49F0-9592-82ABE9327A73} = {C7868400-84D7-45C5-B594-C30777EE5191}
{DCE2DCD6-D15C-4F0D-8D7F-22FF82F62F6F} = {84CBD204-9573-4472-9334-68FB360BD6ED}
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

View file

@ -2,10 +2,14 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="InkForge.Desktop.App" x:Class="InkForge.Desktop.App"
RequestedThemeVariant="Default"> RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
<Application.Styles> <Application.Styles>
<FluentTheme /> <FluentTheme />
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/> <StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml" />
</Application.Styles> </Application.Styles>
<Application.Resources>
<FontFamily x:Key="FluentSystemIcons-Filled">/Assets/Fonts#FluentSystemIcons-Filled</FontFamily>
<FontFamily x:Key="FluentSystemIcons-Regular">/Assets/Fonts#FluentSystemIcons-Regular</FontFamily>
</Application.Resources>
</Application> </Application>

View file

@ -2,6 +2,7 @@ using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Metadata;
using InkForge.Desktop.ViewModels; using InkForge.Desktop.ViewModels;
@ -14,6 +15,11 @@ using ReactiveUI;
using Splat; using Splat;
using Splat.Microsoft.Extensions.DependencyInjection; using Splat.Microsoft.Extensions.DependencyInjection;
[assembly: XmlnsPrefix("app:InkForge", "inkforge")]
[assembly: XmlnsDefinition("app:InkForge", "InkForge.Desktop.Controls")]
[assembly: XmlnsDefinition("app:InkForge", "InkForge.Desktop.MarkupExtensions")]
[assembly: XmlnsDefinition("app:InkForge", "InkForge.Desktop.Services")]
namespace InkForge.Desktop; namespace InkForge.Desktop;
public partial class App : Application public partial class App : Application
@ -54,6 +60,7 @@ public partial class App : Application
public override void OnFrameworkInitializationCompleted() public override void OnFrameworkInitializationCompleted()
{ {
// This kills Avalonia VSCode Previewer.
var viewModel = ActivatorUtilities.GetServiceOrCreateInstance<AppViewModel>(ServiceProvider); var viewModel = ActivatorUtilities.GetServiceOrCreateInstance<AppViewModel>(ServiceProvider);
var view = ViewLocator.Current.ResolveView(viewModel)!; var view = ViewLocator.Current.ResolveView(viewModel)!;
view.ViewModel = viewModel; view.ViewModel = viewModel;

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1 @@
1.1.227

View file

@ -1,68 +0,0 @@
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);
}
}

View file

@ -0,0 +1,175 @@
using System.IO.Hashing;
using System.Runtime.InteropServices;
using System.Text.Json;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using Avalonia.Platform;
namespace InkForge.Desktop.Controls;
public class FluentSymbolIcon : IconElement
{
public static readonly StyledProperty<int> IconSizeProperty
= AvaloniaProperty.Register<FluentSymbolIcon, int>(nameof(IconSize), defaultValue: 20);
public static readonly StyledProperty<FontIconStyle> IconStyleProperty
= AvaloniaProperty.Register<FluentSymbolIcon, FontIconStyle>(nameof(IconStyle));
public static readonly StyledProperty<string> SymbolProperty
= AvaloniaProperty.Register<FluentSymbolIcon, string>(nameof(Symbol));
private static readonly Dictionary<(FontIconStyle, uint Key), string> _glyphCache = [];
private static readonly Dictionary<FontIconStyle, FontFamily> _iconFonts = [];
private TextLayout? _textLayout;
public int IconSize
{
get => GetValue(IconSizeProperty);
set => SetValue(IconSizeProperty, value);
}
public FontIconStyle IconStyle
{
get => GetValue(IconStyleProperty);
set => SetValue(IconStyleProperty, value);
}
public string Symbol
{
get => GetValue(SymbolProperty);
set => SetValue(SymbolProperty, value);
}
static FluentSymbolIcon()
{
AffectsMeasure<FluentSymbolIcon>([
IconStyleProperty,
SymbolProperty,
]);
}
public override void Render(DrawingContext context)
{
_textLayout ??= GenerateText();
var dstRect = new Rect(Bounds.Size);
using (context.PushClip(dstRect))
{
var pt = new Point(dstRect.Center.X - _textLayout.Width / 2,
dstRect.Center.Y - _textLayout.Height / 2);
_textLayout.Draw(context, pt);
}
}
protected override Size MeasureOverride(Size availableSize)
{
_textLayout ??= GenerateText();
return new Size(_textLayout.Width, _textLayout.Height);
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
switch (change.Property.Name)
{
case nameof(IconSize):
case nameof(IconStyle):
case nameof(Symbol):
InvalidateSymbolLayout();
break;
}
}
protected override void OnMeasureInvalidated()
{
_textLayout?.Dispose();
_textLayout = null;
base.OnMeasureInvalidated();
}
private TextLayout GenerateText()
{
var glyph = GetIconGlyph(this);
if (!_iconFonts.TryGetValue(IconStyle, out var fontFamily))
{
_iconFonts[IconStyle] = fontFamily = new FontFamily($"avares://InkForge/Assets/Fonts#FluentSystemIcons-{IconStyle}");
}
return new TextLayout(glyph, new Typeface(fontFamily), FontSize, Foreground, TextAlignment.Left);
}
private void InvalidateSymbolLayout()
{
InvalidateMeasure();
}
private static string GetIconGlyph(FluentSymbolIcon icon)
{
ReadOnlySpan<char> glyphKey = $"ic_fluent_{icon.Symbol}_{icon.IconSize:0}";
var hash = Hash(glyphKey, icon.IconStyle);
if (!_glyphCache.TryGetValue((icon.IconStyle, hash), out var glyph))
{
glyph = LoadIcons(icon.IconStyle, hash);
}
if (string.IsNullOrWhiteSpace(glyph))
{
return string.Empty;
}
return glyph;
}
private static uint Hash(ReadOnlySpan<char> key, FontIconStyle iconStyle)
{
return XxHash32.HashToUInt32(MemoryMarshal.AsBytes(key), (int)iconStyle);
}
private static string LoadIcons(FontIconStyle iconStyle, uint key)
{
Optional<string>? glyph = Optional<string>.Empty;
using (var stream = AssetLoader.Open(new($"avares://InkForge/Assets/Fonts/FluentSystemIcons-{iconStyle}.json")))
using (var document = JsonDocument.Parse(stream))
{
foreach (var element in document.RootElement.EnumerateObject())
{
if (element.Value.ValueKind is not JsonValueKind.Number)
{
continue;
}
var typeSeparator = element.Name.LastIndexOf('_');
if (typeSeparator == -1)
{
continue;
}
ReadOnlySpan<char> elementKey = element.Name.AsSpan(0, typeSeparator);
var hash = Hash(elementKey, iconStyle);
var elementGlyph = char.ConvertFromUtf32(element.Value.GetInt32())!;
if (hash == key)
{
glyph = glyph switch
{
{ HasValue: false } => elementGlyph,
_ => default(Optional<string>?)
};
}
_glyphCache[(iconStyle, hash)] = elementGlyph;
}
}
return glyph?.GetValueOrDefault() ?? string.Empty;
}
public enum FontIconStyle
{
Regular, Filled
}
}

View file

@ -1,14 +1,14 @@
using InkForge.Desktop.Services;
using InkForge.Data; using InkForge.Data;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using SmartFormat; using SmartFormat;
using InkForge.Desktop.Data.Options;
namespace InkForge.Desktop.Data; namespace InkForge.Desktop.Data;
public class NoteDbContextFactory(WorkspaceContext context, IConfiguration configuration) : IDbContextFactory<NoteDbContext> public class NoteDbContextFactory(LocalWorkspaceOptions options, IConfiguration configuration) : IDbContextFactory<NoteDbContext>
{ {
private string? _connectionString; private string? _connectionString;
@ -16,7 +16,7 @@ public class NoteDbContextFactory(WorkspaceContext context, IConfiguration confi
{ {
_connectionString ??= Smart.Format(configuration.GetConnectionString("DefaultConnection")!, new _connectionString ??= Smart.Format(configuration.GetConnectionString("DefaultConnection")!, new
{ {
WorkspaceFile = context.DbPath WorkspaceFile = options.DbPath
}); });
DbContextOptionsBuilder<NoteDbContext> builder = new(); DbContextOptionsBuilder<NoteDbContext> builder = new();

View file

@ -0,0 +1,6 @@
namespace InkForge.Desktop.Data.Options;
public class LocalWorkspaceOptions
{
public string DbPath { get; set; } = default!;
}

View file

@ -23,6 +23,7 @@
<PackageReference Include="Microsoft.Extensions.Http" /> <PackageReference Include="Microsoft.Extensions.Http" />
<PackageReference Include="SmartFormat" /> <PackageReference Include="SmartFormat" />
<PackageReference Include="Splat.Microsoft.Extensions.DependencyInjection" /> <PackageReference Include="Splat.Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="System.IO.Hashing" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -30,6 +31,10 @@
<ProjectReference Include="..\..\shared\migrations\InkForge.Sqlite\InkForge.Sqlite.csproj" /> <ProjectReference Include="..\..\shared\migrations\InkForge.Sqlite\InkForge.Sqlite.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<AvaloniaResource Include="Assets\**" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="Properties\appsettings.json" /> <EmbeddedResource Include="Properties\appsettings.json" />
</ItemGroup> </ItemGroup>

View file

@ -0,0 +1,92 @@
using InkForge.Data;
using InkForge.Desktop.Data.Options;
using InkForge.Desktop.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using ReactiveUI;
namespace InkForge.Desktop.Managers;
public class WorkspaceManager(IServiceProvider serviceProvider) : ReactiveObject
{
private readonly IServiceProvider _serviceProvider = serviceProvider;
private Workspace? _workspace;
public Workspace? Workspace
{
get => _workspace;
private set => this.RaiseAndSetIfChanged(ref _workspace, value);
}
public Task CloseWorkspace()
{
_workspace?.Dispose();
Workspace = null;
return Task.CompletedTask;
}
public async Task OpenWorkspace(string path, bool createFile = false)
{
await CloseWorkspace().ConfigureAwait(false);
if (await CreateLocalWorkspace(path, createFile).ConfigureAwait(false) is { } workspace)
{
Workspace = workspace;
}
}
private async ValueTask<Workspace?> CreateLocalWorkspace(string path, bool createFile)
{
FileInfo file = new(path);
if (!(createFile || file.Exists))
{
return null;
}
file.Directory!.Create();
IServiceScope? scope = null;
IWorkspaceAccessor workspaceAccessor;
try
{
scope = _serviceProvider.CreateScope();
var serviceProvider = scope.ServiceProvider;
var options = serviceProvider.GetRequiredService<LocalWorkspaceOptions>();
options.DbPath = path;
workspaceAccessor = serviceProvider.GetRequiredService<IWorkspaceAccessor>();
workspaceAccessor.Workspace = new Workspace(scope)
{
Name = Path.GetFileNameWithoutExtension(file.Name),
Options = options,
};
var dbFactory = serviceProvider.GetRequiredService<IDbContextFactory<NoteDbContext>>();
await using (var dbContext = dbFactory.CreateDbContext())
{
var db = dbContext.Database;
await using var transaction = await db.BeginTransactionAsync().ConfigureAwait(false);
try
{
await db.MigrateAsync().ConfigureAwait(false);
}
catch
{
// Show Error through TopLevels.ActiveTopLevel
await transaction.RollbackAsync().ConfigureAwait(false);
return null;
}
await transaction.CommitAsync().ConfigureAwait(false);
}
scope = null;
}
finally
{
scope?.Dispose();
}
return workspaceAccessor.Workspace;
}
}

View file

@ -0,0 +1,6 @@
namespace InkForge.Desktop.MarkupExtensions;
public class FluentSymbolIconExtension
{
}

View file

@ -1,8 +1,9 @@
using InkForge.Desktop.Controllers;
using InkForge.Desktop.Data;
using InkForge.Desktop.Services;
using InkForge.Desktop.ViewModels;
using InkForge.Data; using InkForge.Data;
using InkForge.Desktop.Data;
using InkForge.Desktop.Data.Options;
using InkForge.Desktop.Managers;
using InkForge.Desktop.Models;
using InkForge.Desktop.ViewModels;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -18,13 +19,13 @@ public static class InkForgeServiceCollections
{ {
services.AddHttpClient(); services.AddHttpClient();
services.AddScoped<IWorkspaceAccessor, WorkspaceAccessor>();
services.AddScoped<IDbContextFactory<NoteDbContext>, NoteDbContextFactory>(); services.AddScoped<IDbContextFactory<NoteDbContext>, NoteDbContextFactory>();
services.AddScoped(s => s.GetRequiredService<IDbContextFactory<NoteDbContext>>().CreateDbContext());
services.AddScoped<WorkspaceContext>(); services.AddScoped<LocalWorkspaceOptions>();
services.AddSingleton<LandingViewModel>(); services.AddSingleton<LandingViewModel>();
services.AddSingleton<WorkspaceController>(); services.AddSingleton<WorkspaceManager>();
Locator.CurrentMutable.RegisterViewsForViewModels(typeof(InkForgeServiceCollections).Assembly); Locator.CurrentMutable.RegisterViewsForViewModels(typeof(InkForgeServiceCollections).Assembly);

View file

@ -1,19 +1,19 @@
namespace Microsoft.Extensions.DependencyInjection namespace Microsoft.Extensions.DependencyInjection
{ {
public static class TypeFactory<TFactory, T> public static class TypeFactory<TArguments, T>
where TFactory : struct, IObjectParameters<TFactory> where TArguments : IFactoryArguments<TArguments>
{ {
private static ObjectFactory<T>? s_objectFactory; private static ObjectFactory<T>? s_objectFactory;
public static T Create(in TFactory factory, IServiceProvider serviceProvider) public static T Create(IServiceProvider serviceProvider, in TArguments factory)
{ {
s_objectFactory ??= ActivatorUtilities.CreateFactory<T>(TFactory.Types); s_objectFactory ??= ActivatorUtilities.CreateFactory<T>(TArguments.Types);
return s_objectFactory(serviceProvider, (object[])factory); return s_objectFactory(serviceProvider, (object[])factory);
} }
} }
public interface IObjectParameters<T> public interface IFactoryArguments<T>
where T : struct, IObjectParameters<T> where T : IFactoryArguments<T>
{ {
abstract static Type[] Types { get; } abstract static Type[] Types { get; }

View file

@ -1,8 +1,45 @@
using InkForge.Data;
using InkForge.Desktop.Data.Options;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
namespace InkForge.Desktop.Models; namespace InkForge.Desktop.Models;
public class Workspace(IServiceScope scope) public sealed class Workspace : IDisposable
{ {
public IServiceProvider ServiceProvider => scope.ServiceProvider; private readonly IDbContextFactory<NoteDbContext> _dbContextFactory;
private bool _disposedValue;
private IServiceScope? _scope;
public string Name { get; set; } = default!;
public LocalWorkspaceOptions Options { get; set; } = default!;
public IServiceProvider Services => _scope!.ServiceProvider;
public Workspace(IServiceScope scope)
{
_scope = scope;
_dbContextFactory = Services.GetRequiredService<IDbContextFactory<NoteDbContext>>();
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!_disposedValue)
{
{
_scope!.Dispose();
}
_scope = null;
_disposedValue = true;
}
}
} }

View file

@ -0,0 +1,12 @@
namespace InkForge.Desktop.Models
{
public interface IWorkspaceAccessor
{
Workspace? Workspace { get; set; }
}
public class WorkspaceAccessor : IWorkspaceAccessor
{
public Workspace? Workspace { get; set; }
}
}

View file

@ -1,5 +1,6 @@
using Avalonia; using Avalonia;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Metadata;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
using Avalonia.Threading; using Avalonia.Threading;

View file

@ -1,3 +1,5 @@
using System.Reactive.Linq;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
@ -7,12 +9,14 @@ public class TopLevels
{ {
public static readonly AttachedProperty<object?> RegisterProperty public static readonly AttachedProperty<object?> RegisterProperty
= AvaloniaProperty.RegisterAttached<TopLevels, Visual, object?>("Register"); = AvaloniaProperty.RegisterAttached<TopLevels, Visual, object?>("Register");
private static readonly Dictionary<object, Visual> RegistrationMapper = []; private static readonly Dictionary<object, Visual> RegistrationMapper = [];
public static TopLevel? ActiveTopLevel { get; private set; }
static TopLevels() static TopLevels()
{ {
RegisterProperty.Changed.AddClassHandler<Visual>(RegisterChanged); RegisterProperty.Changed.AddClassHandler<Visual>(RegisterChanged);
WindowBase.IsActiveProperty.Changed.Subscribe(WindowActiveChanged);
} }
public static object? GetRegister(AvaloniaObject element) public static object? GetRegister(AvaloniaObject element)
@ -51,4 +55,14 @@ public class TopLevels
RegistrationMapper.Add(e.NewValue, sender); RegistrationMapper.Add(e.NewValue, sender);
} }
} }
private static void WindowActiveChanged(AvaloniaPropertyChangedEventArgs<bool> e)
{
ActiveTopLevel = (e.GetOldAndNewValue<bool>(), e.Sender) switch
{
((false, true), TopLevel topLevel) => topLevel,
((true, false), { } topLevel) when topLevel == ActiveTopLevel => null,
_ => ActiveTopLevel,
};
}
} }

View file

@ -1,4 +1,4 @@
using InkForge.Desktop.Controllers; using InkForge.Desktop.Managers;
using InkForge.Desktop.Models; using InkForge.Desktop.Models;
using ReactiveUI; using ReactiveUI;
@ -8,7 +8,7 @@ namespace InkForge.Desktop.ViewModels;
public class AppViewModel : ReactiveObject public class AppViewModel : ReactiveObject
{ {
private readonly LandingViewModel _landingViewModel; private readonly LandingViewModel _landingViewModel;
private readonly WorkspaceController _workspace; private readonly WorkspaceManager _workspace;
private object _view; private object _view;
public object View public object View
@ -17,7 +17,7 @@ public class AppViewModel : ReactiveObject
set => this.RaiseAndSetIfChanged(ref _view, value); set => this.RaiseAndSetIfChanged(ref _view, value);
} }
public AppViewModel(WorkspaceController workspace, LandingViewModel landingViewModel) public AppViewModel(WorkspaceManager workspace, LandingViewModel landingViewModel)
{ {
_workspace = workspace; _workspace = workspace;
_landingViewModel = landingViewModel; _landingViewModel = landingViewModel;

View file

@ -3,7 +3,7 @@ using System.Reactive;
using Avalonia.Platform.Storage; using Avalonia.Platform.Storage;
using InkForge.Desktop.Controllers; using InkForge.Desktop.Managers;
using InkForge.Desktop.Services; using InkForge.Desktop.Services;
using ReactiveUI; using ReactiveUI;
@ -13,7 +13,7 @@ namespace InkForge.Desktop.ViewModels;
public class LandingViewModel : ReactiveObject public class LandingViewModel : ReactiveObject
{ {
private ReadOnlyObservableCollection<RecentItemViewModel> _recentItems; private ReadOnlyObservableCollection<RecentItemViewModel> _recentItems;
private readonly WorkspaceController _workspaceController; private readonly WorkspaceManager _workspaceController;
public ReactiveCommand<Unit, Unit> CreateNew { get; } public ReactiveCommand<Unit, Unit> CreateNew { get; }
@ -21,7 +21,7 @@ public class LandingViewModel : ReactiveObject
public ReadOnlyObservableCollection<RecentItemViewModel> RecentItems => _recentItems; public ReadOnlyObservableCollection<RecentItemViewModel> RecentItems => _recentItems;
public LandingViewModel(WorkspaceController workspaceController) public LandingViewModel(WorkspaceManager workspaceController)
{ {
_workspaceController = workspaceController; _workspaceController = workspaceController;
CreateNew = ReactiveCommand.CreateFromTask(OnCreateNew); CreateNew = ReactiveCommand.CreateFromTask(OnCreateNew);

View file

@ -7,9 +7,13 @@ namespace InkForge.Desktop.ViewModels;
public class WorkspaceViewModel : ReactiveObject public class WorkspaceViewModel : ReactiveObject
{ {
private readonly Workspace _workspace; private readonly Workspace _workspace;
private readonly ObservableAsPropertyHelper<string> _workspaceNameProperty;
public string WorkspaceName => _workspaceNameProperty.Value;
public WorkspaceViewModel(Workspace workspace) public WorkspaceViewModel(Workspace workspace)
{ {
_workspace = workspace; _workspace = workspace;
_workspaceNameProperty = this.WhenAnyValue(v => v._workspace.Name).ToProperty(this, nameof(WorkspaceName));
} }
} }

View file

@ -4,13 +4,13 @@
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:vm="using:InkForge.Desktop.ViewModels" xmlns:vm="using:InkForge.Desktop.ViewModels"
xmlns:services="using:InkForge.Desktop.Services" xmlns:inkforge="app:InkForge"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignWidth="800" d:DesignWidth="800"
d:DesignHeight="450" d:DesignHeight="450"
x:Class="InkForge.Desktop.Views.LandingView" x:Class="InkForge.Desktop.Views.LandingView"
x:DataType="vm:LandingViewModel" x:DataType="vm:LandingViewModel"
services:TopLevels.Register="{CompiledBinding}"> inkforge:TopLevels.Register="{CompiledBinding}">
<Grid RowDefinitions="Auto, *, Auto"> <Grid RowDefinitions="Auto, *, Auto">
<Label Content="Open Recent" <Label Content="Open Recent"
Grid.Row="0" /> Grid.Row="0" />

View file

@ -3,13 +3,15 @@
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:inkforge="app:InkForge"
xmlns:vm="using:InkForge.Desktop.ViewModels" xmlns:vm="using:InkForge.Desktop.ViewModels"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignWidth="800" Width="800"
d:DesignHeight="450" Height="450"
x:Class="InkForge.Desktop.Views.MainWindow" x:Class="InkForge.Desktop.Views.MainWindow"
x:DataType="vm:AppViewModel" x:DataType="vm:AppViewModel"
Title="MainWindow"> Title="MainWindow"
inkforge:TopLevels.Register="{CompiledBinding}">
<DockPanel> <DockPanel>
<NativeMenuBar /> <NativeMenuBar />

View file

@ -1,3 +1,4 @@
using Avalonia.Input;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
using InkForge.Desktop.ViewModels; using InkForge.Desktop.ViewModels;

View file

@ -3,11 +3,67 @@
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:inkforge="app:InkForge"
xmlns:vm="using:InkForge.Desktop.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.WorkspaceView" x:Class="InkForge.Desktop.Views.WorkspaceView"
x:DataType="vm:WorkspaceViewModel"> x:DataType="vm:WorkspaceViewModel"
Welcome to Avalonia! inkforge:TopLevels.Register="{CompiledBinding}">
<SplitView IsPaneOpen="true"
DisplayMode="Inline"
OpenPaneLength="300">
<SplitView.Pane>
<DockPanel x:Name="FilesView"
Background="Transparent">
<Grid ColumnDefinitions="*, Auto"
DockPanel.Dock="Top">
<TextBlock Text="Notes"
FontWeight="Bold"
Margin="3"
Grid.Column="0" />
<StackPanel x:Name="ToolBar"
Orientation="Horizontal"
Spacing="3"
Grid.Column="1">
<Button>
<inkforge:FluentSymbolIcon Symbol="document_add" />
</Button>
<Button>
<inkforge:FluentSymbolIcon Symbol="arrow_clockwise" />
</Button>
<Button>
<inkforge:FluentSymbolIcon Symbol="subtract_square_multiple" />
</Button>
<StackPanel.Styles>
<Style Selector="#ToolBar > :is(TemplatedControl)">
<Setter Property="Background"
Value="Transparent" />
<Setter Property="Padding"
Value="1" />
<Setter Property="VerticalAlignment"
Value="Center" />
</Style>
<Style Selector="#FilesView:not(:pointerover) StackPanel">
<Setter Property="IsVisible"
Value="False" />
</Style>
</StackPanel.Styles>
</StackPanel>
</Grid>
<TreeView ScrollViewer.VerticalScrollBarVisibility="Visible" />
</DockPanel>
</SplitView.Pane>
<TabControl>
<TabItem Header="Some Note.md">
Hello There!
</TabItem>
</TabControl>
</SplitView>
</UserControl> </UserControl>

View file

@ -8,4 +8,11 @@ Performs OpenAPI calls to Sync server.
*Consideration*: Allow for syncing to local backend. *Consideration*: Allow for syncing to local backend.
## Technical
Figure out a way to get navigation/commands relative to the window they are in.<br>
I.e. make Windows scoped, then get a shell-object in each Window, which then
consumes a Menu-service, which navigates the shell-object tree to find eligible
menu-objects to present.
## Research ## Research