@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.
Files changed (241) hide show
  1. package/README.md +233 -0
  2. package/bin/dialogue-forge.js +78 -0
  3. package/demo/app/layout.tsx +36 -0
  4. package/demo/app/page.tsx +440 -0
  5. package/demo/components/ThemeSwitcher.tsx +611 -0
  6. package/demo/next.config.mjs +7 -0
  7. package/demo/package.json +29 -0
  8. package/demo/postcss.config.mjs +7 -0
  9. package/demo/public/logo.svg +1 -0
  10. package/demo/styles/globals.css +19 -0
  11. package/demo/tailwind.config.ts +90 -0
  12. package/demo/tsconfig.json +42 -0
  13. package/dist/components/ChoiceEdgeV2.d.ts +3 -0
  14. package/dist/components/ChoiceEdgeV2.js +103 -0
  15. package/dist/components/CodeBlock.d.ts +8 -0
  16. package/dist/components/CodeBlock.js +24 -0
  17. package/dist/components/ConditionAutocomplete.d.ts +14 -0
  18. package/dist/components/ConditionAutocomplete.js +284 -0
  19. package/dist/components/ConditionalNodeV2.d.ts +16 -0
  20. package/dist/components/ConditionalNodeV2.js +147 -0
  21. package/dist/components/DialogueEditorV2.d.ts +22 -0
  22. package/dist/components/DialogueEditorV2.js +1170 -0
  23. package/dist/components/EdgeIcon.d.ts +8 -0
  24. package/dist/components/EdgeIcon.js +13 -0
  25. package/dist/components/ExampleLoader.d.ts +11 -0
  26. package/dist/components/ExampleLoader.js +52 -0
  27. package/dist/components/ExampleLoaderButton.d.ts +15 -0
  28. package/dist/components/ExampleLoaderButton.js +102 -0
  29. package/dist/components/FlagManager.d.ts +11 -0
  30. package/dist/components/FlagManager.js +282 -0
  31. package/dist/components/FlagSelector.d.ts +11 -0
  32. package/dist/components/FlagSelector.js +235 -0
  33. package/dist/components/GuidePanel.d.ts +7 -0
  34. package/dist/components/GuidePanel.js +1176 -0
  35. package/dist/components/Minimap.d.ts +16 -0
  36. package/dist/components/Minimap.js +93 -0
  37. package/dist/components/NPCEdgeV2.d.ts +3 -0
  38. package/dist/components/NPCEdgeV2.js +104 -0
  39. package/dist/components/NPCNodeV2.d.ts +26 -0
  40. package/dist/components/NPCNodeV2.js +86 -0
  41. package/dist/components/NodeEditor.d.ts +18 -0
  42. package/dist/components/NodeEditor.js +1025 -0
  43. package/dist/components/PlayView.d.ts +12 -0
  44. package/dist/components/PlayView.js +307 -0
  45. package/dist/components/PlayerNodeV2.d.ts +16 -0
  46. package/dist/components/PlayerNodeV2.js +139 -0
  47. package/dist/components/ReactFlowPOC.d.ts +61 -0
  48. package/dist/components/ReactFlowPOC.js +312 -0
  49. package/dist/components/ScenePlayer.d.ts +18 -0
  50. package/dist/components/ScenePlayer.js +196 -0
  51. package/dist/components/YarnView.d.ts +9 -0
  52. package/dist/components/YarnView.js +45 -0
  53. package/dist/components/ZoomControls.d.ts +11 -0
  54. package/dist/components/ZoomControls.js +34 -0
  55. package/dist/esm/components/ChoiceEdgeV2.d.ts +3 -0
  56. package/dist/esm/components/ChoiceEdgeV2.js +67 -0
  57. package/dist/esm/components/CodeBlock.d.ts +8 -0
  58. package/dist/esm/components/CodeBlock.js +18 -0
  59. package/dist/esm/components/ConditionAutocomplete.d.ts +14 -0
  60. package/dist/esm/components/ConditionAutocomplete.js +248 -0
  61. package/dist/esm/components/ConditionalNodeV2.d.ts +16 -0
  62. package/dist/esm/components/ConditionalNodeV2.js +111 -0
  63. package/dist/esm/components/DialogueEditorV2.d.ts +22 -0
  64. package/dist/esm/components/DialogueEditorV2.js +1134 -0
  65. package/dist/esm/components/EdgeIcon.d.ts +8 -0
  66. package/dist/esm/components/EdgeIcon.js +7 -0
  67. package/dist/esm/components/ExampleLoader.d.ts +11 -0
  68. package/dist/esm/components/ExampleLoader.js +46 -0
  69. package/dist/esm/components/ExampleLoaderButton.d.ts +15 -0
  70. package/dist/esm/components/ExampleLoaderButton.js +66 -0
  71. package/dist/esm/components/FlagManager.d.ts +11 -0
  72. package/dist/esm/components/FlagManager.js +246 -0
  73. package/dist/esm/components/FlagSelector.d.ts +11 -0
  74. package/dist/esm/components/FlagSelector.js +199 -0
  75. package/dist/esm/components/GuidePanel.d.ts +7 -0
  76. package/dist/esm/components/GuidePanel.js +1140 -0
  77. package/dist/esm/components/Minimap.d.ts +16 -0
  78. package/dist/esm/components/Minimap.js +57 -0
  79. package/dist/esm/components/NPCEdgeV2.d.ts +3 -0
  80. package/dist/esm/components/NPCEdgeV2.js +68 -0
  81. package/dist/esm/components/NPCNodeV2.d.ts +26 -0
  82. package/dist/esm/components/NPCNodeV2.js +80 -0
  83. package/dist/esm/components/NodeEditor.d.ts +18 -0
  84. package/dist/esm/components/NodeEditor.js +989 -0
  85. package/dist/esm/components/PlayView.d.ts +12 -0
  86. package/dist/esm/components/PlayView.js +271 -0
  87. package/dist/esm/components/PlayerNodeV2.d.ts +16 -0
  88. package/dist/esm/components/PlayerNodeV2.js +103 -0
  89. package/dist/esm/components/ReactFlowPOC.d.ts +61 -0
  90. package/dist/esm/components/ReactFlowPOC.js +275 -0
  91. package/dist/esm/components/ScenePlayer.d.ts +18 -0
  92. package/dist/esm/components/ScenePlayer.js +160 -0
  93. package/dist/esm/components/YarnView.d.ts +9 -0
  94. package/dist/esm/components/YarnView.js +39 -0
  95. package/dist/esm/components/ZoomControls.d.ts +11 -0
  96. package/dist/esm/components/ZoomControls.js +28 -0
  97. package/dist/esm/examples/example-loader.d.ts +29 -0
  98. package/dist/esm/examples/example-loader.js +103 -0
  99. package/dist/esm/examples/examples-registry.d.ts +38 -0
  100. package/dist/esm/examples/examples-registry.js +153 -0
  101. package/dist/esm/examples/index.d.ts +26 -0
  102. package/dist/esm/examples/index.js +50 -0
  103. package/dist/esm/examples/legacy-examples.d.ts +9 -0
  104. package/dist/esm/examples/legacy-examples.js +814 -0
  105. package/dist/esm/examples/yarn-examples.d.ts +35 -0
  106. package/dist/esm/examples/yarn-examples.js +181 -0
  107. package/dist/esm/index.d.ts +21 -0
  108. package/dist/esm/index.js +26 -0
  109. package/dist/esm/lib/flag-manager.d.ts +21 -0
  110. package/dist/esm/lib/flag-manager.js +93 -0
  111. package/dist/esm/lib/yarn-converter/__tests__/round-trip.test.d.ts +1 -0
  112. package/dist/esm/lib/yarn-converter/__tests__/round-trip.test.js +169 -0
  113. package/dist/esm/lib/yarn-converter.d.ts +17 -0
  114. package/dist/esm/lib/yarn-converter.js +521 -0
  115. package/dist/esm/lib/yarn-runner/__tests__/condition-evaluator.test.d.ts +1 -0
  116. package/dist/esm/lib/yarn-runner/__tests__/condition-evaluator.test.js +171 -0
  117. package/dist/esm/lib/yarn-runner/__tests__/node-processor.test.d.ts +1 -0
  118. package/dist/esm/lib/yarn-runner/__tests__/node-processor.test.js +237 -0
  119. package/dist/esm/lib/yarn-runner/__tests__/variable-manager.test.d.ts +1 -0
  120. package/dist/esm/lib/yarn-runner/__tests__/variable-manager.test.js +106 -0
  121. package/dist/esm/lib/yarn-runner/condition-evaluator.d.ts +12 -0
  122. package/dist/esm/lib/yarn-runner/condition-evaluator.js +56 -0
  123. package/dist/esm/lib/yarn-runner/index.d.ts +12 -0
  124. package/dist/esm/lib/yarn-runner/index.js +11 -0
  125. package/dist/esm/lib/yarn-runner/node-processor.d.ts +18 -0
  126. package/dist/esm/lib/yarn-runner/node-processor.js +129 -0
  127. package/dist/esm/lib/yarn-runner/variable-manager.d.ts +51 -0
  128. package/dist/esm/lib/yarn-runner/variable-manager.js +120 -0
  129. package/dist/esm/lib/yarn-runner/variable-operations.d.ts +16 -0
  130. package/dist/esm/lib/yarn-runner/variable-operations.js +88 -0
  131. package/dist/esm/types/conditionals.d.ts +29 -0
  132. package/dist/esm/types/conditionals.js +1 -0
  133. package/dist/esm/types/constants.d.ts +59 -0
  134. package/dist/esm/types/constants.js +55 -0
  135. package/dist/esm/types/flags.d.ts +49 -0
  136. package/dist/esm/types/flags.js +49 -0
  137. package/dist/esm/types/game-state.d.ts +62 -0
  138. package/dist/esm/types/game-state.js +6 -0
  139. package/dist/esm/types/index.d.ts +77 -0
  140. package/dist/esm/types/index.js +1 -0
  141. package/dist/esm/utils/constants.d.ts +5 -0
  142. package/dist/esm/utils/constants.js +5 -0
  143. package/dist/esm/utils/feature-flags.d.ts +11 -0
  144. package/dist/esm/utils/feature-flags.js +11 -0
  145. package/dist/esm/utils/game-state-flattener.d.ts +41 -0
  146. package/dist/esm/utils/game-state-flattener.js +135 -0
  147. package/dist/esm/utils/layout/collision.d.ts +27 -0
  148. package/dist/esm/utils/layout/collision.js +74 -0
  149. package/dist/esm/utils/layout/index.d.ts +82 -0
  150. package/dist/esm/utils/layout/index.js +98 -0
  151. package/dist/esm/utils/layout/registry.d.ts +91 -0
  152. package/dist/esm/utils/layout/registry.js +148 -0
  153. package/dist/esm/utils/layout/strategies/dagre.d.ts +19 -0
  154. package/dist/esm/utils/layout/strategies/dagre.js +182 -0
  155. package/dist/esm/utils/layout/strategies/force.d.ts +21 -0
  156. package/dist/esm/utils/layout/strategies/force.js +178 -0
  157. package/dist/esm/utils/layout/strategies/grid.d.ts +17 -0
  158. package/dist/esm/utils/layout/strategies/grid.js +91 -0
  159. package/dist/esm/utils/layout/strategies/index.d.ts +8 -0
  160. package/dist/esm/utils/layout/strategies/index.js +8 -0
  161. package/dist/esm/utils/layout/types.d.ts +100 -0
  162. package/dist/esm/utils/layout/types.js +7 -0
  163. package/dist/esm/utils/layout.d.ts +9 -0
  164. package/dist/esm/utils/layout.js +17 -0
  165. package/dist/esm/utils/node-helpers.d.ts +7 -0
  166. package/dist/esm/utils/node-helpers.js +94 -0
  167. package/dist/esm/utils/reactflow-converter.d.ts +42 -0
  168. package/dist/esm/utils/reactflow-converter.js +217 -0
  169. package/dist/examples/example-loader.d.ts +29 -0
  170. package/dist/examples/example-loader.js +109 -0
  171. package/dist/examples/examples-registry.d.ts +38 -0
  172. package/dist/examples/examples-registry.js +160 -0
  173. package/dist/examples/index.d.ts +26 -0
  174. package/dist/examples/index.js +63 -0
  175. package/dist/examples/legacy-examples.d.ts +9 -0
  176. package/dist/examples/legacy-examples.js +817 -0
  177. package/dist/examples/yarn-examples.d.ts +35 -0
  178. package/dist/examples/yarn-examples.js +189 -0
  179. package/dist/index.d.ts +21 -0
  180. package/dist/index.js +66 -0
  181. package/dist/lib/flag-manager.d.ts +21 -0
  182. package/dist/lib/flag-manager.js +99 -0
  183. package/dist/lib/yarn-converter/__tests__/round-trip.test.d.ts +1 -0
  184. package/dist/lib/yarn-converter/__tests__/round-trip.test.js +171 -0
  185. package/dist/lib/yarn-converter.d.ts +17 -0
  186. package/dist/lib/yarn-converter.js +525 -0
  187. package/dist/lib/yarn-runner/__tests__/condition-evaluator.test.d.ts +1 -0
  188. package/dist/lib/yarn-runner/__tests__/condition-evaluator.test.js +173 -0
  189. package/dist/lib/yarn-runner/__tests__/node-processor.test.d.ts +1 -0
  190. package/dist/lib/yarn-runner/__tests__/node-processor.test.js +239 -0
  191. package/dist/lib/yarn-runner/__tests__/variable-manager.test.d.ts +1 -0
  192. package/dist/lib/yarn-runner/__tests__/variable-manager.test.js +108 -0
  193. package/dist/lib/yarn-runner/condition-evaluator.d.ts +12 -0
  194. package/dist/lib/yarn-runner/condition-evaluator.js +60 -0
  195. package/dist/lib/yarn-runner/index.d.ts +12 -0
  196. package/dist/lib/yarn-runner/index.js +21 -0
  197. package/dist/lib/yarn-runner/node-processor.d.ts +18 -0
  198. package/dist/lib/yarn-runner/node-processor.js +133 -0
  199. package/dist/lib/yarn-runner/variable-manager.d.ts +51 -0
  200. package/dist/lib/yarn-runner/variable-manager.js +124 -0
  201. package/dist/lib/yarn-runner/variable-operations.d.ts +16 -0
  202. package/dist/lib/yarn-runner/variable-operations.js +92 -0
  203. package/dist/types/conditionals.d.ts +29 -0
  204. package/dist/types/conditionals.js +2 -0
  205. package/dist/types/constants.d.ts +59 -0
  206. package/dist/types/constants.js +58 -0
  207. package/dist/types/flags.d.ts +49 -0
  208. package/dist/types/flags.js +52 -0
  209. package/dist/types/game-state.d.ts +62 -0
  210. package/dist/types/game-state.js +7 -0
  211. package/dist/types/index.d.ts +77 -0
  212. package/dist/types/index.js +2 -0
  213. package/dist/utils/constants.d.ts +5 -0
  214. package/dist/utils/constants.js +8 -0
  215. package/dist/utils/feature-flags.d.ts +11 -0
  216. package/dist/utils/feature-flags.js +14 -0
  217. package/dist/utils/game-state-flattener.d.ts +41 -0
  218. package/dist/utils/game-state-flattener.js +140 -0
  219. package/dist/utils/layout/collision.d.ts +27 -0
  220. package/dist/utils/layout/collision.js +77 -0
  221. package/dist/utils/layout/index.d.ts +82 -0
  222. package/dist/utils/layout/index.js +109 -0
  223. package/dist/utils/layout/registry.d.ts +91 -0
  224. package/dist/utils/layout/registry.js +151 -0
  225. package/dist/utils/layout/strategies/dagre.d.ts +19 -0
  226. package/dist/utils/layout/strategies/dagre.js +189 -0
  227. package/dist/utils/layout/strategies/force.d.ts +21 -0
  228. package/dist/utils/layout/strategies/force.js +182 -0
  229. package/dist/utils/layout/strategies/grid.d.ts +17 -0
  230. package/dist/utils/layout/strategies/grid.js +95 -0
  231. package/dist/utils/layout/strategies/index.d.ts +8 -0
  232. package/dist/utils/layout/strategies/index.js +14 -0
  233. package/dist/utils/layout/types.d.ts +100 -0
  234. package/dist/utils/layout/types.js +8 -0
  235. package/dist/utils/layout.d.ts +9 -0
  236. package/dist/utils/layout.js +25 -0
  237. package/dist/utils/node-helpers.d.ts +7 -0
  238. package/dist/utils/node-helpers.js +101 -0
  239. package/dist/utils/reactflow-converter.d.ts +42 -0
  240. package/dist/utils/reactflow-converter.js +223 -0
  241. package/package.json +70 -0
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Layout Strategy Registry
3
+ *
4
+ * Central registry for all available layout algorithms.
5
+ * Implements the Registry pattern to manage strategy instances.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * // Register a custom strategy
10
+ * layoutRegistry.register(new MyCustomLayout());
11
+ *
12
+ * // Use a strategy
13
+ * const result = layoutRegistry.apply('dagre', dialogue, { direction: 'LR' });
14
+ *
15
+ * // List available strategies
16
+ * const strategies = layoutRegistry.list();
17
+ * ```
18
+ */
19
+ import { DialogueTree } from '../../types';
20
+ import { LayoutStrategy, LayoutOptions, LayoutResult } from './types';
21
+ declare class LayoutStrategyRegistry {
22
+ private strategies;
23
+ private defaultStrategyId;
24
+ /**
25
+ * Register a layout strategy
26
+ * @param strategy - The strategy to register
27
+ * @param isDefault - Whether this should be the default strategy
28
+ */
29
+ register(strategy: LayoutStrategy, isDefault?: boolean): void;
30
+ /**
31
+ * Unregister a layout strategy
32
+ * @param id - The strategy ID to remove
33
+ */
34
+ unregister(id: string): boolean;
35
+ /**
36
+ * Get a strategy by ID
37
+ * @param id - The strategy ID
38
+ */
39
+ get(id: string): LayoutStrategy | undefined;
40
+ /**
41
+ * Get the default strategy
42
+ */
43
+ getDefault(): LayoutStrategy | undefined;
44
+ /**
45
+ * Set the default strategy
46
+ * @param id - The strategy ID to set as default
47
+ */
48
+ setDefault(id: string): boolean;
49
+ /**
50
+ * List all registered strategies
51
+ */
52
+ list(): Array<{
53
+ id: string;
54
+ name: string;
55
+ description: string;
56
+ isDefault: boolean;
57
+ }>;
58
+ /**
59
+ * Apply a layout strategy to a dialogue tree
60
+ * @param id - The strategy ID (or undefined to use default)
61
+ * @param dialogue - The dialogue tree to layout
62
+ * @param options - Layout options
63
+ */
64
+ apply(id: string | undefined, dialogue: DialogueTree, options?: LayoutOptions): LayoutResult;
65
+ /**
66
+ * Check if a strategy is registered
67
+ * @param id - The strategy ID
68
+ */
69
+ has(id: string): boolean;
70
+ /**
71
+ * Get the number of registered strategies
72
+ */
73
+ get size(): number;
74
+ /**
75
+ * Clear all registered strategies
76
+ */
77
+ clear(): void;
78
+ }
79
+ /**
80
+ * Global layout strategy registry
81
+ *
82
+ * Use this to register custom strategies or apply layouts:
83
+ * ```typescript
84
+ * import { layoutRegistry } from './layout';
85
+ *
86
+ * // Apply layout
87
+ * const result = layoutRegistry.apply('dagre', dialogue);
88
+ * ```
89
+ */
90
+ export declare const layoutRegistry: LayoutStrategyRegistry;
91
+ export {};
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Layout Strategy Registry
3
+ *
4
+ * Central registry for all available layout algorithms.
5
+ * Implements the Registry pattern to manage strategy instances.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * // Register a custom strategy
10
+ * layoutRegistry.register(new MyCustomLayout());
11
+ *
12
+ * // Use a strategy
13
+ * const result = layoutRegistry.apply('dagre', dialogue, { direction: 'LR' });
14
+ *
15
+ * // List available strategies
16
+ * const strategies = layoutRegistry.list();
17
+ * ```
18
+ */
19
+ // ============================================================================
20
+ // Registry Implementation
21
+ // ============================================================================
22
+ class LayoutStrategyRegistry {
23
+ constructor() {
24
+ this.strategies = new Map();
25
+ this.defaultStrategyId = null;
26
+ }
27
+ /**
28
+ * Register a layout strategy
29
+ * @param strategy - The strategy to register
30
+ * @param isDefault - Whether this should be the default strategy
31
+ */
32
+ register(strategy, isDefault = false) {
33
+ if (this.strategies.has(strategy.id)) {
34
+ console.warn(`Layout strategy "${strategy.id}" is already registered. Overwriting.`);
35
+ }
36
+ this.strategies.set(strategy.id, { strategy, isDefault });
37
+ if (isDefault || this.defaultStrategyId === null) {
38
+ this.defaultStrategyId = strategy.id;
39
+ }
40
+ }
41
+ /**
42
+ * Unregister a layout strategy
43
+ * @param id - The strategy ID to remove
44
+ */
45
+ unregister(id) {
46
+ const removed = this.strategies.delete(id);
47
+ if (removed && this.defaultStrategyId === id) {
48
+ // Set a new default if we removed the default
49
+ const first = this.strategies.keys().next().value;
50
+ this.defaultStrategyId = first || null;
51
+ }
52
+ return removed;
53
+ }
54
+ /**
55
+ * Get a strategy by ID
56
+ * @param id - The strategy ID
57
+ */
58
+ get(id) {
59
+ return this.strategies.get(id)?.strategy;
60
+ }
61
+ /**
62
+ * Get the default strategy
63
+ */
64
+ getDefault() {
65
+ if (!this.defaultStrategyId)
66
+ return undefined;
67
+ return this.get(this.defaultStrategyId);
68
+ }
69
+ /**
70
+ * Set the default strategy
71
+ * @param id - The strategy ID to set as default
72
+ */
73
+ setDefault(id) {
74
+ if (!this.strategies.has(id)) {
75
+ console.warn(`Cannot set default: strategy "${id}" not found`);
76
+ return false;
77
+ }
78
+ this.defaultStrategyId = id;
79
+ return true;
80
+ }
81
+ /**
82
+ * List all registered strategies
83
+ */
84
+ list() {
85
+ return Array.from(this.strategies.entries()).map(([id, entry]) => ({
86
+ id,
87
+ name: entry.strategy.name,
88
+ description: entry.strategy.description,
89
+ isDefault: id === this.defaultStrategyId,
90
+ }));
91
+ }
92
+ /**
93
+ * Apply a layout strategy to a dialogue tree
94
+ * @param id - The strategy ID (or undefined to use default)
95
+ * @param dialogue - The dialogue tree to layout
96
+ * @param options - Layout options
97
+ */
98
+ apply(id, dialogue, options) {
99
+ const strategyId = id || this.defaultStrategyId;
100
+ if (!strategyId) {
101
+ throw new Error('No layout strategy available. Register a strategy first.');
102
+ }
103
+ const strategy = this.get(strategyId);
104
+ if (!strategy) {
105
+ throw new Error(`Layout strategy "${strategyId}" not found`);
106
+ }
107
+ // Check if strategy supports this dialogue
108
+ if (strategy.supports && !strategy.supports(dialogue)) {
109
+ console.warn(`Strategy "${strategyId}" may not support this dialogue structure`);
110
+ }
111
+ return strategy.apply(dialogue, options);
112
+ }
113
+ /**
114
+ * Check if a strategy is registered
115
+ * @param id - The strategy ID
116
+ */
117
+ has(id) {
118
+ return this.strategies.has(id);
119
+ }
120
+ /**
121
+ * Get the number of registered strategies
122
+ */
123
+ get size() {
124
+ return this.strategies.size;
125
+ }
126
+ /**
127
+ * Clear all registered strategies
128
+ */
129
+ clear() {
130
+ this.strategies.clear();
131
+ this.defaultStrategyId = null;
132
+ }
133
+ }
134
+ // ============================================================================
135
+ // Singleton Instance
136
+ // ============================================================================
137
+ /**
138
+ * Global layout strategy registry
139
+ *
140
+ * Use this to register custom strategies or apply layouts:
141
+ * ```typescript
142
+ * import { layoutRegistry } from './layout';
143
+ *
144
+ * // Apply layout
145
+ * const result = layoutRegistry.apply('dagre', dialogue);
146
+ * ```
147
+ */
148
+ export const layoutRegistry = new LayoutStrategyRegistry();
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Dagre Layout Strategy
3
+ *
4
+ * Hierarchical layout using the dagre library.
5
+ * Best for dialogue trees with clear start-to-end flow.
6
+ *
7
+ * @see https://github.com/dagrejs/dagre
8
+ * @see https://reactflow.dev/examples/layout/dagre
9
+ */
10
+ import { DialogueTree } from '../../../types';
11
+ import { LayoutStrategy, LayoutOptions, LayoutResult } from '../types';
12
+ export declare class DagreLayoutStrategy implements LayoutStrategy {
13
+ readonly id = "dagre";
14
+ readonly name = "Dagre (Hierarchical)";
15
+ readonly description = "Hierarchical layout that flows from start to end. Best for linear dialogue with branches.";
16
+ readonly defaultOptions: Partial<LayoutOptions>;
17
+ apply(dialogue: DialogueTree, options?: LayoutOptions): LayoutResult;
18
+ supports(dialogue: DialogueTree): boolean;
19
+ }
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Dagre Layout Strategy
3
+ *
4
+ * Hierarchical layout using the dagre library.
5
+ * Best for dialogue trees with clear start-to-end flow.
6
+ *
7
+ * @see https://github.com/dagrejs/dagre
8
+ * @see https://reactflow.dev/examples/layout/dagre
9
+ */
10
+ import dagre from '@dagrejs/dagre';
11
+ // ============================================================================
12
+ // Constants
13
+ // ============================================================================
14
+ const NODE_WIDTH = 220;
15
+ const NODE_HEIGHT = 120;
16
+ const EXTRA_HEIGHT_PER_ITEM = 30;
17
+ // ============================================================================
18
+ // Helper Functions
19
+ // ============================================================================
20
+ /**
21
+ * Calculate depth of each node from start using BFS
22
+ */
23
+ function calculateNodeDepths(dialogue) {
24
+ const depths = new Map();
25
+ if (!dialogue.startNodeId)
26
+ return depths;
27
+ const queue = [
28
+ { id: dialogue.startNodeId, depth: 0 }
29
+ ];
30
+ const visited = new Set();
31
+ while (queue.length > 0) {
32
+ const { id, depth } = queue.shift();
33
+ if (visited.has(id))
34
+ continue;
35
+ visited.add(id);
36
+ depths.set(id, depth);
37
+ const node = dialogue.nodes[id];
38
+ if (!node)
39
+ continue;
40
+ // Queue connected nodes
41
+ const nextIds = getOutgoingNodeIds(node);
42
+ for (const nextId of nextIds) {
43
+ if (dialogue.nodes[nextId] && !visited.has(nextId)) {
44
+ queue.push({ id: nextId, depth: depth + 1 });
45
+ }
46
+ }
47
+ }
48
+ return depths;
49
+ }
50
+ /**
51
+ * Get all outgoing node IDs from a node
52
+ */
53
+ function getOutgoingNodeIds(node) {
54
+ const ids = [];
55
+ if (node.nextNodeId)
56
+ ids.push(node.nextNodeId);
57
+ node.choices?.forEach(c => c.nextNodeId && ids.push(c.nextNodeId));
58
+ node.conditionalBlocks?.forEach(b => b.nextNodeId && ids.push(b.nextNodeId));
59
+ return ids;
60
+ }
61
+ /**
62
+ * Estimate node height based on content
63
+ */
64
+ function estimateNodeHeight(node) {
65
+ const itemCount = Math.max(node.choices?.length || 0, node.conditionalBlocks?.length || 0);
66
+ return NODE_HEIGHT + itemCount * EXTRA_HEIGHT_PER_ITEM;
67
+ }
68
+ // ============================================================================
69
+ // Strategy Implementation
70
+ // ============================================================================
71
+ export class DagreLayoutStrategy {
72
+ constructor() {
73
+ this.id = 'dagre';
74
+ this.name = 'Dagre (Hierarchical)';
75
+ this.description = 'Hierarchical layout that flows from start to end. Best for linear dialogue with branches.';
76
+ this.defaultOptions = {
77
+ direction: 'TB',
78
+ nodeSpacingX: 80,
79
+ nodeSpacingY: 120,
80
+ margin: 50,
81
+ };
82
+ }
83
+ apply(dialogue, options) {
84
+ const startTime = performance.now();
85
+ const opts = { ...this.defaultOptions, ...options };
86
+ const direction = opts.direction || 'TB';
87
+ const isHorizontal = direction === 'LR';
88
+ // Create dagre graph
89
+ const g = new dagre.graphlib.Graph();
90
+ g.setDefaultEdgeLabel(() => ({}));
91
+ // Configure layout
92
+ g.setGraph({
93
+ rankdir: direction,
94
+ nodesep: isHorizontal ? opts.nodeSpacingX : opts.nodeSpacingY,
95
+ ranksep: isHorizontal ? opts.nodeSpacingY : opts.nodeSpacingX,
96
+ marginx: opts.margin,
97
+ marginy: opts.margin,
98
+ ranker: 'network-simplex',
99
+ align: 'UL',
100
+ acyclicer: 'greedy',
101
+ edgesep: 15,
102
+ });
103
+ // Add nodes ordered by depth
104
+ const nodeDepths = calculateNodeDepths(dialogue);
105
+ const sortedNodeIds = Object.keys(dialogue.nodes).sort((a, b) => {
106
+ return (nodeDepths.get(a) ?? Infinity) - (nodeDepths.get(b) ?? Infinity);
107
+ });
108
+ for (const nodeId of sortedNodeIds) {
109
+ const node = dialogue.nodes[nodeId];
110
+ g.setNode(nodeId, {
111
+ width: NODE_WIDTH,
112
+ height: estimateNodeHeight(node),
113
+ });
114
+ }
115
+ // Add edges with weights
116
+ for (const node of Object.values(dialogue.nodes)) {
117
+ if (node.nextNodeId && dialogue.nodes[node.nextNodeId]) {
118
+ g.setEdge(node.id, node.nextNodeId, { weight: 3, minlen: 1 });
119
+ }
120
+ for (const choice of node.choices || []) {
121
+ if (choice.nextNodeId && dialogue.nodes[choice.nextNodeId]) {
122
+ g.setEdge(node.id, choice.nextNodeId, { weight: 2, minlen: 1 });
123
+ }
124
+ }
125
+ for (const block of node.conditionalBlocks || []) {
126
+ if (block.nextNodeId && dialogue.nodes[block.nextNodeId]) {
127
+ g.setEdge(node.id, block.nextNodeId, { weight: 2, minlen: 1 });
128
+ }
129
+ }
130
+ }
131
+ // Run layout
132
+ dagre.layout(g);
133
+ // Extract positions and calculate bounds
134
+ let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
135
+ const updatedNodes = {};
136
+ for (const nodeId of Object.keys(dialogue.nodes)) {
137
+ const node = dialogue.nodes[nodeId];
138
+ const dagreNode = g.node(nodeId);
139
+ if (dagreNode) {
140
+ const x = dagreNode.x - NODE_WIDTH / 2;
141
+ const y = dagreNode.y - NODE_HEIGHT / 2;
142
+ updatedNodes[nodeId] = { ...node, x, y };
143
+ minX = Math.min(minX, x);
144
+ maxX = Math.max(maxX, x + NODE_WIDTH);
145
+ minY = Math.min(minY, y);
146
+ maxY = Math.max(maxY, y + NODE_HEIGHT);
147
+ }
148
+ else {
149
+ // Orphan node
150
+ updatedNodes[nodeId] = { ...node };
151
+ }
152
+ }
153
+ // Ensure start node is at edge
154
+ const startNodeId = dialogue.startNodeId;
155
+ if (startNodeId && updatedNodes[startNodeId]) {
156
+ const threshold = 50;
157
+ if (isHorizontal && updatedNodes[startNodeId].x - minX > threshold) {
158
+ updatedNodes[startNodeId].x = minX;
159
+ }
160
+ else if (!isHorizontal && updatedNodes[startNodeId].y - minY > threshold) {
161
+ updatedNodes[startNodeId].y = minY;
162
+ }
163
+ }
164
+ const computeTimeMs = performance.now() - startTime;
165
+ return {
166
+ dialogue: { ...dialogue, nodes: updatedNodes },
167
+ metadata: {
168
+ computeTimeMs,
169
+ nodeCount: Object.keys(dialogue.nodes).length,
170
+ bounds: {
171
+ minX, minY, maxX, maxY,
172
+ width: maxX - minX,
173
+ height: maxY - minY,
174
+ },
175
+ },
176
+ };
177
+ }
178
+ supports(dialogue) {
179
+ // Dagre works best with connected graphs that have a clear start
180
+ return !!dialogue.startNodeId && Object.keys(dialogue.nodes).length > 0;
181
+ }
182
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Force-Directed Layout Strategy
3
+ *
4
+ * Physics-based layout that spreads nodes evenly.
5
+ * Good for exploring graph structure without hierarchy.
6
+ *
7
+ * Uses a simple force simulation:
8
+ * - Nodes repel each other (like charged particles)
9
+ * - Connected nodes attract (like springs)
10
+ */
11
+ import { DialogueTree } from '../../../types';
12
+ import { LayoutStrategy, LayoutOptions, LayoutResult } from '../types';
13
+ export declare class ForceLayoutStrategy implements LayoutStrategy {
14
+ readonly id = "force";
15
+ readonly name = "Force-Directed";
16
+ readonly description = "Physics-based layout that spreads nodes evenly. Good for exploring complex graphs.";
17
+ readonly defaultOptions: Partial<LayoutOptions>;
18
+ apply(dialogue: DialogueTree, options?: LayoutOptions): LayoutResult;
19
+ private emptyResult;
20
+ supports(): boolean;
21
+ }
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Force-Directed Layout Strategy
3
+ *
4
+ * Physics-based layout that spreads nodes evenly.
5
+ * Good for exploring graph structure without hierarchy.
6
+ *
7
+ * Uses a simple force simulation:
8
+ * - Nodes repel each other (like charged particles)
9
+ * - Connected nodes attract (like springs)
10
+ */
11
+ // ============================================================================
12
+ // Constants
13
+ // ============================================================================
14
+ const NODE_WIDTH = 220;
15
+ const NODE_HEIGHT = 120;
16
+ // Force simulation parameters
17
+ const REPULSION_STRENGTH = 5000; // How strongly nodes push each other
18
+ const ATTRACTION_STRENGTH = 0.1; // How strongly edges pull nodes together
19
+ const DAMPING = 0.9; // Velocity reduction per iteration
20
+ const MAX_ITERATIONS = 100; // Maximum simulation steps
21
+ const MIN_MOVEMENT = 0.5; // Stop when movement is below this
22
+ // ============================================================================
23
+ // Helper Functions
24
+ // ============================================================================
25
+ function getConnectedNodeIds(node) {
26
+ const ids = [];
27
+ if (node.nextNodeId)
28
+ ids.push(node.nextNodeId);
29
+ node.choices?.forEach(c => c.nextNodeId && ids.push(c.nextNodeId));
30
+ node.conditionalBlocks?.forEach(b => b.nextNodeId && ids.push(b.nextNodeId));
31
+ return ids;
32
+ }
33
+ // ============================================================================
34
+ // Strategy Implementation
35
+ // ============================================================================
36
+ export class ForceLayoutStrategy {
37
+ constructor() {
38
+ this.id = 'force';
39
+ this.name = 'Force-Directed';
40
+ this.description = 'Physics-based layout that spreads nodes evenly. Good for exploring complex graphs.';
41
+ this.defaultOptions = {
42
+ nodeSpacingX: 300,
43
+ nodeSpacingY: 200,
44
+ margin: 50,
45
+ };
46
+ }
47
+ apply(dialogue, options) {
48
+ const startTime = performance.now();
49
+ const opts = { ...this.defaultOptions, ...options };
50
+ const nodeIds = Object.keys(dialogue.nodes);
51
+ if (nodeIds.length === 0) {
52
+ return this.emptyResult(dialogue, startTime);
53
+ }
54
+ // Initialize node positions in a circle
55
+ const states = new Map();
56
+ const centerX = 500;
57
+ const centerY = 500;
58
+ const radius = Math.max(200, nodeIds.length * 30);
59
+ nodeIds.forEach((id, i) => {
60
+ const angle = (2 * Math.PI * i) / nodeIds.length;
61
+ states.set(id, {
62
+ id,
63
+ x: centerX + radius * Math.cos(angle),
64
+ y: centerY + radius * Math.sin(angle),
65
+ vx: 0,
66
+ vy: 0,
67
+ });
68
+ });
69
+ // Build edge list for attraction
70
+ const edges = [];
71
+ for (const node of Object.values(dialogue.nodes)) {
72
+ for (const targetId of getConnectedNodeIds(node)) {
73
+ if (dialogue.nodes[targetId]) {
74
+ edges.push({ source: node.id, target: targetId });
75
+ }
76
+ }
77
+ }
78
+ // Run force simulation
79
+ for (let iter = 0; iter < MAX_ITERATIONS; iter++) {
80
+ let maxMovement = 0;
81
+ // Calculate repulsion forces between all node pairs
82
+ for (let i = 0; i < nodeIds.length; i++) {
83
+ for (let j = i + 1; j < nodeIds.length; j++) {
84
+ const a = states.get(nodeIds[i]);
85
+ const b = states.get(nodeIds[j]);
86
+ const dx = b.x - a.x;
87
+ const dy = b.y - a.y;
88
+ const dist = Math.sqrt(dx * dx + dy * dy) || 1;
89
+ // Repulsion force (inverse square law)
90
+ const force = REPULSION_STRENGTH / (dist * dist);
91
+ const fx = (dx / dist) * force;
92
+ const fy = (dy / dist) * force;
93
+ a.vx -= fx;
94
+ a.vy -= fy;
95
+ b.vx += fx;
96
+ b.vy += fy;
97
+ }
98
+ }
99
+ // Calculate attraction forces along edges
100
+ for (const edge of edges) {
101
+ const source = states.get(edge.source);
102
+ const target = states.get(edge.target);
103
+ if (!source || !target)
104
+ continue;
105
+ const dx = target.x - source.x;
106
+ const dy = target.y - source.y;
107
+ const dist = Math.sqrt(dx * dx + dy * dy) || 1;
108
+ // Attraction force (Hooke's law)
109
+ const force = dist * ATTRACTION_STRENGTH;
110
+ const fx = (dx / dist) * force;
111
+ const fy = (dy / dist) * force;
112
+ source.vx += fx;
113
+ source.vy += fy;
114
+ target.vx -= fx;
115
+ target.vy -= fy;
116
+ }
117
+ // Apply velocities with damping
118
+ for (const state of states.values()) {
119
+ state.x += state.vx;
120
+ state.y += state.vy;
121
+ state.vx *= DAMPING;
122
+ state.vy *= DAMPING;
123
+ maxMovement = Math.max(maxMovement, Math.abs(state.vx) + Math.abs(state.vy));
124
+ }
125
+ // Early exit if stable
126
+ if (maxMovement < MIN_MOVEMENT)
127
+ break;
128
+ }
129
+ // Calculate bounds and apply positions
130
+ let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
131
+ const updatedNodes = {};
132
+ for (const [id, state] of states) {
133
+ const node = dialogue.nodes[id];
134
+ updatedNodes[id] = { ...node, x: state.x, y: state.y };
135
+ minX = Math.min(minX, state.x);
136
+ maxX = Math.max(maxX, state.x + NODE_WIDTH);
137
+ minY = Math.min(minY, state.y);
138
+ maxY = Math.max(maxY, state.y + NODE_HEIGHT);
139
+ }
140
+ // Normalize to start from margin
141
+ const margin = opts.margin || 50;
142
+ const offsetX = margin - minX;
143
+ const offsetY = margin - minY;
144
+ for (const id of nodeIds) {
145
+ updatedNodes[id].x += offsetX;
146
+ updatedNodes[id].y += offsetY;
147
+ }
148
+ const computeTimeMs = performance.now() - startTime;
149
+ return {
150
+ dialogue: { ...dialogue, nodes: updatedNodes },
151
+ metadata: {
152
+ computeTimeMs,
153
+ nodeCount: nodeIds.length,
154
+ bounds: {
155
+ minX: margin,
156
+ minY: margin,
157
+ maxX: maxX + offsetX,
158
+ maxY: maxY + offsetY,
159
+ width: maxX - minX,
160
+ height: maxY - minY,
161
+ },
162
+ },
163
+ };
164
+ }
165
+ emptyResult(dialogue, startTime) {
166
+ return {
167
+ dialogue,
168
+ metadata: {
169
+ computeTimeMs: performance.now() - startTime,
170
+ nodeCount: 0,
171
+ bounds: { minX: 0, minY: 0, maxX: 0, maxY: 0, width: 0, height: 0 },
172
+ },
173
+ };
174
+ }
175
+ supports() {
176
+ return true; // Works with any graph
177
+ }
178
+ }
@@ -0,0 +1,17 @@
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
+ import { DialogueTree } from '../../../types';
8
+ import { LayoutStrategy, LayoutOptions, LayoutResult } from '../types';
9
+ export declare class GridLayoutStrategy implements LayoutStrategy {
10
+ readonly id = "grid";
11
+ readonly name = "Grid";
12
+ readonly description = "Arranges nodes in a simple grid pattern. Good for viewing all nodes at once.";
13
+ readonly defaultOptions: Partial<LayoutOptions>;
14
+ apply(dialogue: DialogueTree, options?: LayoutOptions): LayoutResult;
15
+ private emptyResult;
16
+ supports(): boolean;
17
+ }