style: tidy macOS config UI formatting
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user