fix(workspace): refresh expanded folder contents

This commit is contained in:
Feror 2026-06-02 23:35:50 +02:00
parent 3b9b165464
commit bfedd11186
2 changed files with 38 additions and 14 deletions

View file

@ -62,6 +62,7 @@ private final class SaplingAppModel: ObservableObject {
@Published var workspace: Workspace?
@Published var workspaceChildren: [URL: [WorkspaceItem]] = [:]
@Published var loadingTreeItemURLs: Set<URL> = []
@Published var workspaceTreeRevision = 0
@Published var workspaceSelection: WorkspaceTreeSelection?
@Published var selectedProject: Project?
@Published var selectedDocument: MarkdownDocument?
@ -133,6 +134,7 @@ private final class SaplingAppModel: ObservableObject {
self.workspace = workspace
workspaceChildren = [:]
loadingTreeItemURLs = []
invalidateWorkspaceTree()
workspaceSelection = nil
selectedProject = nil
selectedDocument = nil
@ -167,6 +169,7 @@ private final class SaplingAppModel: ObservableObject {
var loadingURLs = loadingTreeItemURLs
loadingURLs.insert(url)
loadingTreeItemURLs = loadingURLs
invalidateWorkspaceTree()
let workspaceManager = self.workspaceManager
Task {
do {
@ -176,6 +179,7 @@ private final class SaplingAppModel: ObservableObject {
var loadedChildren = workspaceChildren
loadedChildren[url] = children
workspaceChildren = loadedChildren
invalidateWorkspaceTree()
} catch {
editorErrorMessage = "Unable to load \(item.displayName): \(error.localizedDescription)"
logger.error("Failed to load workspace tree item: \(error)", category: .workspace)
@ -183,6 +187,7 @@ private final class SaplingAppModel: ObservableObject {
var loadingURLs = loadingTreeItemURLs
loadingURLs.remove(url)
loadingTreeItemURLs = loadingURLs
invalidateWorkspaceTree()
}
}
@ -253,6 +258,10 @@ private final class SaplingAppModel: ObservableObject {
configuration.recentWorkspaceURLs = Array(configuration.recentWorkspaceURLs.prefix(10))
save(configuration: configuration)
}
private func invalidateWorkspaceTree() {
workspaceTreeRevision &+= 1
}
}
private struct MainWindow: View {
@ -265,6 +274,7 @@ private struct MainWindow: View {
WorkspaceTreeView(
workspace: model.workspace,
isLoadingWorkspace: model.isLoadingWorkspace,
treeContentRevision: model.workspaceTreeRevision,
selection: $model.workspaceSelection,
childrenFor: model.children(for:),
isLoadingChildren: model.isLoadingChildren(for:),

View file

@ -11,6 +11,7 @@ public enum WorkspaceTreeSelection: Hashable, Sendable {
public struct WorkspaceTreeView: View {
private let workspace: Workspace?
private let isLoadingWorkspace: Bool
private let treeContentRevision: Int
@Binding private var selection: WorkspaceTreeSelection?
private let childrenFor: (WorkspaceItem) -> [WorkspaceItem]
private let isLoadingChildren: (WorkspaceItem) -> Bool
@ -22,6 +23,7 @@ public struct WorkspaceTreeView: View {
public init(
workspace: Workspace?,
isLoadingWorkspace: Bool = false,
treeContentRevision: Int = 0,
selection: Binding<WorkspaceTreeSelection?>,
childrenFor: @escaping (WorkspaceItem) -> [WorkspaceItem] = { $0.children ?? [] },
isLoadingChildren: @escaping (WorkspaceItem) -> Bool = { _ in false },
@ -32,6 +34,7 @@ public struct WorkspaceTreeView: View {
) {
self.workspace = workspace
self.isLoadingWorkspace = isLoadingWorkspace
self.treeContentRevision = treeContentRevision
self._selection = selection
self.childrenFor = childrenFor
self.isLoadingChildren = isLoadingChildren
@ -53,6 +56,7 @@ public struct WorkspaceTreeView: View {
ForEach(workspace.items, id: \.stableTreeID) { item in
WorkspaceItemRow(
item: item,
treeContentRevision: treeContentRevision,
selection: $selection,
childrenFor: childrenFor,
isLoadingChildren: isLoadingChildren,
@ -87,6 +91,7 @@ public struct WorkspaceTreeView: View {
private struct WorkspaceItemRow: View {
let item: WorkspaceItem
let treeContentRevision: Int
@Binding var selection: WorkspaceTreeSelection?
let childrenFor: (WorkspaceItem) -> [WorkspaceItem]
let isLoadingChildren: (WorkspaceItem) -> Bool
@ -159,22 +164,26 @@ private struct WorkspaceItemRow: View {
@ViewBuilder
private var children: some View {
if isLoadingChildren(item) {
Label("Loading...", systemImage: "progress.indicator")
.foregroundStyle(.secondary)
} else {
ForEach(childrenFor(item), id: \.stableTreeID) { child in
WorkspaceItemRow(
item: child,
selection: $selection,
childrenFor: childrenFor,
isLoadingChildren: isLoadingChildren,
onExpandItem: onExpandItem,
onSelectFile: onSelectFile,
onSelectProject: onSelectProject
)
Group {
if isLoadingChildren(item) {
Label("Loading...", systemImage: "progress.indicator")
.foregroundStyle(.secondary)
} else {
ForEach(childrenFor(item), id: \.stableTreeID) { child in
WorkspaceItemRow(
item: child,
treeContentRevision: treeContentRevision,
selection: $selection,
childrenFor: childrenFor,
isLoadingChildren: isLoadingChildren,
onExpandItem: onExpandItem,
onSelectFile: onSelectFile,
onSelectProject: onSelectProject
)
}
}
}
.id(WorkspaceChildrenContentID(itemURL: item.stableTreeID, revision: treeContentRevision))
}
private func iconName(for kind: WorkspaceFileKind) -> String {
@ -186,6 +195,11 @@ private struct WorkspaceItemRow: View {
}
}
private struct WorkspaceChildrenContentID: Hashable {
var itemURL: URL
var revision: Int
}
private extension WorkspaceItem {
var stableTreeID: URL {
switch self {