style: tidy macOS config UI formatting

This commit is contained in:
Peter Steinberger
2026-01-17 17:22:30 +00:00
parent c79ac3fe81
commit f6681be6f4
5 changed files with 62 additions and 67 deletions

View File

@@ -6,11 +6,11 @@ struct ConfigSchemaForm: View {
let path: ConfigPath let path: ConfigPath
var body: some View { var body: some View {
self.renderNode(schema, path: path) self.renderNode(self.schema, path: self.path)
} }
private func renderNode(_ schema: ConfigSchemaNode, path: ConfigPath) -> AnyView { private func renderNode(_ schema: ConfigSchemaNode, path: ConfigPath) -> AnyView {
let storedValue = store.configValue(at: path) let storedValue = self.store.configValue(at: path)
let value = storedValue ?? schema.explicitDefault let value = storedValue ?? schema.explicitDefault
let label = hintForPath(path, hints: store.configUiHints)?.label ?? schema.title let label = hintForPath(path, hints: store.configUiHints)?.label ?? schema.title
let help = hintForPath(path, hints: store.configUiHints)?.help ?? schema.description let help = hintForPath(path, hints: store.configUiHints)?.help ?? schema.description
@@ -21,7 +21,7 @@ struct ConfigSchemaForm: View {
if nonNull.count == 1, let only = nonNull.first { if nonNull.count == 1, let only = nonNull.first {
return self.renderNode(only, path: path) return self.renderNode(only, path: path)
} }
let literals = nonNull.compactMap { $0.literalValue } let literals = nonNull.compactMap(\.literalValue)
if !literals.isEmpty, literals.count == nonNull.count { if !literals.isEmpty, literals.count == nonNull.count {
return AnyView( return AnyView(
VStack(alignment: .leading, spacing: 6) { VStack(alignment: .leading, spacing: 6) {
@@ -31,15 +31,20 @@ struct ConfigSchemaForm: View {
.font(.caption) .font(.caption)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} }
Picker("", selection: self.enumBinding(path, options: literals, defaultValue: schema.explicitDefault)) { Picker(
"",
selection: self.enumBinding(
path,
options: literals,
defaultValue: schema.explicitDefault))
{
Text("Select…").tag(-1) Text("Select…").tag(-1)
ForEach(literals.indices, id: \ .self) { index in ForEach(literals.indices, id: \ .self) { index in
Text(String(describing: literals[index])).tag(index) Text(String(describing: literals[index])).tag(index)
} }
} }
.pickerStyle(.menu) .pickerStyle(.menu)
} })
)
} }
} }
@@ -71,8 +76,7 @@ struct ConfigSchemaForm: View {
if schema.allowsAdditionalProperties { if schema.allowsAdditionalProperties {
self.renderAdditionalProperties(schema, path: path, value: value) self.renderAdditionalProperties(schema, path: path, value: value)
} }
} })
)
case "array": case "array":
return AnyView(self.renderArray(schema, path: path, value: value, label: label, help: help)) return AnyView(self.renderArray(schema, path: path, value: value, label: label, help: help))
case "boolean": case "boolean":
@@ -80,8 +84,7 @@ struct ConfigSchemaForm: View {
Toggle(isOn: self.boolBinding(path, defaultValue: schema.explicitDefault as? Bool)) { Toggle(isOn: self.boolBinding(path, defaultValue: schema.explicitDefault as? Bool)) {
if let label { Text(label) } else { Text("Enabled") } if let label { Text(label) } else { Text("Enabled") }
} }
.help(help ?? "") .help(help ?? ""))
)
case "number", "integer": case "number", "integer":
return AnyView(self.renderNumberField(schema, path: path, label: label, help: help)) return AnyView(self.renderNumberField(schema, path: path, label: label, help: help))
case "string": case "string":
@@ -93,8 +96,7 @@ struct ConfigSchemaForm: View {
Text("Unsupported field type.") Text("Unsupported field type.")
.font(.caption) .font(.caption)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} })
)
} }
} }
@@ -155,9 +157,7 @@ struct ConfigSchemaForm: View {
text: self.numberBinding( text: self.numberBinding(
path, path,
isInteger: schema.schemaType == "integer", isInteger: schema.schemaType == "integer",
defaultValue: defaultValue defaultValue: defaultValue))
)
)
.textFieldStyle(.roundedBorder) .textFieldStyle(.roundedBorder)
} }
} }
@@ -189,7 +189,7 @@ struct ConfigSchemaForm: View {
Button("Remove") { Button("Remove") {
var next = items var next = items
next.remove(at: index) next.remove(at: index)
store.updateConfigValue(path: path, value: next) self.store.updateConfigValue(path: path, value: next)
} }
.buttonStyle(.bordered) .buttonStyle(.bordered)
.controlSize(.small) .controlSize(.small)
@@ -202,7 +202,7 @@ struct ConfigSchemaForm: View {
} else { } else {
next.append("") next.append("")
} }
store.updateConfigValue(path: path, value: next) self.store.updateConfigValue(path: path, value: next)
} }
.buttonStyle(.bordered) .buttonStyle(.bordered)
.controlSize(.small) .controlSize(.small)
@@ -238,7 +238,7 @@ struct ConfigSchemaForm: View {
Button("Remove") { Button("Remove") {
var next = dict var next = dict
next.removeValue(forKey: key) next.removeValue(forKey: key)
store.updateConfigValue(path: path, value: next) self.store.updateConfigValue(path: path, value: next)
} }
.buttonStyle(.bordered) .buttonStyle(.bordered)
.controlSize(.small) .controlSize(.small)
@@ -254,7 +254,7 @@ struct ConfigSchemaForm: View {
key = "new-\(index)" key = "new-\(index)"
} }
next[key] = additionalSchema.defaultValue next[key] = additionalSchema.defaultValue
store.updateConfigValue(path: path, value: next) self.store.updateConfigValue(path: path, value: next)
} }
.buttonStyle(.bordered) .buttonStyle(.bordered)
.controlSize(.small) .controlSize(.small)
@@ -270,9 +270,8 @@ struct ConfigSchemaForm: View {
}, },
set: { newValue in set: { newValue in
let trimmed = newValue.trimmingCharacters(in: .whitespacesAndNewlines) let trimmed = newValue.trimmingCharacters(in: .whitespacesAndNewlines)
store.updateConfigValue(path: path, value: trimmed.isEmpty ? nil : trimmed) self.store.updateConfigValue(path: path, value: trimmed.isEmpty ? nil : trimmed)
} })
)
} }
private func boolBinding(_ path: ConfigPath, defaultValue: Bool?) -> Binding<Bool> { private func boolBinding(_ path: ConfigPath, defaultValue: Bool?) -> Binding<Bool> {
@@ -282,16 +281,15 @@ struct ConfigSchemaForm: View {
return defaultValue ?? false return defaultValue ?? false
}, },
set: { newValue in set: { newValue in
store.updateConfigValue(path: path, value: newValue) self.store.updateConfigValue(path: path, value: newValue)
} })
)
} }
private func numberBinding( private func numberBinding(
_ path: ConfigPath, _ path: ConfigPath,
isInteger: Bool, isInteger: Bool,
defaultValue: Double? defaultValue: Double?) -> Binding<String>
) -> Binding<String> { {
Binding( Binding(
get: { get: {
if let value = store.configValue(at: path) { return String(describing: value) } if let value = store.configValue(at: path) { return String(describing: value) }
@@ -301,22 +299,21 @@ struct ConfigSchemaForm: View {
set: { newValue in set: { newValue in
let trimmed = newValue.trimmingCharacters(in: .whitespacesAndNewlines) let trimmed = newValue.trimmingCharacters(in: .whitespacesAndNewlines)
if trimmed.isEmpty { if trimmed.isEmpty {
store.updateConfigValue(path: path, value: nil) self.store.updateConfigValue(path: path, value: nil)
} else if let value = Double(trimmed) { } else if let value = Double(trimmed) {
store.updateConfigValue(path: path, value: isInteger ? Int(value) : value) self.store.updateConfigValue(path: path, value: isInteger ? Int(value) : value)
} }
} })
)
} }
private func enumBinding( private func enumBinding(
_ path: ConfigPath, _ path: ConfigPath,
options: [Any], options: [Any],
defaultValue: Any? defaultValue: Any?) -> Binding<Int>
) -> Binding<Int> { {
Binding( Binding(
get: { get: {
let value = store.configValue(at: path) ?? defaultValue let value = self.store.configValue(at: path) ?? defaultValue
guard let value else { return -1 } guard let value else { return -1 }
return options.firstIndex { option in return options.firstIndex { option in
String(describing: option) == String(describing: value) String(describing: option) == String(describing: value)
@@ -324,12 +321,11 @@ struct ConfigSchemaForm: View {
}, },
set: { index in set: { index in
guard index >= 0, index < options.count else { guard index >= 0, index < options.count else {
store.updateConfigValue(path: path, value: nil) self.store.updateConfigValue(path: path, value: nil)
return return
} }
store.updateConfigValue(path: path, value: options[index]) self.store.updateConfigValue(path: path, value: options[index])
} })
)
} }
private func mapKeyBinding(path: ConfigPath, key: String) -> Binding<String> { private func mapKeyBinding(path: ConfigPath, key: String) -> Binding<String> {
@@ -339,14 +335,13 @@ struct ConfigSchemaForm: View {
let trimmed = newValue.trimmingCharacters(in: .whitespacesAndNewlines) let trimmed = newValue.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else { return } guard !trimmed.isEmpty else { return }
guard trimmed != key else { return } guard trimmed != key else { return }
let current = store.configValue(at: path) as? [String: Any] ?? [:] let current = self.store.configValue(at: path) as? [String: Any] ?? [:]
guard current[trimmed] == nil else { return } guard current[trimmed] == nil else { return }
var next = current var next = current
next[trimmed] = current[key] next[trimmed] = current[key]
next.removeValue(forKey: key) next.removeValue(forKey: key)
store.updateConfigValue(path: path, value: next) self.store.updateConfigValue(path: path, value: next)
} })
)
} }
} }
@@ -355,10 +350,10 @@ struct ChannelConfigForm: View {
let channelId: String let channelId: String
var body: some View { var body: some View {
if store.configSchemaLoading { if self.store.configSchemaLoading {
ProgressView().controlSize(.small) ProgressView().controlSize(.small)
} else if let schema = store.channelConfigSchema(for: channelId) { } else if let schema = store.channelConfigSchema(for: channelId) {
ConfigSchemaForm(store: store, schema: schema, path: [.key("channels"), .key(channelId)]) ConfigSchemaForm(store: self.store, schema: schema, path: [.key("channels"), .key(self.channelId)])
} else { } else {
Text("Schema unavailable for this channel.") Text("Schema unavailable for this channel.")
.font(.caption) .font(.caption)

View File

@@ -434,25 +434,25 @@ extension ChannelsSettings {
private func resolveChannelDetailTitle(_ id: String) -> String { private func resolveChannelDetailTitle(_ id: String) -> String {
switch id { switch id {
case "whatsapp": return "WhatsApp Web" case "whatsapp": "WhatsApp Web"
case "telegram": return "Telegram Bot" case "telegram": "Telegram Bot"
case "discord": return "Discord Bot" case "discord": "Discord Bot"
case "slack": return "Slack Bot" case "slack": "Slack Bot"
case "signal": return "Signal REST" case "signal": "Signal REST"
case "imessage": return "iMessage" case "imessage": "iMessage"
default: return self.resolveChannelTitle(id) default: self.resolveChannelTitle(id)
} }
} }
private func resolveChannelSystemImage(_ id: String) -> String { private func resolveChannelSystemImage(_ id: String) -> String {
switch id { switch id {
case "whatsapp": return "message" case "whatsapp": "message"
case "telegram": return "paperplane" case "telegram": "paperplane"
case "discord": return "bubble.left.and.bubble.right" case "discord": "bubble.left.and.bubble.right"
case "slack": return "number" case "slack": "number"
case "signal": return "antenna.radiowaves.left.and.right" case "signal": "antenna.radiowaves.left.and.right"
case "imessage": return "message.fill" case "imessage": "message.fill"
default: return "message" default: "message"
} }
} }

View File

@@ -57,7 +57,7 @@ extension ChannelsStore {
return value return value
} }
guard path.count >= 2 else { return nil } guard path.count >= 2 else { return nil }
if case .key("channels") = path[0], case .key(_) = path[1] { if case .key("channels") = path[0], case .key = path[1] {
let fallbackPath = Array(path.dropFirst()) let fallbackPath = Array(path.dropFirst())
return valueAtPath(self.configDraft, path: fallbackPath) return valueAtPath(self.configDraft, path: fallbackPath)
} }
@@ -93,10 +93,10 @@ private func valueAtPath(_ root: Any, path: ConfigPath) -> Any? {
var current: Any? = root var current: Any? = root
for segment in path { for segment in path {
switch segment { switch segment {
case .key(let key): case let .key(key):
guard let dict = current as? [String: Any] else { return nil } guard let dict = current as? [String: Any] else { return nil }
current = dict[key] current = dict[key]
case .index(let index): case let .index(index):
guard let array = current as? [Any], array.indices.contains(index) else { return nil } guard let array = current as? [Any], array.indices.contains(index) else { return nil }
current = array[index] current = array[index]
} }
@@ -107,7 +107,7 @@ private func valueAtPath(_ root: Any, path: ConfigPath) -> Any? {
private func setValue(_ root: inout Any, path: ConfigPath, value: Any?) { private func setValue(_ root: inout Any, path: ConfigPath, value: Any?) {
guard let segment = path.first else { return } guard let segment = path.first else { return }
switch segment { switch segment {
case .key(let key): case let .key(key):
var dict = root as? [String: Any] ?? [:] var dict = root as? [String: Any] ?? [:]
if path.count == 1 { if path.count == 1 {
if let value { if let value {
@@ -122,7 +122,7 @@ private func setValue(_ root: inout Any, path: ConfigPath, value: Any?) {
setValue(&child, path: Array(path.dropFirst()), value: value) setValue(&child, path: Array(path.dropFirst()), value: value)
dict[key] = child dict[key] = child
root = dict root = dict
case .index(let index): case let .index(index):
var array = root as? [Any] ?? [] var array = root as? [Any] ?? []
if index >= array.count { if index >= array.count {
array.append(contentsOf: repeatElement(NSNull() as Any, count: index - array.count + 1)) array.append(contentsOf: repeatElement(NSNull() as Any, count: index - array.count + 1))

View File

@@ -133,7 +133,7 @@ struct ConfigSchemaNode {
for segment in path { for segment in path {
guard let node = current else { return nil } guard let node = current else { return nil }
switch segment { switch segment {
case .key(let key): case let .key(key):
if node.schemaType == "object" { if node.schemaType == "object" {
if let next = node.properties[key] { if let next = node.properties[key] {
current = next current = next
@@ -174,7 +174,7 @@ func hintForPath(_ path: ConfigPath, hints: [String: ConfigUiHint]) -> ConfigUiH
var match = true var match = true
for (index, seg) in segments.enumerated() { for (index, seg) in segments.enumerated() {
let hintSegment = hintSegments[index] let hintSegment = hintSegments[index]
if hintSegment != "*" && hintSegment != seg { if hintSegment != "*", hintSegment != seg {
match = false match = false
break break
} }
@@ -196,7 +196,7 @@ func isSensitivePath(_ path: ConfigPath) -> Bool {
func pathKey(_ path: ConfigPath) -> String { func pathKey(_ path: ConfigPath) -> String {
path.compactMap { segment -> String? in path.compactMap { segment -> String? in
switch segment { switch segment {
case .key(let key): return key case let .key(key): return key
case .index: return nil case .index: return nil
} }
} }

View File

@@ -47,7 +47,7 @@ extension ConfigSettings {
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} }
} }
if self.store.configDirty && !self.isNixMode { if self.store.configDirty, !self.isNixMode {
Text("Unsaved changes") Text("Unsaved changes")
.font(.caption) .font(.caption)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)