allow adding smart collections to multi collections (#358)
This commit is contained in:
+2
-1
@@ -6,7 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
### Added
|
### Added
|
||||||
- Add `released_inthelast` search field for relative release date queries
|
- Add `released_inthelast` search field for relative release date queries
|
||||||
- Syntax is a number and a unit (days, weeks, months, years) like `1 week` or `2 years`
|
- Syntax is a number and a unit (days, weeks, months, years) like `1 week` or `2 years`
|
||||||
|
- Allow adding smart collections to multi collections
|
||||||
|
|
||||||
## [0.0.56-alpha] - 2021-09-10
|
## [0.0.56-alpha] - 2021-09-10
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ using MediatR;
|
|||||||
|
|
||||||
namespace ErsatzTV.Application.MediaCollections.Commands
|
namespace ErsatzTV.Application.MediaCollections.Commands
|
||||||
{
|
{
|
||||||
public record CreateMultiCollectionItem(int CollectionId, bool ScheduleAsGroup, PlaybackOrder PlaybackOrder);
|
public record CreateMultiCollectionItem(int? CollectionId, int? SmartCollectionId, bool ScheduleAsGroup, PlaybackOrder PlaybackOrder);
|
||||||
|
|
||||||
public record CreateMultiCollection
|
public record CreateMultiCollection
|
||||||
(string Name, List<CreateMultiCollectionItem> Items) : IRequest<Either<BaseError, MultiCollectionViewModel>>;
|
(string Name, List<CreateMultiCollectionItem> Items) : IRequest<Either<BaseError, MultiCollectionViewModel>>;
|
||||||
|
|||||||
@@ -41,6 +41,11 @@ namespace ErsatzTV.Application.MediaCollections.Commands
|
|||||||
.Query()
|
.Query()
|
||||||
.Include(i => i.Collection)
|
.Include(i => i.Collection)
|
||||||
.LoadAsync();
|
.LoadAsync();
|
||||||
|
await dbContext.Entry(multiCollection)
|
||||||
|
.Collection(c => c.MultiCollectionSmartItems)
|
||||||
|
.Query()
|
||||||
|
.Include(i => i.SmartCollection)
|
||||||
|
.LoadAsync();
|
||||||
return ProjectToViewModel(multiCollection);
|
return ProjectToViewModel(multiCollection);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,12 +56,44 @@ namespace ErsatzTV.Application.MediaCollections.Commands
|
|||||||
name => new MultiCollection
|
name => new MultiCollection
|
||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
MultiCollectionItems = request.Items.Map(i => new MultiCollectionItem
|
MultiCollectionItems = request.Items.Map(
|
||||||
{
|
i =>
|
||||||
CollectionId = i.CollectionId,
|
{
|
||||||
ScheduleAsGroup = i.ScheduleAsGroup,
|
if (i.CollectionId.HasValue)
|
||||||
PlaybackOrder = i.PlaybackOrder
|
{
|
||||||
}).ToList()
|
return Some(
|
||||||
|
new MultiCollectionItem
|
||||||
|
{
|
||||||
|
CollectionId = i.CollectionId.Value,
|
||||||
|
ScheduleAsGroup = i.ScheduleAsGroup,
|
||||||
|
PlaybackOrder = i.PlaybackOrder
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return None;
|
||||||
|
})
|
||||||
|
.Sequence()
|
||||||
|
.Flatten()
|
||||||
|
.ToList(),
|
||||||
|
MultiCollectionSmartItems = request.Items.Map(
|
||||||
|
i =>
|
||||||
|
{
|
||||||
|
if (i.SmartCollectionId.HasValue)
|
||||||
|
{
|
||||||
|
return Some(
|
||||||
|
new MultiCollectionSmartItem
|
||||||
|
{
|
||||||
|
SmartCollectionId = i.SmartCollectionId.Value,
|
||||||
|
ScheduleAsGroup = i.ScheduleAsGroup,
|
||||||
|
PlaybackOrder = i.PlaybackOrder
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return None;
|
||||||
|
})
|
||||||
|
.Sequence()
|
||||||
|
.Flatten()
|
||||||
|
.ToList()
|
||||||
});
|
});
|
||||||
|
|
||||||
private static async Task<Validation<BaseError, string>> ValidateName(
|
private static async Task<Validation<BaseError, string>> ValidateName(
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ using LanguageExt;
|
|||||||
|
|
||||||
namespace ErsatzTV.Application.MediaCollections.Commands
|
namespace ErsatzTV.Application.MediaCollections.Commands
|
||||||
{
|
{
|
||||||
public record UpdateMultiCollectionItem(int CollectionId, bool ScheduleAsGroup, PlaybackOrder PlaybackOrder);
|
public record UpdateMultiCollectionItem(int? CollectionId, int? SmartCollectionId, bool ScheduleAsGroup, PlaybackOrder PlaybackOrder);
|
||||||
|
|
||||||
public record UpdateMultiCollection
|
public record UpdateMultiCollection
|
||||||
(
|
(
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Channels;
|
using System.Threading.Channels;
|
||||||
@@ -48,11 +49,13 @@ namespace ErsatzTV.Application.MediaCollections.Commands
|
|||||||
await dbContext.SaveChangesAsync();
|
await dbContext.SaveChangesAsync();
|
||||||
|
|
||||||
var toAdd = request.Items
|
var toAdd = request.Items
|
||||||
.Filter(i => c.MultiCollectionItems.All(i2 => i2.CollectionId != i.CollectionId))
|
.Filter(i => i.CollectionId.HasValue)
|
||||||
.Map(
|
// ReSharper disable once PossibleInvalidOperationException
|
||||||
i => new MultiCollectionItem
|
.Filter(i => c.MultiCollectionItems.All(i2 => i2.CollectionId != i.CollectionId.Value))
|
||||||
|
.Map(i => new MultiCollectionItem
|
||||||
{
|
{
|
||||||
CollectionId = i.CollectionId,
|
// ReSharper disable once PossibleInvalidOperationException
|
||||||
|
CollectionId = i.CollectionId.Value,
|
||||||
MultiCollectionId = c.Id,
|
MultiCollectionId = c.Id,
|
||||||
ScheduleAsGroup = i.ScheduleAsGroup,
|
ScheduleAsGroup = i.ScheduleAsGroup,
|
||||||
PlaybackOrder = i.PlaybackOrder
|
PlaybackOrder = i.PlaybackOrder
|
||||||
@@ -79,6 +82,40 @@ namespace ErsatzTV.Application.MediaCollections.Commands
|
|||||||
// add new items
|
// add new items
|
||||||
c.MultiCollectionItems.AddRange(toAdd);
|
c.MultiCollectionItems.AddRange(toAdd);
|
||||||
|
|
||||||
|
var toAddSmart = request.Items
|
||||||
|
.Filter(i => i.SmartCollectionId.HasValue)
|
||||||
|
// ReSharper disable once PossibleInvalidOperationException
|
||||||
|
.Filter(i => c.MultiCollectionSmartItems.All(i2 => i2.SmartCollectionId != i.SmartCollectionId.Value))
|
||||||
|
.Map(i => new MultiCollectionSmartItem
|
||||||
|
{
|
||||||
|
// ReSharper disable once PossibleInvalidOperationException
|
||||||
|
SmartCollectionId = i.SmartCollectionId.Value,
|
||||||
|
MultiCollectionId = c.Id,
|
||||||
|
ScheduleAsGroup = i.ScheduleAsGroup,
|
||||||
|
PlaybackOrder = i.PlaybackOrder
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
var toRemoveSmart = c.MultiCollectionSmartItems
|
||||||
|
.Filter(i => request.Items.All(i2 => i2.SmartCollectionId != i.SmartCollectionId))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// remove items that are no longer present
|
||||||
|
c.MultiCollectionSmartItems.RemoveAll(toRemoveSmart.Contains);
|
||||||
|
|
||||||
|
// update existing items
|
||||||
|
foreach (MultiCollectionSmartItem item in c.MultiCollectionSmartItems)
|
||||||
|
{
|
||||||
|
foreach (UpdateMultiCollectionItem incoming in request.Items.Filter(
|
||||||
|
i => i.SmartCollectionId == item.SmartCollectionId))
|
||||||
|
{
|
||||||
|
item.ScheduleAsGroup = incoming.ScheduleAsGroup;
|
||||||
|
item.PlaybackOrder = incoming.PlaybackOrder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add new items
|
||||||
|
c.MultiCollectionSmartItems.AddRange(toAddSmart);
|
||||||
|
|
||||||
// rebuild playouts
|
// rebuild playouts
|
||||||
if (await dbContext.SaveChangesAsync() > 0)
|
if (await dbContext.SaveChangesAsync() > 0)
|
||||||
{
|
{
|
||||||
@@ -104,6 +141,7 @@ namespace ErsatzTV.Application.MediaCollections.Commands
|
|||||||
UpdateMultiCollection updateCollection) =>
|
UpdateMultiCollection updateCollection) =>
|
||||||
dbContext.MultiCollections
|
dbContext.MultiCollections
|
||||||
.Include(mc => mc.MultiCollectionItems)
|
.Include(mc => mc.MultiCollectionItems)
|
||||||
|
.Include(mc => mc.MultiCollectionSmartItems)
|
||||||
.SelectOneAsync(c => c.Id, c => c.Id == updateCollection.MultiCollectionId)
|
.SelectOneAsync(c => c.Id, c => c.Id == updateCollection.MultiCollectionId)
|
||||||
.Map(o => o.ToValidation<BaseError>("MultiCollection does not exist."));
|
.Map(o => o.ToValidation<BaseError>("MultiCollection does not exist."));
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ namespace ErsatzTV.Application.MediaCollections
|
|||||||
new(
|
new(
|
||||||
multiCollection.Id,
|
multiCollection.Id,
|
||||||
multiCollection.Name,
|
multiCollection.Name,
|
||||||
Optional(multiCollection.MultiCollectionItems).Flatten().Map(ProjectToViewModel).ToList());
|
Optional(multiCollection.MultiCollectionItems).Flatten().Map(ProjectToViewModel).ToList(),
|
||||||
|
Optional(multiCollection.MultiCollectionSmartItems).Flatten().Map(ProjectToViewModel).ToList());
|
||||||
|
|
||||||
internal static SmartCollectionViewModel ProjectToViewModel(SmartCollection collection) =>
|
internal static SmartCollectionViewModel ProjectToViewModel(SmartCollection collection) =>
|
||||||
new(collection.Id, collection.Name, collection.Query);
|
new(collection.Id, collection.Name, collection.Query);
|
||||||
@@ -24,5 +25,12 @@ namespace ErsatzTV.Application.MediaCollections
|
|||||||
ProjectToViewModel(multiCollectionItem.Collection),
|
ProjectToViewModel(multiCollectionItem.Collection),
|
||||||
multiCollectionItem.ScheduleAsGroup,
|
multiCollectionItem.ScheduleAsGroup,
|
||||||
multiCollectionItem.PlaybackOrder);
|
multiCollectionItem.PlaybackOrder);
|
||||||
|
|
||||||
|
private static MultiCollectionSmartItemViewModel ProjectToViewModel(MultiCollectionSmartItem multiCollectionSmartItem) =>
|
||||||
|
new(
|
||||||
|
multiCollectionSmartItem.MultiCollectionId,
|
||||||
|
ProjectToViewModel(multiCollectionSmartItem.SmartCollection),
|
||||||
|
multiCollectionSmartItem.ScheduleAsGroup,
|
||||||
|
multiCollectionSmartItem.PlaybackOrder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using ErsatzTV.Core.Domain;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.MediaCollections
|
||||||
|
{
|
||||||
|
public record MultiCollectionSmartItemViewModel(
|
||||||
|
int MultiCollectionId,
|
||||||
|
SmartCollectionViewModel SmartCollection,
|
||||||
|
bool ScheduleAsGroup,
|
||||||
|
PlaybackOrder PlaybackOrder);
|
||||||
|
}
|
||||||
@@ -2,5 +2,9 @@
|
|||||||
|
|
||||||
namespace ErsatzTV.Application.MediaCollections
|
namespace ErsatzTV.Application.MediaCollections
|
||||||
{
|
{
|
||||||
public record MultiCollectionViewModel(int Id, string Name, List<MultiCollectionItemViewModel> Items);
|
public record MultiCollectionViewModel(
|
||||||
|
int Id,
|
||||||
|
string Name,
|
||||||
|
List<MultiCollectionItemViewModel> Items,
|
||||||
|
List<MultiCollectionSmartItemViewModel> SmartItems);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ namespace ErsatzTV.Application.MediaCollections.Queries
|
|||||||
return await dbContext.MultiCollections
|
return await dbContext.MultiCollections
|
||||||
.Include(mc => mc.MultiCollectionItems)
|
.Include(mc => mc.MultiCollectionItems)
|
||||||
.ThenInclude(mc => mc.Collection)
|
.ThenInclude(mc => mc.Collection)
|
||||||
|
.Include(mc => mc.MultiCollectionSmartItems)
|
||||||
|
.ThenInclude(mc => mc.SmartCollection)
|
||||||
.SelectOneAsync(c => c.Id, c => c.Id == request.Id)
|
.SelectOneAsync(c => c.Id, c => c.Id == request.Id)
|
||||||
.MapT(ProjectToViewModel);
|
.MapT(ProjectToViewModel);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ namespace ErsatzTV.Core.Domain
|
|||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public List<Collection> Collections { get; set; }
|
public List<Collection> Collections { get; set; }
|
||||||
|
public List<SmartCollection> SmartCollections { get; set; }
|
||||||
public List<MultiCollectionItem> MultiCollectionItems { get; set; }
|
public List<MultiCollectionItem> MultiCollectionItems { get; set; }
|
||||||
|
public List<MultiCollectionSmartItem> MultiCollectionSmartItems { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
namespace ErsatzTV.Core.Domain
|
||||||
|
{
|
||||||
|
public class MultiCollectionSmartItem
|
||||||
|
{
|
||||||
|
public int MultiCollectionId { get; set; }
|
||||||
|
public MultiCollection MultiCollection { get; set; }
|
||||||
|
public int SmartCollectionId { get; set; }
|
||||||
|
public SmartCollection SmartCollection { get; set; }
|
||||||
|
public bool ScheduleAsGroup { get; set; }
|
||||||
|
public PlaybackOrder PlaybackOrder { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,13 @@
|
|||||||
namespace ErsatzTV.Core.Domain
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Core.Domain
|
||||||
{
|
{
|
||||||
public class SmartCollection
|
public class SmartCollection
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public string Query { get; set; }
|
public string Query { get; set; }
|
||||||
|
public List<MultiCollection> MultiCollections { get; set; }
|
||||||
|
public List<MultiCollectionSmartItem> MultiCollectionSmartItems { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
@@ -22,6 +22,19 @@ namespace ErsatzTV.Infrastructure.Data.Configurations
|
|||||||
.HasForeignKey(mci => mci.MultiCollectionId)
|
.HasForeignKey(mci => mci.MultiCollectionId)
|
||||||
.OnDelete(DeleteBehavior.Cascade),
|
.OnDelete(DeleteBehavior.Cascade),
|
||||||
j => j.HasKey(mci => new { mci.MultiCollectionId, mci.CollectionId }));
|
j => j.HasKey(mci => new { mci.MultiCollectionId, mci.CollectionId }));
|
||||||
|
|
||||||
|
builder.HasMany(m => m.SmartCollections)
|
||||||
|
.WithMany(m => m.MultiCollections)
|
||||||
|
.UsingEntity<MultiCollectionSmartItem>(
|
||||||
|
j => j.HasOne(mci => mci.SmartCollection)
|
||||||
|
.WithMany(c => c.MultiCollectionSmartItems)
|
||||||
|
.HasForeignKey(mci => mci.SmartCollectionId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade),
|
||||||
|
j => j.HasOne(mci => mci.MultiCollection)
|
||||||
|
.WithMany(mc => mc.MultiCollectionSmartItems)
|
||||||
|
.HasForeignKey(mci => mci.MultiCollectionId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade),
|
||||||
|
j => j.HasKey(mci => new { mci.MultiCollectionId, mci.SmartCollectionId }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
|||||||
|
|
||||||
Option<MultiCollection> maybeMultiCollection = await dbContext.MultiCollections
|
Option<MultiCollection> maybeMultiCollection = await dbContext.MultiCollections
|
||||||
.Include(mc => mc.Collections)
|
.Include(mc => mc.Collections)
|
||||||
|
.Include(mc => mc.SmartCollections)
|
||||||
.SelectOneAsync(mc => mc.Id, mc => mc.Id == id);
|
.SelectOneAsync(mc => mc.Id, mc => mc.Id == id);
|
||||||
|
|
||||||
foreach (MultiCollection multiCollection in maybeMultiCollection)
|
foreach (MultiCollection multiCollection in maybeMultiCollection)
|
||||||
@@ -79,6 +80,11 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
|||||||
result.AddRange(await GetArtistItems(dbContext, collectionId));
|
result.AddRange(await GetArtistItems(dbContext, collectionId));
|
||||||
result.AddRange(await GetMusicVideoItems(dbContext, collectionId));
|
result.AddRange(await GetMusicVideoItems(dbContext, collectionId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (int smartCollectionId in multiCollection.SmartCollections.Map(c => c.Id))
|
||||||
|
{
|
||||||
|
result.AddRange(await GetSmartCollectionItems(smartCollectionId));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.Distinct().ToList();
|
return result.Distinct().ToList();
|
||||||
@@ -138,8 +144,11 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
|||||||
|
|
||||||
Option<MultiCollection> maybeMultiCollection = await dbContext.MultiCollections
|
Option<MultiCollection> maybeMultiCollection = await dbContext.MultiCollections
|
||||||
.Include(mc => mc.Collections)
|
.Include(mc => mc.Collections)
|
||||||
|
.Include(mc => mc.SmartCollections)
|
||||||
.Include(mc => mc.MultiCollectionItems)
|
.Include(mc => mc.MultiCollectionItems)
|
||||||
.ThenInclude(mci => mci.Collection)
|
.ThenInclude(mci => mci.Collection)
|
||||||
|
.Include(mc => mc.MultiCollectionSmartItems)
|
||||||
|
.ThenInclude(mci => mci.SmartCollection)
|
||||||
.SelectOneAsync(mc => mc.Id, mc => mc.Id == id);
|
.SelectOneAsync(mc => mc.Id, mc => mc.Id == id);
|
||||||
|
|
||||||
foreach (MultiCollection multiCollection in maybeMultiCollection)
|
foreach (MultiCollection multiCollection in maybeMultiCollection)
|
||||||
@@ -178,6 +187,19 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
|||||||
multiCollectionItem.Collection.UseCustomPlaybackOrder));
|
multiCollectionItem.Collection.UseCustomPlaybackOrder));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (MultiCollectionSmartItem multiCollectionSmartItem in multiCollection.MultiCollectionSmartItems)
|
||||||
|
{
|
||||||
|
List<MediaItem> items = await GetSmartCollectionItems(multiCollectionSmartItem.SmartCollectionId);
|
||||||
|
|
||||||
|
result.Add(
|
||||||
|
new CollectionWithItems(
|
||||||
|
multiCollectionSmartItem.SmartCollectionId,
|
||||||
|
items,
|
||||||
|
multiCollectionSmartItem.ScheduleAsGroup,
|
||||||
|
multiCollectionSmartItem.PlaybackOrder,
|
||||||
|
false));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove duplicate items from ungrouped collections
|
// remove duplicate items from ungrouped collections
|
||||||
|
|||||||
Generated
+3150
File diff suppressed because it is too large
Load Diff
+47
@@ -0,0 +1,47 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Infrastructure.Migrations
|
||||||
|
{
|
||||||
|
public partial class Add_MultiCollectionSmartCollection : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "MultiCollectionSmartItem",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
MultiCollectionId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
SmartCollectionId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
ScheduleAsGroup = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||||
|
PlaybackOrder = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_MultiCollectionSmartItem", x => new { x.MultiCollectionId, x.SmartCollectionId });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_MultiCollectionSmartItem_MultiCollection_MultiCollectionId",
|
||||||
|
column: x => x.MultiCollectionId,
|
||||||
|
principalTable: "MultiCollection",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_MultiCollectionSmartItem_SmartCollection_SmartCollectionId",
|
||||||
|
column: x => x.SmartCollectionId,
|
||||||
|
principalTable: "SmartCollection",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_MultiCollectionSmartItem_SmartCollectionId",
|
||||||
|
table: "MultiCollectionSmartItem",
|
||||||
|
column: "SmartCollectionId");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "MultiCollectionSmartItem");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -979,6 +979,27 @@ namespace ErsatzTV.Infrastructure.Migrations
|
|||||||
b.ToTable("MultiCollectionItem");
|
b.ToTable("MultiCollectionItem");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.MultiCollectionSmartItem", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("MultiCollectionId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("SmartCollectionId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("PlaybackOrder")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("ScheduleAsGroup")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("MultiCollectionId", "SmartCollectionId");
|
||||||
|
|
||||||
|
b.HasIndex("SmartCollectionId");
|
||||||
|
|
||||||
|
b.ToTable("MultiCollectionSmartItem");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("ErsatzTV.Core.Domain.MusicVideoMetadata", b =>
|
modelBuilder.Entity("ErsatzTV.Core.Domain.MusicVideoMetadata", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -2247,6 +2268,25 @@ namespace ErsatzTV.Infrastructure.Migrations
|
|||||||
b.Navigation("MultiCollection");
|
b.Navigation("MultiCollection");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.MultiCollectionSmartItem", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ErsatzTV.Core.Domain.MultiCollection", "MultiCollection")
|
||||||
|
.WithMany("MultiCollectionSmartItems")
|
||||||
|
.HasForeignKey("MultiCollectionId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("ErsatzTV.Core.Domain.SmartCollection", "SmartCollection")
|
||||||
|
.WithMany("MultiCollectionSmartItems")
|
||||||
|
.HasForeignKey("SmartCollectionId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("MultiCollection");
|
||||||
|
|
||||||
|
b.Navigation("SmartCollection");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("ErsatzTV.Core.Domain.MusicVideoMetadata", b =>
|
modelBuilder.Entity("ErsatzTV.Core.Domain.MusicVideoMetadata", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("ErsatzTV.Core.Domain.MusicVideo", "MusicVideo")
|
b.HasOne("ErsatzTV.Core.Domain.MusicVideo", "MusicVideo")
|
||||||
@@ -2972,6 +3012,8 @@ namespace ErsatzTV.Infrastructure.Migrations
|
|||||||
modelBuilder.Entity("ErsatzTV.Core.Domain.MultiCollection", b =>
|
modelBuilder.Entity("ErsatzTV.Core.Domain.MultiCollection", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("MultiCollectionItems");
|
b.Navigation("MultiCollectionItems");
|
||||||
|
|
||||||
|
b.Navigation("MultiCollectionSmartItems");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("ErsatzTV.Core.Domain.MusicVideoMetadata", b =>
|
modelBuilder.Entity("ErsatzTV.Core.Domain.MusicVideoMetadata", b =>
|
||||||
@@ -3033,6 +3075,11 @@ namespace ErsatzTV.Infrastructure.Migrations
|
|||||||
b.Navigation("Tags");
|
b.Navigation("Tags");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.SmartCollection", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("MultiCollectionSmartItems");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Artist", b =>
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Artist", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("ArtistMetadata");
|
b.Navigation("ArtistMetadata");
|
||||||
|
|||||||
@@ -24,11 +24,21 @@
|
|||||||
@bind-value="_selectedCollection"
|
@bind-value="_selectedCollection"
|
||||||
SearchFunc="@SearchCollections"
|
SearchFunc="@SearchCollections"
|
||||||
ToStringFunc="@(c => c?.Name)"/>
|
ToStringFunc="@(c => c?.Name)"/>
|
||||||
</MudCardContent>
|
<MudButton Variant="Variant.Filled" Color="Color.Secondary" OnClick="@(_ => AddCollection())" Class="mt-4 mr-auto">
|
||||||
<MudCardActions>
|
|
||||||
<MudButton Variant="Variant.Filled" Color="Color.Secondary" OnClick="@(_ => AddCollection())" Class="ml-2">
|
|
||||||
Add Collection
|
Add Collection
|
||||||
</MudButton>
|
</MudButton>
|
||||||
|
<MudAutocomplete @ref="_smartCollectionAutocomplete"
|
||||||
|
Class="mt-4"
|
||||||
|
T="SmartCollectionViewModel"
|
||||||
|
Label="Smart Collection"
|
||||||
|
@bind-value="_selectedSmartCollection"
|
||||||
|
SearchFunc="@SearchSmartCollections"
|
||||||
|
ToStringFunc="@(c => c?.Name)"/>
|
||||||
|
<MudButton Variant="Variant.Filled" Color="Color.Secondary" OnClick="@(_ => AddSmartCollection())" Class="mt-4 mr-auto">
|
||||||
|
Add Smart Collection
|
||||||
|
</MudButton>
|
||||||
|
</MudCardContent>
|
||||||
|
<MudCardActions>
|
||||||
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Primary" Class="mr-2 ml-auto">
|
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Primary" Class="mr-2 ml-auto">
|
||||||
@(IsEdit ? "Save Changes" : "Add Multi Collection")
|
@(IsEdit ? "Save Changes" : "Add Multi Collection")
|
||||||
</MudButton>
|
</MudButton>
|
||||||
@@ -87,14 +97,20 @@
|
|||||||
private EditContext _editContext;
|
private EditContext _editContext;
|
||||||
private ValidationMessageStore _messageStore;
|
private ValidationMessageStore _messageStore;
|
||||||
private List<MediaCollectionViewModel> _collections;
|
private List<MediaCollectionViewModel> _collections;
|
||||||
|
private List<SmartCollectionViewModel> _smartCollections;
|
||||||
private MediaCollectionViewModel _selectedCollection;
|
private MediaCollectionViewModel _selectedCollection;
|
||||||
|
private SmartCollectionViewModel _selectedSmartCollection;
|
||||||
private MudAutocomplete<MediaCollectionViewModel> _collectionAutocomplete;
|
private MudAutocomplete<MediaCollectionViewModel> _collectionAutocomplete;
|
||||||
|
private MudAutocomplete<SmartCollectionViewModel> _smartCollectionAutocomplete;
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnParametersSetAsync()
|
||||||
{
|
{
|
||||||
_collections = await _mediator.Send(new GetAllCollections())
|
_collections = await _mediator.Send(new GetAllCollections())
|
||||||
.Map(list => list.OrderBy(vm => vm.Name, StringComparer.CurrentCultureIgnoreCase).ToList());
|
.Map(list => list.OrderBy(vm => vm.Name, StringComparer.CurrentCultureIgnoreCase).ToList());
|
||||||
|
|
||||||
|
_smartCollections = await _mediator.Send(new GetAllSmartCollections())
|
||||||
|
.Map(list => list.OrderBy(vm => vm.Name, StringComparer.CurrentCultureIgnoreCase).ToList());
|
||||||
|
|
||||||
if (IsEdit)
|
if (IsEdit)
|
||||||
{
|
{
|
||||||
Option<MultiCollectionViewModel> maybeCollection = await _mediator.Send(new GetMultiCollectionById(Id));
|
Option<MultiCollectionViewModel> maybeCollection = await _mediator.Send(new GetMultiCollectionById(Id));
|
||||||
@@ -102,13 +118,21 @@
|
|||||||
{
|
{
|
||||||
_model.Id = collection.Id;
|
_model.Id = collection.Id;
|
||||||
_model.Name = collection.Name;
|
_model.Name = collection.Name;
|
||||||
_model.Items = collection.Items.Map(item =>
|
_model.Items = collection.Items
|
||||||
new MultiCollectionItemEditViewModel
|
.Map(item =>
|
||||||
{
|
new MultiCollectionItemEditViewModel
|
||||||
Collection = item.Collection,
|
{
|
||||||
ScheduleAsGroup = item.ScheduleAsGroup,
|
Collection = item.Collection,
|
||||||
PlaybackOrder = item.PlaybackOrder,
|
ScheduleAsGroup = item.ScheduleAsGroup,
|
||||||
}).ToList();
|
PlaybackOrder = item.PlaybackOrder,
|
||||||
|
})
|
||||||
|
.Append(collection.SmartItems.Map(item =>
|
||||||
|
new MultiCollectionSmartItemEditViewModel
|
||||||
|
{
|
||||||
|
SmartCollection = item.SmartCollection,
|
||||||
|
ScheduleAsGroup = item.ScheduleAsGroup,
|
||||||
|
PlaybackOrder = item.PlaybackOrder
|
||||||
|
})).ToList();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -132,8 +156,8 @@
|
|||||||
if (_editContext.Validate())
|
if (_editContext.Validate())
|
||||||
{
|
{
|
||||||
Seq<BaseError> errorMessage = IsEdit ?
|
Seq<BaseError> errorMessage = IsEdit ?
|
||||||
(await _mediator.Send(new UpdateMultiCollection(Id, _model.Name, _model.Items.Map(i => new UpdateMultiCollectionItem(i.Collection.Id, i.ScheduleAsGroup, i.PlaybackOrder)).ToList()))).LeftToSeq() :
|
(await _mediator.Send(new UpdateMultiCollection(Id, _model.Name, GetUpdateItems()))).LeftToSeq() :
|
||||||
(await _mediator.Send(new CreateMultiCollection(_model.Name, _model.Items.Map(i => new CreateMultiCollectionItem(i.Collection.Id, i.ScheduleAsGroup, i.PlaybackOrder)).ToList()))).LeftToSeq();
|
(await _mediator.Send(new CreateMultiCollection(_model.Name, GetCreateItems()))).LeftToSeq();
|
||||||
|
|
||||||
errorMessage.HeadOrNone().Match(
|
errorMessage.HeadOrNone().Match(
|
||||||
error =>
|
error =>
|
||||||
@@ -145,6 +169,32 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<UpdateMultiCollectionItem> GetUpdateItems() =>
|
||||||
|
_model.Items.Map(i =>
|
||||||
|
i switch
|
||||||
|
{
|
||||||
|
MultiCollectionSmartItemEditViewModel smartVm =>
|
||||||
|
new UpdateMultiCollectionItem(
|
||||||
|
null,
|
||||||
|
smartVm.SmartCollection.Id,
|
||||||
|
smartVm.ScheduleAsGroup,
|
||||||
|
smartVm.PlaybackOrder),
|
||||||
|
_ => new UpdateMultiCollectionItem(i.Collection.Id, null, i.ScheduleAsGroup, i.PlaybackOrder)
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
private List<CreateMultiCollectionItem> GetCreateItems() =>
|
||||||
|
_model.Items.Map(i =>
|
||||||
|
i switch
|
||||||
|
{
|
||||||
|
MultiCollectionSmartItemEditViewModel smartVm =>
|
||||||
|
new CreateMultiCollectionItem(
|
||||||
|
null,
|
||||||
|
smartVm.SmartCollection.Id,
|
||||||
|
smartVm.ScheduleAsGroup,
|
||||||
|
smartVm.PlaybackOrder),
|
||||||
|
_ => new CreateMultiCollectionItem(i.Collection.Id, null, i.ScheduleAsGroup, i.PlaybackOrder)
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
private void RemoveCollection(MultiCollectionItemEditViewModel item)
|
private void RemoveCollection(MultiCollectionItemEditViewModel item)
|
||||||
{
|
{
|
||||||
_model.Items.Remove(item);
|
_model.Items.Remove(item);
|
||||||
@@ -165,7 +215,25 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AddSmartCollection()
|
||||||
|
{
|
||||||
|
if (_selectedSmartCollection != null && _model.Items.OfType<MultiCollectionSmartItemEditViewModel>().All(i => i.SmartCollection != _selectedSmartCollection))
|
||||||
|
{
|
||||||
|
_model.Items.Add(new MultiCollectionSmartItemEditViewModel
|
||||||
|
{
|
||||||
|
SmartCollection = _selectedSmartCollection,
|
||||||
|
PlaybackOrder = PlaybackOrder.Chronological
|
||||||
|
});
|
||||||
|
|
||||||
|
_selectedSmartCollection = null;
|
||||||
|
_smartCollectionAutocomplete.Reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Task<IEnumerable<MediaCollectionViewModel>> SearchCollections(string value) =>
|
private Task<IEnumerable<MediaCollectionViewModel>> SearchCollections(string value) =>
|
||||||
_collections.Filter(c => _model.Items.All(i => i.Collection != c) && c.Name.Contains(value ?? string.Empty, StringComparison.OrdinalIgnoreCase)).AsTask();
|
_collections.Filter(c => _model.Items.All(i => i.Collection != c) && c.Name.Contains(value ?? string.Empty, StringComparison.OrdinalIgnoreCase)).AsTask();
|
||||||
|
|
||||||
|
private Task<IEnumerable<SmartCollectionViewModel>> SearchSmartCollections(string value) =>
|
||||||
|
_smartCollections.Filter(c => _model.Items.OfType<MultiCollectionSmartItemEditViewModel>().All(i => i.SmartCollection != c) && c.Name.Contains(value ?? string.Empty, StringComparison.OrdinalIgnoreCase)).AsTask();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,7 @@ namespace ErsatzTV.ViewModels
|
|||||||
{
|
{
|
||||||
public class MultiCollectionItemEditViewModel
|
public class MultiCollectionItemEditViewModel
|
||||||
{
|
{
|
||||||
public MediaCollectionViewModel Collection { get; set; }
|
public virtual MediaCollectionViewModel Collection { get; set; }
|
||||||
public bool ScheduleAsGroup { get; set; }
|
public bool ScheduleAsGroup { get; set; }
|
||||||
public PlaybackOrder PlaybackOrder { get; set; }
|
public PlaybackOrder PlaybackOrder { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using ErsatzTV.Application.MediaCollections;
|
||||||
|
|
||||||
|
namespace ErsatzTV.ViewModels
|
||||||
|
{
|
||||||
|
public class MultiCollectionSmartItemEditViewModel : MultiCollectionItemEditViewModel
|
||||||
|
{
|
||||||
|
private SmartCollectionViewModel _smartCollection;
|
||||||
|
|
||||||
|
public SmartCollectionViewModel SmartCollection
|
||||||
|
{
|
||||||
|
get => _smartCollection;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_smartCollection = value;
|
||||||
|
|
||||||
|
Collection = new MediaCollectionViewModel(
|
||||||
|
_smartCollection.Id,
|
||||||
|
_smartCollection.Name,
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override MediaCollectionViewModel Collection { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user