Skip to content

Commit c42d5b2

Browse files
Added Open Handler Callback For Recents List
### Description Added `openHandler` callbacks so developers can use custom opening logic with the default `RecentsListView`; falls back to `NSDocumentController` when not provided. ### Checklist - [x] I read and understood the [contributing guide](https://github.com/CodeEditApp/CodeEdit/blob/main/CONTRIBUTING.md) as well as the [code of conduct](https://github.com/CodeEditApp/CodeEdit/blob/main/CODE_OF_CONDUCT.md) - [x] The issues this PR addresses are related to each other - [x] My changes generate no new warnings - [x] My code builds and runs on my machine - [x] My changes are all related to the related issue above - [x] I documented my code
2 parents f8a17a9 + 3d493a9 commit c42d5b2

File tree

4 files changed

+60
-27
lines changed

4 files changed

+60
-27
lines changed

Sources/WelcomeWindow/Views/RecentsListView.swift

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,27 @@ public struct RecentsListView: View {
2020

2121
@FocusState.Binding private var focusedField: FocusTarget?
2222
private let dismissWindow: () -> Void
23+
private let openHandler: WelcomeOpenHandler
2324

2425
public init(
2526
recentProjects: Binding<[URL]>,
2627
selection: Binding<Set<URL>>,
2728
focusedField: FocusState<FocusTarget?>.Binding,
28-
dismissWindow: @escaping () -> Void
29+
dismissWindow: @escaping () -> Void,
30+
openHandler: @escaping WelcomeOpenHandler
2931
) {
3032
self._recentProjects = recentProjects
3133
self._selection = selection
3234
self._focusedField = focusedField
3335
self.dismissWindow = dismissWindow
36+
self.openHandler = openHandler
3437
}
3538

3639
private var isFocused: Bool {
3740
focusedField == .recentProjects
3841
}
3942

40-
@ViewBuilder
41-
private var listEmptyView: some View {
43+
@ViewBuilder private var listEmptyView: some View {
4244
VStack {
4345
Spacer()
4446
Text("No Recent Projects")
@@ -70,11 +72,7 @@ public struct RecentsListView: View {
7072
}
7173
}
7274
} primaryAction: { items in
73-
for url in items {
74-
NSDocumentController.shared.openDocument(at: url) {
75-
dismissWindow()
76-
}
77-
}
75+
openHandler(Array(items), dismissWindow)
7876
}
7977
.onCopyCommand {
8078
selection.map { NSItemProvider(object: $0.path(percentEncoded: false) as NSString) }
@@ -84,11 +82,7 @@ public struct RecentsListView: View {
8482
}
8583
.background {
8684
Button("") {
87-
selection.forEach { url in
88-
NSDocumentController.shared.openDocument(at: url) {
89-
dismissWindow()
90-
}
91-
}
85+
openHandler(Array(selection), dismissWindow)
9286
}
9387
.keyboardShortcut(.defaultAction)
9488
.hidden()

Sources/WelcomeWindow/Views/WelcomeView.swift

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,7 @@ public struct WelcomeView<SubtitleView: View>: View {
6464
}
6565
}
6666

67-
@ViewBuilder
68-
private var mainContent: some View {
67+
@ViewBuilder private var mainContent: some View {
6968
VStack(spacing: 0) {
7069
Spacer().frame(height: 32)
7170
ZStack {
@@ -154,8 +153,7 @@ public struct WelcomeView<SubtitleView: View>: View {
154153
}
155154
}
156155

157-
@ViewBuilder
158-
private var dismissButton: some View {
156+
@ViewBuilder private var dismissButton: some View {
159157
Button(action: dismissWindow) {
160158
Image(systemName: "xmark.circle.fill")
161159
.foregroundColor(isHoveringCloseButton ? Color(.secondaryLabelColor) : Color(.tertiaryLabelColor))

Sources/WelcomeWindow/Views/WelcomeWindow.swift

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public struct WelcomeWindow<RecentsView: View, SubtitleView: View>: Scene {
1515
private let customRecentsList: ((_ dismissWindow: @escaping () -> Void) -> RecentsView)?
1616
private let onDrop: ((_ url: URL, _ dismiss: @escaping () -> Void) -> Void)?
1717
private let subtitleView: (() -> SubtitleView)?
18+
private let openHandler: WelcomeOpenHandler?
1819

1920
let iconImage: Image?
2021
let title: String?
@@ -33,14 +34,16 @@ public struct WelcomeWindow<RecentsView: View, SubtitleView: View>: Scene {
3334
@ActionsBuilder actions: @escaping (_ dismissWindow: @escaping () -> Void) -> WelcomeActions,
3435
customRecentsList: ((_ dismissWindow: @escaping () -> Void) -> RecentsView)? = nil,
3536
subtitleView: (() -> SubtitleView)? = nil,
36-
onDrop: ((_ url: URL, _ dismiss: @escaping () -> Void) -> Void)? = nil
37+
onDrop: ((_ url: URL, _ dismiss: @escaping () -> Void) -> Void)? = nil,
38+
openHandler: WelcomeOpenHandler? = nil
3739
) {
3840
self.iconImage = iconImage
3941
self.title = title
4042
self.buildActions = actions
4143
self.customRecentsList = customRecentsList
4244
self.subtitleView = subtitleView
4345
self.onDrop = onDrop
46+
self.openHandler = openHandler
4447
}
4548

4649
public var body: some Scene {
@@ -52,7 +55,8 @@ public struct WelcomeWindow<RecentsView: View, SubtitleView: View>: Scene {
5255
subtitleView: subtitleView,
5356
buildActions: buildActions,
5457
onDrop: onDrop,
55-
customRecentsList: customRecentsList
58+
customRecentsList: customRecentsList,
59+
openHandler: openHandler
5660
)
5761
.frame(width: 740, height: isMacOS26 ? 460 - 28 : 460)
5862
.task {
@@ -71,6 +75,20 @@ public struct WelcomeWindow<RecentsView: View, SubtitleView: View>: Scene {
7175
}
7276
}
7377

78+
/// A closure type used to handle opening recent items from the default `RecentsListView`.
79+
///
80+
/// This allows apps to override the default `NSDocumentController` behavior for
81+
/// opening files, making the handling of recent-item URLs fully configurable.
82+
///
83+
/// The closure is executed on the **main actor** to ensure UI safety.
84+
///
85+
/// - Parameters:
86+
/// - urls: The recent-item URLs selected by the user to be opened.
87+
/// - dismiss: A closure to invoke when the opening process is complete and the
88+
/// `RecentsListView` can be dismissed.
89+
///
90+
public typealias WelcomeOpenHandler = @MainActor (_ urls: [URL], _ dismiss: @escaping () -> Void) -> Void
91+
7492
// ──────────────────────────────────────────────────────────────
7593
// 1) NEITHER a custom recents list NOR a subtitle view
7694
// ──────────────────────────────────────────────────────────────
@@ -81,15 +99,17 @@ extension WelcomeWindow where RecentsView == EmptyView, SubtitleView == EmptyVie
8199
iconImage: Image? = nil,
82100
title: String? = nil,
83101
@ActionsBuilder actions: @escaping (_ dismissWindow: @escaping () -> Void) -> WelcomeActions,
84-
onDrop: ((_ url: URL, _ dismissWindow: @escaping () -> Void) -> Void)? = nil
102+
onDrop: ((_ url: URL, _ dismissWindow: @escaping () -> Void) -> Void)? = nil,
103+
openHandler: WelcomeOpenHandler? = nil
85104
) {
86105
self.init(
87106
iconImage: iconImage,
88107
title: title,
89108
actions: actions,
90109
customRecentsList: nil,
91110
subtitleView: nil,
92-
onDrop: onDrop
111+
onDrop: onDrop,
112+
openHandler: openHandler
93113
)
94114
}
95115
}
@@ -105,15 +125,17 @@ extension WelcomeWindow where RecentsView == EmptyView {
105125
title: String? = nil,
106126
subtitleView: @escaping () -> SubtitleView,
107127
@ActionsBuilder actions: @escaping (_ dismissWindow: @escaping () -> Void) -> WelcomeActions,
108-
onDrop: ((_ url: URL, _ dismissWindow: @escaping () -> Void) -> Void)? = nil
128+
onDrop: ((_ url: URL, _ dismissWindow: @escaping () -> Void) -> Void)? = nil,
129+
openHandler: WelcomeOpenHandler? = nil
109130
) {
110131
self.init(
111132
iconImage: iconImage,
112133
title: title,
113134
actions: actions,
114135
customRecentsList: nil,
115136
subtitleView: subtitleView,
116-
onDrop: onDrop
137+
onDrop: onDrop,
138+
openHandler: openHandler
117139
)
118140
}
119141
}
@@ -129,15 +151,17 @@ extension WelcomeWindow where SubtitleView == EmptyView {
129151
title: String? = nil,
130152
@ActionsBuilder actions: @escaping (_ dismissWindow: @escaping () -> Void) -> WelcomeActions,
131153
customRecentsList: ((_ dismissWindow: @escaping () -> Void) -> RecentsView)? = nil,
132-
onDrop: ((_ url: URL, _ dismissWindow: @escaping () -> Void) -> Void)? = nil
154+
onDrop: ((_ url: URL, _ dismissWindow: @escaping () -> Void) -> Void)? = nil,
155+
openHandler: WelcomeOpenHandler? = nil
133156
) {
134157
self.init(
135158
iconImage: iconImage,
136159
title: title,
137160
actions: actions,
138161
customRecentsList: customRecentsList,
139162
subtitleView: nil,
140-
onDrop: onDrop
163+
onDrop: onDrop,
164+
openHandler: openHandler
141165
)
142166
}
143167
}

Sources/WelcomeWindow/Views/WelcomeWindowView.swift

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public struct WelcomeWindowView<RecentsView: View, SubtitleView: View>: View {
2525
private let onDrop: ((_ url: URL, _ dismiss: @escaping () -> Void) -> Void)?
2626
private let customRecentsList: ((_ dismissWindow: @escaping () -> Void) -> RecentsView)?
2727
private let subtitleView: (() -> SubtitleView)?
28+
private let openHandler: WelcomeOpenHandler?
2829

2930
let iconImage: Image?
3031
let title: String?
@@ -35,19 +36,34 @@ public struct WelcomeWindowView<RecentsView: View, SubtitleView: View>: View {
3536
subtitleView: (() -> SubtitleView)? = nil,
3637
buildActions: @escaping (_ dismissWindow: @escaping () -> Void) -> WelcomeActions,
3738
onDrop: ((_ url: URL, _ dismiss: @escaping () -> Void) -> Void)? = nil,
38-
customRecentsList: ((_ dismissWindow: @escaping () -> Void) -> RecentsView)? = nil
39+
customRecentsList: ((_ dismissWindow: @escaping () -> Void) -> RecentsView)? = nil,
40+
openHandler: WelcomeOpenHandler? = nil
3941
) {
4042
self.iconImage = iconImage
4143
self.title = title
4244
self.subtitleView = subtitleView
4345
self.buildActions = buildActions
4446
self.onDrop = onDrop
4547
self.customRecentsList = customRecentsList
48+
self.openHandler = openHandler
49+
}
50+
51+
private func defaultOpenHandler(urls: [URL], dismiss: @escaping () -> Void) {
52+
var dismissed = false
53+
for url in urls {
54+
NSDocumentController.shared.openDocument(at: url) {
55+
if !dismissed {
56+
dismissed = true
57+
dismiss()
58+
}
59+
}
60+
}
4661
}
4762

4863
public var body: some View {
4964
let dismiss = dismissWindow.callAsFunction
5065
let actions = buildActions(dismiss)
66+
let effectiveOpen = openHandler ?? defaultOpenHandler
5167

5268
return HStack(spacing: 0) {
5369
WelcomeView(
@@ -67,7 +83,8 @@ public struct WelcomeWindowView<RecentsView: View, SubtitleView: View>: View {
6783
recentProjects: $recentProjects,
6884
selection: $selection,
6985
focusedField: $focusedField,
70-
dismissWindow: dismiss
86+
dismissWindow: dismiss,
87+
openHandler: effectiveOpen
7188
)
7289
}
7390
}

0 commit comments

Comments
 (0)