- Adaptive iPhone/iPad layout with NavigationSplitView sidebar - Auto-detect SSL-pinned domains, fall back to passthrough - Certificate install via local HTTP server (Safari profile flow) - App Group-backed CA, per-domain leaf cert LRU cache - DB-backed config repository, Darwin notification throttling - Rules engine, breakpoint rules, pinned domain tracking - os.Logger instrumentation across tunnel/proxy/mitm/capture/cert/rules/db/ipc/ui - Fix dyld framework embed, race conditions, thread safety
137 lines
4.4 KiB
Swift
137 lines
4.4 KiB
Swift
import SwiftUI
|
|
|
|
struct JSONTreeView: View {
|
|
let data: Data
|
|
|
|
@State private var root: JSONNode?
|
|
|
|
var body: some View {
|
|
Group {
|
|
if let root {
|
|
JSONNodeExpandableView(
|
|
node: root,
|
|
collapseByDefault: false,
|
|
childCollapseThreshold: 50
|
|
)
|
|
} else {
|
|
Text("Invalid JSON")
|
|
.font(.system(.caption, design: .monospaced))
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
}
|
|
.task {
|
|
root = parseJSON(data)
|
|
}
|
|
}
|
|
|
|
private func parseJSON(_ data: Data) -> JSONNode? {
|
|
guard let obj = try? JSONSerialization.jsonObject(with: data) else { return nil }
|
|
return buildNode(key: nil, value: obj)
|
|
}
|
|
|
|
private func buildNode(key: String?, value: Any) -> JSONNode {
|
|
if let dict = value as? [String: Any] {
|
|
let children = dict.sorted(by: { $0.key < $1.key }).map { buildNode(key: $0.key, value: $0.value) }
|
|
return JSONNode(key: key, kind: .object, displayValue: "{\(dict.count)}", children: children)
|
|
} else if let arr = value as? [Any] {
|
|
let children = arr.enumerated().map { buildNode(key: "[\($0.offset)]", value: $0.element) }
|
|
return JSONNode(key: key, kind: .array, displayValue: "[\(arr.count)]", children: children)
|
|
} else if let str = value as? String {
|
|
return JSONNode(key: key, kind: .string, displayValue: "\"\(str)\"", children: [])
|
|
} else if let num = value as? NSNumber {
|
|
if CFBooleanGetTypeID() == CFGetTypeID(num) {
|
|
let boolVal = num.boolValue
|
|
return JSONNode(key: key, kind: .bool, displayValue: boolVal ? "true" : "false", children: [])
|
|
}
|
|
return JSONNode(key: key, kind: .number, displayValue: "\(num)", children: [])
|
|
} else if value is NSNull {
|
|
return JSONNode(key: key, kind: .null, displayValue: "null", children: [])
|
|
} else {
|
|
return JSONNode(key: key, kind: .string, displayValue: "\(value)", children: [])
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Model
|
|
|
|
private struct JSONNode: Identifiable {
|
|
let id = UUID()
|
|
let key: String?
|
|
let kind: JSONValueKind
|
|
let displayValue: String
|
|
let children: [JSONNode]
|
|
|
|
var childCount: Int {
|
|
children.count + children.reduce(0) { $0 + $1.childCount }
|
|
}
|
|
}
|
|
|
|
private enum JSONValueKind {
|
|
case object, array, string, number, bool, null
|
|
}
|
|
|
|
// MARK: - Expandable wrapper (allows per-node expand state)
|
|
|
|
private struct JSONNodeExpandableView: View {
|
|
let node: JSONNode
|
|
let collapseByDefault: Bool
|
|
let childCollapseThreshold: Int
|
|
|
|
@State private var isExpanded: Bool
|
|
|
|
init(node: JSONNode, collapseByDefault: Bool, childCollapseThreshold: Int) {
|
|
self.node = node
|
|
self.collapseByDefault = collapseByDefault
|
|
self.childCollapseThreshold = childCollapseThreshold
|
|
_isExpanded = State(initialValue: !collapseByDefault)
|
|
}
|
|
|
|
var body: some View {
|
|
if node.children.isEmpty {
|
|
leafRow
|
|
} else {
|
|
DisclosureGroup(isExpanded: $isExpanded) {
|
|
ForEach(node.children) { child in
|
|
JSONNodeExpandableView(
|
|
node: child,
|
|
collapseByDefault: child.childCount > childCollapseThreshold,
|
|
childCollapseThreshold: childCollapseThreshold
|
|
)
|
|
}
|
|
} label: {
|
|
labelRow(valueColor: .secondary)
|
|
}
|
|
}
|
|
}
|
|
|
|
private var leafRow: some View {
|
|
labelRow(valueColor: valueColor)
|
|
}
|
|
|
|
private func labelRow(valueColor: Color) -> some View {
|
|
HStack(alignment: .firstTextBaseline, spacing: 4) {
|
|
if let key = node.key {
|
|
Text(key + ":")
|
|
.font(.system(.caption, design: .monospaced))
|
|
.bold()
|
|
.foregroundStyle(.primary)
|
|
}
|
|
|
|
Text(node.displayValue)
|
|
.font(.system(.caption, design: .monospaced))
|
|
.foregroundStyle(valueColor)
|
|
.textSelection(.enabled)
|
|
}
|
|
}
|
|
|
|
private var valueColor: Color {
|
|
switch node.kind {
|
|
case .string: return .green
|
|
case .number: return .blue
|
|
case .bool: return .orange
|
|
case .null: return .gray
|
|
case .object, .array: return .secondary
|
|
}
|
|
}
|
|
}
|