@magicborn/dialogue-forge 0.1.4 → 0.1.5
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/README.md +83 -0
- package/dist/components/DialogueEditorV2.js +5 -4
- package/dist/esm/components/DialogueEditorV2.js +5 -4
- package/dist/esm/types/constants.d.ts +9 -0
- package/dist/esm/types/constants.js +8 -0
- package/dist/esm/types/index.d.ts +1 -1
- package/dist/types/constants.d.ts +9 -0
- package/dist/types/constants.js +9 -1
- package/dist/types/index.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -156,6 +156,89 @@ See [DATA_STRUCTURES.md](./DATA_STRUCTURES.md) for complete type documentation a
|
|
|
156
156
|
- `GameFlagState` - Current flag values `{ [flagId]: value }`
|
|
157
157
|
- `DialogueResult` - Result from running dialogue (updated flags, visited nodes)
|
|
158
158
|
|
|
159
|
+
### Dialogue Editor V2 with View Modes
|
|
160
|
+
|
|
161
|
+
`DialogueEditorV2` supports graph, yarn, and play views. Use the exported `VIEW_MODE` constant (instead of string literals) alongside the `ViewMode` type so consumers get auto-complete and type safety when switching views.
|
|
162
|
+
|
|
163
|
+
```tsx
|
|
164
|
+
import { DialogueEditorV2, VIEW_MODE, type DialogueTree, type ViewMode } from '@magicborn/dialogue-forge';
|
|
165
|
+
import { useState } from 'react';
|
|
166
|
+
|
|
167
|
+
export function DialogueEditorDemo() {
|
|
168
|
+
const [dialogue, setDialogue] = useState<DialogueTree | null>(null);
|
|
169
|
+
const [viewMode, setViewMode] = useState<ViewMode>(VIEW_MODE.GRAPH);
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
<DialogueEditorV2
|
|
173
|
+
dialogue={dialogue}
|
|
174
|
+
onChange={setDialogue}
|
|
175
|
+
viewMode={viewMode}
|
|
176
|
+
onViewModeChange={setViewMode}
|
|
177
|
+
/>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Authoring dialogue data programmatically
|
|
183
|
+
|
|
184
|
+
You can build dialogue content without the editor and still take advantage of the types and constants that power the scene player. This makes it easy to script nonlinear stories, export them, or feed them directly into `ScenePlayer`.
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
import {
|
|
188
|
+
NODE_TYPE,
|
|
189
|
+
type DialogueTree,
|
|
190
|
+
type DialogueNode,
|
|
191
|
+
ScenePlayer,
|
|
192
|
+
} from '@magicborn/dialogue-forge';
|
|
193
|
+
|
|
194
|
+
const nodes: Record<string, DialogueNode> = {
|
|
195
|
+
npc_1: {
|
|
196
|
+
id: 'npc_1',
|
|
197
|
+
type: NODE_TYPE.NPC,
|
|
198
|
+
speaker: 'Merchant',
|
|
199
|
+
content: 'Welcome, traveler! Looking for supplies?',
|
|
200
|
+
nextNodeId: 'player_1',
|
|
201
|
+
x: 0,
|
|
202
|
+
y: 0,
|
|
203
|
+
},
|
|
204
|
+
player_1: {
|
|
205
|
+
id: 'player_1',
|
|
206
|
+
type: NODE_TYPE.PLAYER,
|
|
207
|
+
content: ' ',
|
|
208
|
+
choices: [
|
|
209
|
+
{ id: 'buy', text: 'Show me your wares.', nextNodeId: 'npc_2' },
|
|
210
|
+
{ id: 'chat', text: 'Any rumors?', nextNodeId: 'npc_3' },
|
|
211
|
+
],
|
|
212
|
+
x: 320,
|
|
213
|
+
y: 0,
|
|
214
|
+
},
|
|
215
|
+
npc_2: {
|
|
216
|
+
id: 'npc_2',
|
|
217
|
+
type: NODE_TYPE.NPC,
|
|
218
|
+
content: 'Take a look! Fresh stock just arrived.',
|
|
219
|
+
x: 640,
|
|
220
|
+
y: -120,
|
|
221
|
+
},
|
|
222
|
+
npc_3: {
|
|
223
|
+
id: 'npc_3',
|
|
224
|
+
type: NODE_TYPE.NPC,
|
|
225
|
+
content: 'Bandits have been spotted near the bridge—stay sharp.',
|
|
226
|
+
x: 640,
|
|
227
|
+
y: 120,
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const merchantIntro: DialogueTree = {
|
|
232
|
+
id: 'merchant_intro',
|
|
233
|
+
title: 'Merchant Greeting',
|
|
234
|
+
startNodeId: 'npc_1',
|
|
235
|
+
nodes,
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// Render or test the dialogue without the editor
|
|
239
|
+
// <ScenePlayer dialogue={merchantIntro} gameState={yourGameState} onComplete={...} />
|
|
240
|
+
```
|
|
241
|
+
|
|
159
242
|
## Complete Example
|
|
160
243
|
|
|
161
244
|
```typescript
|
|
@@ -58,6 +58,7 @@ const PlayerNodeV2_1 = require("./PlayerNodeV2");
|
|
|
58
58
|
const ConditionalNodeV2_1 = require("./ConditionalNodeV2");
|
|
59
59
|
const ChoiceEdgeV2_1 = require("./ChoiceEdgeV2");
|
|
60
60
|
const NPCEdgeV2_1 = require("./NPCEdgeV2");
|
|
61
|
+
const constants_1 = require("../types/constants");
|
|
61
62
|
// Define node and edge types outside component for stability
|
|
62
63
|
const nodeTypes = {
|
|
63
64
|
npc: NPCNodeV2_1.NPCNodeV2,
|
|
@@ -68,7 +69,7 @@ const edgeTypes = {
|
|
|
68
69
|
choice: ChoiceEdgeV2_1.ChoiceEdgeV2,
|
|
69
70
|
default: NPCEdgeV2_1.NPCEdgeV2, // Use custom component for NPC edges instead of React Flow default
|
|
70
71
|
};
|
|
71
|
-
function DialogueEditorV2Internal({ dialogue, onChange, onExportYarn, onExportJSON, className = '', showTitleEditor = true, flagSchema, characters = {}, initialViewMode =
|
|
72
|
+
function DialogueEditorV2Internal({ dialogue, onChange, onExportYarn, onExportJSON, className = '', showTitleEditor = true, flagSchema, characters = {}, initialViewMode = constants_1.VIEW_MODE.GRAPH, viewMode: controlledViewMode, onViewModeChange, layoutStrategy: propLayoutStrategy = 'dagre', // Accept from parent
|
|
72
73
|
onLayoutStrategyChange, onOpenFlagManager, onOpenGuide, onLoadExampleDialogue, onLoadExampleFlags,
|
|
73
74
|
// Event hooks
|
|
74
75
|
onNodeAdd, onNodeDelete, onNodeUpdate, onConnect: onConnectHook, onDisconnect, onNodeSelect, onNodeDoubleClick: onNodeDoubleClickHook, }) {
|
|
@@ -898,7 +899,7 @@ onNodeAdd, onNodeDelete, onNodeUpdate, onConnect: onConnectHook, onDisconnect, o
|
|
|
898
899
|
}, 100);
|
|
899
900
|
}, [dialogue, onChange, reactFlowInstance, layoutDirection, layoutStrategy]);
|
|
900
901
|
return (react_1.default.createElement("div", { className: `dialogue-editor-v2 ${className} w-full h-full flex flex-col` },
|
|
901
|
-
viewMode ===
|
|
902
|
+
viewMode === constants_1.VIEW_MODE.GRAPH && (react_1.default.createElement("div", { className: "flex-1 flex overflow-hidden" },
|
|
902
903
|
react_1.default.createElement("div", { className: "flex-1 relative w-full h-full", ref: reactFlowWrapperRef, style: { minHeight: 0 } },
|
|
903
904
|
react_1.default.createElement(reactflow_1.default, { nodes: nodesWithFlags, edges: edges.map(edge => {
|
|
904
905
|
// Detect back-edges (loops) based on layout direction
|
|
@@ -1174,7 +1175,7 @@ onNodeAdd, onNodeDelete, onNodeUpdate, onConnect: onConnectHook, onDisconnect, o
|
|
|
1174
1175
|
}, 0);
|
|
1175
1176
|
}
|
|
1176
1177
|
}, onDelete: () => handleDeleteNode(selectedNode.id), onAddChoice: () => handleAddChoice(selectedNode.id), onUpdateChoice: (idx, updates) => handleUpdateChoice(selectedNode.id, idx, updates), onRemoveChoice: (idx) => handleRemoveChoice(selectedNode.id, idx), onClose: () => setSelectedNodeId(null), flagSchema: flagSchema })))),
|
|
1177
|
-
viewMode ===
|
|
1178
|
+
viewMode === constants_1.VIEW_MODE.YARN && (react_1.default.createElement(YarnView_1.YarnView, { dialogue: dialogue, onExport: () => {
|
|
1178
1179
|
const yarn = (0, yarn_converter_1.exportToYarn)(dialogue);
|
|
1179
1180
|
if (onExportYarn) {
|
|
1180
1181
|
onExportYarn(yarn);
|
|
@@ -1199,7 +1200,7 @@ onNodeAdd, onNodeDelete, onNodeUpdate, onConnect: onConnectHook, onDisconnect, o
|
|
|
1199
1200
|
alert('Failed to import Yarn file. Please check the format.');
|
|
1200
1201
|
}
|
|
1201
1202
|
} })),
|
|
1202
|
-
viewMode ===
|
|
1203
|
+
viewMode === constants_1.VIEW_MODE.PLAY && (react_1.default.createElement(PlayView_1.PlayView, { dialogue: dialogue, flagSchema: flagSchema }))));
|
|
1203
1204
|
}
|
|
1204
1205
|
function DialogueEditorV2(props) {
|
|
1205
1206
|
return (react_1.default.createElement(reactflow_1.ReactFlowProvider, null,
|
|
@@ -22,6 +22,7 @@ import { PlayerNodeV2 } from './PlayerNodeV2';
|
|
|
22
22
|
import { ConditionalNodeV2 } from './ConditionalNodeV2';
|
|
23
23
|
import { ChoiceEdgeV2 } from './ChoiceEdgeV2';
|
|
24
24
|
import { NPCEdgeV2 } from './NPCEdgeV2';
|
|
25
|
+
import { VIEW_MODE } from '../types/constants';
|
|
25
26
|
// Define node and edge types outside component for stability
|
|
26
27
|
const nodeTypes = {
|
|
27
28
|
npc: NPCNodeV2,
|
|
@@ -32,7 +33,7 @@ const edgeTypes = {
|
|
|
32
33
|
choice: ChoiceEdgeV2,
|
|
33
34
|
default: NPCEdgeV2, // Use custom component for NPC edges instead of React Flow default
|
|
34
35
|
};
|
|
35
|
-
function DialogueEditorV2Internal({ dialogue, onChange, onExportYarn, onExportJSON, className = '', showTitleEditor = true, flagSchema, characters = {}, initialViewMode =
|
|
36
|
+
function DialogueEditorV2Internal({ dialogue, onChange, onExportYarn, onExportJSON, className = '', showTitleEditor = true, flagSchema, characters = {}, initialViewMode = VIEW_MODE.GRAPH, viewMode: controlledViewMode, onViewModeChange, layoutStrategy: propLayoutStrategy = 'dagre', // Accept from parent
|
|
36
37
|
onLayoutStrategyChange, onOpenFlagManager, onOpenGuide, onLoadExampleDialogue, onLoadExampleFlags,
|
|
37
38
|
// Event hooks
|
|
38
39
|
onNodeAdd, onNodeDelete, onNodeUpdate, onConnect: onConnectHook, onDisconnect, onNodeSelect, onNodeDoubleClick: onNodeDoubleClickHook, }) {
|
|
@@ -862,7 +863,7 @@ onNodeAdd, onNodeDelete, onNodeUpdate, onConnect: onConnectHook, onDisconnect, o
|
|
|
862
863
|
}, 100);
|
|
863
864
|
}, [dialogue, onChange, reactFlowInstance, layoutDirection, layoutStrategy]);
|
|
864
865
|
return (React.createElement("div", { className: `dialogue-editor-v2 ${className} w-full h-full flex flex-col` },
|
|
865
|
-
viewMode ===
|
|
866
|
+
viewMode === VIEW_MODE.GRAPH && (React.createElement("div", { className: "flex-1 flex overflow-hidden" },
|
|
866
867
|
React.createElement("div", { className: "flex-1 relative w-full h-full", ref: reactFlowWrapperRef, style: { minHeight: 0 } },
|
|
867
868
|
React.createElement(ReactFlow, { nodes: nodesWithFlags, edges: edges.map(edge => {
|
|
868
869
|
// Detect back-edges (loops) based on layout direction
|
|
@@ -1138,7 +1139,7 @@ onNodeAdd, onNodeDelete, onNodeUpdate, onConnect: onConnectHook, onDisconnect, o
|
|
|
1138
1139
|
}, 0);
|
|
1139
1140
|
}
|
|
1140
1141
|
}, onDelete: () => handleDeleteNode(selectedNode.id), onAddChoice: () => handleAddChoice(selectedNode.id), onUpdateChoice: (idx, updates) => handleUpdateChoice(selectedNode.id, idx, updates), onRemoveChoice: (idx) => handleRemoveChoice(selectedNode.id, idx), onClose: () => setSelectedNodeId(null), flagSchema: flagSchema })))),
|
|
1141
|
-
viewMode ===
|
|
1142
|
+
viewMode === VIEW_MODE.YARN && (React.createElement(YarnView, { dialogue: dialogue, onExport: () => {
|
|
1142
1143
|
const yarn = exportToYarn(dialogue);
|
|
1143
1144
|
if (onExportYarn) {
|
|
1144
1145
|
onExportYarn(yarn);
|
|
@@ -1163,7 +1164,7 @@ onNodeAdd, onNodeDelete, onNodeUpdate, onConnect: onConnectHook, onDisconnect, o
|
|
|
1163
1164
|
alert('Failed to import Yarn file. Please check the format.');
|
|
1164
1165
|
}
|
|
1165
1166
|
} })),
|
|
1166
|
-
viewMode ===
|
|
1167
|
+
viewMode === VIEW_MODE.PLAY && (React.createElement(PlayView, { dialogue: dialogue, flagSchema: flagSchema }))));
|
|
1167
1168
|
}
|
|
1168
1169
|
export function DialogueEditorV2(props) {
|
|
1169
1170
|
return (React.createElement(ReactFlowProvider, null,
|
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
* Type-safe constants for Dialogue Forge
|
|
3
3
|
* Use these instead of string literals for better type safety and IDE support
|
|
4
4
|
*/
|
|
5
|
+
/**
|
|
6
|
+
* View modes for DialogueEditorV2
|
|
7
|
+
*/
|
|
8
|
+
export declare const VIEW_MODE: {
|
|
9
|
+
readonly GRAPH: "graph";
|
|
10
|
+
readonly YARN: "yarn";
|
|
11
|
+
readonly PLAY: "play";
|
|
12
|
+
};
|
|
13
|
+
export type ViewMode = typeof VIEW_MODE[keyof typeof VIEW_MODE];
|
|
5
14
|
/**
|
|
6
15
|
* Node types in a dialogue tree
|
|
7
16
|
*/
|
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
* Type-safe constants for Dialogue Forge
|
|
3
3
|
* Use these instead of string literals for better type safety and IDE support
|
|
4
4
|
*/
|
|
5
|
+
/**
|
|
6
|
+
* View modes for DialogueEditorV2
|
|
7
|
+
*/
|
|
8
|
+
export const VIEW_MODE = {
|
|
9
|
+
GRAPH: 'graph',
|
|
10
|
+
YARN: 'yarn',
|
|
11
|
+
PLAY: 'play',
|
|
12
|
+
};
|
|
5
13
|
/**
|
|
6
14
|
* Node types in a dialogue tree
|
|
7
15
|
*/
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ConditionOperator, NodeType } from './constants';
|
|
2
|
+
export type { ViewMode } from './constants';
|
|
2
3
|
export interface Choice {
|
|
3
4
|
id: string;
|
|
4
5
|
text: string;
|
|
@@ -43,7 +44,6 @@ export interface DialogueTree {
|
|
|
43
44
|
nodes: Record<string, DialogueNode>;
|
|
44
45
|
}
|
|
45
46
|
import { FlagSchema } from './flags';
|
|
46
|
-
export type ViewMode = 'graph' | 'yarn' | 'play';
|
|
47
47
|
export interface DialogueEditorProps {
|
|
48
48
|
dialogue: DialogueTree | null;
|
|
49
49
|
onChange: (dialogue: DialogueTree) => void;
|
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
* Type-safe constants for Dialogue Forge
|
|
3
3
|
* Use these instead of string literals for better type safety and IDE support
|
|
4
4
|
*/
|
|
5
|
+
/**
|
|
6
|
+
* View modes for DialogueEditorV2
|
|
7
|
+
*/
|
|
8
|
+
export declare const VIEW_MODE: {
|
|
9
|
+
readonly GRAPH: "graph";
|
|
10
|
+
readonly YARN: "yarn";
|
|
11
|
+
readonly PLAY: "play";
|
|
12
|
+
};
|
|
13
|
+
export type ViewMode = typeof VIEW_MODE[keyof typeof VIEW_MODE];
|
|
5
14
|
/**
|
|
6
15
|
* Node types in a dialogue tree
|
|
7
16
|
*/
|
package/dist/types/constants.js
CHANGED
|
@@ -4,7 +4,15 @@
|
|
|
4
4
|
* Use these instead of string literals for better type safety and IDE support
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.QUEST_STATE = exports.FLAG_VALUE_TYPE = exports.CONDITION_OPERATOR = exports.FLAG_TYPE = exports.NODE_TYPE = void 0;
|
|
7
|
+
exports.QUEST_STATE = exports.FLAG_VALUE_TYPE = exports.CONDITION_OPERATOR = exports.FLAG_TYPE = exports.NODE_TYPE = exports.VIEW_MODE = void 0;
|
|
8
|
+
/**
|
|
9
|
+
* View modes for DialogueEditorV2
|
|
10
|
+
*/
|
|
11
|
+
exports.VIEW_MODE = {
|
|
12
|
+
GRAPH: 'graph',
|
|
13
|
+
YARN: 'yarn',
|
|
14
|
+
PLAY: 'play',
|
|
15
|
+
};
|
|
8
16
|
/**
|
|
9
17
|
* Node types in a dialogue tree
|
|
10
18
|
*/
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ConditionOperator, NodeType } from './constants';
|
|
2
|
+
export type { ViewMode } from './constants';
|
|
2
3
|
export interface Choice {
|
|
3
4
|
id: string;
|
|
4
5
|
text: string;
|
|
@@ -43,7 +44,6 @@ export interface DialogueTree {
|
|
|
43
44
|
nodes: Record<string, DialogueNode>;
|
|
44
45
|
}
|
|
45
46
|
import { FlagSchema } from './flags';
|
|
46
|
-
export type ViewMode = 'graph' | 'yarn' | 'play';
|
|
47
47
|
export interface DialogueEditorProps {
|
|
48
48
|
dialogue: DialogueTree | null;
|
|
49
49
|
onChange: (dialogue: DialogueTree) => void;
|