@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,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proof of Concept: React Flow Implementation with Custom Choice Edges
|
|
3
|
+
*
|
|
4
|
+
* This demonstrates how we can use React Flow's custom edges feature
|
|
5
|
+
* to implement our choice-based edge system.
|
|
6
|
+
*
|
|
7
|
+
* Key concepts:
|
|
8
|
+
* 1. Dynamic handles on PlayerNode (one handle per choice)
|
|
9
|
+
* 2. Custom ChoiceEdge component that colors based on choice index
|
|
10
|
+
* 3. Edge data stores choiceIndex and choiceId
|
|
11
|
+
*
|
|
12
|
+
* To use this, install: npm install reactflow
|
|
13
|
+
*/
|
|
14
|
+
import React, { useCallback, useMemo } from 'react';
|
|
15
|
+
import { NODE_TYPE } from '../types/constants';
|
|
16
|
+
// Color scheme for choice edges (same as current implementation)
|
|
17
|
+
const CHOICE_COLORS = ['#e94560', '#8b5cf6', '#06b6d4', '#22c55e', '#f59e0b'];
|
|
18
|
+
/**
|
|
19
|
+
* Custom Edge Component for Player Choice Connections
|
|
20
|
+
*
|
|
21
|
+
* This edge:
|
|
22
|
+
* - Colors based on choice index (from edge data)
|
|
23
|
+
* - Uses bezier path for smooth curves
|
|
24
|
+
* - Matches our current visual style
|
|
25
|
+
*/
|
|
26
|
+
// function ChoiceEdge({ id, sourceX, sourceY, targetX, targetY, data }: EdgeProps) {
|
|
27
|
+
// const [edgePath] = getBezierPath({
|
|
28
|
+
// sourceX,
|
|
29
|
+
// sourceY,
|
|
30
|
+
// targetX,
|
|
31
|
+
// targetY,
|
|
32
|
+
// });
|
|
33
|
+
//
|
|
34
|
+
// const choiceIndex = data?.choiceIndex ?? 0;
|
|
35
|
+
// const color = CHOICE_COLORS[choiceIndex % CHOICE_COLORS.length];
|
|
36
|
+
//
|
|
37
|
+
// return (
|
|
38
|
+
// <>
|
|
39
|
+
// <BaseEdge
|
|
40
|
+
// id={id}
|
|
41
|
+
// path={edgePath}
|
|
42
|
+
// style={{ stroke: color, strokeWidth: 2, opacity: 0.7 }}
|
|
43
|
+
// markerEnd={{
|
|
44
|
+
// type: 'arrowclosed',
|
|
45
|
+
// color: color,
|
|
46
|
+
// }}
|
|
47
|
+
// />
|
|
48
|
+
// </>
|
|
49
|
+
// );
|
|
50
|
+
// }
|
|
51
|
+
/**
|
|
52
|
+
* NPC Node Component
|
|
53
|
+
*
|
|
54
|
+
* Features:
|
|
55
|
+
* - Single output handle at bottom
|
|
56
|
+
* - Speaker + content display
|
|
57
|
+
* - Matches current styling
|
|
58
|
+
*/
|
|
59
|
+
// function NPCNode({ data, selected }: NodeProps) {
|
|
60
|
+
// const node = data.node as DialogueNode;
|
|
61
|
+
//
|
|
62
|
+
// return (
|
|
63
|
+
// <div className={`rounded-lg border-2 transition-all ${
|
|
64
|
+
// selected ? 'border-[#e94560] shadow-lg shadow-[#e94560]/20' : 'border-[#2a2a3e]'
|
|
65
|
+
// } bg-[#1a1a2e] min-w-[200px]`}>
|
|
66
|
+
// {/* Input handle at top */}
|
|
67
|
+
// <Handle type="target" position={Position.Top} className="!bg-[#2a2a3e] !border-[#4a4a6a]" />
|
|
68
|
+
//
|
|
69
|
+
// {/* Header */}
|
|
70
|
+
// <div className="px-3 py-1.5 border-b border-[#2a2a3e] bg-[#12121a] flex items-center gap-2 rounded-t-lg">
|
|
71
|
+
// <span className="text-[10px] font-mono text-gray-500 truncate flex-1">{node.id}</span>
|
|
72
|
+
// <span className="text-[10px] text-gray-600">NPC</span>
|
|
73
|
+
// </div>
|
|
74
|
+
//
|
|
75
|
+
// {/* Content */}
|
|
76
|
+
// <div className="px-3 py-2 min-h-[50px]">
|
|
77
|
+
// {node.speaker && (
|
|
78
|
+
// <div className="text-[10px] text-[#e94560] font-medium">{node.speaker}</div>
|
|
79
|
+
// )}
|
|
80
|
+
// <div className="text-xs text-gray-400 line-clamp-2">
|
|
81
|
+
// {node.content.slice(0, 60) + (node.content.length > 60 ? '...' : '')}
|
|
82
|
+
// </div>
|
|
83
|
+
// </div>
|
|
84
|
+
//
|
|
85
|
+
// {/* Output handle at bottom (for nextNodeId connection) */}
|
|
86
|
+
// {node.nextNodeId && (
|
|
87
|
+
// <Handle
|
|
88
|
+
// type="source"
|
|
89
|
+
// position={Position.Bottom}
|
|
90
|
+
// className="!bg-[#2a2a3e] !border-[#4a4a6a] !w-4 !h-4"
|
|
91
|
+
// id="next"
|
|
92
|
+
// />
|
|
93
|
+
// )}
|
|
94
|
+
// </div>
|
|
95
|
+
// );
|
|
96
|
+
// }
|
|
97
|
+
/**
|
|
98
|
+
* Player Node Component
|
|
99
|
+
*
|
|
100
|
+
* Features:
|
|
101
|
+
* - Dynamic handles: one per choice (positioned on right side)
|
|
102
|
+
* - Each handle positioned at choice's Y offset
|
|
103
|
+
* - Matches current styling
|
|
104
|
+
*/
|
|
105
|
+
// function PlayerNode({ data, selected }: NodeProps) {
|
|
106
|
+
// const node = data.node as DialogueNode;
|
|
107
|
+
// const choices = node.choices || [];
|
|
108
|
+
//
|
|
109
|
+
// return (
|
|
110
|
+
// <div className={`rounded-lg border-2 transition-all ${
|
|
111
|
+
// selected ? 'border-[#e94560] shadow-lg shadow-[#e94560]/20' : 'border-[#2a2a3e]'
|
|
112
|
+
// } bg-[#1e1e3a] min-w-[200px]`}>
|
|
113
|
+
// {/* Input handle at top */}
|
|
114
|
+
// <Handle type="target" position={Position.Top} className="!bg-[#2a2a3e] !border-[#4a4a6a]" />
|
|
115
|
+
//
|
|
116
|
+
// {/* Header */}
|
|
117
|
+
// <div className="px-3 py-1.5 border-b border-[#2a2a3e] bg-[#16162a] flex items-center gap-2 rounded-t-lg">
|
|
118
|
+
// <span className="text-[10px] font-mono text-gray-500 truncate flex-1">{node.id}</span>
|
|
119
|
+
// <span className="text-[10px] text-gray-600">PLAYER</span>
|
|
120
|
+
// </div>
|
|
121
|
+
//
|
|
122
|
+
// {/* Choices */}
|
|
123
|
+
// <div className="border-t border-[#2a2a3e]">
|
|
124
|
+
// {choices.map((choice, idx) => {
|
|
125
|
+
// const color = CHOICE_COLORS[idx % CHOICE_COLORS.length];
|
|
126
|
+
// // Calculate Y position: base offset + choice index * height
|
|
127
|
+
// const handleY = 20 + idx * 24; // Matches current: fromY + 10 + idx * 24
|
|
128
|
+
//
|
|
129
|
+
// return (
|
|
130
|
+
// <div
|
|
131
|
+
// key={choice.id}
|
|
132
|
+
// className="px-3 py-1 text-[10px] text-gray-400 flex items-center gap-2 border-b border-[#2a2a3e] last:border-0 relative"
|
|
133
|
+
// style={{ minHeight: 24 }}
|
|
134
|
+
// >
|
|
135
|
+
// <div className="flex-1 min-w-0">
|
|
136
|
+
// <span className="truncate block">{choice.text.slice(0, 25)}...</span>
|
|
137
|
+
// </div>
|
|
138
|
+
//
|
|
139
|
+
// {/* Dynamic handle for this choice */}
|
|
140
|
+
// <Handle
|
|
141
|
+
// type="source"
|
|
142
|
+
// position={Position.Right}
|
|
143
|
+
// id={`choice-${idx}`}
|
|
144
|
+
// style={{
|
|
145
|
+
// top: `${handleY}px`,
|
|
146
|
+
// right: '-6px',
|
|
147
|
+
// background: color,
|
|
148
|
+
// borderColor: color,
|
|
149
|
+
// width: '12px',
|
|
150
|
+
// height: '12px',
|
|
151
|
+
// }}
|
|
152
|
+
// className="!bg-[#2a2a3e] !border-2 hover:!border-[#e94560] hover:!bg-[#e94560]/20"
|
|
153
|
+
// />
|
|
154
|
+
// </div>
|
|
155
|
+
// );
|
|
156
|
+
// })}
|
|
157
|
+
// </div>
|
|
158
|
+
// </div>
|
|
159
|
+
// );
|
|
160
|
+
// }
|
|
161
|
+
/**
|
|
162
|
+
* Convert DialogueTree to React Flow format
|
|
163
|
+
*/
|
|
164
|
+
export function convertDialogueTreeToReactFlow(dialogue) {
|
|
165
|
+
const nodes = [];
|
|
166
|
+
const edges = [];
|
|
167
|
+
// Define node types
|
|
168
|
+
const nodeTypes = {
|
|
169
|
+
// 'npc': NPCNode,
|
|
170
|
+
// 'player': PlayerNode,
|
|
171
|
+
};
|
|
172
|
+
// Define edge types
|
|
173
|
+
const edgeTypes = {
|
|
174
|
+
// 'choice': ChoiceEdge,
|
|
175
|
+
};
|
|
176
|
+
// Convert nodes
|
|
177
|
+
Object.values(dialogue.nodes).forEach(node => {
|
|
178
|
+
nodes.push({
|
|
179
|
+
id: node.id,
|
|
180
|
+
type: node.type, // 'npc' or 'player'
|
|
181
|
+
position: { x: node.x, y: node.y },
|
|
182
|
+
data: { node },
|
|
183
|
+
selected: false,
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
// Convert edges
|
|
187
|
+
Object.values(dialogue.nodes).forEach(node => {
|
|
188
|
+
if (node.type === NODE_TYPE.NPC && node.nextNodeId) {
|
|
189
|
+
// NPC -> next node (single connection)
|
|
190
|
+
edges.push({
|
|
191
|
+
id: `${node.id}-next`,
|
|
192
|
+
source: node.id,
|
|
193
|
+
target: node.nextNodeId,
|
|
194
|
+
sourceHandle: 'next',
|
|
195
|
+
type: 'default', // Use default edge for NPC connections
|
|
196
|
+
style: { stroke: '#4a4a6a', strokeWidth: 2 },
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
if (node.type === NODE_TYPE.PLAYER && node.choices) {
|
|
200
|
+
// Player -> multiple choices (one edge per choice)
|
|
201
|
+
node.choices.forEach((choice, idx) => {
|
|
202
|
+
if (choice.nextNodeId) {
|
|
203
|
+
edges.push({
|
|
204
|
+
id: `${node.id}-choice-${idx}`,
|
|
205
|
+
source: node.id,
|
|
206
|
+
target: choice.nextNodeId,
|
|
207
|
+
sourceHandle: `choice-${idx}`, // Connect to specific choice handle
|
|
208
|
+
type: 'choice', // Use custom ChoiceEdge
|
|
209
|
+
data: {
|
|
210
|
+
choiceIndex: idx,
|
|
211
|
+
choiceId: choice.id,
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
return { nodes, edges, nodeTypes, edgeTypes };
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Main React Flow Component (POC)
|
|
222
|
+
*
|
|
223
|
+
* Usage:
|
|
224
|
+
* ```tsx
|
|
225
|
+
* <ReactFlowProvider>
|
|
226
|
+
* <ReactFlowPOC dialogue={dialogueTree} />
|
|
227
|
+
* </ReactFlowProvider>
|
|
228
|
+
* ```
|
|
229
|
+
*/
|
|
230
|
+
export function ReactFlowPOC({ dialogue }) {
|
|
231
|
+
// Convert dialogue to React Flow format
|
|
232
|
+
const { nodes, edges, nodeTypes, edgeTypes } = useMemo(() => convertDialogueTreeToReactFlow(dialogue), [dialogue]);
|
|
233
|
+
// Handle node changes (drag, etc.)
|
|
234
|
+
const onNodesChange = useCallback((changes) => {
|
|
235
|
+
// Update dialogue tree positions
|
|
236
|
+
// This would sync back to our DialogueTree structure
|
|
237
|
+
console.log('Nodes changed:', changes);
|
|
238
|
+
}, []);
|
|
239
|
+
// Handle edge connections
|
|
240
|
+
const onConnect = useCallback((connection) => {
|
|
241
|
+
// Handle new edge connections
|
|
242
|
+
// This would update our DialogueTree structure
|
|
243
|
+
console.log('Connected:', connection);
|
|
244
|
+
}, []);
|
|
245
|
+
// Handle edge changes (delete, etc.)
|
|
246
|
+
const onEdgesChange = useCallback((changes) => {
|
|
247
|
+
console.log('Edges changed:', changes);
|
|
248
|
+
}, []);
|
|
249
|
+
return (React.createElement("div", { className: "w-full h-full" },
|
|
250
|
+
React.createElement("div", { className: "p-8 text-gray-400" },
|
|
251
|
+
React.createElement("h2", { className: "text-xl font-bold mb-4 text-white" }, "React Flow POC"),
|
|
252
|
+
React.createElement("p", { className: "mb-4" }, "This is a proof of concept for migrating to React Flow."),
|
|
253
|
+
React.createElement("p", { className: "mb-4" }, "To use this:"),
|
|
254
|
+
React.createElement("ol", { className: "list-decimal list-inside space-y-2 mb-4" },
|
|
255
|
+
React.createElement("li", null,
|
|
256
|
+
"Install React Flow: ",
|
|
257
|
+
React.createElement("code", { className: "bg-[#1a1a2e] px-2 py-1 rounded" }, "npm install reactflow")),
|
|
258
|
+
React.createElement("li", null, "Uncomment the React Flow imports and components"),
|
|
259
|
+
React.createElement("li", null, "Test with your dialogue tree")),
|
|
260
|
+
React.createElement("div", { className: "mt-8 p-4 bg-[#1a1a2e] rounded" },
|
|
261
|
+
React.createElement("h3", { className: "font-semibold mb-2" }, "Key Features:"),
|
|
262
|
+
React.createElement("ul", { className: "list-disc list-inside space-y-1 text-sm" },
|
|
263
|
+
React.createElement("li", null, "\u2705 Custom ChoiceEdge component with color-coded edges"),
|
|
264
|
+
React.createElement("li", null, "\u2705 Dynamic handles on PlayerNode (one per choice)"),
|
|
265
|
+
React.createElement("li", null, "\u2705 NPCNode with single output handle"),
|
|
266
|
+
React.createElement("li", null, "\u2705 Conversion functions (DialogueTree \u2194 React Flow)"),
|
|
267
|
+
React.createElement("li", null, "\u2705 Matches current visual style"))),
|
|
268
|
+
React.createElement("div", { className: "mt-4 p-4 bg-[#1a1a2e] rounded" },
|
|
269
|
+
React.createElement("h3", { className: "font-semibold mb-2" }, "Current Stats:"),
|
|
270
|
+
React.createElement("p", { className: "text-sm" },
|
|
271
|
+
"Nodes: ",
|
|
272
|
+
nodes.length,
|
|
273
|
+
", Edges: ",
|
|
274
|
+
edges.length)))));
|
|
275
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { DialogueTree, DialogueNode, Choice } from '../types';
|
|
3
|
+
import { FlagState, DialogueResult } from '../types/game-state';
|
|
4
|
+
import { type FlattenConfig } from '../utils/game-state-flattener';
|
|
5
|
+
export interface ScenePlayerProps {
|
|
6
|
+
dialogue: DialogueTree;
|
|
7
|
+
gameState: Record<string, any>;
|
|
8
|
+
startNodeId?: string;
|
|
9
|
+
onComplete: (result: DialogueResult) => void;
|
|
10
|
+
onFlagUpdate?: (flags: FlagState) => void;
|
|
11
|
+
flattenConfig?: FlattenConfig;
|
|
12
|
+
onNodeEnter?: (nodeId: string, node: DialogueNode) => void;
|
|
13
|
+
onNodeExit?: (nodeId: string, node: DialogueNode) => void;
|
|
14
|
+
onChoiceSelect?: (nodeId: string, choice: Choice) => void;
|
|
15
|
+
onDialogueStart?: () => void;
|
|
16
|
+
onDialogueEnd?: () => void;
|
|
17
|
+
}
|
|
18
|
+
export declare function ScenePlayer({ dialogue, gameState, startNodeId, onComplete, onFlagUpdate, flattenConfig, onNodeEnter, onNodeExit, onChoiceSelect, onDialogueStart, onDialogueEnd, }: ScenePlayerProps): React.JSX.Element;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import React, { useState, useEffect, useRef, useMemo } from 'react';
|
|
2
|
+
import { mergeFlagUpdates } from '../lib/flag-manager';
|
|
3
|
+
import { validateGameState, extractFlagsFromGameState } from '../utils/game-state-flattener';
|
|
4
|
+
export function ScenePlayer({ dialogue, gameState, startNodeId, onComplete, onFlagUpdate, flattenConfig, onNodeEnter, onNodeExit, onChoiceSelect, onDialogueStart, onDialogueEnd, }) {
|
|
5
|
+
// Validate and extract flags from gameState
|
|
6
|
+
const initialFlags = useMemo(() => {
|
|
7
|
+
try {
|
|
8
|
+
validateGameState(gameState);
|
|
9
|
+
return extractFlagsFromGameState(gameState, flattenConfig);
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
console.error('ScenePlayer: Invalid gameState', error);
|
|
13
|
+
throw error;
|
|
14
|
+
}
|
|
15
|
+
}, [gameState, flattenConfig]);
|
|
16
|
+
const [currentNodeId, setCurrentNodeId] = useState(startNodeId || dialogue.startNodeId);
|
|
17
|
+
const [flags, setFlags] = useState(initialFlags);
|
|
18
|
+
const [history, setHistory] = useState([]);
|
|
19
|
+
const [isTyping, setIsTyping] = useState(false);
|
|
20
|
+
const [visitedNodes, setVisitedNodes] = useState(new Set());
|
|
21
|
+
const chatEndRef = useRef(null);
|
|
22
|
+
// Re-extract flags when gameState changes
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
try {
|
|
25
|
+
validateGameState(gameState);
|
|
26
|
+
const newFlags = extractFlagsFromGameState(gameState, flattenConfig);
|
|
27
|
+
setFlags(newFlags);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
console.error('ScenePlayer: Failed to update flags from gameState', error);
|
|
31
|
+
}
|
|
32
|
+
}, [gameState, flattenConfig]);
|
|
33
|
+
// Initialize dialogue
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (currentNodeId === dialogue.startNodeId) {
|
|
36
|
+
onDialogueStart?.();
|
|
37
|
+
}
|
|
38
|
+
}, []); // Only on mount
|
|
39
|
+
// Process current node
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
const node = dialogue.nodes[currentNodeId];
|
|
42
|
+
if (!node)
|
|
43
|
+
return;
|
|
44
|
+
// Call onNodeEnter hook
|
|
45
|
+
onNodeEnter?.(currentNodeId, node);
|
|
46
|
+
if (node.type === 'npc') {
|
|
47
|
+
setIsTyping(true);
|
|
48
|
+
const timer = setTimeout(() => {
|
|
49
|
+
// Mark as visited
|
|
50
|
+
setVisitedNodes(prev => new Set([...prev, node.id]));
|
|
51
|
+
// Update flags
|
|
52
|
+
if (node.setFlags && node.setFlags.length > 0) {
|
|
53
|
+
const updated = mergeFlagUpdates(flags, node.setFlags);
|
|
54
|
+
setFlags(updated);
|
|
55
|
+
onFlagUpdate?.(updated);
|
|
56
|
+
}
|
|
57
|
+
// Add to history
|
|
58
|
+
setHistory(prev => [...prev, {
|
|
59
|
+
nodeId: node.id,
|
|
60
|
+
type: 'npc',
|
|
61
|
+
speaker: node.speaker,
|
|
62
|
+
content: node.content
|
|
63
|
+
}]);
|
|
64
|
+
setIsTyping(false);
|
|
65
|
+
// Call onNodeExit before moving to next
|
|
66
|
+
onNodeExit?.(currentNodeId, node);
|
|
67
|
+
// Auto-advance if there's a next node
|
|
68
|
+
if (node.nextNodeId) {
|
|
69
|
+
setTimeout(() => setCurrentNodeId(node.nextNodeId), 300);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
// Dialogue complete
|
|
73
|
+
onDialogueEnd?.();
|
|
74
|
+
onComplete({
|
|
75
|
+
updatedFlags: flags,
|
|
76
|
+
dialogueTree: dialogue,
|
|
77
|
+
completedNodeIds: Array.from(visitedNodes)
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}, 500);
|
|
81
|
+
return () => clearTimeout(timer);
|
|
82
|
+
}
|
|
83
|
+
}, [currentNodeId, dialogue, flags, onNodeEnter, onNodeExit, onDialogueEnd, onComplete, onFlagUpdate]);
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
chatEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
|
86
|
+
}, [history, isTyping]);
|
|
87
|
+
const currentNode = dialogue.nodes[currentNodeId];
|
|
88
|
+
// Filter choices based on conditions and flags
|
|
89
|
+
const availableChoices = currentNode?.choices?.filter(choice => {
|
|
90
|
+
if (!choice.conditions)
|
|
91
|
+
return true;
|
|
92
|
+
return choice.conditions.every(cond => {
|
|
93
|
+
const flagValue = flags[cond.flag];
|
|
94
|
+
const hasFlag = flagValue !== undefined && flagValue !== false && flagValue !== 0 && flagValue !== '';
|
|
95
|
+
return cond.operator === 'is_set' ? hasFlag : !hasFlag;
|
|
96
|
+
});
|
|
97
|
+
}) || [];
|
|
98
|
+
const handleChoice = (choice) => {
|
|
99
|
+
const currentNode = dialogue.nodes[currentNodeId];
|
|
100
|
+
// Call onChoiceSelect hook
|
|
101
|
+
onChoiceSelect?.(currentNodeId, choice);
|
|
102
|
+
// Call onNodeExit for current player node
|
|
103
|
+
if (currentNode) {
|
|
104
|
+
onNodeExit?.(currentNodeId, currentNode);
|
|
105
|
+
}
|
|
106
|
+
// Add to history
|
|
107
|
+
setHistory(prev => [...prev, {
|
|
108
|
+
nodeId: choice.id,
|
|
109
|
+
type: 'player',
|
|
110
|
+
content: choice.text
|
|
111
|
+
}]);
|
|
112
|
+
// Update flags
|
|
113
|
+
if (choice.setFlags && choice.setFlags.length > 0) {
|
|
114
|
+
const updated = mergeFlagUpdates(flags, choice.setFlags);
|
|
115
|
+
setFlags(updated);
|
|
116
|
+
onFlagUpdate?.(updated);
|
|
117
|
+
}
|
|
118
|
+
// Move to next node
|
|
119
|
+
if (choice.nextNodeId) {
|
|
120
|
+
setCurrentNodeId(choice.nextNodeId);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
// Choice leads nowhere - dialogue complete
|
|
124
|
+
onComplete({
|
|
125
|
+
updatedFlags: flags,
|
|
126
|
+
dialogueTree: dialogue,
|
|
127
|
+
completedNodeIds: Array.from(visitedNodes)
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
return (React.createElement("div", { className: "flex-1 flex flex-col" },
|
|
132
|
+
React.createElement("div", { className: "flex-1 overflow-y-auto p-4" },
|
|
133
|
+
React.createElement("div", { className: "max-w-2xl mx-auto space-y-4" },
|
|
134
|
+
history.map((entry, idx) => (React.createElement("div", { key: idx, className: `flex ${entry.type === 'player' ? 'justify-end' : 'justify-start'}` },
|
|
135
|
+
React.createElement("div", { className: `max-w-[80%] rounded-2xl px-4 py-3 ${entry.type === 'player'
|
|
136
|
+
? 'bg-[#e94560] text-white rounded-br-md'
|
|
137
|
+
: 'bg-[#1a1a2e] text-gray-100 rounded-bl-md'}` },
|
|
138
|
+
entry.type === 'npc' && entry.speaker && (React.createElement("div", { className: "text-xs text-[#e94560] font-medium mb-1" }, entry.speaker)),
|
|
139
|
+
React.createElement("div", { className: "whitespace-pre-wrap" }, entry.content))))),
|
|
140
|
+
isTyping && (React.createElement("div", { className: "flex justify-start" },
|
|
141
|
+
React.createElement("div", { className: "bg-[#1a1a2e] rounded-2xl rounded-bl-md px-4 py-3" },
|
|
142
|
+
React.createElement("div", { className: "flex gap-1" },
|
|
143
|
+
React.createElement("span", { className: "w-2 h-2 bg-[#e94560] rounded-full animate-bounce", style: { animationDelay: '0ms' } }),
|
|
144
|
+
React.createElement("span", { className: "w-2 h-2 bg-[#e94560] rounded-full animate-bounce", style: { animationDelay: '150ms' } }),
|
|
145
|
+
React.createElement("span", { className: "w-2 h-2 bg-[#e94560] rounded-full animate-bounce", style: { animationDelay: '300ms' } }))))),
|
|
146
|
+
React.createElement("div", { ref: chatEndRef }))),
|
|
147
|
+
currentNode?.type === 'player' && !isTyping && availableChoices.length > 0 && (React.createElement("div", { className: "border-t border-[#1a1a2e] bg-[#0d0d14]/80 backdrop-blur-sm p-4" },
|
|
148
|
+
React.createElement("div", { className: "max-w-2xl mx-auto space-y-2" }, availableChoices.map((choice) => (React.createElement("button", { key: choice.id, onClick: () => handleChoice(choice), className: "w-full text-left px-4 py-3 rounded-lg border border-[#2a2a3e] hover:border-[#e94560] bg-[#12121a] hover:bg-[#1a1a2e] text-gray-200 transition-all group flex items-center justify-between" },
|
|
149
|
+
React.createElement("span", null, choice.text),
|
|
150
|
+
React.createElement("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", className: "text-gray-600 group-hover:text-[#e94560] transition-colors" },
|
|
151
|
+
React.createElement("polyline", { points: "9 18 15 12 9 6" })))))))),
|
|
152
|
+
currentNode?.type === 'npc' && !currentNode.nextNodeId && !isTyping && (React.createElement("div", { className: "border-t border-[#1a1a2e] bg-[#0d0d14]/80 backdrop-blur-sm p-4" },
|
|
153
|
+
React.createElement("div", { className: "max-w-2xl mx-auto text-center" },
|
|
154
|
+
React.createElement("p", { className: "text-gray-500 mb-3" }, "Dialogue complete"),
|
|
155
|
+
React.createElement("button", { onClick: () => onComplete({
|
|
156
|
+
updatedFlags: flags,
|
|
157
|
+
dialogueTree: dialogue,
|
|
158
|
+
completedNodeIds: Array.from(visitedNodes)
|
|
159
|
+
}), className: "px-4 py-2 bg-[#e94560] hover:bg-[#d63850] text-white rounded-lg transition-colors" }, "Close"))))));
|
|
160
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { DialogueTree } from '../types';
|
|
3
|
+
interface YarnViewProps {
|
|
4
|
+
dialogue: DialogueTree;
|
|
5
|
+
onExport: () => void;
|
|
6
|
+
onImport?: (yarn: string) => void;
|
|
7
|
+
}
|
|
8
|
+
export declare function YarnView({ dialogue, onExport, onImport }: YarnViewProps): React.JSX.Element;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { exportToYarn } from '../lib/yarn-converter';
|
|
3
|
+
export function YarnView({ dialogue, onExport, onImport }) {
|
|
4
|
+
const fileInputRef = React.useRef(null);
|
|
5
|
+
const handleImport = (e) => {
|
|
6
|
+
const file = e.target.files?.[0];
|
|
7
|
+
if (!file)
|
|
8
|
+
return;
|
|
9
|
+
const reader = new FileReader();
|
|
10
|
+
reader.onload = (ev) => {
|
|
11
|
+
const content = ev.target?.result;
|
|
12
|
+
if (onImport) {
|
|
13
|
+
onImport(content);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
reader.readAsText(file);
|
|
17
|
+
e.target.value = '';
|
|
18
|
+
};
|
|
19
|
+
return (React.createElement("main", { className: "flex-1 flex flex-col bg-[#0d0d14] overflow-hidden" },
|
|
20
|
+
React.createElement("div", { className: "border-b border-[#1a1a2e] px-4 py-2 flex items-center justify-between flex-shrink-0" },
|
|
21
|
+
React.createElement("span", { className: "text-sm text-gray-400" }, "Yarn Spinner Output"),
|
|
22
|
+
React.createElement("div", { className: "flex items-center gap-2" },
|
|
23
|
+
onImport && (React.createElement(React.Fragment, null,
|
|
24
|
+
React.createElement("button", { onClick: () => fileInputRef.current?.click(), className: "px-3 py-1.5 bg-[#2a2a3e] hover:bg-[#3a3a4e] text-gray-300 text-sm rounded flex items-center gap-2" },
|
|
25
|
+
React.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" },
|
|
26
|
+
React.createElement("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
|
|
27
|
+
React.createElement("polyline", { points: "17 8 12 3 7 8" }),
|
|
28
|
+
React.createElement("line", { x1: "12", y1: "3", x2: "12", y2: "15" })),
|
|
29
|
+
"Import .yarn"),
|
|
30
|
+
React.createElement("input", { ref: fileInputRef, type: "file", accept: ".yarn", onChange: handleImport, className: "hidden" }))),
|
|
31
|
+
React.createElement("button", { onClick: onExport, className: "px-3 py-1.5 bg-[#e94560] hover:bg-[#d63850] text-white text-sm rounded flex items-center gap-2" },
|
|
32
|
+
React.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" },
|
|
33
|
+
React.createElement("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
|
|
34
|
+
React.createElement("polyline", { points: "7 10 12 15 17 10" }),
|
|
35
|
+
React.createElement("line", { x1: "12", y1: "15", x2: "12", y2: "3" })),
|
|
36
|
+
"Download .yarn"))),
|
|
37
|
+
React.createElement("div", { className: "flex-1 overflow-y-auto p-4 min-h-0" },
|
|
38
|
+
React.createElement("pre", { className: "font-mono text-sm text-gray-300 whitespace-pre-wrap bg-[#08080c] rounded-lg p-4 border border-[#1a1a2e]" }, exportToYarn(dialogue)))));
|
|
39
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface ZoomControlsProps {
|
|
3
|
+
scale: number;
|
|
4
|
+
onZoomIn: () => void;
|
|
5
|
+
onZoomOut: () => void;
|
|
6
|
+
onZoomFit: () => void;
|
|
7
|
+
onZoomToSelection?: () => void;
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function ZoomControls({ scale, onZoomIn, onZoomOut, onZoomFit, onZoomToSelection, className }: ZoomControlsProps): React.JSX.Element;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export function ZoomControls({ scale, onZoomIn, onZoomOut, onZoomFit, onZoomToSelection, className }) {
|
|
3
|
+
const zoomPercent = Math.round(scale * 100);
|
|
4
|
+
return (React.createElement("div", { className: `flex flex-col gap-1 ${className}` },
|
|
5
|
+
React.createElement("div", { className: "bg-[#0d0d14] border border-[#2a2a3e] rounded px-2 py-1 text-xs text-gray-400 text-center mb-1" },
|
|
6
|
+
zoomPercent,
|
|
7
|
+
"%"),
|
|
8
|
+
React.createElement("div", { className: "flex flex-col gap-1 bg-[#0d0d14] border border-[#2a2a3e] rounded-lg p-1" },
|
|
9
|
+
React.createElement("button", { onClick: onZoomIn, className: "p-1.5 hover:bg-[#1a1a2e] rounded text-gray-400 hover:text-white transition-colors", title: "Zoom In (Ctrl +)" },
|
|
10
|
+
React.createElement("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" },
|
|
11
|
+
React.createElement("circle", { cx: "11", cy: "11", r: "8" }),
|
|
12
|
+
React.createElement("line", { x1: "11", y1: "8", x2: "11", y2: "14" }),
|
|
13
|
+
React.createElement("line", { x1: "8", y1: "11", x2: "14", y2: "11" }))),
|
|
14
|
+
React.createElement("button", { onClick: onZoomOut, className: "p-1.5 hover:bg-[#1a1a2e] rounded text-gray-400 hover:text-white transition-colors", title: "Zoom Out (Ctrl -)" },
|
|
15
|
+
React.createElement("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" },
|
|
16
|
+
React.createElement("circle", { cx: "11", cy: "11", r: "8" }),
|
|
17
|
+
React.createElement("line", { x1: "8", y1: "11", x2: "14", y2: "11" }))),
|
|
18
|
+
React.createElement("div", { className: "h-px bg-[#2a2a3e] my-1" }),
|
|
19
|
+
React.createElement("button", { onClick: onZoomFit, className: "p-1.5 hover:bg-[#1a1a2e] rounded text-gray-400 hover:text-white transition-colors", title: "Zoom to Fit (Ctrl 0)" },
|
|
20
|
+
React.createElement("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" },
|
|
21
|
+
React.createElement("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }),
|
|
22
|
+
React.createElement("path", { d: "M8 8h8v8" }))),
|
|
23
|
+
onZoomToSelection && (React.createElement("button", { onClick: onZoomToSelection, className: "p-1.5 hover:bg-[#1a1a2e] rounded text-gray-400 hover:text-white transition-colors", title: "Zoom to Selection" },
|
|
24
|
+
React.createElement("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" },
|
|
25
|
+
React.createElement("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }),
|
|
26
|
+
React.createElement("path", { d: "M8 8h8v8" }),
|
|
27
|
+
React.createElement("circle", { cx: "12", cy: "12", r: "2" })))))));
|
|
28
|
+
}
|
|
@@ -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;
|