smlflg's picture
Initial public upload from Projekte/PromtGarageMac
4977db2 verified
Raw History Blame Contribute Delete
5.89 kB
import SwiftUI
struct RootView: View {
@Bindable var model: AppModel
var body: some View {
ZStack {
Group {
switch model.launchState {
case .checking, .needsDatabase, .validating, .invalid:
FirstLaunchView(model: model)
case .ready, .readOnly:
AppShellView(model: model)
case .failed:
FirstLaunchView(model: model)
}
}
if let message = model.successMessage {
VStack {
Spacer()
SuccessToast(message: message)
.padding(.bottom, 42)
}
.transition(.move(edge: .bottom).combined(with: .opacity))
.allowsHitTesting(false)
}
}
.tint(AppUI.accent)
.animation(.snappy(duration: 0.25), value: model.successMessage)
.sheet(item: $model.editor) { presentation in
PromptEditorSheet(model: model, presentation: presentation)
}
.alert(
"Prompt löschen?",
isPresented: Binding(
get: { model.promptPendingDeletion != nil },
set: { if !$0 { model.promptPendingDeletion = nil } }
),
presenting: model.promptPendingDeletion
) { _ in
Button("Abbrechen", role: .cancel) {
model.promptPendingDeletion = nil
}
Button("Endgültig löschen", role: .destructive) {
Task { await model.confirmDelete() }
}
} message: { prompt in
Text("„\(prompt.title)“ wird aus der lokalen Datenbank entfernt. Das kann nicht rückgängig gemacht werden.")
}
}
}
private struct AppShellView: View {
@Bindable var model: AppModel
var body: some View {
VStack(spacing: 0) {
if let error = model.errorPresentation {
ErrorBanner(
error: error,
dismiss: model.dismissError,
copyDiagnostic: model.copyErrorDiagnostic
)
}
NavigationSplitView {
SidebarView(model: model)
.navigationSplitViewColumnWidth(min: 185, ideal: 215, max: 250)
} content: {
switch model.destination {
case .all, .favorites, .mostUsed, .byDay, .byTopic:
LibraryView(model: model)
case .importCenter:
ImportCenterView(model: model)
case .statistics:
StatisticsView(model: model)
}
} detail: {
switch model.destination {
case .all, .favorites, .mostUsed, .byDay, .byTopic:
PromptDetailView(model: model)
case .importCenter:
ImportPreviewDetailView(model: model)
case .statistics:
StatisticsDetailView(model: model)
}
}
.navigationSplitViewStyle(.balanced)
HStack {
LocalStatusLabel(databaseName: model.databaseDisplayName)
Spacer()
if model.isReadOnly {
StatusChip(title: "Nur Lesen", systemImage: "lock.fill", tint: .red)
}
Text(model.footerStatus)
.font(.caption)
.foregroundStyle(.tertiary)
}
.padding(.horizontal, 12)
.frame(height: 28)
.background(.bar)
.overlay(alignment: .top) { Divider() }
}
}
}
private struct SidebarView: View {
@Bindable var model: AppModel
var body: some View {
List(selection: Binding<SidebarDestination?>(
get: { model.destination },
set: { destination in
if let destination { model.destination = destination }
}
)) {
Section("Bibliothek") {
sidebarItem(.all, title: "Alle Prompts", icon: "tray.full")
sidebarItem(.favorites, title: "Favoriten", icon: "star")
sidebarItem(.mostUsed, title: "Meistgenutzt", icon: "arrow.up.forward.circle")
sidebarItem(.byDay, title: "Nach Tagen", icon: "calendar")
sidebarItem(.byTopic, title: "Nach Topics", icon: "tag")
}
Section("Werkzeuge") {
sidebarItem(.importCenter, title: "Import", icon: "square.and.arrow.down")
sidebarItem(.statistics, title: "Statistik", icon: "chart.bar.xaxis")
}
}
.listStyle(.sidebar)
.navigationTitle("PromptGarage")
.safeAreaInset(edge: .bottom) {
VStack(alignment: .leading, spacing: 8) {
Divider()
HStack(spacing: 9) {
Image(systemName: "externaldrive.fill")
.foregroundStyle(AppUI.accent)
VStack(alignment: .leading, spacing: 1) {
Text("Diese Garage")
.font(.caption.weight(.semibold))
Text(model.databaseDisplayName ?? "Lokale Datenbank")
.font(.caption2)
.foregroundStyle(.secondary)
.lineLimit(1)
}
}
.padding(.horizontal, 10)
.padding(.bottom, 8)
}
.background(.bar)
}
}
private func sidebarItem(
_ destination: SidebarDestination,
title: String,
icon: String
) -> some View {
Label(title, systemImage: icon)
.tag(destination)
}
}