1
0
mirror of https://github.com/nicoverbruggen/phpmon.git synced 2026-03-25 13:40:08 +01:00

🍱 Improve appearance of alerts

This commit is contained in:
2026-03-03 12:47:19 +01:00
parent e3df36622e
commit fae5e5c4fb
5 changed files with 68 additions and 61 deletions

View File

@@ -11,14 +11,16 @@ import SwiftUI
struct MarkdownTextView: View {
let string: String
let fontSize: CGFloat
let textColor: NSColor
init(_ string: String, fontSize: CGFloat = 12) {
init(_ string: String, fontSize: CGFloat = 12, textColor: NSColor = .labelColor) {
self.string = string
self.fontSize = fontSize
self.textColor = textColor
}
var body: some View {
MarkdownTextViewRepresentable(string: string, fontSize: fontSize)
MarkdownTextViewRepresentable(string: string, fontSize: fontSize, textColor: textColor)
}
}

View File

@@ -13,6 +13,7 @@ import AppKit
struct MarkdownTextViewRepresentable: NSViewRepresentable {
let string: String
let fontSize: CGFloat
let textColor: NSColor
// MARK: - Static Properties
@@ -44,14 +45,15 @@ struct MarkdownTextViewRepresentable: NSViewRepresentable {
func updateNSView(_ textView: CodeBlockTextView, context: Context) {
let coordinator = context.coordinator
guard string != coordinator.lastString || fontSize != coordinator.lastFontSize else { return }
guard string != coordinator.lastString || fontSize != coordinator.lastFontSize || textColor != coordinator.lastTextColor else { return }
configure(textView, coordinator: coordinator)
}
private func configure(_ textView: CodeBlockTextView, coordinator: Coordinator) {
coordinator.lastString = string
coordinator.lastFontSize = fontSize
let attributed = Self.buildAttributedString(from: string, fontSize: fontSize)
coordinator.lastTextColor = textColor
let attributed = Self.buildAttributedString(from: string, fontSize: fontSize, textColor: textColor)
textView.textStorage?.setAttributedString(attributed)
textView.invalidateIntrinsicContentSize()
}
@@ -59,11 +61,12 @@ struct MarkdownTextViewRepresentable: NSViewRepresentable {
class Coordinator {
var lastString: String?
var lastFontSize: CGFloat?
var lastTextColor: NSColor?
}
// MARK: - Attributed String Builder
static func buildAttributedString(from string: String, fontSize: CGFloat) -> NSAttributedString {
static func buildAttributedString(from string: String, fontSize: CGFloat, textColor: NSColor = .labelColor) -> NSAttributedString {
let result = NSMutableAttributedString()
let font = NSFont.systemFont(ofSize: fontSize)
@@ -73,7 +76,7 @@ struct MarkdownTextViewRepresentable: NSViewRepresentable {
let defaultAttributes: [NSAttributedString.Key: Any] = [
.font: font,
.foregroundColor: NSColor.labelColor,
.foregroundColor: textColor,
.paragraphStyle: paragraphStyle
]
@@ -85,8 +88,8 @@ struct MarkdownTextViewRepresentable: NSViewRepresentable {
// Collect code span ranges once for bold and italic passes
let codeRanges = codeSpanRanges(in: result)
handleBoldMarkup(in: result, fontSize: fontSize, paragraphStyle: paragraphStyle, codeRanges: codeRanges)
handleItalicMarkup(in: result, fontSize: fontSize, paragraphStyle: paragraphStyle, codeRanges: codeRanges)
handleBoldMarkup(in: result, fontSize: fontSize, paragraphStyle: paragraphStyle, textColor: textColor, codeRanges: codeRanges)
handleItalicMarkup(in: result, fontSize: fontSize, paragraphStyle: paragraphStyle, textColor: textColor, codeRanges: codeRanges)
return result
}
@@ -156,6 +159,7 @@ struct MarkdownTextViewRepresentable: NSViewRepresentable {
in result: NSMutableAttributedString,
fontSize: CGFloat,
paragraphStyle: NSParagraphStyle,
textColor: NSColor,
codeRanges: [NSRange]
) {
let boldFont = NSFont.boldSystemFont(ofSize: fontSize)
@@ -168,7 +172,7 @@ struct MarkdownTextViewRepresentable: NSViewRepresentable {
string: innerText,
attributes: [
.font: boldFont,
.foregroundColor: NSColor.labelColor,
.foregroundColor: textColor,
.paragraphStyle: paragraphStyle
]
)
@@ -182,6 +186,7 @@ struct MarkdownTextViewRepresentable: NSViewRepresentable {
in result: NSMutableAttributedString,
fontSize: CGFloat,
paragraphStyle: NSParagraphStyle,
textColor: NSColor,
codeRanges: [NSRange]
) {
let italicFont = NSFontManager.shared.convert(
@@ -197,7 +202,7 @@ struct MarkdownTextViewRepresentable: NSViewRepresentable {
string: innerText,
attributes: [
.font: italicFont,
.foregroundColor: NSColor.labelColor,
.foregroundColor: textColor,
.paragraphStyle: paragraphStyle
]
)

View File

@@ -13,24 +13,15 @@ struct StartupAlertHeaderView: View {
let subtitleText: String
var body: some View {
HStack(spacing: 12) {
Image(nsImage: NSApp.applicationIconImage)
.resizable()
.frame(width: 60, height: 60)
VStack(alignment: .leading, spacing: 5) {
Text(titleText)
.font(.system(size: 15, weight: .bold, design: .rounded))
.textSelection(.enabled)
.padding(.bottom, 2)
VStack(alignment: .leading, spacing: 5) {
Text(titleText)
.font(.system(size: 15, weight: .bold))
.textSelection(.enabled)
MarkdownTextView(subtitleText, fontSize: 13)
.fixedSize(horizontal: false, vertical: true)
}
Spacer()
MarkdownTextView(subtitleText, fontSize: 12)
.fixedSize(horizontal: false, vertical: true)
}
.padding(15)
.padding(.top, 0)
}
}

View File

@@ -13,39 +13,48 @@ struct StartupAlertView: View {
var body: some View {
VStack(spacing: 0) {
StartupAlertHeaderView(
titleText: viewModel.check.titleText,
subtitleText: viewModel.check.subtitleText
)
HStack(alignment: .top, spacing: 12) {
Image(nsImage: NSApp.applicationIconImage)
.resizable()
.frame(width: 60, height: 60)
// Fix command description: only shown in idle state when a fix is available
if viewModel.state == .idle && viewModel.hasFix {
StartupFixCommandView(
command: viewModel.check.fixDescription ?? ""
)
.padding(.horizontal, 10).padding(.leading, 72)
VStack(alignment: .leading, spacing: 10) {
StartupAlertHeaderView(
titleText: viewModel.check.titleText,
subtitleText: viewModel.check.subtitleText
)
.padding(.top, 5)
.padding(.bottom, 8)
// Fix command description: only shown in idle state when a fix is available
if viewModel.state == .idle && viewModel.hasFix {
StartupFixCommandView(
command: viewModel.check.fixDescription ?? ""
)
} else if viewModel.state == .idle && !viewModel.hasFix {
Divider()
}
// Terminal output: shown during and after fix execution
if !viewModel.outputLines.isEmpty
&& (viewModel.state == .running || viewModel.state == .completed || viewModel.state == .failed) {
StartupOutputView(
lines: viewModel.outputLines,
isRunning: viewModel.state == .running
)
}
// Description text: shown in idle state
if !viewModel.check.descriptionText.isEmpty && viewModel.state == .idle {
MarkdownTextView(viewModel.check.descriptionText, fontSize: 12, textColor: NSColor.secondaryLabelColor)
.padding(.vertical, 5)
}
}
.padding(.trailing, 10)
.frame(maxWidth: .infinity, alignment: .leading)
}
// Terminal output: shown during and after fix execution
if !viewModel.outputLines.isEmpty
&& (viewModel.state == .running || viewModel.state == .completed || viewModel.state == .failed) {
StartupOutputView(
lines: viewModel.outputLines,
isRunning: viewModel.state == .running
)
.padding(15).padding(.leading, 72)
.frame(maxWidth: .infinity, alignment: .leading)
}
// Description text: shown in idle state
if !viewModel.check.descriptionText.isEmpty && viewModel.state == .idle {
MarkdownTextView(viewModel.check.descriptionText, fontSize: 12)
.padding(.vertical, 15)
.padding(.horizontal, 20)
.padding(.leading, 64)
.frame(maxWidth: .infinity, alignment: .leading)
}
.padding(15)
.padding(.top, -5)
Divider()
@@ -133,7 +142,7 @@ struct StartupAlertView: View {
OutputLine(text: "==> Linking Binary 'php' to '/opt/homebrew/bin/php'", stream: .stdOut),
OutputLine(text: "Warning: php is keg-only and must be linked with --force", stream: .stdErr),
OutputLine(text: "", stream: .stdOut),
OutputLine(text: "---\nFix did not resolve the issue.", stream: .stdOut)
OutputLine(text: "\nFix did not resolve the issue.", stream: .stdOut)
]
))
}
@@ -155,7 +164,7 @@ struct StartupAlertView: View {
outputLines: [
OutputLine(text: "==> Linking Binary 'php' to '/opt/homebrew/bin/php'", stream: .stdOut),
OutputLine(text: "==> Linking php... linked 25 files", stream: .stdOut),
OutputLine(text: "---\nFix applied successfully! Continuing...", stream: .stdOut)
OutputLine(text: "\nFix applied successfully! Continuing...", stream: .stdOut)
]
))
}

View File

@@ -101,19 +101,19 @@ class StartupAlertViewModel: ObservableObject {
/** Marks the current fix as completed, with success. */
@MainActor private func pass() {
self.state = .completed
self.appendOutput("---\n\("startup.fix.applied".localized)", .stdOut)
self.appendOutput("\n\("startup.fix.applied".localized)", .stdOut)
}
/** Marks the current fix as completed, with failure. */
@MainActor private func fail() {
self.state = .failed
self.appendOutput("---\n\("startup.fix.not_resolved".localized)", .stdErr)
self.appendOutput("\n\("startup.fix.not_resolved".localized)", .stdErr)
}
/** An error occurred. */
@MainActor private func errorAndIdle(_ error: Error) {
self.state = .failed
self.appendOutput("---\nError: \(error.localizedDescription)", .stdErr)
self.appendOutput("\nError: \(error.localizedDescription)", .stdErr)
}
// MARK: - Alert Outcomes