diff --git a/Sources/GrowthLapse/ContentView.swift b/Sources/GrowthLapse/ContentView.swift index bf80cd1..8690831 100644 --- a/Sources/GrowthLapse/ContentView.swift +++ b/Sources/GrowthLapse/ContentView.swift @@ -253,6 +253,9 @@ struct ContentView: View { selectedClipID = project?.clips.first?.id } } + .onChange(of: settings.sortMode) { sortMode in + processor.reorderProject(sortMode: sortMode) + } } private var statusHeader: some View { @@ -307,7 +310,8 @@ struct ContentView: View { } private func projectReviewPanel(_ project: GrowthLapseProject) -> some View { - let selectedClip = project.clips.first { $0.id == selectedClipID } ?? project.clips.first + let orderedClips = orderedProjectClips(project.clips) + let selectedClip = orderedClips.first { $0.id == selectedClipID } ?? orderedClips.first return VStack(alignment: .leading, spacing: 10) { HStack { @@ -328,7 +332,7 @@ struct ContentView: View { } HStack(alignment: .top, spacing: 12) { - List(project.clips, selection: $selectedClipID) { clip in + List(orderedClips, selection: $selectedClipID) { clip in VStack(alignment: .leading, spacing: 2) { HStack { Text(String(format: "%03d", clip.index)) @@ -429,6 +433,28 @@ struct ContentView: View { processor.project?.clips.first { $0.id == id } } + private func orderedProjectClips(_ clips: [GrowthLapseProjectClip]) -> [GrowthLapseProjectClip] { + clips.sorted { left, right in + switch settings.sortMode { + case .filenameAscending: + return left.displayName.localizedStandardCompare(right.displayName) == .orderedAscending + case .ageWeeksAscending: + if left.ageWeeks != right.ageWeeks { + return (left.ageWeeks ?? Int.max) < (right.ageWeeks ?? Int.max) + } + return left.displayName.localizedStandardCompare(right.displayName) == .orderedAscending + case .modificationDateAscending: + return left.index < right.index + } + } + .enumerated() + .map { offset, clip in + var updated = clip + updated.index = offset + 1 + return updated + } + } + private var errorBinding: Binding { Binding( get: { diff --git a/Sources/GrowthLapse/VideoProcessor.swift b/Sources/GrowthLapse/VideoProcessor.swift index d9b6eb9..dde525a 100644 --- a/Sources/GrowthLapse/VideoProcessor.swift +++ b/Sources/GrowthLapse/VideoProcessor.swift @@ -69,7 +69,7 @@ final class VideoProcessor: ObservableObject { state = .running let settingsCopy = settings - let projectCopy = project + let projectCopy = reorderedProject(project, sortMode: settings.sortMode) renderTask = Task { do { let outcome = try await render(settings: settingsCopy, existingProject: projectCopy) @@ -106,6 +106,15 @@ final class VideoProcessor: ObservableObject { saveProject(project) } + func reorderProject(sortMode: SortMode) { + guard let project else { + return + } + let reordered = reorderedProject(project, sortMode: sortMode) + self.project = reordered + saveProject(reordered) + } + func confirmProjectAndDeleteCache() { guard let project else { return @@ -1039,6 +1048,41 @@ final class VideoProcessor: ObservableObject { ) } + private func reorderedProject(_ project: GrowthLapseProject, sortMode: SortMode) -> GrowthLapseProject { + var copy = project + copy.clips = sortedProjectClips(project.clips, sortMode: sortMode).enumerated().map { offset, clip in + var updated = clip + updated.index = offset + 1 + return updated + } + return copy + } + + private func sortedProjectClips(_ clips: [GrowthLapseProjectClip], sortMode: SortMode) -> [GrowthLapseProjectClip] { + clips.sorted { left, right in + switch sortMode { + case .filenameAscending: + return left.displayName.localizedStandardCompare(right.displayName) == .orderedAscending + case .ageWeeksAscending: + if left.ageWeeks != right.ageWeeks { + return (left.ageWeeks ?? Int.max) < (right.ageWeeks ?? Int.max) + } + return left.displayName.localizedStandardCompare(right.displayName) == .orderedAscending + case .modificationDateAscending: + let leftDate = modificationDate(for: left.sourceURL) + let rightDate = modificationDate(for: right.sourceURL) + if leftDate != rightDate { + return leftDate < rightDate + } + return left.displayName.localizedStandardCompare(right.displayName) == .orderedAscending + } + } + } + + private func modificationDate(for url: URL) -> Date { + (try? url.resourceValues(forKeys: [.contentModificationDateKey]).contentModificationDate) ?? .distantPast + } + private func saveProject(_ project: GrowthLapseProject) { let url = GrowthLapseProject.url(for: project.outputFileURL) do {