diff options
| author | Dustin Mierau <dustin@mierau.me> | 2025-10-22 21:15:04 -0700 |
|---|---|---|
| committer | Dustin Mierau <dustin@mierau.me> | 2025-10-22 21:15:04 -0700 |
| commit | 485e4d981b6cd19a1e00ebf244ed1dd538fba284 (patch) | |
| tree | 8e5a5996b7597c671e947f1ba732aa07140b031e /Hotline | |
| parent | 46213b6e09b8b23bfa039634ff5af4919c758473 (diff) | |
Improvements to file search UI and config.
Diffstat (limited to 'Hotline')
| -rw-r--r-- | Hotline/Models/Hotline.swift | 25 | ||||
| -rw-r--r-- | Hotline/macOS/FilesView.swift | 64 | ||||
| -rw-r--r-- | Hotline/macOS/TrackerView.swift | 3 |
3 files changed, 64 insertions, 28 deletions
diff --git a/Hotline/Models/Hotline.swift b/Hotline/Models/Hotline.swift index b93fb55..786fb0c 100644 --- a/Hotline/Models/Hotline.swift +++ b/Hotline/Models/Hotline.swift @@ -1,10 +1,10 @@ import SwiftUI struct FileSearchConfig: Equatable { - var initialBurstCount: Int = 8 - var initialDelay: TimeInterval = 0.05 - var backoffMultiplier: Double = 1.6 - var maxDelay: TimeInterval = 1.5 + var initialBurstCount: Int = 15 + var initialDelay: TimeInterval = 0.02 + var backoffMultiplier: Double = 1.1 + var maxDelay: TimeInterval = 1.3 var maxDepth: Int = 40 var loopRepetitionLimit: Int = 4 } @@ -160,6 +160,7 @@ class Hotline: Equatable, HotlineClientDelegate, HotlineFileDownloadClientDelega var fileSearchQuery: String = "" var fileSearchConfig = FileSearchConfig() var fileSearchScannedFolders: Int = 0 + var fileSearchCurrentPath: [String]? = nil @ObservationIgnored private var fileSearchSession: FileSearchSession? = nil @ObservationIgnored private var fileSearchResultKeys: Set<String> = [] var news: [NewsInfo] = [] @@ -335,6 +336,7 @@ class Hotline: Equatable, HotlineClientDelegate, HotlineFileDownloadClientDelega fileSearchQuery = trimmed fileSearchStatus = .searching(processed: 0, pending: 0) fileSearchScannedFolders = 0 + fileSearchCurrentPath = [] let session = FileSearchSession(hotline: self, query: trimmed, config: fileSearchConfig) fileSearchSession = session @@ -348,12 +350,14 @@ class Hotline: Equatable, HotlineClientDelegate, HotlineFileDownloadClientDelega resetFileSearchState() } else if !fileSearchResults.isEmpty { fileSearchStatus = .cancelled(processed: fileSearchScannedFolders) + fileSearchCurrentPath = nil } return } session.cancel() fileSearchSession = nil + fileSearchCurrentPath = nil if clearResults { resetFileSearchState() } @@ -383,6 +387,14 @@ class Hotline: Equatable, HotlineClientDelegate, HotlineFileDownloadClientDelega fileSearchStatus = .searching(processed: processed, pending: pending) } + @MainActor fileprivate func searchSession(_ session: FileSearchSession, didFocusOn path: [String]) { + guard fileSearchSession === session else { + return + } + + fileSearchCurrentPath = path + } + @MainActor fileprivate func searchSessionDidFinish(_ session: FileSearchSession, processed: Int, pending: Int, completed: Bool) { guard fileSearchSession === session else { return @@ -390,6 +402,7 @@ class Hotline: Equatable, HotlineClientDelegate, HotlineFileDownloadClientDelega fileSearchScannedFolders = processed fileSearchSession = nil + fileSearchCurrentPath = nil if completed { fileSearchStatus = .completed(processed: processed) } else { @@ -403,6 +416,7 @@ class Hotline: Equatable, HotlineClientDelegate, HotlineFileDownloadClientDelega fileSearchStatus = .idle fileSearchQuery = "" fileSearchScannedFolders = 0 + fileSearchCurrentPath = nil } private func searchPathKey(for path: [String]) -> String { @@ -1315,11 +1329,13 @@ final class FileSearchSession { await Task.yield() if !hotline.filesLoaded { + hotline.searchSession(self, didFocusOn: []) let rootFiles = await hotline.getFileList(path: [], suppressErrors: true) processedCount = max(processedCount, 1) processListing(rootFiles, depth: 0) } else { + hotline.searchSession(self, didFocusOn: []) processedCount = max(processedCount, 1) processListing(hotline.files, depth: 0) } @@ -1333,6 +1349,7 @@ final class FileSearchSession { continue } + hotline.searchSession(self, didFocusOn: task.path) visited.insert(pathKey(for: task.path)) let children = await hotline.getFileList(path: task.path, suppressErrors: true) diff --git a/Hotline/macOS/FilesView.swift b/Hotline/macOS/FilesView.swift index 206e0a4..4498dc9 100644 --- a/Hotline/macOS/FilesView.swift +++ b/Hotline/macOS/FilesView.swift @@ -255,6 +255,16 @@ struct FilesView: View { return nil } } + + private var searchStatusPath: String? { + guard let path = model.fileSearchCurrentPath else { + return nil + } + if path.isEmpty { + return "/" + } + return "/" + path.joined(separator: "/") + "/" + } private func openPreviewWindow(_ previewInfo: PreviewFileInfo) { switch previewInfo.previewType { @@ -529,56 +539,64 @@ struct FilesView: View { } .safeAreaInset(edge: .top) { if isShowingSearchResults, let message = searchStatusMessage { - HStack { - Spacer() - + HStack(alignment: .center, spacing: 6) { if case .searching(_, _) = model.fileSearchStatus { ProgressView() .controlSize(.small) - .tint(.red) + .accentColor(.white) + .tint(.white) } else if case .completed = model.fileSearchStatus { Image(systemName: "checkmark.circle.fill") .resizable() - .symbolRenderingMode(.palette) - .foregroundStyle(.white, .fileComplete) + .symbolRenderingMode(.monochrome) + .foregroundStyle(.white) .aspectRatio(contentMode: .fit) .frame(width: 16, height: 16) } else if case .failed = model.fileSearchStatus { Image(systemName: "exclamationmark.triangle.fill") .resizable() - .symbolRenderingMode(.multicolor) + .symbolRenderingMode(.monochrome) + .foregroundStyle(.white) .aspectRatio(contentMode: .fit) .frame(width: 16, height: 16) } Text(message) + .lineLimit(1) .font(.body) .foregroundStyle(.white) Spacer() + + if let pathMessage = searchStatusPath { + Text(pathMessage) + .lineLimit(1) + .truncationMode(.tail) + .font(.footnote) +// .fontWeight(.semibold) + .foregroundStyle(.white) + .opacity(0.5) + .padding(.top, 2) + } } -// .overlay(alignment: .leading) { -// Button { -// model.cancelFileSearch(clearResults: true) -// } label: { -// Image(systemName: "xmark.circle.fill") -// .resizable() -// .scaledToFit() -// .foregroundStyle(.white) -// .opacity(0.8) -// } -// .buttonStyle(.plain) -// } - .padding(.horizontal, 8) - .padding(.leading, 4) + .padding(.trailing, 14) + .padding(.leading, 8) .padding(.vertical, 8) .background { - Color(nsColor: .controlAccentColor) - .clipShape(.capsule(style: .continuous)) + Group { + if case .completed = model.fileSearchStatus { + Color.fileComplete + } + else { + Color(nsColor: .controlAccentColor) + } + } + .clipShape(.capsule(style: .continuous)) } .padding(.horizontal, 8) + .padding(.top, 8) } } } diff --git a/Hotline/macOS/TrackerView.swift b/Hotline/macOS/TrackerView.swift index 83a56b8..c14f982 100644 --- a/Hotline/macOS/TrackerView.swift +++ b/Hotline/macOS/TrackerView.swift @@ -416,8 +416,9 @@ struct TrackerView: View { // Not expanded, expand it (which also fetches) self.setExpanded(true, for: bookmark) } + return } - return + break default: break } |