Hermes Agent 36967370fd SwiftBricks: Bricks JSON UI framework native Swift/SwiftUI implementation
- Core: Schema (Codable), Store, EventBus, RPC, Engine, I18n
- Controls: Text, Title1-6, Label, Input, Textarea, Number, Date, Button, Select
  VBox, HBox, ScrollPanel, DynamicColumn, Tabular, Form, InlineForm,
  TabView, Menu, PopupWindow, Html, Image, UrlWidget
- Renderer: BricksView (main entry), ControlRenderer (recursive dispatch)
- 30+ widget types, 9 bind actiontypes
- Form validation (required/minlength/maxlength/min/max/email/number/pattern)
- i18n with multi-language JSON files
- 18 tests covering schema parsing, store, i18n, events
2026-06-21 12:12:03 +08:00

153 lines
4.8 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import SwiftUI
// MARK: - Button Control
struct ButtonControl: View {
let schema: ControlSchema
@ObservedObject var engine: BricksEngine
@State private var isLoading: Bool = false
var body: some View {
Button(action: handleAction) {
HStack(spacing: 6) {
if isLoading {
ProgressView()
.scaleEffect(0.7)
}
if let icon = schema.options.icon {
Text(icon)
}
Text(displayLabel)
}
.padding(.horizontal, 12)
.padding(.vertical, 6)
}
.buttonStyle(buttonStyle)
.disabled(isLoading)
}
private var displayLabel: String {
let raw = schema.options.label ?? ""
return (schema.options.i18n != false) ? engine.i18n.t(raw) : raw
}
private var buttonStyle: BricksButtonStyle {
let css = schema.options.css ?? ""
if css.contains("primary") {
return BricksButtonStyle(variant: .primary)
} else if css.contains("danger") {
return BricksButtonStyle(variant: .danger)
} else if css.contains("text") {
return BricksButtonStyle(variant: .text)
}
return BricksButtonStyle(variant: .default)
}
private func handleAction() {
// bindsclick
Task {
await engine.triggerEvent(widgetId: schema.effectiveId, event: "click")
}
}
}
// MARK: - Button Style
struct BricksButtonStyle: ButtonStyle {
enum Variant { case `default`, primary, danger, text }
let variant: Variant
func makeBody(configuration: Configuration) -> some View {
configuration.label
.foregroundColor(textColor)
.background(backgroundColor(configuration.isPressed))
.cornerRadius(6)
.opacity(configuration.isPressed ? 0.8 : 1.0)
}
private var textColor: Color {
switch variant {
case .primary: return .white
case .danger: return .white
case .text: return .accentColor
case .default: return .primary
}
}
private func backgroundColor(_ pressed: Bool) -> Color {
switch variant {
case .primary: return pressed ? .blue.opacity(0.7) : .blue
case .danger: return pressed ? .red.opacity(0.7) : .red
case .text: return .clear
case .default: return pressed ? Color.secondary.opacity(0.2) : Color.secondary.opacity(0.1)
}
}
}
// MARK: - Select Control
struct SelectControl: View {
let schema: ControlSchema
@ObservedObject var engine: BricksEngine
@State private var selectedValue: String = ""
@State private var items: [CodeItem] = []
var body: some View {
VStack(alignment: .leading, spacing: 4) {
if let label = schema.options.label ?? schema.options.title {
Text(engine.i18n.t(label))
.font(.caption)
.foregroundColor(.secondary)
}
Picker("", selection: $selectedValue) {
ForEach(items, id: \.value) { item in
Text(engine.i18n.t(item.text)).tag(item.value)
}
}
.pickerStyle(.menu)
}
.onAppear {
loadItems()
selectedValue = schema.options.value ?? ""
}
.onChange(of: selectedValue) { _, newValue in
engine.store.setValue(id: schema.effectiveId, value: newValue)
Task {
await engine.triggerEvent(widgetId: schema.effectiveId, event: "change",
data: EventData(["value": newValue]))
}
}
.task {
await loadRemoteData()
}
}
private func loadItems() {
if let data = schema.options.data ?? schema.options.codes {
items = data
} else {
items = []
}
}
private func loadRemoteData() async {
guard let dataurl = schema.options.dataurl else { return }
do {
let response = try await engine.rpc.get(dataurl)
if response.isSuccess, let dict = response.dictionary {
if let rows = dict["rows"] as? [[String: Any]] {
let vf = schema.options.valueField ?? "value"
let tf = schema.options.textField ?? "text"
items = rows.compactMap { row in
guard let v = row[vf] as? String, let t = row[tf] as? String else { return nil }
return CodeItem(value: v, text: t)
}
}
}
} catch {
print("[SwiftBricks] Select data load failed: \(error)")
}
}
}