Skip to content

Commit

Permalink
collection-cards (#2059)
Browse files Browse the repository at this point in the history
* Oof, I suck at UI

* Bit of implementing the actual VM

* Start on the visual layout for the collection cards

* Add the styles to the style index

* Add a collections panel and it integrates with the rest of the app to show known collections

* Load images as part of the collection cards

* Few more code comments

* Document the ICollectionCardViewModel.cs file

* Bit of code cleanup

* clarify the comments on `TotalSize`

* Bit more code cleanup
  • Loading branch information
halgari authored Sep 19, 2024
1 parent f8e3201 commit 05fa47a
Show file tree
Hide file tree
Showing 32 changed files with 994 additions and 116 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using NexusMods.MnemonicDB.Abstractions;
using NexusMods.MnemonicDB.Abstractions.Attributes;
using NexusMods.MnemonicDB.Abstractions.ElementComparers;

namespace NexusMods.Abstractions.NexusModsLibrary.Attributes;

public class FloatAttribute(string ns, string name) : ScalarAttribute<float, float>(ValueTags.Float32, ns, name)
{
protected override float ToLowLevel(float value)
{
return value;
}

protected override float FromLowLevel(float value, ValueTags tags, RegistryId registryId)
{
return value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.Buffers;
using NexusMods.MnemonicDB.Abstractions;
using NexusMods.MnemonicDB.Abstractions.Attributes;
using NexusMods.MnemonicDB.Abstractions.ElementComparers;

namespace NexusMods.Abstractions.NexusModsLibrary.Attributes;

/// <summary>
/// A hashed blob attribute for <see cref="Memory{T}"/>.
/// </summary>
public class MemoryAttribute(string ns, string name) : HashedBlobAttribute<Memory<byte>>(ns, name)
{
/// <inheritdoc />
protected override Memory<byte> FromLowLevel(ReadOnlySpan<byte> value, ValueTags tags, RegistryId registryId)
{
return new Memory<byte>(value.ToArray());
}

/// <inheritdoc />
protected override void WriteValue<TWriter>(Memory<byte> value, TWriter writer)
{
writer.Write(value.Span);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using NexusMods.Abstractions.MnemonicDB.Attributes;
using NexusMods.MnemonicDB.Abstractions.Attributes;
using NexusMods.MnemonicDB.Abstractions.Models;

namespace NexusMods.Abstractions.NexusModsLibrary;

/// <summary>
/// A tag for a collection.
/// </summary>
public partial class CollectionTag : IModelDefinition
{
private const string Namespace = "NexusMods.Abstractions.NexusModsLibrary.CollectionTag";

/// <summary>
/// The name of the collection tag.
/// </summary>
public static readonly StringAttribute Name = new(Namespace, nameof(Name)) { IsIndexed = true };

/// <summary>
/// The Nexus mods id of the collection tag.
/// </summary>
public static readonly ULongAttribute NexusId = new(Namespace, nameof(NexusId)) { IsIndexed = true };
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using NexusMods.MnemonicDB.Abstractions;
using NexusMods.MnemonicDB.Abstractions.Attributes;
using NexusMods.MnemonicDB.Abstractions.Models;
using Splat.ModeDetection;

namespace NexusMods.Abstractions.NexusModsLibrary;

/// <summary>
/// A helper for upserting entities in the database. When created, you must define a "pimary key" attribute and value,
/// these are used to determine if the entity already exists in the database. If it does, the existing entity is updated,
/// otherwise a new entity is created.
///
/// For each attribute you want to add to the entity, call the Add method with the attribute and value and any duplicate values
/// will not be added.
/// </summary>
// ReSharper disable once InconsistentNaming
public readonly struct GraphQLResolver(ITransaction Tx, ReadOnlyModel Model)
{
/// <summary>
/// Create a new resolver using the given primary key attribute and value.
/// </summary>
public static GraphQLResolver Create<THighLevel, TLowLevel>(IDb referenceDb, ITransaction tx, ScalarAttribute<THighLevel, TLowLevel> primaryKeyAttribute, THighLevel primaryKeyValue) where THighLevel : notnull
{
var existing = referenceDb.Datoms(primaryKeyAttribute, primaryKeyValue);
var exists = existing.Count > 0;
var id = existing.Count == 0 ? tx.TempId() : existing[0].E;
if (!exists)
tx.Add(id, primaryKeyAttribute, primaryKeyValue);
return new GraphQLResolver(tx, new ReadOnlyModel(referenceDb, id));
}

/// <summary>
/// The id of the entity, may be temporary if this is a new entity.
/// </summary>
public EntityId Id => Model.Id;

/// <summary>
/// Add a value to the entity. If the value already exists, it will not be added again.
/// </summary>
public void Add<THighLevel, TLowLevel>(ScalarAttribute<THighLevel, TLowLevel> attribute, THighLevel value)
where THighLevel : notnull
{
if (attribute.TryGet(Model, out var foundValue))
{
// Deduplicate values
if (foundValue.Equals(value))
return;
}
// Else add the value
Tx.Add(Model.Id, attribute, value);
}

/// <summary>
/// Add a value to the entity. If the value already exists, it will not be added again.
/// </summary>
public void Add<TOther>(ReferencesAttribute<TOther> attribute, EntityId id)
where TOther : IModelDefinition
{
if (PartitionId.Temp == id.Partition)
{
Tx.Add(Model.Id, attribute, id);
return;
}

if (attribute.Get(Model).Contains(id))
return;

// Else add the value
Tx.Add(Model.Id, attribute, id);
}

/// <summary>
/// Add a value to the entity. If the value already exists, it will not be added again.
/// </summary>
public void Add<TOther>(ReferenceAttribute<TOther> attribute, EntityId id)
where TOther : IModelDefinition
{
if (PartitionId.Temp == id.Partition)
{
Tx.Add(Model.Id, attribute, id);
return;
}

if (attribute.Get(Model).Equals(id))
return;

// Else add the value
Tx.Add(Model.Id, attribute, id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using NexusMods.Abstractions.MnemonicDB.Attributes;
using NexusMods.Abstractions.NexusModsLibrary.Attributes;
using NexusMods.MnemonicDB.Abstractions.Attributes;
using NexusMods.MnemonicDB.Abstractions.Models;

namespace NexusMods.Abstractions.NexusModsLibrary.Models;

/// <summary>
/// Metadata about a collection on Nexus Mods.
/// </summary>
public partial class CollectionMetadata : IModelDefinition
{
private const string Namespace = "NexusMods.Library.NexusModsCollectionMetadata";

/// <summary>
/// The collection slug.
/// </summary>
public static readonly CollectionsSlugAttribute Slug = new(Namespace, nameof(Slug)) { IsIndexed = true };

/// <summary>
/// The name of the collection.
/// </summary>
public static readonly StringAttribute Name = new(Namespace, nameof(Name));

/// <summary>
/// The short description of the collection
/// </summary>
public static readonly StringAttribute Summary = new(Namespace, nameof(Summary));

/// <summary>
/// The Curating user of the collection.
/// </summary>
public static readonly ReferenceAttribute<User> Author = new(Namespace, nameof(Author));

/// <summary>
/// The revisions of the collection.
/// </summary>
public static readonly BackReferenceAttribute<CollectionRevisionMetadata> Revisions = new(CollectionRevisionMetadata.Collection);

/// <summary>
/// The tags on the collection.
/// </summary>
public static readonly ReferencesAttribute<CollectionTag> Tags = new(Namespace, nameof(Tags));

/// <summary>
/// The number of endorsements the collection has.
/// </summary>
public static readonly ULongAttribute Endorsements = new(Namespace, nameof(Endorsements));

/// <summary>
/// The collections' image.
/// </summary>
public static readonly MemoryAttribute TileImage = new(Namespace, nameof(TileImage));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using NexusMods.Abstractions.MnemonicDB.Attributes;
using NexusMods.Abstractions.NexusModsLibrary.Attributes;
using NexusMods.MnemonicDB.Abstractions.Attributes;
using NexusMods.MnemonicDB.Abstractions.Models;

namespace NexusMods.Abstractions.NexusModsLibrary.Models;

/// <summary>
/// Metadata about a collection revision on Nexus Mods. A revision references a collection, but is itself immutable.
/// Each change to a collection is expressed as a separate revision.
/// </summary>
public partial class CollectionRevisionMetadata : IModelDefinition
{
private const string Namespace = "NexusMods.Library.NexusModsCollectionRevision";

/// <summary>
/// The globally unique id identifying a specific revision of a collection.
/// </summary>
public static readonly RevisionIdAttribute RevisionId = new(Namespace, nameof(RevisionId)) { IsIndexed = true };

/// <summary>
/// The locally unique revision number (aka "version") of a collection. Only unique within one collection.
/// </summary>
public static readonly RevisionNumberAttribute RevisionNumber = new(Namespace, nameof(RevisionNumber));

/// <summary>
/// The collection this revision belongs to.
/// </summary>
public static readonly ReferenceAttribute<CollectionMetadata> Collection = new(Namespace, nameof(Collection));

/// <summary>
/// The number of downloads this revision has.
/// </summary>
public static readonly ULongAttribute Downloads = new(Namespace, nameof(Downloads));

/// <summary>
/// Total download size of all files in this revision, including the size of the revision's files.
/// </summary>
public static readonly SizeAttribute TotalSize = new(Namespace, nameof(TotalSize));

/// <summary>
/// The overall rating of this revision (often displayed as a percentage).
/// </summary>
public static readonly FloatAttribute OverallRating = new(Namespace, nameof(OverallRating));

/// <summary>
/// The total number of ratings this revision has.
/// </summary>
public static readonly ULongAttribute TotalRatings = new(Namespace, nameof(TotalRatings));

/// <summary>
/// The total number of mods in this revision.
/// </summary>
public static readonly ULongAttribute ModCount = new(Namespace, nameof(ModCount));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using NexusMods.Abstractions.MnemonicDB.Attributes;
using NexusMods.Abstractions.NexusModsLibrary.Attributes;
using NexusMods.MnemonicDB.Abstractions.Attributes;
using NexusMods.MnemonicDB.Abstractions.Models;

namespace NexusMods.Abstractions.NexusModsLibrary.Models;

/// <summary>
/// A user on Nexus Mods.
/// </summary>
public partial class User : IModelDefinition
{
private const string Namespace = "NexusMods.Abstractions.NexusModsLibrary.User";

/// <summary>
/// The user's username.
/// </summary>
public static readonly StringAttribute Name = new(Namespace, nameof(Name)) { IsIndexed = true };

/// <summary>
/// The nexus id of the user.
/// </summary>
public static readonly ULongAttribute NexusId = new(Namespace, nameof(NexusId)) { IsIndexed = true };

/// <summary>
/// The user's avatar URL.
/// </summary>
public static readonly UriAttribute Avatar = new(Namespace, nameof(Avatar));

/// <summary>
/// The user's avatar image.
/// </summary>
public static readonly MemoryAttribute AvatarImage = new(Namespace, nameof(AvatarImage));
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ public partial class NexusModsCollectionLibraryFile : IModelDefinition
/// <summary>
/// The collection revision this file belongs to.
/// </summary>
public static readonly ReferenceAttribute<NexusModsCollectionRevision> CollectionRevision = new(Namespace, nameof(CollectionRevision));
public static readonly ReferenceAttribute<Models.CollectionRevisionMetadata> CollectionRevision = new(Namespace, nameof(CollectionRevision));
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using NexusMods.Abstractions.NexusModsLibrary.Models;

namespace NexusMods.Abstractions.NexusModsLibrary;

Expand All @@ -18,8 +19,10 @@ public static IServiceCollection AddNexusModsLibraryModels(this IServiceCollecti
.AddNexusModsFileMetadataModel()
.AddNexusModsModPageMetadataModel()
.AddNexusModsLibraryFileModel()
.AddNexusModsCollectionMetadataModel()
.AddNexusModsCollectionRevisionModel()
.AddCollectionMetadataModel()
.AddCollectionRevisionMetadataModel()
.AddCollectionTagModel()
.AddUserModel()
.AddNexusModsCollectionLibraryFileModel();
}
}
Loading

0 comments on commit 05fa47a

Please sign in to comment.