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

View file

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