중간 과정 진행 후 커밋
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Tornado3_2026Election.Common;
|
||||
@@ -12,7 +13,9 @@ public sealed class ChannelScheduleViewModel : ObservableObject
|
||||
{
|
||||
private readonly ChannelScheduleEngine _engine;
|
||||
private readonly ITornado3Adapter _adapter;
|
||||
private readonly DataViewModel _data;
|
||||
private readonly LogService _logService;
|
||||
private readonly IReadOnlyList<FormatTemplateDefinition> _allFormats;
|
||||
private FormatTemplateDefinition? _selectedFormat;
|
||||
private SelectionOption<EmptyScheduleBehavior>? _selectedEmptyBehaviorOption;
|
||||
private bool _loopEnabled;
|
||||
@@ -22,16 +25,19 @@ public sealed class ChannelScheduleViewModel : ObservableObject
|
||||
BroadcastChannel channel,
|
||||
string title,
|
||||
IReadOnlyList<FormatTemplateDefinition> formats,
|
||||
DataViewModel data,
|
||||
ITornado3Adapter adapter,
|
||||
ChannelScheduleEngine engine,
|
||||
LogService logService)
|
||||
{
|
||||
Channel = channel;
|
||||
Title = title;
|
||||
_data = data;
|
||||
_adapter = adapter;
|
||||
_engine = engine;
|
||||
_logService = logService;
|
||||
AvailableFormats = new ObservableCollection<FormatTemplateDefinition>(formats);
|
||||
_allFormats = formats.ToArray();
|
||||
AvailableFormats = new ObservableCollection<FormatTemplateDefinition>();
|
||||
EmptyBehaviorOptions =
|
||||
[
|
||||
new SelectionOption<EmptyScheduleBehavior>(EmptyScheduleBehavior.ImmediateOut, "즉시 아웃"),
|
||||
@@ -42,17 +48,20 @@ public sealed class ChannelScheduleViewModel : ObservableObject
|
||||
StartCommand = new AsyncRelayCommand(StartAsync);
|
||||
StopCommand = new AsyncRelayCommand(StopAsync);
|
||||
ForceNextCommand = new AsyncRelayCommand(ForceNextAsync);
|
||||
AddFormatCommand = new RelayCommand(AddFormat, () => SelectedFormat is not null);
|
||||
AddFormatCommand = new RelayCommand(AddFormat, CanAddFormat);
|
||||
ResetQueueCommand = new RelayCommand(ResetQueue);
|
||||
RemoveItemCommand = new RelayCommand<ChannelScheduleItem>(RemoveItem);
|
||||
MoveUpCommand = new RelayCommand<ChannelScheduleItem>(MoveUp);
|
||||
MoveDownCommand = new RelayCommand<ChannelScheduleItem>(MoveDown);
|
||||
PromoteToNextCommand = new RelayCommand<ChannelScheduleItem>(PromoteToNext);
|
||||
SelectedFormat = AvailableFormats.FirstOrDefault();
|
||||
SelectedEmptyBehaviorOption = FindEmptyBehaviorOption(_emptyScheduleBehavior);
|
||||
|
||||
_engine.QueueChanged += (_, _) => RefreshSummary();
|
||||
_adapter.StateChanged += (_, _) => RefreshSummary();
|
||||
_adapter.ConnectionChanged += (_, _) => RefreshSummary();
|
||||
_data.PropertyChanged += Data_PropertyChanged;
|
||||
|
||||
RebuildAvailableFormats();
|
||||
RefreshSummary();
|
||||
}
|
||||
|
||||
@@ -60,6 +69,18 @@ public sealed class ChannelScheduleViewModel : ObservableObject
|
||||
|
||||
public string Title { get; }
|
||||
|
||||
public bool IsLiveCg => _adapter.IsLiveCg;
|
||||
|
||||
public bool IsCgConnected => _adapter.IsConnected;
|
||||
|
||||
public string CgBackendName => _adapter.BackendName;
|
||||
|
||||
public string CgConnectionTarget => _adapter.ConnectionTarget;
|
||||
|
||||
public string CgStatusSummary => IsLiveCg
|
||||
? $"{(IsCgConnected ? "Connected" : "Disconnected")} / {CgBackendName} / {CgConnectionTarget}"
|
||||
: $"Disconnected / {CgBackendName} / {CgConnectionTarget}";
|
||||
|
||||
public ObservableCollection<FormatTemplateDefinition> AvailableFormats { get; }
|
||||
|
||||
public IReadOnlyList<SelectionOption<EmptyScheduleBehavior>> EmptyBehaviorOptions { get; }
|
||||
@@ -162,11 +183,11 @@ public sealed class ChannelScheduleViewModel : ObservableObject
|
||||
|
||||
public string CurrentItemName => Queue.FirstOrDefault(item => item.State is ScheduleQueueItemState.OnAir or ScheduleQueueItemState.Sending)?.FormatName ?? "대기 화면";
|
||||
|
||||
public string NextItemName => Queue.FirstOrDefault(item => item.State == ScheduleQueueItemState.Next)?.FormatName ?? "다음 포맷 없음";
|
||||
public string NextItemName => Queue.FirstOrDefault(item => item.State == ScheduleQueueItemState.Next)?.FormatName ?? "다음 컷 없음";
|
||||
|
||||
public int QueuedItemCount => Queue.Count(item => item.State == ScheduleQueueItemState.Queued);
|
||||
|
||||
public string QueueFootnote => $"대기 {QueuedItemCount}건 · 프리셋 {AvailableFormats.Count}개";
|
||||
public string QueueFootnote => $"대기 {QueuedItemCount}건 / 컷 {AvailableFormats.Count}개";
|
||||
|
||||
public string QueueSummary
|
||||
{
|
||||
@@ -178,31 +199,31 @@ public sealed class ChannelScheduleViewModel : ObservableObject
|
||||
}
|
||||
}
|
||||
|
||||
public string LoopSummary => LoopEnabled ? "전체 반복 켜짐" : "한 번 재생";
|
||||
public string LoopSummary => LoopEnabled ? "반복 재생" : "1회 재생";
|
||||
|
||||
public string EmptyBehaviorLabel => SelectedEmptyBehaviorOption?.Label ?? "즉시 아웃";
|
||||
|
||||
public string OperatorQuickSummary => $"{AdapterStateLabel} · {LoopSummary} · 빈 스케줄 {EmptyBehaviorLabel}";
|
||||
public string OperatorQuickSummary => $"{AdapterStateLabel} / {LoopSummary} / 빈 스케줄 {EmptyBehaviorLabel}";
|
||||
|
||||
private async Task StartAsync()
|
||||
{
|
||||
await _engine.StartAsync().ConfigureAwait(false);
|
||||
RefreshSummary();
|
||||
_logService.Info($"[{Title}] 스케줄 시작");
|
||||
_logService.Info($"[{Title}] 큐를 시작");
|
||||
}
|
||||
|
||||
private async Task StopAsync()
|
||||
{
|
||||
await _engine.StopAsync().ConfigureAwait(false);
|
||||
RefreshSummary();
|
||||
_logService.Info($"[{Title}] 스케줄 종료");
|
||||
_logService.Info($"[{Title}] 큐를 종료");
|
||||
}
|
||||
|
||||
private async Task ForceNextAsync()
|
||||
{
|
||||
await _engine.ForceNextAsync().ConfigureAwait(false);
|
||||
RefreshSummary();
|
||||
_logService.Info($"[{Title}] 현재 포맷 강제 중지 후 전환");
|
||||
_logService.Info($"[{Title}] 현재 컷을 강제로 전환");
|
||||
}
|
||||
|
||||
private void AddFormat()
|
||||
@@ -212,6 +233,18 @@ public sealed class ChannelScheduleViewModel : ObservableObject
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SelectedFormat.IsAvailableInPhase(_data.BroadcastPhase))
|
||||
{
|
||||
_logService.Warning($"[{Title}] 현재 단계에서는 '{SelectedFormat.Name}' 컷을 추가할 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_data.ValidateForFormat(SelectedFormat, out var validationError))
|
||||
{
|
||||
_logService.Warning($"[{Title}] {validationError}");
|
||||
return;
|
||||
}
|
||||
|
||||
_engine.Enqueue(ChannelScheduleItem.FromTemplate(SelectedFormat));
|
||||
RefreshSummary();
|
||||
}
|
||||
@@ -220,14 +253,14 @@ public sealed class ChannelScheduleViewModel : ObservableObject
|
||||
{
|
||||
_engine.Reset();
|
||||
RefreshSummary();
|
||||
_logService.Info($"[{Title}] 스케줄을 첫 포맷부터 다시 시작하도록 초기화했습니다.");
|
||||
_logService.Info($"[{Title}] 큐를 첫 컷부터 다시 시작하도록 초기화했습니다.");
|
||||
}
|
||||
|
||||
private void RemoveItem(ChannelScheduleItem? item)
|
||||
{
|
||||
if (!_engine.Remove(item))
|
||||
{
|
||||
_logService.Warning($"[{Title}] 송출 중 포맷은 삭제할 수 없습니다.");
|
||||
_logService.Warning($"[{Title}] 송출 중 항목은 제거할 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -265,11 +298,50 @@ public sealed class ChannelScheduleViewModel : ObservableObject
|
||||
nameof(QueuedItemCount),
|
||||
nameof(QueueFootnote),
|
||||
nameof(QueueSummary),
|
||||
nameof(IsCgConnected),
|
||||
nameof(CgStatusSummary),
|
||||
nameof(LoopSummary),
|
||||
nameof(EmptyBehaviorLabel),
|
||||
nameof(OperatorQuickSummary));
|
||||
}
|
||||
|
||||
private bool CanAddFormat()
|
||||
{
|
||||
return SelectedFormat is not null && SelectedFormat.IsAvailableInPhase(_data.BroadcastPhase);
|
||||
}
|
||||
|
||||
private void Data_PropertyChanged(object? sender, PropertyChangedEventArgs args)
|
||||
{
|
||||
if (args.PropertyName != nameof(DataViewModel.BroadcastPhase))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RebuildAvailableFormats();
|
||||
RefreshSummary();
|
||||
}
|
||||
|
||||
private void RebuildAvailableFormats()
|
||||
{
|
||||
var filteredFormats = _allFormats
|
||||
.Where(format => format.IsAvailableInPhase(_data.BroadcastPhase))
|
||||
.ToArray();
|
||||
|
||||
AvailableFormats.Clear();
|
||||
foreach (var format in filteredFormats)
|
||||
{
|
||||
AvailableFormats.Add(format);
|
||||
}
|
||||
|
||||
if (SelectedFormat is null || !AvailableFormats.Contains(SelectedFormat))
|
||||
{
|
||||
SelectedFormat = AvailableFormats.FirstOrDefault();
|
||||
}
|
||||
|
||||
AddFormatCommand.NotifyCanExecuteChanged();
|
||||
OnPropertyChanged(nameof(QueueFootnote));
|
||||
}
|
||||
|
||||
private SelectionOption<EmptyScheduleBehavior>? FindEmptyBehaviorOption(EmptyScheduleBehavior behavior)
|
||||
{
|
||||
return EmptyBehaviorOptions.FirstOrDefault(option => option.Value == behavior);
|
||||
|
||||
Reference in New Issue
Block a user