




















@@ -1,9 +1,27 @@
11import SwiftUI
223+enum ConfigSchemaFormMode {
4+case full
5+case channelQuick
6+}
7+38struct ConfigSchemaForm: View {
49@Bindable var store: ChannelsStore
510let schema: ConfigSchemaNode
611let path: ConfigPath
12+let mode: ConfigSchemaFormMode
13+14+init(
15+ store: ChannelsStore,
16+ schema: ConfigSchemaNode,
17+ path: ConfigPath,
18+ mode: ConfigSchemaFormMode = .full)
19+{
20+self.store = store
21+self.schema = schema
22+self.path = path
23+self.mode = mode
24+}
725826var body: some View {
927self.renderNode(self.schema, path: self.path)
@@ -12,7 +30,7 @@ struct ConfigSchemaForm: View {
1230private func renderNode(_ schema: ConfigSchemaNode, path: ConfigPath) -> AnyView {
1331let storedValue = self.store.configValue(at: path)
1432let value = storedValue ?? schema.explicitDefault
15-let label = hintForPath(path, hints: store.configUiHints)?.label ?? schema.title
33+let label = self.fieldLabel(for: schema, path: path)
1634let help = hintForPath(path, hints: store.configUiHints)?.help ?? schema.description
1735let variants = schema.anyOf.isEmpty ? schema.oneOf : schema.anyOf
1836@@ -62,18 +80,16 @@ struct ConfigSchemaForm: View {
6280.foregroundStyle(.secondary)
6381}
6482let properties = schema.properties
65-let sortedKeys = properties.keys.sorted { lhs, rhs in
66-let orderA = hintForPath(path + [.key(lhs)], hints: store.configUiHints)?.order ?? 0
67-let orderB = hintForPath(path + [.key(rhs)], hints: store.configUiHints)?.order ?? 0
68-if orderA != orderB { return orderA < orderB }
69-return lhs < rhs
70-}
83+let sortedKeys = self.visibleObjectKeys(properties: properties, path: path)
7184ForEach(sortedKeys, id: \ .self) { key in
7285if let child = properties[key] {
7386self.renderNode(child, path: path + [.key(key)])
7487}
7588}
76-if schema.allowsAdditionalProperties {
89+if sortedKeys.isEmpty, self.mode == .channelQuick, self.isChannelRoot(path) {
90+self.renderChannelQuickEmptyState()
91+}
92+if self.shouldRenderAdditionalProperties(schema, path: path, value: value) {
7793self.renderAdditionalProperties(schema, path: path, value: value)
7894}
7995})
@@ -100,6 +116,117 @@ struct ConfigSchemaForm: View {
100116}
101117}
102118119+private func fieldLabel(for schema: ConfigSchemaNode, path: ConfigPath) -> String? {
120+hintForPath(path, hints: self.store.configUiHints)?.label
121+?? schema.title
122+?? labelForConfigPath(path)
123+}
124+125+private func visibleObjectKeys(
126+ properties: [String: ConfigSchemaNode],
127+ path: ConfigPath) -> [String]
128+{
129+let sortedKeys = properties.keys.sorted { lhs, rhs in
130+let orderA = hintForPath(path + [.key(lhs)], hints: store.configUiHints)?.order ?? 0
131+let orderB = hintForPath(path + [.key(rhs)], hints: store.configUiHints)?.order ?? 0
132+if orderA != orderB { return orderA < orderB }
133+return lhs < rhs
134+}
135+136+guard self.mode == .channelQuick, self.isChannelRoot(path) else {
137+return sortedKeys
138+}
139+140+return sortedKeys.filter { key in
141+guard let child = properties[key] else { return false }
142+return self.shouldRenderChannelQuickField(key: key, schema: child, path: path + [.key(key)])
143+}
144+}
145+146+private func shouldRenderChannelQuickField(
147+ key: String,
148+ schema: ConfigSchemaNode,
149+ path: ConfigPath) -> Bool
150+{
151+if hintForPath(path, hints: self.store.configUiHints)?.advanced == true {
152+return false
153+}
154+if Self.channelQuickKeys.contains(key) {
155+return self.isSimpleField(schema)
156+}
157+return self.store.configValue(at: path) != nil && self.isSimpleField(schema)
158+}
159+160+private func isSimpleField(_ schema: ConfigSchemaNode) -> Bool {
161+let variants = schema.anyOf.isEmpty ? schema.oneOf : schema.anyOf
162+let nonNullVariants = variants.filter { !$0.isNullSchema }
163+if !nonNullVariants.isEmpty {
164+return nonNullVariants.allSatisfy(self.isSimpleField)
165+}
166+if let enumValues = schema.enumValues {
167+return !enumValues.isEmpty
168+}
169+switch schema.schemaType {
170+case "boolean", "integer", "number", "string":
171+return true
172+default:
173+return false
174+}
175+}
176+177+private func shouldRenderAdditionalProperties(
178+ _ schema: ConfigSchemaNode,
179+ path: ConfigPath,
180+ value: Any?) -> Bool
181+{
182+guard schema.allowsAdditionalProperties else { return false }
183+if self.mode != .channelQuick { return true }
184+guard let dict = value as? [String: Any] else { return false }
185+let reserved = Set(schema.properties.keys)
186+return dict.keys.contains { !reserved.contains($0) }
187+}
188+189+private func isChannelRoot(_ path: ConfigPath) -> Bool {
190+guard path.count == 2 else { return false }
191+guard case .key("channels") = path[0] else { return false }
192+guard case .key = path[1] else { return false }
193+return true
194+}
195+196+@ViewBuilder
197+private func renderChannelQuickEmptyState() -> some View {
198+VStack(alignment: .leading, spacing: 4) {
199+Text("No quick settings for this channel.")
200+.font(.callout.weight(.semibold))
201+Text("Use Config for account, guild, action, and policy details.")
202+.font(.caption)
203+.foregroundStyle(.secondary)
204+}
205+}
206+207+private static let channelQuickKeys: Set<String> = [
208+"apiHash",
209+"apiId",
210+"appToken",
211+"baseUrl",
212+"botToken",
213+"configWrites",
214+"deviceName",
215+"dmPolicy",
216+"enabled",
217+"groupPolicy",
218+"historyLimit",
219+"mode",
220+"nativeCommands",
221+"nativeSkillCommands",
222+"phoneNumber",
223+"signingSecret",
224+"token",
225+"url",
226+"username",
227+"webhookUrl",
228+]
229+103230@ViewBuilder
104231private func renderStringField(
105232 _ schema: ConfigSchemaNode,
@@ -353,7 +480,11 @@ struct ChannelConfigForm: View {
353480if self.store.configSchemaLoading {
354481ProgressView().controlSize(.small)
355482} else if let schema = store.channelConfigSchema(for: channelId) {
356-ConfigSchemaForm(store: self.store, schema: schema, path: [.key("channels"), .key(self.channelId)])
483+ConfigSchemaForm(
484+ store: self.store,
485+ schema: schema,
486+ path: [.key("channels"), .key(self.channelId)],
487+ mode: .channelQuick)
357488} else {
358489Text("Schema unavailable for this channel.")
359490.font(.caption)
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。