From e88ec44c92bf91a6f4216b90c32b348cf34446c8 Mon Sep 17 00:00:00 2001 From: Flaky Date: Wed, 22 Apr 2026 18:04:14 +0100 Subject: [PATCH] Implement ambiugous tag parent choice upon tag addition. --- .../MusicBeeTagHierarchyImporter.cs | 1 + .../TagDatabase/TagDatabase.ImportMethods.cs | 1 + .../TagDatabase/TagDatabase.WriteMethods.cs | 66 +++++++++++++++++- UI/App.axaml | 2 + UI/Controls/HierarchyTree.axaml | 13 ++-- UI/Controls/SearchPane.axaml | 6 +- UI/TagDatabaseService.cs | 62 +++++++++++++++-- UI/ViewModels/BulkAddViewModel.cs | 15 ++++- UI/ViewModels/HierarchyTreeViewModel.cs | 18 ++--- UI/ViewModels/SaveAmbiguousViewModel.cs | 29 ++++++++ UI/ViewModels/SearchViewModel.cs | 2 +- UI/ViewModels/TagEditorViewModel.cs | 12 ++-- UI/ViewModels/TagItemViewModel.cs | 67 ++++++++----------- UI/Views/Dialogs/SaveAmbiguousDialog.axaml | 48 +++++++++++++ UI/Views/Dialogs/SaveAmbiguousDialog.axaml.cs | 35 ++++++++++ 15 files changed, 300 insertions(+), 77 deletions(-) create mode 100644 UI/ViewModels/SaveAmbiguousViewModel.cs create mode 100644 UI/Views/Dialogs/SaveAmbiguousDialog.axaml create mode 100644 UI/Views/Dialogs/SaveAmbiguousDialog.axaml.cs diff --git a/Core/Importers/MusicBeeTagHierarchyImporter/MusicBeeTagHierarchyImporter.cs b/Core/Importers/MusicBeeTagHierarchyImporter/MusicBeeTagHierarchyImporter.cs index 870d84e..c9dcdef 100644 --- a/Core/Importers/MusicBeeTagHierarchyImporter/MusicBeeTagHierarchyImporter.cs +++ b/Core/Importers/MusicBeeTagHierarchyImporter/MusicBeeTagHierarchyImporter.cs @@ -93,6 +93,7 @@ public partial class MusicBeeTagHierarchyImporter : Importer private static void ValidateHierarchyData(string tagHierarchyData) { + // TODO change on the fly instead of erroring out? if (tagHierarchyData.Contains('\t')) throw new ArgumentException(ErrorMessages.TagHierarchyTabsDetected); if (tagHierarchyData.StartsWith(' ')) throw new ArgumentException(ErrorMessages.TagHierarchyStartsWithSpace); diff --git a/Core/Models/TagDatabase/TagDatabase.ImportMethods.cs b/Core/Models/TagDatabase/TagDatabase.ImportMethods.cs index 5bc69ce..4e4258c 100644 --- a/Core/Models/TagDatabase/TagDatabase.ImportMethods.cs +++ b/Core/Models/TagDatabase/TagDatabase.ImportMethods.cs @@ -27,6 +27,7 @@ public partial class TagDatabase ?? await this.SelectTagFromDatabase(tag.Name).ConfigureAwait(false); if (currentTag is null) throw new InvalidOperationException(ErrorMessages.TagNotFound); + // todo search parent here then save the parents. await this.SaveTagParents(transaction, currentTag.Id, tag.Parents, currentTag).ConfigureAwait(false); } diff --git a/Core/Models/TagDatabase/TagDatabase.WriteMethods.cs b/Core/Models/TagDatabase/TagDatabase.WriteMethods.cs index 62b0af7..f5e5d9f 100644 --- a/Core/Models/TagDatabase/TagDatabase.WriteMethods.cs +++ b/Core/Models/TagDatabase/TagDatabase.WriteMethods.cs @@ -15,9 +15,11 @@ public partial class TagDatabase public async Task WriteTagsToDatabase(List tags, SqliteTransaction? transaction = null) { this.CheckInitialisation(); + bool success = false; bool isTransactionOwner = transaction == null; transaction ??= (SqliteTransaction)await this.currentConnection.BeginTransactionAsync().ConfigureAwait(false); + List oldTags = []; List updatedTags = []; List newlyAddedTags = []; @@ -28,6 +30,17 @@ public partial class TagDatabase { foreach (var tag in tags) { + if (tag.Id != 0) oldTags.Add(new Tag + { + Id = tag.Id, + Name = tag.Name, + ParentIds = tag.ParentIds, + TagBindings = tag.TagBindings, + Aliases = tag.Aliases, + Notes = tag.Notes, + IsTopLevel = tag.IsTopLevel, + }); + bool alreadyOnDatabase = tag.Id != 0; SqliteCommand addCommand = this.currentConnection.CreateCommand(); @@ -40,8 +53,10 @@ public partial class TagDatabase tag.Id = Convert.ToInt32(await addCommand.ExecuteScalarAsync().ConfigureAwait(false), CultureInfo.InvariantCulture); - await this.SaveTagParents(transaction, tag.Id, tag.Parents, tag).ConfigureAwait(false); - + // deprecated - TODO process via TagDatabaseService instead. + // await this.SaveTagParents(transaction, tag.Id, tag.Parents, tag).ConfigureAwait(false); + await SaveTagParentIds(transaction, tag); + int index = this.Tags.FindIndex(t => t.Id == tag.Id); if (index != -1) { @@ -53,9 +68,14 @@ public partial class TagDatabase pendingAdditions.Add(tag); newlyAddedTags.Add(tag); } + + tag.CreatedAt ??= DateTime.Now; + tag.UpdatedAt = DateTime.Now; + + } - if (isTransactionOwner) await transaction.CommitAsync().ConfigureAwait(false); + if (isTransactionOwner) await transaction.CommitAsync(); foreach (var (index, tag) in pendingUpdates) this.Tags[index] = tag; @@ -67,6 +87,12 @@ public partial class TagDatabase catch (Exception) { await transaction.RollbackAsync().ConfigureAwait(false); + foreach (var tag in oldTags) + { + int index = this.Tags.FindIndex(t => t.Id == tag.Id); + + if (index != -1) this.Tags[index] = tag; + } throw; } finally @@ -74,6 +100,40 @@ public partial class TagDatabase if (isTransactionOwner) await transaction.DisposeAsync().ConfigureAwait(false); } } + + private async Task SaveTagParentIds(SqliteTransaction transaction, Tag tag) + { + if (tag.ParentIds.Count == 0) return; + this.CheckInitialisation(); + + // clear existing tag parents so we have a clean slate. + SqliteCommand deleteCommand = this.currentConnection.CreateCommand(); + deleteCommand.Transaction = transaction; + deleteCommand.CommandText = """ + DELETE FROM tag_parent_link + WHERE target_tag_id = @tag_id + """; + deleteCommand.Parameters.AddWithValue("@tag_id", tag.Id); + await deleteCommand.ExecuteNonQueryAsync().ConfigureAwait(false); + + // process + add parent links + SqliteCommand parentCommand = this.currentConnection.CreateCommand(); + parentCommand.Transaction = transaction; + parentCommand.CommandText = """ + INSERT INTO tag_parent_link (target_tag_id, parent_tag_id) + VALUES (@target_tag_id, @parent_tag_id) + """; + parentCommand.Parameters.Clear(); + parentCommand.Parameters.AddWithValue("@target_tag_id", tag.Id); + parentCommand.Parameters.Add("@parent_tag_id", SqliteType.Integer); + await parentCommand.PrepareAsync(); + + foreach (int parentId in tag.ParentIds) + { + parentCommand.Parameters["@parent_tag_id"].Value = (long)parentId; + await parentCommand.ExecuteNonQueryAsync().ConfigureAwait(false); + } + } private async Task SaveTagParents(SqliteTransaction transaction, int id, IReadOnlyCollection parents, Tag? tag = null) diff --git a/UI/App.axaml b/UI/App.axaml index 278df15..82861be 100644 --- a/UI/App.axaml +++ b/UI/App.axaml @@ -3,6 +3,7 @@ x:Class="TagHierarchyManager.UI.App" xmlns:local="using:TagHierarchyManager.UI" xmlns:semi="https://irihi.tech/semi" + xmlns:u-semi="https://irihi.tech/ursa/themes/semi" RequestedThemeVariant="Dark"> @@ -12,6 +13,7 @@ + \ No newline at end of file diff --git a/UI/Controls/HierarchyTree.axaml b/UI/Controls/HierarchyTree.axaml index 2d7c5a9..a3b3724 100644 --- a/UI/Controls/HierarchyTree.axaml +++ b/UI/Controls/HierarchyTree.axaml @@ -13,7 +13,8 @@ ContextRequested="TagTree_ContextRequested" VerticalAlignment="Stretch" ItemsSource="{Binding TopLevelTagNodes, FallbackValue={x:Null}}" - SelectedItem="{Binding SelectedTag, Mode=TwoWay, FallbackValue={x:Null}}"> + SelectedItem="{Binding SelectedTag, Mode=TwoWay, FallbackValue={x:Null}}" + Margin="2"> - + - + - + - + diff --git a/UI/Controls/SearchPane.axaml b/UI/Controls/SearchPane.axaml index 62bed1d..efbf0c1 100644 --- a/UI/Controls/SearchPane.axaml +++ b/UI/Controls/SearchPane.axaml @@ -46,13 +46,13 @@ - + - + diff --git a/UI/TagDatabaseService.cs b/UI/TagDatabaseService.cs index a20066c..0f040c6 100644 --- a/UI/TagDatabaseService.cs +++ b/UI/TagDatabaseService.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; using TagHierarchyManager.Common; @@ -10,6 +12,8 @@ using TagHierarchyManager.Exporters; using TagHierarchyManager.Importers; using TagHierarchyManager.Models; using TagHierarchyManager.UI.Assets; +using TagHierarchyManager.UI.ViewModels; +using TagHierarchyManager.UI.Views; namespace TagHierarchyManager.UI; @@ -154,16 +158,64 @@ public class TagDatabaseService : ObservableObject this.NotifyDatabasePropertiesChanged(); } - public async Task WriteTagsToDatabase(List tags) + public async Task WriteTagsToDatabase(List tags) { + var tagsToSave = new List(); if (this.Database is null) return; - foreach (var tag in tags) + foreach (var tagToSave in tags.Select(tag => new Tag + { + Id = tag.Id, + Name = tag.CurrentName, + IsTopLevel = tag.EditingIsTopLevel, + Aliases = tag.EditingAliases + .Split(';', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).ToList(), + TagBindings = tag.EditingTagBindings + .Split(';', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).ToList(), + Notes = tag.EditingNotes, + Parents = tag.EditingParents + .Split(';', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).ToList(), + })) { - tag.CreatedAt ??= DateTime.Now; - tag.UpdatedAt = DateTime.Now; + var success = await this.GetSavingTagParents(tagToSave); + if (!success) return; + tagToSave.Validate(); + tagsToSave.Add(tagToSave); } - await this.Database.WriteTagsToDatabase(tags); + await this.Database.WriteTagsToDatabase(tagsToSave); + tags.ForEach(tag => tag.CommitEdit()); + } + + private async Task GetSavingTagParents(Tag tag) + { + if (this.Database is null) return false; + if (tag.Parents.Count == 0) return true; + foreach (var parentName in tag.Parents) + { + var parentTags = this.Database.Tags.Where(t => t.Name == parentName).ToList(); + switch (parentTags.Count) + { + case 0: + throw new Exception($"Parent tag '{parentName}' not found."); + case 1: + tag.ParentIds.Add(parentTags[0].Id); + continue; + case > 1: + var ambiguousVm = new SaveAmbiguousViewModel(this, tag, parentTags); + + var dialog = new SaveAmbiguousDialog(); + var dialogOwner = (Application.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime) + ?.Windows + .FirstOrDefault(w => w.IsActive); + dialog.DataContext = ambiguousVm; + var result = await dialog.ShowDialog(dialogOwner!); + + if (result == null) return false; + tag.ParentIds.Add(result.Id); + break; + } + } + return true; } private static IExporter PickExporterFromFileExt(string path) diff --git a/UI/ViewModels/BulkAddViewModel.cs b/UI/ViewModels/BulkAddViewModel.cs index f3ba103..c84f9f0 100644 --- a/UI/ViewModels/BulkAddViewModel.cs +++ b/UI/ViewModels/BulkAddViewModel.cs @@ -20,13 +20,15 @@ public partial class BulkAddViewModel : ViewModelBase [ObservableProperty] private ObservableCollection _tags; [ObservableProperty] private string _windowTitle; + + private TagDatabaseService TagDatabaseService => this._mainWindow.TagDatabaseService; public BulkAddViewModel(MainWindowViewModel mainWindow) { this._mainWindow = mainWindow; this.WindowTitle = string.Format("{0} - " + Resources.ButtonBulkAdd, - this._mainWindow.TagDatabaseService.DatabaseName); + this.TagDatabaseService.DatabaseName); this._tags = [ @@ -34,7 +36,7 @@ public partial class BulkAddViewModel : ViewModelBase { Name = "", IsTopLevel = false, - TagBindings = string.Join("; ", this._mainWindow.TagDatabaseService.DefaultTagBindings) + TagBindings = string.Join("; ", this.TagDatabaseService.DefaultTagBindings) } ]; @@ -89,6 +91,7 @@ public partial class BulkAddViewModel : ViewModelBase { var tag = new Tag { + Name = tagRow.Name, IsTopLevel = tagRow.IsTopLevel, Parents = !string.IsNullOrWhiteSpace(tagRow.Parents) @@ -114,7 +117,13 @@ public partial class BulkAddViewModel : ViewModelBase }) .ToList(); - await this._mainWindow.TagDatabaseService.WriteTagsToDatabase(tags); + await this._mainWindow.TagDatabaseService.WriteTagsToDatabase( + tags.Select(t => + { + var tVm = new TagItemViewModel(t, this.TagDatabaseService.GetParentNamesByIds); + tVm.BeginEdit(); + return tVm; + }).ToList()); this._mainWindow.StatusBlockText = string.Format(Resources.StatusBlockBulkAddSuccess, tags.Count); this.RequestClose?.Invoke(); } diff --git a/UI/ViewModels/HierarchyTreeViewModel.cs b/UI/ViewModels/HierarchyTreeViewModel.cs index 3b73162..c6fd21f 100644 --- a/UI/ViewModels/HierarchyTreeViewModel.cs +++ b/UI/ViewModels/HierarchyTreeViewModel.cs @@ -81,7 +81,7 @@ public partial class HierarchyTreeViewModel : ViewModelBase, IDisposable { if (beingUpdated) { - tag.Children.Clear(); + tag.CurrentChildren.Clear(); if (this.ChildNodeMap.TryGetValue(tag.Id, out var existingParents)) existingParents.Clear(); } @@ -98,7 +98,7 @@ public partial class HierarchyTreeViewModel : ViewModelBase, IDisposable this.AddTagNodeToViewModelMap(childNode); - tag.Children.Add(childNode); + tag.CurrentChildren.Add(childNode); this.AddAllChildrenAsync(childNode); } } @@ -109,7 +109,7 @@ public partial class HierarchyTreeViewModel : ViewModelBase, IDisposable foreach (var parent in parentViewModels) { - if (parent.Children.Any(c => c.Id == tag.Id)) continue; + if (parent.CurrentChildren.Any(c => c.Id == tag.Id)) continue; await Dispatcher.UIThread.InvokeAsync(() => { @@ -117,10 +117,10 @@ public partial class HierarchyTreeViewModel : ViewModelBase, IDisposable this.AddAllChildrenAsync(tagNode); var index = 0; - while (index < parent.Children.Count && string.Compare(parent.Children[index].Name, tagNode.Name, + while (index < parent.CurrentChildren.Count && string.Compare(parent.CurrentChildren[index].CurrentName, tagNode.CurrentName, StringComparison.CurrentCultureIgnoreCase) < 0) index++; - parent.Children.Insert(index, tagNode); + parent.CurrentChildren.Insert(index, tagNode); this.AddTagNodeToViewModelMap(tagNode); }); } @@ -150,7 +150,7 @@ public partial class HierarchyTreeViewModel : ViewModelBase, IDisposable var index = 0; while (index < this.TopLevelTagNodes.Count - && string.Compare(this.TopLevelTagNodes[index].Name, newTopLevelTag.Name, + && string.Compare(this.TopLevelTagNodes[index].CurrentName, newTopLevelTag.CurrentName, StringComparison.CurrentCultureIgnoreCase) < 0) index++; @@ -167,8 +167,8 @@ public partial class HierarchyTreeViewModel : ViewModelBase, IDisposable foreach (var parentTag in parentViewModels) { var foundChild = - parentTag.Children.FirstOrDefault(t => t.Id == idToDelete)!; - parentTag.Children.Remove(foundChild); + parentTag.CurrentChildren.FirstOrDefault(t => t.Id == idToDelete)!; + parentTag.CurrentChildren.Remove(foundChild); } @@ -244,7 +244,7 @@ public partial class HierarchyTreeViewModel : ViewModelBase, IDisposable if (newTag.IsTopLevel) await this.AddTopLevelNode(newTag); - if (newTag.ParentIds.Count == 0) return; + if (newTag.ParentIds.Count == 0) continue; foreach (var parentId in newTag.ParentIds) await this.AddChildNode(newTag, parentId); } diff --git a/UI/ViewModels/SaveAmbiguousViewModel.cs b/UI/ViewModels/SaveAmbiguousViewModel.cs new file mode 100644 index 0000000..9c42953 --- /dev/null +++ b/UI/ViewModels/SaveAmbiguousViewModel.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using CommunityToolkit.Mvvm.ComponentModel; +using TagHierarchyManager.Models; + +namespace TagHierarchyManager.UI.ViewModels; + +public partial class SaveAmbiguousViewModel : ViewModelBase +{ + + [ObservableProperty] private Tag _currentTag; + + [ObservableProperty] private List _tags; + + [ObservableProperty] private TagItemViewModel? _selectedTag; + + public SaveAmbiguousViewModel(TagDatabaseService tagDatabaseService, Tag currentTag, + List tags) + { + this._tags = tags.Select(t => new TagItemViewModel(t, tagDatabaseService.GetParentNamesByIds)).ToList(); + this._currentTag = currentTag; + } + + +} \ No newline at end of file diff --git a/UI/ViewModels/SearchViewModel.cs b/UI/ViewModels/SearchViewModel.cs index a4f9ce4..e60734d 100644 --- a/UI/ViewModels/SearchViewModel.cs +++ b/UI/ViewModels/SearchViewModel.cs @@ -52,7 +52,7 @@ public partial class SearchViewModel : ViewModelBase, IDisposable results.Select(tag => new TagItemViewModel(tag, this._getParentNamesById)) - .OrderBy(tag => tag.Name) + .OrderBy(tag => tag.CurrentName) .ToList() .ForEach(this.SearchResults.Add); diff --git a/UI/ViewModels/TagEditorViewModel.cs b/UI/ViewModels/TagEditorViewModel.cs index d6443c7..721724f 100644 --- a/UI/ViewModels/TagEditorViewModel.cs +++ b/UI/ViewModels/TagEditorViewModel.cs @@ -41,12 +41,12 @@ public partial class TagEditorViewModel(MainWindowViewModel mainWindow) : ViewMo if (userWantsToSave is null) return; this._mainWindow.SelectedTag = new TagItemViewModel( - new Tag - { + new Tag { Name = string.Empty, IsTopLevel = true, TagBindings = this.TagDatabaseService.DefaultTagBindings - } + }, + this.TagDatabaseService.GetParentNamesByIds ); this.UnsavedChanges = true; } @@ -59,12 +59,10 @@ public partial class TagEditorViewModel(MainWindowViewModel mainWindow) : ViewMo if (this._mainWindow is null || this.TagDatabaseService is null) return; if (this._mainWindow.SelectedTag is null || !this.TagDatabaseService.IsDatabaseOpen || !this._mainWindow.IsDbEnabled) return; - - this._mainWindow.SelectedTag.CommitEdit(); - await this.TagDatabaseService.WriteTagsToDatabase([this._mainWindow.SelectedTag.Tag]); + await this.TagDatabaseService.WriteTagsToDatabase([this._mainWindow.SelectedTag]); this._mainWindow.SelectedTag.RefreshParentsString(); this._mainWindow.StatusBlockText = string.Format(Resources.StatusBlockTagSaveSuccessful, - this._mainWindow.SelectedTag.Name); + this._mainWindow.SelectedTag.CurrentName); this.UnsavedChanges = false; } catch (Exception ex) diff --git a/UI/ViewModels/TagItemViewModel.cs b/UI/ViewModels/TagItemViewModel.cs index 5354c5f..b8d3e81 100644 --- a/UI/ViewModels/TagItemViewModel.cs +++ b/UI/ViewModels/TagItemViewModel.cs @@ -9,7 +9,7 @@ using TagHierarchyManager.UI.Assets; namespace TagHierarchyManager.UI.ViewModels; -public partial class TagItemViewModel(Tag tag, Func, List>? getParentNamesByIds = null) +public partial class TagItemViewModel(Tag tag, Func, List> getParentNamesByIds) : ViewModelBase { [ObservableProperty] private string _editingAliases = string.Empty; @@ -24,6 +24,7 @@ public partial class TagItemViewModel(Tag tag, Func, List>? ge [ObservableProperty] private string _editingTagBindings = string.Empty; + private bool _isInitialising; public event EventHandler? UserEditedTag; @@ -35,72 +36,58 @@ public partial class TagItemViewModel(Tag tag, Func, List>? ge public bool CanBeDeleted => !this.HasChildren; - public bool HasChildren => this.Children.Count > 0; + public bool HasChildren => this.CurrentChildren.Count > 0; public int Id => this.Tag.Id; - public string Name => this.Tag.Name; + public string CurrentName => this.Tag.Name; - public string Notes => this.Tag.Notes; + public string CurrentNotes => this.Tag.Notes; public bool OnDatabase => this.Id != 0; - public string TagBindings => + public string CurrentTagBindings => this.Tag.TagBindings.Count > 0 ? string.Join("; ", this.Tag.TagBindings) : string.Empty; - public ObservableCollection Children { get; set; } = []; + public ObservableCollection CurrentChildren { get; set; } = []; internal Tag Tag { get; set; } = tag; private bool IsTopLevel => this.Tag.IsTopLevel; - private string Parents => getParentNamesByIds is not null && this.Tag.ParentIds is { Count: > 0 } - ? string.Join("; ", getParentNamesByIds(this.Tag.ParentIds)) + internal string CurrentParentsString => this.Tag.Parents is { Count: > 0 } + ? string.Join("; ", this.Tag.Parents) : string.Empty; - + + private List CurrentParentIds => this.Tag.ParentIds; + private List EditingParentIds = []; + public void BeginEdit() { this._isInitialising = true; this.EditingName = this.Tag.Name; if (this.OnDatabase || string.IsNullOrEmpty(this.EditingParents)) - this.EditingParents = this.Parents; + this.EditingParents = this.CurrentParentsString; this.EditingIsTopLevel = this.IsTopLevel; - this.EditingTagBindings = this.TagBindings; + this.EditingTagBindings = this.CurrentTagBindings; this.EditingAliases = this.Aliases; - this.EditingNotes = this.Notes; + this.EditingNotes = this.CurrentNotes; + this.EditingParentIds = this.CurrentParentIds; this._isInitialising = false; } public void CommitEdit() { this.Validate(); - this.Tag.Name = this.EditingName; - this.Tag.Parents = !string.IsNullOrWhiteSpace(this.EditingParents) - ? this.EditingParents.Split(';', StringSplitOptions.RemoveEmptyEntries | - StringSplitOptions.TrimEntries) - .ToList() - : []; - this.Tag.TagBindings = !string.IsNullOrWhiteSpace(this.EditingTagBindings) - ? this.EditingTagBindings.Split(';', StringSplitOptions.RemoveEmptyEntries | - StringSplitOptions.TrimEntries) - .ToList() - : []; - this.Tag.Aliases = !string.IsNullOrWhiteSpace(this.EditingAliases) - ? this.EditingAliases.Split(';', StringSplitOptions.RemoveEmptyEntries | - StringSplitOptions.TrimEntries) - .ToList() - : []; - this.Tag.Notes = !string.IsNullOrWhiteSpace(this.EditingNotes) ? this.EditingNotes : ""; - this.Tag.IsTopLevel = this.EditingIsTopLevel; - this.OnPropertyChanged(nameof(this.Name)); - this.OnPropertyChanged(nameof(this.Parents)); + this.OnPropertyChanged(nameof(this.CurrentName)); + this.OnPropertyChanged(nameof(this.CurrentParentsString)); this.OnPropertyChanged(nameof(this.Aliases)); - this.OnPropertyChanged(nameof(this.TagBindings)); - this.OnPropertyChanged(nameof(this.Notes)); + this.OnPropertyChanged(nameof(this.CurrentTagBindings)); + this.OnPropertyChanged(nameof(this.CurrentNotes)); this.OnPropertyChanged(nameof(this.IsTopLevel)); this.OnPropertyChanged(nameof(this.HasChildren)); } @@ -108,8 +95,8 @@ public partial class TagItemViewModel(Tag tag, Func, List>? ge public void RefreshParentsString() { this._isInitialising = true; - this.OnPropertyChanged(nameof(this.Parents)); - var newParents = this.Parents; + this.OnPropertyChanged(nameof(this.CurrentParentsString)); + var newParents = this.CurrentParentsString; if (!string.IsNullOrEmpty(newParents)) { this.EditingParents = newParents; @@ -121,11 +108,11 @@ public partial class TagItemViewModel(Tag tag, Func, List>? ge public void RefreshSelf() { - this.OnPropertyChanged(nameof(this.Name)); - this.OnPropertyChanged(nameof(this.Parents)); + this.OnPropertyChanged(nameof(this.CurrentName)); + this.OnPropertyChanged(nameof(this.CurrentParentsString)); this.OnPropertyChanged(nameof(this.Aliases)); - this.OnPropertyChanged(nameof(this.TagBindings)); - this.OnPropertyChanged(nameof(this.Notes)); + this.OnPropertyChanged(nameof(this.CurrentTagBindings)); + this.OnPropertyChanged(nameof(this.CurrentNotes)); this.OnPropertyChanged(nameof(this.IsTopLevel)); this.OnPropertyChanged(nameof(this.HasChildren)); } diff --git a/UI/Views/Dialogs/SaveAmbiguousDialog.axaml b/UI/Views/Dialogs/SaveAmbiguousDialog.axaml new file mode 100644 index 0000000..67906d3 --- /dev/null +++ b/UI/Views/Dialogs/SaveAmbiguousDialog.axaml @@ -0,0 +1,48 @@ + + + + + SaveAmbiguousDialogDescription + + + + + + + + + + + + + + + + + + + + +