Remove .Common-project
Currently of no use
This commit is contained in:
parent
232231d20d
commit
b1d3ec73c9
31 changed files with 16020 additions and 109 deletions
|
|
@ -10,9 +10,13 @@
|
|||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="$(AvaloniaVersion)" />
|
||||
<PackageVersion Include="Avalonia.Desktop" 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.Themes.Fluent" Version="$(AvaloniaVersion)" />
|
||||
<PackageVersion Include="Avalonia.Themes.Simple" Version="11.0.9" />
|
||||
<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.UI" Version="$(DotNetVersion)" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="$(DotNetVersion)" />
|
||||
|
|
|
|||
|
|
@ -27,8 +27,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "design", "design", "{C78684
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InkForge.Migrations", "design\InkForge.Migrations\InkForge.Migrations.csproj", "{8DF3397E-2717-49F0-9592-82ABE9327A73}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InkForge.Common", "app\InkForge.Common\InkForge.Common.csproj", "{DCE2DCD6-D15C-4F0D-8D7F-22FF82F62F6F}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
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}.Release|Any CPU.ActiveCfg = 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
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{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}
|
||||
{F8A7563F-2647-4623-88E7-470D20F25E93} = {A9F8087F-F148-47A5-94AE-F7B6E1D33096}
|
||||
{8DF3397E-2717-49F0-9592-82ABE9327A73} = {C7868400-84D7-45C5-B594-C30777EE5191}
|
||||
{DCE2DCD6-D15C-4F0D-8D7F-22FF82F62F6F} = {84CBD204-9573-4472-9334-68FB360BD6ED}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
|||
|
|
@ -2,10 +2,14 @@
|
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="InkForge.Desktop.App"
|
||||
RequestedThemeVariant="Default">
|
||||
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
|
||||
|
||||
<Application.Styles>
|
||||
<FluentTheme />
|
||||
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml" />
|
||||
</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>
|
||||
|
|
@ -2,6 +2,7 @@ using Avalonia;
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Metadata;
|
||||
|
||||
using InkForge.Desktop.ViewModels;
|
||||
|
||||
|
|
@ -14,6 +15,11 @@ using ReactiveUI;
|
|||
using Splat;
|
||||
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;
|
||||
|
||||
public partial class App : Application
|
||||
|
|
@ -54,6 +60,7 @@ public partial class App : Application
|
|||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
// This kills Avalonia VSCode Previewer.
|
||||
var viewModel = ActivatorUtilities.GetServiceOrCreateInstance<AppViewModel>(ServiceProvider);
|
||||
var view = ViewLocator.Current.ResolveView(viewModel)!;
|
||||
view.ViewModel = viewModel;
|
||||
|
|
|
|||
7836
app/InkForge.Desktop/Assets/Fonts/FluentSystemIcons-Filled.json
Normal file
7836
app/InkForge.Desktop/Assets/Fonts/FluentSystemIcons-Filled.json
Normal file
File diff suppressed because it is too large
Load diff
BIN
app/InkForge.Desktop/Assets/Fonts/FluentSystemIcons-Filled.ttf
Normal file
BIN
app/InkForge.Desktop/Assets/Fonts/FluentSystemIcons-Filled.ttf
Normal file
Binary file not shown.
7715
app/InkForge.Desktop/Assets/Fonts/FluentSystemIcons-Regular.json
Normal file
7715
app/InkForge.Desktop/Assets/Fonts/FluentSystemIcons-Regular.json
Normal file
File diff suppressed because it is too large
Load diff
BIN
app/InkForge.Desktop/Assets/Fonts/FluentSystemIcons-Regular.ttf
Normal file
BIN
app/InkForge.Desktop/Assets/Fonts/FluentSystemIcons-Regular.ttf
Normal file
Binary file not shown.
1
app/InkForge.Desktop/Assets/Fonts/FluentSystemIcons.txt
Normal file
1
app/InkForge.Desktop/Assets/Fonts/FluentSystemIcons.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
1.1.227
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
175
app/InkForge.Desktop/Controls/FluentSymbolIcon.cs
Normal file
175
app/InkForge.Desktop/Controls/FluentSymbolIcon.cs
Normal 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
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
using InkForge.Desktop.Services;
|
||||
using InkForge.Data;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
using SmartFormat;
|
||||
using InkForge.Desktop.Data.Options;
|
||||
|
||||
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;
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ public class NoteDbContextFactory(WorkspaceContext context, IConfiguration confi
|
|||
{
|
||||
_connectionString ??= Smart.Format(configuration.GetConnectionString("DefaultConnection")!, new
|
||||
{
|
||||
WorkspaceFile = context.DbPath
|
||||
WorkspaceFile = options.DbPath
|
||||
});
|
||||
|
||||
DbContextOptionsBuilder<NoteDbContext> builder = new();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
namespace InkForge.Desktop.Data.Options;
|
||||
|
||||
public class LocalWorkspaceOptions
|
||||
{
|
||||
public string DbPath { get; set; } = default!;
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@
|
|||
<PackageReference Include="Microsoft.Extensions.Http" />
|
||||
<PackageReference Include="SmartFormat" />
|
||||
<PackageReference Include="Splat.Microsoft.Extensions.DependencyInjection" />
|
||||
<PackageReference Include="System.IO.Hashing" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -30,6 +31,10 @@
|
|||
<ProjectReference Include="..\..\shared\migrations\InkForge.Sqlite\InkForge.Sqlite.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AvaloniaResource Include="Assets\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Properties\appsettings.json" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
92
app/InkForge.Desktop/Managers/WorkspaceManager.cs
Normal file
92
app/InkForge.Desktop/Managers/WorkspaceManager.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
namespace InkForge.Desktop.MarkupExtensions;
|
||||
|
||||
public class FluentSymbolIconExtension
|
||||
{
|
||||
|
||||
}
|
||||
|
|
@ -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.Desktop.Data;
|
||||
using InkForge.Desktop.Data.Options;
|
||||
using InkForge.Desktop.Managers;
|
||||
using InkForge.Desktop.Models;
|
||||
using InkForge.Desktop.ViewModels;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
|
|
@ -18,13 +19,13 @@ public static class InkForgeServiceCollections
|
|||
{
|
||||
services.AddHttpClient();
|
||||
|
||||
services.AddScoped<IWorkspaceAccessor, WorkspaceAccessor>();
|
||||
services.AddScoped<IDbContextFactory<NoteDbContext>, NoteDbContextFactory>();
|
||||
services.AddScoped(s => s.GetRequiredService<IDbContextFactory<NoteDbContext>>().CreateDbContext());
|
||||
|
||||
services.AddScoped<WorkspaceContext>();
|
||||
services.AddScoped<LocalWorkspaceOptions>();
|
||||
|
||||
services.AddSingleton<LandingViewModel>();
|
||||
services.AddSingleton<WorkspaceController>();
|
||||
services.AddSingleton<WorkspaceManager>();
|
||||
|
||||
Locator.CurrentMutable.RegisterViewsForViewModels(typeof(InkForgeServiceCollections).Assembly);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
public static class TypeFactory<TFactory, T>
|
||||
where TFactory : struct, IObjectParameters<TFactory>
|
||||
public static class TypeFactory<TArguments, T>
|
||||
where TArguments : IFactoryArguments<TArguments>
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public interface IObjectParameters<T>
|
||||
where T : struct, IObjectParameters<T>
|
||||
public interface IFactoryArguments<T>
|
||||
where T : IFactoryArguments<T>
|
||||
{
|
||||
abstract static Type[] Types { get; }
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,45 @@
|
|||
using InkForge.Data;
|
||||
using InkForge.Desktop.Data.Options;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
12
app/InkForge.Desktop/Models/WorkspaceAccessor.cs
Normal file
12
app/InkForge.Desktop/Models/WorkspaceAccessor.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Metadata;
|
||||
using Avalonia.ReactiveUI;
|
||||
using Avalonia.Threading;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using System.Reactive.Linq;
|
||||
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
|
||||
|
|
@ -7,12 +9,14 @@ public class TopLevels
|
|||
{
|
||||
public static readonly AttachedProperty<object?> RegisterProperty
|
||||
= AvaloniaProperty.RegisterAttached<TopLevels, Visual, object?>("Register");
|
||||
|
||||
private static readonly Dictionary<object, Visual> RegistrationMapper = [];
|
||||
|
||||
public static TopLevel? ActiveTopLevel { get; private set; }
|
||||
|
||||
static TopLevels()
|
||||
{
|
||||
RegisterProperty.Changed.AddClassHandler<Visual>(RegisterChanged);
|
||||
WindowBase.IsActiveProperty.Changed.Subscribe(WindowActiveChanged);
|
||||
}
|
||||
|
||||
public static object? GetRegister(AvaloniaObject element)
|
||||
|
|
@ -51,4 +55,14 @@ public class TopLevels
|
|||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using InkForge.Desktop.Controllers;
|
||||
using InkForge.Desktop.Managers;
|
||||
using InkForge.Desktop.Models;
|
||||
|
||||
using ReactiveUI;
|
||||
|
|
@ -8,7 +8,7 @@ namespace InkForge.Desktop.ViewModels;
|
|||
public class AppViewModel : ReactiveObject
|
||||
{
|
||||
private readonly LandingViewModel _landingViewModel;
|
||||
private readonly WorkspaceController _workspace;
|
||||
private readonly WorkspaceManager _workspace;
|
||||
private object _view;
|
||||
|
||||
public object View
|
||||
|
|
@ -17,7 +17,7 @@ public class AppViewModel : ReactiveObject
|
|||
set => this.RaiseAndSetIfChanged(ref _view, value);
|
||||
}
|
||||
|
||||
public AppViewModel(WorkspaceController workspace, LandingViewModel landingViewModel)
|
||||
public AppViewModel(WorkspaceManager workspace, LandingViewModel landingViewModel)
|
||||
{
|
||||
_workspace = workspace;
|
||||
_landingViewModel = landingViewModel;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ using System.Reactive;
|
|||
|
||||
using Avalonia.Platform.Storage;
|
||||
|
||||
using InkForge.Desktop.Controllers;
|
||||
using InkForge.Desktop.Managers;
|
||||
using InkForge.Desktop.Services;
|
||||
|
||||
using ReactiveUI;
|
||||
|
|
@ -13,7 +13,7 @@ namespace InkForge.Desktop.ViewModels;
|
|||
public class LandingViewModel : ReactiveObject
|
||||
{
|
||||
private ReadOnlyObservableCollection<RecentItemViewModel> _recentItems;
|
||||
private readonly WorkspaceController _workspaceController;
|
||||
private readonly WorkspaceManager _workspaceController;
|
||||
|
||||
public ReactiveCommand<Unit, Unit> CreateNew { get; }
|
||||
|
||||
|
|
@ -21,7 +21,7 @@ public class LandingViewModel : ReactiveObject
|
|||
|
||||
public ReadOnlyObservableCollection<RecentItemViewModel> RecentItems => _recentItems;
|
||||
|
||||
public LandingViewModel(WorkspaceController workspaceController)
|
||||
public LandingViewModel(WorkspaceManager workspaceController)
|
||||
{
|
||||
_workspaceController = workspaceController;
|
||||
CreateNew = ReactiveCommand.CreateFromTask(OnCreateNew);
|
||||
|
|
|
|||
|
|
@ -7,9 +7,13 @@ namespace InkForge.Desktop.ViewModels;
|
|||
public class WorkspaceViewModel : ReactiveObject
|
||||
{
|
||||
private readonly Workspace _workspace;
|
||||
private readonly ObservableAsPropertyHelper<string> _workspaceNameProperty;
|
||||
|
||||
public string WorkspaceName => _workspaceNameProperty.Value;
|
||||
|
||||
public WorkspaceViewModel(Workspace workspace)
|
||||
{
|
||||
_workspace = workspace;
|
||||
_workspaceNameProperty = this.WhenAnyValue(v => v._workspace.Name).ToProperty(this, nameof(WorkspaceName));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@
|
|||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:reactiveui="http://reactiveui.net"
|
||||
xmlns:vm="using:InkForge.Desktop.ViewModels"
|
||||
xmlns:services="using:InkForge.Desktop.Services"
|
||||
xmlns:inkforge="app:InkForge"
|
||||
mc:Ignorable="d"
|
||||
d:DesignWidth="800"
|
||||
d:DesignHeight="450"
|
||||
x:Class="InkForge.Desktop.Views.LandingView"
|
||||
x:DataType="vm:LandingViewModel"
|
||||
services:TopLevels.Register="{CompiledBinding}">
|
||||
inkforge:TopLevels.Register="{CompiledBinding}">
|
||||
<Grid RowDefinitions="Auto, *, Auto">
|
||||
<Label Content="Open Recent"
|
||||
Grid.Row="0" />
|
||||
|
|
|
|||
|
|
@ -3,13 +3,15 @@
|
|||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:reactiveui="http://reactiveui.net"
|
||||
xmlns:inkforge="app:InkForge"
|
||||
xmlns:vm="using:InkForge.Desktop.ViewModels"
|
||||
mc:Ignorable="d"
|
||||
d:DesignWidth="800"
|
||||
d:DesignHeight="450"
|
||||
Width="800"
|
||||
Height="450"
|
||||
x:Class="InkForge.Desktop.Views.MainWindow"
|
||||
x:DataType="vm:AppViewModel"
|
||||
Title="MainWindow">
|
||||
Title="MainWindow"
|
||||
inkforge:TopLevels.Register="{CompiledBinding}">
|
||||
<DockPanel>
|
||||
<NativeMenuBar />
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using Avalonia.Input;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
using InkForge.Desktop.ViewModels;
|
||||
|
|
|
|||
|
|
@ -3,11 +3,67 @@
|
|||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:reactiveui="http://reactiveui.net"
|
||||
xmlns:inkforge="app:InkForge"
|
||||
xmlns:vm="using:InkForge.Desktop.ViewModels"
|
||||
mc:Ignorable="d"
|
||||
d:DesignWidth="800"
|
||||
d:DesignHeight="450"
|
||||
x:Class="InkForge.Desktop.Views.WorkspaceView"
|
||||
x:DataType="vm:WorkspaceViewModel">
|
||||
Welcome to Avalonia!
|
||||
x:DataType="vm:WorkspaceViewModel"
|
||||
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>
|
||||
|
|
@ -8,4 +8,11 @@ Performs OpenAPI calls to Sync server.
|
|||
|
||||
*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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue