Files
le-mystere-professeur-zacus/apps/zacus-hub/Sources/Studio/Blocks/BlockPaletteView.swift
T
L'électron rare 3babe2b3ab feat(apps): zacus-hub SwiftUI hub (macOS + iOS)
New multiplatform SwiftUI app under apps/zacus-hub/ generated via
XcodeGen (project.yml). Three modes share a HubSession actor that
talks to tools/zacus-gateway over Tailscale-only HTTP/WS.

Game-master: live state stream (WebSocket /v1/state/ws), backend
health badges, hint trigger, journal.

Companion: push-to-talk via AVAudioRecorder (M4A AAC 16 kHz),
fallback file picker, hint request, transcript via voice-bridge
proxy.

Studio: YAML editor with validate/save (existing /v1/studio
routes), plus a Scratch tab hosting a Blockly + zelos-renderer
workspace in WKWebView. 38 custom Zacus block types across 10
categories (Scènes, NPC, Audio, LCD, Hardware ESP32, ESP-NOW,
BOX-3, M5, PLIP, Logique), .sb3 export/import via JSZip (procedure
calls + embedded YAML for lossless round-trip), Flasher sheet that
calls /v1/flash/{board} with auto/hot/cold strategies and shows
per-board step results including the cold-flash idf.py command.

ATS exception domains cover the Tailscale CGNAT range plus the
electron-server / studio / macm1 hostnames so the WebView and
URLSession can hit plain HTTP on the tailnet without TLS.

Token stays in the Keychain (cc.saillant.zacus.hub) — never
committed to source.
2026-05-24 10:51:26 +02:00

58 lines
1.8 KiB
Swift

import SwiftUI
/// Sidebar listing block templates. Drag onto canvas, or click "+" for centre spawn.
struct BlockPaletteView: View {
var onAdd: (BlockKind) -> Void
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 14) {
ForEach(BlockCatalog.byCategory(), id: \.0) { category, specs in
section(category, specs: specs)
}
}
.padding(12)
}
.frame(width: 240)
.background(.thinMaterial)
}
private func section(_ cat: BlockCategory, specs: [BlockSpec]) -> some View {
VStack(alignment: .leading, spacing: 6) {
HStack(spacing: 8) {
Circle().fill(cat.color).frame(width: 10, height: 10)
Text(cat.label).font(.subheadline.bold())
}
ForEach(specs, id: \.kind) { spec in
tile(spec, color: cat.color)
}
}
}
private func tile(_ spec: BlockSpec, color: Color) -> some View {
HStack {
Text(spec.title).font(.callout).foregroundStyle(.primary)
Spacer()
Button { onAdd(spec.kind) } label: {
Image(systemName: "plus.circle").foregroundStyle(color)
}
.buttonStyle(.plain)
}
.padding(.vertical, 6).padding(.horizontal, 8)
.background(RoundedRectangle(cornerRadius: 6).fill(color.opacity(0.12)))
.contentShape(Rectangle())
#if os(macOS)
.draggable(PaletteDrop(kind: spec.kind)) {
// drag preview
HStack(spacing: 6) {
Circle().fill(color).frame(width: 8, height: 8)
Text(spec.title).font(.caption.bold())
}
.padding(6)
.background(.thinMaterial)
.cornerRadius(6)
}
#endif
}
}