PadXcode-Daemon/UI/DaemonLogWindowView.swift

63 lines
3.1 KiB
Swift
Raw Normal View History

2026-04-12 00:42:51 -07:00
import SwiftUI
struct DaemonLogWindowView: View {
@Bindable var controller: DaemonController
@State private var filterLevel: DaemonLogLine.Level? = nil
@State private var searchText = ""
@State private var autoscroll = true
private var filtered: [DaemonLogLine] {
var lines = controller.logLines
if let lv = filterLevel { lines = lines.filter { $0.level == lv } }
if !searchText.isEmpty { lines = lines.filter { $0.message.localizedCaseInsensitiveContains(searchText) } }
return lines
}
var body: some View {
VStack(spacing:0) {
HStack(spacing:10) {
HStack(spacing:4) {
levelBtn("All", nil)
levelBtn("Info", .info)
levelBtn("OK", .success)
levelBtn("Warn", .warning)
levelBtn("Err", .error)
}
Spacer()
HStack(spacing:4) {
Image(systemName:"magnifyingglass").foregroundStyle(.secondary).font(.caption)
TextField("Search…", text:$searchText).textFieldStyle(.roundedBorder).frame(width:160)
}
Toggle(isOn:$autoscroll) { Image(systemName:"arrow.down.to.line") }.toggleStyle(.button).help("Auto-scroll")
Button { controller.clearLog() } label: { Image(systemName:"trash") }.help("Clear")
}
.padding(.horizontal,12).padding(.vertical,8).background(Color(NSColor.windowBackgroundColor))
Divider()
ScrollViewReader { proxy in
ScrollView {
LazyVStack(alignment:.leading,spacing:0) {
ForEach(filtered) { line in
HStack(alignment:.top,spacing:8) {
Text(line.formattedTime).font(.system(.caption2,design:.monospaced)).foregroundStyle(.tertiary).frame(width:60,alignment:.leading)
Text(line.level.prefix).font(.caption).frame(width:16)
Text(line.message).font(.system(.caption,design:.monospaced)).textSelection(.enabled).frame(maxWidth:.infinity,alignment:.leading)
}
.padding(.vertical,1).padding(.horizontal,4)
.background(line.level == .error ? Color.red.opacity(0.06) : line.level == .warning ? Color.orange.opacity(0.06) : .clear)
.id(line.id)
}
}.padding(.horizontal,8).padding(.vertical,4)
}
.onChange(of:filtered.count) { _,_ in if autoscroll, let last = filtered.last { proxy.scrollTo(last.id,anchor:.bottom) } }
}
}
.background(Color(NSColor.textBackgroundColor))
}
private func levelBtn(_ label: String, _ level: DaemonLogLine.Level?) -> some View {
Button(label) { filterLevel = level }
.buttonStyle(.bordered).controlSize(.mini)
.tint(filterLevel == level ? .accentColor : .secondary)
}
}