PromtGarageMac / Sources /PromptGarageApp /Views /FirstLaunchView.swift
smlflg's picture
Initial public upload from Projekte/PromtGarageMac
4977db2 verified
Raw History Blame Contribute Delete
9.58 kB
import SwiftUI
struct FirstLaunchView: View {
@Bindable var model: AppModel
private var isBusy: Bool {
if case .validating = model.launchState { return true }
return false
}
var body: some View {
ZStack {
LinearGradient(
colors: [
AppUI.accent.opacity(0.10),
Color.clear,
Color(nsColor: .windowBackgroundColor),
],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
.ignoresSafeArea()
VStack(spacing: 28) {
VStack(spacing: 14) {
Image(systemName: "archivebox.fill")
.font(.system(size: 44, weight: .medium))
.foregroundStyle(AppUI.accent)
.frame(width: 84, height: 84)
.background(.regularMaterial, in: RoundedRectangle(cornerRadius: 22))
.overlay {
RoundedRectangle(cornerRadius: 22)
.stroke(AppUI.accent.opacity(0.22))
}
.shadow(color: AppUI.accent.opacity(0.16), radius: 24, y: 10)
Text("Deine Garage auf diesem Mac")
.font(.largeTitle.weight(.semibold))
Text(model.requiresManualDatabaseRecovery
? "Die vorhandene App-Datenbank braucht eine manuelle Wiederherstellung. Sie wird niemals automatisch ersetzt."
: "Übernimm einmalig eine bestehende PromptGarage-Datenbank oder starte mit einer leeren, lokalen Sammlung.")
.font(.body)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
.frame(maxWidth: 560)
}
VStack(alignment: .leading, spacing: 18) {
Label(
model.requiresManualDatabaseRecovery
? "Manuelle Wiederherstellung erforderlich"
: "Vorhandene Sammlung übernehmen",
systemImage: model.requiresManualDatabaseRecovery
? "externaldrive.badge.exclamationmark"
: "cylinder.split.1x2"
)
.font(.headline)
HStack(spacing: 12) {
Image(systemName: model.requiresManualDatabaseRecovery
? "lock.fill"
: (model.selectedBootstrapURL == nil ? "doc.badge.plus" : "cylinder.fill"))
.font(.title3)
.foregroundStyle(
model.requiresManualDatabaseRecovery || model.selectedBootstrapURL == nil
? .secondary
: AppUI.accent
)
VStack(alignment: .leading, spacing: 3) {
Text(model.requiresManualDatabaseRecovery
? "Vorhandene App-Datenbank gesperrt"
: (model.selectedBootstrapURL?.lastPathComponent ?? "Noch keine prompts.db ausgewählt"))
.font(.callout.weight(.medium))
.lineLimit(1)
Text(model.requiresManualDatabaseRecovery
? model.applicationDatabasePath
: (model.selectedBootstrapURL?.deletingLastPathComponent().path(percentEncoded: false) ?? "Das Original bleibt unverändert."))
.font(.caption)
.foregroundStyle(.secondary)
.lineLimit(1)
}
Spacer()
Button("Auswählen …") {
model.chooseBootstrapDatabase()
}
.disabled(isBusy || !model.canChooseBootstrapDatabase)
}
.padding(14)
.background(
Color(nsColor: .controlBackgroundColor),
in: RoundedRectangle(cornerRadius: 11)
)
.overlay {
RoundedRectangle(cornerRadius: 11)
.stroke(Color(nsColor: .separatorColor).opacity(0.6))
}
LabeledContent("Ziel") {
Text(model.applicationDatabasePath)
.font(.caption.monospaced())
.foregroundStyle(.secondary)
.lineLimit(1)
.truncationMode(.middle)
}
if model.requiresManualDatabaseRecovery {
VStack(alignment: .leading, spacing: 8) {
Label(
model.bootstrapFailureMessage
?? "Die vorhandene App-Datenbank kann nicht geöffnet werden.",
systemImage: "xmark.octagon.fill"
)
.foregroundStyle(.red)
Text("Automatischer Ersatz ist gesperrt. Beende PromptGarage und verschiebe oder benenne die vorhandene Datei im Finder oder Terminal manuell um:")
.foregroundStyle(.secondary)
Text(model.applicationDatabasePath)
.font(.caption.monospaced())
.textSelection(.enabled)
Text("Starte PromptGarage anschließend erneut. Erst wenn am Ziel keine Datenbank mehr liegt, kannst du eine Quelle übernehmen oder leer starten.")
.foregroundStyle(.secondary)
}
.font(.callout)
.textSelection(.enabled)
} else if case let .invalid(message) = model.launchState {
VStack(alignment: .leading, spacing: 5) {
Label(message, systemImage: "xmark.octagon.fill")
.foregroundStyle(.red)
Text("Die gewählte Quelle wurde nicht kopiert. Am App-Ziel liegt weiterhin keine Datenbank; du kannst eine andere Quelle wählen oder leer starten.")
.foregroundStyle(.secondary)
}
.font(.callout)
.textSelection(.enabled)
} else if case let .failed(message) = model.launchState {
VStack(alignment: .leading, spacing: 5) {
Label(message, systemImage: "exclamationmark.triangle.fill")
.foregroundStyle(.red)
.textSelection(.enabled)
if !model.isBootstrapAvailable {
Text("Datenbank auswählen, übernehmen und Leerstart sind deaktiviert, bis der App-Speicher wieder verfügbar ist.")
.foregroundStyle(.secondary)
}
}
.font(.callout)
}
HStack {
if model.requiresManualDatabaseRecovery {
StatusChip(
title: "Auswahl, Übernahme und Leerstart gesperrt",
systemImage: "lock.fill",
tint: .secondary
)
} else {
Button("Leer starten") {
Task { await model.startWithEmptyDatabase() }
}
.disabled(isBusy || !model.canCreateEmptyDatabase)
}
Spacer()
Button {
Task { await model.importSelectedBootstrapDatabase() }
} label: {
if isBusy {
HStack(spacing: 8) {
ProgressView().controlSize(.small)
Text("Prüfen und übernehmen …")
}
} else {
Text("Datenbank prüfen und übernehmen")
}
}
.buttonStyle(.borderedProminent)
.disabled(
!model.canInstallSelectedBootstrapDatabase || isBusy
)
}
}
.padding(24)
.frame(maxWidth: 680)
.background(.regularMaterial, in: RoundedRectangle(cornerRadius: 18))
.overlay {
RoundedRectangle(cornerRadius: 18)
.stroke(Color(nsColor: .separatorColor).opacity(0.55))
}
LocalStatusLabel(databaseName: nil)
}
.padding(40)
}
}
}