Skip to content

Commit

Permalink
Use new columns
Browse files Browse the repository at this point in the history
  • Loading branch information
erri120 committed Oct 10, 2024
1 parent 05b1c4c commit e8f383b
Show file tree
Hide file tree
Showing 23 changed files with 733 additions and 478 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace NexusMods.App.UI.Controls;
/// Adapter class for working with <see cref="TreeDataGrid"/>.
/// </summary>
public abstract class TreeDataGridAdapter<TModel, TKey> : ReactiveR3Object
where TModel : TreeDataGridItemModel<TModel, TKey>
where TModel : class, ITreeDataGridItemModel<TModel, TKey>
where TKey : notnull
{
public Subject<(TModel model, bool isActivating)> ModelActivationSubject { get; } = new();
Expand Down
53 changes: 34 additions & 19 deletions src/NexusMods.App.UI/Controls/TreeDataGrid/TreeDataGridItemModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,51 @@
namespace NexusMods.App.UI.Controls;

[PublicAPI]
public interface ITreeDataGridItemModel : IReactiveR3Object;
public interface ITreeDataGridItemModel : IReactiveR3Object
{
ReactiveProperty<bool> IsSelected { get; }
}

/// <summary>
/// Base class for models of <see cref="Avalonia.Controls.TreeDataGrid"/> items.
/// </summary>
public class TreeDataGridItemModel : ReactiveR3Object, ITreeDataGridItemModel;
public class TreeDataGridItemModel : ReactiveR3Object, ITreeDataGridItemModel
{
public ReactiveProperty<bool> IsSelected { get; } = new(value: false);
}

public interface ITreeDataGridItemModel<out TModel, TKey> : ITreeDataGridItemModel
where TModel : class, ITreeDataGridItemModel<TModel, TKey>
where TKey : notnull
{
BindableReactiveProperty<bool> HasChildren { get; }

IEnumerable<TModel> Children { get; }

bool IsExpanded { get; [UsedImplicitly] set; }

public static HierarchicalExpanderColumn<TModel> CreateExpanderColumn(IColumn<TModel> innerColumn)
{
return new HierarchicalExpanderColumn<TModel>(
inner: innerColumn,
childSelector: static model => model.Children,
hasChildrenSelector: static model => model.HasChildren.Value,
isExpandedSelector: static model => model.IsExpanded
)
{
Tag = "expander",
};
}
}

/// <summary>
/// Generic variant of <see cref="TreeDataGridItemModel"/>.
/// </summary>
[PublicAPI]
public class TreeDataGridItemModel<TModel, TKey> : TreeDataGridItemModel
where TModel : TreeDataGridItemModel<TModel, TKey>
public class TreeDataGridItemModel<TModel, TKey> : TreeDataGridItemModel, ITreeDataGridItemModel<TModel, TKey>
where TModel : class, ITreeDataGridItemModel<TModel, TKey>
where TKey : notnull
{
public ReactiveProperty<bool> IsSelected { get; } = new(value: false);

public IObservable<bool> HasChildrenObservable { get; init; } = Observable.Return(false);
public BindableReactiveProperty<bool> HasChildren { get; } = new();

Expand Down Expand Up @@ -152,17 +180,4 @@ [MustDisposeResource] protected static IDisposable WhenModelActivated<TItemModel
{
return model.WhenActivated(block);
}

public static HierarchicalExpanderColumn<TModel> CreateExpanderColumn(IColumn<TModel> innerColumn)
{
return new HierarchicalExpanderColumn<TModel>(
inner: innerColumn,
childSelector: static model => model.Children,
hasChildrenSelector: static model => model.HasChildren.Value,
isExpandedSelector: static model => model.IsExpanded
)
{
Tag = "expander",
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public static void SetupTreeDataGridAdapter<TView, TViewModel, TItemModel, TKey>
Func<TViewModel, TreeDataGridAdapter<TItemModel, TKey>> getAdapter)
where TView : ReactiveUserControl<TViewModel>
where TViewModel : class, IViewModelInterface
where TItemModel : TreeDataGridItemModel<TItemModel, TKey>
where TItemModel : class, ITreeDataGridItemModel<TItemModel, TKey>
where TKey : notnull
{
treeDataGrid.ElementFactory = new CustomElementFactory();
Expand Down
30 changes: 30 additions & 0 deletions src/NexusMods.App.UI/Extensions/FormatExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Globalization;
using Humanizer;
using Humanizer.Bytes;
using NexusMods.Paths;
using R3;

namespace NexusMods.App.UI.Extensions;

public static class FormatExtensions
{
public static string FormatDate(this DateTimeOffset date, DateTimeOffset now)
{
if (date == DateTimeOffset.MinValue || date == DateTimeOffset.MaxValue || date == DateTimeOffset.UnixEpoch) return "-";
return date.Humanize(dateToCompareAgainst: now, culture: CultureInfo.CurrentUICulture);
}

public static BindableReactiveProperty<string> ToFormattedProperty(this Observable<DateTimeOffset> source)
{
return source
.Select(static date => date.FormatDate(now: TimeProvider.System.GetLocalNow()))
.ToBindableReactiveProperty(initialValue: "");
}

public static BindableReactiveProperty<string> ToFormattedProperty(this Observable<Size> source)
{
return source
.Select(static size => ByteSize.FromBytes(size.Value).Humanize())
.ToBindableReactiveProperty(initialValue: "");
}
}
23 changes: 20 additions & 3 deletions src/NexusMods.App.UI/Extensions/R3Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,40 @@ [MustDisposeResource] public static IDisposable WhenActivated<T>(
this T obj,
Action<T, CompositeDisposable> block)
where T : IReactiveR3Object
{
return WhenActivated(obj, state: block, static (obj, block, disposables) =>
{
block(obj, disposables);
});
}

/// <summary>
/// Provides an activation block for <see cref="ReactiveR3Object"/>.
/// </summary>
[MustDisposeResource]
public static IDisposable WhenActivated<T, TState>(
this T obj,
TState state,
Action<T, TState, CompositeDisposable> block)
where T : IReactiveR3Object
where TState : notnull
{
var d = Disposable.CreateBuilder();

var serialDisposable = new SerialDisposable();
serialDisposable.AddTo(ref d);

obj.Activation.DistinctUntilChanged().Subscribe((obj, serialDisposable, block), onNext: static (isActivated, state) =>
obj.Activation.DistinctUntilChanged().Subscribe(((obj, state), serialDisposable, block), onNext: static (isActivated, state) =>
{
var (model, serialDisposable, block) = state;
var (wrapper, serialDisposable, block) = state;
serialDisposable.Disposable = null;
if (isActivated)
{
var compositeDisposable = new CompositeDisposable();
serialDisposable.Disposable = compositeDisposable;
block(model, compositeDisposable);
block(wrapper.obj, wrapper.state, compositeDisposable);
}
}, onCompleted: static (_, state) =>
{
Expand Down
6 changes: 0 additions & 6 deletions src/NexusMods.App.UI/NexusMods.App.UI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -596,12 +596,6 @@
<Compile Update="Pages\LibraryPage\LibraryViewModel.cs">
<DependentUpon>ILibraryViewModel.cs</DependentUpon>
</Compile>
<Compile Update="Pages\LibraryPage\FakeParentLibraryItemModel.cs">
<DependentUpon>LibraryItemModel.cs</DependentUpon>
</Compile>
<Compile Update="Pages\LibraryPage\NexusModsModPageLibraryItemModel.cs">
<DependentUpon>LibraryItemModel.cs</DependentUpon>
</Compile>
<Compile Update="Pages\LoadoutPage\FakeParentLoadoutItemModel.cs">
<DependentUpon>LoadoutItemModel.cs</DependentUpon>
</Compile>
Expand Down
4 changes: 2 additions & 2 deletions src/NexusMods.App.UI/Pages/ILibraryDataProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ namespace NexusMods.App.UI.Pages;

public interface ILibraryDataProvider
{
IObservable<IChangeSet<LibraryItemModel, EntityId>> ObserveFlatLibraryItems(LibraryFilter libraryFilter);
IObservable<IChangeSet<ILibraryItemModel, EntityId>> ObserveFlatLibraryItems(LibraryFilter libraryFilter);

IObservable<IChangeSet<LibraryItemModel, EntityId>> ObserveNestedLibraryItems(LibraryFilter libraryFilter);
IObservable<IChangeSet<ILibraryItemModel, EntityId>> ObserveNestedLibraryItems(LibraryFilter libraryFilter);
}

public class LibraryFilter
Expand Down

This file was deleted.

76 changes: 75 additions & 1 deletion src/NexusMods.App.UI/Pages/LibraryPage/ILibraryItemModel.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,79 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using DynamicData;
using JetBrains.Annotations;
using NexusMods.Abstractions.Library.Models;
using NexusMods.Abstractions.Loadouts;
using NexusMods.Abstractions.MnemonicDB.Attributes.Extensions;
using NexusMods.App.UI.Controls;
using NexusMods.App.UI.Extensions;
using NexusMods.MnemonicDB.Abstractions;
using ObservableCollections;
using R3;

namespace NexusMods.App.UI.Pages.LibraryPage;

public interface ILibraryItemModel : ITreeDataGridItemModel;
public interface ILibraryItemModel : ITreeDataGridItemModel<ILibraryItemModel, EntityId>;

public interface IHasTicker
{
Observable<DateTimeOffset>? Ticker { get; set; }
}

public interface IHasLinkedLoadoutItems
{
IObservable<IChangeSet<LibraryLinkedLoadoutItem.ReadOnly, EntityId>> LinkedLoadoutItemsObservable { get; }
ObservableDictionary<EntityId, LibraryLinkedLoadoutItem.ReadOnly> LinkedLoadoutItems { get; }

[MustDisposeResource] static IDisposable SetupLinkedLoadoutItems<TModel>(TModel self, SerialDisposable serialDisposable)
where TModel : IHasLinkedLoadoutItems, ILibraryItemWithInstallAction, ILibraryItemWithInstalledDate
{
var disposable = self.LinkedLoadoutItems
.ObserveCountChanged(notifyCurrentCount: true)
.Subscribe(self, static (count, self) =>
{
var isInstalled = count > 0;
self.IsInstalled.Value = isInstalled;
self.InstallButtonText.Value = ILibraryItemWithInstallAction.GetButtonText(isInstalled);
self.InstalledDate.Value = isInstalled ? self.LinkedLoadoutItems.Select(static kv => kv.Value.GetCreatedAt()).Max() : DateTimeOffset.MinValue;
});

if (serialDisposable.Disposable is null)
{
serialDisposable.Disposable = self.LinkedLoadoutItemsObservable.OnUI().SubscribeWithErrorLogging(changes => self.LinkedLoadoutItems.ApplyChanges(changes));
}

return disposable;
}
}

public interface IIsParentLibraryItemModel : ILibraryItemModel
{
IReadOnlyList<LibraryItemId> LibraryItemIds { get; }
}

public interface IIsChildLibraryItemModel : ILibraryItemModel
{
LibraryItemId LibraryItemId { get; }
}

[SuppressMessage("ReSharper", "PossibleInterfaceMemberAmbiguity")]
public interface ILibraryItemWithDates : IHasTicker, ILibraryItemWithDownloadedDate, ILibraryItemWithInstalledDate
{
[MustDisposeResource]
static IDisposable SetupDates<TModel>(TModel self) where TModel : class, ILibraryItemWithDates
{
return self.WhenActivated(static (self, disposables) =>
{
Debug.Assert(self.Ticker is not null, "should've been set before activation");
self.Ticker.Subscribe(self, static (now, self) =>
{
ILibraryItemWithDownloadedDate.FormatDate(self, now: now);
ILibraryItemWithInstalledDate.FormatDate(self, now: now);
}).AddTo(disposables);
ILibraryItemWithDownloadedDate.FormatDate(self, now: TimeProvider.System.GetLocalNow());
ILibraryItemWithInstalledDate.FormatDate(self, now: TimeProvider.System.GetLocalNow());
});
}
}
Loading

0 comments on commit e8f383b

Please sign in to comment.