Last active
December 3, 2023 23:17
-
-
Save katoemba/d8e806e8bc0a5960a2b0f0e27d2b9adb to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// ContentView.swift | |
// RigelianTagger | |
// | |
// Created by Berrie Kremers on 05/03/2022. | |
// | |
// This shows how a directory structure can be shown in a sidebar, with dynamic loading | |
// of additional content as the user drills down in the hierarchy. | |
// | |
import SwiftUI | |
class FileItem: ObservableObject, Identifiable { | |
private(set) var name: String | |
private(set) var url: URL | |
@Published private(set) var children: [FileItem]? | |
private var loaded = false | |
var isDirectory: Bool { | |
children != nil | |
} | |
var isFile: Bool { | |
children == nil | |
} | |
static func root(_ root: URL? = nil) -> FileItem { | |
let url = root ?? URL(fileURLWithPath: "/Users/berrie/Music", isDirectory: true) | |
let item = FileItem(name: url.lastPathComponent, url: url, children: []) | |
// Immediately load the children of this item, so that this data is available when the item is opened | |
Task { | |
await item.loadChildren() | |
} | |
return item | |
} | |
internal init(name: String, url: URL, children: [FileItem]? = nil) { | |
self.name = name | |
self.url = url | |
self.children = children | |
} | |
func reset(_ root: URL) { | |
loaded = false | |
url = root | |
name = root.lastPathComponent | |
children = [] | |
Task { | |
await loadChildren() | |
} | |
} | |
func loadChildren() async { | |
guard loaded == false, isDirectory else { return } | |
loaded = true | |
do { | |
let sortedChildren: [FileItem] = try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles]).map { | |
let child = FileItem(name: $0.lastPathComponent, url: $0) | |
if $0.hasDirectoryPath { | |
child.children = [] | |
} | |
return child | |
} | |
.sorted(by: { $0.name < $1.name }) | |
DispatchQueue.main.async { | |
self.children = sortedChildren | |
} | |
} | |
catch { | |
print("\(error)") | |
} | |
} | |
var id: String { url.absoluteString } | |
} | |
private func toggleSidebar() { | |
NSApp.keyWindow?.contentViewController?.tryToPerform(#selector(NSSplitViewController.toggleSidebar(_:)), with: nil) | |
} | |
struct Sidebar: View { | |
@ObservedObject var root = FileItem.root() | |
var body: some View { | |
List([root], children: \.children) { item in | |
NavigationLink(destination: Text("Selected " + item.name)) { | |
HStack { | |
Image(systemName: item.isDirectory ? "folder" : "doc") | |
Text(item.name) | |
} | |
.onAppear { | |
// Whenever an item appears, load its children. | |
Task { | |
await item.loadChildren() | |
} | |
} | |
} | |
} | |
.listStyle(.sidebar) | |
.frame(minWidth: 150) | |
.toolbar { | |
Button(action: toggleSidebar, label: { | |
Image(systemName: "sidebar.left").help("Toggle Sidebar") | |
}) | |
Button("Select Root") { | |
let openPanel = NSOpenPanel() | |
openPanel.prompt = "Select Directory" | |
openPanel.allowsMultipleSelection = false | |
openPanel.canChooseDirectories = true | |
openPanel.canCreateDirectories = false | |
openPanel.canChooseFiles = false | |
openPanel.begin { (result) -> Void in | |
if result.rawValue == NSApplication.ModalResponse.OK.rawValue { | |
guard let url = openPanel.url else { return } | |
root.reset(url) | |
} | |
} | |
} | |
} | |
} | |
} | |
struct ContentView: View { | |
var body: some View { | |
NavigationView { | |
Sidebar() | |
Text("Main Window") | |
.frame(minWidth: 200) | |
} | |
} | |
} | |
struct ContentView_Previews: PreviewProvider { | |
static var previews: some View { | |
ContentView() | |
} | |
} |
ezathashim
commented
Oct 2, 2023
- very nice - thanks!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment