diff --git a/Knossos.NET/AppStyles.axaml b/Knossos.NET/AppStyles.axaml
index ce7ac926..aea38f22 100644
--- a/Knossos.NET/AppStyles.axaml
+++ b/Knossos.NET/AppStyles.axaml
@@ -80,6 +80,7 @@
M4.21157,12.7326 C3.9244,13.0312 3.93361,13.5059 4.23213,13.7931 C4.53064,14.0803 5.00543,14.0711 5.29259,13.7725 L13.2521,5.49831 L13.2521,24.2511 C13.2521,24.6653 13.5879,25.0011 14.0021,25.0011 C14.4163,25.0011 14.7521,24.6653 14.7521,24.2511 L14.7521,5.4993 L22.7106,13.7725 C22.9978,14.0711 23.4726,14.0803 23.7711,13.7931 C24.0696,13.5059 24.0788,13.0312 23.7916,12.7326 L14.7223,3.30466 C14.3289,2.89568 13.6743,2.89568 13.2809,3.30466 L4.21157,12.7326 Z
M10.5 8.25C10.5 7.2835 11.2835 6.5 12.25 6.5H24V15.25C24 17.3211 25.6789 19 27.75 19H37.5V39.75C37.5 40.7165 36.7165 41.5 35.75 41.5H24.2608C23.7353 42.4086 23.1029 43.2476 22.3809 44H35.75C38.0972 44 40 42.0972 40 39.75V18.4142C40 17.8175 39.7629 17.2452 39.341 16.8232L27.1768 4.65901C26.7548 4.23705 26.1825 4 25.5858 4H12.25C9.90279 4 8 5.90279 8 8.25V22.9963C8.79632 22.6642 9.63275 22.4091 10.5 22.2402V8.25ZM35.4822 16.5H27.75C27.0596 16.5 26.5 15.9404 26.5 15.25V7.51777L35.4822 16.5Z M24 35C24 41.0751 19.0751 46 13 46C6.92487 46 2 41.0751 2 35C2 28.9249 6.92487 24 13 24C19.0751 24 24 28.9249 24 35ZM11.7071 29.2929C11.3166 28.9024 10.6834 28.9024 10.2929 29.2929L5.29289 34.2929C4.90237 34.6834 4.90237 35.3166 5.29289 35.7071L10.2929 40.7071C10.6834 41.0976 11.3166 41.0976 11.7071 40.7071C12.0976 40.3166 12.0976 39.6834 11.7071 39.2929L8.41421 36H20C20.5523 36 21 35.5523 21 35C21 34.4477 20.5523 34 20 34H8.41421L11.7071 30.7071C12.0976 30.3166 12.0976 29.6834 11.7071 29.2929Z
M31.8839 8.36612C32.372 8.85427 32.372 9.64573 31.8839 10.1339L18.0178 24L31.8839 37.8661C32.372 38.3543 32.372 39.1457 31.8839 39.6339C31.3957 40.122 30.6043 40.122 30.1161 39.6339L15.3661 24.8839C14.878 24.3957 14.878 23.6043 15.3661 23.1161L30.1161 8.36612C30.6043 7.87796 31.3957 7.87796 31.8839 8.36612Z
+ M17.25,19 C17.6642136,19 18,19.3357864 18,19.75 C18,20.1642136 17.6642136,20.5 17.25,20.5 L10.75,20.5 C10.3357864,20.5 10,20.1642136 10,19.75 C10,19.3357864 10.3357864,19 10.75,19 L17.25,19 Z M21.25,13 C21.6642136,13 22,13.3357864 22,13.75 C22,14.1642136 21.6642136,14.5 21.25,14.5 L6.75,14.5 C6.33578644,14.5 6,14.1642136 6,13.75 C6,13.3357864 6.33578644,13 6.75,13 L21.25,13 Z M24.25,7 C24.6642136,7 25,7.33578644 25,7.75 C25,8.16421356 24.6642136,8.5 24.25,8.5 L3.75,8.5 C3.33578644,8.5 3,8.16421356 3,7.75 C3,7.33578644 3.33578644,7 3.75,7 L24.25,7 Z
@@ -317,4 +318,8 @@
+
+
diff --git a/Knossos.NET/Assets/general/sort-icon.png b/Knossos.NET/Assets/general/sort-icon.png
deleted file mode 100644
index fdaa9e58..00000000
Binary files a/Knossos.NET/Assets/general/sort-icon.png and /dev/null differ
diff --git a/Knossos.NET/Classes/Knossos.cs b/Knossos.NET/Classes/Knossos.cs
index d0a2f268..1cadfa90 100644
--- a/Knossos.NET/Classes/Knossos.cs
+++ b/Knossos.NET/Classes/Knossos.cs
@@ -702,12 +702,16 @@ public async static void LoadBasePath(bool isQuickLaunch = false)
{
if (globalSettings.basePath != null)
{
+ //Clear Mod Tags
+ ModTags.Clear();
+ MainWindowViewModel.Instance?.tagFilter.Clear();
+
await FolderSearchRecursive(globalSettings.basePath, isQuickLaunch).ConfigureAwait(false);
if (!isQuickLaunch)
{
//Sort/Re-sort installed mods
- MainWindowViewModel.Instance?.InstalledModsView?.ChangeSort(MainWindowViewModel.Instance?.sharedSortType!);
+ MainWindowViewModel.Instance?.InstalledModsView?.ChangeSort(globalSettings.sortType);
//Red border for mod with missing deps
Dispatcher.UIThread.Invoke(() =>
@@ -733,6 +737,13 @@ await Task.Run(async () => {
{
await QuickLaunch();
}
+
+ //generate mod tag buttons
+ Dispatcher.UIThread.Invoke(() =>
+ {
+ NebulaModListView.Instance?.GenerateFilterButtons();
+ ModListView.Instance?.GenerateFilterButtons();
+ });
}
}
diff --git a/Knossos.NET/Classes/ModTags.cs b/Knossos.NET/Classes/ModTags.cs
new file mode 100644
index 00000000..2a7ef542
--- /dev/null
+++ b/Knossos.NET/Classes/ModTags.cs
@@ -0,0 +1,267 @@
+using Knossos.NET.Models;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Knossos.NET.Classes
+{
+ public class ModTag
+ {
+ public ModTag(string modID, string? filter = null, string? tag = null)
+ {
+ ModID = modID;
+ if(filter != null)
+ Filters.Add(filter.ToLower());
+ if(tag != null)
+ Tags.Add(tag.ToLower());
+ }
+
+ public void AddFilter(string filter)
+ {
+ //special cases:
+ //do not add FS2_MOD filter if FS1_MOD exist
+ //Remove FS2_MOD if FS1_MOD mod is added
+ if (filter.ToLower() == "fs1_mod")
+ Filters.Remove("fs2_mod");
+ if (filter.ToLower() == "fs2_mod" && FilterExist("fs1_mod"))
+ return;
+ Filters.Add(filter);
+ }
+
+ ///
+ /// Check if a filter exist in this modid filterlist
+ /// Case insensitive
+ ///
+ ///
+ /// true/false
+ public bool FilterExist(string filter)
+ {
+ for (int i = 0; i < Filters.Count(); i++)
+ {
+ if(string.Compare(Filters[i], filter, true) == 0)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public List GetFilters()
+ {
+ return Filters;
+ }
+
+ public void AddTag(string tag)
+ {
+ Tags.Add(tag);
+ }
+
+ ///
+ /// Check if a tag exist in this modid taglist
+ /// Case insensitive
+ ///
+ ///
+ /// true/false
+ public bool TagExist(string tag)
+ {
+ for (int i = 0; i < Tags.Count(); i++)
+ {
+ if (string.Compare(Tags[i], tag, true) == 0)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public string ModID { get; private set; } = string.Empty;
+ private List Filters { get; set; } = new List();
+ private List Tags { get; set; } = new List();
+ }
+
+ ///
+ /// Handlers for the mod tag system
+ /// Each mod id can contain any number of tags
+ /// Tags are loaded from nebula and local locals at start
+ /// Then we can check here if a mod ID contains a tag
+ /// Note:
+ /// tags are case insensitive, but ModIDs arent!
+ ///
+ public static class ModTags
+ {
+ private static List modTags = new List();
+
+ ///
+ /// Use _ for spaces and remember these are checked with string.compare so no tags that can contain another
+ ///
+ public enum Filters
+ {
+ Total_Conversion,
+ Retail_FS2,
+ FS2_MOD,
+ FS1_MOD,
+ TC_MOD,
+ Utility,
+ Dependency,
+ VR_MOD,
+ Asset_Pack,
+ Demo,
+ Multiplayer,
+ Test
+ }
+
+ ///
+ /// Clear the all loaded tags and filters
+ ///
+ public static void Clear()
+ {
+ modTags.Clear();
+ }
+
+ ///
+ /// Get a list of all filters loaded, without the mod id
+ ///
+ /// List
+ public static List GetListAllFilters()
+ {
+ List list = new List();
+ for (int i = 0; i < modTags.Count(); i++)
+ {
+ var filters = modTags[i].GetFilters();
+ for (int j = 0; j < filters.Count(); j++)
+ {
+ if (!list.Contains(filters[j]))
+ list.Add(filters[j]);
+ }
+ }
+ return list;
+ }
+
+ ///
+ /// Checks if a mod id contains a filter
+ ///
+ ///
+ ///
+ /// true/false
+ public static bool IsFilterPresentInModID(string modid, string filter)
+ {
+ for (int i = 0; i < modTags.Count(); i++)
+ {
+ if (modTags[i].ModID == modid)
+ {
+ return modTags[i].FilterExist(filter);
+ }
+ }
+ return false;
+ }
+
+ ///
+ /// Checks if a mod id contains a tag
+ ///
+ ///
+ ///
+ /// true/false
+ public static bool IsTagPresentInModID(string modid, string tag)
+ {
+ for (int i = 0; i < modTags.Count(); i++)
+ {
+ if (modTags[i].ModID == modid)
+ {
+ return modTags[i].TagExist(tag);
+ }
+ }
+ return false;
+ }
+
+ ///
+ /// Adds a mod filter to the list
+ /// If the id and filter dosent exist already
+ ///
+ ///
+ ///
+ public static void AddModFilter(string id, string filter)
+ {
+ bool found = false;
+ for (int i = 0; i < modTags.Count(); i++)
+ {
+ if (modTags[i].ModID == id)
+ {
+ if(!modTags[i].FilterExist(filter))
+ {
+ modTags[i].AddFilter(filter);
+ found = true;
+ break;
+ }
+ }
+ }
+ if(!found)
+ {
+ modTags.Add(new ModTag(id, filter));
+ }
+ }
+
+ ///
+ /// Adds a mod tag to the list
+ /// If the id and tag dosent exist already
+ ///
+ ///
+ ///
+ public static void AddModTag(string id, string tag)
+ {
+ bool found = false;
+ for (int i = 0; i < modTags.Count(); i++)
+ {
+ if (modTags[i].ModID == id)
+ {
+ if (!modTags[i].TagExist(tag))
+ {
+ modTags[i].AddTag(tag);
+ found = true;
+ break;
+ }
+ }
+ }
+ if (!found)
+ {
+ modTags.Add(new ModTag(id, null, tag));
+ }
+ }
+
+ ///
+ /// Generate runtime mod tags for this mod
+ /// Tags will be determined based on avalible mod information
+ ///
+ ///
+ public static void AddModFiltersRuntime(Mod? mod)
+ {
+ if(mod == null) return;
+ if(mod.id.ToLower() == "fs2")
+ {
+ AddModFilter(mod.id, Filters.Retail_FS2.ToString());
+ return;
+ }
+ if(mod.parent == null)
+ {
+ AddModFilter(mod.id,Filters.Total_Conversion.ToString());
+ }
+ else
+ {
+ if (mod.parent.ToLower() == "fs2")
+ {
+ if(mod.GetDependency("fsport", true) != null)
+ {
+ AddModFilter(mod.id, Filters.FS1_MOD.ToString()); // only going to work for installed mods
+ }
+ else
+ {
+ AddModFilter(mod.id, Filters.FS2_MOD.ToString());
+ }
+ }
+ else
+ {
+ AddModFilter(mod.id, Filters.TC_MOD.ToString());
+ }
+ }
+ }
+
+ }
+}
diff --git a/Knossos.NET/Knossos.NET.csproj b/Knossos.NET/Knossos.NET.csproj
index 48c302fb..b11acfef 100644
--- a/Knossos.NET/Knossos.NET.csproj
+++ b/Knossos.NET/Knossos.NET.csproj
@@ -83,6 +83,7 @@
+
diff --git a/Knossos.NET/Models/GlobalSettings.cs b/Knossos.NET/Models/GlobalSettings.cs
index 29dc644c..5f87c691 100644
--- a/Knossos.NET/Models/GlobalSettings.cs
+++ b/Knossos.NET/Models/GlobalSettings.cs
@@ -165,9 +165,9 @@ public bool hideBuildNightly
}
[JsonIgnore]
- private MainWindowViewModel.SortType _sortType = MainWindowViewModel.SortType.name;
+ private ModSortType _sortType = ModSortType.name;
[JsonPropertyName("last_sort_type"), JsonConverter(typeof(JsonStringEnumConverter))]
- public MainWindowViewModel.SortType sortType
+ public ModSortType sortType
{
get { return _sortType; }
set { if (_sortType != value) { _sortType = value; pendingChangesOnAppClose = true; } }
@@ -915,7 +915,7 @@ public void Save(bool writeIni = true)
{
// Quickly update the sort type which is managed elsewhere
if (MainWindowViewModel.Instance != null){
- sortType = MainWindowViewModel.Instance.sharedSortType;
+ sortType = Knossos.globalSettings.sortType;
}
var options = new JsonSerializerOptions
diff --git a/Knossos.NET/Models/Mod.cs b/Knossos.NET/Models/Mod.cs
index d8f6da6c..e03cac6a 100644
--- a/Knossos.NET/Models/Mod.cs
+++ b/Knossos.NET/Models/Mod.cs
@@ -9,6 +9,8 @@
using System.Threading.Tasks;
using IniParser;
using System.Linq;
+using Knossos.NET.ViewModels;
+using System.Globalization;
namespace Knossos.NET.Models
{
@@ -844,6 +846,71 @@ public static int CompareTitles(string title1, string title2)
return String.Compare(KnUtils.RemoveArticles(title1), KnUtils.RemoveArticles(title2), StringComparison.CurrentCultureIgnoreCase);
}
+ ///
+ /// Does mod sorting based on the global Knossos.globalSettings.sortType variable
+ /// Returns:
+ /// Less than zero if modA precedes modB
+ /// Zero if they are equal
+ /// Higher than zero if modA follows mobB
+ ///
+ ///
+ ///
+ ///
+ public static int SortMods(Mod? modA, Mod? modB)
+ {
+ if (modA == null && modB == null) return 0;
+ if (modA == null && modB != null) return 1;
+ if (modA != null && modB == null) return -1;
+ try
+ {
+ switch (Knossos.globalSettings.sortType)
+ {
+ case ModSortType.name:
+ return Mod.CompareTitles(modA!.title, modB!.title);
+ case ModSortType.release:
+ if (modA!.firstRelease == modB!.firstRelease)
+ return 0;
+ if (modA.firstRelease != null && modB.firstRelease != null)
+ {
+ if (DateTime.Parse(modA.firstRelease, CultureInfo.InvariantCulture) < DateTime.Parse(modB.firstRelease, CultureInfo.InvariantCulture))
+ return 1;
+ else
+ return -1;
+ }
+ else
+ {
+ if (modA.firstRelease == null)
+ return -1;
+ else
+ return 1;
+ }
+ case ModSortType.update:
+ if (modA!.lastUpdate == modB!.lastUpdate)
+ return 0;
+ if (modA.lastUpdate != null && modB.lastUpdate != null)
+ {
+ if (DateTime.Parse(modA.lastUpdate, CultureInfo.InvariantCulture) < DateTime.Parse(modB.lastUpdate, CultureInfo.InvariantCulture))
+ return 1;
+ else
+ return -1;
+ }
+ else
+ {
+ if (modA.lastUpdate == null)
+ return 1;
+ else
+ return -1;
+ }
+ default: return 0;
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.Add(Log.LogSeverity.Warning, "NebulaModCardViewModel.CompareMods()", ex.Message);
+ return 0;
+ }
+ }
+
///
/// Compares two mods and determines if the metadata is different
/// Full data must be loaded on both mods for this to work properly
diff --git a/Knossos.NET/Models/Nebula.cs b/Knossos.NET/Models/Nebula.cs
index a57dd525..c538be63 100644
--- a/Knossos.NET/Models/Nebula.cs
+++ b/Knossos.NET/Models/Nebula.cs
@@ -35,6 +35,7 @@ private struct NebulaSettings
public string? pass { get; set; }
public bool logged { get; set; }
public List NewerModsVersions { get; set; }
+ public NebulaModTagJson[] modtags { get; set; }
public NebulaSettings()
@@ -44,6 +45,7 @@ public NebulaSettings()
pass = null;
logged = false;
NewerModsVersions = new List();
+ modtags = new NebulaModTagJson[0];
}
}
@@ -59,6 +61,15 @@ private struct NebulaCache
public string modString { get; set; }
}
+ public struct NebulaModTagJson
+ {
+ public string modid { get; set; }
+ public string[] filters { get; set; }
+ public string[] tags { get; set; }
+ }
+
+ private readonly static string ModTagsURL = "https://raw.githubusercontent.com/KnossosNET/KNet-General-Resources-Repo/main/modtags.json";
+
//https://cf.fsnebula.org/storage/repo.json
//https://dl.fsnebula.org/storage/repo.json
//https://fsnebula.org/storage/repo.json"
@@ -174,6 +185,27 @@ public static async Task Trinity()
var updates = await InitialRepoLoad().ConfigureAwait(false);
if (updates != null && updates.Any())
{
+ /**************************************************************************************************************************/
+ //If we have a repo update lets update modtags as well
+ //This part of the code should be replaced once modtags are integrated into nebula
+ if (!CustomLauncher.IsCustomMode)
+ {
+ try
+ {
+ HttpResponseMessage response = await KnUtils.GetHttpClient().GetAsync(ModTagsURL).ConfigureAwait(false);
+ var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
+ var tagsRepo = JsonSerializer.Deserialize(json)!;
+ if (tagsRepo != null)
+ {
+ settings.modtags = tagsRepo;
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.Add(Log.LogSeverity.Error, "Nebula.Trinity()", ex);
+ }
+ }
+ /**************************************************************************************************************************/
SaveSettings();
if (displayUpdates)
{
@@ -192,6 +224,32 @@ public static async Task Trinity()
{
await LoadPrivateMods(cancellationToken).ConfigureAwait(false);
}
+ int filters = 0, tags = 0;
+ //Load mod tags
+ if (!CustomLauncher.IsCustomMode)
+ {
+ foreach (var modtag in settings.modtags)
+ {
+ if (modtag.filters != null)
+ {
+ foreach (var f in modtag.filters)
+ {
+ ModTags.AddModFilter(modtag.modid, f);
+ filters++;
+ }
+ }
+ if (modtag.tags != null)
+ {
+ foreach (var t in modtag.tags)
+ {
+ ModTags.AddModTag(modtag.modid, t);
+ tags++;
+ }
+ }
+ }
+ }
+
+ Log.Add(Log.LogSeverity.Information, "Nebula.Trinity()", "ModTags has loaded: " + filters + " filters (" + ModTags.GetListAllFilters().Count() + " unique) and " + tags + " mod tags.");
repoLoaded = true;
}
catch (TaskCanceledException)
diff --git a/Knossos.NET/ViewModels/ModListViewModel.cs b/Knossos.NET/ViewModels/ModListViewModel.cs
index b59d7504..0b89db47 100644
--- a/Knossos.NET/ViewModels/ModListViewModel.cs
+++ b/Knossos.NET/ViewModels/ModListViewModel.cs
@@ -1,11 +1,10 @@
using System;
-using System.Collections.ObjectModel;
-using System.Globalization;
-using System.IO;
using System.Linq;
-using Avalonia.Threading;
+using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
+using Knossos.NET.Classes;
using Knossos.NET.Models;
+using ObservableCollections;
namespace Knossos.NET.ViewModels
{
@@ -14,11 +13,8 @@ namespace Knossos.NET.ViewModels
///
public partial class ModListViewModel : ViewModelBase
{
- ///
- /// Is this tab in the middle of sorting or filtering mod tiles
- ///
[ObservableProperty]
- internal bool sorting = false;
+ internal bool sorting = true;
///
/// For the knossos Loading animation
@@ -29,47 +25,117 @@ public partial class ModListViewModel : ViewModelBase
///
/// Current Sort Mode
///
- internal MainWindowViewModel.SortType sortType = MainWindowViewModel.SortType.unsorted;
+ internal ModSortType localSort = ModSortType.name;
internal string search = string.Empty;
internal string Search
{
get { return search; }
- set
+ set
{
- if (value != Search){
+ if (value != Search)
+ {
this.SetProperty(ref search, value);
- if (value.Trim() != string.Empty)
- {
- foreach(var mod in Mods)
- {
- if( mod.Name != null && mod.Name.ToLower().Contains(value.ToLower()))
- {
- mod.Visible = true;
- }
- else
- {
- mod.Visible = false;
- }
- }
- }
- else
- {
- Mods.ForEach(m => m.Visible = true);
- }
+ ApplyFilters();
}
}
}
- [ObservableProperty]
- internal ObservableCollection mods = new ObservableCollection();
+ //The actual collection were the mods are
+ private ObservableList Mods = new ObservableList();
+ //A hook for the UI, do not access directly
+ internal NotifyCollectionChangedSynchronizedViewList CardsView { get; set; }
public ModListViewModel()
{
LoadingAnimation = new LoadingIconViewModel();
+ CardsView = Mods.ToNotifyCollectionChangedSlim(SynchronizationContextCollectionEventDispatcher.Current);
+ }
+ public void ApplyTagFilter(int tagIndex)
+ {
+ if (MainWindowViewModel.Instance != null)
+ {
+ var tags = ModTags.GetListAllFilters();
+ if (tags.Count() > tagIndex)
+ {
+ MainWindowViewModel.Instance.tagFilter.Add(tags[tagIndex]);
+ }
+ ApplyFilters();
+ }
+ }
+
+ public void RemoveTagFilter(int tagIndex)
+ {
+ if (MainWindowViewModel.Instance != null)
+ {
+ var tags = ModTags.GetListAllFilters();
+ if (tags.Count() > tagIndex)
+ {
+ MainWindowViewModel.Instance.tagFilter.Remove(tags[tagIndex]);
+ }
+ ApplyFilters();
+ }
+ }
+
+ private void ApplyFilters()
+ {
+ Parallel.ForEach(Mods, new ParallelOptions { MaxDegreeOfParallelism = 4 }, card =>
+ {
+ bool visibility = true;
+ //By search
+ if (Search.Trim() != string.Empty)
+ {
+ if (card.Name == null || !card.Name.Contains(Search, StringComparison.CurrentCultureIgnoreCase))
+ {
+ //if it dosent passes title search check tags
+ visibility = ModTags.IsTagPresentInModID(card.ID!, search);
+ }
+ }
+ //Filters
+ if (visibility && MainWindowViewModel.Instance != null && MainWindowViewModel.Instance.tagFilter.Any())
+ {
+ visibility = false;
+ foreach (var filter in MainWindowViewModel.Instance.tagFilter)
+ {
+ if (card.ID != null && ModTags.IsFilterPresentInModID(card.ID, filter))
+ {
+ visibility = true;
+ break;
+ }
+ }
+ }
+ card.Visible = visibility;
+ });
+ }
+
+ ///
+ ///
+ ///
+ public void OpenTab()
+ {
+ if (MainWindowViewModel.Instance != null)
+ {
+ if (Search != MainWindowViewModel.Instance.sharedSearch)
+ {
+ Search = MainWindowViewModel.Instance.sharedSearch;
+ }
+ else
+ {
+ ApplyFilters();
+ }
+ }
}
+ public void CloseTab()
+ {
+ if (MainWindowViewModel.Instance != null)
+ MainWindowViewModel.Instance.sharedSearch = Search;
+ Parallel.ForEach(Mods, new ParallelOptions { MaxDegreeOfParallelism = 4 }, card =>
+ {
+ card.Visible = false;
+ });
+ }
///
/// Clears all mods in view
@@ -102,13 +168,14 @@ public void AddMod(Mod modJson)
{
if (Mods[i].ActiveVersion != null)
{
- if(CompareMods(Mods[i].ActiveVersion!,modJson) > 0)
+ if(Mod.SortMods(Mods[i].ActiveVersion!,modJson) > 0)
{
break;
}
}
}
Mods.Insert(i, new ModCardViewModel(modJson));
+ ModTags.AddModFiltersRuntime(modJson);
}
else
{
@@ -122,107 +189,25 @@ public void AddMod(Mod modJson)
///
internal void ChangeSort(object sort)
{
- try
+ Sorting = true;
+ LoadingAnimation.Animate = 1;
+ var newSort = ModSortType.unsorted;
+ if (sort is ModSortType)
{
- MainWindowViewModel.SortType newSort;
-
- if (sort is MainWindowViewModel.SortType){
- newSort = (MainWindowViewModel.SortType)sort;
- } else {
- newSort = (MainWindowViewModel.SortType)Enum.Parse(typeof(MainWindowViewModel.SortType), (string)sort);
- }
-
- if (newSort != sortType)
- {
- if (MainWindowViewModel.Instance != null && newSort != MainWindowViewModel.SortType.unsorted && MainWindowViewModel.Instance.sharedSortType != newSort )
- {
- MainWindowViewModel.Instance.sharedSortType = newSort;
- }
- LoadingAnimation.Animate = 1;
- Sorting = true;
- Dispatcher.UIThread.Invoke( () =>
- {
- var tempList = Mods.ToList();
- // Only sort and update to the new sort type if we have mods to sort!
- if (tempList.Any()){
- sortType = newSort;
- tempList.Sort(CompareMods);
- for (int i = 0; i < tempList.Count; i++)
- {
- Mods.Move(Mods.IndexOf(tempList[i]), i);
- }
- }
- GC.Collect();
- Sorting = false;
- LoadingAnimation.Animate = 0;
- },DispatcherPriority.Background);
-
- }
- }catch(Exception ex)
- {
- Sorting = false;
- LoadingAnimation.Animate = 0;
- Log.Add(Log.LogSeverity.Error, "ModListViewModel.ChangeSort()", ex);
+ newSort = (ModSortType)sort;
}
- }
-
- private int CompareMods(ModCardViewModel x, ModCardViewModel y)
- {
- if (x.ActiveVersion != null && y.ActiveVersion != null)
- return CompareMods(x.ActiveVersion, y.ActiveVersion);
else
- return 0;
- }
-
- private int CompareMods(Mod modA,Mod modB)
- {
- try
{
- switch (sortType)
- {
- case MainWindowViewModel.SortType.name:
- return Mod.CompareTitles(modA.title, modB.title);
- case MainWindowViewModel.SortType.release:
- if (modA.firstRelease == modB.firstRelease)
- return 0;
- if (modA.firstRelease != null && modB.firstRelease != null)
- {
- if (DateTime.Parse(modA.firstRelease, CultureInfo.InvariantCulture) < DateTime.Parse(modB.firstRelease, CultureInfo.InvariantCulture))
- return 1;
- else
- return -1;
- }
- else
- {
- if (modA.firstRelease == null)
- return -1;
- else
- return 1;
- }
- case MainWindowViewModel.SortType.update:
- if (modA.lastUpdate == modB.lastUpdate)
- return 0;
- if (modA.lastUpdate != null && modB.lastUpdate != null)
- {
- if (DateTime.Parse(modA.lastUpdate, CultureInfo.InvariantCulture) < DateTime.Parse(modB.lastUpdate, CultureInfo.InvariantCulture))
- return 1;
- else
- return -1;
- }
- else
- {
- if (modA.lastUpdate == null)
- return 1;
- else
- return -1;
- }
- default: return 0;
- }
- }catch(Exception ex)
- {
- Log.Add(Log.LogSeverity.Warning, "ModListViewModel.CompareMods()",ex.Message);
- return 0;
+ newSort = (ModSortType)Enum.Parse(typeof(ModSortType), (string)sort);
+ }
+ if (newSort != localSort)
+ {
+ localSort = newSort;
+ Knossos.globalSettings.sortType = newSort;
+ Mods.Sort(); //It will use ModCardViewModel.CompareTo()
}
+ LoadingAnimation.Animate = 0;
+ Sorting = false;
}
///
diff --git a/Knossos.NET/ViewModels/NebulaModListViewModel.cs b/Knossos.NET/ViewModels/NebulaModListViewModel.cs
index 74a82c0b..10484e7f 100644
--- a/Knossos.NET/ViewModels/NebulaModListViewModel.cs
+++ b/Knossos.NET/ViewModels/NebulaModListViewModel.cs
@@ -1,13 +1,12 @@
using System;
using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
+using Knossos.NET.Classes;
using Knossos.NET.Models;
+using ObservableCollections;
namespace Knossos.NET.ViewModels
{
@@ -16,12 +15,6 @@ namespace Knossos.NET.ViewModels
///
public partial class NebulaModListViewModel : ViewModelBase
{
-
- ///
- /// Current Sort Mode
- ///
- internal MainWindowViewModel.SortType sortType = MainWindowViewModel.SortType.unsorted;
-
///
/// The user has opened this tab in this session?
///
@@ -32,7 +25,7 @@ internal bool isLoading{
get { return _isLoading; }
set {
_isLoading = value;
- ShowTiles = !sorting && !isLoading;
+ ShowTiles = !isLoading;
if (ShowTiles)
{
LoadingAnimation.Animate = 0;
@@ -44,29 +37,11 @@ internal bool isLoading{
}
}
- private bool _sorting = true;
- internal bool sorting {
- get { return _sorting; }
- set {
- _sorting = value;
- ShowTiles = !sorting && !isLoading;
-
- if (ShowTiles)
- {
- LoadingAnimation.Animate = 0;
- }
- else
- {
- LoadingAnimation.Animate = 1;
- }
- }
- }
-
///
- /// For the UI to detmerine whether to show mod tiles. It needs to check more than one property, so this gets updated when sorting or isLoading do.
+ /// For the UI to detmerine whether to show mod tiles. It needs to check more than one property
///
[ObservableProperty]
- internal bool showTiles = false;
+ public bool showTiles = false;
[ObservableProperty]
internal LoadingIconViewModel loadingAnimation = new LoadingIconViewModel();
@@ -80,91 +55,132 @@ internal string Search
get { return search; }
set
{
- sorting = true;
- LoadingAnimation.Animate = 1;
-
- if (value != Search){
+ if (value != Search)
+ {
this.SetProperty(ref search, value);
- if (value.Trim() != string.Empty)
- {
- foreach(var mod in Mods)
- {
- if( mod.Name != null && mod.Name.ToLower().Contains(value.ToLower()))
- {
- mod.Visible = true;
- }
- else
- {
- mod.Visible = false;
- }
- }
- }
- else
- {
- Mods.ForEach(m => m.Visible = true);
- }
+ ApplyFilters();
}
- sorting = false;
- LoadingAnimation.Animate = 1;
}
}
- [ObservableProperty]
- internal ObservableCollection mods = new ObservableCollection();
+ private ModSortType localSort = ModSortType.name;
+ //The actual collection were the mods are
+ private ObservableList Mods = new ObservableList();
+ //A hook for the UI, do not access directly
+ internal NotifyCollectionChangedSynchronizedViewList CardsView { get; set; }
public NebulaModListViewModel()
{
+ LoadingAnimation.Animate = 1;
+ CardsView = Mods.ToNotifyCollectionChangedSlim(SynchronizationContextCollectionEventDispatcher.Current);
}
- ///
- /// Open the tab and slowly display modcards to avoid ui lock
- ///
- public async void OpenTab(string newSearch, MainWindowViewModel.SortType newSortType)
+ public void ApplyTagFilter(int tagIndex)
{
- Search = newSearch;
- if (isLoading)
+ if (MainWindowViewModel.Instance != null)
{
- IsTabOpen = true;
- return;
+ var tags = ModTags.GetListAllFilters();
+ if(tags.Count() > tagIndex)
+ {
+ MainWindowViewModel.Instance.tagFilter.Add(tags[tagIndex]);
+ }
+ ApplyFilters();
}
+ }
- if (!IsTabOpen)
+ public void RemoveTagFilter(int tagIndex)
+ {
+ if (MainWindowViewModel.Instance != null)
{
- IsTabOpen = true;
- // This should remain true until we get to Change Sort. It is guaranteed to be finished then
- sorting = true;
+ var tags = ModTags.GetListAllFilters();
+ if (tags.Count() > tagIndex)
+ {
+ MainWindowViewModel.Instance.tagFilter.Remove(tags[tagIndex]);
+ }
+ ApplyFilters();
+ }
+ }
- try
+ private void ApplyFilters()
+ {
+ Parallel.ForEach(Mods, new ParallelOptions { MaxDegreeOfParallelism = 4 }, card =>
+ {
+ bool visibility = true;
+ //By search
+ if (Search.Trim() != string.Empty)
{
- await Task.Delay(200).ConfigureAwait(false);
- List? modsInView = null;
- await Dispatcher.UIThread.InvokeAsync(() =>
+ if (card.Name == null || !card.Name.Contains(Search, StringComparison.CurrentCultureIgnoreCase))
{
- isLoading = false;
- modsInView = Mods.ToList();
- });
- if (modsInView != null)
+ //if it dosent passes title search check tags
+ visibility = ModTags.IsTagPresentInModID(card.ID!, search);
+ }
+ }
+ //Filters
+ if (visibility && MainWindowViewModel.Instance != null && MainWindowViewModel.Instance.tagFilter.Any())
+ {
+ visibility = false;
+ foreach (var filter in MainWindowViewModel.Instance.tagFilter)
{
- foreach (var m in modsInView)
+ if(card.ID != null && ModTags.IsFilterPresentInModID(card.ID, filter))
{
- await Dispatcher.UIThread.InvokeAsync(() =>
- {
- if (Search.Trim() == string.Empty || m.Name != null && m.Name.ToLower().Contains(Search.ToLower()))
- {
- m.Visible = true;
- }
- });
- await Task.Delay(1).ConfigureAwait(false);
+ visibility = true;
+ break;
}
}
}
- catch (Exception ex)
+ card.Visible = visibility;
+ });
+ }
+
+ ///
+ /// Open the tab code
+ ///
+ public void OpenTab()
+ {
+ IsTabOpen = true;
+ if (!isLoading)
+ {
+ Task.Run(() =>
{
- Log.Add(Log.LogSeverity.Error, "NebulaModListViewModel.OpenTab", ex);
- }
+ ShowTiles = false;
+ LoadingAnimation.Animate = 1;
+ if (MainWindowViewModel.Instance != null)
+ {
+ if (Search != MainWindowViewModel.Instance.sharedSearch)
+ {
+ Search = MainWindowViewModel.Instance.sharedSearch;
+ }
+ else
+ {
+ ApplyFilters();
+ }
+ }
+
+ ChangeSort(Knossos.globalSettings.sortType);
+ Parallel.ForEach(Mods, new ParallelOptions { MaxDegreeOfParallelism = 4 }, async card =>
+ {
+ await card.LoadImage();
+ });
+ LoadingAnimation.Animate = 0;
+ ShowTiles = true;
+ });
+ }
+ }
+ ///
+ /// Close the tab code
+ ///
+ public void CloseTab()
+ {
+ ShowTiles = false;
+ if (MainWindowViewModel.Instance != null)
+ {
+ MainWindowViewModel.Instance.sharedSearch = Search;
}
- ChangeSort(newSortType);
+ Parallel.ForEach(Mods, new ParallelOptions { MaxDegreeOfParallelism = 4 }, card =>
+ {
+ card.Visible = false;
+ });
}
///
@@ -192,26 +208,12 @@ public void AddMod(Mod modJson)
var modCard = Mods.FirstOrDefault(m => m.ID == modJson.id);
if (modCard == null)
{
- int i;
- for (i = 0; i < Mods.Count; i++)
- {
- if (Mods[i].modJson != null)
- {
- if(CompareMods(Mods[i].modJson!,modJson) > 0)
- {
- break;
- }
- }
- }
var card = new NebulaModCardViewModel(modJson);
- if (!isLoading)
- {
- if (Search.Trim() == string.Empty || card.Name != null && card.Name.ToLower().Contains(Search.ToLower()))
- {
- card.Visible = true;
- }
- }
- Mods.Insert(i, card);
+ ModTags.AddModFiltersRuntime(modJson);
+ Mods.Add(card);
+ Mods.Sort();
+ if(IsTabOpen)
+ _ = card.LoadImage();
}
else
{
@@ -220,48 +222,29 @@ public void AddMod(Mod modJson)
}
///
- /// Adds a new list of mods into the, in a more efficient way that loading one by one
- /// It replaces all mods, all old mods are deleted, intended only for the first big load of mods
+ /// Adds a new list of mods into the view
///
///
- public async void AddMods(List modList)
+ public void AddMods(List modList)
{
- isLoading = true;
- await Task.Delay(20).ConfigureAwait(false);
- var newModCardList = new ObservableCollection();
- foreach (Mod? mod in modList)
- {
- var modCard = newModCardList.FirstOrDefault(m => m.ID == mod.id);
- if (modCard == null)
+ Task.Factory.StartNew(() => {
+ Parallel.ForEach(modList, new ParallelOptions { MaxDegreeOfParallelism = 4 }, mod =>
{
- int i;
- for (i = 0; i < newModCardList.Count; i++)
+ Mods.Add(new NebulaModCardViewModel(mod));
+ });
+ Mods.Sort();
+ if(IsTabOpen)
+ {
+ Parallel.ForEach(Mods, new ParallelOptions { MaxDegreeOfParallelism = 4 }, async card =>
{
- if (newModCardList[i].modJson != null)
- {
- if (CompareMods(newModCardList[i].modJson!, mod) > 0)
- {
- break;
- }
- }
- }
- var card = new NebulaModCardViewModel(mod);
- newModCardList.Insert(i, card);
+ await card.LoadImage();
+ });
}
- else
+ foreach(var mod in modList)
{
- //Update? Should NOT be needed for Nebula mods
+ ModTags.AddModFiltersRuntime(mod);
}
- }
- await Dispatcher.UIThread.InvokeAsync(() =>
- {
- Mods = newModCardList;
isLoading = false;
- if (IsTabOpen)
- {
- IsTabOpen = false;
- OpenTab(Search, sortType);
- }
});
}
@@ -271,107 +254,20 @@ await Dispatcher.UIThread.InvokeAsync(() =>
///
internal void ChangeSort(object sort)
{
- try
- {
- MainWindowViewModel.SortType newSort;
-
- if (sort is MainWindowViewModel.SortType){
- newSort = (MainWindowViewModel.SortType)sort;
- } else {
- newSort = (MainWindowViewModel.SortType)Enum.Parse(typeof(MainWindowViewModel.SortType), (string)sort);
- }
-
- if (newSort != sortType)
- {
- if (MainWindowViewModel.Instance != null && newSort != MainWindowViewModel.SortType.unsorted && MainWindowViewModel.Instance.sharedSortType != newSort)
- {
- MainWindowViewModel.Instance.sharedSortType = newSort;
- }
- if (sortType != MainWindowViewModel.SortType.unsorted)
- {
- sorting = true;
- }
-
- Dispatcher.UIThread.Invoke( () =>
- {
- sortType = newSort;
- var tempList = Mods.ToList();
- tempList.Sort(CompareMods);
- isLoading = true;
- for (int i = 0; i < tempList.Count; i++)
- {
- Mods.Move(Mods.IndexOf(tempList[i]), i);
- }
- isLoading = false;
- GC.Collect();
- },DispatcherPriority.Background);
- }
- }catch(Exception ex)
+ var newSort = ModSortType.unsorted;
+ if (sort is ModSortType)
{
- Log.Add(Log.LogSeverity.Error, "ModListViewModel.ChangeSort()", ex);
+ newSort = (ModSortType)sort;
}
-
- // There is no reason to keep this on, whether in success or fail, and some of the functions that call this
- // set sorting to true.
- sorting = false;
- }
-
- private int CompareMods(NebulaModCardViewModel x, NebulaModCardViewModel y)
- {
- if (x.modJson != null && y.modJson != null)
- return CompareMods(x.modJson, y.modJson);
else
- return 0;
- }
-
- private int CompareMods(Mod modA,Mod modB)
- {
- try
{
- switch (sortType)
- {
- case MainWindowViewModel.SortType.name:
- return Mod.CompareTitles(modA.title, modB.title);
- case MainWindowViewModel.SortType.release:
- if (modA.firstRelease == modB.firstRelease)
- return 0;
- if (modA.firstRelease != null && modB.firstRelease != null)
- {
- if (DateTime.Parse(modA.firstRelease, CultureInfo.InvariantCulture) < DateTime.Parse(modB.firstRelease, CultureInfo.InvariantCulture))
- return 1;
- else
- return -1;
- }
- else
- {
- if (modA.firstRelease == null)
- return -1;
- else
- return 1;
- }
- case MainWindowViewModel.SortType.update:
- if (modA.lastUpdate == modB.lastUpdate)
- return 0;
- if (modA.lastUpdate != null && modB.lastUpdate != null)
- {
- if (DateTime.Parse(modA.lastUpdate, CultureInfo.InvariantCulture) < DateTime.Parse(modB.lastUpdate, CultureInfo.InvariantCulture))
- return 1;
- else
- return -1;
- }
- else
- {
- if (modA.lastUpdate == null)
- return 1;
- else
- return -1;
- }
- default: return 0;
- }
- }catch(Exception ex)
- {
- Log.Add(Log.LogSeverity.Warning, "NebulaModListViewModel.CompareMods()",ex.Message);
- return 0;
+ newSort = (ModSortType)Enum.Parse(typeof(ModSortType), (string)sort);
+ }
+ if (newSort != localSort)
+ {
+ localSort = newSort;
+ Knossos.globalSettings.sortType = newSort;
+ Mods.Sort(); //It will use NebulaModCardViewModel.CompareTo()
}
}
diff --git a/Knossos.NET/ViewModels/Templates/ModCardViewModel.cs b/Knossos.NET/ViewModels/Templates/ModCardViewModel.cs
index 06e56752..825692b3 100644
--- a/Knossos.NET/ViewModels/Templates/ModCardViewModel.cs
+++ b/Knossos.NET/ViewModels/Templates/ModCardViewModel.cs
@@ -15,7 +15,7 @@
namespace Knossos.NET.ViewModels
{
- public partial class ModCardViewModel : ViewModelBase
+ public partial class ModCardViewModel : ViewModelBase, IComparable
{
/* General Mod variables */
private ModDetailsView? detailsView = null;
@@ -43,8 +43,33 @@ public Mod? ActiveVersion
internal string? modVersion;
[ObservableProperty]
internal Bitmap? image;
- [ObservableProperty]
- internal bool visible = true;
+ private bool visible = true;
+ ///
+ /// External property to enable/disable mod card visibility
+ ///
+ public bool Visible
+ {
+ get
+ {
+ return visible;
+ }
+ set
+ {
+ if (visible != value)
+ {
+ visible = value;
+ CardVisible = value;
+ if (CardVisible)
+ {
+ _ = LazyReLoadTileImageAsync();
+ }
+ else
+ {
+ Image = MainWindowViewModel.Instance?.placeholderTileImage;
+ }
+ }
+ }
+ }
[ObservableProperty]
internal bool updateAvailable = false;
[ObservableProperty]
@@ -53,7 +78,9 @@ public Mod? ActiveVersion
internal bool isLocalMod = false;
[ObservableProperty]
internal bool isCustomConfig = false;
-
+ [ObservableProperty]
+ internal bool cardVisible = true;
+ private Bitmap? tileModBitmap;
/* Should only be used by the editor preview */
public ModCardViewModel()
@@ -92,7 +119,17 @@ public ModCardViewModel(Mod modJson)
}
LoadImage();
}
-
+
+ ///
+ /// Reloads the tile image to the view at a random time
+ /// after the mod card itseft becomes visible
+ ///
+ private async Task LazyReLoadTileImageAsync()
+ {
+ await Task.Delay(new Random().Next(200, 700));
+ Image = tileModBitmap != null ? tileModBitmap : MainWindowViewModel.Instance?.placeholderTileImage;
+ }
+
public void AddModVersion(Mod modJson)
{
modJson.ClearUnusedData();
@@ -330,8 +367,7 @@ internal async void ButtonCommandSettings()
private void LoadImage()
{
- Image?.Dispose();
- Image = new Bitmap(AssetLoader.Open(new Uri("avares://Knossos.NET/Assets/general/NebulaDefault.png")));
+ Image = MainWindowViewModel.Instance?.placeholderTileImage;
try
{
@@ -340,7 +376,7 @@ private void LoadImage()
{
if (!tile.ToLower().Contains("http"))
{
- Image = new Bitmap(modVersions[activeVersionIndex].fullPath + Path.DirectorySeparatorChar + tile);
+ tileModBitmap = new Bitmap(modVersions[activeVersionIndex].fullPath + Path.DirectorySeparatorChar + tile);
}
else
{
@@ -349,10 +385,14 @@ private void LoadImage()
using (var fs = await KnUtils.GetRemoteResourceStream(tile))
{
if(fs != null)
- Image = new Bitmap(fs);
+ tileModBitmap = new Bitmap(fs);
}
});
}
+ if (tileModBitmap != null)
+ {
+ Image = tileModBitmap;
+ }
}
}
catch (Exception ex)
@@ -360,5 +400,12 @@ private void LoadImage()
Log.Add(Log.LogSeverity.Warning, "ModCardViewModel.LoadImage", ex);
}
}
+
+ public int CompareTo(ModCardViewModel? other)
+ {
+ if (other == null)
+ return -1;
+ return Mod.SortMods(ActiveVersion, other.ActiveVersion);
+ }
}
}
diff --git a/Knossos.NET/ViewModels/Templates/NebulaModCardViewModel.cs b/Knossos.NET/ViewModels/Templates/NebulaModCardViewModel.cs
index 3dee01d9..400a9983 100644
--- a/Knossos.NET/ViewModels/Templates/NebulaModCardViewModel.cs
+++ b/Knossos.NET/ViewModels/Templates/NebulaModCardViewModel.cs
@@ -5,14 +5,12 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Knossos.NET.Views;
using System.IO;
-using System.Net.Http;
using System.Threading.Tasks;
using System.Threading;
-using Avalonia.Threading;
namespace Knossos.NET.ViewModels
{
- public partial class NebulaModCardViewModel : ViewModelBase
+ public partial class NebulaModCardViewModel : ViewModelBase, IComparable
{
private CancellationTokenSource? cancellationTokenSource = null;
public Mod? modJson { get; set; }
@@ -23,30 +21,40 @@ public partial class NebulaModCardViewModel : ViewModelBase
internal string? ModVersion { get { return modJson != null ? modJson.version : null; } }
[ObservableProperty]
internal Bitmap? tileImage;
- internal bool visible = false;
- internal bool Visible
+
+ private Bitmap? tileModBitmap;
+
+ private bool visible = true;
+ ///
+ /// External property to enable/disable mod card visibility
+ ///
+ public bool Visible
{
- get
- {
+ get {
return visible;
}
- set
- {
- if(visible != value)
+ set {
+ if (visible != value)
{
- SetProperty(ref visible, value);
- if(value && TileImage == null && modJson != null)
+ visible = value;
+ CardVisible = value;
+ if(CardVisible)
+ {
+ _ = LazyReLoadTileImageAsync();
+ }
+ else
{
- Dispatcher.UIThread.Invoke(() => {
- LoadImage(modJson.fullPath, modJson.tile);
- });
+ TileImage = MainWindowViewModel.Instance?.placeholderTileImage;
}
}
}
}
+
[ObservableProperty]
internal bool isInstalling = false;
+ [ObservableProperty]
+ internal bool cardVisible = true;
/* Should only be used by the editor preview */
public NebulaModCardViewModel()
@@ -63,10 +71,31 @@ public NebulaModCardViewModel(Mod modJson)
Log.Add(Log.LogSeverity.Information, "NebulaModCardViewModel(Constructor)", "Creating mod card for " + modJson);
modJson.ClearUnusedData();
this.modJson = modJson;
- //Moved to load when visible only
+ //Moved to load by external call only
//LoadImage(modJson.fullPath, modJson.tile);
}
+ ///
+ /// Calls to load the tile image
+ ///
+ public async Task LoadImage()
+ {
+ if (TileImage == null && modJson != null)
+ {
+ await LoadImage(modJson.fullPath, modJson.tile);
+ }
+ }
+
+ ///
+ /// Reloads the tile image to the view at a random time
+ /// after the mod card itseft becomes visible
+ ///
+ private async Task LazyReLoadTileImageAsync()
+ {
+ await Task.Delay(new Random().Next(200,700));
+ TileImage = tileModBitmap != null? tileModBitmap : MainWindowViewModel.Instance?.placeholderTileImage;
+ }
+
/* Button Commands */
internal void ButtonCommand(object command)
{
@@ -128,10 +157,9 @@ internal async void ButtonCommandDetails()
}
}
- private void LoadImage(string modFullPath, string? tileString)
+ private async Task LoadImage(string modFullPath, string? tileString)
{
- TileImage?.Dispose();
- TileImage = new Bitmap(AssetLoader.Open(new Uri("avares://Knossos.NET/Assets/general/NebulaDefault.png")));
+ TileImage = MainWindowViewModel.Instance?.placeholderTileImage;
try
{
@@ -139,21 +167,19 @@ private void LoadImage(string modFullPath, string? tileString)
{
if (!tileString.ToLower().Contains("http"))
{
- TileImage = new Bitmap(modFullPath + Path.DirectorySeparatorChar + tileString);
+ tileModBitmap = new Bitmap(modFullPath + Path.DirectorySeparatorChar + tileString);
}
else
{
- Task.Run(async () =>
+ using (var fs = await KnUtils.GetRemoteResourceStream(tileString).ConfigureAwait(false))
{
- using (var fs = await KnUtils.GetRemoteResourceStream(tileString).ConfigureAwait(false))
- {
- Dispatcher.UIThread.Invoke(() =>
- {
- if (fs != null)
- TileImage = new Bitmap(fs);
- });
- }
- }).ConfigureAwait(false);
+ if (fs != null)
+ tileModBitmap = new Bitmap(fs);
+ }
+ }
+ if (tileModBitmap != null)
+ {
+ TileImage = tileModBitmap;
}
}
}
@@ -162,5 +188,12 @@ private void LoadImage(string modFullPath, string? tileString)
Log.Add(Log.LogSeverity.Warning, "NebulaModCardViewModel.LoadImage", ex);
}
}
+
+ public int CompareTo(NebulaModCardViewModel? other)
+ {
+ if (other == null)
+ return -1;
+ return Mod.SortMods(modJson, other.modJson);
+ }
}
}
diff --git a/Knossos.NET/ViewModels/Windows/MainWindowViewModel.cs b/Knossos.NET/ViewModels/Windows/MainWindowViewModel.cs
index 7781a0de..100ed8f9 100644
--- a/Knossos.NET/ViewModels/Windows/MainWindowViewModel.cs
+++ b/Knossos.NET/ViewModels/Windows/MainWindowViewModel.cs
@@ -9,11 +9,21 @@
using System.Collections.ObjectModel;
using Avalonia.Threading;
using System.Threading;
+using Avalonia.Media.Imaging;
+using Avalonia.Platform;
namespace Knossos.NET.ViewModels
{
public record MainViewMenuItem(ViewModelBase vm, string? iconRoute, string label, string tooltip);
+ public enum ModSortType
+ {
+ name,
+ release,
+ update,
+ unsorted
+ }
+
///
/// Main Windows View Mode
/// Everything starts here
@@ -70,35 +80,14 @@ public partial class MainWindowViewModel : ViewModelBase
[ObservableProperty]
internal int buttomListRow = 1;
-
- internal string sharedSearch = string.Empty;
-
+ public string sharedSearch = string.Empty;
public string LatestNightly = string.Empty;
public string LatestStable = string.Empty;
-
- public enum SortType
- {
- name,
- release,
- update,
- unsorted
- }
-
- private SortType _sortType = SortType.name; //do not use directly
- internal SortType sharedSortType
- {
- get { return _sortType; }
- set
- {
- if (_sortType != value)
- {
- //change sort and update globalsettings value
- //to be saved at app close
- this.SetProperty(ref _sortType, value);
- Knossos.globalSettings.sortType = value;
- }
- }
- }
+ public List tagFilter { get; private set; } = new List();
+ ///
+ /// Placeholder tile image for mod cards
+ ///
+ public Bitmap? placeholderTileImage;
public MainWindowViewModel()
{
@@ -120,6 +109,7 @@ public MainWindowViewModel()
}
if (!CustomLauncher.IsCustomMode)
{
+ placeholderTileImage = new Bitmap(AssetLoader.Open(new Uri("avares://Knossos.NET/Assets/general/NebulaDefault.png")));
InstalledModsView = new ModListViewModel();
NebulaModsView = new NebulaModListViewModel();
FsoBuildsView = new FsoBuildsViewModel();
@@ -273,11 +263,11 @@ partial void OnSelectedMenuItemChanged(MainViewMenuItem? value)
// Things to do on tab exit
if (InstalledModsView != null && CurrentViewModel == InstalledModsView) //Exiting the Play tab.
{
- sharedSearch = InstalledModsView.Search;
+ InstalledModsView.CloseTab();
}
if (NebulaModsView != null && CurrentViewModel == NebulaModsView) //Exiting the Nebula tab.
{
- sharedSearch = NebulaModsView.Search;
+ NebulaModsView.CloseTab();
}
if(GlobalSettingsView != null && CurrentViewModel == GlobalSettingsView) //Exiting the settings view
{
@@ -295,12 +285,11 @@ partial void OnSelectedMenuItemChanged(MainViewMenuItem? value)
//Run code when entering a new view
if (CurrentViewModel == InstalledModsView) //Play Tab
{
- InstalledModsView.Search = sharedSearch;
- InstalledModsView.ChangeSort(sharedSortType);
+ InstalledModsView.OpenTab();
}
if (CurrentViewModel == NebulaModsView) //Nebula Mods
{
- NebulaModsView.OpenTab(sharedSearch, sharedSortType);
+ NebulaModsView.OpenTab();
}
if (CurrentViewModel == DeveloperModView) //Dev Tab
{
@@ -488,10 +477,8 @@ internal void ApplySettings()
{
Dispatcher.UIThread.Invoke(() => {
IsMenuOpen = Knossos.globalSettings.mainMenuOpen;
- sharedSortType = Knossos.globalSettings.sortType;
- InstalledModsView?.ChangeSort(sharedSortType);
- if(NebulaModsView != null)
- NebulaModsView.sortType = sharedSortType;
+ InstalledModsView?.ChangeSort(Knossos.globalSettings.sortType);
+ NebulaModsView?.ChangeSort(Knossos.globalSettings.sortType);
});
}
diff --git a/Knossos.NET/Views/ModListView.axaml b/Knossos.NET/Views/ModListView.axaml
index fd2da1fd..67a2a342 100644
--- a/Knossos.NET/Views/ModListView.axaml
+++ b/Knossos.NET/Views/ModListView.axaml
@@ -15,15 +15,34 @@
-
-
-
-
+
+
+
-
+
-
+
diff --git a/Knossos.NET/Views/ModListView.axaml.cs b/Knossos.NET/Views/ModListView.axaml.cs
index 828b2f53..df51e215 100644
--- a/Knossos.NET/Views/ModListView.axaml.cs
+++ b/Knossos.NET/Views/ModListView.axaml.cs
@@ -1,12 +1,183 @@
+using Avalonia;
using Avalonia.Controls;
+using Knossos.NET.Classes;
+using Knossos.NET.ViewModels;
+using System;
+using System.Globalization;
+using System.Linq;
+using System.Threading.Tasks;
namespace Knossos.NET.Views
{
public partial class ModListView : UserControl
{
+ public static ModListView? Instance;
+ private bool filterButtonsGenerated = false;
+ private StackPanel? sortPanel;
+ private WrapPanel? filterPanel;
+
public ModListView()
{
InitializeComponent();
+ Instance = this;
+ AttachButtonUpdate();
+ }
+
+ private void AttachButtonUpdate()
+ {
+ try
+ {
+ sortPanel = this.FindControl("SortPanel");
+ filterPanel = this.FindControl("FilterPanel");
+ var filterFlyout = this.FindControl("FilterFlyout");
+ if (filterFlyout != null)
+ {
+ filterFlyout.Click += (_, __) =>
+ {
+ if (!filterButtonsGenerated)
+ GenerateFilterButtons();
+ ApplySortButtonsStyle();
+ ApplyFilterButtonsStyle();
+ };
+ }
+
+ if (sortPanel != null)
+ {
+ foreach (var item in sortPanel.Children)
+ {
+ if (item is Button button)
+ {
+ button.Click += async (_, __) =>
+ {
+ //Change colors when clicked, wait until after the active sort was saved
+ await Task.Delay(100);
+ ApplySortButtonsStyle();
+ };
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.Add(Log.LogSeverity.Error, "ModListView.AttachButtonUpdate()", ex);
+ }
+ }
+
+ private void ApplyFilterButtonsStyle()
+ {
+ try
+ {
+ if (filterPanel != null && MainWindowViewModel.Instance != null)
+ {
+ if (MainWindowViewModel.Instance.tagFilter.Any())
+ {
+ var tags = ModTags.GetListAllFilters();
+ foreach (var item in filterPanel.Children)
+ {
+ if (item is Button button && button.Tag is int tagIndex)
+ {
+ if (tags.Count() > tagIndex && MainWindowViewModel.Instance.tagFilter.Contains(tags[tagIndex]))
+ {
+ button.Classes.Add("Secondary");
+ }
+ else
+ {
+ button.Classes.Remove("Secondary");
+ }
+ }
+ }
+ }
+ else
+ {
+ foreach (var item in filterPanel.Children)
+ {
+ if (item is Button button)
+ button.Classes.Remove("Secondary");
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.Add(Log.LogSeverity.Error, "ModListView.ApplyFilterButtonsStyle()", ex);
+ }
+ }
+
+ private void ApplySortButtonsStyle()
+ {
+ try
+ {
+ if (sortPanel != null)
+ {
+ foreach (var item in sortPanel.Children)
+ {
+ if (item is Button button)
+ {
+ if (button.CommandParameter != null && (string)button.CommandParameter == Knossos.globalSettings.sortType.ToString())
+ {
+ button.Classes.Add("Secondary");
+ }
+ else
+ {
+ button.Classes.Remove("Secondary");
+ }
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.Add(Log.LogSeverity.Error, "ModListView.ApplySortButtonsStyle()", ex);
+ }
+ }
+
+ public void GenerateFilterButtons()
+ {
+ try
+ {
+ filterButtonsGenerated = true;
+ var filterPanel = this.FindControl("FilterPanel");
+ if (filterPanel != null)
+ {
+ filterPanel.Children.Clear();
+ var tags = ModTags.GetListAllFilters();
+ if (tags != null && tags.Any())
+ {
+ foreach (var tag in tags.Select((x, i) => new { Value = x, Index = i }))
+ {
+ TextInfo myTI = new CultureInfo("en-US", false).TextInfo;
+ var displayName = myTI.ToTitleCase(tag.Value.Replace("_", " "));
+ var button = new Button { Content = displayName, Tag = tag.Index, Width = 150, Margin = new Thickness(2) };
+ button.Click += (_, __) =>
+ {
+ //This code runs when the button is clicked
+ if (button.Tag is int tagIndex && this.DataContext is ModListViewModel dt)
+ {
+ if (!button.Classes.Contains("Secondary"))
+ {
+ dt.ApplyTagFilter(tagIndex);
+ button.Classes.Add("Secondary");
+ }
+ else
+ {
+ dt.RemoveTagFilter(tagIndex);
+ button.Classes.Remove("Secondary");
+ }
+ }
+ };
+ filterPanel.Children.Add(button);
+ }
+ }
+ }
+ else
+ {
+ Log.Add(Log.LogSeverity.Error, "ModListView.GenerateFilterButtons()", "Unable to find FilterPanel to generate filter buttons");
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.Add(Log.LogSeverity.Error, "ModListView.GenerateFilterButtons()", ex);
+ }
}
}
}
diff --git a/Knossos.NET/Views/NebulaModListView.axaml b/Knossos.NET/Views/NebulaModListView.axaml
index 0bd95e46..9aa866ab 100644
--- a/Knossos.NET/Views/NebulaModListView.axaml
+++ b/Knossos.NET/Views/NebulaModListView.axaml
@@ -15,15 +15,34 @@
-
-
+
+
-
-
- Sort by Name
- Sort by Update date
- Sort by Release date
-
+
+
+
+
+
+ Sort by
+
+
+ Name
+ Update date
+ Release date
+
+
+
+
+
+ Filters
+
+
+
+
+
+
+
+
@@ -37,14 +56,14 @@
-
-
-
+
+
+
-
+
-
+
diff --git a/Knossos.NET/Views/NebulaModListView.axaml.cs b/Knossos.NET/Views/NebulaModListView.axaml.cs
index 6561b451..6a67ecaf 100644
--- a/Knossos.NET/Views/NebulaModListView.axaml.cs
+++ b/Knossos.NET/Views/NebulaModListView.axaml.cs
@@ -1,12 +1,181 @@
+using Avalonia;
using Avalonia.Controls;
+using Knossos.NET.Classes;
+using Knossos.NET.ViewModels;
+using System;
+using System.Globalization;
+using System.Linq;
+using System.Threading.Tasks;
namespace Knossos.NET.Views
{
public partial class NebulaModListView : UserControl
{
+ public static NebulaModListView? Instance;
+ private bool filterButtonsGenerated = false;
+ private StackPanel? sortPanel;
+ private WrapPanel? filterPanel;
+
public NebulaModListView()
{
InitializeComponent();
+ Instance = this;
+ AttachButtonUpdate();
+ }
+
+ private void AttachButtonUpdate()
+ {
+ try
+ {
+ sortPanel = this.FindControl("SortPanel");
+ filterPanel = this.FindControl("FilterPanel");
+ var filterFlyout = this.FindControl("FilterFlyout");
+ if (filterFlyout != null)
+ {
+ filterFlyout.Click += (_, __) =>
+ {
+ if (!filterButtonsGenerated)
+ GenerateFilterButtons();
+ ApplySortButtonsStyle();
+ ApplyFilterButtonsStyle();
+ };
+ }
+
+ if (sortPanel != null)
+ {
+ foreach (var item in sortPanel.Children)
+ {
+ if (item is Button button)
+ {
+ button.Click += async (_, __) =>
+ {
+ //Change colors when clicked, wait until after the active sort was saved
+ await Task.Delay(100);
+ ApplySortButtonsStyle();
+ };
+ }
+ }
+ }
+ }catch(Exception ex)
+ {
+ Log.Add(Log.LogSeverity.Error, "NebulaModListView.AttachButtonUpdate()", ex);
+ }
+ }
+
+ private void ApplyFilterButtonsStyle()
+ {
+ try
+ {
+ if (filterPanel != null && MainWindowViewModel.Instance != null)
+ {
+ if (MainWindowViewModel.Instance.tagFilter.Any())
+ {
+ var tags = ModTags.GetListAllFilters();
+ foreach (var item in filterPanel.Children)
+ {
+ if (item is Button button && button.Tag is int tagIndex)
+ {
+ if (tags.Count() > tagIndex && MainWindowViewModel.Instance.tagFilter.Contains(tags[tagIndex]))
+ {
+ button.Classes.Add("Secondary");
+ }
+ else
+ {
+ button.Classes.Remove("Secondary");
+ }
+ }
+ }
+ }
+ else
+ {
+ foreach (var item in filterPanel.Children)
+ {
+ if (item is Button button)
+ button.Classes.Remove("Secondary");
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.Add(Log.LogSeverity.Error, "NebulaModListView.ApplyFilterButtonsStyle()", ex);
+ }
+ }
+
+ private void ApplySortButtonsStyle()
+ {
+ try
+ {
+ if (sortPanel != null)
+ {
+ foreach (var item in sortPanel.Children)
+ {
+ if (item is Button button)
+ {
+ if (button.CommandParameter != null && (string)button.CommandParameter == Knossos.globalSettings.sortType.ToString())
+ {
+ button.Classes.Add("Secondary");
+ }
+ else
+ {
+ button.Classes.Remove("Secondary");
+ }
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.Add(Log.LogSeverity.Error, "NebulaModListView.ApplySortButtonsStyle()", ex);
+ }
+ }
+
+ public void GenerateFilterButtons()
+ {
+ try
+ {
+ filterButtonsGenerated = true;
+ if (filterPanel != null)
+ {
+ filterPanel.Children.Clear();
+ var tags = ModTags.GetListAllFilters();
+ if (tags != null && tags.Any())
+ {
+ foreach (var tag in tags.Select((x, i) => new { Value = x, Index = i }))
+ {
+ TextInfo myTI = new CultureInfo("en-US", false).TextInfo;
+ var displayName = myTI.ToTitleCase(tag.Value.Replace("_", " "));
+ var button = new Button { Content = displayName, Tag = tag.Index, Width = 150, Margin = new Thickness(2) };
+ button.Click += (_, __) =>
+ {
+ //This code runs when the button is clicked
+ if (button.Tag is int tagIndex && this.DataContext is NebulaModListViewModel dt)
+ {
+ if (!button.Classes.Contains("Secondary"))
+ {
+ dt.ApplyTagFilter(tagIndex);
+ button.Classes.Add("Secondary");
+ }
+ else
+ {
+ dt.RemoveTagFilter(tagIndex);
+ button.Classes.Remove("Secondary");
+ }
+ }
+ };
+ filterPanel.Children.Add(button);
+ }
+ }
+ }
+ else
+ {
+ Log.Add(Log.LogSeverity.Error, "NebulaModListView.GenerateFilterButtons()", "Unable to find FilterPanel to generate filter buttons");
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.Add(Log.LogSeverity.Error, "NebulaModListView.GenerateFilterButtons()", ex);
+ }
}
}
}
diff --git a/Knossos.NET/Views/Templates/ModCardView.axaml b/Knossos.NET/Views/Templates/ModCardView.axaml
index d294030e..e57d35c9 100644
--- a/Knossos.NET/Views/Templates/ModCardView.axaml
+++ b/Knossos.NET/Views/Templates/ModCardView.axaml
@@ -1,4 +1,4 @@
-