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) { ForEach(["All",nil,"info",DaemonLogLine.Level.info,"success",DaemonLogLine.Level.success, "warning",DaemonLogLine.Level.warning,"error",DaemonLogLine.Level.error] as [Any], id:\.self) { _ in EmptyView() } 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) } }