스케줄에
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tornado3_2026Election.Common;
|
||||
using Tornado3_2026Election.Domain;
|
||||
@@ -17,9 +18,11 @@ public sealed class ChannelScheduleViewModel : ObservableObject
|
||||
private readonly LogService _logService;
|
||||
private readonly IReadOnlyList<FormatTemplateDefinition> _allFormats;
|
||||
private FormatTemplateDefinition? _selectedFormat;
|
||||
private ScheduleRegionOption? _selectedRegionOption;
|
||||
private SelectionOption<EmptyScheduleBehavior>? _selectedEmptyBehaviorOption;
|
||||
private bool _loopEnabled;
|
||||
private EmptyScheduleBehavior _emptyScheduleBehavior = EmptyScheduleBehavior.ImmediateOut;
|
||||
private int _regionOptionsRevision;
|
||||
|
||||
public ChannelScheduleViewModel(
|
||||
BroadcastChannel channel,
|
||||
@@ -38,6 +41,7 @@ public sealed class ChannelScheduleViewModel : ObservableObject
|
||||
_logService = logService;
|
||||
_allFormats = formats.ToArray();
|
||||
AvailableFormats = new ObservableCollection<FormatTemplateDefinition>();
|
||||
RegionOptions = new ObservableCollection<ScheduleRegionOption>();
|
||||
EmptyBehaviorOptions =
|
||||
[
|
||||
new SelectionOption<EmptyScheduleBehavior>(EmptyScheduleBehavior.ImmediateOut, "즉시 아웃"),
|
||||
@@ -62,6 +66,7 @@ public sealed class ChannelScheduleViewModel : ObservableObject
|
||||
_data.PropertyChanged += Data_PropertyChanged;
|
||||
|
||||
RebuildAvailableFormats();
|
||||
_ = RebuildRegionOptionsAsync();
|
||||
RefreshSummary();
|
||||
}
|
||||
|
||||
@@ -83,6 +88,8 @@ public sealed class ChannelScheduleViewModel : ObservableObject
|
||||
|
||||
public ObservableCollection<FormatTemplateDefinition> AvailableFormats { get; }
|
||||
|
||||
public ObservableCollection<ScheduleRegionOption> RegionOptions { get; }
|
||||
|
||||
public IReadOnlyList<SelectionOption<EmptyScheduleBehavior>> EmptyBehaviorOptions { get; }
|
||||
|
||||
public ObservableCollection<ChannelScheduleItem> Queue { get; }
|
||||
@@ -112,10 +119,20 @@ public sealed class ChannelScheduleViewModel : ObservableObject
|
||||
{
|
||||
if (SetProperty(ref _selectedFormat, value))
|
||||
{
|
||||
if (AddFormatCommand is not null)
|
||||
{
|
||||
AddFormatCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
_ = RebuildRegionOptionsAsync();
|
||||
AddFormatCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ScheduleRegionOption? SelectedRegionOption
|
||||
{
|
||||
get => _selectedRegionOption;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _selectedRegionOption, value))
|
||||
{
|
||||
AddFormatCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -181,9 +198,9 @@ public sealed class ChannelScheduleViewModel : ObservableObject
|
||||
? "송출 중"
|
||||
: "대기";
|
||||
|
||||
public string CurrentItemName => Queue.FirstOrDefault(item => item.State is ScheduleQueueItemState.OnAir or ScheduleQueueItemState.Sending)?.FormatName ?? "대기 화면";
|
||||
public string CurrentItemName => Queue.FirstOrDefault(item => item.State is ScheduleQueueItemState.OnAir or ScheduleQueueItemState.Sending)?.DisplayName ?? "대기 화면";
|
||||
|
||||
public string NextItemName => Queue.FirstOrDefault(item => item.State == ScheduleQueueItemState.Next)?.FormatName ?? "다음 컷 없음";
|
||||
public string NextItemName => Queue.FirstOrDefault(item => item.State == ScheduleQueueItemState.Next)?.DisplayName ?? "다음 컷 없음";
|
||||
|
||||
public int QueuedItemCount => Queue.Count(item => item.State == ScheduleQueueItemState.Queued);
|
||||
|
||||
@@ -193,8 +210,8 @@ public sealed class ChannelScheduleViewModel : ObservableObject
|
||||
{
|
||||
get
|
||||
{
|
||||
var current = Queue.FirstOrDefault(item => item.State is ScheduleQueueItemState.OnAir or ScheduleQueueItemState.Sending)?.FormatName ?? "-";
|
||||
var next = Queue.FirstOrDefault(item => item.State == ScheduleQueueItemState.Next)?.FormatName ?? "-";
|
||||
var current = Queue.FirstOrDefault(item => item.State is ScheduleQueueItemState.OnAir or ScheduleQueueItemState.Sending)?.DisplayName ?? "-";
|
||||
var next = Queue.FirstOrDefault(item => item.State == ScheduleQueueItemState.Next)?.DisplayName ?? "-";
|
||||
return $"현재 {current} / 다음 {next} / 대기 {Queue.Count(item => item.State == ScheduleQueueItemState.Queued)}";
|
||||
}
|
||||
}
|
||||
@@ -205,6 +222,11 @@ public sealed class ChannelScheduleViewModel : ObservableObject
|
||||
|
||||
public string OperatorQuickSummary => $"{AdapterStateLabel} / {LoopSummary} / 빈 스케줄 {EmptyBehaviorLabel}";
|
||||
|
||||
public async Task RefreshRegionOptionsAsync()
|
||||
{
|
||||
await RebuildRegionOptionsAsync();
|
||||
}
|
||||
|
||||
private async Task StartAsync()
|
||||
{
|
||||
await _engine.StartAsync().ConfigureAwait(false);
|
||||
@@ -239,13 +261,14 @@ public sealed class ChannelScheduleViewModel : ObservableObject
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_data.ValidateForFormat(SelectedFormat, out var validationError))
|
||||
var regionOption = SelectedRegionOption ?? RegionOptions.FirstOrDefault();
|
||||
if (regionOption is null)
|
||||
{
|
||||
_logService.Warning($"[{Title}] {validationError}");
|
||||
_logService.Warning($"[{Title}] 선택 가능한 지역 정보가 아직 준비되지 않았습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
_engine.Enqueue(ChannelScheduleItem.FromTemplate(SelectedFormat));
|
||||
_engine.Enqueue(ChannelScheduleItem.FromTemplate(SelectedFormat, regionOption));
|
||||
RefreshSummary();
|
||||
}
|
||||
|
||||
@@ -307,18 +330,19 @@ public sealed class ChannelScheduleViewModel : ObservableObject
|
||||
|
||||
private bool CanAddFormat()
|
||||
{
|
||||
return SelectedFormat is not null && SelectedFormat.IsAvailableInPhase(_data.BroadcastPhase);
|
||||
return SelectedFormat is not null &&
|
||||
SelectedFormat.IsAvailableInPhase(_data.BroadcastPhase) &&
|
||||
SelectedRegionOption is not null;
|
||||
}
|
||||
|
||||
private void Data_PropertyChanged(object? sender, PropertyChangedEventArgs args)
|
||||
{
|
||||
if (args.PropertyName != nameof(DataViewModel.BroadcastPhase))
|
||||
if (args.PropertyName is nameof(DataViewModel.BroadcastPhase) or nameof(DataViewModel.ElectionType))
|
||||
{
|
||||
return;
|
||||
RebuildAvailableFormats();
|
||||
_ = RebuildRegionOptionsAsync();
|
||||
RefreshSummary();
|
||||
}
|
||||
|
||||
RebuildAvailableFormats();
|
||||
RefreshSummary();
|
||||
}
|
||||
|
||||
private void RebuildAvailableFormats()
|
||||
@@ -342,6 +366,64 @@ public sealed class ChannelScheduleViewModel : ObservableObject
|
||||
OnPropertyChanged(nameof(QueueFootnote));
|
||||
}
|
||||
|
||||
private async Task RebuildRegionOptionsAsync()
|
||||
{
|
||||
var revision = Interlocked.Increment(ref _regionOptionsRevision);
|
||||
var selectedFormat = SelectedFormat;
|
||||
var previousSelection = SelectedRegionOption;
|
||||
|
||||
if (selectedFormat is null)
|
||||
{
|
||||
RegionOptions.Clear();
|
||||
SelectedRegionOption = null;
|
||||
AddFormatCommand.NotifyCanExecuteChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
var options = await _data.GetScheduleRegionOptionsAsync(selectedFormat);
|
||||
if (revision != _regionOptionsRevision)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RegionOptions.Clear();
|
||||
foreach (var option in options)
|
||||
{
|
||||
RegionOptions.Add(option);
|
||||
}
|
||||
|
||||
SelectedRegionOption = ResolvePreferredRegionOption(options, previousSelection);
|
||||
AddFormatCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
|
||||
private static ScheduleRegionOption? ResolvePreferredRegionOption(
|
||||
IReadOnlyList<ScheduleRegionOption> options,
|
||||
ScheduleRegionOption? previousSelection)
|
||||
{
|
||||
if (options.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (previousSelection is null)
|
||||
{
|
||||
return options[0];
|
||||
}
|
||||
|
||||
if (previousSelection.Scope == ScheduleRegionScope.Single)
|
||||
{
|
||||
var matchedSingle = options.FirstOrDefault(option =>
|
||||
option.Scope == ScheduleRegionScope.Single &&
|
||||
string.Equals(option.DistrictCode, previousSelection.DistrictCode, System.StringComparison.OrdinalIgnoreCase));
|
||||
if (matchedSingle is not null)
|
||||
{
|
||||
return matchedSingle;
|
||||
}
|
||||
}
|
||||
|
||||
return options.FirstOrDefault(option => option.Scope == previousSelection.Scope) ?? options[0];
|
||||
}
|
||||
|
||||
private SelectionOption<EmptyScheduleBehavior>? FindEmptyBehaviorOption(EmptyScheduleBehavior behavior)
|
||||
{
|
||||
return EmptyBehaviorOptions.FirstOrDefault(option => option.Value == behavior);
|
||||
|
||||
Reference in New Issue
Block a user