Improve file browser to have back button

This commit is contained in:
Peter Steinberger 2025-06-20 19:06:40 +02:00
parent f197e26fb0
commit 3d775afaaa

View file

@ -8,7 +8,8 @@ import QuickLook
/// navigation, selection, and directory creation capabilities. /// navigation, selection, and directory creation capabilities.
struct FileBrowserView: View { struct FileBrowserView: View {
@State private var viewModel = FileBrowserViewModel() @State private var viewModel = FileBrowserViewModel()
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss)
private var dismiss
@State private var showingFileEditor = false @State private var showingFileEditor = false
@State private var showingNewFileAlert = false @State private var showingNewFileAlert = false
@State private var newFileName = "" @State private var newFileName = ""
@ -39,18 +40,44 @@ struct FileBrowserView: View {
Color.black.ignoresSafeArea() Color.black.ignoresSafeArea()
VStack(spacing: 0) { VStack(spacing: 0) {
// Current path display // Navigation header
HStack(spacing: 12) { HStack(spacing: 16) {
Image(systemName: "folder.fill") // Back button
.foregroundColor(Theme.Colors.terminalAccent) if viewModel.canGoUp {
.font(.system(size: 16)) Button {
UIImpactFeedbackGenerator(style: .light).impactOccurred()
viewModel.navigateToParent()
} label: {
HStack(spacing: 6) {
Image(systemName: "chevron.left")
.font(.system(size: 14, weight: .semibold))
Text("Back")
.font(.custom("SF Mono", size: 14))
}
.foregroundColor(Theme.Colors.terminalAccent)
.padding(.horizontal, 12)
.padding(.vertical, 6)
.background(
RoundedRectangle(cornerRadius: 6)
.fill(Theme.Colors.terminalAccent.opacity(0.1))
)
}
.buttonStyle(TerminalButtonStyle())
}
// Current path display
HStack(spacing: 8) {
Image(systemName: "folder.fill")
.foregroundColor(Theme.Colors.terminalAccent)
.font(.system(size: 16))
Text(viewModel.currentPath) Text(viewModel.displayPath)
.font(.custom("SF Mono", size: 14)) .font(.custom("SF Mono", size: 14))
.foregroundColor(Theme.Colors.terminalGray) .foregroundColor(Theme.Colors.terminalGray)
.lineLimit(1) .lineLimit(1)
.truncationMode(.middle) .truncationMode(.middle)
.frame(maxWidth: .infinity, alignment: .leading) }
.frame(maxWidth: .infinity, alignment: .leading)
} }
.padding(.horizontal, 20) .padding(.horizontal, 20)
.padding(.vertical, 16) .padding(.vertical, 16)
@ -59,38 +86,24 @@ struct FileBrowserView: View {
// File list // File list
ScrollView { ScrollView {
LazyVStack(spacing: 0) { LazyVStack(spacing: 0) {
// Parent directory
if viewModel.canGoUp {
FileBrowserRow(
name: "..",
isDirectory: true,
isParent: true,
onTap: {
viewModel.navigateToParent()
}
)
.transition(.opacity)
}
// Directories first, then files // Directories first, then files
ForEach(viewModel.sortedEntries) { entry in ForEach(viewModel.sortedEntries) { entry in
FileBrowserRow( FileBrowserRow(
name: entry.name, name: entry.name,
isDirectory: entry.isDir, isDirectory: entry.isDir,
size: entry.isDir ? nil : entry.formattedSize, size: entry.isDir ? nil : entry.formattedSize,
modifiedTime: entry.formattedDate, modifiedTime: entry.formattedDate
onTap: { ) {
if entry.isDir { if entry.isDir {
viewModel.navigate(to: entry.path) viewModel.navigate(to: entry.path)
} else if mode == .browseFiles { } else if mode == .browseFiles {
// Preview file with Quick Look // Preview file with Quick Look
selectedFile = entry selectedFile = entry
Task { Task {
await viewModel.previewFile(entry) await viewModel.previewFile(entry)
}
} }
} }
) }
.transition(.opacity) .transition(.opacity)
// Context menu disabled - file operations not implemented in backend // Context menu disabled - file operations not implemented in backend
/* /*
@ -278,7 +291,7 @@ struct FileBrowserView: View {
if let file = selectedFile { if let file = selectedFile {
FileEditorView( FileEditorView(
path: file.path, path: file.path,
isNewFile: !viewModel.entries.contains(where: { $0.path == file.path }) isNewFile: !viewModel.entries.contains { $0.path == file.path }
) )
.onDisappear { .onDisappear {
// Reload directory to show any new files // Reload directory to show any new files
@ -450,6 +463,24 @@ class FileBrowserViewModel {
var canGoUp: Bool { var canGoUp: Bool {
currentPath != "/" && currentPath != "~" currentPath != "/" && currentPath != "~"
} }
var displayPath: String {
// Show a more user-friendly path
if currentPath == "/" {
return "/"
} else if currentPath.hasPrefix("/Users/") {
// Extract username from path like /Users/username/...
let components = currentPath.components(separatedBy: "/")
if components.count > 2 {
let username = components[2]
let homePath = "/Users/\(username)"
if currentPath == homePath || currentPath.hasPrefix(homePath + "/") {
return currentPath.replacingOccurrences(of: homePath, with: "~")
}
}
}
return currentPath
}
func loadDirectory(path: String) { func loadDirectory(path: String) {
Task { Task {
@ -470,7 +501,7 @@ class FileBrowserViewModel {
entries = result.files entries = result.files
} }
} catch { } catch {
print("[FileBrowser] Failed to load directory: \(error)") // Failed to load directory: \(error)
errorMessage = "Failed to load directory: \(error.localizedDescription)" errorMessage = "Failed to load directory: \(error.localizedDescription)"
showError = true showError = true
} }
@ -505,7 +536,7 @@ class FileBrowserViewModel {
// Reload directory to show new folder // Reload directory to show new folder
await loadDirectoryAsync(path: currentPath) await loadDirectoryAsync(path: currentPath)
} catch { } catch {
print("[FileBrowser] Failed to create folder: \(error)") // Failed to create folder: \(error)
errorMessage = "Failed to create folder: \(error.localizedDescription)" errorMessage = "Failed to create folder: \(error.localizedDescription)"
showError = true showError = true
UINotificationFeedbackGenerator().notificationOccurred(.error) UINotificationFeedbackGenerator().notificationOccurred(.error)
@ -532,7 +563,7 @@ class FileBrowserViewModel {
} }
#Preview { #Preview {
FileBrowserView { path in FileBrowserView { _ in
print("Selected path: \(path)") // Selected path
} }
} }