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
71
src/Everything2Everything.Core/Filters/QueueFilterMatcher.cs
Normal file
71
src/Everything2Everything.Core/Filters/QueueFilterMatcher.cs
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Everything2Everything.Core.Filters;
|
||||
|
||||
public enum FilterCategory
|
||||
{
|
||||
All,
|
||||
Image,
|
||||
Document,
|
||||
Media,
|
||||
Data,
|
||||
}
|
||||
|
||||
public static class QueueFilterMatcher
|
||||
{
|
||||
public static FilterCategory GetCategory(string pathOrExt)
|
||||
{
|
||||
var ext = Path.GetExtension(pathOrExt).Trim().ToLowerInvariant();
|
||||
if (string.IsNullOrEmpty(ext))
|
||||
{
|
||||
ext = pathOrExt.Trim().ToLowerInvariant();
|
||||
if (!ext.StartsWith('.')) ext = "." + ext;
|
||||
}
|
||||
|
||||
return ext switch
|
||||
{
|
||||
".jpg" or ".jpeg" or ".png" or ".webp" or ".avif" or ".gif" or ".bmp" or ".tif" or ".tiff"
|
||||
or ".svg" or ".heic" or ".heif" or ".raw" or ".dng" or ".cr2" or ".cr3" or ".nef" or ".arw"
|
||||
=> FilterCategory.Image,
|
||||
|
||||
".pdf" or ".docx" or ".doc" or ".hwp" or ".hwpx" or ".txt" or ".md" or ".markdown" or ".html" or ".htm" or ".xlsx" or ".xls"
|
||||
=> FilterCategory.Document,
|
||||
|
||||
".mp4" or ".mkv" or ".webm" or ".mov" or ".avi" or ".mp3" or ".wav" or ".flac" or ".aac" or ".m4a" or ".ogg" or ".opus"
|
||||
=> FilterCategory.Media,
|
||||
|
||||
".csv" or ".json" or ".tsv" or ".xml" or ".yaml" or ".yml"
|
||||
=> FilterCategory.Data,
|
||||
|
||||
_ => FilterCategory.All,
|
||||
};
|
||||
}
|
||||
|
||||
public static bool Matches(string filename, string query, FilterCategory category)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filename)) return false;
|
||||
|
||||
// 1. 카테고리 필터 검사
|
||||
if (category != FilterCategory.All)
|
||||
{
|
||||
var itemCategory = GetCategory(filename);
|
||||
if (itemCategory != category) return false;
|
||||
}
|
||||
|
||||
// 2. 검색어 필터 검사
|
||||
if (string.IsNullOrWhiteSpace(query)) return true;
|
||||
|
||||
var cleanQuery = query.Trim();
|
||||
return filename.Contains(cleanQuery, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public static System.Collections.Generic.IEnumerable<T> Filter<T>(
|
||||
System.Collections.Generic.IEnumerable<T> source,
|
||||
Func<T, string> fileNameSelector,
|
||||
string query,
|
||||
FilterCategory category)
|
||||
{
|
||||
return System.Linq.Enumerable.Where(source, item => Matches(fileNameSelector(item), query, category));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using Everything2Everything.Core.Filters;
|
||||
|
||||
namespace Everything2Everything.Core.Inspector;
|
||||
|
||||
public sealed record FileInspectorInfo(
|
||||
string FileName,
|
||||
string FullPath,
|
||||
FilterCategory Category,
|
||||
long FileSizeBytes,
|
||||
string FormattedSize,
|
||||
string Extension,
|
||||
string DimensionsOrMeta,
|
||||
bool CanOpen,
|
||||
bool CanReveal);
|
||||
|
||||
public static class FileInspectorBuilder
|
||||
{
|
||||
public static FileInspectorInfo Build(string filePath)
|
||||
{
|
||||
var fileName = Path.GetFileName(filePath);
|
||||
if (string.IsNullOrEmpty(fileName)) fileName = filePath;
|
||||
|
||||
var ext = Path.GetExtension(filePath).ToLowerInvariant();
|
||||
var category = QueueFilterMatcher.GetCategory(filePath);
|
||||
|
||||
long size = 0;
|
||||
bool exists = false;
|
||||
try
|
||||
{
|
||||
exists = File.Exists(filePath);
|
||||
if (exists)
|
||||
{
|
||||
size = new FileInfo(filePath).Length;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
var formattedSize = HumanizeBytes(size);
|
||||
var meta = $"{ext.TrimStart('.').ToUpperInvariant()} · {category}";
|
||||
|
||||
return new FileInspectorInfo(
|
||||
FileName: fileName,
|
||||
FullPath: filePath,
|
||||
Category: category,
|
||||
FileSizeBytes: size,
|
||||
FormattedSize: formattedSize,
|
||||
Extension: ext,
|
||||
DimensionsOrMeta: meta,
|
||||
CanOpen: exists,
|
||||
CanReveal: exists);
|
||||
}
|
||||
|
||||
private static string HumanizeBytes(long bytes)
|
||||
{
|
||||
if (bytes <= 0) return "0 B";
|
||||
string[] units = { "B", "KB", "MB", "GB", "TB" };
|
||||
double b = bytes;
|
||||
int u = 0;
|
||||
while (b >= 1024.0 && u < units.Length - 1)
|
||||
{
|
||||
b /= 1024.0;
|
||||
u++;
|
||||
}
|
||||
return u == 0 ? $"{b:0} B" : $"{b:0.1} {units[u]}";
|
||||
}
|
||||
}
|
||||
68
src/Everything2Everything.Core/Presets/ConversionPreset.cs
Normal file
68
src/Everything2Everything.Core/Presets/ConversionPreset.cs
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
using System;
|
||||
|
||||
namespace Everything2Everything.Core.Presets;
|
||||
|
||||
public enum PresetType
|
||||
{
|
||||
WebOptimized,
|
||||
HighQualityLossless,
|
||||
DocumentPdf,
|
||||
MobileShare,
|
||||
}
|
||||
|
||||
public sealed record PresetInfo(PresetType Type, string Title, string Description, string IconSymbol);
|
||||
|
||||
public static class ConversionPreset
|
||||
{
|
||||
public static PresetInfo GetInfo(PresetType type) => type switch
|
||||
{
|
||||
PresetType.WebOptimized => new PresetInfo(type, "웹 최적화", "WebP · 80% 압축 · 메타데이터 제거", "Globe24"),
|
||||
PresetType.HighQualityLossless => new PresetInfo(type, "초고화질 보존", "무손실 PNG/FLAC · 100% 품질", "Sparkle24"),
|
||||
PresetType.DocumentPdf => new PresetInfo(type, "문서 PDF 보관", "표준 PDF/A 문서 변환", "DocumentPdf24"),
|
||||
PresetType.MobileShare => new PresetInfo(type, "모바일 공유", "MP4 H.264 · 가벼운 용량 전송", "Phone24"),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type)),
|
||||
};
|
||||
|
||||
public static string Apply(PresetType type, dynamic options, string inputExtension)
|
||||
{
|
||||
var ext = inputExtension.Trim().ToLowerInvariant();
|
||||
if (!ext.StartsWith('.')) ext = "." + ext;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case PresetType.WebOptimized:
|
||||
options.ImageQuality = 80;
|
||||
options.StripMetadata = true;
|
||||
if (IsVideo(ext)) return ".mp4";
|
||||
if (IsAudio(ext)) return ".mp3";
|
||||
if (IsDoc(ext)) return ".pdf";
|
||||
return ".webp";
|
||||
|
||||
case PresetType.HighQualityLossless:
|
||||
options.ImageQuality = 100;
|
||||
options.StripMetadata = false;
|
||||
options.AudioBitrateKbps = 320;
|
||||
if (IsAudio(ext)) return ".flac";
|
||||
if (IsVideo(ext)) return ".mkv";
|
||||
return ".png";
|
||||
|
||||
case PresetType.DocumentPdf:
|
||||
return ".pdf";
|
||||
|
||||
case PresetType.MobileShare:
|
||||
options.VideoCrf = 26;
|
||||
options.AudioBitrateKbps = 128;
|
||||
options.VideoPreset = "veryfast";
|
||||
if (IsVideo(ext)) return ".mp4";
|
||||
if (IsAudio(ext)) return ".mp3";
|
||||
return ".jpg";
|
||||
|
||||
default:
|
||||
return ext;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsVideo(string ext) => ext is ".mp4" or ".mkv" or ".webm" or ".mov" or ".avi" or ".m4v";
|
||||
private static bool IsAudio(string ext) => ext is ".mp3" or ".wav" or ".flac" or ".aac" or ".m4a" or ".ogg" or ".opus";
|
||||
private static bool IsDoc(string ext) => ext is ".docx" or ".doc" or ".hwp" or ".hwpx" or ".txt" or ".md" or ".markdown" or ".html";
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue