우클릭 진입 통합
- App.OnStartup의 dialog 분기에서 ConvertWindow 대신 MainWindow를 띄우면서
파일을 Active Queue에 자동 추가, 첫 항목 자동 Preview 로드.
- 사용자 시점에서 우클릭 → "JPEG로 변환…" 클릭 시 작은 다이얼로그가
아니라 풀스크린 FormatShift 메인 화면이 그대로 노출.
- ConvertWindow 자체는 FormatShift 토큰으로 통일했지만 더 이상 호출되지
않음 (코드는 향후 활용 위해 보존).
Preview를 탭 → 우측 메인 컬럼으로 승격
- 메인 영역 그리드를 1 column → 2 column 으로 분할: 좌측 *(min 360),
우측 380px 고정 Preview 컬럼.
- 탭은 Active Queue / Past Results 두 개만 (Preview 탭과 무의미한 0 뱃지
제거). Preview는 항상 보임.
- Active Queue 행 클릭 → 우측 Preview에 즉시 표시 (탭 전환 없음).
- Past Results 행도 클릭 가능: 실제 파일이면 미리보기, 데모/소실 파일이면
안내 문구.
- Drop hint overlay는 ColumnSpan=2로 양쪽 컬럼 모두 덮음.
이벤트 처리 개선
- 행 Border에 PreviewMouseLeftButtonUp(tunnel) 사용 → 자식 컨트롤이 클릭을
가로채는 이슈 해소.
- IsInsideButton 헬퍼로 Remove/Open 버튼이 발화시킨 클릭은 행 핸들러에서
무시하여 의도 충돌 방지.
App.xaml 정리
- FormatShiftTheme.xaml 을 App-level resources에 머지 → 모든 윈도우에서
Application.Current.FindResource("Fs*") 조회 가능 (이전엔 Window-level
머지만 해서 ResourceReferenceKeyNotFoundException 발생).
LoadPreview 통합
- LoadPreviewByPathAsync(path, fileName, format, size) 단일 메서드로 통합.
- _selectedPreviewPath 로 Active/Past 두 케이스 모두 Open in Explorer 일관
처리.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
155 lines
5 KiB
C#
155 lines
5 KiB
C#
using System.Windows;
|
|
using EverythingToJpeg.App.Cli;
|
|
using EverythingToJpeg.App.Views;
|
|
using EverythingToJpeg.Core;
|
|
|
|
namespace EverythingToJpeg.App;
|
|
|
|
public partial class App : Application
|
|
{
|
|
public ConversionEngine Engine { get; } = EverythingToJpegBootstrap.CreateDefault();
|
|
|
|
protected override async void OnStartup(StartupEventArgs e)
|
|
{
|
|
base.OnStartup(e);
|
|
|
|
WireGlobalExceptionLogging();
|
|
|
|
var parsed = CliRouter.Parse(e.Args);
|
|
|
|
switch (parsed.Mode)
|
|
{
|
|
case CliRouter.Mode.Help:
|
|
ConsoleHelper.WriteLine(CliRouter.HelpText());
|
|
Environment.Exit(0);
|
|
return;
|
|
|
|
case CliRouter.Mode.Register:
|
|
Environment.Exit(CliRouter.RunRegister(register: true));
|
|
return;
|
|
|
|
case CliRouter.Mode.Unregister:
|
|
Environment.Exit(CliRouter.RunRegister(register: false));
|
|
return;
|
|
|
|
case CliRouter.Mode.Diagnose:
|
|
ShowDiagnoseWindow();
|
|
return;
|
|
|
|
case CliRouter.Mode.Quick:
|
|
if (parsed.Files.Count == 0)
|
|
{
|
|
MessageBox.Show("변환할 파일이 없습니다.", "EverythingToJpeg",
|
|
MessageBoxButton.OK, MessageBoxImage.Information);
|
|
Shutdown(1);
|
|
return;
|
|
}
|
|
await RunQuickAsync(parsed.Files);
|
|
return;
|
|
|
|
case CliRouter.Mode.Dialog:
|
|
ShowConvertDialog(parsed.Files);
|
|
return;
|
|
|
|
case CliRouter.Mode.ShowMain:
|
|
default:
|
|
ShowMainWindow();
|
|
return;
|
|
}
|
|
}
|
|
|
|
private void ShowMainWindow()
|
|
{
|
|
var window = new MainWindow();
|
|
MainWindow = window;
|
|
window.Show();
|
|
}
|
|
|
|
private void ShowConvertDialog(IReadOnlyList<string> files)
|
|
{
|
|
var window = new Views.MainWindow(files);
|
|
MainWindow = window;
|
|
window.Show();
|
|
}
|
|
|
|
private void ShowDiagnoseWindow()
|
|
{
|
|
var window = new DiagnoseWindow(Engine);
|
|
MainWindow = window;
|
|
window.Show();
|
|
}
|
|
|
|
private async Task RunQuickAsync(IReadOnlyList<string> files)
|
|
{
|
|
var logPath = Path.Combine(Path.GetTempPath(), "EverythingToJpeg_quick.log");
|
|
var log = new System.Text.StringBuilder();
|
|
log.AppendLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] Quick start, {files.Count} file(s)");
|
|
foreach (var f in files) log.AppendLine($" src: {f}");
|
|
|
|
var progress = new QuickProgressWindow(files.Count);
|
|
progress.Show();
|
|
|
|
try
|
|
{
|
|
var options = ConvertOptions.Quick();
|
|
var reporter = new Progress<ConvertProgress>(p => progress.Report(p));
|
|
var results = await Engine.ConvertManyAsync(files, options, reporter);
|
|
|
|
foreach (var r in results)
|
|
{
|
|
log.AppendLine($" [{r.Status}] {Path.GetFileName(r.SourcePath)} → {r.OutputPaths.Count} output(s)");
|
|
if (r.Message is { Length: > 0 }) log.AppendLine($" msg: {r.Message}");
|
|
if (r.Error is not null) log.AppendLine($" err: {r.Error}");
|
|
foreach (var o in r.OutputPaths) log.AppendLine($" out: {o}");
|
|
}
|
|
|
|
progress.Finish(results);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
log.AppendLine($" EXCEPTION {ex.GetType().Name}: {ex.Message}");
|
|
log.AppendLine(ex.ToString());
|
|
try { progress.Close(); } catch { }
|
|
MessageBox.Show($"변환 중 오류: {ex.Message}\n\n로그: {logPath}", "EverythingToJpeg",
|
|
MessageBoxButton.OK, MessageBoxImage.Error);
|
|
}
|
|
finally
|
|
{
|
|
try { File.WriteAllText(logPath, log.ToString()); } catch { }
|
|
}
|
|
}
|
|
|
|
private static void WireGlobalExceptionLogging()
|
|
{
|
|
var path = Path.Combine(Path.GetTempPath(), "EverythingToJpeg_unhandled.log");
|
|
|
|
void Append(string source, Exception? ex)
|
|
{
|
|
try
|
|
{
|
|
File.AppendAllText(path,
|
|
$"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] {source}\n{ex}\n\n");
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
AppDomain.CurrentDomain.UnhandledException += (_, e) =>
|
|
Append("AppDomain.UnhandledException", e.ExceptionObject as Exception);
|
|
|
|
Current.DispatcherUnhandledException += (_, e) =>
|
|
{
|
|
Append("Application.DispatcherUnhandledException", e.Exception);
|
|
MessageBox.Show(
|
|
"예기치 못한 오류:\n\n" + e.Exception.Message + "\n\n로그: " + path,
|
|
"EverythingToJpeg",
|
|
MessageBoxButton.OK, MessageBoxImage.Error);
|
|
e.Handled = true;
|
|
};
|
|
|
|
System.Threading.Tasks.TaskScheduler.UnobservedTaskException += (_, e) =>
|
|
{
|
|
Append("TaskScheduler.UnobservedTaskException", e.Exception);
|
|
e.SetObserved();
|
|
};
|
|
}
|
|
}
|