Replace ReactiveUI

This commit is contained in:
Jöran Malek 2024-05-02 21:44:13 +02:00
parent 43b4d50e43
commit 5584ab4ec8
41 changed files with 472 additions and 1013 deletions

View file

@ -5,13 +5,15 @@
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Avalonia" Version="11.0.10" />
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.0.6" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.0.10" />
<PackageVersion Include="Avalonia.Desktop" Version="11.0.10" />
<PackageVersion Include="Avalonia.Fonts.Inter" Version="11.0.10" />
<PackageVersion Include="Avalonia.ReactiveUI" Version="11.0.10" />
<PackageVersion Include="Avalonia.Themes.Fluent" Version="11.0.10" />
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.2.2" />
<PackageVersion Include="Dock.Avalonia" Version="11.0.0.7" />
<PackageVersion Include="Dock.Model.ReactiveUI" Version="11.0.0.7" />
<PackageVersion Include="Dock.Model.Mvvm" Version="11.0.0.7" />
<PackageVersion Include="DynamicData" Version="8.4.1" />
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.5" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.3" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.UI" Version="8.0.3" />
@ -26,8 +28,9 @@
<PackageVersion Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageVersion Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="8.0.1" />
<PackageVersion Include="SmartFormat" Version="3.3.2" />
<PackageVersion Include="Splat" Version="15.0.1" />
<PackageVersion Include="Splat.Microsoft.Extensions.DependencyInjection" Version="14.8.12" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageVersion Include="System.IO.Hashing" Version="8.0.0" />
</ItemGroup>
</Project>
</Project>

View file

@ -5,6 +5,7 @@
<Application.Styles>
<FluentTheme />
<StyleInclude Source="avares://AvaloniaEdit/Themes/Fluent/AvaloniaEdit.xaml" />
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml" />
<DockFluentTheme />
</Application.Styles>
@ -13,4 +14,4 @@
<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

@ -8,8 +8,6 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using ReactiveUI;
using Splat;
using Splat.Microsoft.Extensions.DependencyInjection;
@ -47,7 +45,6 @@ public partial class App : Application
services.UseMicrosoftDependencyResolver();
Locator.CurrentMutable.InitializeSplat();
Locator.CurrentMutable.InitializeReactiveUI();
services.AddInkForge();
}

View file

@ -1,3 +1,4 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
@ -7,8 +8,6 @@ using InkForge.Desktop.ViewModels.Workspaces;
using InkForge.Desktop.Views.Documents;
using InkForge.Desktop.Views.Workspaces;
using ReactiveUI;
namespace InkForge.Desktop;
public class AppViewLocator : IDataTemplate
@ -19,6 +18,7 @@ public class AppViewLocator : IDataTemplate
return param switch
#pragma warning restore CS8509 // The switch expression does not handle all possible values of its input type (it is not exhaustive).
{
NoteEditDocumentViewModel viewModel => _(new NoteEditDocument(), viewModel),
ViewModels.Tools.WorkspaceTool viewModel => _(new Views.Tools.WorkspaceTool(), viewModel),
WelcomePageDocumentViewModel viewModel => _(new WelcomePageDocument(), viewModel),
WorkspaceViewModel viewModel => _(new WorkspaceView(), viewModel),
@ -26,9 +26,9 @@ public class AppViewLocator : IDataTemplate
static TView _<TView, TViewModel>(TView view, TViewModel viewModel)
where TViewModel : class
where TView : IViewFor<TViewModel>
where TView : StyledElement
{
view.ViewModel = viewModel;
view.DataContext = viewModel;
return view;
}
}
@ -36,6 +36,7 @@ public class AppViewLocator : IDataTemplate
public bool Match(object? data)
{
return data is
NoteEditDocumentViewModel or
RecentItemViewModel or
ViewModels.Tools.WorkspaceTool or
WelcomePageDocumentViewModel or

View file

@ -12,18 +12,21 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia.AvaloniaEdit" />
<PackageReference Include="Avalonia.Controls.DataGrid" />
<PackageReference Include="Avalonia.Desktop" />
<PackageReference Include="Avalonia.Fonts.Inter" />
<PackageReference Include="Avalonia.ReactiveUI" />
<PackageReference Include="Avalonia.Themes.Fluent" />
<PackageReference Include="CommunityToolkit.Mvvm" />
<PackageReference Include="Dock.Avalonia" />
<PackageReference Include="Dock.Model.ReactiveUI" />
<PackageReference Include="Dock.Model.Mvvm" />
<PackageReference Include="DynamicData" />
<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" />
<PackageReference Include="Splat.Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="System.IO.Hashing" />
</ItemGroup>
@ -41,4 +44,4 @@
<EmbeddedResource Include="Properties\appsettings.json" />
</ItemGroup>
</Project>
</Project>

View file

@ -1,84 +1,67 @@
using Dock.Model.ReactiveUI;
using Avalonia;
using Dock.Model.Controls;
using Dock.Model.Core;
using Dock.Model.ReactiveUI.Controls;
using Dock.Avalonia.Controls;
using Dock.Model.Mvvm;
using Dock.Model.Mvvm.Controls;
using DynamicData.Binding;
using InkForge.Desktop.Managers;
using InkForge.Desktop.Models;
using InkForge.Desktop.ViewModels.Documents;
using InkForge.Desktop.ViewModels.Tools;
using Microsoft.Extensions.DependencyInjection;
using Avalonia;
using InkForge.Desktop.ViewModels;
namespace InkForge.Desktop;
public class InkForgeFactory : Factory
{
private readonly IDocumentDock _documentDock;
private readonly IDock _mainDock;
private readonly IRootDock _rootDock;
private readonly ViewModels.Tools.WorkspaceTool _workspaceTool;
private readonly WelcomePageDocumentViewModel _welcomePage;
private readonly WorkspaceTool _workspaceTool;
public InkForgeFactory()
public InkForgeFactory(WorkspaceManager workspace)
{
_rootDock = new RootDock
{
IsCollapsable = false,
};
_documentDock = new DocumentDock
{
Id = "Documents",
Title = "Documents",
CanCreateDocument = false,
IsCollapsable = false,
Proportion = double.NaN,
};
_rootDock = CreateRootDock();
_mainDock = CreateDockDock();
_mainDock.IsCollapsable = false;
_mainDock.CanClose = false;
_welcomePage = CreateWelcomePageDocumentViewModel();
_workspaceTool = CreateWorkspaceTool();
workspace.WhenValueChanged(m => m.Workspace).Subscribe(OnWorkspaceChanged);
}
public override IRootDock CreateLayout()
{
ProportionalDock workspaceLayout = new()
ToolDock toolDock = new()
{
Proportion = 0.3,
Alignment = Alignment.Left,
Proportion = 0.25,
VisibleDockables = [_workspaceTool],
};
ProportionalDock windowLayoutContent = new()
{
Orientation = Orientation.Horizontal,
IsCollapsable = false,
VisibleDockables = [workspaceLayout, new ProportionalDockSplitter(), _documentDock]
VisibleDockables = [toolDock, new ProportionalDockSplitter(), _mainDock]
};
RootDock windowLayout = new()
{
Title = "Default",
IsCollapsable = false,
VisibleDockables = [windowLayoutContent],
ActiveDockable = windowLayoutContent,
};
_rootDock.VisibleDockables = [windowLayout];
_rootDock.ActiveDockable = windowLayout;
_rootDock.DefaultDockable = windowLayout;
_rootDock.VisibleDockables = [windowLayoutContent];
_rootDock.DefaultDockable = windowLayoutContent;
return _rootDock;
}
public override void InitLayout(IDockable layout)
private static WelcomePageDocumentViewModel CreateWelcomePageDocumentViewModel()
{
DockableLocator = new Dictionary<string, Func<IDockable?>>
{
["Root"] = () => _rootDock,
["Documents"] = () => _documentDock,
["Workspace"] = () => _workspaceTool,
};
HostWindowLocator = new Dictionary<string, Func<IHostWindow?>>
{
[nameof(IDockWindow)] = () => new HostWindow()
};
base.InitLayout(layout);
return ActivatorUtilities.CreateInstance<WelcomePageDocumentViewModel>(
Application.Current!.GetValue(App.ServiceProviderProperty)
);
}
private static ViewModels.Tools.WorkspaceTool CreateWorkspaceTool()
@ -87,4 +70,16 @@ public class InkForgeFactory : Factory
Application.Current!.GetValue(App.ServiceProviderProperty)
);
}
private void OnWorkspaceChanged(Workspace? workspace)
{
IDockable dock = workspace switch
{
null => _welcomePage,
_ => workspace.Services.GetRequiredService<DocumentManager>().Dock,
};
AddDockable(_mainDock, dock);
CloseOtherDockables(dock);
}
}

View file

@ -1,48 +1,38 @@
using Avalonia;
using AvaloniaEdit.Document;
using Dock.Model.Core;
using CommunityToolkit.Mvvm.Input;
using Dock.Model.Controls;
using InkForge.Desktop.Models;
using InkForge.Desktop.ViewModels.Documents;
using Microsoft.Extensions.DependencyInjection;
using ReactiveUI;
namespace InkForge.Desktop.Managers;
public class DocumentManager
public partial class DocumentManager
{
private readonly IDock _documents;
private readonly InkForgeFactory _factory;
private readonly WelcomePageDocumentViewModel _welcomePage;
private readonly WorkspaceManager _workspaceManager;
public DocumentManager(WorkspaceManager workspaceManager, InkForgeFactory factory)
public IDocumentDock Dock { get; }
public DocumentManager(NoteStore noteStore, InkForgeFactory factory)
{
_workspaceManager = workspaceManager;
_factory = factory;
_documents = factory.GetDockable<IDock>("Documents")!;
_welcomePage = CreateWelcomePageDocumentViewModel();
workspaceManager.WhenAnyValue(v => v.Workspace).Subscribe(OnWorkspaceChanged);
Dock = factory.CreateDocumentDock();
Dock.IsCollapsable = false;
Dock.CanCreateDocument = true;
Dock.CreateDocument = CreateDocumentCommand;
}
private void OnWorkspaceChanged(Workspace? workspace)
[RelayCommand]
private void OnCreateDocument()
{
if (workspace is null)
NoteEditDocumentViewModel editViewModel = new(new()
{
_factory.AddDockable(_documents, _welcomePage);
}
else
{
_factory.RemoveDockable(_welcomePage, false);
}
}
private static WelcomePageDocumentViewModel CreateWelcomePageDocumentViewModel()
{
return ActivatorUtilities.CreateInstance<WelcomePageDocumentViewModel>(
Application.Current!.GetValue(App.ServiceProviderProperty)
);
Name = "Untitled Note",
}, new());
_factory.AddDockable(Dock, editViewModel);
_factory.SetActiveDockable(editViewModel);
_factory.SetFocusedDockable(Dock, editViewModel);
}
}

View file

@ -1,3 +1,7 @@
using Avalonia;
using CommunityToolkit.Mvvm.ComponentModel;
using InkForge.Data;
using InkForge.Desktop.Data.Options;
using InkForge.Desktop.Models;
@ -5,24 +9,16 @@ using InkForge.Desktop.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using ReactiveUI;
namespace InkForge.Desktop.Managers;
public class WorkspaceManager(IServiceProvider serviceProvider) : ReactiveObject
public partial class WorkspaceManager(IServiceProvider serviceProvider) : ObservableObject
{
private readonly IServiceProvider _serviceProvider = serviceProvider;
private Workspace? _workspace;
public Workspace? Workspace
{
get => _workspace;
private set => this.RaiseAndSetIfChanged(ref _workspace, value);
}
[ObservableProperty] private Workspace? _workspace;
public ValueTask CloseWorkspace()
{
_workspace?.Dispose();
Workspace?.Dispose();
Workspace = null;
return ValueTask.CompletedTask;
}
@ -66,7 +62,7 @@ public class WorkspaceManager(IServiceProvider serviceProvider) : ReactiveObject
var db = dbContext.Database;
if ((await db.GetPendingMigrationsAsync().ConfigureAwait(false)).Any())
{
if (file.Exists)
if ((await db.GetAppliedMigrationsAsync().ConfigureAwait(false)).Any())
{
file.CopyTo(Path.ChangeExtension(file.FullName, $"{DateTime.Now:s}{file.Extension}"));
}
@ -74,6 +70,8 @@ public class WorkspaceManager(IServiceProvider serviceProvider) : ReactiveObject
await db.MigrateAsync().ConfigureAwait(false);
}
await serviceProvider.GetRequiredService<NoteStore>().Load().ConfigureAwait(false);
scope = null;
}
catch (Exception)

View file

@ -23,7 +23,6 @@ public static class InkForgeServiceCollections
// Singletons
// - Concrete
services.AddSingleton<DocumentManager>();
services.AddSingleton<InkForgeFactory>();
services.AddSingleton<WorkspaceManager>();
@ -33,6 +32,7 @@ public static class InkForgeServiceCollections
// Scoped
// - Concrete
services.AddScoped<DocumentManager>();
services.AddScoped<LocalWorkspaceOptions>();
services.AddScoped<NoteStore>();

View file

@ -1,61 +1,12 @@
using System.Reactive.Linq;
using System.Reactive.Subjects;
using ReactiveUI;
using CommunityToolkit.Mvvm.ComponentModel;
namespace InkForge.Desktop.Models;
public class Note : ReactiveObject
public partial class Note : ObservableObject
{
private readonly ObservableAsPropertyHelper<Note?> _parent;
private readonly BehaviorSubject<int?> _parentId = new(default);
private DateTimeOffset _createdTime;
private int _id;
private string _name = default!;
private DateTimeOffset _updatedTime;
public DateTimeOffset CreatedTime
{
get => _createdTime;
set => this.RaiseAndSetIfChanged(ref _createdTime, value);
}
public int Id
{
get => _id;
set => this.RaiseAndSetIfChanged(ref _id, value);
}
public string Name
{
get => _name;
set => this.RaiseAndSetIfChanged(ref _name, value);
}
public DateTimeOffset UpdatedTime
{
get => _updatedTime;
set => this.RaiseAndSetIfChanged(ref _updatedTime, value);
}
public Note? Parent
{
get => _parent.Value;
set => ParentId = value?.Id;
}
public int? ParentId
{
get => _parentId.Value;
set => _parentId.OnNext(value);
}
public Note(NoteStore noteStore)
{
_parent = _parentId.Select(id => id switch
{
{ } => noteStore.Watch((int)id),
_ => Observable.Empty<Note?>(),
}).Switch(null).ToProperty(this, nameof(Parent), deferSubscription: true);
}
[ObservableProperty] private DateTimeOffset _createdTime;
[ObservableProperty] private int _id;
[ObservableProperty] private string _name = default!;
[ObservableProperty] private int? _parentId;
[ObservableProperty] private DateTimeOffset _updatedTime;
}

View file

@ -1,7 +1,4 @@
using System.Collections.ObjectModel;
using System.Reactive.Linq;
using DynamicData;
using Avalonia.Collections;
using InkForge.Data;
@ -9,51 +6,46 @@ using Microsoft.EntityFrameworkCore;
namespace InkForge.Desktop.Models;
public class NoteStore
public class NoteStore(IDbContextFactory<NoteDbContext> dbContextFactory)
{
private readonly IDbContextFactory<NoteDbContext> _dbContextFactory;
private readonly SourceCache<Note, int> _notesCache = new(m => m.Id);
public AvaloniaDictionary<int, Note> Notes { get; } = [];
public ReadOnlyObservableCollection<Note> Notes { get; }
public NoteStore(IDbContextFactory<NoteDbContext> dbContextFactory)
public async ValueTask Load()
{
_dbContextFactory = dbContextFactory;
await using var dbContext = await dbContextFactory.CreateDbContextAsync().ConfigureAwait(false);
Notes.Clear();
await foreach (var note in dbContext.Notes.AsAsyncEnumerable().ConfigureAwait(false))
{
Notes.Add(note.Id, Map(note));
}
}
public void AddNote(Note note)
{
using var dbContext = _dbContextFactory.CreateDbContext();
var entity = ToEntity(note);
using var dbContext = dbContextFactory.CreateDbContext();
var entity = Map(note);
var entry = dbContext.Notes.Add(entity);
dbContext.SaveChanges();
}
public Note CreateNote() => new(this);
public Note? GetById(int id)
{
if (((Note?)_notesCache.Lookup(id)) is not Note note)
if (!Notes.TryGetValue(id, out var note))
{
using var dbContext = _dbContextFactory.CreateDbContext();
using var dbContext = dbContextFactory.CreateDbContext();
if (dbContext.Notes.Find(id) is not { } dbNote)
{
return null;
}
note = ToNote(dbNote);
Notes.Add(id, note = Map(dbNote));
}
return note;
}
public IObservable<Note?> Watch(int id)
{
return _notesCache.WatchValue(id);
}
private NoteEntity ToEntity(Note note)
private NoteEntity Map(Note note)
{
return new()
{
@ -64,25 +56,26 @@ public class NoteStore
Created = note.CreatedTime,
Updated = note.UpdatedTime,
},
Parent = note.Parent switch
Parent = note.ParentId switch
{
{ Id: { } parentId } => new()
{ } parentId => new()
{
Id = parentId
},
_ => null,
}
_ => null
},
};
}
private Note ToNote(NoteEntity entity)
private Note Map(NoteEntity entity)
{
var note = CreateNote();
note.Id = entity.Id;
note.Name = entity.Value.Name;
note.CreatedTime = entity.Value.Created;
note.UpdatedTime = entity.Value.Updated;
note.ParentId = entity.Parent?.Id;
return note;
return new()
{
Id = entity.Id,
Name = entity.Value.Name,
CreatedTime = entity.Value.Created,
UpdatedTime = entity.Value.Updated,
ParentId = entity.Parent?.Id
};
}
}

View file

@ -0,0 +1,131 @@
using System.Buffers;
using System.Text;
using Avalonia.Collections;
using AvaloniaEdit.Document;
using InkForge.Data;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
namespace InkForge.Desktop.Models;
public class TextDocumentStore(IDbContextFactory<NoteDbContext> dbContextFactory)
{
private readonly AvaloniaDictionary<int, TextDocument> _cache = [];
public void Close(int id)
{
if (_cache.Remove(id, out var document))
{
document.Remove(0, document.TextLength);
}
}
public TextDocument Get(int id)
{
if (!_cache.TryGetValue(id, out var document))
{
using var dbContext = dbContextFactory.CreateDbContext();
var connection = dbContext.Database.GetDbConnection();
using var command = connection.CreateCommand();
command.CommandText = $"SELECT rowid FROM Blobs WHERE Id={id}";
using var commandReader = command.ExecuteReader();
document = new();
if (commandReader.Read())
{
using SqliteBlob stream = new((SqliteConnection)connection, "Blob", "Value", commandReader.GetInt64(0), true);
CopyDocumentContent(stream, document);
}
_cache.Add(id, document);
}
return document;
static void CopyDocumentContent(Stream stream, TextDocument document)
{
DocumentTextWriter writer = new(document, 0);
StreamReader reader = new(stream, Encoding.UTF8);
Span<char> buffer = stackalloc char[128];
while (!reader.EndOfStream)
{
var read = reader.Read(buffer);
writer.Write(buffer[..read]);
}
}
}
public int? Save(int? id, TextDocument textDocument)
{
var state = textDocument.CreateSnapshot();
var bytes = TextBytes(state);
using var dbContext = dbContextFactory.CreateDbContext();
var connection = dbContext.Database.GetDbConnection();
using var transaction = dbContext.Database.BeginTransaction();
using var command = connection.CreateCommand();
command.CommandText = id switch
{
null => $"INSERT INTO Blobs(Value) VALUES(zeroblob({bytes})) RETURNING (Id, rowid)",
_ => $"UPDATE Blobs SET Value=zeroblob({bytes}) WHERE Id={id} RETURNING (Id, rowid)"
};
using var commandReader = command.ExecuteReader();
if (!commandReader.Read())
{
return null;
}
id = commandReader.GetInt32(0);
using (SqliteBlob stream = new((SqliteConnection)connection, "Blobs", "Value", commandReader.GetInt64(1)))
{
using StreamWriter writer = new(stream, Encoding.UTF8);
state.WriteTextTo(writer);
}
transaction.Commit();
_cache.TryAdd((int)id, textDocument);
return id;
static int TextBytes(ITextSource text)
{
int length = 0;
Span<byte> byteBuffer = stackalloc byte[128];
using var reader = text.CreateReader();
ArrayBufferWriter<char> buffer = new();
var encoder = Encoding.UTF8.GetEncoder();
while (reader.Peek() != -1)
{
var memory = buffer.GetMemory();
buffer.Advance(reader.Read(memory.Span));
ReadOnlySpan<char> chars = buffer.WrittenSpan;
convert:
encoder.Convert(buffer.WrittenSpan, byteBuffer, false, out var charsUsed, out var bytesUsed, out _);
chars = chars[charsUsed..];
if (bytesUsed > 0)
{
length += bytesUsed;
goto convert;
}
Span<char> remaining = [];
if (!chars.IsEmpty)
{
remaining = buffer.GetSpan(chars.Length);
chars.CopyTo(remaining);
}
buffer.Clear();
if (!remaining.IsEmpty)
{
buffer.Write(remaining);
}
}
return length;
}
}
}

View file

@ -4,21 +4,16 @@ using Microsoft.Extensions.DependencyInjection;
namespace InkForge.Desktop.Models;
public sealed class Workspace : IDisposable
public sealed class Workspace(IServiceScope scope) : IDisposable
{
private bool _disposedValue;
private IServiceScope? _scope;
private IServiceScope? _scope = scope;
public string Name { get; set; } = default!;
public LocalWorkspaceOptions Options { get; set; } = default!;
public IServiceProvider Services => _scope!.ServiceProvider;
public Workspace(IServiceScope scope)
{
_scope = scope;
}
public IServiceProvider Services { get; } = scope.ServiceProvider;
public void Dispose()
{
@ -33,14 +28,4 @@ public sealed class Workspace : IDisposable
_disposedValue = true;
}
}
// private async Task LoadNotes()
// {
// await using var dbContext = await _dbContextFactory.CreateDbContextAsync().ConfigureAwait(false);
// await foreach (var asdf in dbContext.Notes.AsAsyncEnumerable().ConfigureAwait(false))
// {
// }
// _ = (await dbContext.Notes.ToListAsync().ConfigureAwait(false));
// }
}

View file

@ -1,6 +1,5 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.ReactiveUI;
using Avalonia.Threading;
using InkForge.Desktop;
@ -20,7 +19,6 @@ static class Program
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.UseReactiveUI()
.WithInterFont()
.LogToTrace();
@ -40,7 +38,7 @@ static class Program
_ = app.ApplicationLifetime switch
{
IClassicDesktopStyleApplicationLifetime desktop => desktop.MainWindow = new MainWindow(),
_ => throw new NotSupportedException(),
_ => throw new NotSupportedException(),
};
}

View file

@ -1,4 +1,5 @@
using System.Reactive.Linq;
using System.Runtime.InteropServices;
using Avalonia;
using Avalonia.Controls;
@ -52,7 +53,7 @@ public class TopLevels
// Register any new context
if (e.NewValue != null)
{
RegistrationMapper.Add(e.NewValue, sender);
CollectionsMarshal.GetValueRefOrAddDefault(RegistrationMapper, e.NewValue, out _) = sender;
}
}

View file

@ -0,0 +1,30 @@
using System.Reactive.Linq;
using AvaloniaEdit.Document;
using Dock.Model.Mvvm.Controls;
using DynamicData.Binding;
using InkForge.Desktop.Models;
namespace InkForge.Desktop.ViewModels.Documents;
public class NoteEditDocumentViewModel : Document
{
public Note Note { get; }
public TextDocument Document { get; }
public NoteEditDocumentViewModel(Note note, TextDocument textDocument)
{
Note = note;
Document = textDocument;
Observable.CombineLatest(
this.WhenValueChanged(v => v.Note.Name),
this.WhenValueChanged(v => v.Document.UndoStack.IsOriginalFile),
(name, original) => original ? name : $"{name} *"
).Subscribe(title => Title = title!);
}
}

View file

@ -2,33 +2,28 @@ using System.Reactive;
using Avalonia.Platform.Storage;
using Dock.Model.ReactiveUI.Controls;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Dock.Model.Mvvm.Controls;
using InkForge.Desktop.Managers;
using InkForge.Desktop.Services;
using ReactiveUI;
namespace InkForge.Desktop.ViewModels.Documents;
public class WelcomePageDocumentViewModel : Document
public partial class WelcomePageDocumentViewModel : Document
{
private readonly WorkspaceManager _workspaceController;
public ReactiveCommand<Unit, Unit> CreateNew { get; }
public ReactiveCommand<Unit, Unit> OpenNew { get; }
public WelcomePageDocumentViewModel(WorkspaceManager workspaceController)
{
CanClose = false;
Title = "Welcome";
_workspaceController = workspaceController;
CreateNew = ReactiveCommand.CreateFromTask(OnCreateNew);
OpenNew = ReactiveCommand.CreateFromTask(OnOpenNew);
}
[RelayCommand]
private async Task OnCreateNew()
{
var storageProvider = this.GetStorageProvider()!;
@ -56,6 +51,7 @@ public class WelcomePageDocumentViewModel : Document
await _workspaceController.OpenWorkspace(filePath, true);
}
[RelayCommand]
private async Task OnOpenNew()
{
var storageProvider = this.GetStorageProvider()!;

View file

@ -1,13 +0,0 @@
using Dock.Model.Core;
using Dock.Model.ReactiveUI.Controls;
namespace InkForge.Desktop.ViewModels;
public class InkForgeDocumentDock : DocumentDock, IDock
{
bool IDock.IsEmpty
{
get => false;
set { }
}
}

View file

@ -1,32 +1,16 @@
using Avalonia;
using CommunityToolkit.Mvvm.ComponentModel;
using Dock.Model.Core;
using InkForge.Desktop.Managers;
using Microsoft.Extensions.DependencyInjection;
using ReactiveUI;
namespace InkForge.Desktop.ViewModels;
public class MainViewModel : ReactiveObject
public class MainViewModel : ObservableObject
{
private readonly DocumentManager _documentManager;
public IDock Layout { get; }
public MainViewModel(InkForgeFactory factory)
{
Layout = factory.CreateLayout();
factory.InitLayout(Layout);
_documentManager = CreateDocumentManager();
}
private static DocumentManager CreateDocumentManager()
{
return ActivatorUtilities.CreateInstance<DocumentManager>(
Application.Current!.GetValue(App.ServiceProviderProperty)
);
}
}

View file

@ -1,9 +1,14 @@
using ReactiveUI;
using CommunityToolkit.Mvvm.ComponentModel;
namespace InkForge.Desktop.ViewModels;
public record class RecentItemViewModel(
DateTimeOffset Created,
string Name,
DateTimeOffset LastUsed
) : ReactiveRecord;
public partial class RecentItemViewModel(
DateTimeOffset created,
string name,
DateTimeOffset lastUsed
) : ObservableObject
{
[ObservableProperty] private DateTimeOffset _created = created;
[ObservableProperty] private string _name = name;
[ObservableProperty] private DateTimeOffset _lastUsed = lastUsed;
}

View file

@ -1,32 +1,40 @@
using Dock.Model.ReactiveUI.Controls;
using System.Reactive.Linq;
using CommunityToolkit.Mvvm.ComponentModel;
using Dock.Model.Mvvm.Controls;
using DynamicData.Binding;
using InkForge.Desktop.Managers;
using InkForge.Desktop.Models;
using InkForge.Desktop.ViewModels.Workspaces;
using ReactiveUI;
namespace InkForge.Desktop.ViewModels.Tools;
public class WorkspaceTool : Tool
public partial class WorkspaceTool : Tool
{
private WorkspaceViewModel? _workspace;
public WorkspaceViewModel? Workspace
{
get => _workspace;
private set => this.RaiseAndSetIfChanged(ref _workspace, value);
}
private readonly IWorkspaceViewModelFactory _workspaceViewModelFactory;
[ObservableProperty] private WorkspaceViewModel? _workspace;
public WorkspaceTool(WorkspaceManager workspaceManager, IWorkspaceViewModelFactory workspaceViewModelFactory)
{
Title = "Workspace";
_workspaceViewModelFactory = workspaceViewModelFactory;
Title = "Explorer";
CanClose = false;
CanFloat = false;
CanPin = false;
workspaceManager.WhenAnyValue(v => v.Workspace,
v => v switch
{
{ } => workspaceViewModelFactory.Create(v),
_ => null
}).BindTo(this, v => v.Workspace);
workspaceManager.WhenValueChanged(v => v.Workspace).Subscribe(OnWorkspaceManagerWorkspaceChanged);
}
private void OnWorkspaceManagerWorkspaceChanged(Workspace? workspace)
{
Workspace = workspace switch
{
{ } v => _workspaceViewModelFactory.Create(v),
_ => null
};
}
}

View file

@ -1,3 +1,9 @@
using System.Collections.ObjectModel;
using Avalonia;
using DynamicData;
using InkForge.Desktop.Models;
using Microsoft.Extensions.DependencyInjection;
@ -6,30 +12,24 @@ namespace InkForge.Desktop.ViewModels.Workspaces
{
public class WorkspaceViewModel
{
private readonly Workspace _workspace;
private readonly NoteStore _noteStore;
// private readonly ObservableAsPropertyHelper<string> _workspaceNameProperty;
private readonly ReadOnlyObservableCollection<Node<Note, int>> _notes;
// public string WorkspaceName => _workspaceNameProperty.Value;
public string Name => _workspace.Name;
// public ReactiveCommand<Unit, Unit> AddDocument { get; }
public ReadOnlyObservableCollection<Node<Note, int>> Notes => _notes;
public WorkspaceViewModel(NoteStore noteStore)
public WorkspaceViewModel(Workspace workspace, NoteStore noteStore)
{
_workspace = workspace;
_noteStore = noteStore;
noteStore.Notes
.AsObservableChangeSet(m => m.Key)
.Transform(m => m.Value, true)
.TransformToTree(m => m.Id)
.Bind(out _notes).Subscribe();
}
// public WorkspacesViewModel(Workspace workspace)
// {
// _workspace = workspace;
// _workspaceNameProperty = this.WhenAnyValue(v => v._workspace.Name).ToProperty(this, nameof(WorkspaceName));
// AddDocument = ReactiveCommand.Create(OnAddDocument);
// }
// private void OnAddDocument()
// {
// }
}
public interface IWorkspaceViewModelFactory
@ -39,14 +39,14 @@ namespace InkForge.Desktop.ViewModels.Workspaces
namespace Internal
{
internal class WorkspaceViewModelFactory(IServiceProvider services) : IWorkspaceViewModelFactory
internal class WorkspaceViewModelFactory : IWorkspaceViewModelFactory
{
private static ObjectFactory<WorkspaceViewModel>? s_workspaceViewModelFactory;
public WorkspaceViewModel Create(Workspace workspace)
public static WorkspaceViewModel Create(Workspace workspace)
{
s_workspaceViewModelFactory ??= ActivatorUtilities.CreateFactory<WorkspaceViewModel>([typeof(Workspace)]);
return s_workspaceViewModelFactory(services, [workspace]);
return s_workspaceViewModelFactory(workspace.Services, [workspace]);
}
WorkspaceViewModel IWorkspaceViewModelFactory.Create(Workspace workspace) => Create(workspace);

View file

@ -0,0 +1,15 @@
<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:vm="using:InkForge.Desktop.ViewModels.Documents"
xmlns:avaloniaedit="https://github.com/avaloniaui/avaloniaedit"
xmlns:inkforge="app:InkForge"
mc:Ignorable="d"
d:DesignWidth="800"
d:DesignHeight="450"
x:Class="InkForge.Desktop.Views.Documents.NoteEditDocument"
x:DataType="vm:NoteEditDocumentViewModel"
inkforge:TopLevels.Register="{CompiledBinding}">
<avaloniaedit:TextEditor Document="{CompiledBinding Document}" />
</UserControl>

View file

@ -0,0 +1,11 @@
using Avalonia.Controls;
namespace InkForge.Desktop.Views.Documents;
public partial class NoteEditDocument : UserControl
{
public NoteEditDocument()
{
InitializeComponent();
}
}

View file

@ -28,11 +28,11 @@
<Menu Grid.Row="2">
<MenuItem Header="Create New"
Command="{CompiledBinding CreateNew}" />
Command="{CompiledBinding CreateNewCommand}" />
<MenuItem Header="Open"
IsEnabled="False" />
<MenuItem Header="Open File"
Command="{CompiledBinding OpenNew}" />
Command="{CompiledBinding OpenNewCommand}" />
</Menu>
</Grid>
</UserControl>
</UserControl>

View file

@ -1,10 +1,8 @@
using Avalonia.ReactiveUI;
using InkForge.Desktop.ViewModels.Documents;
using Avalonia.Controls;
namespace InkForge.Desktop.Views.Documents;
public partial class WelcomePageDocument : ReactiveUserControl<WelcomePageDocumentViewModel>
public partial class WelcomePageDocument : UserControl
{
public WelcomePageDocument()
{

View file

@ -1,5 +1,5 @@
using Avalonia;
using Avalonia.ReactiveUI;
using Avalonia.Controls;
using InkForge.Desktop.ViewModels;
@ -7,12 +7,12 @@ using Microsoft.Extensions.DependencyInjection;
namespace InkForge.Desktop.Views;
public partial class MainWindow : ReactiveWindow<MainViewModel>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ViewModel = CreateViewModel();
DataContext = CreateViewModel();
}
private static MainViewModel CreateViewModel()

View file

@ -29,4 +29,4 @@
</Style>
</Style>
</UserControl.Styles>
</UserControl>
</UserControl>

View file

@ -1,8 +1,8 @@
using Avalonia.ReactiveUI;
using Avalonia.Controls;
namespace InkForge.Desktop.Views.Tools;
public partial class WorkspaceTool : ReactiveUserControl<ViewModels.Tools.WorkspaceTool>
public partial class WorkspaceTool : UserControl
{
public WorkspaceTool()
{

View file

@ -12,7 +12,8 @@
<Grid ColumnDefinitions="*, Auto"
RowDefinitions="Auto, *">
<TextBlock Grid.Column="0"
<TextBlock Text="{CompiledBinding Name}"
Grid.Column="0"
Grid.Row="0" />
<StackPanel Classes="WorkspaceToolbar"
@ -29,25 +30,9 @@
<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>
<TreeView Grid.ColumnSpan="2"
Grid.Row="1" />
</Grid>
</UserControl>
</UserControl>

View file

@ -1,10 +1,8 @@
using Avalonia.ReactiveUI;
using InkForge.Desktop.ViewModels.Workspaces;
using Avalonia.Controls;
namespace InkForge.Desktop.Views.Workspaces;
public partial class WorkspaceView : ReactiveUserControl<WorkspaceViewModel>
public partial class WorkspaceView : UserControl
{
public WorkspaceView()
{

View file

@ -1,3 +1,3 @@
namespace InkForge.Data;
public class Blob : Entity<byte[], string>;
public class Blob : Entity<byte[], int>;

View file

@ -26,7 +26,10 @@ public class NoteDbContext(
{
options.HasKey(m => m.Id);
options.OwnsOne(m => m.Value);
options.OwnsOne(m => m.Value, m =>
{
m.HasOne<Blob>().WithOne().HasForeignKey<Note>(m => m.ContentId).IsRequired();
});
options.HasOne(m => m.Parent);
});

View file

@ -4,13 +4,13 @@ namespace InkForge.Data
{
public DateTimeOffset Created { get; set; }
public string Name { get; set; } = default!;
public DateTimeOffset Updated { get; set; }
public int ContentId { get; set; }
public DateTimeOffset? Deleted { get; set; }
public Blob Content { get; set; } = default!;
public string Name { get; set; } = default!;
public DateTimeOffset Updated { get; set; }
}
public class NoteEntity : Entity<NoteEntity, Note, int>;

View file

@ -1,172 +0,0 @@
// <auto-generated />
using System;
using InkForge.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace InkForge.Sqlite.Migrations
{
[DbContext(typeof(NoteDbContext))]
[Migration("20240207000000__01_Initial")]
partial class _01_Initial
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.1");
modelBuilder.Entity("InkForge.Data.Blob", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<byte[]>("Content")
.IsRequired()
.HasColumnType("BLOB");
b.HasKey("Id");
b.ToTable("Blobs");
});
modelBuilder.Entity("InkForge.Data.Infrastructure.NoteEntity", b =>
{
b.Property<int?>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("Notes");
});
modelBuilder.Entity("InkForge.Data.Infrastructure.NoteVersionEntity", b =>
{
b.Property<int?>("Version")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("Id")
.HasColumnType("INTEGER");
b.HasKey("Version");
b.HasIndex("Id", "Version")
.IsUnique();
b.ToTable("NoteVersions");
});
modelBuilder.Entity("InkForge.Data.Infrastructure.NoteEntity", b =>
{
b.OwnsOne("InkForge.Data.Domain.Note", "Value", b1 =>
{
b1.Property<int>("ParentId")
.HasColumnType("INTEGER");
b1.Property<string>("ContentId")
.IsRequired()
.HasColumnType("TEXT");
b1.Property<DateTimeOffset>("Created")
.HasColumnType("TEXT");
b1.Property<DateTimeOffset?>("Deleted")
.HasColumnType("TEXT");
b1.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b1.Property<DateTimeOffset>("Updated")
.HasColumnType("TEXT");
b1.HasKey("ParentId");
b1.HasIndex("ContentId");
b1.ToTable("Notes");
b1.HasOne("InkForge.Data.Blob", "Content")
.WithMany()
.HasForeignKey("ContentId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b1.WithOwner("Parent")
.HasForeignKey("ParentId");
b1.Navigation("Content");
b1.Navigation("Parent");
});
b.Navigation("Value")
.IsRequired();
});
modelBuilder.Entity("InkForge.Data.Infrastructure.NoteVersionEntity", b =>
{
b.OwnsOne("InkForge.Data.Domain.Note", "Value", b1 =>
{
b1.Property<int>("NoteVersionEntityVersion")
.HasColumnType("INTEGER");
b1.Property<string>("ContentId")
.IsRequired()
.HasColumnType("TEXT");
b1.Property<DateTimeOffset>("Created")
.HasColumnType("TEXT");
b1.Property<DateTimeOffset?>("Deleted")
.HasColumnType("TEXT");
b1.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b1.Property<int?>("ParentId")
.HasColumnType("INTEGER");
b1.Property<DateTimeOffset>("Updated")
.HasColumnType("TEXT");
b1.HasKey("NoteVersionEntityVersion");
b1.HasIndex("ContentId");
b1.HasIndex("ParentId");
b1.ToTable("NoteVersions");
b1.HasOne("InkForge.Data.Blob", "Content")
.WithMany()
.HasForeignKey("ContentId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b1.WithOwner()
.HasForeignKey("NoteVersionEntityVersion");
b1.HasOne("InkForge.Data.Infrastructure.NoteEntity", "Parent")
.WithMany()
.HasForeignKey("ParentId");
b1.Navigation("Content");
b1.Navigation("Parent");
});
b.Navigation("Value")
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View file

@ -1,207 +0,0 @@
// <auto-generated />
using System;
using InkForge.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace InkForge.Sqlite.Migrations
{
[DbContext(typeof(NoteDbContext))]
[Migration("20240214000000__02_Metadata")]
partial class _02_Metadata
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.2");
modelBuilder.Entity("InkForge.Data.Blob", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<byte[]>("Content")
.IsRequired()
.HasColumnType("BLOB");
b.HasKey("Id");
b.ToTable("Blobs");
});
modelBuilder.Entity("InkForge.Data.Infrastructure.MetadataEntity", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Metadata");
});
modelBuilder.Entity("InkForge.Data.Infrastructure.MetadataVersionEntity", b =>
{
b.Property<int?>("Version")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Version");
b.HasIndex("Id", "Version")
.IsUnique();
b.ToTable("MetadataHistory");
});
modelBuilder.Entity("InkForge.Data.Infrastructure.NoteEntity", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("Notes");
});
modelBuilder.Entity("InkForge.Data.Infrastructure.NoteVersionEntity", b =>
{
b.Property<int?>("Version")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("Id")
.HasColumnType("INTEGER");
b.HasKey("Version");
b.HasIndex("Id", "Version")
.IsUnique();
b.ToTable("NoteVersions");
});
modelBuilder.Entity("InkForge.Data.Infrastructure.NoteEntity", b =>
{
b.OwnsOne("InkForge.Data.Domain.Note", "Value", b1 =>
{
b1.Property<int>("ParentId")
.HasColumnType("INTEGER");
b1.Property<string>("ContentId")
.IsRequired()
.HasColumnType("TEXT");
b1.Property<DateTimeOffset>("Created")
.HasColumnType("TEXT");
b1.Property<DateTimeOffset?>("Deleted")
.HasColumnType("TEXT");
b1.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b1.Property<DateTimeOffset>("Updated")
.HasColumnType("TEXT");
b1.HasKey("ParentId");
b1.HasIndex("ContentId");
b1.ToTable("Notes");
b1.HasOne("InkForge.Data.Blob", "Content")
.WithMany()
.HasForeignKey("ContentId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b1.WithOwner("Parent")
.HasForeignKey("ParentId");
b1.Navigation("Content");
b1.Navigation("Parent");
});
b.Navigation("Value")
.IsRequired();
});
modelBuilder.Entity("InkForge.Data.Infrastructure.NoteVersionEntity", b =>
{
b.OwnsOne("InkForge.Data.Domain.Note", "Value", b1 =>
{
b1.Property<int>("NoteVersionEntityVersion")
.HasColumnType("INTEGER");
b1.Property<string>("ContentId")
.IsRequired()
.HasColumnType("TEXT");
b1.Property<DateTimeOffset>("Created")
.HasColumnType("TEXT");
b1.Property<DateTimeOffset?>("Deleted")
.HasColumnType("TEXT");
b1.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b1.Property<int?>("ParentId")
.HasColumnType("INTEGER");
b1.Property<DateTimeOffset>("Updated")
.HasColumnType("TEXT");
b1.HasKey("NoteVersionEntityVersion");
b1.HasIndex("ContentId");
b1.HasIndex("ParentId");
b1.ToTable("NoteVersions");
b1.HasOne("InkForge.Data.Blob", "Content")
.WithMany()
.HasForeignKey("ContentId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b1.WithOwner()
.HasForeignKey("NoteVersionEntityVersion");
b1.HasOne("InkForge.Data.Infrastructure.NoteEntity", "Parent")
.WithMany()
.HasForeignKey("ParentId");
b1.Navigation("Content");
b1.Navigation("Parent");
});
b.Navigation("Value")
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View file

@ -1,56 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace InkForge.Sqlite.Migrations
{
/// <inheritdoc />
public partial class _02_Metadata : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Metadata",
columns: table => new
{
Id = table.Column<string>(type: "TEXT", nullable: false),
Value = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Metadata", x => x.Id);
});
migrationBuilder.CreateTable(
name: "MetadataHistory",
columns: table => new
{
Version = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Value = table.Column<string>(type: "TEXT", nullable: false),
Id = table.Column<string>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_MetadataHistory", x => x.Version);
});
migrationBuilder.CreateIndex(
name: "IX_MetadataHistory_Id_Version",
table: "MetadataHistory",
columns: new[] { "Id", "Version" },
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Metadata");
migrationBuilder.DropTable(
name: "MetadataHistory");
}
}
}

View file

@ -11,8 +11,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace InkForge.Sqlite.Migrations
{
[DbContext(typeof(NoteDbContext))]
[Migration("20240405000000__03_DropHistory")]
partial class _03_DropHistory
[Migration("20240401_Initial")]
partial class _20240401_Initial
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
@ -22,8 +22,9 @@ namespace InkForge.Sqlite.Migrations
modelBuilder.Entity("InkForge.Data.Blob", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<byte[]>("Value")
.IsRequired()
@ -75,9 +76,8 @@ namespace InkForge.Sqlite.Migrations
b1.Property<int>("NoteEntityId")
.HasColumnType("INTEGER");
b1.Property<string>("ContentId")
.IsRequired()
.HasColumnType("TEXT");
b1.Property<int>("ContentId")
.HasColumnType("INTEGER");
b1.Property<DateTimeOffset>("Created")
.HasColumnType("TEXT");
@ -94,20 +94,19 @@ namespace InkForge.Sqlite.Migrations
b1.HasKey("NoteEntityId");
b1.HasIndex("ContentId");
b1.HasIndex("ContentId")
.IsUnique();
b1.ToTable("Notes");
b1.HasOne("InkForge.Data.Blob", "Content")
.WithMany()
.HasForeignKey("ContentId")
b1.HasOne("InkForge.Data.Blob", null)
.WithOne()
.HasForeignKey("InkForge.Data.NoteEntity.Value#InkForge.Data.Note", "ContentId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b1.WithOwner()
.HasForeignKey("NoteEntityId");
b1.Navigation("Content");
});
b.Navigation("Parent");

View file

@ -6,7 +6,7 @@ using Microsoft.EntityFrameworkCore.Migrations;
namespace InkForge.Sqlite.Migrations
{
/// <inheritdoc />
public partial class _01_Initial : Migration
public partial class _20240401_Initial : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
@ -15,25 +15,38 @@ namespace InkForge.Sqlite.Migrations
name: "Blobs",
columns: table => new
{
Id = table.Column<string>(type: "TEXT", nullable: false),
Content = table.Column<byte[]>(type: "BLOB", nullable: false)
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Value = table.Column<byte[]>(type: "BLOB", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Blobs", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Metadata",
columns: table => new
{
Id = table.Column<string>(type: "TEXT", nullable: false),
Value = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Metadata", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Notes",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Id = table.Column<int>(type: "INTEGER", nullable: false),
Value_Created = table.Column<DateTimeOffset>(type: "TEXT", nullable: false),
Value_ContentId = table.Column<int>(type: "INTEGER", nullable: false),
Value_Deleted = table.Column<DateTimeOffset>(type: "TEXT", nullable: true),
Value_Name = table.Column<string>(type: "TEXT", nullable: false),
Value_Updated = table.Column<DateTimeOffset>(type: "TEXT", nullable: false),
Value_Deleted = table.Column<DateTimeOffset>(type: "TEXT", nullable: true),
Value_ContentId = table.Column<string>(type: "TEXT", nullable: false)
ParentId = table.Column<int>(type: "INTEGER", nullable: true)
},
constraints: table =>
{
@ -44,65 +57,30 @@ namespace InkForge.Sqlite.Migrations
principalTable: "Blobs",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "NoteVersions",
columns: table => new
{
Version = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Value_Created = table.Column<DateTimeOffset>(type: "TEXT", nullable: false),
Value_ParentId = table.Column<int>(type: "INTEGER", nullable: true),
Value_Name = table.Column<string>(type: "TEXT", nullable: false),
Value_Updated = table.Column<DateTimeOffset>(type: "TEXT", nullable: false),
Value_Deleted = table.Column<DateTimeOffset>(type: "TEXT", nullable: true),
Value_ContentId = table.Column<string>(type: "TEXT", nullable: false),
Id = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_NoteVersions", x => x.Version);
table.ForeignKey(
name: "FK_NoteVersions_Blobs_Value_ContentId",
column: x => x.Value_ContentId,
principalTable: "Blobs",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_NoteVersions_Notes_Value_ParentId",
column: x => x.Value_ParentId,
name: "FK_Notes_Notes_ParentId",
column: x => x.ParentId,
principalTable: "Notes",
principalColumn: "Id");
});
migrationBuilder.CreateIndex(
name: "IX_Notes_ParentId",
table: "Notes",
column: "ParentId");
migrationBuilder.CreateIndex(
name: "IX_Notes_Value_ContentId",
table: "Notes",
column: "Value_ContentId");
migrationBuilder.CreateIndex(
name: "IX_NoteVersions_Id_Version",
table: "NoteVersions",
columns: new[] { "Id", "Version" },
column: "Value_ContentId",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_NoteVersions_Value_ContentId",
table: "NoteVersions",
column: "Value_ContentId");
migrationBuilder.CreateIndex(
name: "IX_NoteVersions_Value_ParentId",
table: "NoteVersions",
column: "Value_ParentId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "NoteVersions");
name: "Metadata");
migrationBuilder.DropTable(
name: "Notes");

View file

@ -1,149 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace InkForge.Sqlite.Migrations
{
/// <inheritdoc />
public partial class _03_DropHistory : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "MetadataHistory");
migrationBuilder.DropTable(
name: "NoteVersions");
migrationBuilder.RenameColumn(
name: "Content",
table: "Blobs",
newName: "Value");
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "Notes",
type: "INTEGER",
nullable: false,
oldClrType: typeof(int),
oldType: "INTEGER")
.OldAnnotation("Sqlite:Autoincrement", true);
migrationBuilder.AddColumn<int>(
name: "ParentId",
table: "Notes",
type: "INTEGER",
nullable: true);
migrationBuilder.CreateIndex(
name: "IX_Notes_ParentId",
table: "Notes",
column: "ParentId");
migrationBuilder.AddForeignKey(
name: "FK_Notes_Notes_ParentId",
table: "Notes",
column: "ParentId",
principalTable: "Notes",
principalColumn: "Id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Notes_Notes_ParentId",
table: "Notes");
migrationBuilder.DropIndex(
name: "IX_Notes_ParentId",
table: "Notes");
migrationBuilder.DropColumn(
name: "ParentId",
table: "Notes");
migrationBuilder.RenameColumn(
name: "Value",
table: "Blobs",
newName: "Content");
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "Notes",
type: "INTEGER",
nullable: false,
oldClrType: typeof(int),
oldType: "INTEGER")
.Annotation("Sqlite:Autoincrement", true);
migrationBuilder.CreateTable(
name: "MetadataHistory",
columns: table => new
{
Version = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Id = table.Column<string>(type: "TEXT", nullable: true),
Value = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_MetadataHistory", x => x.Version);
});
migrationBuilder.CreateTable(
name: "NoteVersions",
columns: table => new
{
Version = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Id = table.Column<int>(type: "INTEGER", nullable: false),
Value_ContentId = table.Column<string>(type: "TEXT", nullable: false),
Value_ParentId = table.Column<int>(type: "INTEGER", nullable: true),
Value_Created = table.Column<DateTimeOffset>(type: "TEXT", nullable: false),
Value_Deleted = table.Column<DateTimeOffset>(type: "TEXT", nullable: true),
Value_Name = table.Column<string>(type: "TEXT", nullable: false),
Value_Updated = table.Column<DateTimeOffset>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_NoteVersions", x => x.Version);
table.ForeignKey(
name: "FK_NoteVersions_Blobs_Value_ContentId",
column: x => x.Value_ContentId,
principalTable: "Blobs",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_NoteVersions_Notes_Value_ParentId",
column: x => x.Value_ParentId,
principalTable: "Notes",
principalColumn: "Id");
});
migrationBuilder.CreateIndex(
name: "IX_MetadataHistory_Id_Version",
table: "MetadataHistory",
columns: new[] { "Id", "Version" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_NoteVersions_Id_Version",
table: "NoteVersions",
columns: new[] { "Id", "Version" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_NoteVersions_Value_ContentId",
table: "NoteVersions",
column: "Value_ContentId");
migrationBuilder.CreateIndex(
name: "IX_NoteVersions_Value_ParentId",
table: "NoteVersions",
column: "Value_ParentId");
}
}
}

View file

@ -19,8 +19,9 @@ namespace InkForge.Sqlite.Migrations
modelBuilder.Entity("InkForge.Data.Blob", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<byte[]>("Value")
.IsRequired()
@ -28,7 +29,7 @@ namespace InkForge.Sqlite.Migrations
b.HasKey("Id");
b.ToTable("Blobs", (string)null);
b.ToTable("Blobs");
});
modelBuilder.Entity("InkForge.Data.MetadataEntity", b =>
@ -42,7 +43,7 @@ namespace InkForge.Sqlite.Migrations
b.HasKey("Id");
b.ToTable("Metadata", (string)null);
b.ToTable("Metadata");
});
modelBuilder.Entity("InkForge.Data.NoteEntity", b =>
@ -58,7 +59,7 @@ namespace InkForge.Sqlite.Migrations
b.HasIndex("ParentId");
b.ToTable("Notes", (string)null);
b.ToTable("Notes");
});
modelBuilder.Entity("InkForge.Data.NoteEntity", b =>
@ -67,14 +68,13 @@ namespace InkForge.Sqlite.Migrations
.WithMany()
.HasForeignKey("ParentId");
b.OwnsOne("InkForge.Data.NoteEntity.Value#InkForge.Data.Note", "Value", b1 =>
b.OwnsOne("InkForge.Data.Note", "Value", b1 =>
{
b1.Property<int>("NoteEntityId")
.HasColumnType("INTEGER");
b1.Property<string>("ContentId")
.IsRequired()
.HasColumnType("TEXT");
b1.Property<int>("ContentId")
.HasColumnType("INTEGER");
b1.Property<DateTimeOffset>("Created")
.HasColumnType("TEXT");
@ -91,20 +91,19 @@ namespace InkForge.Sqlite.Migrations
b1.HasKey("NoteEntityId");
b1.HasIndex("ContentId");
b1.HasIndex("ContentId")
.IsUnique();
b1.ToTable("Notes", (string)null);
b1.ToTable("Notes");
b1.HasOne("InkForge.Data.Blob", "Content")
.WithMany()
.HasForeignKey("ContentId")
b1.HasOne("InkForge.Data.Blob", null)
.WithOne()
.HasForeignKey("InkForge.Data.NoteEntity.Value#InkForge.Data.Note", "ContentId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b1.WithOwner()
.HasForeignKey("NoteEntityId");
b1.Navigation("Content");
});
b.Navigation("Parent");