ux: 우클릭 → 메인 윈도우 통합 + Preview를 우측 컬럼으로 승격
우클릭 진입 통합
- 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>
This commit is contained in:
parent
01467a4d27
commit
00d1eeff96
6 changed files with 595 additions and 373 deletions
|
|
@ -9,6 +9,7 @@
|
|||
<ResourceDictionary.MergedDictionaries>
|
||||
<ui:ThemesDictionary Theme="Dark"/>
|
||||
<ui:ControlsDictionary/>
|
||||
<ResourceDictionary Source="Views/FormatShiftTheme.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<!-- Spacing tokens (Fluent 8pt grid) -->
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ public partial class App : Application
|
|||
|
||||
private void ShowConvertDialog(IReadOnlyList<string> files)
|
||||
{
|
||||
var window = new ConvertWindow(Engine, files);
|
||||
var window = new Views.MainWindow(files);
|
||||
MainWindow = window;
|
||||
window.Show();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,47 +3,66 @@
|
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
Title="JPEG로 변환"
|
||||
Width="640" Height="780"
|
||||
MinWidth="520" MinHeight="560"
|
||||
Width="720" Height="780"
|
||||
MinWidth="560" MinHeight="560"
|
||||
ExtendsContentIntoTitleBar="True"
|
||||
WindowBackdropType="Mica"
|
||||
WindowBackdropType="None"
|
||||
WindowCornerPreference="Round"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
AllowDrop="True"
|
||||
Drop="OnFilesDropped"
|
||||
DragOver="OnDragOver">
|
||||
<Grid>
|
||||
DragOver="OnDragOver"
|
||||
TextOptions.TextFormattingMode="Display"
|
||||
UseLayoutRounding="True">
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="FormatShiftTheme.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
|
||||
<Grid Background="{StaticResource FsBgBase}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 0: TitleBar -->
|
||||
<ui:TitleBar Grid.Row="0" Title="JPEG로 변환"/>
|
||||
<!-- 0: TitleBar (window controls only) -->
|
||||
<ui:TitleBar Grid.Row="0" Title=""/>
|
||||
|
||||
<!-- 1: 헤더 (제목 + 파일 카운트) -->
|
||||
<Grid Grid.Row="1" Margin="32,8,32,12">
|
||||
<!-- 1: 헤더 -->
|
||||
<Grid Grid.Row="1" Margin="32,8,32,16">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel>
|
||||
<TextBlock Text="JPEG로 변환" Style="{StaticResource TextTitle}"/>
|
||||
<TextBlock x:Name="FilesSummaryText" Style="{StaticResource TextCaption}" Margin="0,2,0,0"/>
|
||||
<TextBlock Text="JPEG로 변환"
|
||||
FontFamily="{StaticResource FsFontSans}"
|
||||
FontSize="20" FontWeight="SemiBold"
|
||||
Foreground="{StaticResource FsTextPrimary}"/>
|
||||
<TextBlock x:Name="FilesSummaryText" Margin="0,2,0,0"
|
||||
Style="{StaticResource FsCaptionStyle}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<ui:Button Content="추가" Icon="{ui:SymbolIcon Add24}"
|
||||
Click="OnAddFilesClick" Margin="0,0,8,0"/>
|
||||
<ui:Button Content="비우기" Icon="{ui:SymbolIcon Delete24}"
|
||||
Click="OnClearFilesClick"/>
|
||||
<Button Content="+ 추가" Margin="0,0,8,0"
|
||||
Style="{StaticResource FsSecondaryButtonStyle}"
|
||||
Click="OnAddFilesClick"/>
|
||||
<Button Content="비우기"
|
||||
Style="{StaticResource FsSecondaryButtonStyle}"
|
||||
Click="OnClearFilesClick"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- 2: 옵션 바 (고정 핵심 옵션 + 고급 expander) -->
|
||||
<ui:Card Grid.Row="2" Margin="32,0,32,12" Padding="16,12">
|
||||
<!-- 2: 옵션 카드 -->
|
||||
<Border Grid.Row="2" Margin="32,0,32,16"
|
||||
Background="{StaticResource FsBgPanel}"
|
||||
BorderBrush="{StaticResource FsBorderSubtle}"
|
||||
BorderThickness="1" CornerRadius="8" Padding="20">
|
||||
<StackPanel>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
|
|
@ -53,128 +72,194 @@
|
|||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 품질 -->
|
||||
<StackPanel Grid.Column="0" Margin="0,0,8,0">
|
||||
<TextBlock Text="JPEG 품질" Style="{StaticResource TextCaption}"/>
|
||||
<Grid Margin="0,2,0,0">
|
||||
<StackPanel Grid.Column="0" Margin="0,0,12,0">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="32"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Slider x:Name="QualitySlider" Minimum="50" Maximum="100" Value="92"
|
||||
SmallChange="1" LargeChange="5" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding ElementName=QualitySlider, Path=Value, StringFormat={}{0:0}}"
|
||||
FontWeight="SemiBold" VerticalAlignment="Center" HorizontalAlignment="Right"/>
|
||||
<TextBlock Text="JPEG 품질" Style="{StaticResource FsLabelStyle}"/>
|
||||
<Border Grid.Column="1" CornerRadius="4"
|
||||
Background="{StaticResource FsBgInput}"
|
||||
BorderBrush="{StaticResource FsBorderSubtle}"
|
||||
BorderThickness="1" Padding="6,1">
|
||||
<TextBlock x:Name="QualityValueText"
|
||||
FontFamily="{StaticResource FsFontMono}" FontSize="11"
|
||||
Foreground="{StaticResource FsAccentBlue}"
|
||||
Text="92"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
<Slider x:Name="QualitySlider" Margin="0,8,0,0"
|
||||
Style="{StaticResource FsSliderStyle}"
|
||||
Minimum="50" Maximum="100" Value="92"
|
||||
ValueChanged="OnQualityChanged"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 출력 위치 -->
|
||||
<StackPanel Grid.Column="1" Margin="8,0,8,0">
|
||||
<TextBlock Text="출력 위치" Style="{StaticResource TextCaption}"/>
|
||||
<ComboBox x:Name="OutputModeCombo" SelectedIndex="0" Margin="0,2,0,0">
|
||||
<ComboBoxItem Content="원본 폴더 안 _jpeg 하위 폴더"/>
|
||||
<ComboBoxItem Content="원본과 같은 폴더"/>
|
||||
<ComboBoxItem Content="사용자 지정 폴더…"/>
|
||||
</ComboBox>
|
||||
<StackPanel Grid.Column="1" Margin="6,0,6,0">
|
||||
<TextBlock Text="출력 위치" 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="OutputSubBtn" Content="하위"
|
||||
Style="{StaticResource FsSegmentStyle}"
|
||||
IsChecked="True"
|
||||
Click="OnOutputSegmentClick" Tag="Sub"
|
||||
ToolTip="원본 폴더 안 _jpeg 하위 폴더"/>
|
||||
<ToggleButton x:Name="OutputSameBtn" Content="동일"
|
||||
Style="{StaticResource FsSegmentStyle}"
|
||||
Click="OnOutputSegmentClick" Tag="Same"
|
||||
ToolTip="원본과 같은 폴더"/>
|
||||
<ToggleButton x:Name="OutputCustomBtn" Content="지정"
|
||||
Style="{StaticResource FsSegmentStyle}"
|
||||
Click="OnOutputSegmentClick" Tag="Custom"
|
||||
ToolTip="사용자 지정 폴더"/>
|
||||
</UniformGrid>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 충돌 -->
|
||||
<StackPanel Grid.Column="2" Margin="8,0,0,0">
|
||||
<TextBlock Text="이름 충돌" Style="{StaticResource TextCaption}"/>
|
||||
<ComboBox x:Name="CollisionCombo" SelectedIndex="0" Margin="0,2,0,0">
|
||||
<ComboBoxItem Content="번호 붙여 새 파일"/>
|
||||
<ComboBoxItem Content="덮어쓰기"/>
|
||||
<ComboBoxItem Content="건너뛰기"/>
|
||||
</ComboBox>
|
||||
<!-- 이름 충돌 -->
|
||||
<StackPanel Grid.Column="2" Margin="12,0,0,0">
|
||||
<TextBlock Text="이름 충돌" 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="ConflictRenameBtn" Content="번호"
|
||||
Style="{StaticResource FsSegmentStyle}"
|
||||
IsChecked="True"
|
||||
Click="OnConflictSegmentClick" Tag="Rename"/>
|
||||
<ToggleButton x:Name="ConflictReplaceBtn" Content="덮어쓰기"
|
||||
Style="{StaticResource FsSegmentStyle}"
|
||||
Click="OnConflictSegmentClick" Tag="Replace"/>
|
||||
<ToggleButton x:Name="ConflictSkipBtn" Content="건너뛰기"
|
||||
Style="{StaticResource FsSegmentStyle}"
|
||||
Click="OnConflictSegmentClick" Tag="Skip"/>
|
||||
</UniformGrid>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- 사용자 지정 폴더 (옵션) -->
|
||||
<Grid Margin="0,8,0,0" x:Name="CustomFolderRow" Visibility="Collapsed">
|
||||
<!-- 사용자 지정 폴더 -->
|
||||
<Grid Margin="0,12,0,0" x:Name="CustomFolderRow" Visibility="Collapsed">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBox x:Name="CustomFolderTextBox"/>
|
||||
<ui:Button Grid.Column="1" Content="찾아보기" Margin="6,0,0,0" Click="OnBrowseClick"/>
|
||||
<TextBox x:Name="CustomFolderTextBox"
|
||||
Style="{StaticResource FsPathInputStyle}"/>
|
||||
<Button Grid.Column="1" Margin="8,0,0,0" Width="40"
|
||||
Content="…" Style="{StaticResource FsSecondaryButtonStyle}"
|
||||
Click="OnBrowseClick"/>
|
||||
</Grid>
|
||||
|
||||
<!-- 고급 옵션 expander -->
|
||||
<Expander Header="고급 옵션" Margin="0,8,0,0" IsExpanded="False"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}">
|
||||
<Grid Margin="0,8,0,0">
|
||||
<!-- 고급 옵션 -->
|
||||
<Expander Margin="0,12,0,0" IsExpanded="False"
|
||||
Foreground="{StaticResource FsTextSecondary}">
|
||||
<Expander.Header>
|
||||
<TextBlock Text="고급 옵션" Style="{StaticResource FsLabelStyle}"/>
|
||||
</Expander.Header>
|
||||
<Grid Margin="0,12,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel Grid.Column="0" Margin="0,0,8,0">
|
||||
<TextBlock Text="긴 변 최대 픽셀" Style="{StaticResource TextCaption}"
|
||||
<StackPanel Grid.Column="0" Margin="0,0,12,0">
|
||||
<TextBlock Text="긴 변 최대 픽셀" Style="{StaticResource FsLabelStyle}"
|
||||
ToolTip="비워 두면 원본 크기 유지"/>
|
||||
<TextBox x:Name="MaxLongEdgeTextBox" Margin="0,2,0,0"
|
||||
ToolTip="비워 두면 원본 크기 유지"/>
|
||||
<TextBox x:Name="MaxLongEdgeTextBox" Margin="0,8,0,0"
|
||||
Style="{StaticResource FsPathInputStyle}"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Column="1" Margin="8,0,8,0">
|
||||
<TextBlock Text="PDF 렌더 DPI" Style="{StaticResource TextCaption}"/>
|
||||
<Grid Margin="0,2,0,0">
|
||||
<StackPanel Grid.Column="1" Margin="6,0,12,0">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="32"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Slider x:Name="DpiSlider" Minimum="72" Maximum="450" Value="200"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding ElementName=DpiSlider, Path=Value, StringFormat={}{0:0}}"
|
||||
VerticalAlignment="Center" HorizontalAlignment="Right" FontWeight="SemiBold"/>
|
||||
<TextBlock Text="PDF 렌더 DPI" Style="{StaticResource FsLabelStyle}"/>
|
||||
<TextBlock Grid.Column="1"
|
||||
FontFamily="{StaticResource FsFontMono}" FontSize="11"
|
||||
Foreground="{StaticResource FsAccentBlue}"
|
||||
Text="{Binding ElementName=DpiSlider, Path=Value, StringFormat={}{0:0}}"/>
|
||||
</Grid>
|
||||
<Slider x:Name="DpiSlider" Margin="0,8,0,0"
|
||||
Style="{StaticResource FsSliderStyle}"
|
||||
Minimum="72" Maximum="450" Value="200"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Column="2" Margin="8,0,0,0" VerticalAlignment="Bottom">
|
||||
<CheckBox x:Name="FlattenCheckBox" Content="투명을 흰색으로"
|
||||
IsChecked="True" Margin="0,0,0,4"/>
|
||||
</StackPanel>
|
||||
<CheckBox Grid.Column="2" x:Name="FlattenCheckBox"
|
||||
Content="투명을 흰색으로"
|
||||
IsChecked="True" VerticalAlignment="Bottom"
|
||||
Foreground="{StaticResource FsTextSecondary}"
|
||||
FontFamily="{StaticResource FsFontSans}"
|
||||
FontSize="12"/>
|
||||
</Grid>
|
||||
</Expander>
|
||||
</StackPanel>
|
||||
</ui:Card>
|
||||
</Border>
|
||||
|
||||
<!-- 3: 파일 리스트 (메인 — 세로로 끝까지) -->
|
||||
<ui:Card Grid.Row="3" Margin="32,0,32,0" Padding="0">
|
||||
<!-- 3: 파일 리스트 -->
|
||||
<Border Grid.Row="3" Margin="32,0,32,0"
|
||||
Background="{StaticResource FsBgPanel}"
|
||||
BorderBrush="{StaticResource FsBorderSubtle}"
|
||||
BorderThickness="1" CornerRadius="8">
|
||||
<DockPanel>
|
||||
<Border DockPanel.Dock="Top" Padding="20,12,20,8"
|
||||
BorderBrush="{DynamicResource ControlStrokeColorDefaultBrush}"
|
||||
<Border DockPanel.Dock="Top" Padding="20,14,20,10"
|
||||
BorderBrush="{StaticResource FsBorderSubtle}"
|
||||
BorderThickness="0,0,0,1">
|
||||
<TextBlock Text="대상 파일" Style="{StaticResource TextBodyStrong}"/>
|
||||
<TextBlock Text="대상 파일" Style="{StaticResource FsLabelStyle}"/>
|
||||
</Border>
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto" Padding="20,8,20,16">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto" Padding="20,12,20,16">
|
||||
<ItemsControl x:Name="FilesList">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border Margin="0,0,0,6" Padding="12,8" CornerRadius="6"
|
||||
Background="{DynamicResource SubtleFillColorTransparentBrush}"
|
||||
BorderBrush="{DynamicResource ControlStrokeColorDefaultBrush}"
|
||||
<Border Margin="0,0,0,8" Padding="12,10" CornerRadius="6"
|
||||
Background="{StaticResource FsBgSurface}"
|
||||
BorderBrush="{StaticResource FsBorderSubtle}"
|
||||
BorderThickness="1">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="44"/>
|
||||
<ColumnDefinition Width="40"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Width="40" Height="40" CornerRadius="4"
|
||||
Background="{DynamicResource ControlFillColorDefaultBrush}">
|
||||
<Image Source="{Binding Thumbnail}" Stretch="UniformToFill"/>
|
||||
|
||||
<Border Width="32" Height="32" CornerRadius="4"
|
||||
Background="{Binding FormatBrush}"
|
||||
VerticalAlignment="Center">
|
||||
<Grid>
|
||||
<Image Source="{Binding Thumbnail}" Stretch="UniformToFill"/>
|
||||
<TextBlock Text="{Binding FormatLabel}"
|
||||
Foreground="White" FontSize="9"
|
||||
FontWeight="SemiBold"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Visibility="{Binding ShowFormatLabel}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
<StackPanel Grid.Column="1" Margin="12,0,8,0" VerticalAlignment="Center">
|
||||
<TextBlock Text="{Binding FileName}" FontWeight="SemiBold"
|
||||
|
||||
<StackPanel Grid.Column="1" Margin="14,0,8,0"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock Text="{Binding FileName}"
|
||||
FontFamily="{StaticResource FsFontSans}"
|
||||
FontSize="13" FontWeight="Medium"
|
||||
Foreground="{StaticResource FsTextPrimary}"
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
<TextBlock Text="{Binding SubText}" FontSize="11"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
<TextBlock Text="{Binding SubText}"
|
||||
FontSize="11" Margin="0,2,0,0"
|
||||
Foreground="{StaticResource FsTextTertiary}"
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
<TextBlock Text="{Binding State}" FontSize="11" Margin="0,2,0,0">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Foreground"
|
||||
Value="{DynamicResource TextFillColorTertiaryBrush}"/>
|
||||
Value="{StaticResource FsTextTertiary}"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsFailed}" Value="True">
|
||||
<Setter Property="Foreground" Value="#F87171"/>
|
||||
|
|
@ -184,9 +269,19 @@
|
|||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<ui:Button Grid.Column="2" Icon="{ui:SymbolIcon Dismiss20}"
|
||||
Appearance="Transparent" Click="OnRemoveEntry"
|
||||
ToolTip="목록에서 제거"/>
|
||||
|
||||
<Button Grid.Column="2"
|
||||
Style="{StaticResource FsSecondaryButtonStyle}"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Padding="6"
|
||||
Click="OnRemoveEntry"
|
||||
ToolTip="목록에서 제거">
|
||||
<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>
|
||||
|
|
@ -194,31 +289,33 @@
|
|||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</DockPanel>
|
||||
</ui:Card>
|
||||
</Border>
|
||||
|
||||
<!-- 4: 액션 바 (하단 고정) -->
|
||||
<Border Grid.Row="4" Background="{DynamicResource LayerOnAcrylicFillColorDefaultBrush}"
|
||||
BorderThickness="0,1,0,0"
|
||||
BorderBrush="{DynamicResource ControlStrokeColorDefaultBrush}"
|
||||
Padding="32,16" Margin="0,16,0,0">
|
||||
<!-- 4: 액션바 -->
|
||||
<Border Grid.Row="4" Padding="32,16,32,20" Margin="0,16,0,0"
|
||||
BorderBrush="{StaticResource FsBorderSubtle}" BorderThickness="0,1,0,0"
|
||||
Background="{StaticResource FsBgPanel}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel VerticalAlignment="Center">
|
||||
<ProgressBar x:Name="OverallProgress" Height="6" Minimum="0" Maximum="1"
|
||||
IsIndeterminate="False"/>
|
||||
<TextBlock x:Name="ProgressStatusText" Style="{StaticResource TextCaption}"
|
||||
Margin="0,6,0,0" TextTrimming="CharacterEllipsis"/>
|
||||
<ProgressBar x:Name="OverallProgress" Height="3"
|
||||
Background="{StaticResource FsBgInput}"
|
||||
Foreground="{StaticResource FsAccentBlue}"
|
||||
Minimum="0" Maximum="1"/>
|
||||
<TextBlock x:Name="ProgressStatusText" Margin="0,8,0,0"
|
||||
Style="{StaticResource FsCaptionStyle}"
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" Margin="20,0,0,0">
|
||||
<ui:Button x:Name="CancelButton" Content="닫기" Click="OnCancelClick"
|
||||
Margin="0,0,8,0" Padding="20,6"/>
|
||||
<ui:Button x:Name="ConvertButton" Content="변환 시작"
|
||||
Icon="{ui:SymbolIcon ArrowRight24}"
|
||||
Appearance="Primary" Click="OnConvertClick"
|
||||
Padding="24,6"/>
|
||||
<Button x:Name="CancelButton" Content="닫기" Click="OnCancelClick"
|
||||
Margin="0,0,8,0" MinWidth="80"
|
||||
Style="{StaticResource FsSecondaryButtonStyle}"/>
|
||||
<Button x:Name="ConvertButton" Content="→ 변환 시작"
|
||||
Click="OnConvertClick" MinWidth="120"
|
||||
Style="{StaticResource FsPrimaryButtonStyle}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
|
|
|||
|
|
@ -1,35 +1,46 @@
|
|||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using EverythingToJpeg.Core;
|
||||
using EverythingToJpeg.Core.Providers;
|
||||
using Wpf.Ui.Controls;
|
||||
|
||||
namespace EverythingToJpeg.App.Views;
|
||||
|
||||
public partial class ConvertWindow : FluentWindow
|
||||
{
|
||||
private static readonly string DialogLogPath =
|
||||
Path.Combine(Path.GetTempPath(), "EverythingToJpeg_dialog.log");
|
||||
|
||||
private readonly ConversionEngine _engine;
|
||||
private readonly ObservableCollection<FileEntry> _entries = new();
|
||||
private readonly ObservableCollection<ConvertFileEntry> _entries = new();
|
||||
private CancellationTokenSource? _cts;
|
||||
private OutputLocation _outputMode = OutputLocation.SubfolderBesideSource;
|
||||
private NameCollision _conflictRule = NameCollision.AppendNumber;
|
||||
|
||||
public ConvertWindow(ConversionEngine engine, IReadOnlyList<string> initialFiles)
|
||||
{
|
||||
_engine = engine;
|
||||
InitializeComponent();
|
||||
|
||||
FilesList.ItemsSource = _entries;
|
||||
|
||||
AddFiles(initialFiles);
|
||||
|
||||
OutputModeCombo.SelectionChanged += (_, _) =>
|
||||
CustomFolderRow.Visibility = OutputModeCombo.SelectedIndex == 2
|
||||
? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private static void DiagLog(string line)
|
||||
{
|
||||
try { File.AppendAllText(DialogLogPath,
|
||||
$"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] {line}{Environment.NewLine}"); }
|
||||
catch { }
|
||||
}
|
||||
|
||||
// ============== Files ==============
|
||||
|
||||
private void AddFiles(IEnumerable<string> paths)
|
||||
{
|
||||
var existing = new HashSet<string>(_entries.Select(e => e.Path), StringComparer.OrdinalIgnoreCase);
|
||||
|
|
@ -38,7 +49,7 @@ public partial class ConvertWindow : FluentWindow
|
|||
if (!File.Exists(p)) continue;
|
||||
if (existing.Contains(p)) continue;
|
||||
|
||||
var entry = new FileEntry(p, _engine);
|
||||
var entry = ConvertFileEntry.From(p, _engine);
|
||||
_entries.Add(entry);
|
||||
_ = entry.LoadThumbnailAsync();
|
||||
}
|
||||
|
|
@ -85,7 +96,7 @@ public partial class ConvertWindow : FluentWindow
|
|||
|
||||
private void OnRemoveEntry(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is FrameworkElement fe && fe.DataContext is FileEntry entry)
|
||||
if (sender is FrameworkElement fe && fe.DataContext is ConvertFileEntry entry)
|
||||
{
|
||||
_entries.Remove(entry);
|
||||
UpdateSummary();
|
||||
|
|
@ -105,33 +116,74 @@ public partial class ConvertWindow : FluentWindow
|
|||
{
|
||||
if (File.Exists(p)) yield return p;
|
||||
else if (Directory.Exists(p))
|
||||
{
|
||||
foreach (var f in Directory.EnumerateFiles(p, "*", SearchOption.TopDirectoryOnly))
|
||||
yield return f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly string DialogLogPath =
|
||||
Path.Combine(Path.GetTempPath(), "EverythingToJpeg_dialog.log");
|
||||
// ============== Segments ==============
|
||||
|
||||
private static void DiagLog(string line)
|
||||
private void OnQualityChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.AppendAllText(DialogLogPath,
|
||||
$"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] {line}{Environment.NewLine}");
|
||||
}
|
||||
catch { }
|
||||
if (QualityValueText is null) return;
|
||||
QualityValueText.Text = ((int)e.NewValue).ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
private void OnOutputSegmentClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is not ToggleButton clicked) return;
|
||||
OutputSubBtn.IsChecked = clicked == OutputSubBtn;
|
||||
OutputSameBtn.IsChecked = clicked == OutputSameBtn;
|
||||
OutputCustomBtn.IsChecked = clicked == OutputCustomBtn;
|
||||
|
||||
_outputMode = (clicked.Tag as string) switch
|
||||
{
|
||||
"Same" => OutputLocation.SameFolderAsSource,
|
||||
"Custom" => OutputLocation.Custom,
|
||||
_ => OutputLocation.SubfolderBesideSource,
|
||||
};
|
||||
CustomFolderRow.Visibility = _outputMode == OutputLocation.Custom
|
||||
? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void OnConflictSegmentClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is not ToggleButton clicked) return;
|
||||
ConflictRenameBtn.IsChecked = clicked == ConflictRenameBtn;
|
||||
ConflictReplaceBtn.IsChecked = clicked == ConflictReplaceBtn;
|
||||
ConflictSkipBtn.IsChecked = clicked == ConflictSkipBtn;
|
||||
_conflictRule = (clicked.Tag as string) switch
|
||||
{
|
||||
"Skip" => NameCollision.Skip,
|
||||
"Replace" => NameCollision.Overwrite,
|
||||
_ => NameCollision.AppendNumber,
|
||||
};
|
||||
}
|
||||
|
||||
private ConvertOptions BuildOptions()
|
||||
{
|
||||
var opts = new ConvertOptions
|
||||
{
|
||||
Quality = (int)QualitySlider.Value,
|
||||
PdfDpi = (int)DpiSlider.Value,
|
||||
FlattenTransparency = FlattenCheckBox.IsChecked == true,
|
||||
OutputLocation = _outputMode,
|
||||
OnCollision = _conflictRule,
|
||||
};
|
||||
if (_outputMode == OutputLocation.Custom)
|
||||
opts.CustomOutputDirectory = CustomFolderTextBox.Text;
|
||||
if (int.TryParse(MaxLongEdgeTextBox.Text, out var maxEdge) && maxEdge > 0)
|
||||
opts.MaxLongEdgePixels = maxEdge;
|
||||
return opts;
|
||||
}
|
||||
|
||||
// ============== Convert ==============
|
||||
|
||||
private async void OnConvertClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DiagLog($"OnConvertClick: entries={_entries.Count}");
|
||||
|
||||
if (_entries.Count == 0)
|
||||
{
|
||||
DiagLog(" → no entries, showing info");
|
||||
ShowInfo("변환할 파일이 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
|
@ -145,7 +197,7 @@ public partial class ConvertWindow : FluentWindow
|
|||
try
|
||||
{
|
||||
options = BuildOptions();
|
||||
DiagLog($" options: Quality={options.Quality} OutputLocation={options.OutputLocation} Custom={options.CustomOutputDirectory} Collision={options.OnCollision} MaxLong={options.MaxLongEdgePixels} PdfDpi={options.PdfDpi}");
|
||||
DiagLog($" options: Quality={options.Quality} OutputLocation={options.OutputLocation} Custom={options.CustomOutputDirectory} Collision={options.OnCollision}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -171,24 +223,19 @@ public partial class ConvertWindow : FluentWindow
|
|||
DiagLog($" starting ConvertManyAsync, {sources.Count} files");
|
||||
var results = await _engine.ConvertManyAsync(sources, options, reporter, _cts.Token);
|
||||
DiagLog($" finished, {results.Count} results");
|
||||
foreach (var r in results)
|
||||
DiagLog($" [{r.Status}] {Path.GetFileName(r.SourcePath)} msg={r.Message}");
|
||||
ApplyResults(results);
|
||||
ProgressStatusText.Text = SummarizeResults(results);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
DiagLog(" canceled");
|
||||
ProgressStatusText.Text = "변환이 취소되었습니다.";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DiagLog(" EXCEPTION: " + ex);
|
||||
ProgressStatusText.Text = "오류: " + ex.Message;
|
||||
MessageBox.Show(this,
|
||||
"변환 중 오류가 발생했습니다:\n\n" + ex.Message + "\n\n로그: " + DialogLogPath,
|
||||
"EverythingToJpeg",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
MessageBox.Show(this, "변환 중 오류:\n\n" + ex.Message + "\n\n로그: " + DialogLogPath,
|
||||
"EverythingToJpeg", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
@ -241,84 +288,68 @@ public partial class ConvertWindow : FluentWindow
|
|||
Close();
|
||||
}
|
||||
|
||||
private ConvertOptions BuildOptions()
|
||||
{
|
||||
var opts = new ConvertOptions
|
||||
{
|
||||
Quality = (int)QualitySlider.Value,
|
||||
PdfDpi = (int)DpiSlider.Value,
|
||||
FlattenTransparency = FlattenCheckBox.IsChecked == true,
|
||||
};
|
||||
opts.OutputLocation = OutputModeCombo.SelectedIndex switch
|
||||
{
|
||||
1 => OutputLocation.SameFolderAsSource,
|
||||
2 => OutputLocation.Custom,
|
||||
_ => OutputLocation.SubfolderBesideSource,
|
||||
};
|
||||
if (opts.OutputLocation == OutputLocation.Custom)
|
||||
opts.CustomOutputDirectory = CustomFolderTextBox.Text;
|
||||
|
||||
opts.OnCollision = CollisionCombo.SelectedIndex switch
|
||||
{
|
||||
1 => NameCollision.Overwrite,
|
||||
2 => NameCollision.Skip,
|
||||
_ => NameCollision.AppendNumber,
|
||||
};
|
||||
|
||||
if (int.TryParse(MaxLongEdgeTextBox.Text, out var maxEdge) && maxEdge > 0)
|
||||
opts.MaxLongEdgePixels = maxEdge;
|
||||
|
||||
return opts;
|
||||
}
|
||||
|
||||
private void ShowInfo(string message)
|
||||
=> MessageBox.Show(this, message, "EverythingToJpeg",
|
||||
MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
|
||||
}
|
||||
|
||||
public sealed class FileEntry : System.ComponentModel.INotifyPropertyChanged
|
||||
public sealed class ConvertFileEntry : INotifyPropertyChanged
|
||||
{
|
||||
private readonly ConversionEngine _engine;
|
||||
private string _state = "대기";
|
||||
private bool _isFailed;
|
||||
private ImageSource? _thumbnail;
|
||||
|
||||
public FileEntry(string path, ConversionEngine engine)
|
||||
public required string Path { get; init; }
|
||||
public required string FileName { get; init; }
|
||||
public required string SubText { get; init; }
|
||||
public required string FormatLabel { get; init; }
|
||||
public required Brush FormatBrush { get; init; }
|
||||
|
||||
public string State
|
||||
{
|
||||
Path = path;
|
||||
_engine = engine;
|
||||
get => _state;
|
||||
set { _state = value; Raise(nameof(State)); }
|
||||
}
|
||||
|
||||
public string Path { get; }
|
||||
public string FileName => System.IO.Path.GetFileName(Path);
|
||||
|
||||
public string SubText
|
||||
public bool IsFailed
|
||||
{
|
||||
get
|
||||
get => _isFailed;
|
||||
set { _isFailed = value; Raise(nameof(IsFailed)); }
|
||||
}
|
||||
|
||||
public ImageSource? Thumbnail
|
||||
{
|
||||
get => _thumbnail;
|
||||
set { _thumbnail = value; Raise(nameof(Thumbnail)); Raise(nameof(ShowFormatLabel)); }
|
||||
}
|
||||
|
||||
public Visibility ShowFormatLabel => _thumbnail is null ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
public static ConvertFileEntry From(string path, ConversionEngine engine)
|
||||
{
|
||||
var ext = System.IO.Path.GetExtension(path).TrimStart('.').ToLowerInvariant();
|
||||
var (label, brushKey) = FormatPalette.For(ext);
|
||||
|
||||
string handler;
|
||||
if (engine.Providers.TryGetForFile(path, out var provider) && provider is not null)
|
||||
handler = provider.Capability.DisplayName;
|
||||
else
|
||||
handler = "지원되지 않음";
|
||||
|
||||
long size = 0;
|
||||
try { size = new FileInfo(path).Length; } catch { }
|
||||
var sub = $".{ext} · {handler} · {MainWindow.HumanizeBytes(size)}";
|
||||
|
||||
return new ConvertFileEntry
|
||||
{
|
||||
var ext = System.IO.Path.GetExtension(Path).ToLowerInvariant();
|
||||
string handler;
|
||||
if (_engine.Providers.TryGetForFile(Path, out var provider) && provider is not null)
|
||||
handler = provider.Capability.DisplayName;
|
||||
else
|
||||
handler = "지원되지 않음";
|
||||
try
|
||||
{
|
||||
var size = new FileInfo(Path).Length;
|
||||
return $"{ext} · {handler} · {FormatBytes(size)}";
|
||||
}
|
||||
catch
|
||||
{
|
||||
return $"{ext} · {handler}";
|
||||
}
|
||||
}
|
||||
Path = path,
|
||||
FileName = System.IO.Path.GetFileName(path),
|
||||
SubText = sub,
|
||||
FormatLabel = label,
|
||||
FormatBrush = (Brush)Application.Current.FindResource(brushKey),
|
||||
};
|
||||
}
|
||||
|
||||
public string State { get => _state; set { _state = value; Raise(nameof(State)); } }
|
||||
public bool IsFailed { get => _isFailed; set { _isFailed = value; Raise(nameof(IsFailed)); } }
|
||||
public ImageSource? Thumbnail { get => _thumbnail; set { _thumbnail = value; Raise(nameof(Thumbnail)); } }
|
||||
|
||||
public Task LoadThumbnailAsync() => Task.Run(() =>
|
||||
{
|
||||
try
|
||||
|
|
@ -333,21 +364,13 @@ public sealed class FileEntry : System.ComponentModel.INotifyPropertyChanged
|
|||
bmp.CacheOption = BitmapCacheOption.OnLoad;
|
||||
bmp.EndInit();
|
||||
bmp.Freeze();
|
||||
System.Windows.Application.Current.Dispatcher.Invoke(() => Thumbnail = bmp);
|
||||
Application.Current.Dispatcher.Invoke(() => Thumbnail = bmp);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
});
|
||||
|
||||
private static string FormatBytes(long bytes)
|
||||
{
|
||||
string[] units = { "B", "KB", "MB", "GB" };
|
||||
double size = bytes;
|
||||
var unit = 0;
|
||||
while (size >= 1024 && unit < units.Length - 1) { size /= 1024; unit++; }
|
||||
return $"{size:0.#} {units[unit]}";
|
||||
}
|
||||
|
||||
public event System.ComponentModel.PropertyChangedEventHandler? PropertyChanged;
|
||||
private void Raise(string n) => PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(n));
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
private void Raise([CallerMemberName] string? n = null)
|
||||
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(n));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -246,18 +246,6 @@
|
|||
</Border>
|
||||
</StackPanel>
|
||||
</ToggleButton>
|
||||
<ToggleButton x:Name="TabPreviewBtn" Margin="8,0,0,0"
|
||||
Style="{StaticResource FsTabPillStyle}"
|
||||
Click="OnTabClick" Tag="Preview">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="Preview" VerticalAlignment="Center"/>
|
||||
<Border Margin="8,0,0,0" Style="{StaticResource FsTabBadgeStyle}">
|
||||
<TextBlock x:Name="TabPreviewBadge" 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}"
|
||||
|
|
@ -292,8 +280,15 @@
|
|||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- View content -->
|
||||
<!-- 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">
|
||||
|
|
@ -323,7 +318,7 @@
|
|||
BorderBrush="{StaticResource FsBorderSubtle}"
|
||||
BorderThickness="1"
|
||||
Cursor="Hand"
|
||||
MouseLeftButtonUp="OnQueueRowClick"
|
||||
PreviewMouseLeftButtonUp="OnQueueRowClick"
|
||||
Tag="{Binding}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
|
|
@ -381,129 +376,6 @@
|
|||
</ScrollViewer>
|
||||
</Grid>
|
||||
|
||||
<!-- Preview view -->
|
||||
<Grid x:Name="PreviewView" Visibility="Collapsed" Background="{StaticResource FsBgBase}">
|
||||
<Grid Margin="32">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="320"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Image area -->
|
||||
<Border Background="{StaticResource FsBgPanel}"
|
||||
BorderBrush="{StaticResource FsBorderSubtle}"
|
||||
BorderThickness="1" CornerRadius="8">
|
||||
<Grid>
|
||||
<!-- Empty state -->
|
||||
<StackPanel x:Name="PreviewEmpty" HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<Path Width="40" Height="40" 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="14" FontWeight="SemiBold"
|
||||
Foreground="{StaticResource FsTextSecondary}"
|
||||
HorizontalAlignment="Center"
|
||||
Text="No preview selected"/>
|
||||
<TextBlock Margin="0,4,0,0" Style="{StaticResource FsCaptionStyle}"
|
||||
HorizontalAlignment="Center" TextAlignment="Center"
|
||||
Text="Active Queue 항목을 클릭하면
여기에 미리보기가 표시됩니다."/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Loading -->
|
||||
<StackPanel x:Name="PreviewLoading" Visibility="Collapsed"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<ProgressBar IsIndeterminate="True" Width="200" Height="3"
|
||||
Foreground="{StaticResource FsAccentBlue}"
|
||||
Background="{StaticResource FsBgInput}"/>
|
||||
<TextBlock Margin="0,12,0,0" Style="{StaticResource FsCaptionStyle}"
|
||||
HorizontalAlignment="Center"
|
||||
Text="미리보기 생성 중…"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Image -->
|
||||
<Image x:Name="PreviewImage" Stretch="Uniform" Margin="24"
|
||||
Visibility="Collapsed"/>
|
||||
|
||||
<!-- Reason / fallback -->
|
||||
<StackPanel x:Name="PreviewReason" Visibility="Collapsed"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<Path Width="36" Height="36" 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}"
|
||||
HorizontalAlignment="Center" TextAlignment="Center"
|
||||
TextWrapping="Wrap" MaxWidth="360"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Meta panel -->
|
||||
<Border Grid.Column="1" Margin="16,0,0,0"
|
||||
Background="{StaticResource FsBgPanel}"
|
||||
BorderBrush="{StaticResource FsBorderSubtle}"
|
||||
BorderThickness="1" CornerRadius="8" Padding="20">
|
||||
<StackPanel>
|
||||
<TextBlock Text="FILE DETAIL" Style="{StaticResource FsLabelStyle}"/>
|
||||
<TextBlock x:Name="PreviewFileName" Margin="0,8,0,0"
|
||||
Style="{StaticResource FsBodyStyle}"
|
||||
FontWeight="SemiBold" TextWrapping="Wrap"
|
||||
Text="—"/>
|
||||
<TextBlock x:Name="PreviewFilePath" Margin="0,4,0,0"
|
||||
FontFamily="{StaticResource FsFontMono}" FontSize="11"
|
||||
Foreground="{StaticResource FsTextTertiary}"
|
||||
TextWrapping="Wrap"/>
|
||||
|
||||
<Border Margin="0,16,0,16" 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,6"/>
|
||||
<TextBlock x:Name="PreviewFormatText" Grid.Row="0" Grid.Column="1"
|
||||
FontFamily="{StaticResource FsFontMono}" FontSize="12"
|
||||
Foreground="{StaticResource FsTextPrimary}" Margin="0,0,0,6"/>
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Text="Size"
|
||||
Style="{StaticResource FsCaptionStyle}" Margin="0,0,0,6"/>
|
||||
<TextBlock x:Name="PreviewSizeText" Grid.Row="1" Grid.Column="1"
|
||||
FontFamily="{StaticResource FsFontMono}" FontSize="12"
|
||||
Foreground="{StaticResource FsTextPrimary}" Margin="0,0,0,6"/>
|
||||
|
||||
<TextBlock Grid.Row="2" Grid.Column="0" Text="Dimensions"
|
||||
Style="{StaticResource FsCaptionStyle}" Margin="0,0,0,6"/>
|
||||
<TextBlock x:Name="PreviewDimText" Grid.Row="2" Grid.Column="1"
|
||||
FontFamily="{StaticResource FsFontMono}" FontSize="12"
|
||||
Foreground="{StaticResource FsTextPrimary}" Text="—"
|
||||
Margin="0,0,0,6"/>
|
||||
|
||||
<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>
|
||||
|
||||
<Border Margin="0,20,0,16" Height="1" Background="{StaticResource FsBorderSubtle}"/>
|
||||
|
||||
<Button Content="Open in Explorer"
|
||||
Style="{StaticResource FsSecondaryButtonStyle}"
|
||||
Click="OnPreviewOpenFolder"
|
||||
HorizontalAlignment="Stretch"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<!-- Past Results view (default) -->
|
||||
<ScrollViewer x:Name="PastResultsView" VerticalScrollBarVisibility="Auto"
|
||||
Padding="24">
|
||||
|
|
@ -537,7 +409,11 @@
|
|||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border BorderBrush="#0DFFFFFF"
|
||||
BorderThickness="0,0,0,1" Padding="0,12">
|
||||
BorderThickness="0,0,0,1" Padding="0,12"
|
||||
Cursor="Hand"
|
||||
Background="Transparent"
|
||||
PreviewMouseLeftButtonUp="OnPastRowClick"
|
||||
Tag="{Binding}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="40"/>
|
||||
|
|
@ -617,8 +493,154 @@
|
|||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
|
||||
<!-- Drop hint overlay -->
|
||||
<Border x:Name="DropHintOverlay" Visibility="Collapsed"
|
||||
</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"
|
||||
|
|
|
|||
|
|
@ -20,7 +20,9 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow
|
|||
private CancellationTokenSource? _cts;
|
||||
private NameCollision _conflictRule = NameCollision.AppendNumber;
|
||||
|
||||
public MainWindow()
|
||||
public MainWindow() : this(null) { }
|
||||
|
||||
public MainWindow(IReadOnlyList<string>? initialFiles)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
|
|
@ -30,9 +32,18 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow
|
|||
SeedDemoHistory();
|
||||
UpdateBadges();
|
||||
UpdateProcessQueueButton();
|
||||
ShowTab("Past");
|
||||
UpdateActiveQueueVisibility();
|
||||
|
||||
if (initialFiles is { Count: > 0 })
|
||||
{
|
||||
AddToQueue(initialFiles);
|
||||
ShowTab("Active");
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowTab("Past");
|
||||
}
|
||||
|
||||
UpdateActiveQueueVisibility();
|
||||
ApplyAppDataStats();
|
||||
}
|
||||
|
||||
|
|
@ -49,11 +60,9 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow
|
|||
private void ShowTab(string tag)
|
||||
{
|
||||
TabActiveBtn.IsChecked = tag == "Active";
|
||||
TabPreviewBtn.IsChecked = tag == "Preview";
|
||||
TabPastBtn.IsChecked = tag == "Past";
|
||||
|
||||
ActiveQueueView.Visibility = tag == "Active" ? Visibility.Visible : Visibility.Collapsed;
|
||||
PreviewView.Visibility = tag == "Preview" ? Visibility.Visible : Visibility.Collapsed;
|
||||
PastResultsView.Visibility = tag == "Past" ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
|
|
@ -98,6 +107,7 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow
|
|||
|
||||
private void AddToQueue(IEnumerable<string> paths)
|
||||
{
|
||||
var wasEmpty = _activeQueue.Count == 0;
|
||||
var existing = new HashSet<string>(_activeQueue.Select(q => q.SourcePath), StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var path in paths)
|
||||
{
|
||||
|
|
@ -107,6 +117,12 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow
|
|||
UpdateBadges();
|
||||
UpdateProcessQueueButton();
|
||||
UpdateActiveQueueVisibility();
|
||||
|
||||
if (wasEmpty && _activeQueue.Count > 0 && _selectedPreviewItem is null)
|
||||
{
|
||||
_selectedPreviewItem = _activeQueue[0];
|
||||
_ = LoadPreviewAsync(_selectedPreviewItem);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRemoveQueueItem(object sender, RoutedEventArgs e)
|
||||
|
|
@ -130,7 +146,6 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow
|
|||
private void UpdateBadges()
|
||||
{
|
||||
TabActiveBadge.Text = _activeQueue.Count.ToString(CultureInfo.InvariantCulture);
|
||||
TabPreviewBadge.Text = "0";
|
||||
var count = _pastResults.Sum(g => g.Entries.Count);
|
||||
TabPastBadge.Text = count.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
|
@ -211,37 +226,74 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow
|
|||
// ============== Preview ==============
|
||||
|
||||
private QueueItem? _selectedPreviewItem;
|
||||
private string? _selectedPreviewPath;
|
||||
private CancellationTokenSource? _previewCts;
|
||||
|
||||
private async void OnQueueRowClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.OriginalSource is DependencyObject src && IsInsideButton(src)) return;
|
||||
if (sender is not FrameworkElement fe || fe.Tag is not QueueItem item) return;
|
||||
|
||||
_selectedPreviewItem = item;
|
||||
ShowTab("Preview");
|
||||
await LoadPreviewAsync(item);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private async Task LoadPreviewAsync(QueueItem item)
|
||||
private async void OnPastRowClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.OriginalSource is DependencyObject src && IsInsideButton(src)) return;
|
||||
if (sender is not FrameworkElement fe || fe.Tag is not HistoryRow row) return;
|
||||
|
||||
if (row.SourcePath == "<demo>")
|
||||
{
|
||||
SetPreviewMeta(row.FileName, row.SourcePath, row.FormatLabel, row.SizeText);
|
||||
ShowPreviewReason("샘플 데모 항목입니다. 원본 파일이 없으므로 미리보기를 만들 수 없습니다.");
|
||||
_selectedPreviewPath = null;
|
||||
}
|
||||
else if (!File.Exists(row.SourcePath))
|
||||
{
|
||||
SetPreviewMeta(row.FileName, row.SourcePath, row.FormatLabel, row.SizeText);
|
||||
ShowPreviewReason("원본 파일을 찾을 수 없습니다. 파일이 이동·삭제되었을 수 있습니다.");
|
||||
_selectedPreviewPath = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
_selectedPreviewItem = null;
|
||||
_selectedPreviewPath = row.SourcePath;
|
||||
await LoadPreviewByPathAsync(row.SourcePath, row.FileName, row.FormatLabel, row.SizeText);
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private static bool IsInsideButton(DependencyObject? node)
|
||||
{
|
||||
while (node is not null)
|
||||
{
|
||||
if (node is Button) return true;
|
||||
node = System.Windows.Media.VisualTreeHelper.GetParent(node)
|
||||
?? (node is FrameworkElement fe ? fe.Parent : null);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private Task LoadPreviewAsync(QueueItem item)
|
||||
{
|
||||
_selectedPreviewPath = item.SourcePath;
|
||||
return LoadPreviewByPathAsync(item.SourcePath, item.FileName, item.FormatLabel, item.SizeText);
|
||||
}
|
||||
|
||||
private async Task LoadPreviewByPathAsync(string sourcePath, string fileName, string formatLabel, string sizeText)
|
||||
{
|
||||
_previewCts?.Cancel();
|
||||
_previewCts = new CancellationTokenSource();
|
||||
var token = _previewCts.Token;
|
||||
|
||||
PreviewEmpty.Visibility = Visibility.Collapsed;
|
||||
PreviewImage.Visibility = Visibility.Collapsed;
|
||||
PreviewReason.Visibility = Visibility.Collapsed;
|
||||
PreviewLoading.Visibility = Visibility.Visible;
|
||||
|
||||
PreviewFileName.Text = item.FileName;
|
||||
PreviewFilePath.Text = item.SourcePath;
|
||||
PreviewFormatText.Text = item.FormatLabel;
|
||||
PreviewSizeText.Text = item.SizeText;
|
||||
PreviewDimText.Text = "—";
|
||||
PreviewPageText.Text = "—";
|
||||
SetPreviewMeta(fileName, sourcePath, formatLabel, sizeText);
|
||||
ShowPreviewLoading();
|
||||
|
||||
try
|
||||
{
|
||||
var result = await PreviewService.CreateAsync(item.SourcePath, 720, token);
|
||||
var result = await PreviewService.CreateAsync(sourcePath, 720, token);
|
||||
if (token.IsCancellationRequested) return;
|
||||
|
||||
PreviewLoading.Visibility = Visibility.Collapsed;
|
||||
|
|
@ -264,20 +316,47 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow
|
|||
catch (Exception ex)
|
||||
{
|
||||
PreviewLoading.Visibility = Visibility.Collapsed;
|
||||
PreviewReasonText.Text = "미리보기 오류: " + ex.Message;
|
||||
PreviewReason.Visibility = Visibility.Visible;
|
||||
ShowPreviewReason("미리보기 오류: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetPreviewMeta(string fileName, string filePath, string formatLabel, string sizeText)
|
||||
{
|
||||
PreviewFileName.Text = fileName;
|
||||
PreviewFilePath.Text = filePath;
|
||||
PreviewFormatText.Text = formatLabel;
|
||||
PreviewSizeText.Text = sizeText;
|
||||
PreviewDimText.Text = "—";
|
||||
PreviewPageText.Text = "—";
|
||||
}
|
||||
|
||||
private void ShowPreviewLoading()
|
||||
{
|
||||
PreviewEmpty.Visibility = Visibility.Collapsed;
|
||||
PreviewImage.Visibility = Visibility.Collapsed;
|
||||
PreviewReason.Visibility = Visibility.Collapsed;
|
||||
PreviewLoading.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
private void ShowPreviewReason(string reason)
|
||||
{
|
||||
PreviewEmpty.Visibility = Visibility.Collapsed;
|
||||
PreviewImage.Visibility = Visibility.Collapsed;
|
||||
PreviewLoading.Visibility = Visibility.Collapsed;
|
||||
PreviewReasonText.Text = reason;
|
||||
PreviewReason.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
private void OnPreviewOpenFolder(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_selectedPreviewItem is null) return;
|
||||
var path = _selectedPreviewPath ?? _selectedPreviewItem?.SourcePath;
|
||||
if (string.IsNullOrEmpty(path) || !File.Exists(path)) return;
|
||||
try
|
||||
{
|
||||
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
|
||||
{
|
||||
FileName = "explorer.exe",
|
||||
Arguments = $"/select,\"{_selectedPreviewItem.SourcePath}\"",
|
||||
Arguments = $"/select,\"{path}\"",
|
||||
UseShellExecute = true,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue