1
0
Fork 0

feat: Everything2Everything 양방향 변환 매트릭스 피보팅

EverythingToJpeg(N→JPEG 단방향)에서 Everything2Everything(N×M 매트릭스)으로
앱을 피보팅. 7개 Provider, 200+ 변환 쌍 지원.

리네이밍
- 디렉토리/솔루션/csproj/namespace/AssemblyName/MSIX Identity 일괄 치환
- 임시파일 prefix e2j_ → e2e_, 로고 라벨 E2J → E2E

아키텍처
- ConversionPair(input, output) 신규 + ProviderCapability.SupportedConversions
- IConverterProvider.ConvertAsync에 outputExtension 파라미터 추가
- ProviderRegistry를 Dictionary<(input, output), Provider> 매트릭스로 재작성
  + OutputsForInput / OutputsForFile 쿼리 API
- ConversionEngine: 출력 ext 라우팅, 동일 입출력 자동 skip,
  서브폴더 suffix를 출력 ext에서 자동 도출
- ConvertOptions를 형식별 sub-record로 분리
  (Jpeg/Png/Webp/Avif/Tiff/Pdf*/Html*/Ocr)

Provider 매트릭스
- MagickProvider: PNG/JPEG/WebP/AVIF/BMP/TIFF/GIF/PDF 양방향 + RAW/PSD 디코딩
  (단일이미지→1페이지 PDF, GIF/TIFF→다페이지 PDF)
- HeicProvider: HEIC/HEIF → 이미지 7종
- PdfProvider: PDF → 이미지 6종 (PNG임베드 후 ImageMagick 인코딩)
- HtmlProvider: HTML/HTM → 이미지 + PDF (CDP printToPDF)
- DocxProvider/HwpxProvider: PDF + 이미지 7종 (LibreOffice 활용)
- OcrProvider 신규: 이미지/PDF → TXT/DOCX
  (Windows.Media.Ocr + DocumentFormat.OpenXml, PDF는 페이지별 OCR 후 결합)

UI
- 사이드바 TARGET FORMAT을 동적 ComboBox로 (큐 파일 매트릭스의 교집합만 표시)
- 출력 형식별 색상 배지 + Quality 패널 라벨 동적
- OUTPUT DESTINATION hint도 출력 ext 기반 동적

CLI
- 신규 'to <ext> <files...>' verb (예: to png photo.heic doc.docx)
- help 텍스트 양방향 컨셉으로 갱신

셸 통합
- ContextMenuRegistrar 카스케이드로 재설계
  (ExtendedSubCommandsKey + Everything2Everything.SubMenu.<ext>)
- 입력별 매트릭스에 따라 인기 출력 10종(JPEG/PNG/WebP/PDF/TXT/DOCX/AVIF/GIF/TIFF/BMP)
  동적 노출, 마지막에 '변환…' 옵션
- 메뉴 라벨 'JPEG로 변환' → 'Everything2Everything으로 변환'

기타
- Core.csproj TFM net9.0-windows → net9.0-windows10.0.19041.0 (WinRT API용)
- DocumentFormat.OpenXml 3.1.0 NuGet 추가
- README 양방향 매트릭스 기준 전면 개편
- MSIX appxmanifest Description / IExplorerCommand DLL 라벨 갱신

빌드: 0 errors, 0 warnings
This commit is contained in:
Yun Chan 2026-05-07 13:40:08 +09:00
parent 96861e627d
commit 9b7e5f0d0c
63 changed files with 1788 additions and 733 deletions

View file

@ -0,0 +1,681 @@
<ui:FluentWindow x:Class="Everything2Everything.App.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
Title="FormatShift Utility"
Width="1280" Height="960"
MinWidth="1080" MinHeight="640"
ExtendsContentIntoTitleBar="True"
WindowBackdropType="None"
WindowCornerPreference="Round"
WindowStartupLocation="CenterScreen"
AllowDrop="True"
Drop="OnFilesDropped"
DragOver="OnDragOver"
DragLeave="OnDragLeave"
TextOptions.TextFormattingMode="Display"
UseLayoutRounding="True">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="FormatShiftTheme.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Window.InputBindings>
<KeyBinding Key="O" Modifiers="Ctrl" Command="{Binding AddFilesCommand, RelativeSource={RelativeSource AncestorType=Window}}"/>
<KeyBinding Key="Enter" Modifiers="Ctrl" Command="{Binding ProcessQueueCommand, RelativeSource={RelativeSource AncestorType=Window}}"/>
<KeyBinding Key="Escape" Command="{Binding CloseCommand, RelativeSource={RelativeSource AncestorType=Window}}"/>
<KeyBinding Key="F5" Command="{Binding RefreshCommand, RelativeSource={RelativeSource AncestorType=Window}}"/>
</Window.InputBindings>
<Grid Background="{StaticResource FsBgBase}">
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Title bar (window controls only) -->
<ui:TitleBar Grid.Row="0" Title="" ShowMaximize="True" ShowMinimize="True"/>
<!-- App body -->
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="320"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- ============== SIDEBAR ============== -->
<Border Grid.Column="0"
Background="{StaticResource FsBgPanel}"
BorderBrush="{StaticResource FsBorderSubtle}"
BorderThickness="0,0,1,0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="56"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Sidebar header -->
<Border Grid.Row="0" BorderBrush="{StaticResource FsBorderSubtle}"
BorderThickness="0,0,0,1" Padding="24,0">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<Path Width="18" Height="18" Stretch="Uniform"
Stroke="{StaticResource FsAccentBlue}" StrokeThickness="2"
Data="M3,3 L21,3 L21,21 L3,21 Z M12,8 L12,16 M8,12 L16,12"/>
<TextBlock Margin="8,0,0,0" FontSize="14" FontWeight="SemiBold"
Foreground="{StaticResource FsTextPrimary}"
FontFamily="{StaticResource FsFontSans}">
FormatShift
<Run Foreground="{StaticResource FsTextTertiary}" FontWeight="Medium"
Text=" Utility"/>
</TextBlock>
</StackPanel>
</Border>
<!-- Sidebar content -->
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto" Padding="24,24,24,24">
<StackPanel>
<!-- Target Format -->
<StackPanel Margin="0,0,0,32">
<TextBlock Text="TARGET FORMAT" Style="{StaticResource FsLabelStyle}"/>
<Grid Margin="0,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border x:Name="OutputFormatBadge" Grid.Column="0"
Width="36" Height="28" CornerRadius="4"
Background="{StaticResource FsFmtJpg}"
VerticalAlignment="Center">
<TextBlock x:Name="OutputFormatBadgeText"
Text="JPG" Foreground="White" FontSize="9"
FontWeight="SemiBold"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ComboBox x:Name="OutputFormatCombo" Grid.Column="1"
Margin="8,0,0,0" VerticalAlignment="Center"
SelectionChanged="OnOutputFormatChanged"/>
</Grid>
<TextBlock x:Name="OutputFormatHint" Margin="0,6,0,0"
Style="{StaticResource FsCaptionStyle}"
Text="모든 입력에서 변환 가능한 형식이 표시됩니다"/>
</StackPanel>
<!-- Encoding Quality -->
<StackPanel x:Name="QualityPanel" Margin="0,0,0,32">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="QualityLabelText" Text="ENCODING QUALITY" Style="{StaticResource FsLabelStyle}"/>
<Border Grid.Column="1" CornerRadius="4"
Background="{StaticResource FsBgInput}"
BorderBrush="{StaticResource FsBorderSubtle}"
BorderThickness="1" Padding="6,2">
<TextBlock x:Name="QualityValueText"
FontFamily="{StaticResource FsFontMono}" FontSize="12"
Foreground="{StaticResource FsAccentBlue}"
Text="85%"/>
</Border>
</Grid>
<Slider x:Name="QualitySlider" Margin="0,12,0,0"
Style="{StaticResource FsSliderStyle}"
Minimum="1" Maximum="100" Value="85"
ValueChanged="OnQualityChanged"/>
<Grid Margin="0,4,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Smaller File" Style="{StaticResource FsCaptionStyle}"/>
<TextBlock Grid.Column="1" Text="Higher Quality" Style="{StaticResource FsCaptionStyle}"/>
</Grid>
</StackPanel>
<!-- Output Destination -->
<StackPanel Margin="0,0,0,32">
<TextBlock Text="OUTPUT DESTINATION" Style="{StaticResource FsLabelStyle}"/>
<Grid Margin="0,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="OutputPathTextBox"
Style="{StaticResource FsPathInputStyle}"
ToolTip="비워두면 원본 폴더 옆 서브폴더에 저장"/>
<Button Grid.Column="1" Margin="8,0,0,0" Width="36"
Content="…" Style="{StaticResource FsSecondaryButtonStyle}"
Click="OnPickOutputFolderClick"/>
</Grid>
<TextBlock x:Name="OutputDestHint" Margin="0,6,0,0"
Style="{StaticResource FsCaptionStyle}"
Text="비워두면 원본 옆 _jpg 폴더에 저장됩니다"/>
</StackPanel>
<!-- File Conflict Rule -->
<StackPanel Margin="0,0,0,32">
<TextBlock Text="FILE CONFLICT RULE" Style="{StaticResource FsLabelStyle}"/>
<Border Margin="0,8,0,0"
Background="{StaticResource FsBgInput}"
BorderBrush="{StaticResource FsBorderSubtle}"
BorderThickness="1" CornerRadius="6" Padding="2">
<UniformGrid Rows="1" Columns="3">
<ToggleButton x:Name="ConflictSkipBtn" Content="Skip"
Style="{StaticResource FsSegmentStyle}"
Click="OnConflictSegmentClick"
Tag="Skip"/>
<ToggleButton x:Name="ConflictRenameBtn" Content="Rename"
Style="{StaticResource FsSegmentStyle}"
IsChecked="True"
Click="OnConflictSegmentClick"
Tag="Rename"/>
<ToggleButton x:Name="ConflictReplaceBtn" Content="Replace"
Style="{StaticResource FsSegmentStyle}"
Click="OnConflictSegmentClick"
Tag="Replace"/>
</UniformGrid>
</Border>
</StackPanel>
<!-- Stats Box -->
<Border Background="{StaticResource FsBgInput}"
BorderBrush="{StaticResource FsBorderSubtle}"
BorderThickness="1" CornerRadius="6" Padding="12">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel>
<TextBlock Text="PROCESSED TODAY"
FontFamily="{StaticResource FsFontSans}"
FontSize="10" FontWeight="SemiBold"
Foreground="{StaticResource FsTextSecondary}"/>
<TextBlock x:Name="ProcessedTodayText"
FontFamily="{StaticResource FsFontMono}"
FontSize="14"
Foreground="{StaticResource FsTextPrimary}"
Text="0" Margin="0,4,0,0"/>
</StackPanel>
<StackPanel Grid.Column="1">
<TextBlock Text="EST. SPACE SAVED"
FontFamily="{StaticResource FsFontSans}"
FontSize="10" FontWeight="SemiBold"
Foreground="{StaticResource FsTextSecondary}"/>
<TextBlock x:Name="SpaceSavedText"
FontFamily="{StaticResource FsFontMono}"
FontSize="14"
Foreground="{StaticResource FsAccentGreen}"
Text="0 B" Margin="0,4,0,0"/>
</StackPanel>
</Grid>
</Border>
</StackPanel>
</ScrollViewer>
<!-- Sidebar footer -->
<Border Grid.Row="2" BorderBrush="{StaticResource FsBorderSubtle}"
BorderThickness="0,1,0,0" Padding="24">
<StackPanel>
<TextBlock x:Name="CapabilityStatusText" Margin="0,0,0,12"
Style="{StaticResource FsCaptionStyle}"
TextWrapping="Wrap" Visibility="Collapsed"/>
<Button x:Name="ProcessQueueButton"
Content="Idle — drop files to begin"
Style="{StaticResource FsPrimaryButtonStyle}"
IsEnabled="False"
Click="OnProcessQueueClick"/>
</StackPanel>
</Border>
</Grid>
</Border>
<!-- ============== MAIN ============== -->
<Grid Grid.Column="1" Background="{StaticResource FsBgBase}">
<Grid.RowDefinitions>
<RowDefinition Height="56"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Nav bar -->
<Border Grid.Row="0" BorderBrush="{StaticResource FsBorderSubtle}"
BorderThickness="0,0,0,1" Padding="24,0">
<Grid VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Tabs -->
<Border HorizontalAlignment="Left"
Background="{StaticResource FsBgPanel}"
BorderBrush="{StaticResource FsBorderSubtle}"
BorderThickness="1" CornerRadius="20" Padding="4">
<StackPanel Orientation="Horizontal">
<ToggleButton x:Name="TabActiveBtn"
Style="{StaticResource FsTabPillStyle}"
Click="OnTabClick" Tag="Active">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Active Queue" VerticalAlignment="Center"/>
<Border Margin="8,0,0,0" Style="{StaticResource FsTabBadgeStyle}">
<TextBlock x:Name="TabActiveBadge" Text="0"
FontSize="10" FontWeight="SemiBold"
Foreground="{StaticResource FsTextTertiary}"/>
</Border>
</StackPanel>
</ToggleButton>
<ToggleButton x:Name="TabPastBtn" Margin="8,0,0,0"
IsChecked="True"
Style="{StaticResource FsTabPillStyle}"
Click="OnTabClick" Tag="Past">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Past Results" VerticalAlignment="Center"/>
<Border Margin="8,0,0,0" Style="{StaticResource FsTabBadgeStyle}">
<TextBlock x:Name="TabPastBadge" Text="0"
FontSize="10" FontWeight="SemiBold"
Foreground="{StaticResource FsTextTertiary}"/>
</Border>
</StackPanel>
</ToggleButton>
</StackPanel>
</Border>
<!-- Actions -->
<StackPanel Grid.Column="1" Orientation="Horizontal">
<Button Content="Register Menu" Margin="0,0,8,0"
Style="{StaticResource FsSecondaryButtonStyle}"
Click="OnRegisterClick"/>
<Button Content="Diagnose" Margin="0,0,8,0"
Style="{StaticResource FsSecondaryButtonStyle}"
Click="OnDiagnoseClick"/>
<Button Content="Export Log" Margin="0,0,8,0"
Style="{StaticResource FsSecondaryButtonStyle}"
Click="OnExportLogClick"/>
<Button x:Name="ClearAllButton" Content="Clear All"
Style="{StaticResource FsSecondaryButtonStyle}"
Click="OnClearAllClick"/>
</StackPanel>
</Grid>
</Border>
<!-- View content (좌측: 탭 컨텐츠 / 우측: Preview 컬럼) -->
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" MinWidth="360"/>
<ColumnDefinition Width="380"/>
</Grid.ColumnDefinitions>
<!-- 좌측: Active Queue OR Past Results -->
<Grid Grid.Column="0">
<!-- Active Queue view -->
<Grid x:Name="ActiveQueueView" Visibility="Collapsed">
<Grid x:Name="DropZoneEmpty">
<Border Background="{StaticResource FsBgBase}">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"
Margin="48">
<Path Width="48" Height="48" Stretch="Uniform"
Stroke="{StaticResource FsAccentBlue}" StrokeThickness="1.5"
Data="M21,15 L21,19 C21,20.1 20.1,21 19,21 L5,21 C3.9,21 3,20.1 3,19 L3,15 M7,10 L12,15 L17,10 M12,15 L12,3"/>
<TextBlock Margin="0,16,0,0" FontSize="16" FontWeight="SemiBold"
Foreground="{StaticResource FsTextPrimary}"
HorizontalAlignment="Center"
Text="Drop files anywhere to queue"/>
<TextBlock Margin="0,4,0,0" Style="{StaticResource FsCaptionStyle}"
HorizontalAlignment="Center"
Text="PNG · JPG · GIF · HEIC · RAW · PDF · DOCX · HTML · HWP/HWPX"/>
</StackPanel>
</Border>
</Grid>
<ScrollViewer x:Name="ActiveQueueScroll" VerticalScrollBarVisibility="Auto"
Padding="24" Visibility="Collapsed">
<ItemsControl x:Name="ActiveQueueList">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Margin="0,0,0,8" Padding="12,10" CornerRadius="6"
Background="{StaticResource FsBgPanel}"
BorderBrush="{StaticResource FsBorderSubtle}"
BorderThickness="1"
Cursor="Hand"
PreviewMouseLeftButtonUp="OnQueueRowClick"
Tag="{Binding}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="120"/>
<ColumnDefinition Width="40"/>
</Grid.ColumnDefinitions>
<Border Width="32" Height="32" CornerRadius="4"
Background="{Binding FormatBrush}"
VerticalAlignment="Center">
<TextBlock Text="{Binding FormatLabel}"
Foreground="White" FontSize="10"
FontWeight="SemiBold"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
<StackPanel Grid.Column="1" Margin="16,0,0,0"
VerticalAlignment="Center">
<TextBlock Text="{Binding FileName}"
Style="{StaticResource FsBodyStyle}"
FontWeight="Medium"
TextTrimming="CharacterEllipsis"/>
<TextBlock Text="{Binding MetaLine}"
Style="{StaticResource FsCaptionStyle}"
Margin="0,2,0,0"/>
</StackPanel>
<TextBlock Grid.Column="2" Text="{Binding SizeText}"
Style="{StaticResource FsMonoStyle}"
HorizontalAlignment="Right"
VerticalAlignment="Center"/>
<TextBlock Grid.Column="3" Text="{Binding StateText}"
FontFamily="{StaticResource FsFontMono}"
FontSize="12"
Foreground="{Binding StateBrush}"
VerticalAlignment="Center"/>
<Button Grid.Column="4"
Style="{StaticResource FsSecondaryButtonStyle}"
Background="Transparent"
BorderThickness="0"
Padding="6"
Click="OnRemoveQueueItem"
Tag="{Binding}">
<Path Width="14" Height="14" Stretch="Uniform"
Stroke="{StaticResource FsTextTertiary}"
StrokeThickness="2"
Data="M18,6 L6,18 M6,6 L18,18"/>
</Button>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
<!-- Past Results view (default) -->
<ScrollViewer x:Name="PastResultsView" VerticalScrollBarVisibility="Auto"
Padding="24">
<ItemsControl x:Name="PastResultsList">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,40">
<!-- Date header -->
<Grid Margin="0,0,0,12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding DateTitle}"
FontFamily="{StaticResource FsFontSans}"
FontSize="14" FontWeight="SemiBold"
Foreground="{StaticResource FsTextPrimary}"/>
<Border Grid.Column="1" CornerRadius="20" Padding="10,4"
Background="#10B98122">
<TextBlock Text="{Binding SessionSavingsText}"
FontFamily="{StaticResource FsFontMono}"
FontSize="12"
Foreground="{StaticResource FsAccentGreen}"/>
</Border>
</Grid>
<Border Height="1" Background="{StaticResource FsBorderSubtle}"
Margin="0,0,0,12"/>
<!-- Rows -->
<ItemsControl ItemsSource="{Binding Entries}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="#0DFFFFFF"
BorderThickness="0,0,0,1" Padding="0,12"
Cursor="Hand"
Background="Transparent"
PreviewMouseLeftButtonUp="OnPastRowClick"
Tag="{Binding}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="120"/>
<ColumnDefinition Width="40"/>
</Grid.ColumnDefinitions>
<Border Width="32" Height="32" CornerRadius="4"
Background="{Binding FormatBrush}"
VerticalAlignment="Center">
<TextBlock Text="{Binding FormatLabel}"
Foreground="White" FontSize="10"
FontWeight="SemiBold"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
<StackPanel Grid.Column="1" Margin="16,0,0,0"
VerticalAlignment="Center">
<TextBlock Text="{Binding FileName}"
Style="{StaticResource FsBodyStyle}"
FontWeight="Medium"
TextTrimming="CharacterEllipsis"/>
<TextBlock Text="{Binding MetaLine}"
Style="{StaticResource FsCaptionStyle}"
Margin="0,2,0,0"/>
</StackPanel>
<TextBlock Grid.Column="2"
Text="{Binding SizeText}"
Style="{StaticResource FsMonoStyle}"
HorizontalAlignment="Right"
VerticalAlignment="Center"/>
<TextBlock Grid.Column="3"
Text="{Binding SavingsText}"
FontFamily="{StaticResource FsFontMono}"
FontSize="12"
Foreground="{StaticResource FsAccentGreen}"
VerticalAlignment="Center"/>
<StackPanel Grid.Column="4" Orientation="Horizontal"
VerticalAlignment="Center">
<Path Width="12" Height="12" Stretch="Uniform"
Stroke="{StaticResource FsAccentGreen}"
StrokeThickness="3"
Data="M20,6 L9,17 L4,12"/>
<TextBlock Text="Success" Margin="6,0,0,0"
FontSize="11" FontWeight="Medium"
Foreground="{StaticResource FsAccentGreen}"/>
</StackPanel>
<Button Grid.Column="5"
Style="{StaticResource FsSecondaryButtonStyle}"
Background="Transparent"
BorderThickness="0"
Padding="6"
Click="OnOpenFolderClick"
Tag="{Binding SourcePath}"
ToolTip="원본 폴더 열기">
<Path Width="14" Height="14" Stretch="Uniform"
Stroke="{StaticResource FsTextTertiary}"
StrokeThickness="2"
Data="M22,19 C22,20.1 21.1,21 20,21 L4,21 C2.9,21 2,20.1 2,19 L2,5 C2,3.9 2.9,3 4,3 L9,3 L11,6 L20,6 C21.1,6 22,6.9 22,8 Z"/>
</Button>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
<!-- /좌측 컬럼 끝 -->
<!-- 우측: Preview 컬럼 (항상 보임) -->
<Border Grid.Column="1"
Background="{StaticResource FsBgPanel}"
BorderBrush="{StaticResource FsBorderSubtle}"
BorderThickness="1,0,0,0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 헤더 -->
<Border Grid.Row="0" Padding="20,16,20,12"
BorderBrush="{StaticResource FsBorderSubtle}"
BorderThickness="0,0,0,1">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<Path Width="14" Height="14" Stretch="Uniform"
Stroke="{StaticResource FsTextSecondary}" StrokeThickness="2"
Data="M1,12 C1,12 5,4 12,4 C19,4 23,12 23,12 C23,12 19,20 12,20 C5,20 1,12 1,12 Z M12,9 A3,3 0 1,1 12,15 A3,3 0 1,1 12,9 Z"/>
<TextBlock Text="PREVIEW" Margin="8,0,0,0"
Style="{StaticResource FsLabelStyle}"
VerticalAlignment="Center"/>
</StackPanel>
</Border>
<!-- 이미지 영역 -->
<Grid Grid.Row="1" Margin="20,16,20,16" x:Name="PreviewImageArea">
<Border Background="{StaticResource FsBgInput}"
BorderBrush="{StaticResource FsBorderSubtle}"
BorderThickness="1" CornerRadius="6">
<Grid>
<!-- Empty state -->
<StackPanel x:Name="PreviewEmpty" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="20">
<Path Width="36" Height="36" Stretch="Uniform"
Stroke="{StaticResource FsTextTertiary}" StrokeThickness="1.5"
Data="M1,12 C1,12 5,4 12,4 C19,4 23,12 23,12 C23,12 19,20 12,20 C5,20 1,12 1,12 Z M12,9 A3,3 0 1,1 12,15 A3,3 0 1,1 12,9 Z"/>
<TextBlock Margin="0,12,0,0" FontSize="13" FontWeight="SemiBold"
Foreground="{StaticResource FsTextSecondary}"
HorizontalAlignment="Center"
Text="No selection"/>
<TextBlock Margin="0,4,0,0" Style="{StaticResource FsCaptionStyle}"
HorizontalAlignment="Center" TextAlignment="Center"
TextWrapping="Wrap" MaxWidth="240"
Text="Active Queue 항목을 클릭하면 여기에 미리보기가 표시됩니다."/>
</StackPanel>
<!-- Loading -->
<StackPanel x:Name="PreviewLoading" Visibility="Collapsed"
HorizontalAlignment="Center" VerticalAlignment="Center">
<ProgressBar IsIndeterminate="True" Width="180" Height="3"
Foreground="{StaticResource FsAccentBlue}"
Background="{StaticResource FsBgSurface}"/>
<TextBlock Margin="0,12,0,0" Style="{StaticResource FsCaptionStyle}"
HorizontalAlignment="Center"
Text="미리보기 생성 중…"/>
</StackPanel>
<!-- Image -->
<Image x:Name="PreviewImage" Stretch="Uniform" Margin="16"
Visibility="Collapsed"/>
<!-- Reason -->
<StackPanel x:Name="PreviewReason" Visibility="Collapsed"
HorizontalAlignment="Center" VerticalAlignment="Center" Margin="20">
<Path Width="32" Height="32" Stretch="Uniform"
Stroke="{StaticResource FsAccentAmber}" StrokeThickness="1.5"
Data="M12,2 L22,20 L2,20 Z M12,9 L12,14 M12,17 L12,17.01"/>
<TextBlock x:Name="PreviewReasonText" Margin="0,12,0,0"
Style="{StaticResource FsBodyStyle}"
FontSize="12"
HorizontalAlignment="Center" TextAlignment="Center"
TextWrapping="Wrap" MaxWidth="280"/>
</StackPanel>
</Grid>
</Border>
</Grid>
<!-- 메타 -->
<Border Grid.Row="2" Padding="20,12,20,20"
BorderBrush="{StaticResource FsBorderSubtle}"
BorderThickness="0,1,0,0">
<StackPanel>
<TextBlock x:Name="PreviewFileName"
Style="{StaticResource FsBodyStyle}"
FontWeight="SemiBold" TextWrapping="NoWrap"
TextTrimming="CharacterEllipsis"
Text="—"/>
<TextBlock x:Name="PreviewFilePath" Margin="0,4,0,0"
FontFamily="{StaticResource FsFontMono}" FontSize="11"
Foreground="{StaticResource FsTextTertiary}"
TextTrimming="CharacterEllipsis" MaxHeight="32"/>
<Border Margin="0,12,0,12" Height="1"
Background="{StaticResource FsBorderSubtle}"/>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Format"
Style="{StaticResource FsCaptionStyle}" Margin="0,0,0,4"/>
<TextBlock x:Name="PreviewFormatText" Grid.Row="0" Grid.Column="1"
FontFamily="{StaticResource FsFontMono}" FontSize="12"
Foreground="{StaticResource FsTextPrimary}" Margin="0,0,0,4"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Size"
Style="{StaticResource FsCaptionStyle}" Margin="0,0,0,4"/>
<TextBlock x:Name="PreviewSizeText" Grid.Row="1" Grid.Column="1"
FontFamily="{StaticResource FsFontMono}" FontSize="12"
Foreground="{StaticResource FsTextPrimary}" Margin="0,0,0,4"/>
<TextBlock Grid.Row="2" Grid.Column="0" Text="Dimensions"
Style="{StaticResource FsCaptionStyle}" Margin="0,0,0,4"/>
<TextBlock x:Name="PreviewDimText" Grid.Row="2" Grid.Column="1"
FontFamily="{StaticResource FsFontMono}" FontSize="12"
Foreground="{StaticResource FsTextPrimary}" Text="—"
Margin="0,0,0,4"/>
<TextBlock Grid.Row="3" Grid.Column="0" Text="Pages"
Style="{StaticResource FsCaptionStyle}"/>
<TextBlock x:Name="PreviewPageText" Grid.Row="3" Grid.Column="1"
FontFamily="{StaticResource FsFontMono}" FontSize="12"
Foreground="{StaticResource FsTextPrimary}" Text="—"/>
</Grid>
<Button Content="Open in Explorer" Margin="0,16,0,0"
Style="{StaticResource FsSecondaryButtonStyle}"
Click="OnPreviewOpenFolder"
HorizontalAlignment="Stretch"/>
</StackPanel>
</Border>
</Grid>
</Border>
<!-- Drop hint overlay (전체 덮음) -->
<Border x:Name="DropHintOverlay" Visibility="Collapsed" Grid.ColumnSpan="2"
Background="#CC090A0C" IsHitTestVisible="False">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<Path Width="64" Height="64" Stretch="Uniform"
Stroke="{StaticResource FsAccentBlue}" StrokeThickness="2"
Data="M21,15 L21,19 C21,20.1 20.1,21 19,21 L5,21 C3.9,21 3,20.1 3,19 L3,15 M7,10 L12,15 L17,10 M12,15 L12,3"/>
<TextBlock Text="Drop to add to queue" Margin="0,16,0,0"
FontSize="18" FontWeight="SemiBold"
Foreground="{StaticResource FsTextPrimary}"
HorizontalAlignment="Center"/>
</StackPanel>
</Border>
</Grid>
</Grid>
</Grid>
</Grid>
</ui:FluentWindow>