Browse Source

add search query collection type to block schedules

pull/2644/head
Jason Dove 9 months ago
parent
commit
88b913f5a8
No known key found for this signature in database
  1. 2
      CHANGELOG.md
  2. 2
      ErsatzTV.Application/Scheduling/BlockItemViewModel.cs
  3. 2
      ErsatzTV.Application/Scheduling/Commands/PreviewBlockPlayoutHandler.cs
  4. 2
      ErsatzTV.Application/Scheduling/Commands/ReplaceBlockItem.cs
  5. 9
      ErsatzTV.Application/Scheduling/Commands/ReplaceBlockItemsHandler.cs
  6. 2
      ErsatzTV.Application/Scheduling/Mapper.cs
  7. 2
      ErsatzTV.Core/Domain/Scheduling/BlockItem.cs
  8. 5
      ErsatzTV.Core/Scheduling/CollectionKey.cs
  9. 6876
      ErsatzTV.Infrastructure.MySql/Migrations/20251113162710_Add_BlockItemSearchQuery.Designer.cs
  10. 40
      ErsatzTV.Infrastructure.MySql/Migrations/20251113162710_Add_BlockItemSearchQuery.cs
  11. 6
      ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs
  12. 6703
      ErsatzTV.Infrastructure.Sqlite/Migrations/20251113162629_Add_BlockItemSearchQuery.Designer.cs
  13. 38
      ErsatzTV.Infrastructure.Sqlite/Migrations/20251113162629_Add_BlockItemSearchQuery.cs
  14. 6
      ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs
  15. 2
      ErsatzTV.Infrastructure/Search/LuceneSearchIndex.cs
  16. 25
      ErsatzTV/Pages/BlockEditor.razor
  17. 7
      ErsatzTV/ViewModels/BlockItemEditViewModel.cs

2
CHANGELOG.md

@ -13,7 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Add `ScaledResolution` template data (the final size of the frame before padding) - Add `ScaledResolution` template data (the final size of the frame before padding)
- Add `place_within_source_content` (true/false) field to image graphics element - Add `place_within_source_content` (true/false) field to image graphics element
- Add `name` field to all graphics elements to display in the UI - Add `name` field to all graphics elements to display in the UI
- Classic schedules: add collection type `Search Query` - Classic and block schedules: add collection type `Search Query`
- This allows defining search queries directly on schedule items without creating smart collections beforehand - This allows defining search queries directly on schedule items without creating smart collections beforehand
- As an example, this can be used to filter or combine existing smart collections - As an example, this can be used to filter or combine existing smart collections
- Filter: `smart_collection:"sd movies" AND plot:"christmas"` - Filter: `smart_collection:"sd movies" AND plot:"christmas"`

2
ErsatzTV.Application/Scheduling/BlockItemViewModel.cs

@ -14,6 +14,8 @@ public record BlockItemViewModel(
MultiCollectionViewModel MultiCollection, MultiCollectionViewModel MultiCollection,
SmartCollectionViewModel SmartCollection, SmartCollectionViewModel SmartCollection,
NamedMediaItemViewModel MediaItem, NamedMediaItemViewModel MediaItem,
string SearchTitle,
string SearchQuery,
PlaybackOrder PlaybackOrder, PlaybackOrder PlaybackOrder,
bool IncludeInProgramGuide, bool IncludeInProgramGuide,
bool DisableWatermarks, bool DisableWatermarks,

2
ErsatzTV.Application/Scheduling/Commands/PreviewBlockPlayoutHandler.cs

@ -136,6 +136,8 @@ public class PreviewBlockPlayoutHandler(
CollectionId = request.CollectionId, CollectionId = request.CollectionId,
MultiCollectionId = request.MultiCollectionId, MultiCollectionId = request.MultiCollectionId,
SmartCollectionId = request.SmartCollectionId, SmartCollectionId = request.SmartCollectionId,
SearchTitle = request.SearchTitle,
SearchQuery = request.SearchQuery,
MediaItemId = request.MediaItemId, MediaItemId = request.MediaItemId,
PlaybackOrder = request.PlaybackOrder PlaybackOrder = request.PlaybackOrder
}; };

2
ErsatzTV.Application/Scheduling/Commands/ReplaceBlockItem.cs

@ -9,6 +9,8 @@ public record ReplaceBlockItem(
int? MultiCollectionId, int? MultiCollectionId,
int? SmartCollectionId, int? SmartCollectionId,
int? MediaItemId, int? MediaItemId,
string SearchTitle,
string SearchQuery,
PlaybackOrder PlaybackOrder, PlaybackOrder PlaybackOrder,
bool IncludeInProgramGuide, bool IncludeInProgramGuide,
bool DisableWatermarks, bool DisableWatermarks,

9
ErsatzTV.Application/Scheduling/Commands/ReplaceBlockItemsHandler.cs

@ -55,6 +55,8 @@ public class ReplaceBlockItemsHandler(IDbContextFactory<TvContext> dbContextFact
MultiCollectionId = item.MultiCollectionId, MultiCollectionId = item.MultiCollectionId,
SmartCollectionId = item.SmartCollectionId, SmartCollectionId = item.SmartCollectionId,
MediaItemId = item.MediaItemId, MediaItemId = item.MediaItemId,
SearchTitle = item.SearchTitle,
SearchQuery = item.SearchQuery,
PlaybackOrder = item.PlaybackOrder, PlaybackOrder = item.PlaybackOrder,
IncludeInProgramGuide = item.IncludeInProgramGuide, IncludeInProgramGuide = item.IncludeInProgramGuide,
DisableWatermarks = item.DisableWatermarks, DisableWatermarks = item.DisableWatermarks,
@ -163,6 +165,13 @@ public class ReplaceBlockItemsHandler(IDbContextFactory<TvContext> dbContextFact
return BaseError.New("[SmartCollection] is required for collection type 'SmartCollection'"); return BaseError.New("[SmartCollection] is required for collection type 'SmartCollection'");
} }
break;
case CollectionType.SearchQuery:
if (string.IsNullOrWhiteSpace(item.SearchQuery))
{
return BaseError.New("[SearchQuery] is required for collection type 'SearchQuery'");
}
break; break;
case CollectionType.FakeCollection: case CollectionType.FakeCollection:
default: default:

2
ErsatzTV.Application/Scheduling/Mapper.cs

@ -53,6 +53,8 @@ internal static class Mapper
Artist artist => MediaItems.Mapper.ProjectToViewModel(artist), Artist artist => MediaItems.Mapper.ProjectToViewModel(artist),
_ => null _ => null
}, },
blockItem.SearchTitle,
blockItem.SearchQuery,
blockItem.PlaybackOrder, blockItem.PlaybackOrder,
blockItem.IncludeInProgramGuide, blockItem.IncludeInProgramGuide,
blockItem.DisableWatermarks, blockItem.DisableWatermarks,

2
ErsatzTV.Core/Domain/Scheduling/BlockItem.cs

@ -15,6 +15,8 @@ public class BlockItem
public MultiCollection MultiCollection { get; set; } public MultiCollection MultiCollection { get; set; }
public int? SmartCollectionId { get; set; } public int? SmartCollectionId { get; set; }
public SmartCollection SmartCollection { get; set; } public SmartCollection SmartCollection { get; set; }
public string SearchTitle { get; set; }
public string SearchQuery { get; set; }
public PlaybackOrder PlaybackOrder { get; set; } public PlaybackOrder PlaybackOrder { get; set; }
public bool IncludeInProgramGuide { get; set; } public bool IncludeInProgramGuide { get; set; }
public bool DisableWatermarks { get; set; } public bool DisableWatermarks { get; set; }

5
ErsatzTV.Core/Scheduling/CollectionKey.cs

@ -124,6 +124,11 @@ public class CollectionKey : Record<CollectionKey>
CollectionType = item.CollectionType, CollectionType = item.CollectionType,
SmartCollectionId = item.SmartCollectionId SmartCollectionId = item.SmartCollectionId
}, },
CollectionType.SearchQuery => new CollectionKey
{
CollectionType = item.CollectionType,
SearchQuery = item.SearchQuery
},
CollectionType.FakeCollection => new CollectionKey CollectionType.FakeCollection => new CollectionKey
{ {
CollectionType = item.CollectionType CollectionType = item.CollectionType

6876
ErsatzTV.Infrastructure.MySql/Migrations/20251113162710_Add_BlockItemSearchQuery.Designer.cs generated

File diff suppressed because it is too large Load Diff

40
ErsatzTV.Infrastructure.MySql/Migrations/20251113162710_Add_BlockItemSearchQuery.cs

@ -0,0 +1,40 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.MySql.Migrations
{
/// <inheritdoc />
public partial class Add_BlockItemSearchQuery : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "SearchQuery",
table: "BlockItem",
type: "longtext",
nullable: true)
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AddColumn<string>(
name: "SearchTitle",
table: "BlockItem",
type: "longtext",
nullable: true)
.Annotation("MySql:CharSet", "utf8mb4");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "SearchQuery",
table: "BlockItem");
migrationBuilder.DropColumn(
name: "SearchTitle",
table: "BlockItem");
}
}
}

6
ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs

@ -2658,6 +2658,12 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
b.Property<int>("PlaybackOrder") b.Property<int>("PlaybackOrder")
.HasColumnType("int"); .HasColumnType("int");
b.Property<string>("SearchQuery")
.HasColumnType("longtext");
b.Property<string>("SearchTitle")
.HasColumnType("longtext");
b.Property<int?>("SmartCollectionId") b.Property<int?>("SmartCollectionId")
.HasColumnType("int"); .HasColumnType("int");

6703
ErsatzTV.Infrastructure.Sqlite/Migrations/20251113162629_Add_BlockItemSearchQuery.Designer.cs generated

File diff suppressed because it is too large Load Diff

38
ErsatzTV.Infrastructure.Sqlite/Migrations/20251113162629_Add_BlockItemSearchQuery.cs

@ -0,0 +1,38 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
{
/// <inheritdoc />
public partial class Add_BlockItemSearchQuery : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "SearchQuery",
table: "BlockItem",
type: "TEXT",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "SearchTitle",
table: "BlockItem",
type: "TEXT",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "SearchQuery",
table: "BlockItem");
migrationBuilder.DropColumn(
name: "SearchTitle",
table: "BlockItem");
}
}
}

6
ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs

@ -2537,6 +2537,12 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
b.Property<int>("PlaybackOrder") b.Property<int>("PlaybackOrder")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<string>("SearchQuery")
.HasColumnType("TEXT");
b.Property<string>("SearchTitle")
.HasColumnType("TEXT");
b.Property<int?>("SmartCollectionId") b.Property<int?>("SmartCollectionId")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");

2
ErsatzTV.Infrastructure/Search/LuceneSearchIndex.cs

@ -226,6 +226,8 @@ public sealed class LuceneSearchIndex : ISearchIndex
client?.Breadcrumbs?.Leave("SearchIndex.Search", BreadcrumbType.State, metadata); client?.Breadcrumbs?.Leave("SearchIndex.Search", BreadcrumbType.State, metadata);
query ??= string.Empty;
if (string.IsNullOrWhiteSpace(query.Replace("*", string.Empty).Replace("?", string.Empty)) || if (string.IsNullOrWhiteSpace(query.Replace("*", string.Empty).Replace("?", string.Empty)) ||
_writer.MaxDoc == 0) _writer.MaxDoc == 0)
{ {

25
ErsatzTV/Pages/BlockEditor.razor

@ -160,6 +160,7 @@
@* <MudSelectItem Value="ProgramScheduleItemCollectionType.Artist">Artist</MudSelectItem> *@ @* <MudSelectItem Value="ProgramScheduleItemCollectionType.Artist">Artist</MudSelectItem> *@
@* <MudSelectItem Value="ProgramScheduleItemCollectionType.MultiCollection">Multi Collection</MudSelectItem> *@ @* <MudSelectItem Value="ProgramScheduleItemCollectionType.MultiCollection">Multi Collection</MudSelectItem> *@
<MudSelectItem Value="CollectionType.SmartCollection">Smart Collection</MudSelectItem> <MudSelectItem Value="CollectionType.SmartCollection">Smart Collection</MudSelectItem>
<MudSelectItem Value="CollectionType.SearchQuery">Search Query</MudSelectItem>
</MudSelect> </MudSelect>
</MudStack> </MudStack>
@if (_selectedItem.CollectionType == CollectionType.Collection) @if (_selectedItem.CollectionType == CollectionType.Collection)
@ -216,6 +217,24 @@
</MudStack> </MudStack>
} }
@if (_selectedItem.CollectionType == CollectionType.SearchQuery)
{
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Search Title</MudText>
</div>
<MudTextField @bind-Value="@_selectedItem.SearchTitle"
For="@(() => _selectedItem.SearchTitle)"
HelperText="Optional title that will be displayed in the UI"/>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Search Query</MudText>
</div>
<MudTextField @bind-Value="@_selectedItem.SearchQuery" For="@(() => _selectedItem.SearchQuery)"/>
</MudStack>
}
@if (_selectedItem.CollectionType == CollectionType.TelevisionShow) @if (_selectedItem.CollectionType == CollectionType.TelevisionShow)
{ {
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5"> <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
@ -489,6 +508,8 @@ else if (_previewItems != null)
MultiCollection = item.MultiCollection, MultiCollection = item.MultiCollection,
SmartCollection = item.SmartCollection, SmartCollection = item.SmartCollection,
MediaItem = item.MediaItem, MediaItem = item.MediaItem,
SearchTitle = item.SearchTitle,
SearchQuery = item.SearchQuery,
PlaybackOrder = item.PlaybackOrder, PlaybackOrder = item.PlaybackOrder,
IncludeInProgramGuide = item.IncludeInProgramGuide, IncludeInProgramGuide = item.IncludeInProgramGuide,
DisableWatermarks = item.DisableWatermarks, DisableWatermarks = item.DisableWatermarks,
@ -522,6 +543,8 @@ else if (_previewItems != null)
MultiCollection = item.MultiCollection, MultiCollection = item.MultiCollection,
SmartCollection = item.SmartCollection, SmartCollection = item.SmartCollection,
MediaItem = item.MediaItem, MediaItem = item.MediaItem,
SearchTitle = item.SearchTitle,
SearchQuery = item.SearchQuery,
IncludeInProgramGuide = item.IncludeInProgramGuide, IncludeInProgramGuide = item.IncludeInProgramGuide,
DisableWatermarks = item.DisableWatermarks, DisableWatermarks = item.DisableWatermarks,
Watermarks = item.Watermarks.ToList(), Watermarks = item.Watermarks.ToList(),
@ -581,6 +604,8 @@ else if (_previewItems != null)
item.MultiCollection?.Id, item.MultiCollection?.Id,
item.SmartCollection?.Id, item.SmartCollection?.Id,
item.MediaItem?.MediaItemId, item.MediaItem?.MediaItemId,
item.SearchTitle,
item.SearchQuery,
item.PlaybackOrder, item.PlaybackOrder,
item.IncludeInProgramGuide, item.IncludeInProgramGuide,
item.DisableWatermarks, item.DisableWatermarks,

7
ErsatzTV/ViewModels/BlockItemEditViewModel.cs

@ -28,11 +28,15 @@ public class BlockItemEditViewModel : INotifyPropertyChanged
MultiCollection = null; MultiCollection = null;
MediaItem = null; MediaItem = null;
SmartCollection = null; SmartCollection = null;
SearchTitle = null;
SearchQuery = null;
OnPropertyChanged(nameof(Collection)); OnPropertyChanged(nameof(Collection));
OnPropertyChanged(nameof(MultiCollection)); OnPropertyChanged(nameof(MultiCollection));
OnPropertyChanged(nameof(MediaItem)); OnPropertyChanged(nameof(MediaItem));
OnPropertyChanged(nameof(SmartCollection)); OnPropertyChanged(nameof(SmartCollection));
OnPropertyChanged(nameof(SearchTitle));
OnPropertyChanged(nameof(SearchQuery));
} }
if (_collectionType == CollectionType.MultiCollection) if (_collectionType == CollectionType.MultiCollection)
@ -46,6 +50,8 @@ public class BlockItemEditViewModel : INotifyPropertyChanged
public MultiCollectionViewModel MultiCollection { get; set; } public MultiCollectionViewModel MultiCollection { get; set; }
public SmartCollectionViewModel SmartCollection { get; set; } public SmartCollectionViewModel SmartCollection { get; set; }
public NamedMediaItemViewModel MediaItem { get; set; } public NamedMediaItemViewModel MediaItem { get; set; }
public string SearchTitle { get; set; }
public string SearchQuery { get; set; }
public string CollectionName => CollectionType switch public string CollectionName => CollectionType switch
{ {
@ -55,6 +61,7 @@ public class BlockItemEditViewModel : INotifyPropertyChanged
CollectionType.Artist => MediaItem?.Name, CollectionType.Artist => MediaItem?.Name,
CollectionType.MultiCollection => MultiCollection?.Name, CollectionType.MultiCollection => MultiCollection?.Name,
CollectionType.SmartCollection => SmartCollection?.Name, CollectionType.SmartCollection => SmartCollection?.Name,
CollectionType.SearchQuery => string.IsNullOrWhiteSpace(SearchTitle) ? SearchQuery : SearchTitle,
_ => string.Empty _ => string.Empty
}; };

Loading…
Cancel
Save