1
0
Fork 0

feat: P4-P8 한방 마무리 — 자체서명 MSIX, 영구저장, 단축키, capability 알림

P4 — 자체 서명 MSIX 자동화
- packaging/BuildAndSign.ps1: 인증서 생성→PFX export→MSIX 빌드→서명을
  한 스크립트로 일관 처리. 기존 인증서 있으면 재사용.
- Install-EverythingToJpeg.ps1: 기본 비밀번호 'EverythingToJpegDev' 자동
  사용 (사용자 5대 PC 본인 사용 한정).
- 검증: 53MB 서명된 MSIX 산출, signtool 정상 통과(DigiCert TimeStamp 박힘).

P6 — Past Results 영구 저장
- HistoryStorage.cs: %LocalAppData%\EverythingToJpeg\history.jsonl 에
  변환 결과 append. 시작 시 로드해서 _pastResults에 채움.
- 첫 실행에만 데모 시드, 이후엔 실데이터만 표시.
- Past Results의 Clear All은 영구 파일도 함께 삭제 (확인 다이얼로그 추가).

P7 — 정리 + 단축키
- 사용 안 하는 ConvertWindow.xaml/.cs 폐기 (코드 600줄 제거).
- KeyBinding 4개: Ctrl+O 파일 추가, Ctrl+Enter 변환, Esc 닫기, F5 새로고침.
- RelayCommand 헬퍼 클래스로 단축키 ICommand 패턴 구현.

P8 — Capability 자동 안내
- 시작 시 RequiresExternal Provider들의 가용성 비동기 체크.
- 외부 도구 미설치 형식이 있으면 사이드바 footer에
  "⚠ N개 형식이 외부 도구를 기다립니다 (Diagnose 참조)" 한 줄 표시.

README — Phase 1~8 전체 진척, 단축키 표 정리.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Yun Chan 2026-05-06 19:36:55 +09:00
parent 00d1eeff96
commit 784167ccff
8 changed files with 276 additions and 709 deletions

View file

@ -20,16 +20,27 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow
private CancellationTokenSource? _cts;
private NameCollision _conflictRule = NameCollision.AppendNumber;
public ICommand AddFilesCommand { get; }
public ICommand ProcessQueueCommand { get; }
public ICommand CloseCommand { get; }
public ICommand RefreshCommand { get; }
public MainWindow() : this(null) { }
public MainWindow(IReadOnlyList<string>? initialFiles)
{
AddFilesCommand = new RelayCommand(_ => PickAndAddFiles());
ProcessQueueCommand = new RelayCommand(_ => OnProcessQueueClick(this, new RoutedEventArgs()),
_ => _activeQueue.Count > 0 && _cts is null);
CloseCommand = new RelayCommand(_ => Close());
RefreshCommand = new RelayCommand(_ => ApplyAppDataStats());
InitializeComponent();
ActiveQueueList.ItemsSource = _activeQueue;
PastResultsList.ItemsSource = _pastResults;
SeedDemoHistory();
LoadHistory();
UpdateBadges();
UpdateProcessQueueButton();
@ -45,6 +56,47 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow
UpdateActiveQueueVisibility();
ApplyAppDataStats();
_ = RefreshCapabilityStatusAsync();
}
private async Task RefreshCapabilityStatusAsync()
{
var engine = ((App)Application.Current).Engine;
var notReady = new List<string>();
foreach (var p in engine.Providers.All)
{
if (p.Capability.Status == EverythingToJpeg.Core.Providers.ProviderStatus.RequiresExternal)
{
var availability = await p.CheckAvailabilityAsync();
if (!availability.IsReady)
notReady.Add(p.Capability.DisplayName);
}
}
if (notReady.Count == 0)
{
CapabilityStatusText.Visibility = Visibility.Collapsed;
return;
}
CapabilityStatusText.Text = $"⚠ {notReady.Count}개 형식이 외부 도구를 기다립니다 (Diagnose 참조)";
CapabilityStatusText.Visibility = Visibility.Visible;
}
private void PickAndAddFiles()
{
var dlg = new Microsoft.Win32.OpenFileDialog
{
Multiselect = true,
Title = "변환할 파일 추가",
Filter = "지원 파일|*.png;*.jpg;*.jpeg;*.gif;*.bmp;*.tif;*.tiff;*.webp;*.avif;*.heic;*.heif;*.psd;*.dng;*.nef;*.cr2;*.cr3;*.arw;*.raf;*.orf;*.rw2;*.srw;*.pef;*.pdf;*.docx;*.doc;*.html;*.htm;*.hwp;*.hwpx|모든 파일|*.*",
};
if (dlg.ShowDialog(this) == true)
{
AddToQueue(dlg.FileNames);
ShowTab("Active");
}
}
// ============== Tabs ==============
@ -517,6 +569,12 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow
// ============== History ==============
private void AddToHistory(HistoryEntry entry)
{
AddToHistoryGroups(entry);
HistoryStorage.Append(entry);
}
private void AddToHistoryGroups(HistoryEntry entry)
{
var label = FormatDateLabel(entry.Date);
var group = _pastResults.FirstOrDefault(g => g.DateTitle == label);
@ -528,6 +586,21 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow
group.Add(HistoryRow.From(entry));
}
private void LoadHistory()
{
var entries = HistoryStorage.Load();
if (entries.Count == 0)
{
// 첫 실행: 데모 데이터로 시각적 가이드 제공
SeedDemoHistory();
return;
}
// 가장 오래된 것부터 추가 (Insert(0)이 누적)
foreach (var e in entries.OrderBy(e => e.Timestamp))
AddToHistoryGroups(e);
}
private void SeedDemoHistory()
{
var today = FormatDateLabel(DateOnly.FromDateTime(DateTime.Today));
@ -624,7 +697,14 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow
}
else if (TabPastBtn.IsChecked == true)
{
var confirm = MessageBox.Show(this,
"Past Results 전체를 삭제하시겠습니까?\n영구 저장된 이력도 함께 삭제됩니다.",
"EverythingToJpeg",
MessageBoxButton.OKCancel, MessageBoxImage.Question);
if (confirm != MessageBoxResult.OK) return;
_pastResults.Clear();
HistoryStorage.Clear();
}
UpdateBadges();
UpdateProcessQueueButton();
@ -789,3 +869,24 @@ internal static class FormatPalette
_ => (ext.ToUpperInvariant(), "FsFmtOther"),
};
}
internal sealed class RelayCommand : ICommand
{
private readonly Action<object?> _execute;
private readonly Func<object?, bool>? _canExecute;
public RelayCommand(Action<object?> execute, Func<object?, bool>? canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object? parameter) => _canExecute?.Invoke(parameter) ?? true;
public void Execute(object? parameter) => _execute(parameter);
public event EventHandler? CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}