mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-27 15:17:38 +00:00
Improve file browser to have back button
This commit is contained in:
parent
f197e26fb0
commit
3d775afaaa
1 changed files with 72 additions and 41 deletions
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue