- DeviceRegistry with load/save/add/remove/instantiate and _DEVICE_CLASSES dict
- ShowStore with save/load/list_ids using aiofiles for async I/O
- FastAPI app with lifespan hook (startup: load registry/store, shutdown: save registry)
- API routes: GET/POST /api/devices/, DELETE /api/devices/{id}, GET/POST /api/shows/, GET /api/shows/{id}
- WebSocket stub at /ws with ConnectionManager and ack echo
- Frontend shell: DAW layout with header, sidebar (DEVICES + ANIMATIONS), TIMELINE, TRANSPORT
- Terminal aesthetic: dark theme, cyan accents, monospace fonts, no drop shadows
- .gitignore for runtime data (devices.json, shows/, __pycache__, .venv)
46 lines
1.3 KiB
Python
46 lines
1.3 KiB
Python
import json
|
|
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
class ConnectionManager:
|
|
def __init__(self):
|
|
self.active_connections: list[WebSocket] = []
|
|
|
|
async def connect(self, ws: WebSocket) -> None:
|
|
await ws.accept()
|
|
self.active_connections.append(ws)
|
|
|
|
def disconnect(self, ws: WebSocket) -> None:
|
|
if ws in self.active_connections:
|
|
self.active_connections.remove(ws)
|
|
|
|
async def broadcast(self, message: dict) -> None:
|
|
dead = []
|
|
for connection in self.active_connections:
|
|
try:
|
|
await connection.send_text(json.dumps(message))
|
|
except Exception:
|
|
dead.append(connection)
|
|
for d in dead:
|
|
if d in self.active_connections:
|
|
self.active_connections.remove(d)
|
|
|
|
|
|
manager = ConnectionManager()
|
|
|
|
|
|
@router.websocket("/ws")
|
|
async def websocket_endpoint(websocket: WebSocket):
|
|
await manager.connect(websocket)
|
|
try:
|
|
while True:
|
|
data = await websocket.receive_text()
|
|
msg = json.loads(data)
|
|
# Phase 2 will dispatch msg["type"] to handlers
|
|
# For now: echo back with type=ack
|
|
await websocket.send_text(json.dumps({"type": "ack", "echo": msg}))
|
|
except WebSocketDisconnect:
|
|
manager.disconnect(websocket)
|