Download Sources/PromptGarageApp/Views/FirstLaunchView.swift from smlflg/PromtGarageMac: direct link, hf CLI and curl.
- Browser
- Download file 9.58 kB
-
https://huggingface.co/smlflg/PromtGarageMac/resolve/main/Sources/PromptGarageApp/Views/FirstLaunchView.swift
- Command line
-
hf download hf://smlflg/PromtGarageMac/Sources/PromptGarageApp/Views/FirstLaunchView.swift
-
curl -L -o FirstLaunchView.swift https://huggingface.co/smlflg/PromtGarageMac/resolve/main/Sources/PromptGarageApp/Views/FirstLaunchView.swift
9.58 kB
| import SwiftUI | |
| struct FirstLaunchView: View { | |
| 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) | |
| } | |
| } | |
| } | |