1
0
mirror of https://github.com/nicoverbruggen/phpmon.git synced 2026-04-07 03:20:09 +02:00
Files
app/phpmon/Domain/SwiftUI/Startup/StartupAlertView.swift
2026-02-27 14:09:23 +01:00

187 lines
6.9 KiB
Swift

//
// StartupAlertView.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 25/02/2026.
// Copyright © 2026 Nico Verbruggen. All rights reserved.
//
import SwiftUI
struct StartupAlertView: View {
@ObservedObject var viewModel: StartupAlertViewModel
/// Whether the bottom section (description text and/or past output) has content to display.
/// This is used to conditionally show the section and its divider,
/// avoiding empty padded sections and double dividers.
private var hasBottomContent: Bool {
let hasDescription = !viewModel.check.descriptionText.isEmpty && viewModel.state == .idle
let hasOutput = !viewModel.outputLines.isEmpty
&& (viewModel.state == .idle || viewModel.state == .completed)
return hasDescription || hasOutput
}
var body: some View {
VStack(spacing: 0) {
StartupAlertHeaderView(
titleText: viewModel.check.titleText,
subtitleText: viewModel.check.subtitleText
)
if viewModel.state == .running
|| viewModel.state == .failed
|| (viewModel.hasFix && viewModel.state == .idle) {
Divider()
VStack(alignment: .leading, spacing: 12) {
if viewModel.state == .running || viewModel.state == .failed {
StartupOutputView(
lines: viewModel.outputLines,
isRunning: viewModel.state == .running
)
} else {
StartupFixCommandView(
command: viewModel.check.fixDescription ?? ""
)
}
}
.padding(15)
.frame(maxWidth: .infinity, alignment: .leading)
}
if hasBottomContent {
Divider()
VStack(alignment: .leading, spacing: 12) {
if !viewModel.check.descriptionText.isEmpty,
viewModel.state == .idle {
MarkdownTextView(viewModel.check.descriptionText, fontSize: 12)
}
if !viewModel.outputLines.isEmpty,
viewModel.state == .idle || viewModel.state == .completed {
StartupOutputView(
lines: viewModel.outputLines,
isRunning: false
)
}
}
.padding(15)
.frame(maxWidth: .infinity, alignment: .leading)
}
Divider()
StartupAlertButtonRow(
state: viewModel.state,
hasFix: viewModel.hasFix,
onQuit: { viewModel.quit() },
onRetry: { viewModel.retryAllChecks() },
onFix: { viewModel.runFix() }
)
}
.frame(width: 500)
}
}
// MARK: - Previews
#Preview("No Fix Available") {
return StartupAlertView(
viewModel: StartupAlertViewModel(check: EnvironmentCheck(
command: { _ in return true },
fix: nil,
name: "preview_php_binary",
titleText: "startup.errors.php_binary.title".localized,
subtitleText: "startup.errors.php_binary.subtitle".localized,
descriptionText: "startup.errors.php_binary.desc".localized(
App.shared.container.paths.php
)
))
)
}
#Preview("Fix Available") {
return StartupAlertView(
viewModel: StartupAlertViewModel(check: EnvironmentCheck(
command: { _ in return true },
fix: { _, output in output("Running brew link php...", .stdOut) },
fixDescription: "brew link php",
name: "preview_php_binary",
titleText: "startup.errors.php_binary.title".localized,
subtitleText: "startup.errors.php_binary.subtitle".localized,
descriptionText: "startup.errors.php_binary.desc".localized(
App.shared.container.paths.php
)
))
)
}
#Preview("Fix Running") {
StartupAlertView(viewModel: StartupAlertViewModel(
check: EnvironmentCheck(
command: { _ in return true },
fix: { _, output in output("Running...", .stdOut) },
fixDescription: "brew link php",
name: "preview_php_binary_running",
titleText: "startup.errors.php_binary.title".localized,
subtitleText: "startup.errors.php_binary.subtitle".localized,
descriptionText: "startup.errors.php_binary.desc".localized(
App.shared.container.paths.php
)
),
state: .running,
outputLines: [
OutputLine(text: "==> Linking Binary 'php' to '/opt/homebrew/bin/php'", stream: .stdOut),
OutputLine(text: "==> Linking php... linked 25 files", stream: .stdOut)
]
))
}
#Preview("Fix Failed") {
StartupAlertView(viewModel: StartupAlertViewModel(
check: EnvironmentCheck(
command: { _ in return true },
fix: { _, output in output("Running brew link php...", .stdOut) },
fixDescription: "brew link php",
name: "preview_php_binary_failed",
titleText: "startup.errors.php_binary.title".localized,
subtitleText: "startup.errors.php_binary.subtitle".localized,
descriptionText: "startup.errors.php_binary.desc".localized(
App.shared.container.paths.php
)
),
state: .failed,
outputLines: [
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)
]
))
}
#Preview("Fix Completed") {
StartupAlertView(viewModel: StartupAlertViewModel(
check: EnvironmentCheck(
command: { _ in return true },
fix: { _, output in output("Running brew link php...", .stdOut) },
fixDescription: "brew link php",
name: "preview_php_binary_completed",
titleText: "startup.errors.php_binary.title".localized,
subtitleText: "startup.errors.php_binary.subtitle".localized,
descriptionText: "startup.errors.php_binary.desc".localized(
App.shared.container.paths.php
)
),
state: .completed,
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)
]
))
}