@stinkycomputing/sesame-editor-api 1.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/helpers.d.ts +45 -0
- package/dist/helpers.js +65 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/node-types.d.ts +119 -0
- package/dist/node-types.js +5 -0
- package/dist/plugin.d.ts +78 -0
- package/dist/plugin.js +8 -0
- package/package.json +21 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serialization helper utilities for plugin authors.
|
|
3
|
+
*
|
|
4
|
+
* These work with minimal structural interfaces rather than importing from
|
|
5
|
+
* @xyflow/react, so this package remains free of a React dependency.
|
|
6
|
+
* ReactFlow's Node and Edge satisfy the MinimalNode / MinimalEdge contracts.
|
|
7
|
+
*/
|
|
8
|
+
import type { SesameNodeData } from './node-types';
|
|
9
|
+
export interface MinimalNode {
|
|
10
|
+
id: string;
|
|
11
|
+
position: {
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
};
|
|
15
|
+
data: unknown;
|
|
16
|
+
}
|
|
17
|
+
export interface MinimalEdge {
|
|
18
|
+
source: string;
|
|
19
|
+
sourceHandle?: string | null;
|
|
20
|
+
target: string;
|
|
21
|
+
targetHandle?: string | null;
|
|
22
|
+
data?: unknown;
|
|
23
|
+
}
|
|
24
|
+
export declare function getData<TNode extends MinimalNode>(node: TNode): SesameNodeData;
|
|
25
|
+
export declare function nodeProps<TNode extends MinimalNode>(node: TNode): Record<string, unknown>;
|
|
26
|
+
export declare function nodeMeta<TNode extends MinimalNode>(node: TNode): {
|
|
27
|
+
x: number;
|
|
28
|
+
y: number;
|
|
29
|
+
collapsed?: boolean;
|
|
30
|
+
};
|
|
31
|
+
export declare function getNodesByCategory<TNode extends MinimalNode>(nodes: TNode[], category: string): TNode[];
|
|
32
|
+
/**
|
|
33
|
+
* Find the node connected to a specific input handle on targetNodeId.
|
|
34
|
+
* Returns the source node, its output handle index, and the built connectionId.
|
|
35
|
+
*/
|
|
36
|
+
export declare function findSourceConnection<TNode extends MinimalNode, TEdge extends MinimalEdge>(edges: TEdge[], nodes: TNode[], targetNodeId: string, targetHandleId: string): {
|
|
37
|
+
node: TNode;
|
|
38
|
+
handleIdx: number;
|
|
39
|
+
connectionId: string;
|
|
40
|
+
} | undefined;
|
|
41
|
+
/** Find all edges going into targetNodeId on ports of portType. */
|
|
42
|
+
export declare function findInputConnectionsByType<TNode extends MinimalNode, TEdge extends MinimalEdge>(edges: TEdge[], nodes: TNode[], targetNodeId: string, portType: string): {
|
|
43
|
+
connectionId: string;
|
|
44
|
+
inputIdx: number;
|
|
45
|
+
}[];
|
package/dist/helpers.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serialization helper utilities for plugin authors.
|
|
3
|
+
*
|
|
4
|
+
* These work with minimal structural interfaces rather than importing from
|
|
5
|
+
* @xyflow/react, so this package remains free of a React dependency.
|
|
6
|
+
* ReactFlow's Node and Edge satisfy the MinimalNode / MinimalEdge contracts.
|
|
7
|
+
*/
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Node data accessors
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
export function getData(node) {
|
|
12
|
+
return node.data;
|
|
13
|
+
}
|
|
14
|
+
export function nodeProps(node) {
|
|
15
|
+
return getData(node).properties;
|
|
16
|
+
}
|
|
17
|
+
export function nodeMeta(node) {
|
|
18
|
+
return {
|
|
19
|
+
x: node.position.x,
|
|
20
|
+
y: node.position.y,
|
|
21
|
+
collapsed: getData(node).collapsed || false,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export function getNodesByCategory(nodes, category) {
|
|
25
|
+
return nodes.filter(n => getData(n).def.category === category);
|
|
26
|
+
}
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Edge traversal helpers
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
/**
|
|
31
|
+
* Find the node connected to a specific input handle on targetNodeId.
|
|
32
|
+
* Returns the source node, its output handle index, and the built connectionId.
|
|
33
|
+
*/
|
|
34
|
+
export function findSourceConnection(edges, nodes, targetNodeId, targetHandleId) {
|
|
35
|
+
const edge = edges.find(e => e.target === targetNodeId && e.targetHandle === targetHandleId);
|
|
36
|
+
if (!edge)
|
|
37
|
+
return undefined;
|
|
38
|
+
const srcNode = nodes.find(n => n.id === edge.source);
|
|
39
|
+
if (!srcNode)
|
|
40
|
+
return undefined;
|
|
41
|
+
const handleIdx = parseInt((edge.sourceHandle || '').replace('out-', ''), 10);
|
|
42
|
+
const data = getData(srcNode);
|
|
43
|
+
const outputs = (data.dynamicOutputs ?? data.def.outputs);
|
|
44
|
+
const port = outputs[handleIdx];
|
|
45
|
+
const portName = port?.name || '';
|
|
46
|
+
const connectionId = `${data.properties.id}${portName}`;
|
|
47
|
+
return { node: srcNode, handleIdx, connectionId };
|
|
48
|
+
}
|
|
49
|
+
/** Find all edges going into targetNodeId on ports of portType. */
|
|
50
|
+
export function findInputConnectionsByType(edges, nodes, targetNodeId, portType) {
|
|
51
|
+
const targetNode = nodes.find(n => n.id === targetNodeId);
|
|
52
|
+
if (!targetNode)
|
|
53
|
+
return [];
|
|
54
|
+
const data = getData(targetNode);
|
|
55
|
+
const inputs = (data.dynamicInputs ?? data.def.inputs);
|
|
56
|
+
const results = [];
|
|
57
|
+
inputs.forEach((port, i) => {
|
|
58
|
+
if (port.type !== portType)
|
|
59
|
+
return;
|
|
60
|
+
const conn = findSourceConnection(edges, nodes, targetNodeId, `in-${i}`);
|
|
61
|
+
if (conn)
|
|
62
|
+
results.push({ connectionId: conn.connectionId, inputIdx: i });
|
|
63
|
+
});
|
|
64
|
+
return results;
|
|
65
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core node type definitions shared between the config editor and all plugins.
|
|
3
|
+
* Plugins import these types to describe their own node types.
|
|
4
|
+
*/
|
|
5
|
+
export type MetadataFormat = 'json' | 'protobuf/transport-status' | 'protobuf/audio-state' | 'protobuf/transport-control' | 'protobuf/audio-control';
|
|
6
|
+
export interface PortDef {
|
|
7
|
+
/** Display label shown next to the handle. */
|
|
8
|
+
label: string;
|
|
9
|
+
/**
|
|
10
|
+
* Connection-type string used for validation.
|
|
11
|
+
* Only ports with the same type can be connected.
|
|
12
|
+
* Examples: "source/device", "audio/raw", "video/composition", …
|
|
13
|
+
*/
|
|
14
|
+
type: string;
|
|
15
|
+
/** Optional port name used during serialization to build "connectionId" strings. */
|
|
16
|
+
name?: string;
|
|
17
|
+
/** Optional max outgoing connections allowed from this output port. */
|
|
18
|
+
maxConnections?: number;
|
|
19
|
+
}
|
|
20
|
+
export type MetadataDirection = 'emit' | 'receive';
|
|
21
|
+
export interface MetadataDef {
|
|
22
|
+
/** Unique identifier for this metadata channel, e.g. 'transport-status', 'audio-state'. */
|
|
23
|
+
id: string;
|
|
24
|
+
/** Human-readable label shown in the node UI. */
|
|
25
|
+
label: string;
|
|
26
|
+
/** Whether this node emits or receives this metadata. */
|
|
27
|
+
direction: MetadataDirection;
|
|
28
|
+
/**
|
|
29
|
+
* Port type string used for connection matching.
|
|
30
|
+
* Convention: 'metadata/<id>', e.g. 'metadata/transport-status'.
|
|
31
|
+
*/
|
|
32
|
+
portType: string;
|
|
33
|
+
/** Wire format for the payload. Maps to DataType in sesame/v1/wire/wire.proto. */
|
|
34
|
+
format: MetadataFormat;
|
|
35
|
+
}
|
|
36
|
+
export type WidgetType = 'text' | 'number' | 'slider' | 'toggle' | 'combo' | 'chromacolor';
|
|
37
|
+
export interface WidgetDef {
|
|
38
|
+
type: WidgetType;
|
|
39
|
+
label: string;
|
|
40
|
+
/** Key into the node's `properties` record. */
|
|
41
|
+
property: string;
|
|
42
|
+
options?: {
|
|
43
|
+
min?: number;
|
|
44
|
+
max?: number;
|
|
45
|
+
step?: number;
|
|
46
|
+
precision?: number;
|
|
47
|
+
values?: string[] | Record<string, string>;
|
|
48
|
+
getDisplayText?: (value: number) => string;
|
|
49
|
+
getEditValue?: (value: number) => number;
|
|
50
|
+
parseEditValue?: (edited: number) => number;
|
|
51
|
+
cbProperty?: string;
|
|
52
|
+
crProperty?: string;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export interface NodeTypeDef {
|
|
56
|
+
/** Unique type key, e.g. "source/decklink". */
|
|
57
|
+
type: string;
|
|
58
|
+
/** Human-readable title shown in the node header. */
|
|
59
|
+
title: string;
|
|
60
|
+
/**
|
|
61
|
+
* Category used during serialization grouping.
|
|
62
|
+
* Open string to allow plugin-defined categories alongside built-in ones.
|
|
63
|
+
*/
|
|
64
|
+
category: string;
|
|
65
|
+
/** Header background color (CSS). */
|
|
66
|
+
color: string;
|
|
67
|
+
/** Label shown in the context menu group heading. */
|
|
68
|
+
menuGroup: string;
|
|
69
|
+
/** Sort order for the context menu group (lower = higher). */
|
|
70
|
+
menuOrder: number;
|
|
71
|
+
/** Sort order within the menu group (lower = higher). Defaults to 0 when omitted. */
|
|
72
|
+
menuItemOrder?: number;
|
|
73
|
+
/** Input port definitions (left-hand handles). */
|
|
74
|
+
inputs: PortDef[];
|
|
75
|
+
/** Output port definitions (right-hand handles). */
|
|
76
|
+
outputs: PortDef[];
|
|
77
|
+
/** Inline widget controls rendered inside the node body. */
|
|
78
|
+
widgets: WidgetDef[];
|
|
79
|
+
/** Default property values for a newly created node. */
|
|
80
|
+
defaultProperties: Record<string, unknown>;
|
|
81
|
+
/**
|
|
82
|
+
* Metadata channels this node exposes — status streams it emits or
|
|
83
|
+
* control streams it receives. Each entry generates a port with the
|
|
84
|
+
* given portType so it can be wired to an output's metadata tracks.
|
|
85
|
+
*/
|
|
86
|
+
metadata?: MetadataDef[];
|
|
87
|
+
/** Whether this node supports dynamic ports – ports added/removed based on connections. */
|
|
88
|
+
dynamicPorts?: boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Optional key that selects a specialised property-panel editor instead of
|
|
91
|
+
* the default widget list.
|
|
92
|
+
*/
|
|
93
|
+
customEditor?: string;
|
|
94
|
+
/**
|
|
95
|
+
* Optional callback invoked when the count of connected dynamic input ports changes.
|
|
96
|
+
* Return partial properties to merge, or undefined for no change.
|
|
97
|
+
*
|
|
98
|
+
* @param properties Current node properties.
|
|
99
|
+
* @param dynamicInputCount Total dynamic input count including the trailing empty slot.
|
|
100
|
+
* @param inputConnectionIds Resolved connection-ID string for each dynamic input slot
|
|
101
|
+
* (undefined when the slot is unconnected).
|
|
102
|
+
*/
|
|
103
|
+
onDynamicPortsChanged?: (properties: Record<string, unknown>, dynamicInputCount: number, inputConnectionIds: (string | undefined)[]) => Partial<Record<string, unknown>> | undefined;
|
|
104
|
+
}
|
|
105
|
+
/** Stored in each React Flow node's `data` field. */
|
|
106
|
+
export interface SesameNodeData {
|
|
107
|
+
/** Index signature for React Flow compatibility. */
|
|
108
|
+
[key: string]: unknown;
|
|
109
|
+
/** Reference to the declarative node definition. */
|
|
110
|
+
def: NodeTypeDef;
|
|
111
|
+
/** Mutable property bag (widget values, id, etc.). */
|
|
112
|
+
properties: Record<string, unknown>;
|
|
113
|
+
/** Whether the node body is collapsed. */
|
|
114
|
+
collapsed?: boolean;
|
|
115
|
+
/** Dynamic input ports (used for nodes with `dynamicPorts`). */
|
|
116
|
+
dynamicInputs?: PortDef[];
|
|
117
|
+
/** Dynamic output ports (used for nodes with `dynamicPorts`). */
|
|
118
|
+
dynamicOutputs?: PortDef[];
|
|
119
|
+
}
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin interface types for the Sesame config editor.
|
|
3
|
+
*
|
|
4
|
+
* TNode / TEdge are generic so this package does not depend on @xyflow/react.
|
|
5
|
+
* In practice the config-editor and all plugins instantiate them as
|
|
6
|
+
* Node and Edge from @xyflow/react.
|
|
7
|
+
*/
|
|
8
|
+
import type { SesameConfig } from '@stinkycomputing/sesame-interop';
|
|
9
|
+
import type { NodeTypeDef, PortDef } from './node-types';
|
|
10
|
+
type ISesameConfig = SesameConfig.ISesameConfig;
|
|
11
|
+
export interface NodeFactory<TNode = unknown, TEdge = unknown> {
|
|
12
|
+
/**
|
|
13
|
+
* Create a React Flow node. Registers the node ID for collision detection
|
|
14
|
+
* and generates an empty-ID warning if `id` is falsy.
|
|
15
|
+
*/
|
|
16
|
+
makeNode(id: string, type: string, position: {
|
|
17
|
+
x: number;
|
|
18
|
+
y: number;
|
|
19
|
+
}, properties: Record<string, unknown>, collapsed?: boolean, extraInputs?: PortDef[], extraOutputs?: PortDef[]): TNode;
|
|
20
|
+
/** Create a React Flow edge. */
|
|
21
|
+
edge(sourceId: string, sourceHandle: string, targetId: string, targetHandle: string): TEdge;
|
|
22
|
+
/** Find the handle ID for an output port matching portType (and optional portName). */
|
|
23
|
+
findOutputSlot(nodes: TNode[], nodeId: string, portType: string, outputName?: string): string | null;
|
|
24
|
+
/** Find the handle ID for the nth input port matching portType. */
|
|
25
|
+
findInputSlot(nodes: TNode[], nodeId: string, portType: string, nthOfType?: number): string | null;
|
|
26
|
+
/** Read _meta position from a config object, or fall back to a grid position. */
|
|
27
|
+
pos(obj: unknown, fallbackX: number, fallbackIdx: number): {
|
|
28
|
+
x: number;
|
|
29
|
+
y: number;
|
|
30
|
+
};
|
|
31
|
+
/** Read _meta collapsed flag from a config object. */
|
|
32
|
+
collapsed(obj: unknown): boolean;
|
|
33
|
+
/** Horizontal spacing between node columns. */
|
|
34
|
+
readonly X_OFFSET: number;
|
|
35
|
+
}
|
|
36
|
+
export interface CoreNodeMaps<TNode = unknown> {
|
|
37
|
+
/** All source nodes keyed by config ID. */
|
|
38
|
+
sourceNodes: ReadonlyMap<string, TNode>;
|
|
39
|
+
/** All output nodes keyed by config ID. */
|
|
40
|
+
outputNodes: ReadonlyMap<string, TNode>;
|
|
41
|
+
}
|
|
42
|
+
export interface PluginBuildResult<TNode = unknown, TEdge = unknown> {
|
|
43
|
+
/** Nodes created by this plugin. */
|
|
44
|
+
nodes: TNode[];
|
|
45
|
+
/**
|
|
46
|
+
* Edges created by this plugin, including edges to/from core nodes.
|
|
47
|
+
* Also includes edges that cross the plugin boundary (e.g. source → plugin node).
|
|
48
|
+
*/
|
|
49
|
+
edges: TEdge[];
|
|
50
|
+
/**
|
|
51
|
+
* Nodes this plugin exposes for cross-plugin edge resolution.
|
|
52
|
+
* The graph-builder inspects their output port types and routes them into
|
|
53
|
+
* the relevant core lookup maps automatically:
|
|
54
|
+
* - ports of type audio/stereo → merged into the audio-mix lookup
|
|
55
|
+
* - ports of type video/composition → merged into the compositor lookup
|
|
56
|
+
* This allows outputs, RTT sources, and encoders to connect to plugin nodes
|
|
57
|
+
* without any hardcoded knowledge of the plugin.
|
|
58
|
+
*/
|
|
59
|
+
supplementalNodes: Map<string, TNode>;
|
|
60
|
+
}
|
|
61
|
+
export interface ISesameEditorPlugin<TNode = unknown, TEdge = unknown> {
|
|
62
|
+
/** Unique reverse-domain ID, e.g. "com.snigel". */
|
|
63
|
+
id: string;
|
|
64
|
+
/** Node type definitions to register in the graph editor's node registry. */
|
|
65
|
+
nodeTypes: NodeTypeDef[];
|
|
66
|
+
/**
|
|
67
|
+
* Build React Flow nodes and edges from this plugin's portion of the config.
|
|
68
|
+
* Called after all core nodes have been created, before cross-plugin edges
|
|
69
|
+
* are wired.
|
|
70
|
+
*/
|
|
71
|
+
buildNodes(extensions: Record<string, unknown>, config: ISesameConfig, factory: NodeFactory<TNode, TEdge>, coreNodes: CoreNodeMaps<TNode>): PluginBuildResult<TNode, TEdge>;
|
|
72
|
+
/**
|
|
73
|
+
* Serialize plugin-specific graph nodes back to a flat record that is stored
|
|
74
|
+
* under `config.extensions` keyed by this plugin's namespace.
|
|
75
|
+
*/
|
|
76
|
+
serializeNodes(allNodes: TNode[], allEdges: TEdge[]): Record<string, unknown>;
|
|
77
|
+
}
|
|
78
|
+
export {};
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin interface types for the Sesame config editor.
|
|
3
|
+
*
|
|
4
|
+
* TNode / TEdge are generic so this package does not depend on @xyflow/react.
|
|
5
|
+
* In practice the config-editor and all plugins instantiate them as
|
|
6
|
+
* Node and Edge from @xyflow/react.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@stinkycomputing/sesame-editor-api",
|
|
3
|
+
"version": "1.0.0-alpha.1",
|
|
4
|
+
"description": "Plugin API types for Sesame config editor",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"LICENSE"
|
|
11
|
+
],
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@stinkycomputing/sesame-interop": "^1.5.0-alpha.1"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"typescript": "^5.5.4"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc"
|
|
20
|
+
}
|
|
21
|
+
}
|