@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 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 = 'graph', viewMode: controlledViewMode, onViewModeChange, layoutStrategy: propLayoutStrategy = 'dagre', // Accept from parent
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 === 'graph' && (react_1.default.createElement("div", { className: "flex-1 flex overflow-hidden" },
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 === 'yarn' && (react_1.default.createElement(YarnView_1.YarnView, { dialogue: dialogue, onExport: () => {
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 === 'play' && (react_1.default.createElement(PlayView_1.PlayView, { dialogue: dialogue, flagSchema: flagSchema }))));
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 = 'graph', viewMode: controlledViewMode, onViewModeChange, layoutStrategy: propLayoutStrategy = 'dagre', // Accept from parent
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 === 'graph' && (React.createElement("div", { className: "flex-1 flex overflow-hidden" },
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 === 'yarn' && (React.createElement(YarnView, { dialogue: dialogue, onExport: () => {
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 === 'play' && (React.createElement(PlayView, { dialogue: dialogue, flagSchema: flagSchema }))));
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
  */
@@ -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
  */
@@ -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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magicborn/dialogue-forge",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Visual node-based dialogue editor with Yarn Spinner support",
5
5
  "author": "Ben Garrard <b2gdevs@gmail.com>",
6
6
  "license": "MIT",