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.
Musubi/App/Sources/SceneMenu.swift

94 lines
3.6 KiB

import DesignSystem
import MusubiCore
import SwiftUI
import UniformTypeIdentifiers
/// Toolbar menu: current scene name and scene actions (control only).
struct SceneMenu: View {
let coordinator: Coordinator
@State private var askingName = false
@State private var newName = ""
@State private var exporting = false
@State private var importing = false
@State private var importError: String?
var body: some View {
Menu {
if !coordinator.store.scenes.isEmpty {
Section("Szene laden") {
ForEach(coordinator.store.scenes) { scene in
Button(scene.name) { coordinator.loadScene(scene) }
}
}
}
Button("Speichern") {
if coordinator.currentSceneName == nil { askName() } else { coordinator.saveScene() }
}
.keyboardShortcut("s")
Button("Speichern als …") { askName() }
.keyboardShortcut("s", modifiers: [.command, .shift])
Divider()
Button("Exportieren …") { exporting = true }
.disabled(coordinator.currentSceneName == nil)
Button("Importieren …") { importing = true }
} label: {
HStack(spacing: Theme.Space.unit / 2) {
Text(verbatim: coordinator.currentSceneName ?? String(localized: "Keine Szene"))
if coordinator.isModified {
Text("· geändert").foregroundStyle(Theme.Color.nezumi)
}
}
}
.fixedSize()
.help("Szene")
.alert("Szene speichern", isPresented: $askingName) {
TextField("Name", text: $newName)
Button("Speichern") {
let name = newName.trimmingCharacters(in: .whitespaces)
if !name.isEmpty { coordinator.saveScene(as: name) }
}
Button("Abbrechen", role: .cancel) {}
} message: {
Text("Speichert, was Regie und Knoten gerade zeigen und wohin der Ton geht.")
}
.fileExporter(isPresented: $exporting, document: SceneDocument(scene: coordinator.exportableScene()),
contentType: .json, defaultFilename: coordinator.currentSceneName ?? "Szene") { _ in }
.fileImporter(isPresented: $importing, allowedContentTypes: [.json]) { result in
do {
let scene = try coordinator.store.importScene(from: result.get())
coordinator.loadScene(scene)
} catch {
importError = error.localizedDescription
}
}
.alert("Import fehlgeschlagen", isPresented: Binding(get: { importError != nil }, set: { if !$0 { importError = nil } })) {
Button("OK") {}
} message: {
Text("Die Datei ist keine Musubi-Szene.")
}
}
private func askName() {
newName = coordinator.currentSceneName ?? ""
askingName = true
}
}
struct SceneDocument: FileDocument {
static let readableContentTypes: [UTType] = [.json]
var scene: MusubiCore.Scene
init(scene: MusubiCore.Scene) { self.scene = scene }
init(configuration: ReadConfiguration) throws {
guard let data = configuration.file.regularFileContents else { throw CocoaError(.fileReadCorruptFile) }
scene = try JSONDecoder().decode(MusubiCore.Scene.self, from: data)
}
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
return FileWrapper(regularFileWithContents: try encoder.encode(scene))
}
}

Powered by TurnKey Linux.