Browse Source

add search query collection type to block schedules (#2644)

* add search query collection type to block schedules

* fix history
pull/2645/head
Jason Dove 9 months ago committed by GitHub
parent
commit
b6d509b9cd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  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. 4
      ErsatzTV.Core/Scheduling/HistoryDetails.cs
  10. 6876
      ErsatzTV.Infrastructure.MySql/Migrations/20251113162710_Add_BlockItemSearchQuery.Designer.cs
  11. 40
      ErsatzTV.Infrastructure.MySql/Migrations/20251113162710_Add_BlockItemSearchQuery.cs
  12. 6
      ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs
  13. 6703
      ErsatzTV.Infrastructure.Sqlite/Migrations/20251113162629_Add_BlockItemSearchQuery.Designer.cs
  14. 38
      ErsatzTV.Infrastructure.Sqlite/Migrations/20251113162629_Add_BlockItemSearchQuery.cs
  15. 6
      ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs
  16. 2
      ErsatzTV.Infrastructure/Search/LuceneSearchIndex.cs
  17. 25
      ErsatzTV/Pages/BlockEditor.razor
  18. 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

4
ErsatzTV.Core/Scheduling/HistoryDetails.cs

@ -23,7 +23,8 @@ internal static class HistoryDetails
blockItem.CollectionId, blockItem.CollectionId,
blockItem.MultiCollectionId, blockItem.MultiCollectionId,
blockItem.SmartCollectionId, blockItem.SmartCollectionId,
blockItem.MediaItemId blockItem.MediaItemId,
blockItem.SearchQuery
}; };
return JsonConvert.SerializeObject(key, Formatting.None, JsonSettings); return JsonConvert.SerializeObject(key, Formatting.None, JsonSettings);
@ -96,6 +97,7 @@ internal static class HistoryDetails
collectionKey.SmartCollectionId, collectionKey.SmartCollectionId,
collectionKey.MediaItemId, collectionKey.MediaItemId,
collectionKey.PlaylistId, collectionKey.PlaylistId,
collectionKey.SearchQuery,
collectionKey.FakeCollectionKey collectionKey.FakeCollectionKey
}; };

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