Skip to content

Commit a66bbe7

Browse files
committed
Add UI to present errors in widget
1 parent 7e982b7 commit a66bbe7

5 files changed

Lines changed: 190 additions & 114 deletions

File tree

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import SwiftUI
2+
3+
struct CodeBlock: View {
4+
var suggestion: SuggestionPanelViewModel.Suggestion
5+
6+
var body: some View {
7+
VStack {
8+
ForEach(0..<suggestion.code.endIndex, id: \.self) { index in
9+
HStack(alignment: .firstTextBaseline) {
10+
Text("\(index + suggestion.startLineIndex + 1)")
11+
.multilineTextAlignment(.trailing)
12+
.foregroundColor(.secondary)
13+
.frame(minWidth: 40)
14+
Text(AttributedString(suggestion.code[index]))
15+
.foregroundColor(.white.opacity(0.1))
16+
.frame(maxWidth: .infinity, alignment: .leading)
17+
.multilineTextAlignment(.leading)
18+
.lineSpacing(4)
19+
}
20+
}
21+
}
22+
.foregroundColor(.white)
23+
.font(.system(size: 12, design: .monospaced))
24+
.padding()
25+
}
26+
}
27+
28+
struct CodeBlockSuggestionPanel: View {
29+
@ObservedObject var viewModel: SuggestionPanelViewModel
30+
var suggestion: SuggestionPanelViewModel.Suggestion
31+
32+
struct ToolBar: View {
33+
@ObservedObject var viewModel: SuggestionPanelViewModel
34+
var suggestion: SuggestionPanelViewModel.Suggestion
35+
36+
var body: some View {
37+
HStack {
38+
Button(action: {
39+
viewModel.onPreviousButtonTapped?()
40+
}) {
41+
Image(systemName: "chevron.left")
42+
}.buttonStyle(.plain)
43+
44+
Text(
45+
"\(suggestion.currentSuggestionIndex + 1) / \(suggestion.suggestionCount)"
46+
)
47+
.monospacedDigit()
48+
49+
Button(action: {
50+
viewModel.onNextButtonTapped?()
51+
}) {
52+
Image(systemName: "chevron.right")
53+
}.buttonStyle(.plain)
54+
55+
Spacer()
56+
57+
Button(action: {
58+
viewModel.onRejectButtonTapped?()
59+
}) {
60+
Text("Reject")
61+
}.buttonStyle(CommandButtonStyle(color: .gray))
62+
63+
Button(action: {
64+
viewModel.onAcceptButtonTapped?()
65+
}) {
66+
Text("Accept")
67+
}.buttonStyle(CommandButtonStyle(color: .indigo))
68+
}
69+
.padding()
70+
.foregroundColor(.secondary)
71+
.background(.regularMaterial)
72+
}
73+
}
74+
75+
var body: some View {
76+
VStack(spacing: 0) {
77+
ScrollView {
78+
CodeBlock(suggestion: suggestion)
79+
.frame(maxWidth: .infinity)
80+
}
81+
.background(Color(nsColor: {
82+
switch viewModel.colorScheme {
83+
case .dark:
84+
return #colorLiteral(red: 0.1580096483, green: 0.1730263829, blue: 0.2026666105, alpha: 1)
85+
case .light:
86+
return .white
87+
@unknown default:
88+
return .white
89+
}
90+
}()))
91+
92+
ToolBar(viewModel: viewModel, suggestion: suggestion)
93+
}
94+
}
95+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import SwiftUI
2+
3+
struct ErrorPanel: View {
4+
@ObservedObject var viewModel: SuggestionPanelViewModel
5+
var description: String
6+
7+
var body: some View {
8+
ZStack(alignment: .topTrailing) {
9+
Text(description)
10+
.multilineTextAlignment(.leading)
11+
.frame(maxWidth: .infinity, alignment: .leading)
12+
.foregroundColor(.white)
13+
.padding()
14+
.background(Color.red)
15+
16+
// close button
17+
Button(action: {
18+
viewModel.isPanelDisplayed = false
19+
viewModel.content = .empty
20+
}) {
21+
Image(systemName: "xmark")
22+
.padding([.leading, .bottom], 16)
23+
.padding([.top, .trailing], 8)
24+
}
25+
.buttonStyle(.plain)
26+
}
27+
}
28+
}

Core/Sources/SuggestionWidget/SuggestionPanelView.swift

Lines changed: 39 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,13 @@ final class SuggestionPanelViewModel: ObservableObject {
1616
)
1717
}
1818

19-
@Published var suggestion: Suggestion
19+
enum Content: Equatable {
20+
case empty
21+
case suggestion(Suggestion)
22+
case error(String)
23+
}
24+
25+
@Published var content: Content
2026
@Published var isPanelDisplayed: Bool
2127
@Published var alignTopToAnchor = false
2228
@Published var colorScheme: ColorScheme
@@ -27,15 +33,15 @@ final class SuggestionPanelViewModel: ObservableObject {
2733
var onNextButtonTapped: (() -> Void)?
2834

2935
public init(
30-
suggestion: Suggestion = .empty,
36+
content: Content = .empty,
3137
isPanelDisplayed: Bool = false,
3238
colorScheme: ColorScheme = .dark,
3339
onAcceptButtonTapped: (() -> Void)? = nil,
3440
onRejectButtonTapped: (() -> Void)? = nil,
3541
onPreviousButtonTapped: (() -> Void)? = nil,
3642
onNextButtonTapped: (() -> Void)? = nil
3743
) {
38-
self.suggestion = suggestion
44+
self.content = content
3945
self.isPanelDisplayed = isPanelDisplayed
4046
self.colorScheme = colorScheme
4147
self.onAcceptButtonTapped = onAcceptButtonTapped
@@ -57,23 +63,13 @@ struct SuggestionPanelView: View {
5763
}
5864

5965
ZStack(alignment: .topLeading) {
60-
VStack(spacing: 0) {
61-
ScrollView {
62-
CodeBlock(viewModel: viewModel)
63-
.frame(maxWidth: .infinity)
64-
}
65-
.background(Color(nsColor: {
66-
switch viewModel.colorScheme {
67-
case .dark:
68-
return #colorLiteral(red: 0.1580096483, green: 0.1730263829, blue: 0.2026666105, alpha: 1)
69-
case .light:
70-
return .white
71-
@unknown default:
72-
return .white
73-
}
74-
}()))
75-
76-
ToolBar(viewModel: viewModel)
66+
switch viewModel.content {
67+
case .empty:
68+
EmptyView()
69+
case let .suggestion(suggestion):
70+
CodeBlockSuggestionPanel(viewModel: viewModel, suggestion: suggestion)
71+
case let .error(description):
72+
ErrorPanel(viewModel: viewModel, description: description)
7773
}
7874
}
7975
.frame(maxWidth: .infinity, maxHeight: Style.panelHeight)
@@ -88,7 +84,7 @@ struct SuggestionPanelView: View {
8884
.stroke(Color.white.opacity(0.2), style: .init(lineWidth: 1))
8985
.padding(1)
9086
)
91-
.allowsHitTesting(viewModel.isPanelDisplayed && !viewModel.suggestion.code.isEmpty)
87+
.allowsHitTesting(viewModel.isPanelDisplayed && viewModel.content != .empty)
9288
.preferredColorScheme(viewModel.colorScheme)
9389

9490
if viewModel.alignTopToAnchor {
@@ -99,81 +95,14 @@ struct SuggestionPanelView: View {
9995
}
10096
.opacity({
10197
guard viewModel.isPanelDisplayed else { return 0 }
102-
guard !viewModel.suggestion.code.isEmpty else { return 0 }
98+
guard viewModel.content != .empty else { return 0 }
10399
return 1
104100
}())
105-
.animation(.easeInOut(duration: 0.2), value: viewModel.suggestion)
101+
.animation(.easeInOut(duration: 0.2), value: viewModel.content)
106102
.animation(.easeInOut(duration: 0.2), value: viewModel.isPanelDisplayed)
107103
}
108104
}
109105

110-
struct CodeBlock: View {
111-
@ObservedObject var viewModel: SuggestionPanelViewModel
112-
113-
var body: some View {
114-
VStack {
115-
ForEach(0..<viewModel.suggestion.code.endIndex, id: \.self) { index in
116-
HStack(alignment: .firstTextBaseline) {
117-
Text("\(index + viewModel.suggestion.startLineIndex + 1)")
118-
.multilineTextAlignment(.trailing)
119-
.foregroundColor(.secondary)
120-
.frame(minWidth: 40)
121-
Text(AttributedString(viewModel.suggestion.code[index]))
122-
.foregroundColor(.white.opacity(0.1))
123-
.frame(maxWidth: .infinity, alignment: .leading)
124-
.multilineTextAlignment(.leading)
125-
.lineSpacing(4)
126-
}
127-
}
128-
}
129-
.foregroundColor(.white)
130-
.font(.system(size: 12, design: .monospaced))
131-
.padding()
132-
}
133-
}
134-
135-
struct ToolBar: View {
136-
@ObservedObject var viewModel: SuggestionPanelViewModel
137-
138-
var body: some View {
139-
HStack {
140-
Button(action: {
141-
viewModel.onPreviousButtonTapped?()
142-
}) {
143-
Image(systemName: "chevron.left")
144-
}.buttonStyle(.plain)
145-
146-
Text(
147-
"\(viewModel.suggestion.currentSuggestionIndex + 1) / \(viewModel.suggestion.suggestionCount)"
148-
)
149-
.monospacedDigit()
150-
151-
Button(action: {
152-
viewModel.onNextButtonTapped?()
153-
}) {
154-
Image(systemName: "chevron.right")
155-
}.buttonStyle(.plain)
156-
157-
Spacer()
158-
159-
Button(action: {
160-
viewModel.onRejectButtonTapped?()
161-
}) {
162-
Text("Reject")
163-
}.buttonStyle(CommandButtonStyle(color: .gray))
164-
165-
Button(action: {
166-
viewModel.onAcceptButtonTapped?()
167-
}) {
168-
Text("Accept")
169-
}.buttonStyle(CommandButtonStyle(color: .indigo))
170-
}
171-
.padding()
172-
.foregroundColor(.secondary)
173-
.background(.regularMaterial)
174-
}
175-
}
176-
177106
struct CommandButtonStyle: ButtonStyle {
178107
let color: Color
179108

@@ -194,10 +123,12 @@ struct CommandButtonStyle: ButtonStyle {
194123
}
195124
}
196125

126+
// MARK: - Previews
127+
197128
struct SuggestionPanelView_Dark_Preview: PreviewProvider {
198129
static var previews: some View {
199130
SuggestionPanelView(viewModel: .init(
200-
suggestion: .init(
131+
content: .suggestion(.init(
201132
startLineIndex: 8,
202133
code: highlighted(
203134
code: """
@@ -213,7 +144,7 @@ struct SuggestionPanelView_Dark_Preview: PreviewProvider {
213144
),
214145
suggestionCount: 2,
215146
currentSuggestionIndex: 0
216-
),
147+
)),
217148
isPanelDisplayed: true,
218149
colorScheme: .dark
219150
))
@@ -231,7 +162,7 @@ struct SuggestionPanelView_Dark_Preview: PreviewProvider {
231162
struct SuggestionPanelView_Bright_Preview: PreviewProvider {
232163
static var previews: some View {
233164
SuggestionPanelView(viewModel: .init(
234-
suggestion: .init(
165+
content: .suggestion(.init(
235166
startLineIndex: 8,
236167
code: highlighted(
237168
code: """
@@ -247,7 +178,7 @@ struct SuggestionPanelView_Bright_Preview: PreviewProvider {
247178
),
248179
suggestionCount: 2,
249180
currentSuggestionIndex: 0
250-
),
181+
)),
251182
isPanelDisplayed: true,
252183
colorScheme: .light
253184
))
@@ -265,7 +196,7 @@ struct SuggestionPanelView_Bright_Preview: PreviewProvider {
265196
struct SuggestionPanelView_Dark_Objc_Preview: PreviewProvider {
266197
static var previews: some View {
267198
SuggestionPanelView(viewModel: .init(
268-
suggestion: .init(
199+
content: .suggestion(.init(
269200
startLineIndex: 8,
270201
code: highlighted(
271202
code: """
@@ -278,7 +209,7 @@ struct SuggestionPanelView_Dark_Objc_Preview: PreviewProvider {
278209
),
279210
suggestionCount: 2,
280211
currentSuggestionIndex: 0
281-
),
212+
)),
282213
isPanelDisplayed: true,
283214
colorScheme: .dark
284215
))
@@ -296,7 +227,7 @@ struct SuggestionPanelView_Dark_Objc_Preview: PreviewProvider {
296227
struct SuggestionPanelView_Bright_Objc_Preview: PreviewProvider {
297228
static var previews: some View {
298229
SuggestionPanelView(viewModel: .init(
299-
suggestion: .init(
230+
content: .suggestion(.init(
300231
startLineIndex: 8,
301232
code: highlighted(
302233
code: """
@@ -309,7 +240,7 @@ struct SuggestionPanelView_Bright_Objc_Preview: PreviewProvider {
309240
),
310241
suggestionCount: 2,
311242
currentSuggestionIndex: 0
312-
),
243+
)),
313244
isPanelDisplayed: true,
314245
colorScheme: .light
315246
))
@@ -323,3 +254,13 @@ struct SuggestionPanelView_Bright_Objc_Preview: PreviewProvider {
323254
}
324255
}
325256
}
257+
258+
struct SuggestionPanelView_Error_Preview: PreviewProvider {
259+
static var previews: some View {
260+
SuggestionPanelView(viewModel: .init(
261+
content: .error("This is an error\nerror"),
262+
isPanelDisplayed: true
263+
))
264+
.frame(width: 450, height: 200)
265+
}
266+
}

0 commit comments

Comments
 (0)