144 lines
5.4 KiB
Swift
144 lines
5.4 KiB
Swift
import LumaCore
|
|
import SwiftUI
|
|
|
|
struct CustomInstrumentMetadataMenu: View {
|
|
let defID: UUID
|
|
let engine: Engine
|
|
@Binding var selection: SidebarItemID?
|
|
|
|
@State private var isShowingRename = false
|
|
@State private var isShowingCompatibility = false
|
|
@State private var isShowingFeaturesEditor = false
|
|
@State private var isShowingWidgetsEditor = false
|
|
@State private var isShowingDeleteConfirm = false
|
|
@State private var renameEntrypointPrompt = RenamePromptState()
|
|
@State private var errorMessage: String?
|
|
|
|
private var def: CustomInstrumentDef? {
|
|
engine.customInstruments.def(withId: defID)
|
|
}
|
|
|
|
var body: some View {
|
|
Group {
|
|
if def != nil {
|
|
Menu {
|
|
Button {
|
|
isShowingRename = true
|
|
} label: {
|
|
Label("Rename & Icon\u{2026}", systemImage: "pencil")
|
|
}
|
|
Button {
|
|
isShowingCompatibility = true
|
|
} label: {
|
|
Label("Compatibility\u{2026}", systemImage: "checkmark.shield")
|
|
}
|
|
Button {
|
|
isShowingFeaturesEditor = true
|
|
} label: {
|
|
Label("Features\u{2026}", systemImage: "switch.2")
|
|
}
|
|
Button {
|
|
isShowingWidgetsEditor = true
|
|
} label: {
|
|
Label("Widgets\u{2026}", systemImage: "chart.xyaxis.line")
|
|
}
|
|
if let entrypoint = def?.entrypoint {
|
|
Divider()
|
|
Button {
|
|
renameEntrypointPrompt.present(current: entrypoint)
|
|
} label: {
|
|
Label("Rename Entrypoint File\u{2026}", systemImage: "pencil")
|
|
}
|
|
}
|
|
Divider()
|
|
Button(role: .destructive) {
|
|
isShowingDeleteConfirm = true
|
|
} label: {
|
|
Label("Delete Custom Instrument", systemImage: "trash")
|
|
}
|
|
} label: {
|
|
Image(systemName: "ellipsis.circle")
|
|
}
|
|
.accessibilityLabel("Custom instrument actions")
|
|
}
|
|
}
|
|
.popover(isPresented: $isShowingRename, arrowEdge: .trailing) {
|
|
if let def {
|
|
CustomInstrumentRenamePopover(def: def, engine: engine)
|
|
}
|
|
}
|
|
.popover(isPresented: $isShowingCompatibility, arrowEdge: .trailing) {
|
|
if let def {
|
|
CustomInstrumentCompatibilityPopover(def: def, engine: engine)
|
|
}
|
|
}
|
|
.popover(isPresented: $isShowingFeaturesEditor, arrowEdge: .trailing) {
|
|
if let def {
|
|
CustomInstrumentFeaturesPopover(def: def, engine: engine)
|
|
}
|
|
}
|
|
.popover(isPresented: $isShowingWidgetsEditor, arrowEdge: .trailing) {
|
|
if let def {
|
|
CustomInstrumentWidgetsPopover(def: def, engine: engine)
|
|
}
|
|
}
|
|
.alert("Rename Entrypoint File", isPresented: $renameEntrypointPrompt.isPresented) {
|
|
TextField("path/to/file.ts", text: $renameEntrypointPrompt.draft)
|
|
.disableAutocorrection(true)
|
|
Button("Rename") { commitRenameEntrypoint() }
|
|
.disabled(!renameEntrypointPrompt.canCommit(originalPath: def?.entrypoint ?? ""))
|
|
Button("Cancel", role: .cancel) {}
|
|
} message: {
|
|
Text("Renames the entrypoint file and updates the entrypoint automatically.")
|
|
}
|
|
.confirmationDialog(
|
|
"Delete \"\(def?.name ?? "")\"?",
|
|
isPresented: $isShowingDeleteConfirm,
|
|
titleVisibility: .visible
|
|
) {
|
|
Button("Delete", role: .destructive) { deleteInstrument() }
|
|
Button("Cancel", role: .cancel) {}
|
|
} message: {
|
|
Text("This removes the custom instrument from the project and from any sessions where it is loaded.")
|
|
}
|
|
.alert("Custom instrument error", isPresented: errorBinding, presenting: errorMessage) { _ in
|
|
Button("OK") { errorMessage = nil }
|
|
} message: { message in
|
|
Text(message)
|
|
}
|
|
}
|
|
|
|
private func commitRenameEntrypoint() {
|
|
let trimmed = renameEntrypointPrompt.draft.trimmingCharacters(in: .whitespaces)
|
|
guard !trimmed.isEmpty, let original = def?.entrypoint, trimmed != original else { return }
|
|
let id = defID
|
|
Task { @MainActor in
|
|
do {
|
|
let renamedPath = try engine.renameCustomInstrumentFile(defID: id, from: original, to: trimmed)
|
|
if selection == .customInstrumentFile(id, original) {
|
|
selection = .customInstrumentFile(id, renamedPath)
|
|
}
|
|
} catch {
|
|
errorMessage = error.localizedDescription
|
|
}
|
|
}
|
|
}
|
|
|
|
private func deleteInstrument() {
|
|
let id = defID
|
|
Task { @MainActor in
|
|
await engine.deleteCustomInstrument(id)
|
|
if selection?.belongsTo(defID: id) ?? false {
|
|
selection = .notebook
|
|
}
|
|
}
|
|
}
|
|
|
|
private var errorBinding: Binding<Bool> {
|
|
Binding(
|
|
get: { errorMessage != nil },
|
|
set: { if !$0 { errorMessage = nil } }
|
|
)
|
|
}
|
|
}
|