add graphical coding doc
This commit is contained in:
@@ -0,0 +1,226 @@
|
|||||||
|
# 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.
|
||||||
Reference in New Issue
Block a user