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.
94 lines
3.6 KiB
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))
|
|
}
|
|
}
|