diff --git a/Directory.Packages.props b/Directory.Packages.props index 6b5521d..14a1347 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -5,13 +5,15 @@ + - + - + + @@ -26,8 +28,9 @@ + - \ No newline at end of file + diff --git a/app/InkForge.Desktop/App.axaml b/app/InkForge.Desktop/App.axaml index 7042e4e..77b1e58 100644 --- a/app/InkForge.Desktop/App.axaml +++ b/app/InkForge.Desktop/App.axaml @@ -5,6 +5,7 @@ + @@ -13,4 +14,4 @@ /Assets/Fonts#FluentSystemIcons-Filled /Assets/Fonts#FluentSystemIcons-Regular - \ No newline at end of file + diff --git a/app/InkForge.Desktop/App.axaml.cs b/app/InkForge.Desktop/App.axaml.cs index e38d88e..ed789b2 100644 --- a/app/InkForge.Desktop/App.axaml.cs +++ b/app/InkForge.Desktop/App.axaml.cs @@ -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(); } diff --git a/app/InkForge.Desktop/AppViewLocator.cs b/app/InkForge.Desktop/AppViewLocator.cs index 6fd1ce3..0212785 100644 --- a/app/InkForge.Desktop/AppViewLocator.cs +++ b/app/InkForge.Desktop/AppViewLocator.cs @@ -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 view, TViewModel viewModel) where TViewModel : class - where TView : IViewFor + 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 diff --git a/app/InkForge.Desktop/InkForge.Desktop.csproj b/app/InkForge.Desktop/InkForge.Desktop.csproj index 0475604..58ae8e9 100644 --- a/app/InkForge.Desktop/InkForge.Desktop.csproj +++ b/app/InkForge.Desktop/InkForge.Desktop.csproj @@ -12,18 +12,21 @@ + - + - + + + @@ -41,4 +44,4 @@ - \ No newline at end of file + diff --git a/app/InkForge.Desktop/InkForgeFactory.cs b/app/InkForge.Desktop/InkForgeFactory.cs index 7183d12..a59d961 100644 --- a/app/InkForge.Desktop/InkForgeFactory.cs +++ b/app/InkForge.Desktop/InkForgeFactory.cs @@ -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> - { - ["Root"] = () => _rootDock, - ["Documents"] = () => _documentDock, - ["Workspace"] = () => _workspaceTool, - }; - - HostWindowLocator = new Dictionary> - { - [nameof(IDockWindow)] = () => new HostWindow() - }; - - base.InitLayout(layout); + return ActivatorUtilities.CreateInstance( + 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().Dock, + }; + + AddDockable(_mainDock, dock); + CloseOtherDockables(dock); + } } diff --git a/app/InkForge.Desktop/Managers/DocumentManager.cs b/app/InkForge.Desktop/Managers/DocumentManager.cs index 7aff6e9..9b9784c 100644 --- a/app/InkForge.Desktop/Managers/DocumentManager.cs +++ b/app/InkForge.Desktop/Managers/DocumentManager.cs @@ -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("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( - Application.Current!.GetValue(App.ServiceProviderProperty) - ); + Name = "Untitled Note", + }, new()); + _factory.AddDockable(Dock, editViewModel); + _factory.SetActiveDockable(editViewModel); + _factory.SetFocusedDockable(Dock, editViewModel); } } diff --git a/app/InkForge.Desktop/Managers/WorkspaceManager.cs b/app/InkForge.Desktop/Managers/WorkspaceManager.cs index 3bee05d..f5bd450 100644 --- a/app/InkForge.Desktop/Managers/WorkspaceManager.cs +++ b/app/InkForge.Desktop/Managers/WorkspaceManager.cs @@ -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().Load().ConfigureAwait(false); + scope = null; } catch (Exception) diff --git a/app/InkForge.Desktop/Microsoft/Extensions/DependencyInjection/InkForgeServiceCollection.cs b/app/InkForge.Desktop/Microsoft/Extensions/DependencyInjection/InkForgeServiceCollection.cs index 328f8ce..bf99529 100644 --- a/app/InkForge.Desktop/Microsoft/Extensions/DependencyInjection/InkForgeServiceCollection.cs +++ b/app/InkForge.Desktop/Microsoft/Extensions/DependencyInjection/InkForgeServiceCollection.cs @@ -23,7 +23,6 @@ public static class InkForgeServiceCollections // Singletons // - Concrete - services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -33,6 +32,7 @@ public static class InkForgeServiceCollections // Scoped // - Concrete + services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/app/InkForge.Desktop/Models/Note.cs b/app/InkForge.Desktop/Models/Note.cs index 58a0295..c19675e 100644 --- a/app/InkForge.Desktop/Models/Note.cs +++ b/app/InkForge.Desktop/Models/Note.cs @@ -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 _parent; - private readonly BehaviorSubject _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(), - }).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; } diff --git a/app/InkForge.Desktop/Models/NoteStore.cs b/app/InkForge.Desktop/Models/NoteStore.cs index 6be8a08..ff9d6aa 100644 --- a/app/InkForge.Desktop/Models/NoteStore.cs +++ b/app/InkForge.Desktop/Models/NoteStore.cs @@ -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 dbContextFactory) { - private readonly IDbContextFactory _dbContextFactory; - private readonly SourceCache _notesCache = new(m => m.Id); + public AvaloniaDictionary Notes { get; } = []; - public ReadOnlyObservableCollection Notes { get; } - - public NoteStore(IDbContextFactory 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 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 + }; } } diff --git a/app/InkForge.Desktop/Models/TextDocumentStore.cs b/app/InkForge.Desktop/Models/TextDocumentStore.cs new file mode 100644 index 0000000..2e079a0 --- /dev/null +++ b/app/InkForge.Desktop/Models/TextDocumentStore.cs @@ -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 dbContextFactory) +{ + private readonly AvaloniaDictionary _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 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 byteBuffer = stackalloc byte[128]; + using var reader = text.CreateReader(); + ArrayBufferWriter buffer = new(); + var encoder = Encoding.UTF8.GetEncoder(); + while (reader.Peek() != -1) + { + var memory = buffer.GetMemory(); + buffer.Advance(reader.Read(memory.Span)); + ReadOnlySpan 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 remaining = []; + if (!chars.IsEmpty) + { + remaining = buffer.GetSpan(chars.Length); + chars.CopyTo(remaining); + } + + buffer.Clear(); + if (!remaining.IsEmpty) + { + buffer.Write(remaining); + } + } + + return length; + } + } +} diff --git a/app/InkForge.Desktop/Models/Workspace.cs b/app/InkForge.Desktop/Models/Workspace.cs index 4cb23de..7da3d8c 100644 --- a/app/InkForge.Desktop/Models/Workspace.cs +++ b/app/InkForge.Desktop/Models/Workspace.cs @@ -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)); - // } } diff --git a/app/InkForge.Desktop/Program.cs b/app/InkForge.Desktop/Program.cs index 12dc592..5f8493d 100644 --- a/app/InkForge.Desktop/Program.cs +++ b/app/InkForge.Desktop/Program.cs @@ -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() .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(), }; } diff --git a/app/InkForge.Desktop/Services/TopLevels.cs b/app/InkForge.Desktop/Services/TopLevels.cs index 05f63d9..c5344d1 100644 --- a/app/InkForge.Desktop/Services/TopLevels.cs +++ b/app/InkForge.Desktop/Services/TopLevels.cs @@ -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; } } diff --git a/app/InkForge.Desktop/ViewModels/Documents/NoteEditDocumentViewModel.cs b/app/InkForge.Desktop/ViewModels/Documents/NoteEditDocumentViewModel.cs new file mode 100644 index 0000000..9dfe8c9 --- /dev/null +++ b/app/InkForge.Desktop/ViewModels/Documents/NoteEditDocumentViewModel.cs @@ -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!); + } +} diff --git a/app/InkForge.Desktop/ViewModels/Documents/WelcomePageDocumentViewModel.cs b/app/InkForge.Desktop/ViewModels/Documents/WelcomePageDocumentViewModel.cs index 8108738..d916dc0 100644 --- a/app/InkForge.Desktop/ViewModels/Documents/WelcomePageDocumentViewModel.cs +++ b/app/InkForge.Desktop/ViewModels/Documents/WelcomePageDocumentViewModel.cs @@ -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 CreateNew { get; } - - public ReactiveCommand 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()!; diff --git a/app/InkForge.Desktop/ViewModels/InkForgeDocumentDock.cs b/app/InkForge.Desktop/ViewModels/InkForgeDocumentDock.cs deleted file mode 100644 index bd0f1f6..0000000 --- a/app/InkForge.Desktop/ViewModels/InkForgeDocumentDock.cs +++ /dev/null @@ -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 { } - } -} diff --git a/app/InkForge.Desktop/ViewModels/MainViewModel.cs b/app/InkForge.Desktop/ViewModels/MainViewModel.cs index 6925461..469c7dc 100644 --- a/app/InkForge.Desktop/ViewModels/MainViewModel.cs +++ b/app/InkForge.Desktop/ViewModels/MainViewModel.cs @@ -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( - Application.Current!.GetValue(App.ServiceProviderProperty) - ); } } diff --git a/app/InkForge.Desktop/ViewModels/RecentItemViewModel.cs b/app/InkForge.Desktop/ViewModels/RecentItemViewModel.cs index c5dd596..4f4a08b 100644 --- a/app/InkForge.Desktop/ViewModels/RecentItemViewModel.cs +++ b/app/InkForge.Desktop/ViewModels/RecentItemViewModel.cs @@ -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; +} diff --git a/app/InkForge.Desktop/ViewModels/Tools/WorkspaceTool.cs b/app/InkForge.Desktop/ViewModels/Tools/WorkspaceTool.cs index b5f0777..583c976 100644 --- a/app/InkForge.Desktop/ViewModels/Tools/WorkspaceTool.cs +++ b/app/InkForge.Desktop/ViewModels/Tools/WorkspaceTool.cs @@ -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 + }; } } diff --git a/app/InkForge.Desktop/ViewModels/Workspaces/WorkspaceViewModel.cs b/app/InkForge.Desktop/ViewModels/Workspaces/WorkspaceViewModel.cs index f28ea4e..6a7bab7 100644 --- a/app/InkForge.Desktop/ViewModels/Workspaces/WorkspaceViewModel.cs +++ b/app/InkForge.Desktop/ViewModels/Workspaces/WorkspaceViewModel.cs @@ -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 _workspaceNameProperty; + private readonly ReadOnlyObservableCollection> _notes; - // public string WorkspaceName => _workspaceNameProperty.Value; + public string Name => _workspace.Name; - // public ReactiveCommand AddDocument { get; } + public ReadOnlyObservableCollection> 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? s_workspaceViewModelFactory; - public WorkspaceViewModel Create(Workspace workspace) + public static WorkspaceViewModel Create(Workspace workspace) { s_workspaceViewModelFactory ??= ActivatorUtilities.CreateFactory([typeof(Workspace)]); - return s_workspaceViewModelFactory(services, [workspace]); + return s_workspaceViewModelFactory(workspace.Services, [workspace]); } WorkspaceViewModel IWorkspaceViewModelFactory.Create(Workspace workspace) => Create(workspace); diff --git a/app/InkForge.Desktop/Views/Documents/NoteEditDocument.axaml b/app/InkForge.Desktop/Views/Documents/NoteEditDocument.axaml new file mode 100644 index 0000000..da3412d --- /dev/null +++ b/app/InkForge.Desktop/Views/Documents/NoteEditDocument.axaml @@ -0,0 +1,15 @@ + + + diff --git a/app/InkForge.Desktop/Views/Documents/NoteEditDocument.axaml.cs b/app/InkForge.Desktop/Views/Documents/NoteEditDocument.axaml.cs new file mode 100644 index 0000000..8c0f5d4 --- /dev/null +++ b/app/InkForge.Desktop/Views/Documents/NoteEditDocument.axaml.cs @@ -0,0 +1,11 @@ +using Avalonia.Controls; + +namespace InkForge.Desktop.Views.Documents; + +public partial class NoteEditDocument : UserControl +{ + public NoteEditDocument() + { + InitializeComponent(); + } +} diff --git a/app/InkForge.Desktop/Views/Documents/WelcomePageDocument.axaml b/app/InkForge.Desktop/Views/Documents/WelcomePageDocument.axaml index 63d4366..5b3a72e 100644 --- a/app/InkForge.Desktop/Views/Documents/WelcomePageDocument.axaml +++ b/app/InkForge.Desktop/Views/Documents/WelcomePageDocument.axaml @@ -28,11 +28,11 @@ + Command="{CompiledBinding CreateNewCommand}" /> + Command="{CompiledBinding OpenNewCommand}" /> - \ No newline at end of file + diff --git a/app/InkForge.Desktop/Views/Documents/WelcomePageDocument.axaml.cs b/app/InkForge.Desktop/Views/Documents/WelcomePageDocument.axaml.cs index 61334e9..563892b 100644 --- a/app/InkForge.Desktop/Views/Documents/WelcomePageDocument.axaml.cs +++ b/app/InkForge.Desktop/Views/Documents/WelcomePageDocument.axaml.cs @@ -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 +public partial class WelcomePageDocument : UserControl { public WelcomePageDocument() { diff --git a/app/InkForge.Desktop/Views/MainWindow.axaml.cs b/app/InkForge.Desktop/Views/MainWindow.axaml.cs index 42b4735..d7bbbf9 100644 --- a/app/InkForge.Desktop/Views/MainWindow.axaml.cs +++ b/app/InkForge.Desktop/Views/MainWindow.axaml.cs @@ -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 +public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); - ViewModel = CreateViewModel(); + DataContext = CreateViewModel(); } private static MainViewModel CreateViewModel() diff --git a/app/InkForge.Desktop/Views/Tools/WorkspaceTool.axaml b/app/InkForge.Desktop/Views/Tools/WorkspaceTool.axaml index c5e7d85..a0b6cd4 100644 --- a/app/InkForge.Desktop/Views/Tools/WorkspaceTool.axaml +++ b/app/InkForge.Desktop/Views/Tools/WorkspaceTool.axaml @@ -29,4 +29,4 @@ - \ No newline at end of file + diff --git a/app/InkForge.Desktop/Views/Tools/WorkspaceTool.axaml.cs b/app/InkForge.Desktop/Views/Tools/WorkspaceTool.axaml.cs index a0cfc1d..534ff7d 100644 --- a/app/InkForge.Desktop/Views/Tools/WorkspaceTool.axaml.cs +++ b/app/InkForge.Desktop/Views/Tools/WorkspaceTool.axaml.cs @@ -1,8 +1,8 @@ -using Avalonia.ReactiveUI; +using Avalonia.Controls; namespace InkForge.Desktop.Views.Tools; -public partial class WorkspaceTool : ReactiveUserControl +public partial class WorkspaceTool : UserControl { public WorkspaceTool() { diff --git a/app/InkForge.Desktop/Views/Workspaces/WorkspaceView.axaml b/app/InkForge.Desktop/Views/Workspaces/WorkspaceView.axaml index c07ca41..549557f 100644 --- a/app/InkForge.Desktop/Views/Workspaces/WorkspaceView.axaml +++ b/app/InkForge.Desktop/Views/Workspaces/WorkspaceView.axaml @@ -12,7 +12,8 @@ - - - - \ No newline at end of file + diff --git a/app/InkForge.Desktop/Views/Workspaces/WorkspaceView.axaml.cs b/app/InkForge.Desktop/Views/Workspaces/WorkspaceView.axaml.cs index 1505573..9386646 100644 --- a/app/InkForge.Desktop/Views/Workspaces/WorkspaceView.axaml.cs +++ b/app/InkForge.Desktop/Views/Workspaces/WorkspaceView.axaml.cs @@ -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 +public partial class WorkspaceView : UserControl { public WorkspaceView() { diff --git a/shared/InkForge.Data/Blob.cs b/shared/InkForge.Data/Blob.cs index 6596df0..a3630fd 100644 --- a/shared/InkForge.Data/Blob.cs +++ b/shared/InkForge.Data/Blob.cs @@ -1,3 +1,3 @@ namespace InkForge.Data; -public class Blob : Entity; +public class Blob : Entity; diff --git a/shared/InkForge.Data/NoteDbContext.cs b/shared/InkForge.Data/NoteDbContext.cs index 0d058ec..17fecbe 100644 --- a/shared/InkForge.Data/NoteDbContext.cs +++ b/shared/InkForge.Data/NoteDbContext.cs @@ -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().WithOne().HasForeignKey(m => m.ContentId).IsRequired(); + }); options.HasOne(m => m.Parent); }); diff --git a/shared/InkForge.Data/Notes.cs b/shared/InkForge.Data/Notes.cs index 894d143..98fbcb3 100644 --- a/shared/InkForge.Data/Notes.cs +++ b/shared/InkForge.Data/Notes.cs @@ -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; diff --git a/shared/migrations/InkForge.Sqlite/Migrations/20240207000000__01_Initial.Designer.cs b/shared/migrations/InkForge.Sqlite/Migrations/20240207000000__01_Initial.Designer.cs deleted file mode 100644 index bcfd152..0000000 --- a/shared/migrations/InkForge.Sqlite/Migrations/20240207000000__01_Initial.Designer.cs +++ /dev/null @@ -1,172 +0,0 @@ -// -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 - { - /// - 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("Id") - .HasColumnType("TEXT"); - - b.Property("Content") - .IsRequired() - .HasColumnType("BLOB"); - - b.HasKey("Id"); - - b.ToTable("Blobs"); - }); - - modelBuilder.Entity("InkForge.Data.Infrastructure.NoteEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.ToTable("Notes"); - }); - - modelBuilder.Entity("InkForge.Data.Infrastructure.NoteVersionEntity", b => - { - b.Property("Version") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("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("ParentId") - .HasColumnType("INTEGER"); - - b1.Property("ContentId") - .IsRequired() - .HasColumnType("TEXT"); - - b1.Property("Created") - .HasColumnType("TEXT"); - - b1.Property("Deleted") - .HasColumnType("TEXT"); - - b1.Property("Name") - .IsRequired() - .HasColumnType("TEXT"); - - b1.Property("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("NoteVersionEntityVersion") - .HasColumnType("INTEGER"); - - b1.Property("ContentId") - .IsRequired() - .HasColumnType("TEXT"); - - b1.Property("Created") - .HasColumnType("TEXT"); - - b1.Property("Deleted") - .HasColumnType("TEXT"); - - b1.Property("Name") - .IsRequired() - .HasColumnType("TEXT"); - - b1.Property("ParentId") - .HasColumnType("INTEGER"); - - b1.Property("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 - } - } -} diff --git a/shared/migrations/InkForge.Sqlite/Migrations/20240214000000__02_Metadata.Designer.cs b/shared/migrations/InkForge.Sqlite/Migrations/20240214000000__02_Metadata.Designer.cs deleted file mode 100644 index 89a839f..0000000 --- a/shared/migrations/InkForge.Sqlite/Migrations/20240214000000__02_Metadata.Designer.cs +++ /dev/null @@ -1,207 +0,0 @@ -// -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 - { - /// - 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("Id") - .HasColumnType("TEXT"); - - b.Property("Content") - .IsRequired() - .HasColumnType("BLOB"); - - b.HasKey("Id"); - - b.ToTable("Blobs"); - }); - - modelBuilder.Entity("InkForge.Data.Infrastructure.MetadataEntity", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("Value") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Metadata"); - }); - - modelBuilder.Entity("InkForge.Data.Infrastructure.MetadataVersionEntity", b => - { - b.Property("Version") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("Value") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Version"); - - b.HasIndex("Id", "Version") - .IsUnique(); - - b.ToTable("MetadataHistory"); - }); - - modelBuilder.Entity("InkForge.Data.Infrastructure.NoteEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.ToTable("Notes"); - }); - - modelBuilder.Entity("InkForge.Data.Infrastructure.NoteVersionEntity", b => - { - b.Property("Version") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("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("ParentId") - .HasColumnType("INTEGER"); - - b1.Property("ContentId") - .IsRequired() - .HasColumnType("TEXT"); - - b1.Property("Created") - .HasColumnType("TEXT"); - - b1.Property("Deleted") - .HasColumnType("TEXT"); - - b1.Property("Name") - .IsRequired() - .HasColumnType("TEXT"); - - b1.Property("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("NoteVersionEntityVersion") - .HasColumnType("INTEGER"); - - b1.Property("ContentId") - .IsRequired() - .HasColumnType("TEXT"); - - b1.Property("Created") - .HasColumnType("TEXT"); - - b1.Property("Deleted") - .HasColumnType("TEXT"); - - b1.Property("Name") - .IsRequired() - .HasColumnType("TEXT"); - - b1.Property("ParentId") - .HasColumnType("INTEGER"); - - b1.Property("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 - } - } -} diff --git a/shared/migrations/InkForge.Sqlite/Migrations/20240214000000__02_Metadata.cs b/shared/migrations/InkForge.Sqlite/Migrations/20240214000000__02_Metadata.cs deleted file mode 100644 index 698c390..0000000 --- a/shared/migrations/InkForge.Sqlite/Migrations/20240214000000__02_Metadata.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace InkForge.Sqlite.Migrations -{ - /// - public partial class _02_Metadata : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "Metadata", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - Value = table.Column(type: "TEXT", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Metadata", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "MetadataHistory", - columns: table => new - { - Version = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Value = table.Column(type: "TEXT", nullable: false), - Id = table.Column(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); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "Metadata"); - - migrationBuilder.DropTable( - name: "MetadataHistory"); - } - } -} diff --git a/shared/migrations/InkForge.Sqlite/Migrations/20240405000000__03_DropHistory.Designer.cs b/shared/migrations/InkForge.Sqlite/Migrations/20240401_Initial.Designer.cs similarity index 83% rename from shared/migrations/InkForge.Sqlite/Migrations/20240405000000__03_DropHistory.Designer.cs rename to shared/migrations/InkForge.Sqlite/Migrations/20240401_Initial.Designer.cs index eaf5c59..8a092e2 100644 --- a/shared/migrations/InkForge.Sqlite/Migrations/20240405000000__03_DropHistory.Designer.cs +++ b/shared/migrations/InkForge.Sqlite/Migrations/20240401_Initial.Designer.cs @@ -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 { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -22,8 +22,9 @@ namespace InkForge.Sqlite.Migrations modelBuilder.Entity("InkForge.Data.Blob", b => { - b.Property("Id") - .HasColumnType("TEXT"); + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); b.Property("Value") .IsRequired() @@ -75,9 +76,8 @@ namespace InkForge.Sqlite.Migrations b1.Property("NoteEntityId") .HasColumnType("INTEGER"); - b1.Property("ContentId") - .IsRequired() - .HasColumnType("TEXT"); + b1.Property("ContentId") + .HasColumnType("INTEGER"); b1.Property("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"); diff --git a/shared/migrations/InkForge.Sqlite/Migrations/20240207000000__01_Initial.cs b/shared/migrations/InkForge.Sqlite/Migrations/20240401_Initial.cs similarity index 56% rename from shared/migrations/InkForge.Sqlite/Migrations/20240207000000__01_Initial.cs rename to shared/migrations/InkForge.Sqlite/Migrations/20240401_Initial.cs index 0ac6965..0f53da1 100644 --- a/shared/migrations/InkForge.Sqlite/Migrations/20240207000000__01_Initial.cs +++ b/shared/migrations/InkForge.Sqlite/Migrations/20240401_Initial.cs @@ -6,7 +6,7 @@ using Microsoft.EntityFrameworkCore.Migrations; namespace InkForge.Sqlite.Migrations { /// - public partial class _01_Initial : Migration + public partial class _20240401_Initial : Migration { /// protected override void Up(MigrationBuilder migrationBuilder) @@ -15,25 +15,38 @@ namespace InkForge.Sqlite.Migrations name: "Blobs", columns: table => new { - Id = table.Column(type: "TEXT", nullable: false), - Content = table.Column(type: "BLOB", nullable: false) + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Value = table.Column(type: "BLOB", nullable: false) }, constraints: table => { table.PrimaryKey("PK_Blobs", x => x.Id); }); + migrationBuilder.CreateTable( + name: "Metadata", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Value = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Metadata", x => x.Id); + }); + migrationBuilder.CreateTable( name: "Notes", columns: table => new { - Id = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), + Id = table.Column(type: "INTEGER", nullable: false), Value_Created = table.Column(type: "TEXT", nullable: false), + Value_ContentId = table.Column(type: "INTEGER", nullable: false), + Value_Deleted = table.Column(type: "TEXT", nullable: true), Value_Name = table.Column(type: "TEXT", nullable: false), Value_Updated = table.Column(type: "TEXT", nullable: false), - Value_Deleted = table.Column(type: "TEXT", nullable: true), - Value_ContentId = table.Column(type: "TEXT", nullable: false) + ParentId = table.Column(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(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Value_Created = table.Column(type: "TEXT", nullable: false), - Value_ParentId = table.Column(type: "INTEGER", nullable: true), - Value_Name = table.Column(type: "TEXT", nullable: false), - Value_Updated = table.Column(type: "TEXT", nullable: false), - Value_Deleted = table.Column(type: "TEXT", nullable: true), - Value_ContentId = table.Column(type: "TEXT", nullable: false), - Id = table.Column(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"); } /// protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable( - name: "NoteVersions"); + name: "Metadata"); migrationBuilder.DropTable( name: "Notes"); diff --git a/shared/migrations/InkForge.Sqlite/Migrations/20240405000000__03_DropHistory.cs b/shared/migrations/InkForge.Sqlite/Migrations/20240405000000__03_DropHistory.cs deleted file mode 100644 index ca5ed12..0000000 --- a/shared/migrations/InkForge.Sqlite/Migrations/20240405000000__03_DropHistory.cs +++ /dev/null @@ -1,149 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace InkForge.Sqlite.Migrations -{ - /// - public partial class _03_DropHistory : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "MetadataHistory"); - - migrationBuilder.DropTable( - name: "NoteVersions"); - - migrationBuilder.RenameColumn( - name: "Content", - table: "Blobs", - newName: "Value"); - - migrationBuilder.AlterColumn( - name: "Id", - table: "Notes", - type: "INTEGER", - nullable: false, - oldClrType: typeof(int), - oldType: "INTEGER") - .OldAnnotation("Sqlite:Autoincrement", true); - - migrationBuilder.AddColumn( - 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"); - } - - /// - 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( - 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(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Id = table.Column(type: "TEXT", nullable: true), - Value = table.Column(type: "TEXT", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_MetadataHistory", x => x.Version); - }); - - migrationBuilder.CreateTable( - name: "NoteVersions", - columns: table => new - { - Version = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Id = table.Column(type: "INTEGER", nullable: false), - Value_ContentId = table.Column(type: "TEXT", nullable: false), - Value_ParentId = table.Column(type: "INTEGER", nullable: true), - Value_Created = table.Column(type: "TEXT", nullable: false), - Value_Deleted = table.Column(type: "TEXT", nullable: true), - Value_Name = table.Column(type: "TEXT", nullable: false), - Value_Updated = table.Column(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"); - } - } -} diff --git a/shared/migrations/InkForge.Sqlite/Migrations/NoteDbContextModelSnapshot.cs b/shared/migrations/InkForge.Sqlite/Migrations/NoteDbContextModelSnapshot.cs index 09d4e1f..2c82e81 100644 --- a/shared/migrations/InkForge.Sqlite/Migrations/NoteDbContextModelSnapshot.cs +++ b/shared/migrations/InkForge.Sqlite/Migrations/NoteDbContextModelSnapshot.cs @@ -19,8 +19,9 @@ namespace InkForge.Sqlite.Migrations modelBuilder.Entity("InkForge.Data.Blob", b => { - b.Property("Id") - .HasColumnType("TEXT"); + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); b.Property("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("NoteEntityId") .HasColumnType("INTEGER"); - b1.Property("ContentId") - .IsRequired() - .HasColumnType("TEXT"); + b1.Property("ContentId") + .HasColumnType("INTEGER"); b1.Property("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");