Files
xrwise-datastructure/data-structure-graphical-coding.md
T
2026-06-29 18:14:46 +02:00

227 lines
5.5 KiB
Markdown

# Graphical Coding — Database Save Format
This document describes the JSON structure stored in the Supabase `rooms` table under the `graphical_coding` column.
---
## Top-level shape
```json
{
"nodes": [ ...GraphNode ],
"connections": [ ...Connection ]
}
```
The entire graph is serialized as a single JSON blob — no normalization, no references across rows.
---
## `GraphNode`
```json
{
"id": "gc-1-1234567890",
"kind": "source | trigger | effect",
"label": "My Node",
"position": { "x": 100, "y": 200 },
"inputs": [ ...PortDef ],
"outputs": [ ...PortDef ],
"config": { ...kind-specific config }
}
```
| Field | Type | Description |
|---|---|---|
| `id` | `string` | Unique node identifier, format `gc-<index>-<timestamp>` |
| `kind` | `"source" \| "trigger" \| "effect"` | Node category |
| `label` | `string` | User-visible name |
| `position` | `{ x: number, y: number }` | Canvas position in pixels |
| `inputs` | `PortDef[]` | Input ports |
| `outputs` | `PortDef[]` | Output ports |
| `config` | `object` | Kind-specific configuration (see below) |
### `PortDef`
```json
{ "id": "gc-1-1234567890", "name": "fired", "dataType": "boolean" }
```
| Field | Type | Description |
|---|---|---|
| `id` | `string` | Port identifier (shares format with node IDs) |
| `name` | `string` | Display name, e.g. `"fired"`, `"value"`, `"trigger"` |
| `dataType` | `string` | `"number"`, `"string"`, `"boolean"`, `"object"`, or `"any"` |
---
## `Connection`
```json
{
"id": "conn-1234567890",
"fromNode": "gc-1-111",
"fromPort": "gc-1-111-o0",
"toNode": "gc-3-333",
"toPort": "gc-3-333-i0"
}
```
| Field | Type | Description |
|---|---|---|
| `id` | `string` | Unique connection identifier |
| `fromNode` | `string` | Source node ID |
| `fromPort` | `string` | Source port ID (output port) |
| `toNode` | `string` | Target node ID |
| `toPort` | `string` | Target port ID (input port) |
---
## Node `config` by kind
### `source` — REST API data feed
```json
{
"endpoint": "https://api.example.com/sensor",
"condition": ">",
"threshold": 42
}
```
| Field | Type | Description |
|---|---|---|
| `endpoint` | `string` | REST API URL polled for data |
| `condition` | `">" \| "<" \| "==" \| "!=" \| ">=" \| "<="` | Comparison operator applied to the API response value |
| `threshold` | `number` | Value compared against the API response |
---
### `trigger` — User interaction in the scene
```json
{
"sourceItemId": 7,
"triggerEvent": "clicked",
"cooldown": 500
}
```
| Field | Type | Description |
|---|---|---|
| `sourceItemId` | `number \| null` | Scene item ID to watch (`null` = any item) |
| `triggerEvent` | `"clicked" \| "proximity_enter" \| "proximity_exit"` | Interaction type |
| `cooldown` | `number` | Minimum milliseconds between trigger firings |
---
### `effect` — Scene modification
```json
{
"inputMode": "trigger",
"targetObjectType": "item",
"targetItemId": 3,
"effectProperty": "color",
"targetColor": "#ff0000"
}
```
| Field | Type | Description |
|---|---|---|
| `inputMode` | `"trigger" \| "stream"` | Whether the effect fires on a trigger pulse or follows a continuous stream value |
| `targetObjectType` | `"item" \| "sky"` | What to modify |
| `targetItemId` | `number \| null` | Scene item ID (only relevant when `targetObjectType` is `"item"`) |
| `effectProperty` | `"color" \| "position" \| "rotation" \| "scale" \| "visibility"` | Which property to change |
Additional fields depend on `effectProperty`:
**`color`**
```json
{ "targetColor": "#ffffff" }
```
**`position`**
```json
{ "positionAxis": "x", "targetPositionValue": 2.5 }
```
**`rotation`**
```json
{ "rotationAxis": "y", "targetRotationValue": 90 }
```
`targetRotationValue` is in degrees.
**`scale`**
```json
{ "targetScale": 2.0 }
```
**`visibility`**
```json
{ "targetVisibility": false }
```
---
## Full example
A graph with one trigger and one effect, connected:
```json
{
"nodes": [
{
"id": "gc-1-1000000000",
"kind": "trigger",
"label": "On Click",
"position": { "x": 120, "y": 200 },
"inputs": [],
"outputs": [
{ "id": "gc-1-1000000000-o0", "name": "fired", "dataType": "boolean" }
],
"config": {
"sourceItemId": null,
"triggerEvent": "clicked",
"cooldown": 500
}
},
{
"id": "gc-2-2000000000",
"kind": "effect",
"label": "Skybox Color",
"position": { "x": 480, "y": 200 },
"inputs": [
{ "id": "gc-2-2000000000-i0", "name": "trigger", "dataType": "boolean" }
],
"outputs": [],
"config": {
"inputMode": "trigger",
"targetObjectType": "sky",
"targetItemId": null,
"effectProperty": "color",
"targetColor": "#3a0f8c"
}
}
],
"connections": [
{
"id": "conn-9999999999",
"fromNode": "gc-1-1000000000",
"fromPort": "gc-1-1000000000-o0",
"toNode": "gc-2-2000000000",
"toPort": "gc-2-2000000000-i0"
}
]
}
```
---
## Persistence notes
- **Column:** `graphical_coding` (JSONB) in the Supabase `rooms` table. Not yet reflected in `supabase-types.ts`.
- **Save:** triggered manually via the "Save Project" button; the whole object is JSON-stringified and sent as a `FormData` field to `PATCH /api/save-room/[id]`.
- **Load:** the value is read from `roomData.graphical_coding` in `creator.tsx` and hydrated into the Zustand `SceneStore`.
- **Local cache:** Zustand's `persist` middleware also writes the graph to `localStorage` under the key `"scene-storage"`, so edits survive a page refresh before an explicit save.