From 754c142c8d01a2b1c973e4d16bd6932c6cd980c6 Mon Sep 17 00:00:00 2001 From: DSPAUL Date: Tue, 7 Nov 2023 21:56:59 +0100 Subject: [PATCH 001/100] Fix tags clearing when quick adding tag --- src/ViewModels/CodexEditViewModel.cs | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/ViewModels/CodexEditViewModel.cs b/src/ViewModels/CodexEditViewModel.cs index e06ceb17..8a640836 100644 --- a/src/ViewModels/CodexEditViewModel.cs +++ b/src/ViewModels/CodexEditViewModel.cs @@ -115,15 +115,35 @@ private void UpdateTagList() public ActionCommand QuickCreateTagCommand => _quickCreateTagCommand ??= new(QuickCreateTag); public void QuickCreateTag() { - TagPropWindow tpw = new(new TagEditViewModel(null, true)) + //keep track of count to check of tags were created + int tagCount = MainViewModel.CollectionVM.CurrentCollection.RootTags.Count; + + TagEditViewModel tagEditVM = new(null, createNew: true); + TagPropWindow tpw = new(tagEditVM) { Topmost = true }; _ = tpw.ShowDialog(); - //recalculate treeview source - _treeViewSource = null; - RaisePropertyChanged(nameof(TreeViewSource)); + if (MainViewModel.CollectionVM.CurrentCollection.RootTags.Count > tagCount) //new tag was created + { + //recalculate treeview source + _treeViewSource = null; + RaisePropertyChanged(nameof(TreeViewSource)); + + //Apply right checkboxes in AllTags + foreach (TreeViewNode t in AllTreeViewNodes) + { + t.Expanded = false; + t.Selected = TempCodex.Tags.Contains(t.Tag); + if (t.Children.Any(node => TempCodex.Tags.Contains(node.Tag))) t.Expanded = true; + } + + //check the newly created tag + TreeViewSource.Last().Selected = true; + + UpdateTagList(); + } } private ActionCommand _deleteCodexCommand; From 00aabc30bd564f011f27fb3fce7d25e904bbbc43 Mon Sep 17 00:00:00 2001 From: DSPAUL Date: Fri, 10 Nov 2023 20:00:43 +0100 Subject: [PATCH 002/100] Change messages with outdated wording --- src/ViewModels/CodexViewModel.cs | 19 +++++++++---------- src/ViewModels/CollectionViewModel.cs | 12 ++++++------ src/ViewModels/Import/ImportViewModel.cs | 2 +- src/ViewModels/SettingsViewModel.cs | 2 +- src/Windows/SettingsWindow.xaml | 4 ++-- 5 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/ViewModels/CodexViewModel.cs b/src/ViewModels/CodexViewModel.cs index 4fa42b0a..27d203ec 100644 --- a/src/ViewModels/CodexViewModel.cs +++ b/src/ViewModels/CodexViewModel.cs @@ -25,11 +25,11 @@ public class CodexViewModel : ObservableObject, IDropTarget public static bool OpenCodex(Codex codex) { bool success = PreferableFunction.TryFunctions(SettingsViewModel.GetInstance().OpenCodexPriority, codex); - if (!success) MessageBox.Show("Could not open codex, please check local path or URL"); + if (!success) MessageBox.Show("Could not open item, please check local path or URL"); return success; } - //Open File Offline + //Open codex Offline private ReturningRelayCommand _openCodexLocallyCommand; public ReturningRelayCommand OpenCodexLocallyCommand => _openCodexLocallyCommand ??= new(OpenCodexLocally, CanOpenCodexLocally); public static bool OpenCodexLocally(Codex toOpen) @@ -62,7 +62,7 @@ public static bool CanOpenCodexLocally(Codex toOpen) return toOpen.HasOfflineSource(); } - //Open File Online + //Open codex Online private ReturningRelayCommand _openCodexOnlineCommand; public ReturningRelayCommand OpenCodexOnlineCommand => _openCodexOnlineCommand ??= new(OpenCodexOnline, CanOpenCodexOnline); public static bool OpenCodexOnline(Codex toOpen) @@ -80,7 +80,7 @@ public static bool OpenCodexOnline(Codex toOpen) { Logger.Error($"Failed to open {toOpen.SourceURL}", ex); //fails if no internet, pinging 8.8.8.8 DNS instead of server because some sites like gmbinder block ping - if (!Utils.PingURL()) Logger.Warn($"Cannot open online files when not connected to the internet", ex); + if (!Utils.PingURL()) Logger.Warn($"Cannot open this item online when not connected to the internet", ex); return false; } @@ -105,7 +105,7 @@ public static bool OpenSelectedCodices(IList toOpen) } //MessageBox "Are you Sure?" - string messageBoxText = "You are about to open " + toOpen.Count + " Files. Are you sure you wish to continue?"; + string messageBoxText = "You are about to open " + toOpen.Count + " items. Are you sure you wish to continue?"; const string caption = "Are you Sure?"; const MessageBoxButton btnMessageBox = MessageBoxButton.YesNo; @@ -239,8 +239,8 @@ public static void MoveToCollection(CodexCollection targetCollection, List "The Tag data seems to be corrupted and could not be read.", - -2 => "The Codex data seems to be corrupted and could not be read.", - -3 => "Both the Tag and Codex data seems to be corrupted and could not be read.", + -1 => "The save file for the Tags seems to be corrupted and could not be read.", + -2 => "The save file with all items seems to be corrupted and could not be read.", + -3 => "Both the save file with tags and items seems to be corrupted and could not be read.", _ => "" }; - _ = MessageBox.Show($"Could not load {collection.DirectoryName}. \n" + msg, "Fail to Load Collection", MessageBoxButton.OK, MessageBoxImage.Error); + _ = MessageBox.Show($"Could not load {collection.DirectoryName}. \n" + msg, "Failed to Load Collection", MessageBoxButton.OK, MessageBoxImage.Error); return; } @@ -254,8 +254,8 @@ public void RaiseDeleteCollectionWarning() //MessageBox "Are you Sure?" string sCaption = "Are you Sure?"; - const string messageSingle = "There is still one file in this collection, if you don't want to remove these from COMPASS, move them to another collection first. Are you sure you want to continue?"; - string messageMultiple = $"There are still {CurrentCollection.AllCodices.Count} files in this collection, if you don't want to remove these from COMPASS, move them to another collection first. Are you sure you want to continue?"; + const string messageSingle = "There is still one item in this collection, if you don't want to remove it from COMPASS, move it to another collection first. Are you sure you want to continue?"; + string messageMultiple = $"There are still {CurrentCollection.AllCodices.Count} items in this collection, if you don't want to remove these from COMPASS, move them to another collection first. Are you sure you want to continue?"; string sMessageBoxText = CurrentCollection.AllCodices.Count == 1 ? messageSingle : messageMultiple; diff --git a/src/ViewModels/Import/ImportViewModel.cs b/src/ViewModels/Import/ImportViewModel.cs index c1ba4089..53616014 100644 --- a/src/ViewModels/Import/ImportViewModel.cs +++ b/src/ViewModels/Import/ImportViewModel.cs @@ -72,7 +72,7 @@ public static async void ImportFiles(List paths, CodexCollection targetC { targetCollection ??= MainViewModel.CollectionVM.CurrentCollection; - //filter out files already in collection & banned paths + //filter out codices already in collection & banned paths IEnumerable existingPaths = targetCollection.AllCodices.Select(codex => codex.Path); paths = paths .Except(existingPaths) diff --git a/src/ViewModels/SettingsViewModel.cs b/src/ViewModels/SettingsViewModel.cs index fd8842ab..feccfe94 100644 --- a/src/ViewModels/SettingsViewModel.cs +++ b/src/ViewModels/SettingsViewModel.cs @@ -361,7 +361,7 @@ public int AmountRenamed } } - public string RenameCompleteMessage => $"Renamed Path Reference in {AmountRenamed} Files."; + public string RenameCompleteMessage => $"Renamed Path Reference in {AmountRenamed} items."; private RelayCommand _renameFolderRefCommand; public RelayCommand RenameFolderRefCommand => _renameFolderRefCommand ??= new(RenameFolderReferences); diff --git a/src/Windows/SettingsWindow.xaml b/src/Windows/SettingsWindow.xaml index 159b94fa..87900c9e 100644 --- a/src/Windows/SettingsWindow.xaml +++ b/src/Windows/SettingsWindow.xaml @@ -82,7 +82,7 @@ - @@ -474,7 +474,7 @@ - Only files with broken references will be updated and only if the updated path points to a file that exists. + Only items with broken references will be updated and only if the updated path points to a file that exists. If you moved half of your files out of a folder, you can safely use this tool to rename the folder reference. From 73fa5463ebbe420f1c3be9fb81570deb32de4de0 Mon Sep 17 00:00:00 2001 From: DSPAUL Date: Sun, 12 Nov 2023 16:41:23 +0100 Subject: [PATCH 003/100] Rename another ref to files --- src/Models/CodexCollection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Models/CodexCollection.cs b/src/Models/CodexCollection.cs index 54b88cfc..0d0b50d9 100644 --- a/src/Models/CodexCollection.cs +++ b/src/Models/CodexCollection.cs @@ -306,7 +306,7 @@ public void DeleteCodex(Codex toDelete) public void DeleteCodices(IList toDelete) { int count = toDelete.Count; - string message = $"You are about to remove {count} file{(count > 1 ? @"s" : @"")}. " + + string message = $"You are about to remove {count} item{(count > 1 ? @"s" : @"")}. " + $"This cannot be undone. " + $"Are you sure you want to continue?"; var result = MessageBox.Show(message, "Remove", MessageBoxButton.OKCancel); From fcfb7fd85a7ffe885ff9bbce52fc472ea893c98f Mon Sep 17 00:00:00 2001 From: DSPAUL Date: Sun, 12 Nov 2023 17:38:18 +0100 Subject: [PATCH 004/100] Add sorting of tags, closes #31 --- src/ViewModels/TagsViewModel.cs | 32 +++++++++++++++++++++++++++++++- src/Views/LeftDockView.xaml | 29 ++++++++++++++++++++++++----- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/ViewModels/TagsViewModel.cs b/src/ViewModels/TagsViewModel.cs index 9a999a5f..25e8d451 100644 --- a/src/ViewModels/TagsViewModel.cs +++ b/src/ViewModels/TagsViewModel.cs @@ -172,6 +172,37 @@ private void CreateChildTag() } } + private ActionCommand _sortChildrenCommand; + public ActionCommand SortChildrenCommand => _sortChildrenCommand ??= new(SortChildren, CanSortChildren); + public void SortChildren() + { + SortChildren(ContextTag); + BuildTagTreeView(); + } + public void SortChildren(Tag tag) + { + if (tag == null) return; + tag.Children = new(tag.Children.OrderBy(t => t.Content)); + foreach (Tag child in tag.Children) + { + SortChildren(child); + } + } + public bool CanSortChildren() => CanSortChildren(ContextTag); + public bool CanSortChildren(Tag tag) => tag?.Children.Any() == true; + + private ActionCommand _sortAllTagsCommand; + public ActionCommand SortAllTagsCommand => _sortAllTagsCommand ??= new(SortAllTags); + public void SortAllTags() + { + Tag t = new() + { + Children = new(MainViewModel.CollectionVM.CurrentCollection.RootTags) + }; + SortChildren(t); + MainViewModel.CollectionVM.CurrentCollection.RootTags = t.Children.ToList(); + BuildTagTreeView(); + } private ActionCommand _editTagCommand; public ActionCommand EditTagCommand => _editTagCommand ??= new(EditTag); @@ -187,7 +218,6 @@ public void EditTag() private ActionCommand _deleteTagCommand; public ActionCommand DeleteTagCommand => _deleteTagCommand ??= new(DeleteTag); - public void DeleteTag() { //tag to delete is context, because DeleteTag is called from context menu diff --git a/src/Views/LeftDockView.xaml b/src/Views/LeftDockView.xaml index 19d8c286..d1a4e393 100644 --- a/src/Views/LeftDockView.xaml +++ b/src/Views/LeftDockView.xaml @@ -20,6 +20,7 @@ mc:Ignorable="d" FontSize="20" d:DesignHeight="650" d:DesignWidth="300"> + + + + - From 44352280d104f144737ee70c79c2cbe5777d32e1 Mon Sep 17 00:00:00 2001 From: DSPAUL Date: Sun, 26 Nov 2023 19:30:50 +0100 Subject: [PATCH 014/100] Pull selection logic out of import --- src/Models/CollectionInfo.cs | 1 + .../CollectionContentSelectorViewModel.cs | 267 ++++++++++++++++++ .../Import/ImportCollectionViewModel.cs | 249 +++------------- src/Windows/ImportCollectionWizard.xaml | 39 +-- 4 files changed, 321 insertions(+), 235 deletions(-) create mode 100644 src/ViewModels/CollectionContentSelectorViewModel.cs diff --git a/src/Models/CollectionInfo.cs b/src/Models/CollectionInfo.cs index c7d2859d..09256480 100644 --- a/src/Models/CollectionInfo.cs +++ b/src/Models/CollectionInfo.cs @@ -61,6 +61,7 @@ public void MergeWith(CollectionInfo other) AutoImportDirectories.AddRange(other.AutoImportDirectories); BanishedPaths.AddRange(other.BanishedPaths); //For file type prefs, overwrite if already in dict, add otherwise + other.PrepareSave(); // make sure serializable prefs are in sync in prefs in other foreach (var pref in other.SerializableFiletypePreferences) { var existing = SerializableFiletypePreferences.FirstOrDefault(x => x.Key == pref.Key); diff --git a/src/ViewModels/CollectionContentSelectorViewModel.cs b/src/ViewModels/CollectionContentSelectorViewModel.cs new file mode 100644 index 00000000..a7092740 --- /dev/null +++ b/src/ViewModels/CollectionContentSelectorViewModel.cs @@ -0,0 +1,267 @@ +using COMPASS.Commands; +using COMPASS.Models; +using COMPASS.Tools; +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Windows.Data; + +namespace COMPASS.ViewModels +{ + /// + /// Class with logic to select only a subset of the content in a collection for import and export purposes + /// + public class CollectionContentSelectorViewModel : WizardViewModel + { + public CollectionContentSelectorViewModel(CodexCollection completeCollection) + { + CompleteCollection = completeCollection; + + if (MainViewModel.CollectionVM.CurrentCollection == completeCollection) + { + CompleteCollection.Save(); + } + else + { + CompleteCollection.Load(hidden: true); + } + + //Checks which steps need to be included in wizard + HasCodices = CompleteCollection.AllCodices.Any(); + HasTags = CompleteCollection.AllTags.Any(); + HasSettings = CompleteCollection.Info.ContainsSettings(); + UpdateSteps(); + + //Put Tags in Checkable Wrapper + SelectableTags = CompleteCollection.RootTags.Select(t => new CheckableTreeNode(t)).ToList(); + + //Put codices in dictionary so they can be labeled true/false for import + SelectableCodices = CompleteCollection.AllCodices.Select(codex => new SelectableCodex(codex)).ToList(); + + //prep settings data for selection + AutoImportFolders = CompleteCollection.Info.AutoImportDirectories.Select(folder => new SelectableWithPathHelper(folder)).ToList(); + BanishedPaths = CompleteCollection.Info.BanishedPaths.Select(path => new SelectableWithPathHelper(path)).ToList(); + FileTypePrefs = CompleteCollection.Info.FiletypePreferences + .Select(x => new ObservableKeyValuePair(x)) + .OrderByDescending(x => x.Value) + .ToList(); + FolderTagLinks = CompleteCollection.Info.FolderTagPairs + .Select(link => new SelectableFolderTagLink(link.Folder, link.Tag, Utils.FlattenTree(CheckableTreeNode.GetCheckedItems(SelectableTags)))) + .ToList(); + } + + /// + /// Complete collections whose content will be subselected + /// + public CodexCollection CompleteCollection { get; set; } + + private CodexCollection _curatedCollection; + /// + /// Curated collection that containts only the selected items + /// + public CodexCollection CuratedCollection + { + get => _curatedCollection ??= new("__tmp_collection"); + set => _curatedCollection = value; + } + + public bool HasTags { get; set; } + public bool HasCodices { get; set; } + public bool HasSettings { get; set; } + + //TAGS STEP + public List> SelectableTags { get; set; } + + // CODICES STEP + public List SelectableCodices { get; set; } + + public bool RemovePersonalData { get; set; } = true; + + //SETTINGS STEP + + //Auto Import Folders + private bool _selectAutoImportFolders = false; + public bool SelectAutoImportFolders + { + get => _selectAutoImportFolders; + set => SetProperty(ref _selectAutoImportFolders, value); + } + public List AutoImportFolders { get; init; } + + //Banished paths + private bool _selectBanishedFiles = false; + public bool SelectBanishedFiles + { + get => _selectBanishedFiles; + set => SetProperty(ref _selectBanishedFiles, value); + } + public List BanishedPaths { get; init; } + + //File type preferences + private bool _selectFileTypePrefs = false; + public bool SelectFileTypePrefs + { + get => _selectFileTypePrefs; + set => SetProperty(ref _selectFileTypePrefs, value); + } + public List> FileTypePrefs { get; init; } + + //Tag-Folder links + private bool _selectFolderTagLinks = false; + public bool SelectFolderTagLinks + { + get => _selectFolderTagLinks; + set => SetProperty(ref _selectFolderTagLinks, value); + } + public List FolderTagLinks { get; init; } + + public CollectionViewSource FolderTagLinksVS + { + get + { + CollectionViewSource temp = new() + { + Source = FolderTagLinks, + }; + temp.SortDescriptions.Add(new SortDescription("Folder", ListSortDirection.Ascending)); + return temp; + } + } + + #region Helper classes + public class SelectableWithPathHelper : ObservableObject + { + public SelectableWithPathHelper(string path) + { + Path = path; + Selected = PathExits; + } + + private bool _selected; + public bool Selected + { + get => _selected; + set => SetProperty(ref _selected, value); + } + + public string Path { get; set; } + + public bool PathExits => !System.IO.Path.IsPathFullyQualified(Path) || System.IO.Path.Exists(Path); + } + + public class SelectableFolderTagLink : SelectableWithPathHelper + { + public SelectableFolderTagLink(string path, Tag t, IEnumerable existingTags) : base(path) + { + Tag = t; + _existingTags = existingTags; + } + private IEnumerable _existingTags; + public Tag Tag { get; } + public bool TagExists => _existingTags.Contains(Tag); + } + + public class SelectableCodex : SelectableWithPathHelper + { + public SelectableCodex(Codex codex) : base(codex.Path) + { + Codex = codex; + } + public Codex Codex { get; } + + private RelayCommand _itemCheckedCommand; + public RelayCommand ItemCheckedCommand => _itemCheckedCommand ??= new((items) => + items.Cast() + .ToList() + .ForEach(c => c.Selected = Selected)); + } + #endregion + + public void ApplySelectedTags() + { + CuratedCollection.RootTags = CheckableTreeNode.GetCheckedItems(SelectableTags).ToList(); + + //Remove the tags that didn't make it from codices + var RemovedTags = CompleteCollection.AllTags.Except(Utils.FlattenTree(CompleteCollection.RootTags)).ToList(); + foreach (Tag t in RemovedTags) + { + CuratedCollection.AllTags.Remove(t); + foreach (var codex in CuratedCollection.AllCodices) + { + codex.Tags.Remove(t); + } + } + } + + public void ApplySelectedCodices() + { + if (RemovePersonalData) + { + SelectableCodices.ForEach(c => c.Codex.ClearPersonalData()); + } + + CuratedCollection.AllCodices.Clear(); + CuratedCollection.AllCodices.AddRange(SelectableCodices.Where(x => x.Selected).Select(x => x.Codex)); + } + + public void ApplySelectedPreferences() + { + + CuratedCollection.Info.AutoImportDirectories.Clear(); + if (SelectAutoImportFolders) + { + CuratedCollection.Info.AutoImportDirectories = new(AutoImportFolders.Where(x => x.Selected).Select(x => x.Path)); + } + + CuratedCollection.Info.BanishedPaths.Clear(); + if (SelectBanishedFiles) + { + CuratedCollection.Info.BanishedPaths = new(BanishedPaths.Where(x => x.Selected).Select(x => x.Path)); + } + + if (!SelectFileTypePrefs) + { + //for file types, select all or nothing because checking whether to select a checkbox becomes ridiculous + CuratedCollection.Info.FiletypePreferences.Clear(); + } + + CuratedCollection.Info.FolderTagPairs.Clear(); + if (SelectFolderTagLinks) + { + CuratedCollection.Info.FolderTagPairs = new(FolderTagLinks.Where(linkHelper => linkHelper.Selected && linkHelper.TagExists) + .Select(linkHelper => new FolderTagPair(linkHelper.Path, linkHelper.Tag))); + } + } + + public override void Finish() + { + + ApplySelectedTags(); + ApplySelectedCodices(); + ApplySelectedPreferences(); + } + + public void UpdateSteps() + { + //Checks which steps need to be included in wizard + Steps.Clear(); + + if (HasTags) + { + Steps.Add("Tags"); + } + if (HasCodices) + { + Steps.Add("Items"); + } + if (HasSettings) + { + Steps.Add("Settings"); + } + + RaisePropertyChanged(nameof(Steps)); + } + } +} diff --git a/src/ViewModels/Import/ImportCollectionViewModel.cs b/src/ViewModels/Import/ImportCollectionViewModel.cs index db996544..b0dcce76 100644 --- a/src/ViewModels/Import/ImportCollectionViewModel.cs +++ b/src/ViewModels/Import/ImportCollectionViewModel.cs @@ -1,12 +1,6 @@ -using COMPASS.Commands; -using COMPASS.Models; +using COMPASS.Models; using COMPASS.Tools; -using System.Collections; -using System.Collections.Generic; -using System.ComponentModel; using System.IO; -using System.Linq; -using System.Windows.Data; namespace COMPASS.ViewModels.Import { @@ -15,30 +9,12 @@ public class ImportCollectionViewModel : WizardViewModel public ImportCollectionViewModel(CodexCollection collectionToImport) { CollectionToImport = collectionToImport; - CollectionToImport.Load(hidden: true); + CollectionName = CollectionToImport.DirectoryName.Substring(2, CollectionToImport.DirectoryName.Length - 2 - Constants.COMPASSFileExtension.Length); - //Checks which steps need to be included in wizard - HasCodices = CollectionToImport.AllCodices.Any(); - HasTags = CollectionToImport.AllTags.Any(); - HasSettings = CollectionToImport.Info.ContainsSettings(); - UpdateSteps(); - - //Put Tags in Checkable Wrapper - TagsToImport = CollectionToImport.RootTags.Select(t => new CheckableTreeNode(t)).ToList(); + ContentSelectorVM = new(collectionToImport); + ContentSelectorVM.CuratedCollection = collectionToImport; //The import collection is tmp anyway so result can be saved on top of it - //Put codices in dictionary so they can be labeled true/false for import - CodicesToImport = CollectionToImport.AllCodices.Select(codex => new ImportCodexHelper(codex)).ToList(); - - //prep settings data for selection - AutoImportFolders = CollectionToImport.Info.AutoImportDirectories.Select(folder => new ImportPathHelper(folder)).ToList(); - BanishedPaths = CollectionToImport.Info.BanishedPaths.Select(path => new ImportPathHelper(path)).ToList(); - FileTypePrefs = CollectionToImport.Info.FiletypePreferences - .Select(x => new ObservableKeyValuePair(x)) - .OrderByDescending(x => x.Value) - .ToList(); - FolderTagLinks = CollectionToImport.Info.FolderTagPairs - .Select(link => new ImportFolderTagLinkHelper(link.Folder, link.Tag, Utils.FlattenTree(CheckableTreeNode.GetCheckedItems(TagsToImport)))) - .ToList(); + UpdateSteps(); //if files were included in compass file, set paths of codices to those files if (Directory.Exists(CollectionToImport.UserFilesPath)) @@ -54,11 +30,12 @@ public ImportCollectionViewModel(CodexCollection collectionToImport) } } - public CodexCollection TargetCollection { get; set; } = null; //null means new collection should be made + public CollectionContentSelectorViewModel ContentSelectorVM { get; set; } + public CodexCollection CollectionToImport { get; set; } = null; //collection that was in the cmpss file //OVERVIEW STEP - public bool MergeIntoCollection { get; set; } = true; + public bool MergeIntoCollection { get; set; } = false; private string _collectionName = "Unnamed Collection"; public string CollectionName @@ -72,10 +49,6 @@ public string CollectionName } public bool IsCollectionNameLegal => CollectionViewModel.IsLegalCollectionName(CollectionName); - public bool HasTags { get; set; } - public bool HasCodices { get; set; } - public bool HasSettings { get; set; } - public bool ImportAllTags { get; set; } = true; public bool ImportAllCodices { get; set; } = true; public bool ImportAllSettings { get; set; } = true; @@ -91,114 +64,6 @@ public bool AdvancedImport } } - //TAGS STEP - public List> TagsToImport { get; set; } - - // CODICES STEP - public List CodicesToImport { get; set; } - - public bool RemovePersonalData { get; set; } = true; - - //SETTINGS STEP - - //Auto Import Folders - private bool _importAutoImportFolders = false; - public bool ImportAutoImportFolders - { - get => (_importAutoImportFolders && AdvancedImport) || (ImportAllSettings && !AdvancedImport); - set => SetProperty(ref _importAutoImportFolders, value); - } - public List AutoImportFolders { get; init; } - - //Banished paths - private bool _importBanishedFiles = false; - public bool ImportBanishedFiles - { - get => (_importBanishedFiles && AdvancedImport) || (ImportAllSettings && !AdvancedImport); - set => SetProperty(ref _importBanishedFiles, value); - } - public List BanishedPaths { get; init; } - - //File type preferences - private bool _importFileTypePrefs = false; - public bool ImportFileTypePrefs - { - get => (_importFileTypePrefs && AdvancedImport) || (ImportAllSettings && !AdvancedImport); - set => SetProperty(ref _importFileTypePrefs, value); - } - public List> FileTypePrefs { get; init; } - - //Tag-Folder links - private bool _importFolderTagLinks = false; - public bool ImportFolderTagLinks - { - get => (_importFolderTagLinks && AdvancedImport) || (ImportAllSettings && !AdvancedImport); - set => SetProperty(ref _importFolderTagLinks, value); - } - public List FolderTagLinks { get; init; } - - public CollectionViewSource FolderTagLinksVS - { - get - { - CollectionViewSource temp = new() - { - Source = FolderTagLinks, - }; - temp.SortDescriptions.Add(new SortDescription("Folder", ListSortDirection.Ascending)); - return temp; - } - } - - #region Helper classes - public class ImportPathHelper : ObservableObject - { - public ImportPathHelper(string path) - { - Path = path; - ShouldImport = PathExits; - } - - private bool _shouldImport; - public bool ShouldImport - { - get => _shouldImport; - set => SetProperty(ref _shouldImport, value); - } - - public string Path { get; set; } - - public bool PathExits => !System.IO.Path.IsPathFullyQualified(Path) || System.IO.Path.Exists(Path); - } - - public class ImportFolderTagLinkHelper : ImportPathHelper - { - public ImportFolderTagLinkHelper(string path, Tag t, IEnumerable existingTags) : base(path) - { - Tag = t; - _existingTags = existingTags; - } - private IEnumerable _existingTags; - public Tag Tag { get; } - public bool TagExists => _existingTags.Contains(Tag); - } - - public class ImportCodexHelper : ImportPathHelper - { - public ImportCodexHelper(Codex codex) : base(codex.Path) - { - Codex = codex; - } - public Codex Codex { get; } - - private RelayCommand _itemCheckedCommand; - public RelayCommand ItemCheckedCommand => _itemCheckedCommand ??= new((items) => - items.Cast() - .ToList() - .ForEach(helper => helper.ShouldImport = ShouldImport)); - } - #endregion - //Don't show on overview tab if new collection is chosen with illegal name public override bool ShowNextButton() => base.ShowNextButton() && !(CurrentStep == "Overview" && !MergeIntoCollection && !IsCollectionNameLegal); @@ -207,80 +72,42 @@ public override bool ShowFinishButton() => base.ShowFinishButton() && public override void Finish() { - TargetCollection = MergeIntoCollection ? - MainViewModel.CollectionVM.CurrentCollection : - MainViewModel.CollectionVM.CreateAndLoadCollection(CollectionName); - - //add selected Tags to tmp collection - if (AdvancedImport) + //add selected Tags + if (!AdvancedImport) { - CollectionToImport.RootTags = CheckableTreeNode.GetCheckedItems(TagsToImport).ToList(); - - //Remove the tags that didn't make it from codices - var RemovedTags = CollectionToImport.AllTags.Except(Utils.FlattenTree(CollectionToImport.RootTags)).ToList(); - foreach (Tag t in RemovedTags) + foreach (var selectableTag in ContentSelectorVM.SelectableTags) { - CollectionToImport.AllTags.Remove(t); - foreach (var codex in CollectionToImport.AllCodices) - { - codex.Tags.Remove(t); - } + selectableTag.IsChecked = ImportAllTags; } } - else if (!ImportAllTags) + ContentSelectorVM.ApplySelectedTags(); + + //Add codices + if (!AdvancedImport) { - foreach (var tag in CollectionToImport.AllTags) + foreach (var selectableCodex in ContentSelectorVM.SelectableCodices) { - foreach (var codex in CollectionToImport.AllCodices) - { - codex.Tags.Remove(tag); - } + selectableCodex.Selected = ImportAllCodices; } - CollectionToImport.RootTags.Clear(); - CollectionToImport.AllTags.Clear(); } + ContentSelectorVM.ApplySelectedCodices(); - //add selected Codices to tmp collection - if (RemovePersonalData) + //Add preferences + if (!AdvancedImport) { - CodicesToImport.ForEach(c => c.Codex.ClearPersonalData()); + ContentSelectorVM.SelectAutoImportFolders = ImportAllSettings; + ContentSelectorVM.SelectBanishedFiles = ImportAllSettings; + ContentSelectorVM.SelectFileTypePrefs = ImportAllSettings; + ContentSelectorVM.SelectFolderTagLinks = ImportAllSettings; } + ContentSelectorVM.ApplySelectedPreferences(); - CollectionToImport.AllCodices.Clear(); - if (AdvancedImport) - { - CollectionToImport.AllCodices.AddRange(CodicesToImport.Where(x => x.ShouldImport).Select(x => x.Codex)); - } - else if (ImportAllCodices) - { - CollectionToImport.AllCodices.AddRange(CodicesToImport.Select(x => x.Codex)); - } - - //Add selected Settings to tmp collection - CollectionToImport.Info.AutoImportDirectories.Clear(); - if (ImportAutoImportFolders) - { - CollectionToImport.Info.AutoImportDirectories = new(AutoImportFolders.Where(x => x.ShouldImport).Select(x => x.Path)); - } - CollectionToImport.Info.BanishedPaths.Clear(); - if (ImportBanishedFiles) - { - CollectionToImport.Info.BanishedPaths = new(BanishedPaths.Where(x => x.ShouldImport).Select(x => x.Path)); - } - if (!ImportFileTypePrefs) - { - //for file types, import all or nothing because checking whether to import a checkbox becomes ridiculous - CollectionToImport.Info.FiletypePreferences.Clear(); - } - CollectionToImport.Info.FolderTagPairs.Clear(); - if (ImportFolderTagLinks) - { - CollectionToImport.Info.FolderTagPairs = new(FolderTagLinks.Where(linkHelper => linkHelper.ShouldImport && linkHelper.TagExists) - .Select(linkHelper => new FolderTagPair(linkHelper.Path, linkHelper.Tag))); - } + //Save the changes to a permanent collection + var targetCollection = MergeIntoCollection ? + MainViewModel.CollectionVM.CurrentCollection : + MainViewModel.CollectionVM.CreateAndLoadCollection(CollectionName); - //If some tags are no longer present, they should be deleted from Codices - TargetCollection.MergeWith(CollectionToImport); + targetCollection.MergeWith(ContentSelectorVM.CuratedCollection); CloseAction.Invoke(); } @@ -292,18 +119,8 @@ public void UpdateSteps() Steps.Add("Overview"); if (AdvancedImport) { - if (HasTags) - { - Steps.Add("Tags"); - } - if (HasCodices) - { - Steps.Add("Items"); - } - if (HasSettings) - { - Steps.Add("Settings"); - } + ContentSelectorVM.UpdateSteps(); + Steps.AddRange(ContentSelectorVM.Steps); } RaisePropertyChanged(nameof(Steps)); } diff --git a/src/Windows/ImportCollectionWizard.xaml b/src/Windows/ImportCollectionWizard.xaml index 357b6685..d1dcf236 100644 --- a/src/Windows/ImportCollectionWizard.xaml +++ b/src/Windows/ImportCollectionWizard.xaml @@ -11,7 +11,7 @@ xmlns:tools="clr-namespace:COMPASS.Tools" d:DataContext="{d:DesignInstance Type=import:ImportCollectionViewModel}" mc:Ignorable="d" Background="{StaticResource WindowBackground}" - Title="{Binding CollectionToImport.DirectoryName, StringFormat=Importing {0:}}" + Title="{Binding CollectionName, StringFormat=Importing {0:}}" Height="650" Width="1200" Closed="Window_Closed"> @@ -113,14 +113,10 @@ - - - - + @@ -139,6 +135,11 @@ ConverterParameter=True}"/> + + + + Visibility="{Binding ContentSelectorVM.HasTags, Converter={StaticResource ToVisibilityConverter}}" Margin="5"/> + Visibility="{Binding ContentSelectorVM.HasCodices, Converter={StaticResource ToVisibilityConverter}}" Margin="5"/> + Visibility="{Binding ContentSelectorVM.HasSettings, Converter={StaticResource ToVisibilityConverter}}" Margin="5"/> @@ -179,12 +180,12 @@ - + + ToolTip="'Personal data' includes fields such as - Favorite - Physically owned - Rating - Last openened - Times opened - Date added"/> - - + + + + + + + + + + + + + + + + @@ -875,6 +898,7 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/ViewModels/CollectionViewModel.cs b/src/ViewModels/CollectionViewModel.cs index c5c2e6e4..89162868 100644 --- a/src/ViewModels/CollectionViewModel.cs +++ b/src/ViewModels/CollectionViewModel.cs @@ -227,9 +227,7 @@ public CodexCollection CreateCollection(string dirName) if (string.IsNullOrEmpty(dirName)) return null; CodexCollection newCollection = new(dirName); - Directory.CreateDirectory(newCollection.CoverArtPath); - Directory.CreateDirectory(newCollection.ThumbnailsPath); - Directory.CreateDirectory(newCollection.UserFilesPath); + newCollection.CreateDirectories(); AllCodexCollections.Add(newCollection); return newCollection; @@ -292,76 +290,13 @@ public void DeleteCollection(CodexCollection toDelete) //Export Collection private ActionCommand _exportCommand; - public ActionCommand ExportCommand => _exportCommand ??= new(async () => await Export()); - public async Task Export() + public ActionCommand ExportCommand => _exportCommand ??= new(Export); + public void Export() { - SaveFileDialog saveFileDialog = new() - { - Filter = $"COMPASS File (*{Constants.COMPASSFileExtension})|*{Constants.COMPASSFileExtension}", - FileName = CurrentCollection.DirectoryName, - DefaultExt = Constants.COMPASSFileExtension - }; - - if (saveFileDialog.ShowDialog() == true) - { - //make sure to save first - MainViewModel.CollectionVM.CurrentCollection.Save(); - - string targetPath = saveFileDialog.FileName; - using ZipFile zip = new(); - zip.AddDirectory(CurrentCollection.FullDataPath); - - //copy everything to temp because codex paths it will be modified - string tmpCollectionPath = Path.Combine(CodexCollection.CollectionsPath, $"__{CurrentCollection.DirectoryName}"); - Directory.CreateDirectory(tmpCollectionPath); - string filesPath = Path.Combine(tmpCollectionPath, "Files"); - CodexCollection tmpCollection = new($"__{CurrentCollection.DirectoryName}"); - - File.Copy(CurrentCollection.CodicesDataFilePath, tmpCollection.CodicesDataFilePath, true); - File.Copy(CurrentCollection.TagsDataFilePath, tmpCollection.TagsDataFilePath, true); //tags should also be copied otherwise Loaded codices will be tagless - tmpCollection.Load(hidden: true); - - //Change Codex Path to relative and add those files if the options is set - var itemsWithOfflineSource = tmpCollection.AllCodices.Where(codex => codex.HasOfflineSource()); - string commonFolder = Utils.GetCommonFolder(itemsWithOfflineSource.Select(codex => codex.Path).ToList()); - foreach (Codex codex in itemsWithOfflineSource) - { - string relativePath = codex.Path[commonFolder.Length..].TrimStart(Path.DirectorySeparatorChar); - if (IncludeFilesInExport && File.Exists(codex.Path)) - { - int index_start_filename = relativePath.Length - Path.GetFileName(codex.Path).Length; - zip.AddFile(codex.Path, Path.Combine("Files", relativePath[0..index_start_filename])); - } - //strip longest common path so relative paths stay, given that full paths will break anyway - codex.Path = relativePath; - } - - tmpCollection.SaveCodices(); - zip.UpdateFile(tmpCollection.CodicesDataFilePath, ""); - - //Progress reporting - var ProgressVM = ProgressViewModel.GetInstance(); - ProgressVM.Text = "Exporting Collection"; - ProgressVM.ShowCount = false; - ProgressVM.ResetCounter(); - zip.SaveProgress += (object _, SaveProgressEventArgs args) => - { - ProgressVM.TotalAmount = Math.Max(ProgressVM.TotalAmount, args.EntriesTotal); - if (args.EventType == ZipProgressEventType.Saving_AfterWriteEntry) - { - ProgressVM.IncrementCounter(); - } - }; - - //Export - await Task.Run(() => - { - zip.Save(targetPath); - Directory.Delete(tmpCollectionPath, true); - ProgressVM.ShowCount = false; - }); - Logger.Info($"Exported {CurrentCollection.DirectoryName} to {targetPath}"); - } + //open wizard + ExportCollectionViewModel exportCollectionVM = new(CurrentCollection); + ExportCollectionWizard wizard = new(exportCollectionVM); + wizard.Show(); } private ActionCommand _exportTagsCommand; diff --git a/src/ViewModels/ExportCollectionViewModel.cs b/src/ViewModels/ExportCollectionViewModel.cs new file mode 100644 index 00000000..c92ae44d --- /dev/null +++ b/src/ViewModels/ExportCollectionViewModel.cs @@ -0,0 +1,155 @@ +using COMPASS.Models; +using COMPASS.Tools; +using Ionic.Zip; +using Microsoft.Win32; +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace COMPASS.ViewModels +{ + public class ExportCollectionViewModel : WizardViewModel + { + public ExportCollectionViewModel(CodexCollection collectionToExport) + { + CollectionToExport = collectionToExport; + + ContentSelectorVM = new(collectionToExport); + ContentSelectorVM.CuratedCollection = new CodexCollection("__export__tmp"); + + UpdateSteps(); + } + + public CollectionContentSelectorViewModel ContentSelectorVM { get; set; } + + public CodexCollection CollectionToExport { get; set; } = null; + + //OVERVIEW STEP + + public bool ExportAllTags { get; set; } = true; + public bool ExportAllCodices { get; set; } = true; + public bool ExportAllSettings { get; set; } = true; + + private bool _advancedExport = false; + public bool AdvancedExport + { + get => _advancedExport; + set + { + SetProperty(ref _advancedExport, value); + UpdateSteps(); + } + } + + public bool IncludeFiles { get; set; } + + public async override void ApplyAll() + { + //if we do a quick import, set all the things in the contentSelector have the right value + if (!AdvancedExport) + { + //Set it on tags + foreach (var selectableTag in ContentSelectorVM.SelectableTags) + { + selectableTag.IsChecked = ExportAllTags; + } + + //Set it on codices + foreach (var selectableCodex in ContentSelectorVM.SelectableCodices) + { + selectableCodex.Selected = ExportAllCodices; + } + + //Set it on all the settings + ContentSelectorVM.SelectAutoImportFolders = ExportAllSettings; + ContentSelectorVM.SelectBanishedFiles = ExportAllSettings; + ContentSelectorVM.SelectFileTypePrefs = ExportAllSettings; + ContentSelectorVM.SelectFolderTagLinks = ExportAllSettings; + } + + //Apply the selection + ContentSelectorVM.ApplyAll(); + + CloseAction.Invoke(); + + await ExportToFile(); + + } + + public async Task ExportToFile() + { + SaveFileDialog saveFileDialog = new() + { + Filter = $"COMPASS File (*{Constants.COMPASSFileExtension})|*{Constants.COMPASSFileExtension}", + FileName = CollectionToExport.DirectoryName, + DefaultExt = Constants.COMPASSFileExtension + }; + + if (saveFileDialog.ShowDialog() == true) + { + //make sure to save first + ContentSelectorVM.CuratedCollection.CreateDirectories(); + ContentSelectorVM.CuratedCollection.Save(); + + string targetPath = saveFileDialog.FileName; + using ZipFile zip = new(); + zip.AddDirectory(ContentSelectorVM.CuratedCollection.FullDataPath); + + //Change Codex Path to relative and add those files if the options is set + var itemsWithOfflineSource = ContentSelectorVM.CuratedCollection.AllCodices.Where(codex => codex.HasOfflineSource()); + string commonFolder = Utils.GetCommonFolder(itemsWithOfflineSource.Select(codex => codex.Path).ToList()); + foreach (Codex codex in itemsWithOfflineSource) + { + string relativePath = codex.Path[commonFolder.Length..].TrimStart(Path.DirectorySeparatorChar); + if (IncludeFiles && File.Exists(codex.Path)) + { + int index_start_filename = relativePath.Length - Path.GetFileName(codex.Path).Length; + zip.AddFile(codex.Path, Path.Combine("Files", relativePath[0..index_start_filename])); + } + //strip longest common path so relative paths stay, given that full paths will break anyway + codex.Path = relativePath; + } + + ContentSelectorVM.CuratedCollection.SaveCodices(); + zip.UpdateFile(ContentSelectorVM.CuratedCollection.CodicesDataFilePath, ""); + + //Progress reporting + var ProgressVM = ProgressViewModel.GetInstance(); + ProgressVM.Text = "Exporting Collection"; + ProgressVM.ShowCount = false; + ProgressVM.ResetCounter(); + zip.SaveProgress += (object _, SaveProgressEventArgs args) => + { + ProgressVM.TotalAmount = Math.Max(ProgressVM.TotalAmount, args.EntriesTotal); + if (args.EventType == ZipProgressEventType.Saving_AfterWriteEntry) + { + ProgressVM.IncrementCounter(); + } + }; + + //Export + await Task.Run(() => + { + zip.Save(targetPath); + Directory.Delete(ContentSelectorVM.CuratedCollection.FullDataPath, true); + ProgressVM.ShowCount = false; + }); + Logger.Info($"Exported {CollectionToExport.DirectoryName} to {targetPath}"); + } + } + + public void UpdateSteps() + { + //Checks which steps need to be included in wizard + Steps.Clear(); + Steps.Add("Overview"); + if (AdvancedExport) + { + ContentSelectorVM.UpdateSteps(); + Steps.AddRange(ContentSelectorVM.Steps); + } + RaisePropertyChanged(nameof(Steps)); + } + } +} diff --git a/src/Windows/ExportCollectionWizard.xaml b/src/Windows/ExportCollectionWizard.xaml new file mode 100644 index 00000000..976bbe68 --- /dev/null +++ b/src/Windows/ExportCollectionWizard.xaml @@ -0,0 +1,372 @@ + + + + + + + + + + + + + + + Choose which + + to export. + + + + / + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +