@magicborn/dialogue-forge 0.1.0
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 +233 -0
- package/bin/dialogue-forge.js +78 -0
- package/demo/app/layout.tsx +36 -0
- package/demo/app/page.tsx +440 -0
- package/demo/components/ThemeSwitcher.tsx +611 -0
- package/demo/next.config.mjs +7 -0
- package/demo/package.json +29 -0
- package/demo/postcss.config.mjs +7 -0
- package/demo/public/logo.svg +1 -0
- package/demo/styles/globals.css +19 -0
- package/demo/tailwind.config.ts +90 -0
- package/demo/tsconfig.json +42 -0
- package/dist/components/ChoiceEdgeV2.d.ts +3 -0
- package/dist/components/ChoiceEdgeV2.js +103 -0
- package/dist/components/CodeBlock.d.ts +8 -0
- package/dist/components/CodeBlock.js +24 -0
- package/dist/components/ConditionAutocomplete.d.ts +14 -0
- package/dist/components/ConditionAutocomplete.js +284 -0
- package/dist/components/ConditionalNodeV2.d.ts +16 -0
- package/dist/components/ConditionalNodeV2.js +147 -0
- package/dist/components/DialogueEditorV2.d.ts +22 -0
- package/dist/components/DialogueEditorV2.js +1170 -0
- package/dist/components/EdgeIcon.d.ts +8 -0
- package/dist/components/EdgeIcon.js +13 -0
- package/dist/components/ExampleLoader.d.ts +11 -0
- package/dist/components/ExampleLoader.js +52 -0
- package/dist/components/ExampleLoaderButton.d.ts +15 -0
- package/dist/components/ExampleLoaderButton.js +102 -0
- package/dist/components/FlagManager.d.ts +11 -0
- package/dist/components/FlagManager.js +282 -0
- package/dist/components/FlagSelector.d.ts +11 -0
- package/dist/components/FlagSelector.js +235 -0
- package/dist/components/GuidePanel.d.ts +7 -0
- package/dist/components/GuidePanel.js +1176 -0
- package/dist/components/Minimap.d.ts +16 -0
- package/dist/components/Minimap.js +93 -0
- package/dist/components/NPCEdgeV2.d.ts +3 -0
- package/dist/components/NPCEdgeV2.js +104 -0
- package/dist/components/NPCNodeV2.d.ts +26 -0
- package/dist/components/NPCNodeV2.js +86 -0
- package/dist/components/NodeEditor.d.ts +18 -0
- package/dist/components/NodeEditor.js +1025 -0
- package/dist/components/PlayView.d.ts +12 -0
- package/dist/components/PlayView.js +307 -0
- package/dist/components/PlayerNodeV2.d.ts +16 -0
- package/dist/components/PlayerNodeV2.js +139 -0
- package/dist/components/ReactFlowPOC.d.ts +61 -0
- package/dist/components/ReactFlowPOC.js +312 -0
- package/dist/components/ScenePlayer.d.ts +18 -0
- package/dist/components/ScenePlayer.js +196 -0
- package/dist/components/YarnView.d.ts +9 -0
- package/dist/components/YarnView.js +45 -0
- package/dist/components/ZoomControls.d.ts +11 -0
- package/dist/components/ZoomControls.js +34 -0
- package/dist/esm/components/ChoiceEdgeV2.d.ts +3 -0
- package/dist/esm/components/ChoiceEdgeV2.js +67 -0
- package/dist/esm/components/CodeBlock.d.ts +8 -0
- package/dist/esm/components/CodeBlock.js +18 -0
- package/dist/esm/components/ConditionAutocomplete.d.ts +14 -0
- package/dist/esm/components/ConditionAutocomplete.js +248 -0
- package/dist/esm/components/ConditionalNodeV2.d.ts +16 -0
- package/dist/esm/components/ConditionalNodeV2.js +111 -0
- package/dist/esm/components/DialogueEditorV2.d.ts +22 -0
- package/dist/esm/components/DialogueEditorV2.js +1134 -0
- package/dist/esm/components/EdgeIcon.d.ts +8 -0
- package/dist/esm/components/EdgeIcon.js +7 -0
- package/dist/esm/components/ExampleLoader.d.ts +11 -0
- package/dist/esm/components/ExampleLoader.js +46 -0
- package/dist/esm/components/ExampleLoaderButton.d.ts +15 -0
- package/dist/esm/components/ExampleLoaderButton.js +66 -0
- package/dist/esm/components/FlagManager.d.ts +11 -0
- package/dist/esm/components/FlagManager.js +246 -0
- package/dist/esm/components/FlagSelector.d.ts +11 -0
- package/dist/esm/components/FlagSelector.js +199 -0
- package/dist/esm/components/GuidePanel.d.ts +7 -0
- package/dist/esm/components/GuidePanel.js +1140 -0
- package/dist/esm/components/Minimap.d.ts +16 -0
- package/dist/esm/components/Minimap.js +57 -0
- package/dist/esm/components/NPCEdgeV2.d.ts +3 -0
- package/dist/esm/components/NPCEdgeV2.js +68 -0
- package/dist/esm/components/NPCNodeV2.d.ts +26 -0
- package/dist/esm/components/NPCNodeV2.js +80 -0
- package/dist/esm/components/NodeEditor.d.ts +18 -0
- package/dist/esm/components/NodeEditor.js +989 -0
- package/dist/esm/components/PlayView.d.ts +12 -0
- package/dist/esm/components/PlayView.js +271 -0
- package/dist/esm/components/PlayerNodeV2.d.ts +16 -0
- package/dist/esm/components/PlayerNodeV2.js +103 -0
- package/dist/esm/components/ReactFlowPOC.d.ts +61 -0
- package/dist/esm/components/ReactFlowPOC.js +275 -0
- package/dist/esm/components/ScenePlayer.d.ts +18 -0
- package/dist/esm/components/ScenePlayer.js +160 -0
- package/dist/esm/components/YarnView.d.ts +9 -0
- package/dist/esm/components/YarnView.js +39 -0
- package/dist/esm/components/ZoomControls.d.ts +11 -0
- package/dist/esm/components/ZoomControls.js +28 -0
- package/dist/esm/examples/example-loader.d.ts +29 -0
- package/dist/esm/examples/example-loader.js +103 -0
- package/dist/esm/examples/examples-registry.d.ts +38 -0
- package/dist/esm/examples/examples-registry.js +153 -0
- package/dist/esm/examples/index.d.ts +26 -0
- package/dist/esm/examples/index.js +50 -0
- package/dist/esm/examples/legacy-examples.d.ts +9 -0
- package/dist/esm/examples/legacy-examples.js +814 -0
- package/dist/esm/examples/yarn-examples.d.ts +35 -0
- package/dist/esm/examples/yarn-examples.js +181 -0
- package/dist/esm/index.d.ts +21 -0
- package/dist/esm/index.js +26 -0
- package/dist/esm/lib/flag-manager.d.ts +21 -0
- package/dist/esm/lib/flag-manager.js +93 -0
- package/dist/esm/lib/yarn-converter/__tests__/round-trip.test.d.ts +1 -0
- package/dist/esm/lib/yarn-converter/__tests__/round-trip.test.js +169 -0
- package/dist/esm/lib/yarn-converter.d.ts +17 -0
- package/dist/esm/lib/yarn-converter.js +521 -0
- package/dist/esm/lib/yarn-runner/__tests__/condition-evaluator.test.d.ts +1 -0
- package/dist/esm/lib/yarn-runner/__tests__/condition-evaluator.test.js +171 -0
- package/dist/esm/lib/yarn-runner/__tests__/node-processor.test.d.ts +1 -0
- package/dist/esm/lib/yarn-runner/__tests__/node-processor.test.js +237 -0
- package/dist/esm/lib/yarn-runner/__tests__/variable-manager.test.d.ts +1 -0
- package/dist/esm/lib/yarn-runner/__tests__/variable-manager.test.js +106 -0
- package/dist/esm/lib/yarn-runner/condition-evaluator.d.ts +12 -0
- package/dist/esm/lib/yarn-runner/condition-evaluator.js +56 -0
- package/dist/esm/lib/yarn-runner/index.d.ts +12 -0
- package/dist/esm/lib/yarn-runner/index.js +11 -0
- package/dist/esm/lib/yarn-runner/node-processor.d.ts +18 -0
- package/dist/esm/lib/yarn-runner/node-processor.js +129 -0
- package/dist/esm/lib/yarn-runner/variable-manager.d.ts +51 -0
- package/dist/esm/lib/yarn-runner/variable-manager.js +120 -0
- package/dist/esm/lib/yarn-runner/variable-operations.d.ts +16 -0
- package/dist/esm/lib/yarn-runner/variable-operations.js +88 -0
- package/dist/esm/types/conditionals.d.ts +29 -0
- package/dist/esm/types/conditionals.js +1 -0
- package/dist/esm/types/constants.d.ts +59 -0
- package/dist/esm/types/constants.js +55 -0
- package/dist/esm/types/flags.d.ts +49 -0
- package/dist/esm/types/flags.js +49 -0
- package/dist/esm/types/game-state.d.ts +62 -0
- package/dist/esm/types/game-state.js +6 -0
- package/dist/esm/types/index.d.ts +77 -0
- package/dist/esm/types/index.js +1 -0
- package/dist/esm/utils/constants.d.ts +5 -0
- package/dist/esm/utils/constants.js +5 -0
- package/dist/esm/utils/feature-flags.d.ts +11 -0
- package/dist/esm/utils/feature-flags.js +11 -0
- package/dist/esm/utils/game-state-flattener.d.ts +41 -0
- package/dist/esm/utils/game-state-flattener.js +135 -0
- package/dist/esm/utils/layout/collision.d.ts +27 -0
- package/dist/esm/utils/layout/collision.js +74 -0
- package/dist/esm/utils/layout/index.d.ts +82 -0
- package/dist/esm/utils/layout/index.js +98 -0
- package/dist/esm/utils/layout/registry.d.ts +91 -0
- package/dist/esm/utils/layout/registry.js +148 -0
- package/dist/esm/utils/layout/strategies/dagre.d.ts +19 -0
- package/dist/esm/utils/layout/strategies/dagre.js +182 -0
- package/dist/esm/utils/layout/strategies/force.d.ts +21 -0
- package/dist/esm/utils/layout/strategies/force.js +178 -0
- package/dist/esm/utils/layout/strategies/grid.d.ts +17 -0
- package/dist/esm/utils/layout/strategies/grid.js +91 -0
- package/dist/esm/utils/layout/strategies/index.d.ts +8 -0
- package/dist/esm/utils/layout/strategies/index.js +8 -0
- package/dist/esm/utils/layout/types.d.ts +100 -0
- package/dist/esm/utils/layout/types.js +7 -0
- package/dist/esm/utils/layout.d.ts +9 -0
- package/dist/esm/utils/layout.js +17 -0
- package/dist/esm/utils/node-helpers.d.ts +7 -0
- package/dist/esm/utils/node-helpers.js +94 -0
- package/dist/esm/utils/reactflow-converter.d.ts +42 -0
- package/dist/esm/utils/reactflow-converter.js +217 -0
- package/dist/examples/example-loader.d.ts +29 -0
- package/dist/examples/example-loader.js +109 -0
- package/dist/examples/examples-registry.d.ts +38 -0
- package/dist/examples/examples-registry.js +160 -0
- package/dist/examples/index.d.ts +26 -0
- package/dist/examples/index.js +63 -0
- package/dist/examples/legacy-examples.d.ts +9 -0
- package/dist/examples/legacy-examples.js +817 -0
- package/dist/examples/yarn-examples.d.ts +35 -0
- package/dist/examples/yarn-examples.js +189 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +66 -0
- package/dist/lib/flag-manager.d.ts +21 -0
- package/dist/lib/flag-manager.js +99 -0
- package/dist/lib/yarn-converter/__tests__/round-trip.test.d.ts +1 -0
- package/dist/lib/yarn-converter/__tests__/round-trip.test.js +171 -0
- package/dist/lib/yarn-converter.d.ts +17 -0
- package/dist/lib/yarn-converter.js +525 -0
- package/dist/lib/yarn-runner/__tests__/condition-evaluator.test.d.ts +1 -0
- package/dist/lib/yarn-runner/__tests__/condition-evaluator.test.js +173 -0
- package/dist/lib/yarn-runner/__tests__/node-processor.test.d.ts +1 -0
- package/dist/lib/yarn-runner/__tests__/node-processor.test.js +239 -0
- package/dist/lib/yarn-runner/__tests__/variable-manager.test.d.ts +1 -0
- package/dist/lib/yarn-runner/__tests__/variable-manager.test.js +108 -0
- package/dist/lib/yarn-runner/condition-evaluator.d.ts +12 -0
- package/dist/lib/yarn-runner/condition-evaluator.js +60 -0
- package/dist/lib/yarn-runner/index.d.ts +12 -0
- package/dist/lib/yarn-runner/index.js +21 -0
- package/dist/lib/yarn-runner/node-processor.d.ts +18 -0
- package/dist/lib/yarn-runner/node-processor.js +133 -0
- package/dist/lib/yarn-runner/variable-manager.d.ts +51 -0
- package/dist/lib/yarn-runner/variable-manager.js +124 -0
- package/dist/lib/yarn-runner/variable-operations.d.ts +16 -0
- package/dist/lib/yarn-runner/variable-operations.js +92 -0
- package/dist/types/conditionals.d.ts +29 -0
- package/dist/types/conditionals.js +2 -0
- package/dist/types/constants.d.ts +59 -0
- package/dist/types/constants.js +58 -0
- package/dist/types/flags.d.ts +49 -0
- package/dist/types/flags.js +52 -0
- package/dist/types/game-state.d.ts +62 -0
- package/dist/types/game-state.js +7 -0
- package/dist/types/index.d.ts +77 -0
- package/dist/types/index.js +2 -0
- package/dist/utils/constants.d.ts +5 -0
- package/dist/utils/constants.js +8 -0
- package/dist/utils/feature-flags.d.ts +11 -0
- package/dist/utils/feature-flags.js +14 -0
- package/dist/utils/game-state-flattener.d.ts +41 -0
- package/dist/utils/game-state-flattener.js +140 -0
- package/dist/utils/layout/collision.d.ts +27 -0
- package/dist/utils/layout/collision.js +77 -0
- package/dist/utils/layout/index.d.ts +82 -0
- package/dist/utils/layout/index.js +109 -0
- package/dist/utils/layout/registry.d.ts +91 -0
- package/dist/utils/layout/registry.js +151 -0
- package/dist/utils/layout/strategies/dagre.d.ts +19 -0
- package/dist/utils/layout/strategies/dagre.js +189 -0
- package/dist/utils/layout/strategies/force.d.ts +21 -0
- package/dist/utils/layout/strategies/force.js +182 -0
- package/dist/utils/layout/strategies/grid.d.ts +17 -0
- package/dist/utils/layout/strategies/grid.js +95 -0
- package/dist/utils/layout/strategies/index.d.ts +8 -0
- package/dist/utils/layout/strategies/index.js +14 -0
- package/dist/utils/layout/types.d.ts +100 -0
- package/dist/utils/layout/types.js +8 -0
- package/dist/utils/layout.d.ts +9 -0
- package/dist/utils/layout.js +25 -0
- package/dist/utils/node-helpers.d.ts +7 -0
- package/dist/utils/node-helpers.js +101 -0
- package/dist/utils/reactflow-converter.d.ts +42 -0
- package/dist/utils/reactflow-converter.js +223 -0
- package/package.json +70 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grid Layout Strategy
|
|
3
|
+
*
|
|
4
|
+
* Simple grid-based layout that arranges nodes in rows and columns.
|
|
5
|
+
* Useful for getting a quick overview of all nodes.
|
|
6
|
+
*/
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Constants
|
|
9
|
+
// ============================================================================
|
|
10
|
+
const NODE_WIDTH = 220;
|
|
11
|
+
const NODE_HEIGHT = 120;
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// Strategy Implementation
|
|
14
|
+
// ============================================================================
|
|
15
|
+
export class GridLayoutStrategy {
|
|
16
|
+
constructor() {
|
|
17
|
+
this.id = 'grid';
|
|
18
|
+
this.name = 'Grid';
|
|
19
|
+
this.description = 'Arranges nodes in a simple grid pattern. Good for viewing all nodes at once.';
|
|
20
|
+
this.defaultOptions = {
|
|
21
|
+
nodeSpacingX: 50,
|
|
22
|
+
nodeSpacingY: 50,
|
|
23
|
+
margin: 50,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
apply(dialogue, options) {
|
|
27
|
+
const startTime = performance.now();
|
|
28
|
+
const opts = { ...this.defaultOptions, ...options };
|
|
29
|
+
const margin = opts.margin || 50;
|
|
30
|
+
const spacingX = opts.nodeSpacingX || 50;
|
|
31
|
+
const spacingY = opts.nodeSpacingY || 50;
|
|
32
|
+
const nodeIds = Object.keys(dialogue.nodes);
|
|
33
|
+
if (nodeIds.length === 0) {
|
|
34
|
+
return this.emptyResult(dialogue, startTime);
|
|
35
|
+
}
|
|
36
|
+
// Calculate grid dimensions
|
|
37
|
+
const cols = Math.ceil(Math.sqrt(nodeIds.length));
|
|
38
|
+
const cellWidth = NODE_WIDTH + spacingX;
|
|
39
|
+
const cellHeight = NODE_HEIGHT + spacingY;
|
|
40
|
+
// Sort nodes: start node first, then by ID
|
|
41
|
+
const sortedIds = [...nodeIds].sort((a, b) => {
|
|
42
|
+
if (a === dialogue.startNodeId)
|
|
43
|
+
return -1;
|
|
44
|
+
if (b === dialogue.startNodeId)
|
|
45
|
+
return 1;
|
|
46
|
+
return a.localeCompare(b);
|
|
47
|
+
});
|
|
48
|
+
// Position nodes in grid
|
|
49
|
+
const updatedNodes = {};
|
|
50
|
+
let maxX = 0;
|
|
51
|
+
let maxY = 0;
|
|
52
|
+
sortedIds.forEach((id, index) => {
|
|
53
|
+
const col = index % cols;
|
|
54
|
+
const row = Math.floor(index / cols);
|
|
55
|
+
const x = margin + col * cellWidth;
|
|
56
|
+
const y = margin + row * cellHeight;
|
|
57
|
+
updatedNodes[id] = { ...dialogue.nodes[id], x, y };
|
|
58
|
+
maxX = Math.max(maxX, x + NODE_WIDTH);
|
|
59
|
+
maxY = Math.max(maxY, y + NODE_HEIGHT);
|
|
60
|
+
});
|
|
61
|
+
const computeTimeMs = performance.now() - startTime;
|
|
62
|
+
return {
|
|
63
|
+
dialogue: { ...dialogue, nodes: updatedNodes },
|
|
64
|
+
metadata: {
|
|
65
|
+
computeTimeMs,
|
|
66
|
+
nodeCount: nodeIds.length,
|
|
67
|
+
bounds: {
|
|
68
|
+
minX: margin,
|
|
69
|
+
minY: margin,
|
|
70
|
+
maxX,
|
|
71
|
+
maxY,
|
|
72
|
+
width: maxX - margin,
|
|
73
|
+
height: maxY - margin,
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
emptyResult(dialogue, startTime) {
|
|
79
|
+
return {
|
|
80
|
+
dialogue,
|
|
81
|
+
metadata: {
|
|
82
|
+
computeTimeMs: performance.now() - startTime,
|
|
83
|
+
nodeCount: 0,
|
|
84
|
+
bounds: { minX: 0, minY: 0, maxX: 0, maxY: 0, width: 0, height: 0 },
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
supports() {
|
|
89
|
+
return true; // Works with any graph
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layout Strategy Types
|
|
3
|
+
*
|
|
4
|
+
* Defines the interface and types for layout algorithms.
|
|
5
|
+
* Implements the Strategy pattern to allow swappable layout algorithms.
|
|
6
|
+
*/
|
|
7
|
+
import { DialogueTree } from '../../types';
|
|
8
|
+
/**
|
|
9
|
+
* Layout direction for hierarchical layouts
|
|
10
|
+
* - TB: Top to Bottom (vertical flow)
|
|
11
|
+
* - LR: Left to Right (horizontal flow)
|
|
12
|
+
*/
|
|
13
|
+
export type LayoutDirection = 'TB' | 'LR';
|
|
14
|
+
/**
|
|
15
|
+
* Configuration options for layout algorithms
|
|
16
|
+
*/
|
|
17
|
+
export interface LayoutOptions {
|
|
18
|
+
/** Direction of flow for hierarchical layouts */
|
|
19
|
+
direction?: LayoutDirection;
|
|
20
|
+
/** Horizontal spacing between nodes */
|
|
21
|
+
nodeSpacingX?: number;
|
|
22
|
+
/** Vertical spacing between nodes */
|
|
23
|
+
nodeSpacingY?: number;
|
|
24
|
+
/** Margin around the entire layout */
|
|
25
|
+
margin?: number;
|
|
26
|
+
/** Whether to animate the transition */
|
|
27
|
+
animate?: boolean;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Result of a layout operation
|
|
31
|
+
*/
|
|
32
|
+
export interface LayoutResult {
|
|
33
|
+
/** The dialogue tree with updated node positions */
|
|
34
|
+
dialogue: DialogueTree;
|
|
35
|
+
/** Metadata about the layout operation */
|
|
36
|
+
metadata: {
|
|
37
|
+
/** Time taken to compute layout in milliseconds */
|
|
38
|
+
computeTimeMs: number;
|
|
39
|
+
/** Number of nodes processed */
|
|
40
|
+
nodeCount: number;
|
|
41
|
+
/** Bounding box of the layout */
|
|
42
|
+
bounds: {
|
|
43
|
+
minX: number;
|
|
44
|
+
minY: number;
|
|
45
|
+
maxX: number;
|
|
46
|
+
maxY: number;
|
|
47
|
+
width: number;
|
|
48
|
+
height: number;
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Layout Strategy Interface
|
|
54
|
+
*
|
|
55
|
+
* All layout algorithms must implement this interface.
|
|
56
|
+
* This enables the Strategy pattern - algorithms can be swapped at runtime.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* class MyCustomLayout implements LayoutStrategy {
|
|
61
|
+
* readonly id = 'my-custom';
|
|
62
|
+
* readonly name = 'My Custom Layout';
|
|
63
|
+
* readonly description = 'A custom layout algorithm';
|
|
64
|
+
*
|
|
65
|
+
* apply(dialogue: DialogueTree, options?: LayoutOptions): LayoutResult {
|
|
66
|
+
* // Your layout logic here
|
|
67
|
+
* }
|
|
68
|
+
* }
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export interface LayoutStrategy {
|
|
72
|
+
/** Unique identifier for the strategy */
|
|
73
|
+
readonly id: string;
|
|
74
|
+
/** Human-readable name */
|
|
75
|
+
readonly name: string;
|
|
76
|
+
/** Description of what this layout does */
|
|
77
|
+
readonly description: string;
|
|
78
|
+
/** Default options for this strategy */
|
|
79
|
+
readonly defaultOptions?: Partial<LayoutOptions>;
|
|
80
|
+
/**
|
|
81
|
+
* Apply the layout algorithm to a dialogue tree
|
|
82
|
+
* @param dialogue - The dialogue tree to layout
|
|
83
|
+
* @param options - Optional configuration
|
|
84
|
+
* @returns The layout result with updated positions
|
|
85
|
+
*/
|
|
86
|
+
apply(dialogue: DialogueTree, options?: LayoutOptions): LayoutResult;
|
|
87
|
+
/**
|
|
88
|
+
* Check if this strategy supports the given dialogue
|
|
89
|
+
* Some strategies may not work well with certain graph structures
|
|
90
|
+
*/
|
|
91
|
+
supports?(dialogue: DialogueTree): boolean;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Registry entry for a layout strategy
|
|
95
|
+
*/
|
|
96
|
+
export interface LayoutStrategyEntry {
|
|
97
|
+
strategy: LayoutStrategy;
|
|
98
|
+
/** Whether this is the default strategy */
|
|
99
|
+
isDefault?: boolean;
|
|
100
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layout Utilities - Re-export from modular layout system
|
|
3
|
+
*
|
|
4
|
+
* This file provides backward compatibility.
|
|
5
|
+
* For new code, import directly from './layout/index.ts'
|
|
6
|
+
*
|
|
7
|
+
* @see LAYOUT_STRATEGIES.md for documentation
|
|
8
|
+
*/
|
|
9
|
+
export { applyLayout, listLayouts, applyDagreLayout, applyHierarchicalLayout, resolveNodeCollisions, type LayoutStrategy, type LayoutOptions, type LayoutResult, type LayoutDirection, layoutRegistry, DagreLayoutStrategy, ForceLayoutStrategy, GridLayoutStrategy, } from './layout/index';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layout Utilities - Re-export from modular layout system
|
|
3
|
+
*
|
|
4
|
+
* This file provides backward compatibility.
|
|
5
|
+
* For new code, import directly from './layout/index.ts'
|
|
6
|
+
*
|
|
7
|
+
* @see LAYOUT_STRATEGIES.md for documentation
|
|
8
|
+
*/
|
|
9
|
+
export {
|
|
10
|
+
// Main functions
|
|
11
|
+
applyLayout, listLayouts,
|
|
12
|
+
// Backward compatibility
|
|
13
|
+
applyDagreLayout, applyHierarchicalLayout, resolveNodeCollisions,
|
|
14
|
+
// Registry
|
|
15
|
+
layoutRegistry,
|
|
16
|
+
// Strategies (for extension)
|
|
17
|
+
DagreLayoutStrategy, ForceLayoutStrategy, GridLayoutStrategy, } from './layout/index';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { DialogueNode, DialogueTree, Choice } from '../types';
|
|
2
|
+
import { NodeType } from '../types/constants';
|
|
3
|
+
export declare function createNode(type: NodeType, id: string, x: number, y: number): DialogueNode;
|
|
4
|
+
export declare function addChoiceToNode(node: DialogueNode): DialogueNode;
|
|
5
|
+
export declare function removeChoiceFromNode(node: DialogueNode, choiceIdx: number): DialogueNode;
|
|
6
|
+
export declare function updateChoiceInNode(node: DialogueNode, choiceIdx: number, updates: Partial<Choice>): DialogueNode;
|
|
7
|
+
export declare function deleteNodeFromTree(tree: DialogueTree, nodeId: string): DialogueTree;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { NODE_TYPE } from '../types/constants';
|
|
2
|
+
export function createNode(type, id, x, y) {
|
|
3
|
+
if (type === NODE_TYPE.NPC) {
|
|
4
|
+
return {
|
|
5
|
+
id,
|
|
6
|
+
type,
|
|
7
|
+
content: 'New dialogue...',
|
|
8
|
+
speaker: 'Character',
|
|
9
|
+
x,
|
|
10
|
+
y
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
else if (type === NODE_TYPE.PLAYER) {
|
|
14
|
+
return {
|
|
15
|
+
id,
|
|
16
|
+
type,
|
|
17
|
+
content: '',
|
|
18
|
+
choices: [{ id: `c_${Date.now()}`, text: 'Choice 1', nextNodeId: '' }],
|
|
19
|
+
x,
|
|
20
|
+
y
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
else if (type === NODE_TYPE.CONDITIONAL) {
|
|
24
|
+
return {
|
|
25
|
+
id,
|
|
26
|
+
type,
|
|
27
|
+
content: '',
|
|
28
|
+
conditionalBlocks: [{
|
|
29
|
+
id: `block_${Date.now()}`,
|
|
30
|
+
type: 'if',
|
|
31
|
+
condition: [],
|
|
32
|
+
content: 'New dialogue...',
|
|
33
|
+
speaker: undefined
|
|
34
|
+
}],
|
|
35
|
+
x,
|
|
36
|
+
y
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
// Fallback
|
|
40
|
+
return {
|
|
41
|
+
id,
|
|
42
|
+
type,
|
|
43
|
+
content: '',
|
|
44
|
+
x,
|
|
45
|
+
y
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export function addChoiceToNode(node) {
|
|
49
|
+
if (node.type !== NODE_TYPE.PLAYER)
|
|
50
|
+
return node;
|
|
51
|
+
const newChoice = {
|
|
52
|
+
id: `c_${Date.now()}`,
|
|
53
|
+
text: 'New choice...',
|
|
54
|
+
nextNodeId: ''
|
|
55
|
+
};
|
|
56
|
+
return {
|
|
57
|
+
...node,
|
|
58
|
+
choices: [...(node.choices || []), newChoice]
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export function removeChoiceFromNode(node, choiceIdx) {
|
|
62
|
+
if (node.type !== NODE_TYPE.PLAYER || !node.choices)
|
|
63
|
+
return node;
|
|
64
|
+
return {
|
|
65
|
+
...node,
|
|
66
|
+
choices: node.choices.filter((_, i) => i !== choiceIdx)
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export function updateChoiceInNode(node, choiceIdx, updates) {
|
|
70
|
+
if (node.type !== NODE_TYPE.PLAYER || !node.choices)
|
|
71
|
+
return node;
|
|
72
|
+
const newChoices = [...node.choices];
|
|
73
|
+
newChoices[choiceIdx] = { ...newChoices[choiceIdx], ...updates };
|
|
74
|
+
return { ...node, choices: newChoices };
|
|
75
|
+
}
|
|
76
|
+
export function deleteNodeFromTree(tree, nodeId) {
|
|
77
|
+
if (nodeId === tree.startNodeId) {
|
|
78
|
+
throw new Error("Cannot delete the start node");
|
|
79
|
+
}
|
|
80
|
+
const { [nodeId]: _, ...rest } = tree.nodes;
|
|
81
|
+
// Remove references to deleted node
|
|
82
|
+
Object.keys(rest).forEach(id => {
|
|
83
|
+
if (rest[id].nextNodeId === nodeId) {
|
|
84
|
+
rest[id] = { ...rest[id], nextNodeId: undefined };
|
|
85
|
+
}
|
|
86
|
+
if (rest[id].choices) {
|
|
87
|
+
rest[id] = {
|
|
88
|
+
...rest[id],
|
|
89
|
+
choices: rest[id].choices.map(c => c.nextNodeId === nodeId ? { ...c, nextNodeId: '' } : c)
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
return { ...tree, nodes: rest };
|
|
94
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React Flow Converter Utilities
|
|
3
|
+
*
|
|
4
|
+
* Converts between DialogueTree format and React Flow format.
|
|
5
|
+
*
|
|
6
|
+
* React Flow Format:
|
|
7
|
+
* - nodes: Array of { id, type, position: {x, y}, data }
|
|
8
|
+
* - edges: Array of { id, source, target, sourceHandle?, targetHandle?, type, data? }
|
|
9
|
+
*/
|
|
10
|
+
import { DialogueTree, DialogueNode } from '../types';
|
|
11
|
+
import { Node, Edge, Position } from 'reactflow';
|
|
12
|
+
import { LayoutDirection } from './layout';
|
|
13
|
+
export declare const CHOICE_COLORS: string[];
|
|
14
|
+
export interface ReactFlowNodeData {
|
|
15
|
+
node: DialogueNode;
|
|
16
|
+
flagSchema?: any;
|
|
17
|
+
layoutDirection?: LayoutDirection;
|
|
18
|
+
}
|
|
19
|
+
export type ReactFlowNode = Node<ReactFlowNodeData>;
|
|
20
|
+
export type ReactFlowEdge = Edge;
|
|
21
|
+
/**
|
|
22
|
+
* Get handle positions based on layout direction
|
|
23
|
+
* For horizontal (LR): source on right, target on left
|
|
24
|
+
* For vertical (TB): source on bottom, target on top
|
|
25
|
+
*/
|
|
26
|
+
export declare function getHandlePositions(direction: LayoutDirection): {
|
|
27
|
+
sourcePosition: Position;
|
|
28
|
+
targetPosition: Position;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Convert DialogueTree to React Flow format
|
|
32
|
+
*/
|
|
33
|
+
export declare function convertDialogueTreeToReactFlow(dialogue: DialogueTree, layoutDirection?: LayoutDirection): {
|
|
34
|
+
nodes: ReactFlowNode[];
|
|
35
|
+
edges: ReactFlowEdge[];
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Convert React Flow format back to DialogueTree
|
|
39
|
+
*
|
|
40
|
+
* This updates node positions and edge connections in the DialogueTree.
|
|
41
|
+
*/
|
|
42
|
+
export declare function updateDialogueTreeFromReactFlow(dialogue: DialogueTree, nodes: Node[], edges: Edge[]): DialogueTree;
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React Flow Converter Utilities
|
|
3
|
+
*
|
|
4
|
+
* Converts between DialogueTree format and React Flow format.
|
|
5
|
+
*
|
|
6
|
+
* React Flow Format:
|
|
7
|
+
* - nodes: Array of { id, type, position: {x, y}, data }
|
|
8
|
+
* - edges: Array of { id, source, target, sourceHandle?, targetHandle?, type, data? }
|
|
9
|
+
*/
|
|
10
|
+
import { NODE_TYPE } from '../types/constants';
|
|
11
|
+
import { Position } from 'reactflow';
|
|
12
|
+
// Color scheme for choice edges (same as current implementation)
|
|
13
|
+
export const CHOICE_COLORS = ['#e94560', '#8b5cf6', '#06b6d4', '#22c55e', '#f59e0b'];
|
|
14
|
+
/**
|
|
15
|
+
* Get handle positions based on layout direction
|
|
16
|
+
* For horizontal (LR): source on right, target on left
|
|
17
|
+
* For vertical (TB): source on bottom, target on top
|
|
18
|
+
*/
|
|
19
|
+
export function getHandlePositions(direction) {
|
|
20
|
+
if (direction === 'LR') {
|
|
21
|
+
return { sourcePosition: Position.Right, targetPosition: Position.Left };
|
|
22
|
+
}
|
|
23
|
+
return { sourcePosition: Position.Bottom, targetPosition: Position.Top };
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Convert DialogueTree to React Flow format
|
|
27
|
+
*/
|
|
28
|
+
export function convertDialogueTreeToReactFlow(dialogue, layoutDirection = 'TB') {
|
|
29
|
+
const nodes = [];
|
|
30
|
+
const edges = [];
|
|
31
|
+
const { sourcePosition, targetPosition } = getHandlePositions(layoutDirection);
|
|
32
|
+
// Convert nodes - create new node objects to avoid sharing references
|
|
33
|
+
Object.values(dialogue.nodes).forEach(node => {
|
|
34
|
+
// Deep copy node to avoid sharing references, especially for arrays
|
|
35
|
+
const nodeCopy = {
|
|
36
|
+
...node,
|
|
37
|
+
choices: node.choices ? node.choices.map(choice => ({ ...choice })) : undefined,
|
|
38
|
+
setFlags: node.setFlags ? [...node.setFlags] : undefined,
|
|
39
|
+
conditionalBlocks: node.conditionalBlocks ? node.conditionalBlocks.map(block => ({
|
|
40
|
+
...block,
|
|
41
|
+
condition: block.condition ? [...block.condition] : undefined,
|
|
42
|
+
})) : undefined,
|
|
43
|
+
};
|
|
44
|
+
nodes.push({
|
|
45
|
+
id: node.id,
|
|
46
|
+
type: node.type,
|
|
47
|
+
position: { x: node.x, y: node.y },
|
|
48
|
+
data: {
|
|
49
|
+
node: nodeCopy,
|
|
50
|
+
layoutDirection,
|
|
51
|
+
},
|
|
52
|
+
sourcePosition,
|
|
53
|
+
targetPosition,
|
|
54
|
+
selected: false,
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
// Convert edges - using smoothstep type for cleaner angular look
|
|
58
|
+
Object.values(dialogue.nodes).forEach(node => {
|
|
59
|
+
if (node.type === NODE_TYPE.NPC && node.nextNodeId) {
|
|
60
|
+
// NPC -> next node (single connection)
|
|
61
|
+
edges.push({
|
|
62
|
+
id: `${node.id}-next`,
|
|
63
|
+
source: node.id,
|
|
64
|
+
target: node.nextNodeId,
|
|
65
|
+
sourceHandle: 'next',
|
|
66
|
+
type: 'default', // Uses NPCEdgeV2 component (smoothstep style)
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
if (node.type === NODE_TYPE.PLAYER && node.choices) {
|
|
70
|
+
// Player -> multiple choices (one edge per choice)
|
|
71
|
+
node.choices.forEach((choice, idx) => {
|
|
72
|
+
if (choice.nextNodeId) {
|
|
73
|
+
const color = CHOICE_COLORS[idx % CHOICE_COLORS.length];
|
|
74
|
+
edges.push({
|
|
75
|
+
id: `${node.id}-choice-${idx}`,
|
|
76
|
+
source: node.id,
|
|
77
|
+
target: choice.nextNodeId,
|
|
78
|
+
sourceHandle: `choice-${idx}`, // Connect to specific choice handle
|
|
79
|
+
type: 'choice', // Use custom ChoiceEdge (smoothstep style)
|
|
80
|
+
data: {
|
|
81
|
+
choiceIndex: idx,
|
|
82
|
+
choiceId: choice.id,
|
|
83
|
+
},
|
|
84
|
+
style: {
|
|
85
|
+
stroke: color,
|
|
86
|
+
strokeWidth: 2,
|
|
87
|
+
opacity: 0.7,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
if (node.type === NODE_TYPE.CONDITIONAL && node.conditionalBlocks) {
|
|
94
|
+
// Conditional -> multiple blocks (one edge per block)
|
|
95
|
+
node.conditionalBlocks.forEach((block, idx) => {
|
|
96
|
+
// Each block can have its own nextNodeId (though typically only one path is taken)
|
|
97
|
+
// For now, we'll treat it like choices - each block can connect to a node
|
|
98
|
+
if (block.nextNodeId) {
|
|
99
|
+
const color = CHOICE_COLORS[idx % CHOICE_COLORS.length];
|
|
100
|
+
edges.push({
|
|
101
|
+
id: `${node.id}-block-${idx}`,
|
|
102
|
+
source: node.id,
|
|
103
|
+
target: block.nextNodeId,
|
|
104
|
+
sourceHandle: `block-${idx}`, // Connect to specific block handle
|
|
105
|
+
type: 'choice', // Use same edge type as choices (smoothstep style)
|
|
106
|
+
data: {
|
|
107
|
+
choiceIndex: idx,
|
|
108
|
+
blockId: block.id,
|
|
109
|
+
},
|
|
110
|
+
style: {
|
|
111
|
+
stroke: color,
|
|
112
|
+
strokeWidth: 2,
|
|
113
|
+
opacity: 0.7,
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
return { nodes, edges };
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Convert React Flow format back to DialogueTree
|
|
124
|
+
*
|
|
125
|
+
* This updates node positions and edge connections in the DialogueTree.
|
|
126
|
+
*/
|
|
127
|
+
export function updateDialogueTreeFromReactFlow(dialogue, nodes, edges) {
|
|
128
|
+
// Create a deep copy of all nodes to avoid mutations
|
|
129
|
+
const updatedNodes = {};
|
|
130
|
+
// First, create copies of all nodes with cleared connections
|
|
131
|
+
Object.values(dialogue.nodes).forEach(node => {
|
|
132
|
+
if (node.type === NODE_TYPE.NPC) {
|
|
133
|
+
updatedNodes[node.id] = {
|
|
134
|
+
...node,
|
|
135
|
+
nextNodeId: undefined,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
else if (node.type === NODE_TYPE.PLAYER) {
|
|
139
|
+
updatedNodes[node.id] = {
|
|
140
|
+
...node,
|
|
141
|
+
choices: node.choices ? node.choices.map(choice => ({
|
|
142
|
+
...choice,
|
|
143
|
+
nextNodeId: '',
|
|
144
|
+
})) : [],
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
else if (node.type === NODE_TYPE.CONDITIONAL) {
|
|
148
|
+
updatedNodes[node.id] = {
|
|
149
|
+
...node,
|
|
150
|
+
conditionalBlocks: node.conditionalBlocks ? node.conditionalBlocks.map(block => ({
|
|
151
|
+
...block,
|
|
152
|
+
nextNodeId: undefined,
|
|
153
|
+
})) : [],
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
updatedNodes[node.id] = { ...node };
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
// Update node positions
|
|
161
|
+
nodes.forEach(rfNode => {
|
|
162
|
+
if (updatedNodes[rfNode.id]) {
|
|
163
|
+
updatedNodes[rfNode.id] = {
|
|
164
|
+
...updatedNodes[rfNode.id],
|
|
165
|
+
x: rfNode.position.x,
|
|
166
|
+
y: rfNode.position.y,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
// Apply edge connections
|
|
171
|
+
edges.forEach(edge => {
|
|
172
|
+
const sourceNode = updatedNodes[edge.source];
|
|
173
|
+
if (!sourceNode)
|
|
174
|
+
return;
|
|
175
|
+
if (edge.sourceHandle === 'next' && sourceNode.type === NODE_TYPE.NPC) {
|
|
176
|
+
// NPC next connection - create new node object
|
|
177
|
+
updatedNodes[edge.source] = {
|
|
178
|
+
...sourceNode,
|
|
179
|
+
nextNodeId: edge.target,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
else if (edge.sourceHandle?.startsWith('choice-') && sourceNode.type === NODE_TYPE.PLAYER) {
|
|
183
|
+
// Player choice connection - create new node and choice objects
|
|
184
|
+
const choiceIdx = parseInt(edge.sourceHandle.replace('choice-', ''));
|
|
185
|
+
if (sourceNode.choices && sourceNode.choices[choiceIdx]) {
|
|
186
|
+
const updatedChoices = [...sourceNode.choices];
|
|
187
|
+
updatedChoices[choiceIdx] = {
|
|
188
|
+
...updatedChoices[choiceIdx],
|
|
189
|
+
nextNodeId: edge.target,
|
|
190
|
+
};
|
|
191
|
+
updatedNodes[edge.source] = {
|
|
192
|
+
...sourceNode,
|
|
193
|
+
choices: updatedChoices,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
else if (edge.sourceHandle?.startsWith('block-') && sourceNode.type === NODE_TYPE.CONDITIONAL) {
|
|
198
|
+
// Conditional block connection
|
|
199
|
+
const blockIdx = parseInt(edge.sourceHandle.replace('block-', ''));
|
|
200
|
+
if (sourceNode.conditionalBlocks && sourceNode.conditionalBlocks[blockIdx]) {
|
|
201
|
+
const updatedBlocks = [...sourceNode.conditionalBlocks];
|
|
202
|
+
updatedBlocks[blockIdx] = {
|
|
203
|
+
...updatedBlocks[blockIdx],
|
|
204
|
+
nextNodeId: edge.target,
|
|
205
|
+
};
|
|
206
|
+
updatedNodes[edge.source] = {
|
|
207
|
+
...sourceNode,
|
|
208
|
+
conditionalBlocks: updatedBlocks,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
return {
|
|
214
|
+
...dialogue,
|
|
215
|
+
nodes: updatedNodes,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { DialogueTree } from '../types';
|
|
2
|
+
import { FlagSchema } from '../types/flags';
|
|
3
|
+
/**
|
|
4
|
+
* Load an example dialogue from a Yarn file
|
|
5
|
+
* This is the unified way to load examples - all examples are Yarn files
|
|
6
|
+
*/
|
|
7
|
+
export declare function loadExampleDialogue(exampleId: string): Promise<DialogueTree | null>;
|
|
8
|
+
/**
|
|
9
|
+
* Get flag schema for an example
|
|
10
|
+
*/
|
|
11
|
+
export declare function getFlagSchemaForExample(exampleId: string): FlagSchema | null;
|
|
12
|
+
/**
|
|
13
|
+
* Get all examples with their metadata
|
|
14
|
+
*/
|
|
15
|
+
export declare function getAllExamples(): {
|
|
16
|
+
flagSchema: FlagSchema | null;
|
|
17
|
+
id: string;
|
|
18
|
+
title: string;
|
|
19
|
+
description: string;
|
|
20
|
+
filename: string;
|
|
21
|
+
flagSchemaId: string;
|
|
22
|
+
nodeCount?: number;
|
|
23
|
+
features: string[];
|
|
24
|
+
}[];
|
|
25
|
+
/**
|
|
26
|
+
* Synchronous version for use with pre-loaded examples
|
|
27
|
+
* This is used when examples are bundled/inlined
|
|
28
|
+
*/
|
|
29
|
+
export declare function loadExampleDialogueSync(exampleId: string, yarnContent: string): DialogueTree | null;
|