Skip to content

Commit 57adea2

Browse files
committed
feat: ability to unselect all files in a subdirectory
Signed-off-by: Tommy van der Vorst <tommy@pixelspark.nl>
1 parent 950cf91 commit 57adea2

File tree

2 files changed

+148
-7
lines changed

2 files changed

+148
-7
lines changed

Localizable.xcstrings

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15936,6 +15936,52 @@
1593615936
}
1593715937
}
1593815938
},
15939+
"For all files in here" : {
15940+
"localizations" : {
15941+
"de" : {
15942+
"stringUnit" : {
15943+
"state" : "translated",
15944+
"value" : "Für alle Dateien hier"
15945+
}
15946+
},
15947+
"es" : {
15948+
"stringUnit" : {
15949+
"state" : "translated",
15950+
"value" : "Para todos los archivos aquí"
15951+
}
15952+
},
15953+
"it" : {
15954+
"stringUnit" : {
15955+
"state" : "translated",
15956+
"value" : "Per tutti i file qui"
15957+
}
15958+
},
15959+
"ja" : {
15960+
"stringUnit" : {
15961+
"state" : "translated",
15962+
"value" : "ここにあるすべてのファイルについて"
15963+
}
15964+
},
15965+
"nl" : {
15966+
"stringUnit" : {
15967+
"state" : "translated",
15968+
"value" : "Voor alle bestanden hier"
15969+
}
15970+
},
15971+
"uk" : {
15972+
"stringUnit" : {
15973+
"state" : "translated",
15974+
"value" : "Для всіх файлів тут"
15975+
}
15976+
},
15977+
"zh-Hans" : {
15978+
"stringUnit" : {
15979+
"state" : "translated",
15980+
"value" : "对于此处的所有文件"
15981+
}
15982+
}
15983+
}
15984+
},
1593915985
"Free up space" : {
1594015986
"localizations" : {
1594115987
"de" : {
@@ -25804,6 +25850,52 @@
2580425850
}
2580525851
}
2580625852
},
25853+
"Remove from this device if available on other devices" : {
25854+
"localizations" : {
25855+
"de" : {
25856+
"stringUnit" : {
25857+
"state" : "translated",
25858+
"value" : "Von diesem Gerät entfernen, wenn auf anderen Geräten verfügbar"
25859+
}
25860+
},
25861+
"es" : {
25862+
"stringUnit" : {
25863+
"state" : "translated",
25864+
"value" : "Eliminar de este dispositivo si está disponible en otros dispositivos"
25865+
}
25866+
},
25867+
"it" : {
25868+
"stringUnit" : {
25869+
"state" : "translated",
25870+
"value" : "Rimuovi da questo dispositivo se disponibile su altri dispositivi"
25871+
}
25872+
},
25873+
"ja" : {
25874+
"stringUnit" : {
25875+
"state" : "translated",
25876+
"value" : "他のデバイスで利用可能な場合はこのデバイスから削除"
25877+
}
25878+
},
25879+
"nl" : {
25880+
"stringUnit" : {
25881+
"state" : "translated",
25882+
"value" : "Verwijder van dit apparaat als het beschikbaar is op andere apparaten"
25883+
}
25884+
},
25885+
"uk" : {
25886+
"stringUnit" : {
25887+
"state" : "translated",
25888+
"value" : "Видалити з цього пристрою, якщо доступний на інших пристроях"
25889+
}
25890+
},
25891+
"zh-Hans" : {
25892+
"stringUnit" : {
25893+
"state" : "translated",
25894+
"value" : "如果其他设备可用,则从此设备移除"
25895+
}
25896+
}
25897+
}
25898+
},
2580725899
"Remove from this device, keep on %lld others" : {
2580825900
"localizations" : {
2580925901
"de" : {

Sushitrain/SelectiveFolderView.swift

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ struct SelectiveFolderView: View {
5050

5151
List(selection: $listSelection) {
5252
Section(self.prefix.isEmpty ? "Files kept on device" : "Files in '\(self.prefix)' kept on this device") {
53-
PathsOutlineGroup(paths: self.selectedFilteredPaths, disableIntermediateSelection: true) { item, isIntermediate in
53+
PathsOutlineGroup(paths: self.selectedFilteredPaths, disableIntermediateSelection: false) { item, isIntermediate in
5454
if item.isEmpty {
5555
EmptyView()
5656
}
@@ -60,7 +60,13 @@ struct SelectiveFolderView: View {
6060
folder: folder,
6161
deselect: {
6262
Task {
63-
await self.deselectItems([item])
63+
let entry = try folder.getFileInformation(item)
64+
if entry.isExplicitlySelected() {
65+
await self.deselectItems([item])
66+
}
67+
else {
68+
await self.deselectPrefix(item)
69+
}
6470
}
6571
}
6672
)
@@ -334,14 +340,22 @@ struct SelectiveFolderView: View {
334340

335341
private nonisolated func deselect(paths: Set<String>, includingLastCopy: Bool) async throws {
336342
var paths = paths
337-
// If we should not delete if our copy is the only one available, check availability for each file first
338-
if !includingLastCopy {
339-
for path in paths {
340-
let entry = try folder.getFileInformation(path)
343+
344+
for path in paths {
345+
let entry = try folder.getFileInformation(path)
346+
if !entry.isExplicitlySelected() {
347+
Log.info("Not deselecting \(path), it is not explicitly selected")
348+
paths.remove(path)
349+
continue
350+
}
351+
352+
// If we should not delete if our copy is the only one available, check availability for each file first
353+
if !includingLastCopy {
341354
let peersWithFullCopy = try entry.peersWithFullCopy()
342355
if peersWithFullCopy.count() == 0 {
343356
Log.info("Not removing \(path), it is the last copy")
344357
paths.remove(path)
358+
continue
345359
}
346360
}
347361
}
@@ -358,6 +372,18 @@ struct SelectiveFolderView: View {
358372
}
359373
}
360374

375+
// Deselects all items contained here that are explicitly selected, and available on at least one other device
376+
private func deselectPrefix(_ path: String) async {
377+
do {
378+
let prefix = path.withoutEndingSlash + "/"
379+
let items = Set(self.selectedFilteredPaths.filter { $0.starts(with: prefix) })
380+
try await self.deselect(paths: items, includingLastCopy: false)
381+
}
382+
catch {
383+
Log.warn("Could not deselect prefix \(path): \(error.localizedDescription)")
384+
}
385+
}
386+
361387
private func deselectItems(_ paths: [String]) async {
362388
do {
363389
let verdicts = paths.reduce(into: [:]) { dict, p in
@@ -393,7 +419,7 @@ private struct SelectiveFileView: View {
393419
Label(entry.fileName(), systemImage: entry.systemImage).strikethrough()
394420
}
395421
else if !entry.isExplicitlySelected() {
396-
Label(entry.fileName(), systemImage: entry.systemImage).opacity(0.8)
422+
IntermediateSelectiveFileView(entry: entry, deselect: deselect)
397423
}
398424
else {
399425
SelectedFileView(entry: entry, folder: folder, deselect: deselect)
@@ -416,6 +442,29 @@ private struct SelectiveFileView: View {
416442
}
417443
}
418444

445+
/** Entry in the list that is not explicitly selected itself, but can deselect a whole prefix */
446+
private struct IntermediateSelectiveFileView: View {
447+
let entry: SushitrainEntry
448+
let deselect: () -> Void
449+
450+
var body: some View {
451+
HStack {
452+
Label(entry.fileName(), systemImage: entry.systemImage).opacity(0.8)
453+
Spacer()
454+
Menu {
455+
Section("For all files in here") {
456+
Button("Remove from this device if available on other devices") {
457+
self.deselect()
458+
}
459+
}
460+
} label: {
461+
Label("", systemImage: "pin.slash.fill").accessibilityLabel("Actions")
462+
}.buttonStyle(.borderless)
463+
}
464+
}
465+
}
466+
467+
/** Entry in the list that is explicitly selected itself */
419468
private struct SelectedFileView: View {
420469
@Environment(AppState.self) private var appState
421470
let entry: SushitrainEntry

0 commit comments

Comments
 (0)