You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

116 lines
4.3 KiB

import SwiftUI
import UniformTypeIdentifiers
enum Tab: String, CaseIterable, Identifiable {
case overview = "Übersicht", history = "Verlauf", persons = "Personen", tasks = "Aufgaben"
var id: String { rawValue }
}
/// Zustand der Oberfläche (welcher Bereich, welcher Eintrag wird gerade bearbeitet).
@MainActor
final class UIState: ObservableObject {
@Published var tab: Tab = .overview
@Published var draft: Entry?
@Published var toast: String?
func newEntry(task: String = "", person: String = "", partner: String = "") {
draft = Entry(id: "", date: Day.today, task: task, person: person, partner: partner)
}
func show(_ message: String) {
toast = message
Task { try? await Task.sleep(for: .seconds(3)); if toast == message { toast = nil } }
}
}
@main
struct MawariApp: App {
@StateObject private var store = Store()
@StateObject private var ui = UIState()
@StateObject private var updates = UpdateController()
var body: some Scene {
Window("Mawari", id: "main") {
ContentView()
.environmentObject(store)
.environmentObject(ui)
.frame(minWidth: 960, minHeight: 600)
}
.defaultSize(width: 1280, height: 820)
.commands {
CommandGroup(after: .appInfo) {
Button("Nach Updates suchen …") { updates.checkForUpdates() }
.disabled(!updates.canCheck)
}
CommandGroup(replacing: .newItem) {
Button("Neuer Eintrag") { ui.newEntry() }.keyboardShortcut("n")
}
CommandGroup(replacing: .importExport) {
Button("Export für Numbers …") { Exporter.exportXlsx(store: store, ui: ui) }.keyboardShortcut("e")
Divider()
Button("Sicherung speichern …") { Exporter.saveBackup(store: store, ui: ui) }
Button("Sicherung / HTML-Daten laden …") { Exporter.importBackup(store: store, ui: ui) }
Button("Datenordner im Finder zeigen") { NSWorkspace.shared.activateFileViewerSelecting([store.folder]) }
}
CommandMenu("Ansicht") {
ForEach(Array(Tab.allCases.enumerated()), id: \.element) { i, tab in
Button(tab.rawValue) { ui.tab = tab }.keyboardShortcut(KeyEquivalent(Character("\(i + 1)")))
}
}
}
}
}
struct ContentView: View {
@EnvironmentObject var store: Store
@EnvironmentObject var ui: UIState
var body: some View {
Group {
switch ui.tab {
case .overview: OverviewView()
case .history: HistoryView()
case .persons: PersonsView()
case .tasks: TasksView()
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.background(Color(nsColor: .textBackgroundColor))
.toolbar {
ToolbarItem(placement: .principal) {
Picker("Bereich", selection: $ui.tab) {
ForEach(Tab.allCases) { Text($0.rawValue).tag($0) }
}
.pickerStyle(.segmented)
.frame(width: 380)
}
ToolbarItemGroup(placement: .primaryAction) {
Button { Exporter.exportXlsx(store: store, ui: ui) } label: {
Label("Export für Numbers", systemImage: "tablecells")
}
.help("Aktuellen Stand als Tabelle für Numbers speichern (⌘E)")
Button { ui.newEntry() } label: { Label("Neuer Eintrag", systemImage: "plus") }
.help("Neuer Eintrag (⌘N)")
}
}
.sheet(item: $ui.draft) { draft in
EntrySheet(entry: draft)
}
.overlay(alignment: .bottom) {
if let t = ui.toast ?? store.lastError {
Text(t)
.font(.callout)
.padding(.horizontal, 14).padding(.vertical, 8)
.background(.regularMaterial, in: RoundedRectangle(cornerRadius: 8))
.padding(.bottom, 18)
.transition(.opacity)
}
}
.animation(.easeInOut(duration: 0.2), value: ui.toast)
.tint(Theme.tokiwa)
}
}
extension Entry {
var isNew: Bool { id.isEmpty }
}

Powered by TurnKey Linux.