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

View File

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