Give mixed selections something to do

A Drag of different kinds showed an empty wheel, because the catalog only
answers for a single category. Filee's answer is a mixed profile, so add
Zip (one archive, flat names) and Merge (non-PDFs are converted to PDF
first); a multi-file drag of one kind gets Zip too.
This commit is contained in:
2026-10-20 01:30:42 +08:00
parent 2d031e58a4
commit a01d5def89
8 changed files with 295 additions and 41 deletions
+30 -3
View File
@@ -20,7 +20,7 @@ enum ToolID: String, CaseIterable, Identifiable, Codable {
// Subtitles
case shiftSubtitle
// Archives
case extractArchive, listArchive
case extractArchive, listArchive, createArchive
// Media metadata
case mediaInfo
@@ -65,6 +65,7 @@ enum ToolID: String, CaseIterable, Identifiable, Codable {
case .shiftSubtitle: return "Shift Time"
case .extractArchive: return "Extract"
case .listArchive: return "List Contents"
case .createArchive: return "Zip"
case .mediaInfo: return "Info"
}
}
@@ -100,6 +101,7 @@ enum ToolID: String, CaseIterable, Identifiable, Codable {
case .shiftSubtitle: return "clock.arrow.circlepath"
case .extractArchive: return "archivebox"
case .listArchive: return "list.bullet.rectangle"
case .createArchive: return "doc.zipper"
}
}
@@ -122,7 +124,7 @@ enum ToolID: String, CaseIterable, Identifiable, Codable {
return .spreadsheet
case .shiftSubtitle:
return .subtitle
case .extractArchive, .listArchive:
case .extractArchive, .listArchive, .createArchive:
return .archive
case .mediaInfo:
return .video
@@ -131,7 +133,10 @@ enum ToolID: String, CaseIterable, Identifiable, Codable {
/// Tools that only make sense for a multi-file selection.
var requiresMultipleFiles: Bool {
self == .mergePDF || self == .imagesToPDF || self == .collageImages
switch self {
case .mergePDF, .imagesToPDF, .collageImages, .createArchive: return true
default: return false
}
}
var minimumFileCount: Int { requiresMultipleFiles ? 2 : 1 }
@@ -151,6 +156,26 @@ enum ToolID: String, CaseIterable, Identifiable, Codable {
static func tools(for category: FileCategory) -> [ToolID] {
allCases.filter { $0.category == category }
}
/// Tools that accept a drag of mixed kinds. When a selection cannot become
/// one format, there is still something useful to do with it: Filee calls
/// this a mixed profile, and it is why its donut is never empty.
static func combineTools(for urls: [URL]) -> [ToolID] {
guard urls.count >= 2 else { return [] }
var tools: [ToolID] = []
if urls.allSatisfy(canBecomePDF) { tools.append(.mergePDF) }
tools.append(.createArchive)
return tools
}
/// Kinds that `Converter` knows how to turn into a PDF, which is what
/// ``mergePDF`` needs from anything that is not a PDF already.
static func canBecomePDF(_ url: URL) -> Bool {
switch FileKind.category(of: url) {
case .image, .pdf, .document, .slides, .spreadsheet, .ebook: return true
default: return false
}
}
}
/// One editable field in a tool's options form.
@@ -256,6 +281,8 @@ extension ToolID {
]
case .shiftSubtitle:
return [ToolField("offset", "Shift by", .decimal(min: -600, max: 600), default: "1", unit: "s")]
case .createArchive:
return [ToolField("name", "Archive name", .text, default: "Archive", help: "Saved beside the first file")]
case .listArchive, .extractArchive, .wordCount, .mediaInfo, .readQR, .stripMetadata,
.grayscaleImage, .normalizeAudio, .extractPDFText, .documentToPDF, .sheetsToCSV,
.muteVideo, .mergePDF, .imagesToPDF: