Give instruments that have hooks a disclosure chevron and persist the per-instrument collapsed state in the session UI state. Toggle the module and thread groups on a full header-row click. Pad the module view so its tabs clear the top-left edge.
126 lines
3.7 KiB
Swift
126 lines
3.7 KiB
Swift
import SwiftUI
|
|
import LumaCore
|
|
|
|
struct TracerUI: InstrumentUI {
|
|
func makeConfigEditor(
|
|
configJSON: Binding<Data>,
|
|
engine: Engine,
|
|
selection: Binding<SidebarItemID?>
|
|
) -> AnyView {
|
|
let configBinding = Binding<TracerConfig>(
|
|
get: {
|
|
(try? TracerConfig.decode(from: configJSON.wrappedValue)) ?? TracerConfig()
|
|
},
|
|
set: { newValue in
|
|
configJSON.wrappedValue = newValue.encode()
|
|
}
|
|
)
|
|
|
|
return AnyView(
|
|
TracerConfigView(
|
|
config: configBinding,
|
|
engine: engine,
|
|
selection: selection
|
|
)
|
|
)
|
|
}
|
|
|
|
func renderEvent(
|
|
_ event: RuntimeEvent,
|
|
engine: Engine,
|
|
selection: Binding<SidebarItemID?>
|
|
) -> AnyView {
|
|
guard case .jsValue(let v) = event.payload,
|
|
let ev = Engine.parseTracerEvent(from: v)
|
|
else {
|
|
return AnyView(
|
|
Text(String(describing: event.payload))
|
|
.font(.system(.footnote, design: .monospaced))
|
|
.textSelection(.enabled)
|
|
)
|
|
}
|
|
|
|
let messageView: AnyView = {
|
|
if case .array(_, let elems) = ev.message,
|
|
elems.count == 1,
|
|
case .string(let messageText) = elems[0]
|
|
{
|
|
return AnyView(
|
|
Text(messageText)
|
|
.font(.system(.footnote, design: .monospaced))
|
|
.textSelection(.enabled)
|
|
)
|
|
} else {
|
|
return AnyView(
|
|
JSInspectValueView(
|
|
value: ev.message,
|
|
sessionID: event.sessionID ?? UUID(),
|
|
engine: engine,
|
|
selection: selection
|
|
)
|
|
.font(.system(.footnote, design: .monospaced))
|
|
)
|
|
}
|
|
}()
|
|
|
|
return AnyView(
|
|
TracerEventRowView(
|
|
messageView: messageView,
|
|
sessionID: event.sessionID,
|
|
backtrace: ev.backtrace,
|
|
engine: engine,
|
|
selection: selection
|
|
)
|
|
)
|
|
}
|
|
|
|
func makeEventContextMenuItems(
|
|
_ event: RuntimeEvent,
|
|
engine: Engine,
|
|
selection: Binding<SidebarItemID?>
|
|
) -> [InstrumentEventMenuItem] {
|
|
guard case .instrument(let instrumentID, _) = event.source,
|
|
case .jsValue(let v) = event.payload,
|
|
let ev = Engine.parseTracerEvent(from: v),
|
|
let sessionID = event.sessionID
|
|
else {
|
|
return []
|
|
}
|
|
|
|
return [
|
|
InstrumentEventMenuItem(
|
|
title: "Go to Hook",
|
|
systemImage: "arrow.turn.down.right",
|
|
role: .normal
|
|
) {
|
|
selection.wrappedValue = .instrumentComponent(
|
|
sessionID,
|
|
instrumentID,
|
|
ev.id
|
|
)
|
|
},
|
|
]
|
|
}
|
|
|
|
func sidebarChildren(
|
|
sessionID: UUID,
|
|
instance: LumaCore.InstrumentInstance,
|
|
engine: Engine,
|
|
selection: Binding<SidebarItemID?>
|
|
) -> AnyView {
|
|
AnyView(
|
|
TracerSidebarChildren(
|
|
sessionID: sessionID,
|
|
instance: instance,
|
|
engine: engine,
|
|
selection: selection
|
|
)
|
|
)
|
|
}
|
|
|
|
func hasSidebarChildren(instance: LumaCore.InstrumentInstance) -> Bool {
|
|
guard let config = try? TracerConfig.decode(from: instance.configJSON) else { return false }
|
|
return !config.hooksByMostRecentlyEdited().isEmpty
|
|
}
|
|
}
|