Polish Log Viewer for macOS

Convert detail and filter sheets to shared helpers, add inline Filter /
Export / Clear buttons next to the search bar (toolbar items weren't
surfacing in the settings detail pane), inline the Reset Filters button
at the bottom of the filter sheet, use a 'Close' text button, and trim
the macOS Share Sheet to just the scrollable log with a Copy button.
This commit is contained in:
Arkadiusz Fal
2026-04-21 03:31:11 +02:00
parent 22b9cb7135
commit b275dbd7c0
2 changed files with 71 additions and 20 deletions

View File

@@ -22,16 +22,14 @@ struct ShareSheet: View {
let items: [Any]
var body: some View {
VStack {
Text(String(localized: "settings.advanced.logs.export.instructions"))
.padding()
VStack(spacing: 12) {
if let text = items.first as? String {
ScrollView {
Text(text)
.font(.caption)
.fontDesign(.monospaced)
.textSelection(.enabled)
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
}
.frame(maxHeight: 400)
@@ -43,9 +41,10 @@ struct ShareSheet: View {
NSPasteboard.general.setString(text, forType: .string)
}
}
.padding()
.keyboardShortcut(.defaultAction)
}
.frame(minWidth: 400, minHeight: 300)
.padding()
.frame(minWidth: 420, minHeight: 320)
}
}
#endif

View File

@@ -19,8 +19,38 @@ struct LogViewerView: View {
var body: some View {
VStack(spacing: 0) {
// Search bar
#if os(macOS)
HStack(spacing: 8) {
searchBar
Button {
showingFilters = true
} label: {
Label(String(localized: "settings.advanced.logs.filter"), systemImage: "line.3.horizontal.decrease.circle")
.labelStyle(.iconOnly)
}
.help(String(localized: "settings.advanced.logs.filter"))
Button {
showingExportSheet = true
} label: {
Label(String(localized: "settings.advanced.logs.export"), systemImage: "square.and.arrow.up")
.labelStyle(.iconOnly)
}
.help(String(localized: "settings.advanced.logs.export"))
Button(role: .destructive) {
loggingService.clearLogs()
} label: {
Label(String(localized: "settings.advanced.logs.clear"), systemImage: "trash")
.labelStyle(.iconOnly)
}
.help(String(localized: "settings.advanced.logs.clear"))
}
.padding(.trailing)
#else
searchBar
#endif
// Log list
logList
@@ -192,13 +222,18 @@ private struct LogEntryDetailView: View {
var body: some View {
NavigationStack {
List {
Section(String(localized: "settings.advanced.logs.detail.info")) {
LabeledContent(String(localized: "settings.advanced.logs.detail.timestamp")) {
SettingsFormContainer {
SettingsFormSection("settings.advanced.logs.detail.info") {
HStack {
Text(String(localized: "settings.advanced.logs.detail.timestamp"))
Spacer()
Text(entry.timestamp.formatted(date: .abbreviated, time: .standard))
.foregroundStyle(.secondary)
}
LabeledContent(String(localized: "settings.advanced.logs.detail.level")) {
HStack {
Text(String(localized: "settings.advanced.logs.detail.level"))
Spacer()
HStack {
Image(systemName: entry.level.icon)
Text(entry.level.rawValue.capitalized)
@@ -206,27 +241,32 @@ private struct LogEntryDetailView: View {
.foregroundStyle(levelColor)
}
LabeledContent(String(localized: "settings.advanced.logs.detail.category")) {
HStack {
Text(String(localized: "settings.advanced.logs.detail.category"))
Spacer()
HStack {
Image(systemName: entry.category.icon)
Text(entry.category.rawValue)
}
.foregroundStyle(.secondary)
}
}
Section(String(localized: "settings.advanced.logs.detail.message")) {
SettingsFormSection("settings.advanced.logs.detail.message") {
Text(entry.message)
.font(.body)
.frame(maxWidth: .infinity, alignment: .leading)
#if !os(tvOS)
.textSelection(.enabled)
#endif
}
if let details = entry.details {
Section(String(localized: "settings.advanced.logs.detail.details")) {
SettingsFormSection("settings.advanced.logs.detail.details") {
Text(details)
.font(.caption)
.fontDesign(.monospaced)
.frame(maxWidth: .infinity, alignment: .leading)
#if !os(tvOS)
.textSelection(.enabled)
#endif
@@ -249,6 +289,9 @@ private struct LogEntryDetailView: View {
}
}
.presentationDetents([.medium, .large])
#if os(macOS)
.frame(minWidth: 500, minHeight: 400)
#endif
}
private var levelColor: Color {
@@ -269,28 +312,38 @@ private struct LogFiltersSheet: View {
var body: some View {
NavigationStack {
List {
Section(String(localized: "settings.advanced.logs.filter.categories")) {
SettingsFormContainer {
SettingsFormSection("settings.advanced.logs.filter.categories") {
ForEach(LogCategory.allCases, id: \.self) { category in
Toggle(isOn: binding(for: category)) {
Label(category.rawValue, systemImage: category.icon)
}
}
}
#if os(macOS)
.labelStyle(FixedIconWidthLabelStyle())
#endif
Section(String(localized: "settings.advanced.logs.filter.levels")) {
SettingsFormSection("settings.advanced.logs.filter.levels") {
ForEach(LogLevel.allCases, id: \.self) { level in
Toggle(isOn: binding(for: level)) {
Label(level.rawValue.capitalized, systemImage: level.icon)
}
}
}
#if os(macOS)
.labelStyle(FixedIconWidthLabelStyle())
#endif
Section {
HStack {
Button(String(localized: "settings.advanced.logs.filter.reset")) {
loggingService.resetFilters()
}
Spacer()
}
.padding(.horizontal, 16)
.padding(.top, 4)
.padding(.bottom, 16)
}
.navigationTitle(String(localized: "settings.advanced.logs.filter.title"))
#if os(iOS)
@@ -301,8 +354,7 @@ private struct LogFiltersSheet: View {
Button(role: .cancel) {
dismiss()
} label: {
Label(String(localized: "common.close"), systemImage: "xmark")
.labelStyle(.iconOnly)
Text(String(localized: "common.close"))
}
}
}