Browse Source

rework schedule items editor (#2146)

pull/2147/head
Jason Dove 1 month ago committed by GitHub
parent
commit
833bf3506a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      CHANGELOG.md
  2. 880
      ErsatzTV/Pages/ScheduleItemsEditor.razor

2
CHANGELOG.md

@ -83,7 +83,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Remove some limits on multithreading that are no longer needed with latest ffmpeg - Remove some limits on multithreading that are no longer needed with latest ffmpeg
- Mixed transcoding (software decode, hardware filters/encode) can now use multiple decode threads - Mixed transcoding (software decode, hardware filters/encode) can now use multiple decode threads
- Split main `Settings` page into multiple pages - Split main `Settings` page into multiple pages
- Update UI layout on most pages to be less cramped and to work better on mobile - Update UI layout on all pages to be less cramped and to work better on mobile
### Fixed ### Fixed
- Fix QSV acceleration in docker with older Intel devices - Fix QSV acceleration in docker with older Intel devices

880
ErsatzTV/Pages/ScheduleItemsEditor.razor

@ -13,405 +13,548 @@
@inject ISnackbar Snackbar @inject ISnackbar Snackbar
@inject IMediator Mediator @inject IMediator Mediator
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8"> <MudForm @ref="_form" @bind-IsValid="@_success" Style="max-height: 100%">
<MudTable T="ProgramScheduleItemEditViewModel" <MudPaper Square="true" Style="display: flex; height: 64px; min-height: 64px; width: 100%; z-index: 100; align-items: center">
Hover="true" <div style="display: flex; flex-direction: row; margin-bottom: auto; margin-top: auto; width: 100%; align-items: center" class="ml-6 mr-6">
Items="_schedule?.Items?.OrderBy(i => i.Index)" <div class="flex-grow-1">
Dense="true" @if (_selectedItem is not null)
SelectedItemChanged="@(vm => SelectedItemChanged(vm))"
RowClassFunc="@SelectedRowClassFunc">
<ToolBarContent>
<MudText Typo="Typo.h6">@_schedule.Name Items</MudText>
</ToolBarContent>
<ColGroup>
<col/>
<col/>
<col/>
<col style="width: 60px;"/>
<col style="width: 60px;"/>
<col style="width: 60px;"/>
<col style="width: 60px;"/>
</ColGroup>
<HeaderContent>
<MudTh>Start Time</MudTh>
<MudTh>Collection</MudTh>
<MudTh>Playout Mode</MudTh>
<MudTh/>
<MudTh/>
<MudTh/>
<MudTh/>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Start Time">
<MudText Typo="@Typo.body2">
@(context.StartType == StartType.Fixed ? context.StartTime == null ? string.Empty : DateTime.Today.Add(context.StartTime.Value).ToShortTimeString() : "Dynamic")
</MudText>
</MudTd>
<MudTd DataLabel="Collection">
<MudText Typo="@Typo.body2">
@context.CollectionName
</MudText>
</MudTd>
<MudTd DataLabel="Playout Mode">
<MudText Typo="@Typo.body2">
@context.PlayoutMode
@if (context.PlayoutMode == PlayoutMode.Multiple && context.MultipleCount.HasValue)
{ {
@($" ({context.MultipleCount})") string start = _selectedItem.StartType == StartType.Fixed ? _selectedItem.StartTime == null ? string.Empty : DateTime.Today.Add(_selectedItem.StartTime.Value).ToShortTimeString() : "Dynamic";
} var mode = _selectedItem.PlayoutMode.ToString();
</MudText> @if (_selectedItem.PlayoutMode == PlayoutMode.Multiple && _selectedItem.MultipleCount.HasValue)
</MudTd>
<MudTd>
<MudIconButton Icon="@Icons.Material.Filled.ContentCopy"
OnClick="@(_ => CopyItem(context))">
</MudIconButton>
</MudTd>
<MudTd>
@if (!_schedule.ShuffleScheduleItems)
{
<MudIconButton Icon="@Icons.Material.Filled.ArrowUpward"
OnClick="@(_ => MoveItemUp(context))"
Disabled="@(_schedule.ShuffleScheduleItems || _schedule.Items.All(x => x.Index >= context.Index))">
</MudIconButton>
}
</MudTd>
<MudTd>
@if (!_schedule.ShuffleScheduleItems)
{
<MudIconButton Icon="@Icons.Material.Filled.ArrowDownward"
OnClick="@(_ => MoveItemDown(context))"
Disabled="@(_schedule.ShuffleScheduleItems || _schedule.Items.All(x => x.Index <= context.Index))">
</MudIconButton>
}
</MudTd>
<MudTd>
<MudIconButton Icon="@Icons.Material.Filled.Delete"
OnClick="@(_ => RemoveScheduleItem(context))">
</MudIconButton>
</MudTd>
</RowTemplate>
</MudTable>
<MudButton Variant="Variant.Filled" Color="Color.Default" OnClick="@(_ => AddScheduleItem())" Class="mt-4">
Add Schedule Item
</MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(_ => SaveChanges())" Class="mt-4 ml-4">
Save Changes
</MudButton>
@if (_selectedItem is not null)
{
<EditForm Model="_selectedItem">
<FluentValidationValidator/>
<div style="display: flex; flex-direction: row;" class="mt-6">
<div style="flex-grow: 1; max-width: 400px;" class="mr-6">
<MudCard>
<MudCardContent>
<MudSelect Label="Start Type" @bind-Value="_selectedItem.StartType" For="@(() => _selectedItem.StartType)">
<MudSelectItem Value="StartType.Dynamic">Dynamic</MudSelectItem>
@if (!_schedule.ShuffleScheduleItems)
{ {
<MudSelectItem Value="StartType.Fixed">Fixed</MudSelectItem> mode += $" ({_selectedItem.MultipleCount})";
} }
</MudSelect>
<MudTimePicker Class="mt-3" Label="Start Time" @bind-Time="@_selectedItem.StartTime" For="@(() => _selectedItem.StartTime)" Disabled="@(_selectedItem.StartType == StartType.Dynamic)" Editable="true"/> var result = $"{_schedule.Name} ({start} - {_selectedItem.CollectionName} - {mode})";
@if (_selectedItem.StartType == StartType.Fixed) <MudText>@result</MudText>
}
else
{ {
<MudSelect Class="mt-3" Label="Fixed Start Time Behavior" @bind-Value="_selectedItem.FixedStartTimeBehavior" For="@(() => _selectedItem.FixedStartTimeBehavior)"> <MudText>@_schedule.Name</MudText>
}
</div>
<div style="margin-left: auto" class="d-none d-md-flex">
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="SaveChanges" StartIcon="@Icons.Material.Filled.Save">
Save Schedule Items
</MudButton>
<MudButton Class="ml-3" Variant="Variant.Filled" Color="Color.Default" OnClick="AddScheduleItem" StartIcon="@Icons.Material.Filled.PlaylistAdd">
Add Schedule Item
</MudButton>
</div>
<div style="align-items: center; display: flex; margin-left: auto;" class="d-md-none">
<div class="flex-grow-1"></div>
<MudMenu Icon="@Icons.Material.Filled.MoreVert">
<MudMenuItem Icon="@Icons.Material.Filled.Save" Label="Save Schedule Items" OnClick="SaveChanges"/>
<MudMenuItem Icon="@Icons.Material.Filled.PlaylistAdd" Label="Add Schedule Item" OnClick="AddScheduleItem"/>
</MudMenu>
</div>
</div>
</MudPaper>
<div class="d-flex flex-column" style="height: 100vh; overflow-x: auto">
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
<MudText Typo="Typo.h5" Class="mb-2">Schedule Items</MudText>
<MudDivider Class="mb-6"/>
<MudTable T="ProgramScheduleItemEditViewModel"
Hover="true"
Items="_schedule?.Items?.OrderBy(i => i.Index)"
Dense="true"
SelectedItemChanged="@(vm => SelectedItemChanged(vm))"
RowClassFunc="@SelectedRowClassFunc">
<ColGroup>
<MudHidden Breakpoint="Breakpoint.Xs">
<col/>
<col/>
<col/>
<col style="width: 240px;"/>
</MudHidden>
</ColGroup>
<HeaderContent>
<MudTh>Start Time</MudTh>
<MudTh>Collection</MudTh>
<MudTh>Playout Mode</MudTh>
<MudTh/>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Start Time">
<MudText Typo="@Typo.body2">
@(context.StartType == StartType.Fixed ? context.StartTime == null ? string.Empty : DateTime.Today.Add(context.StartTime.Value).ToShortTimeString() : "Dynamic")
</MudText>
</MudTd>
<MudTd DataLabel="Collection">
<MudText Typo="@Typo.body2">
@context.CollectionName
</MudText>
</MudTd>
<MudTd DataLabel="Playout Mode">
<MudText Typo="@Typo.body2">
@context.PlayoutMode
@if (context.PlayoutMode == PlayoutMode.Multiple && context.MultipleCount.HasValue)
{
@($" ({context.MultipleCount})")
}
</MudText>
</MudTd>
<MudTd>
<div class="d-flex">
<MudIconButton Icon="@Icons.Material.Filled.ContentCopy"
OnClick="@(_ => CopyItem(context))">
</MudIconButton>
<MudIconButton Icon="@Icons.Material.Filled.ArrowUpward"
OnClick="@(_ => MoveItemUp(context))"
Disabled="@(_schedule.ShuffleScheduleItems || _schedule.Items.All(x => x.Index >= context.Index))">
</MudIconButton>
<MudIconButton Icon="@Icons.Material.Filled.ArrowDownward"
OnClick="@(_ => MoveItemDown(context))"
Disabled="@(_schedule.ShuffleScheduleItems || _schedule.Items.All(x => x.Index <= context.Index))">
</MudIconButton>
<MudIconButton Icon="@Icons.Material.Filled.Delete"
OnClick="@(_ => RemoveScheduleItem(context))">
</MudIconButton>
</div>
</MudTd>
</RowTemplate>
</MudTable>
@if (_selectedItem is not null)
{
<MudText Typo="Typo.h5" Class="mt-10 mb-2">Schedule Item</MudText>
<MudDivider Class="mb-6"/>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Start Type</MudText>
</div>
<MudSelect @bind-Value="_selectedItem.StartType" For="@(() => _selectedItem.StartType)">
<MudSelectItem Value="StartType.Dynamic">Dynamic</MudSelectItem>
@if (!_schedule.ShuffleScheduleItems)
{
<MudSelectItem Value="StartType.Fixed">Fixed</MudSelectItem>
}
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Start Time</MudText>
</div>
<MudTimePicker @bind-Time="@_selectedItem.StartTime"
For="@(() => _selectedItem.StartTime)"
Disabled="@(_selectedItem.StartType == StartType.Dynamic)"
Editable="true"/>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Fixed Start Time Behavior</MudText>
</div>
<MudSelect @bind-Value="_selectedItem.FixedStartTimeBehavior" For="@(() => _selectedItem.FixedStartTimeBehavior)" Disabled="@(_selectedItem.StartType is not StartType.Fixed)">
<MudSelectItem Value="@((FixedStartTimeBehavior?)null)">Inherit</MudSelectItem> <MudSelectItem Value="@((FixedStartTimeBehavior?)null)">Inherit</MudSelectItem>
<MudSelectItem T="FixedStartTimeBehavior?" Value="FixedStartTimeBehavior.Strict">Strict - Always Wait For Exact Start Time</MudSelectItem> <MudSelectItem T="FixedStartTimeBehavior?" Value="FixedStartTimeBehavior.Strict">Strict - Always Wait For Exact Start Time</MudSelectItem>
<MudSelectItem T="FixedStartTimeBehavior?" Value="FixedStartTimeBehavior.Flexible">Flexible - Start As Soon As Possible After Start Time</MudSelectItem> <MudSelectItem T="FixedStartTimeBehavior?" Value="FixedStartTimeBehavior.Flexible">Flexible - Start As Soon As Possible After Start Time</MudSelectItem>
</MudSelect> </MudSelect>
} </MudStack>
<MudSelect Class="mt-3" Label="Collection Type" @bind-Value="_selectedItem.CollectionType" For="@(() => _selectedItem.CollectionType)"> <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<MudSelectItem Value="ProgramScheduleItemCollectionType.Collection">Collection</MudSelectItem> <div class="d-flex">
<MudSelectItem Value="ProgramScheduleItemCollectionType.TelevisionShow">Television Show</MudSelectItem> <MudText>Collection Type</MudText>
<MudSelectItem Value="ProgramScheduleItemCollectionType.TelevisionSeason">Television Season</MudSelectItem> </div>
<MudSelectItem Value="ProgramScheduleItemCollectionType.Artist">Artist</MudSelectItem> <MudSelect @bind-Value="_selectedItem.CollectionType" For="@(() => _selectedItem.CollectionType)">
<MudSelectItem Value="ProgramScheduleItemCollectionType.MultiCollection">Multi Collection</MudSelectItem> <MudSelectItem Value="ProgramScheduleItemCollectionType.Collection">Collection</MudSelectItem>
<MudSelectItem Value="ProgramScheduleItemCollectionType.SmartCollection">Smart Collection</MudSelectItem> <MudSelectItem Value="ProgramScheduleItemCollectionType.TelevisionShow">Television Show</MudSelectItem>
<MudSelectItem Value="ProgramScheduleItemCollectionType.Playlist">Playlist</MudSelectItem> <MudSelectItem Value="ProgramScheduleItemCollectionType.TelevisionSeason">Television Season</MudSelectItem>
</MudSelect> <MudSelectItem Value="ProgramScheduleItemCollectionType.Artist">Artist</MudSelectItem>
<MudSelectItem Value="ProgramScheduleItemCollectionType.MultiCollection">Multi Collection</MudSelectItem>
<MudSelectItem Value="ProgramScheduleItemCollectionType.SmartCollection">Smart Collection</MudSelectItem>
<MudSelectItem Value="ProgramScheduleItemCollectionType.Playlist">Playlist</MudSelectItem>
</MudSelect>
</MudStack>
@if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.Collection) @if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.Collection)
{ {
<MudAutocomplete Class="mt-3" T="MediaCollectionViewModel" Label="Collection" <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
@bind-Value="_selectedItem.Collection" SearchFunc="@SearchCollections" <div class="d-flex">
ToStringFunc="@(c => c?.Name)" Placeholder="Type to search..."> <MudText>Collection</MudText>
<MoreItemsTemplate> </div>
<MudText Align="Align.Center" Class="px-4 py-1"> <MudAutocomplete T="MediaCollectionViewModel"
Only the first 10 items are shown @bind-Value="_selectedItem.Collection" SearchFunc="@SearchCollections"
</MudText> ToStringFunc="@(c => c?.Name)" Placeholder="Type to search...">
</MoreItemsTemplate> <MoreItemsTemplate>
</MudAutocomplete> <MudText Align="Align.Center" Class="px-4 py-1">
Only the first 10 items are shown
</MudText>
</MoreItemsTemplate>
</MudAutocomplete>
</MudStack>
} }
@if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.MultiCollection) @if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.MultiCollection)
{ {
<MudAutocomplete Class="mt-3" T="MultiCollectionViewModel" Label="Multi Collection" <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
@bind-Value="_selectedItem.MultiCollection" SearchFunc="@SearchMultiCollections" <div class="d-flex">
ToStringFunc="@(c => c?.Name)" Placeholder="Type to search..."> <MudText>Multi Collection</MudText>
<MoreItemsTemplate> </div>
<MudText Align="Align.Center" Class="px-4 py-1"> <MudAutocomplete T="MultiCollectionViewModel"
Only the first 10 items are shown @bind-Value="_selectedItem.MultiCollection" SearchFunc="@SearchMultiCollections"
</MudText> ToStringFunc="@(c => c?.Name)" Placeholder="Type to search...">
</MoreItemsTemplate> <MoreItemsTemplate>
</MudAutocomplete> <MudText Align="Align.Center" Class="px-4 py-1">
Only the first 10 items are shown
</MudText>
</MoreItemsTemplate>
</MudAutocomplete>
</MudStack>
} }
@if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.SmartCollection) @if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.SmartCollection)
{ {
<MudAutocomplete Class="mt-3" T="SmartCollectionViewModel" Label="Smart Collection" <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
@bind-Value="_selectedItem.SmartCollection" SearchFunc="@SearchSmartCollections" <div class="d-flex">
ToStringFunc="@(c => c?.Name)" Placeholder="Type to search..."> <MudText>Smart Collection</MudText>
<MoreItemsTemplate> </div>
<MudText Align="Align.Center" Class="px-4 py-1"> <MudAutocomplete T="SmartCollectionViewModel"
Only the first 10 items are shown @bind-Value="_selectedItem.SmartCollection" SearchFunc="@SearchSmartCollections"
</MudText> ToStringFunc="@(c => c?.Name)" Placeholder="Type to search...">
</MoreItemsTemplate> <MoreItemsTemplate>
</MudAutocomplete> <MudText Align="Align.Center" Class="px-4 py-1">
Only the first 10 items are shown
</MudText>
</MoreItemsTemplate>
</MudAutocomplete>
</MudStack>
} }
@if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.TelevisionShow) @if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.TelevisionShow)
{ {
<MudAutocomplete Class="mt-3" T="NamedMediaItemViewModel" Label="Television Show" <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
@bind-Value="_selectedItem.MediaItem" SearchFunc="@SearchTelevisionShows" <div class="d-flex">
ToStringFunc="@(c => c?.Name)" Placeholder="Type to search..."> <MudText>Television Show</MudText>
<MoreItemsTemplate> </div>
<MudText Align="Align.Center" Class="px-4 py-1"> <MudAutocomplete T="NamedMediaItemViewModel"
Only the first 10 items are shown @bind-Value="_selectedItem.MediaItem" SearchFunc="@SearchTelevisionShows"
</MudText> ToStringFunc="@(c => c?.Name)" Placeholder="Type to search...">
</MoreItemsTemplate> <MoreItemsTemplate>
</MudAutocomplete> <MudText Align="Align.Center" Class="px-4 py-1">
Only the first 10 items are shown
</MudText>
</MoreItemsTemplate>
</MudAutocomplete>
</MudStack>
} }
@if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.TelevisionSeason) @if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.TelevisionSeason)
{ {
<MudAutocomplete Class="mt-3" T="NamedMediaItemViewModel" Label="Television Season" <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
@bind-Value="_selectedItem.MediaItem" SearchFunc="@SearchTelevisionSeasons" <div class="d-flex">
ToStringFunc="@(c => c?.Name)" Placeholder="Type to search..." <MudText>Television Season</MudText>
MaxItems="20"> </div>
<MoreItemsTemplate> <MudAutocomplete T="NamedMediaItemViewModel"
<MudText Align="Align.Center" Class="px-4 py-1"> @bind-Value="_selectedItem.MediaItem" SearchFunc="@SearchTelevisionSeasons"
Only the first 20 items are shown ToStringFunc="@(c => c?.Name)" Placeholder="Type to search..."
</MudText> MaxItems="20">
</MoreItemsTemplate> <MoreItemsTemplate>
</MudAutocomplete> <MudText Align="Align.Center" Class="px-4 py-1">
Only the first 20 items are shown
</MudText>
</MoreItemsTemplate>
</MudAutocomplete>
</MudStack>
} }
@if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.Artist) @if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.Artist)
{ {
<MudAutocomplete Class="mt-3" T="NamedMediaItemViewModel" Label="Artist" <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
@bind-Value="_selectedItem.MediaItem" SearchFunc="@SearchArtists" <div class="d-flex">
ToStringFunc="@(c => c?.Name)" Placeholder="Type to search..." <MudText>Artist</MudText>
MaxItems="10"> </div>
<MoreItemsTemplate> <MudAutocomplete T="NamedMediaItemViewModel"
<MudText Align="Align.Center" Class="px-4 py-1"> @bind-Value="_selectedItem.MediaItem" SearchFunc="@SearchArtists"
Only the first 10 items are shown ToStringFunc="@(c => c?.Name)" Placeholder="Type to search..."
</MudText> MaxItems="10">
</MoreItemsTemplate> <MoreItemsTemplate>
</MudAutocomplete> <MudText Align="Align.Center" Class="px-4 py-1">
Only the first 10 items are shown
</MudText>
</MoreItemsTemplate>
</MudAutocomplete>
</MudStack>
} }
@if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.Playlist) @if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.Playlist)
{ {
<MudSelect Class="mt-3" <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
T="PlaylistGroupViewModel" <div class="d-flex">
Value="@_selectedPlaylistGroup" <MudText>Playlist Group</MudText>
Label="Playlist Group" </div>
ValueChanged="@(vm => UpdatePlaylistGroupItems(vm))"> <MudSelect T="PlaylistGroupViewModel"
@foreach (PlaylistGroupViewModel playlistGroup in _playlistGroups) Value="@_selectedPlaylistGroup"
ValueChanged="@(vm => UpdatePlaylistGroupItems(vm))">
@foreach (PlaylistGroupViewModel playlistGroup in _playlistGroups)
{
<MudSelectItem Value="@playlistGroup">@playlistGroup.Name</MudSelectItem>
}
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Playlist</MudText>
</div>
<MudSelect T="PlaylistViewModel" @bind-value="_selectedItem.Playlist">
@foreach (PlaylistViewModel playlist in _playlists)
{
<MudSelectItem Value="@playlist">@playlist.Name</MudSelectItem>
}
</MudSelect>
</MudStack>
}
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Playback Order</MudText>
</div>
<MudSelect @bind-Value="@_selectedItem.PlaybackOrder"
For="@(() => _selectedItem.PlaybackOrder)"
Disabled="@(_selectedItem.CollectionType is ProgramScheduleItemCollectionType.Playlist)">
@switch (_selectedItem.CollectionType)
{ {
<MudSelectItem Value="@playlistGroup">@playlistGroup.Name</MudSelectItem> case ProgramScheduleItemCollectionType.MultiCollection:
<MudSelectItem Value="PlaybackOrder.Shuffle">Shuffle</MudSelectItem>
<MudSelectItem Value="PlaybackOrder.ShuffleInOrder">Shuffle In Order</MudSelectItem>
break;
case ProgramScheduleItemCollectionType.Collection:
case ProgramScheduleItemCollectionType.SmartCollection:
<MudSelectItem Value="PlaybackOrder.Chronological">Chronological</MudSelectItem>
<MudSelectItem Value="PlaybackOrder.Random">Random</MudSelectItem>
<MudSelectItem Value="PlaybackOrder.Shuffle">Shuffle</MudSelectItem>
<MudSelectItem Value="PlaybackOrder.ShuffleInOrder">Shuffle In Order</MudSelectItem>
break;
case ProgramScheduleItemCollectionType.TelevisionShow:
<MudSelectItem Value="PlaybackOrder.Chronological">Chronological</MudSelectItem>
<MudSelectItem Value="PlaybackOrder.SeasonEpisode">Season, Episode</MudSelectItem>
<MudSelectItem Value="PlaybackOrder.Random">Random</MudSelectItem>
<MudSelectItem Value="PlaybackOrder.Shuffle">Shuffle</MudSelectItem>
<MudSelectItem Value="PlaybackOrder.MultiEpisodeShuffle">Multi-Episode Shuffle</MudSelectItem>
break;
case ProgramScheduleItemCollectionType.Playlist:
break;
default:
<MudSelectItem Value="PlaybackOrder.Chronological">Chronological</MudSelectItem>
<MudSelectItem Value="PlaybackOrder.Random">Random</MudSelectItem>
<MudSelectItem Value="PlaybackOrder.Shuffle">Shuffle</MudSelectItem>
break;
} }
</MudSelect> </MudSelect>
<MudSelect Class="mt-3" </MudStack>
T="PlaylistViewModel" <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
Label="Playlist" <div class="d-flex">
@bind-value="_selectedItem.Playlist"> <MudText>Playout Mode</MudText>
@foreach (PlaylistViewModel playlist in _playlists) </div>
<MudSelect @bind-Value="@_selectedItem.PlayoutMode" For="@(() => _selectedItem.PlayoutMode)">
@if (!_schedule.ShuffleScheduleItems)
{ {
<MudSelectItem Value="@playlist">@playlist.Name</MudSelectItem> <MudSelectItem Value="PlayoutMode.Flood">Flood</MudSelectItem>
} }
<MudSelectItem Value="PlayoutMode.One">One</MudSelectItem>
<MudSelectItem Value="PlayoutMode.Multiple">Multiple</MudSelectItem>
<MudSelectItem Value="PlayoutMode.Duration">Duration</MudSelectItem>
</MudSelect> </MudSelect>
} </MudStack>
<MudSelect Class="mt-3" <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
Label="Playback Order" <div class="d-flex">
@bind-Value="@_selectedItem.PlaybackOrder" <MudText>Multiple Count</MudText>
For="@(() => _selectedItem.PlaybackOrder)" </div>
Disabled="@(_selectedItem.CollectionType is ProgramScheduleItemCollectionType.Playlist)"> <MudTextField @bind-Value="@_selectedItem.MultipleCount"
@switch (_selectedItem.CollectionType) For="@(() => _selectedItem.MultipleCount)"
{ Disabled="@(_selectedItem.PlayoutMode != PlayoutMode.Multiple)"/>
case ProgramScheduleItemCollectionType.MultiCollection: </MudStack>
<MudSelectItem Value="PlaybackOrder.Shuffle">Shuffle</MudSelectItem> <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<MudSelectItem Value="PlaybackOrder.ShuffleInOrder">Shuffle In Order</MudSelectItem> <div class="d-flex">
break; <MudText>Playout Duration</MudText>
case ProgramScheduleItemCollectionType.Collection: </div>
case ProgramScheduleItemCollectionType.SmartCollection: <MudTextField T="int"
<MudSelectItem Value="PlaybackOrder.Chronological">Chronological</MudSelectItem> @bind-Value="_selectedItem.PlayoutDurationHours"
<MudSelectItem Value="PlaybackOrder.Random">Random</MudSelectItem> Adornment="Adornment.End"
<MudSelectItem Value="PlaybackOrder.Shuffle">Shuffle</MudSelectItem> AdornmentText="hours"
<MudSelectItem Value="PlaybackOrder.ShuffleInOrder">Shuffle In Order</MudSelectItem> Disabled="@(_selectedItem.PlayoutMode != PlayoutMode.Duration)"/>
break; </MudStack>
case ProgramScheduleItemCollectionType.TelevisionShow: <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<MudSelectItem Value="PlaybackOrder.Chronological">Chronological</MudSelectItem> <div class="d-flex"></div>
<MudSelectItem Value="PlaybackOrder.SeasonEpisode">Season, Episode</MudSelectItem> <MudTextField T="int"
<MudSelectItem Value="PlaybackOrder.Random">Random</MudSelectItem> @bind-Value="_selectedItem.PlayoutDurationMinutes"
<MudSelectItem Value="PlaybackOrder.Shuffle">Shuffle</MudSelectItem> Adornment="Adornment.End"
<MudSelectItem Value="PlaybackOrder.MultiEpisodeShuffle">Multi-Episode Shuffle</MudSelectItem> AdornmentText="minutes"
break; Disabled="@(_selectedItem.PlayoutMode != PlayoutMode.Duration)"/>
case ProgramScheduleItemCollectionType.Playlist: </MudStack>
break; <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
default: <div class="d-flex">
<MudSelectItem Value="PlaybackOrder.Chronological">Chronological</MudSelectItem> <MudText>Fill With Group Mode (Show or Artist)</MudText>
<MudSelectItem Value="PlaybackOrder.Random">Random</MudSelectItem> </div>
<MudSelectItem Value="PlaybackOrder.Shuffle">Shuffle</MudSelectItem> <MudSelect @bind-Value="@_selectedItem.FillWithGroupMode"
break; For="@(() => _selectedItem.FillWithGroupMode)"
} Disabled="@(_selectedItem.CanFillWithGroups == false)">
</MudSelect> <MudSelectItem Value="FillWithGroupMode.None">(none)</MudSelectItem>
<MudSelect Class="mt-3" Label="Playout Mode" @bind-Value="@_selectedItem.PlayoutMode" For="@(() => _selectedItem.PlayoutMode)"> @if (_selectedItem.CanFillWithGroups)
@if (!_schedule.ShuffleScheduleItems) {
{ <MudSelectItem Value="FillWithGroupMode.FillWithOrderedGroups">Ordered Groups</MudSelectItem>
<MudSelectItem Value="PlayoutMode.Flood">Flood</MudSelectItem> <MudSelectItem Value="FillWithGroupMode.FillWithShuffledGroups">Shuffled Groups</MudSelectItem>
} }
<MudSelectItem Value="PlayoutMode.One">One</MudSelectItem> else
<MudSelectItem Value="PlayoutMode.Multiple">Multiple</MudSelectItem> {
<MudSelectItem Value="PlayoutMode.Duration">Duration</MudSelectItem> _selectedItem.FillWithGroupMode = FillWithGroupMode.None;
</MudSelect> }
<MudTextField Class="mt-3" Label="Multiple Count" @bind-Value="@_selectedItem.MultipleCount" For="@(() => _selectedItem.MultipleCount)" Disabled="@(_selectedItem.PlayoutMode != PlayoutMode.Multiple)"/> </MudSelect>
<MudGrid Class="mt-3" Style="align-items: center" Justify="Justify.Center"> </MudStack>
<MudItem xs="6"> <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<MudTextField T="int" <div class="d-flex">
Label="Playout Duration" <MudText>Tail Mode</MudText>
@bind-Value="_selectedItem.PlayoutDurationHours" </div>
Adornment="Adornment.End" <MudSelect @bind-Value="@_selectedItem.TailMode"
AdornmentText="hours" For="@(() => _selectedItem.TailMode)"
Disabled="@(_selectedItem.PlayoutMode != PlayoutMode.Duration)"/> Disabled="@(_selectedItem.PlayoutMode != PlayoutMode.Duration)">
</MudItem> <MudSelectItem Value="@TailMode.None">(none)</MudSelectItem>
<MudItem xs="6"> <MudSelectItem Value="@TailMode.Offline">Offline</MudSelectItem>
<MudTextField T="int" <MudSelectItem Value="@TailMode.Filler">Filler</MudSelectItem>
@bind-Value="_selectedItem.PlayoutDurationMinutes" </MudSelect>
Adornment="Adornment.End" </MudStack>
AdornmentText="minutes" <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
Disabled="@(_selectedItem.PlayoutMode != PlayoutMode.Duration)"/> <div class="d-flex">
</MudItem> <MudText>Discard To Fill Attempts</MudText>
</MudGrid> </div>
<MudSelect Class="mt-3" Label="Fill With Group Mode (Show or Artist)" @bind-Value="@_selectedItem.FillWithGroupMode" For="@(() => _selectedItem.FillWithGroupMode)" Disabled="@(_selectedItem.CanFillWithGroups == false)"> <MudTextField @bind-Value="@_selectedItem.DiscardToFillAttempts"
<MudSelectItem Value="FillWithGroupMode.None">(none)</MudSelectItem> For="@(() => _selectedItem.DiscardToFillAttempts)"
@if (_selectedItem.CanFillWithGroups) Disabled="@(_selectedItem.PlayoutMode != PlayoutMode.Duration)"/>
{ </MudStack>
<MudSelectItem Value="FillWithGroupMode.FillWithOrderedGroups">Ordered Groups</MudSelectItem> <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<MudSelectItem Value="FillWithGroupMode.FillWithShuffledGroups">Shuffled Groups</MudSelectItem> <div class="d-flex">
} <MudText>Custom Title</MudText>
else </div>
{ <MudTextField @bind-Value="@_selectedItem.CustomTitle" For="@(() => _selectedItem.CustomTitle)"/>
_selectedItem.FillWithGroupMode = FillWithGroupMode.None; </MudStack>
} <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
</MudSelect> <div class="d-flex">
<MudSelect Class="mt-3" Label="Tail Mode" @bind-Value="@_selectedItem.TailMode" For="@(() => _selectedItem.TailMode)" Disabled="@(_selectedItem.PlayoutMode != PlayoutMode.Duration)"> <MudText>Guide Mode</MudText>
<MudSelectItem Value="@TailMode.None">(none)</MudSelectItem> </div>
<MudSelectItem Value="@TailMode.Offline">Offline</MudSelectItem> <MudSelect @bind-Value="@_selectedItem.GuideMode" For="@(() => _selectedItem.GuideMode)">
<MudSelectItem Value="@TailMode.Filler">Filler</MudSelectItem> <MudSelectItem Value="@GuideMode.Normal">Normal</MudSelectItem>
</MudSelect> <MudSelectItem Value="@GuideMode.Filler">Filler</MudSelectItem>
<MudTextField Class="mt-3" Label="Discard To Fill Attempts" @bind-Value="@_selectedItem.DiscardToFillAttempts" For="@(() => _selectedItem.DiscardToFillAttempts)" Disabled="@(_selectedItem.PlayoutMode != PlayoutMode.Duration)"/> </MudSelect>
<MudTextField Class="mt-3" Label="Custom Title" @bind-Value="@_selectedItem.CustomTitle" For="@(() => _selectedItem.CustomTitle)"/> </MudStack>
<MudSelect Class="mt-3" Label="Guide Mode" @bind-Value="@_selectedItem.GuideMode" For="@(() => _selectedItem.GuideMode)"> <MudText Typo="Typo.h5" Class="mt-10 mb-2">Schedule Item Filler</MudText>
<MudSelectItem Value="@GuideMode.Normal">Normal</MudSelectItem> <MudDivider Class="mb-6"/>
<MudSelectItem Value="@GuideMode.Filler">Filler</MudSelectItem> <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
</MudSelect> <div class="d-flex">
</MudCardContent> <MudText>Pre-Roll Filler</MudText>
</MudCard> </div>
</div> <MudSelect T="FillerPresetViewModel"
<div style="flex-grow: 1; max-width: 400px;"> @bind-value="_selectedItem.PreRollFiller"
<MudCard> Clearable="true">
<MudCardContent> @foreach (FillerPresetViewModel filler in _fillerPresets.Where(f => f.FillerKind == FillerKind.PreRoll))
<MudSelect T="FillerPresetViewModel" {
Label="Pre-Roll Filler" <MudSelectItem Value="@filler">@filler.Name</MudSelectItem>
@bind-value="_selectedItem.PreRollFiller" }
Clearable="true"> </MudSelect>
@foreach (FillerPresetViewModel filler in _fillerPresets.Where(f => f.FillerKind == FillerKind.PreRoll)) </MudStack>
{ <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<MudSelectItem Value="@filler">@filler.Name</MudSelectItem> <div class="d-flex">
} <MudText>Mid-Roll Filler</MudText>
</MudSelect> </div>
<MudSelect Class="mt-3" <MudSelect T="FillerPresetViewModel"
T="FillerPresetViewModel" @bind-value="_selectedItem.MidRollFiller"
Label="Mid-Roll Filler" Clearable="true">
@bind-value="_selectedItem.MidRollFiller" @foreach (FillerPresetViewModel filler in _fillerPresets.Where(f => f.FillerKind == FillerKind.MidRoll))
Clearable="true"> {
@foreach (FillerPresetViewModel filler in _fillerPresets.Where(f => f.FillerKind == FillerKind.MidRoll)) <MudSelectItem Value="@filler">@filler.Name</MudSelectItem>
{ }
<MudSelectItem Value="@filler">@filler.Name</MudSelectItem> </MudSelect>
} </MudStack>
</MudSelect> <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<MudSelect Class="mt-3" <div class="d-flex">
T="FillerPresetViewModel" <MudText>Post-Roll Filler</MudText>
Label="Post-Roll Filler" </div>
@bind-value="_selectedItem.PostRollFiller" <MudSelect T="FillerPresetViewModel"
Clearable="true"> @bind-value="_selectedItem.PostRollFiller"
@foreach (FillerPresetViewModel filler in _fillerPresets.Where(f => f.FillerKind == FillerKind.PostRoll)) Clearable="true">
{ @foreach (FillerPresetViewModel filler in _fillerPresets.Where(f => f.FillerKind == FillerKind.PostRoll))
<MudSelectItem Value="@filler">@filler.Name</MudSelectItem> {
} <MudSelectItem Value="@filler">@filler.Name</MudSelectItem>
</MudSelect> }
<MudSelect Class="mt-3" </MudSelect>
T="FillerPresetViewModel" </MudStack>
Label="Tail Filler" <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
@bind-value="_selectedItem.TailFiller" <div class="d-flex">
Clearable="true"> <MudText>Tail Filler</MudText>
@foreach (FillerPresetViewModel filler in _fillerPresets.Where(f => f.FillerKind == FillerKind.Tail)) </div>
{ <MudSelect T="FillerPresetViewModel"
<MudSelectItem Value="@filler">@filler.Name</MudSelectItem> @bind-value="_selectedItem.TailFiller"
} Clearable="true">
</MudSelect> @foreach (FillerPresetViewModel filler in _fillerPresets.Where(f => f.FillerKind == FillerKind.Tail))
<MudSelect Class="mt-3" {
T="FillerPresetViewModel" <MudSelectItem Value="@filler">@filler.Name</MudSelectItem>
Label="Fallback Filler" }
@bind-value="_selectedItem.FallbackFiller" </MudSelect>
Clearable="true"> </MudStack>
@foreach (FillerPresetViewModel filler in _fillerPresets.Where(f => f.FillerKind == FillerKind.Fallback)) <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
{ <div class="d-flex">
<MudSelectItem Value="@filler">@filler.Name</MudSelectItem> <MudText>Fallback Filler</MudText>
} </div>
</MudSelect> <MudSelect T="FillerPresetViewModel"
</MudCardContent> @bind-value="_selectedItem.FallbackFiller"
</MudCard> Clearable="true">
<MudCard Class="mt-4"> @foreach (FillerPresetViewModel filler in _fillerPresets.Where(f => f.FillerKind == FillerKind.Fallback))
<MudCardContent> {
<MudSelect Label="Watermark" @bind-Value="@_selectedItem.Watermark" For="@(() => _selectedItem.Watermark)" Clearable="true"> <MudSelectItem Value="@filler">@filler.Name</MudSelectItem>
@foreach (WatermarkViewModel watermark in _watermarks) }
{ </MudSelect>
<MudSelectItem Value="@watermark">@watermark.Name</MudSelectItem> </MudStack>
} <MudText Typo="Typo.h5" Class="mt-10 mb-2">Schedule Item Overrides</MudText>
</MudSelect> <MudDivider Class="mb-6"/>
<MudSelect Class="mt-3" <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
Label="Preferred Audio Language" <div class="d-flex">
@bind-Value="_selectedItem.PreferredAudioLanguageCode" <MudText>Watermark</MudText>
For="@(() => _selectedItem.PreferredAudioLanguageCode)" </div>
Clearable="true"> <MudSelect @bind-Value="@_selectedItem.Watermark" For="@(() => _selectedItem.Watermark)" Clearable="true">
<MudSelectItem Value="@((string)null)">(none)</MudSelectItem> @foreach (WatermarkViewModel watermark in _watermarks)
@foreach (LanguageCodeViewModel culture in _availableCultures) {
{ <MudSelectItem Value="@watermark">@watermark.Name</MudSelectItem>
<MudSelectItem Value="@culture.ThreeLetterISOLanguageName">@culture.EnglishName</MudSelectItem> }
} </MudSelect>
</MudSelect> </MudStack>
<MudTextField Class="mt-3" Label="Preferred Audio Title" @bind-Value="@_selectedItem.PreferredAudioTitle" For="@(() => _selectedItem.PreferredAudioTitle)"/> <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<MudSelect Class="mt-3" <div class="d-flex">
Label="Preferred Subtitle Language" <MudText>Preferred Audio Language</MudText>
@bind-Value="_selectedItem.PreferredSubtitleLanguageCode" </div>
For="@(() => _selectedItem.PreferredSubtitleLanguageCode)" <MudSelect @bind-Value="_selectedItem.PreferredAudioLanguageCode"
Clearable="true"> For="@(() => _selectedItem.PreferredAudioLanguageCode)"
<MudSelectItem Value="@((string)null)">(none)</MudSelectItem> Clearable="true">
@foreach (LanguageCodeViewModel culture in _availableCultures) <MudSelectItem Value="@((string)null)">(none)</MudSelectItem>
{ @foreach (LanguageCodeViewModel culture in _availableCultures)
<MudSelectItem Value="@culture.ThreeLetterISOLanguageName">@culture.EnglishName</MudSelectItem> {
} <MudSelectItem Value="@culture.ThreeLetterISOLanguageName">@culture.EnglishName</MudSelectItem>
</MudSelect> }
<MudSelect T="ChannelSubtitleMode?" Class="mt-3" Label="Subtitle Mode" @bind-Value="_selectedItem.SubtitleMode" For="@(() => _selectedItem.SubtitleMode)" Clearable="true"> </MudSelect>
<MudSelectItem T="ChannelSubtitleMode?" Value="@(ChannelSubtitleMode.None)">None</MudSelectItem> </MudStack>
<MudSelectItem T="ChannelSubtitleMode?" Value="@(ChannelSubtitleMode.Forced)">Forced</MudSelectItem> <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<MudSelectItem T="ChannelSubtitleMode?" Value="@(ChannelSubtitleMode.Default)">Default</MudSelectItem> <div class="d-flex">
<MudSelectItem T="ChannelSubtitleMode?" Value="@(ChannelSubtitleMode.Any)">Any</MudSelectItem> <MudText>Preferred Audio Title</MudText>
</MudSelect> </div>
</MudCardContent> <MudTextField @bind-Value="@_selectedItem.PreferredAudioTitle" For="@(() => _selectedItem.PreferredAudioTitle)"/>
</MudCard> </MudStack>
</div> <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Preferred Subtitle Language</MudText>
</div>
<MudSelect @bind-Value="_selectedItem.PreferredSubtitleLanguageCode"
For="@(() => _selectedItem.PreferredSubtitleLanguageCode)"
Clearable="true">
<MudSelectItem Value="@((string)null)">(none)</MudSelectItem>
@foreach (LanguageCodeViewModel culture in _availableCultures)
{
<MudSelectItem Value="@culture.ThreeLetterISOLanguageName">@culture.EnglishName</MudSelectItem>
}
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Subtitle Mode</MudText>
</div>
<MudSelect T="ChannelSubtitleMode?" @bind-Value="_selectedItem.SubtitleMode" For="@(() => _selectedItem.SubtitleMode)" Clearable="true">
<MudSelectItem T="ChannelSubtitleMode?" Value="@(ChannelSubtitleMode.None)">None</MudSelectItem>
<MudSelectItem T="ChannelSubtitleMode?" Value="@(ChannelSubtitleMode.Forced)">Forced</MudSelectItem>
<MudSelectItem T="ChannelSubtitleMode?" Value="@(ChannelSubtitleMode.Default)">Default</MudSelectItem>
<MudSelectItem T="ChannelSubtitleMode?" Value="@(ChannelSubtitleMode.Any)">Any</MudSelectItem>
</MudSelect>
</MudStack>
}
</MudContainer>
</div> </div>
</EditForm> </MudForm>
}
</MudContainer>
@code { @code {
private readonly CancellationTokenSource _cts = new(); private readonly CancellationTokenSource _cts = new();
@ -429,6 +572,9 @@
private PlaylistGroupViewModel _selectedPlaylistGroup; private PlaylistGroupViewModel _selectedPlaylistGroup;
private ProgramScheduleItemEditViewModel _selectedItem; private ProgramScheduleItemEditViewModel _selectedItem;
private MudForm _form;
private bool _success;
public void Dispose() public void Dispose()
{ {
_cts.Cancel(); _cts.Cancel();
@ -675,6 +821,12 @@
private async Task SaveChanges() private async Task SaveChanges()
{ {
await _form.Validate();
if (!_success)
{
return;
}
var items = _schedule.Items.Map(item => new ReplaceProgramScheduleItem( var items = _schedule.Items.Map(item => new ReplaceProgramScheduleItem(
item.Index, item.Index,
item.StartType, item.StartType,

Loading…
Cancel
Save