1
0
Fork 0

feat(ui): implement 1-click presets, real-time search & filter bar, batch queue actions, and file inspector

This commit is contained in:
Yun Chan 2026-09-03 17:12:14 +09:00
parent 41c8a8a94b
commit b67b3f1b61
17 changed files with 1240 additions and 38 deletions

View file

@ -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) },

View 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;
}
}
}
}

View file

@ -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"

View file

@ -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,