feat(ui): implement 1-click presets, real-time search & filter bar, batch queue actions, and file inspector
This commit is contained in:
parent
41c8a8a94b
commit
b67b3f1b61
17 changed files with 1240 additions and 38 deletions
|
|
@ -13,6 +13,26 @@ public partial class OptionsViewModel : ObservableObject
|
|||
/// <summary>JPEG/WebP 품질(1~100). AVIF는 -30 보정.</summary>
|
||||
[ObservableProperty] private int _quality = 85;
|
||||
|
||||
/// <summary>EXIF 및 메타데이터 제거 여부.</summary>
|
||||
[ObservableProperty] private bool _stripMetadata;
|
||||
|
||||
public int ImageQuality { get => Quality; set => Quality = value; }
|
||||
public int VideoCrf { get => Crf; set => Crf = value; }
|
||||
public int AudioBitrateKbps
|
||||
{
|
||||
get => AudioBitrateIndex switch { 0 => 96, 1 => 128, 3 => 256, 4 => 320, _ => 192 };
|
||||
set => AudioBitrateIndex = value switch { <= 96 => 0, <= 128 => 1, <= 192 => 2, <= 256 => 3, _ => 4 };
|
||||
}
|
||||
public string VideoPreset
|
||||
{
|
||||
get => ((VideoSpeedPreset)PresetIndex).ToString().ToLowerInvariant();
|
||||
set => PresetIndex = value switch
|
||||
{
|
||||
"ultrafast" => 0, "superfast" => 1, "veryfast" => 2, "faster" => 3,
|
||||
"fast" => 4, "medium" => 5, "slow" => 6, "slower" => 7, "veryslow" => 8, _ => 5
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>비우면 원본 옆 서브폴더. 값이 있으면 사용자 지정 출력 폴더.</summary>
|
||||
[ObservableProperty] private string? _customOutputDirectory;
|
||||
|
||||
|
|
@ -62,6 +82,7 @@ public partial class OptionsViewModel : ObservableObject
|
|||
OnCollision = ConflictRule,
|
||||
OutputLocation = hasCustom ? OutputLocation.Custom : OutputLocation.SubfolderBesideSource,
|
||||
CustomOutputDirectory = hasCustom ? CustomOutputDirectory!.Trim() : null,
|
||||
KeepExifWhenPossible = !StripMetadata,
|
||||
Jpeg = new JpegEncodingOptions { Quality = Quality },
|
||||
Webp = new WebpEncodingOptions { Quality = Quality },
|
||||
Avif = new AvifEncodingOptions { Quality = Math.Clamp(Quality - 30, 1, 100) },
|
||||
|
|
|
|||
50
src/Everything2Everything.App/Views/BatchQueueService.cs
Normal file
50
src/Everything2Everything.App/Views/BatchQueueService.cs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Everything2Everything.App.Views;
|
||||
|
||||
public static class BatchQueueService
|
||||
{
|
||||
public static void SetSelectionAll(IEnumerable<QueueItem> items, bool isSelected)
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
item.IsSelected = isSelected;
|
||||
}
|
||||
}
|
||||
|
||||
public static void RemoveSelected(IList<QueueItem> items)
|
||||
{
|
||||
var toRemove = items.Where(i => i.IsSelected).ToList();
|
||||
foreach (var item in toRemove)
|
||||
{
|
||||
items.Remove(item);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ClearCompleted(IList<QueueItem> items)
|
||||
{
|
||||
var toRemove = items.Where(i => i.IsDone || i.StateText == "done").ToList();
|
||||
foreach (var item in toRemove)
|
||||
{
|
||||
items.Remove(item);
|
||||
}
|
||||
}
|
||||
|
||||
public static void BatchChangeOutput(IEnumerable<QueueItem> items, string newOutputExt, IEnumerable<string> eligibleInputExtensions)
|
||||
{
|
||||
var eligibleSet = new HashSet<string>(
|
||||
eligibleInputExtensions.Select(e => e.StartsWith('.') ? e.ToLowerInvariant() : "." + e.ToLowerInvariant())
|
||||
);
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
var ext = Path.GetExtension(item.SourcePath).ToLowerInvariant();
|
||||
if (eligibleSet.Contains(ext))
|
||||
{
|
||||
item.SelectedOutputExtension = newOutputExt;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -89,6 +89,49 @@
|
|||
<!-- Sidebar content -->
|
||||
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto" Padding="24,24,24,24">
|
||||
<StackPanel>
|
||||
<!-- Quick Presets -->
|
||||
<StackPanel Margin="0,0,0,24">
|
||||
<TextBlock Text="빠른 최적화 프리셋" Style="{StaticResource FsLabelStyle}" Margin="0,0,0,8"/>
|
||||
<WrapPanel Margin="-2">
|
||||
<Button Margin="2" Padding="8,6" Style="{StaticResource FsSecondaryButtonStyle}"
|
||||
Command="{Binding PresetCommand, RelativeSource={RelativeSource AncestorType=Window}}"
|
||||
CommandParameter="WebOptimized"
|
||||
ToolTip="WebP · 80% 압축 · 메타데이터 제거">
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<ui:SymbolIcon Symbol="Globe24" FontSize="12" VerticalAlignment="Center" Margin="0,0,4,0"/>
|
||||
<TextBlock Text="웹 최적화" FontSize="11" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Margin="2" Padding="8,6" Style="{StaticResource FsSecondaryButtonStyle}"
|
||||
Command="{Binding PresetCommand, RelativeSource={RelativeSource AncestorType=Window}}"
|
||||
CommandParameter="HighQualityLossless"
|
||||
ToolTip="무손실 PNG/FLAC · 100% 품질">
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<ui:SymbolIcon Symbol="Sparkle24" FontSize="12" VerticalAlignment="Center" Margin="0,0,4,0"/>
|
||||
<TextBlock Text="고화질 보존" FontSize="11" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Margin="2" Padding="8,6" Style="{StaticResource FsSecondaryButtonStyle}"
|
||||
Command="{Binding PresetCommand, RelativeSource={RelativeSource AncestorType=Window}}"
|
||||
CommandParameter="DocumentPdf"
|
||||
ToolTip="표준 PDF 문서 변환">
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<ui:SymbolIcon Symbol="DocumentPdf24" FontSize="12" VerticalAlignment="Center" Margin="0,0,4,0"/>
|
||||
<TextBlock Text="문서 PDF" FontSize="11" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Margin="2" Padding="8,6" Style="{StaticResource FsSecondaryButtonStyle}"
|
||||
Command="{Binding PresetCommand, RelativeSource={RelativeSource AncestorType=Window}}"
|
||||
CommandParameter="MobileShare"
|
||||
ToolTip="MP4 H.264 · 가벼운 모바일 전송">
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<ui:SymbolIcon Symbol="Phone24" FontSize="12" VerticalAlignment="Center" Margin="0,0,4,0"/>
|
||||
<TextBlock Text="모바일 공유" FontSize="11" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</WrapPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Target Format -->
|
||||
<StackPanel Margin="0,0,0,32">
|
||||
<TextBlock Text="TARGET FORMAT" Style="{StaticResource FsLabelStyle}"/>
|
||||
|
|
@ -585,15 +628,120 @@
|
|||
|
||||
<!-- View content (좌측: 탭 컨텐츠 / 우측: Preview 컬럼) -->
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" MinWidth="360"/>
|
||||
<ColumnDefinition Width="380"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 실시간 검색 & 카테고리 필터 툴바 -->
|
||||
<Border Grid.Row="0" Grid.ColumnSpan="2"
|
||||
Background="{StaticResource FsBgPanel}"
|
||||
BorderBrush="{StaticResource FsBorderSubtle}"
|
||||
BorderThickness="0,0,0,1" Padding="24,8">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="280"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<!-- Search Box -->
|
||||
<Grid Grid.Column="0">
|
||||
<TextBox x:Name="SearchBox" Style="{StaticResource FsPathInputStyle}"
|
||||
Padding="32,6,10,6" Text="{Binding SearchText, RelativeSource={RelativeSource AncestorType=Window}, UpdateSourceTrigger=PropertyChanged}"
|
||||
ToolTip="파일명 또는 확장자로 검색"/>
|
||||
<ui:SymbolIcon Symbol="Search24" FontSize="14" Foreground="{StaticResource FsTextTertiary}"
|
||||
HorizontalAlignment="Left" VerticalAlignment="Center" Margin="10,0,0,0" IsHitTestVisible="False"/>
|
||||
</Grid>
|
||||
<!-- Category Filter Chips -->
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center">
|
||||
<Button Margin="0,0,4,0" Padding="10,4" Style="{StaticResource FsSecondaryButtonStyle}"
|
||||
Command="{Binding FilterCategoryCommand, RelativeSource={RelativeSource AncestorType=Window}}"
|
||||
CommandParameter="All">
|
||||
<TextBlock Text="전체" FontSize="11" VerticalAlignment="Center"/>
|
||||
</Button>
|
||||
<Button Margin="0,0,4,0" Padding="10,4" Style="{StaticResource FsSecondaryButtonStyle}"
|
||||
Command="{Binding FilterCategoryCommand, RelativeSource={RelativeSource AncestorType=Window}}"
|
||||
CommandParameter="Image">
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<ui:SymbolIcon Symbol="Image24" FontSize="12" VerticalAlignment="Center" Margin="0,0,4,0"/>
|
||||
<TextBlock Text="이미지" FontSize="11" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Margin="0,0,4,0" Padding="10,4" Style="{StaticResource FsSecondaryButtonStyle}"
|
||||
Command="{Binding FilterCategoryCommand, RelativeSource={RelativeSource AncestorType=Window}}"
|
||||
CommandParameter="Document">
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<ui:SymbolIcon Symbol="Document24" FontSize="12" VerticalAlignment="Center" Margin="0,0,4,0"/>
|
||||
<TextBlock Text="문서" FontSize="11" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Margin="0,0,4,0" Padding="10,4" Style="{StaticResource FsSecondaryButtonStyle}"
|
||||
Command="{Binding FilterCategoryCommand, RelativeSource={RelativeSource AncestorType=Window}}"
|
||||
CommandParameter="Media">
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<ui:SymbolIcon Symbol="Video24" FontSize="12" VerticalAlignment="Center" Margin="0,0,4,0"/>
|
||||
<TextBlock Text="미디어" FontSize="11" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Padding="10,4" Style="{StaticResource FsSecondaryButtonStyle}"
|
||||
Command="{Binding FilterCategoryCommand, RelativeSource={RelativeSource AncestorType=Window}}"
|
||||
CommandParameter="Data">
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<ui:SymbolIcon Symbol="Database24" FontSize="12" VerticalAlignment="Center" Margin="0,0,4,0"/>
|
||||
<TextBlock Text="데이터" FontSize="11" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- 좌측: Active Queue OR Past Results -->
|
||||
<Grid Grid.Column="0">
|
||||
<Grid Grid.Row="1" Grid.Column="0">
|
||||
<!-- Active Queue view -->
|
||||
<Grid x:Name="ActiveQueueView" Visibility="Collapsed">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Batch Action Bar -->
|
||||
<Border x:Name="BatchActionBar" Grid.Row="0" Background="{StaticResource FsBgPanel}"
|
||||
BorderBrush="{StaticResource FsBorderSubtle}" BorderThickness="0,0,0,1"
|
||||
Padding="24,8" Visibility="Collapsed">
|
||||
<Grid VerticalAlignment="Center">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<CheckBox x:Name="BatchSelectAllCheck" Content="전체 선택" VerticalAlignment="Center"
|
||||
Command="{Binding BatchSelectAllCommand, RelativeSource={RelativeSource AncestorType=Window}}"
|
||||
CommandParameter="{Binding IsChecked, RelativeSource={RelativeSource Self}}"/>
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<Button Margin="0,0,8,0" Padding="10,4" Style="{StaticResource FsSecondaryButtonStyle}"
|
||||
Command="{Binding BatchRemoveSelectedCommand, RelativeSource={RelativeSource AncestorType=Window}}"
|
||||
ToolTip="선택된 파일들을 큐에서 제거합니다">
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<ui:SymbolIcon Symbol="Delete24" FontSize="12" VerticalAlignment="Center" Margin="0,0,4,0"/>
|
||||
<TextBlock Text="선택 항목 삭제" FontSize="11" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Padding="10,4" Style="{StaticResource FsSecondaryButtonStyle}"
|
||||
Command="{Binding BatchClearCompletedCommand, RelativeSource={RelativeSource AncestorType=Window}}"
|
||||
ToolTip="변환이 완료된 항목들을 큐에서 정리합니다">
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<ui:SymbolIcon Symbol="Checkmark24" FontSize="12" VerticalAlignment="Center" Margin="0,0,4,0"/>
|
||||
<TextBlock Text="완료 항목 정리" FontSize="11" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Grid Grid.Row="1">
|
||||
<Grid x:Name="DropZoneEmpty">
|
||||
<Border Background="{StaticResource FsBgBase}" Padding="32">
|
||||
<!-- 점선 드롭존: 절제된 dashed 보더 + 중앙 정렬 안내 -->
|
||||
|
|
@ -663,16 +811,19 @@
|
|||
CornerRadius="6,0,0,6"/>
|
||||
<Grid Grid.Column="1" Margin="12,10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="28"/>
|
||||
<ColumnDefinition Width="40"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="100"/>
|
||||
<ColumnDefinition Width="120"/>
|
||||
<ColumnDefinition Width="40"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image Width="34" Height="34" VerticalAlignment="Center"
|
||||
<CheckBox Grid.Column="0" IsChecked="{Binding IsSelected, Mode=TwoWay}"
|
||||
VerticalAlignment="Center"/>
|
||||
<Image Grid.Column="1" Width="34" Height="34" VerticalAlignment="Center"
|
||||
RenderOptions.BitmapScalingMode="HighQuality"
|
||||
Source="{Binding GlyphSource}"/>
|
||||
<StackPanel Grid.Column="1" Margin="16,0,0,0"
|
||||
<StackPanel Grid.Column="2" Margin="16,0,0,0"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock Text="{Binding FileName}"
|
||||
Style="{StaticResource FsBodyStyle}"
|
||||
|
|
@ -686,16 +837,16 @@
|
|||
Value="{Binding ProgressValue, Mode=OneWay}"
|
||||
Visibility="{Binding ProgressVisibility, Mode=OneWay}"/>
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="2" Text="{Binding SizeText}"
|
||||
<TextBlock Grid.Column="3" Text="{Binding SizeText}"
|
||||
Style="{StaticResource FsMonoStyle}"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="3" Text="{Binding StateText}"
|
||||
<TextBlock Grid.Column="4" Text="{Binding StateText}"
|
||||
FontFamily="{StaticResource FsFontMono}"
|
||||
FontSize="12"
|
||||
Foreground="{Binding StateBrush}"
|
||||
VerticalAlignment="Center"/>
|
||||
<Button Grid.Column="4"
|
||||
<Button Grid.Column="5"
|
||||
Style="{StaticResource FsIconButtonStyle}"
|
||||
Width="28" Height="28"
|
||||
VerticalAlignment="Center"
|
||||
|
|
@ -725,6 +876,7 @@
|
|||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<!-- Past Results view (default) -->
|
||||
<Grid x:Name="PastResultsContainer">
|
||||
|
|
@ -872,7 +1024,7 @@
|
|||
<!-- /좌측 컬럼 끝 -->
|
||||
|
||||
<!-- 우측: Preview 컬럼 (항상 보임) -->
|
||||
<Border Grid.Column="1"
|
||||
<Border Grid.Row="1" Grid.Column="1"
|
||||
Background="{StaticResource FsBgPanel}"
|
||||
BorderBrush="{StaticResource FsBorderSubtle}"
|
||||
BorderThickness="1,0,0,0">
|
||||
|
|
@ -1008,17 +1160,40 @@
|
|||
Foreground="{StaticResource FsTextPrimary}" Text="—"/>
|
||||
</Grid>
|
||||
|
||||
<Button Content="Open in Explorer" Margin="0,16,0,0"
|
||||
Style="{StaticResource FsSecondaryButtonStyle}"
|
||||
Command="{Binding PreviewOpenFolderCommand, RelativeSource={RelativeSource AncestorType=Window}}"
|
||||
HorizontalAlignment="Stretch"/>
|
||||
<Grid Margin="0,16,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="8"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button Grid.Column="0"
|
||||
Style="{StaticResource FsSecondaryButtonStyle}"
|
||||
Padding="12,8"
|
||||
Command="{Binding PreviewOpenFileCommand, RelativeSource={RelativeSource AncestorType=Window}}"
|
||||
ToolTip="기본 프로그램으로 파일 열기">
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<ui:SymbolIcon Symbol="Open24" FontSize="13" VerticalAlignment="Center" Margin="0,0,6,0"/>
|
||||
<TextBlock Text="열기" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Grid.Column="2"
|
||||
Style="{StaticResource FsSecondaryButtonStyle}"
|
||||
Padding="12,8"
|
||||
Command="{Binding PreviewOpenFolderCommand, RelativeSource={RelativeSource AncestorType=Window}}"
|
||||
ToolTip="탐색기에서 파일 위치 열기">
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<ui:SymbolIcon Symbol="FolderOpen24" FontSize="13" VerticalAlignment="Center" Margin="0,0,6,0"/>
|
||||
<TextBlock Text="폴더에서 보기" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Drop hint overlay (전체 덮음) -->
|
||||
<Border x:Name="DropHintOverlay" Visibility="Collapsed" Grid.ColumnSpan="2"
|
||||
<Border x:Name="DropHintOverlay" Visibility="Collapsed" Grid.RowSpan="2" Grid.ColumnSpan="2"
|
||||
Background="#CC090A0C" IsHitTestVisible="False">
|
||||
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<Path Width="64" Height="64" Stretch="Uniform"
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ using System.Windows.Media.Imaging;
|
|||
using Everything2Everything.App.Shell;
|
||||
using Everything2Everything.App.ViewModels;
|
||||
using Everything2Everything.Core;
|
||||
using Everything2Everything.Core.Filters;
|
||||
using Everything2Everything.Core.Inspector;
|
||||
using Everything2Everything.Core.Presets;
|
||||
using LossClass = Everything2Everything.Core.Providers.LossClass;
|
||||
|
||||
namespace Everything2Everything.App.Views;
|
||||
|
|
@ -48,6 +51,7 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow
|
|||
public ICommand PickOutputFolderCommand { get; }
|
||||
public ICommand CancelProcessingCommand { get; }
|
||||
public ICommand PreviewOpenFolderCommand { get; }
|
||||
public ICommand PreviewOpenFileCommand { get; }
|
||||
public ICommand RemoveQueueItemCommand { get; }
|
||||
public ICommand OpenFolderCommand { get; }
|
||||
public ICommand TabCommand { get; }
|
||||
|
|
@ -60,6 +64,41 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow
|
|||
public ICommand QueueRowCommand { get; }
|
||||
public ICommand PastRowCommand { get; }
|
||||
|
||||
// 신규 프리셋, 필터, 일괄 작업 커맨드
|
||||
public ICommand PresetCommand { get; }
|
||||
public ICommand FilterCategoryCommand { get; }
|
||||
public ICommand BatchSelectAllCommand { get; }
|
||||
public ICommand BatchRemoveSelectedCommand { get; }
|
||||
public ICommand BatchClearCompletedCommand { get; }
|
||||
|
||||
private string _searchText = "";
|
||||
public string SearchText
|
||||
{
|
||||
get => _searchText;
|
||||
set
|
||||
{
|
||||
if (_searchText != value)
|
||||
{
|
||||
_searchText = value;
|
||||
ApplyQueueFilters();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private FilterCategory _selectedCategory = FilterCategory.All;
|
||||
public FilterCategory SelectedCategory
|
||||
{
|
||||
get => _selectedCategory;
|
||||
set
|
||||
{
|
||||
if (_selectedCategory != value)
|
||||
{
|
||||
_selectedCategory = value;
|
||||
ApplyQueueFilters();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public MainWindow(ConversionEngine engine, ISettingsStore settings, IReadOnlyList<string>? initialFiles = null)
|
||||
{
|
||||
_engine = engine;
|
||||
|
|
@ -79,6 +118,12 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow
|
|||
PickOutputFolderCommand = new RelayCommand(_ => OnPickOutputFolderClick(this, new RoutedEventArgs()));
|
||||
CancelProcessingCommand = new RelayCommand(_ => OnCancelProcessingClick(this, new RoutedEventArgs()));
|
||||
PreviewOpenFolderCommand = new RelayCommand(_ => OnPreviewOpenFolder(this, new RoutedEventArgs()));
|
||||
PreviewOpenFileCommand = new RelayCommand(_ => OnPreviewOpenFile(this, new RoutedEventArgs()));
|
||||
PresetCommand = new RelayCommand(p => ApplyPreset(p?.ToString()));
|
||||
FilterCategoryCommand = new RelayCommand(p => ApplyFilterCategory(p?.ToString()));
|
||||
BatchSelectAllCommand = new RelayCommand(p => BatchSelectAll(p));
|
||||
BatchRemoveSelectedCommand = new RelayCommand(_ => BatchRemoveSelected());
|
||||
BatchClearCompletedCommand = new RelayCommand(_ => BatchClearCompleted());
|
||||
RemoveQueueItemCommand = new RelayCommand(p => RemoveQueueItem(p as QueueItem));
|
||||
OpenFolderCommand = new RelayCommand(p => OpenFolderForPath(p as string));
|
||||
TabCommand = new RelayCommand(p => ShowTab(p as string ?? "Past"));
|
||||
|
|
@ -246,6 +291,10 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow
|
|||
var hasItems = _activeQueue.Count > 0;
|
||||
DropZoneEmpty.Visibility = hasItems ? Visibility.Collapsed : Visibility.Visible;
|
||||
ActiveQueueScroll.Visibility = hasItems ? Visibility.Visible : Visibility.Collapsed;
|
||||
if (FindName("BatchActionBar") is UIElement batchBar)
|
||||
{
|
||||
batchBar.Visibility = hasItems ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePastResultsVisibility()
|
||||
|
|
@ -422,12 +471,13 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow
|
|||
|
||||
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 = "—";
|
||||
var info = FileInspectorBuilder.Build(filePath);
|
||||
PreviewFileName.Text = string.IsNullOrEmpty(fileName) ? info.FileName : fileName;
|
||||
PreviewFilePath.Text = string.IsNullOrEmpty(filePath) ? info.FullPath : filePath;
|
||||
PreviewFormatText.Text = string.IsNullOrEmpty(formatLabel) || formatLabel == "—" ? info.Extension.TrimStart('.').ToUpperInvariant() : formatLabel;
|
||||
PreviewSizeText.Text = string.IsNullOrEmpty(sizeText) || sizeText == "—" ? info.FormattedSize : sizeText;
|
||||
PreviewDimText.Text = info.DimensionsOrMeta;
|
||||
PreviewPageText.Text = info.Category.ToString();
|
||||
}
|
||||
|
||||
private void ShowPreviewLoading()
|
||||
|
|
@ -478,6 +528,97 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow
|
|||
catch { }
|
||||
}
|
||||
|
||||
private void OnPreviewOpenFile(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var path = _selectedPreviewPath ?? _selectedPreviewItem?.SourcePath;
|
||||
if (string.IsNullOrEmpty(path) || !File.Exists(path)) return;
|
||||
try
|
||||
{
|
||||
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(path)
|
||||
{
|
||||
UseShellExecute = true,
|
||||
});
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void ApplyPreset(string? presetName)
|
||||
{
|
||||
if (Enum.TryParse<PresetType>(presetName, true, out var type))
|
||||
{
|
||||
var firstItem = _activeQueue.FirstOrDefault()?.SourcePath;
|
||||
var ext = !string.IsNullOrEmpty(firstItem) ? Path.GetExtension(firstItem) : ".png";
|
||||
var recommended = ConversionPreset.Apply(type, _options, ext);
|
||||
|
||||
for (int i = 0; i < OutputFormatCombo.Items.Count; i++)
|
||||
{
|
||||
if (OutputFormatCombo.Items[i] is OutputFormatInfo info &&
|
||||
info.Extension.Equals(recommended, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
OutputFormatCombo.SelectedIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var targets = _activeQueue.Where(q => q.IsSelected).ToList();
|
||||
if (targets.Count == 0) targets = _activeQueue.ToList();
|
||||
foreach (var item in targets)
|
||||
{
|
||||
item.SelectedOutputExtension = recommended;
|
||||
}
|
||||
|
||||
QualitySlider.Value = _options.Quality;
|
||||
QualityValueText.Text = _options.Quality.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyFilterCategory(string? categoryName)
|
||||
{
|
||||
if (Enum.TryParse<FilterCategory>(categoryName, true, out var cat))
|
||||
{
|
||||
SelectedCategory = cat;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyQueueFilters()
|
||||
{
|
||||
var view = System.Windows.Data.CollectionViewSource.GetDefaultView(_activeQueue);
|
||||
if (view != null)
|
||||
{
|
||||
view.Filter = item =>
|
||||
{
|
||||
if (item is QueueItem q)
|
||||
{
|
||||
return QueueFilterMatcher.Matches(q.FileName, _searchText, _selectedCategory);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
view.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private void BatchSelectAll(object? parameter)
|
||||
{
|
||||
bool select = parameter is true;
|
||||
BatchQueueService.SetSelectionAll(_activeQueue, select);
|
||||
}
|
||||
|
||||
private void BatchRemoveSelected()
|
||||
{
|
||||
BatchQueueService.RemoveSelected(_activeQueue);
|
||||
UpdateBadges();
|
||||
UpdateProcessQueueButton();
|
||||
UpdateActiveQueueVisibility();
|
||||
}
|
||||
|
||||
private void BatchClearCompleted()
|
||||
{
|
||||
BatchQueueService.ClearCompleted(_activeQueue);
|
||||
UpdateBadges();
|
||||
UpdateProcessQueueButton();
|
||||
UpdateActiveQueueVisibility();
|
||||
}
|
||||
|
||||
// ============== Export Log ==============
|
||||
|
||||
private void OnExportLogClick(object sender, RoutedEventArgs e)
|
||||
|
|
@ -1132,14 +1273,30 @@ public sealed class QueueItem : INotifyPropertyChanged
|
|||
private string _state = "queued";
|
||||
private double _progressValue;
|
||||
private Visibility _progressVisibility = Visibility.Collapsed;
|
||||
private bool _isSelected;
|
||||
private string? _selectedOutputExtension;
|
||||
|
||||
public required string SourcePath { get; init; }
|
||||
public required string FileName { get; init; }
|
||||
public required string FormatLabel { get; init; }
|
||||
public required Brush FormatBrush { get; init; }
|
||||
public required string SizeText { get; init; }
|
||||
public required string MetaLine { get; init; }
|
||||
public required long SourceSizeBytes { get; init; }
|
||||
public string SourcePath { get; init; } = "";
|
||||
public string FileName { get; init; } = "";
|
||||
public string FormatLabel { get; init; } = "";
|
||||
public Brush FormatBrush { get; init; } = Brushes.Gray;
|
||||
public string SizeText { get; init; } = "";
|
||||
public string MetaLine { get; init; } = "";
|
||||
public long SourceSizeBytes { get; init; }
|
||||
|
||||
public bool IsSelected
|
||||
{
|
||||
get => _isSelected;
|
||||
set { _isSelected = value; Raise(nameof(IsSelected)); }
|
||||
}
|
||||
|
||||
public string? SelectedOutputExtension
|
||||
{
|
||||
get => _selectedOutputExtension;
|
||||
set { _selectedOutputExtension = value; Raise(nameof(SelectedOutputExtension)); }
|
||||
}
|
||||
|
||||
public bool IsDone => _state == "done";
|
||||
|
||||
/// <summary>형식 카테고리 글리프(라벨 아이콘).</summary>
|
||||
public System.Windows.Media.ImageSource GlyphSource => CategoryGlyphs.ForExtension(Path.GetExtension(SourcePath));
|
||||
|
|
@ -1147,14 +1304,14 @@ public sealed class QueueItem : INotifyPropertyChanged
|
|||
public string StateText
|
||||
{
|
||||
get => _state;
|
||||
set { _state = value; Raise(nameof(StateText)); }
|
||||
set { _state = value; Raise(nameof(StateText)); Raise(nameof(IsDone)); }
|
||||
}
|
||||
|
||||
public Brush StateBrush => _state switch
|
||||
{
|
||||
"queued" => (Brush)Application.Current.FindResource("FsTextTertiary"),
|
||||
"done" => (Brush)Application.Current.FindResource("FsAccentGreen"),
|
||||
_ => (Brush)Application.Current.FindResource("FsAccentBlue"),
|
||||
"queued" => (Application.Current?.TryFindResource("FsTextTertiary") as Brush) ?? Brushes.Gray,
|
||||
"done" => (Application.Current?.TryFindResource("FsAccentGreen") as Brush) ?? Brushes.LightGreen,
|
||||
_ => (Application.Current?.TryFindResource("FsAccentBlue") as Brush) ?? Brushes.DodgerBlue,
|
||||
};
|
||||
|
||||
public double ProgressValue
|
||||
|
|
@ -1198,12 +1355,15 @@ public sealed class QueueItem : INotifyPropertyChanged
|
|||
long size = 0;
|
||||
try { size = new FileInfo(path).Length; } catch { }
|
||||
|
||||
var brush = (Application.Current?.TryFindResource(brushKey) as Brush)
|
||||
?? new SolidColorBrush(Color.FromRgb(0x10, 0xB9, 0x81));
|
||||
|
||||
return new QueueItem
|
||||
{
|
||||
SourcePath = path,
|
||||
FileName = Path.GetFileName(path),
|
||||
FormatLabel = label,
|
||||
FormatBrush = (Brush)Application.Current.FindResource(brushKey),
|
||||
FormatBrush = brush,
|
||||
SizeText = MainWindow.HumanizeBytes(size),
|
||||
MetaLine = $"{ext.ToUpperInvariant()} • {MainWindow.HumanizeBytes(size)}",
|
||||
SourceSizeBytes = size,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue