@magicborn/dialogue-forge 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -0
- package/bin/postbuild.js +69 -0
- package/dist/components/DialogueEditorV2.js +5 -4
- package/dist/esm/components/DialogueEditorV2.js +5 -4
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.js +2 -2
- package/dist/esm/types/constants.d.ts +9 -0
- package/dist/esm/types/constants.js +8 -0
- package/dist/esm/types/index.d.ts +1 -1
- package/dist/styles/scrollbar.css +108 -0
- package/dist/styles/theme.css +106 -0
- package/dist/types/constants.d.ts +9 -0
- package/dist/types/constants.js +9 -1
- package/dist/types/index.d.ts +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -156,6 +156,89 @@ See [DATA_STRUCTURES.md](./DATA_STRUCTURES.md) for complete type documentation a
|
|
|
156
156
|
- `GameFlagState` - Current flag values `{ [flagId]: value }`
|
|
157
157
|
- `DialogueResult` - Result from running dialogue (updated flags, visited nodes)
|
|
158
158
|
|
|
159
|
+
### Dialogue Editor V2 with View Modes
|
|
160
|
+
|
|
161
|
+
`DialogueEditorV2` supports graph, yarn, and play views. Use the exported `VIEW_MODE` constant (instead of string literals) alongside the `ViewMode` type so consumers get auto-complete and type safety when switching views.
|
|
162
|
+
|
|
163
|
+
```tsx
|
|
164
|
+
import { DialogueEditorV2, VIEW_MODE, type DialogueTree, type ViewMode } from '@magicborn/dialogue-forge';
|
|
165
|
+
import { useState } from 'react';
|
|
166
|
+
|
|
167
|
+
export function DialogueEditorDemo() {
|
|
168
|
+
const [dialogue, setDialogue] = useState<DialogueTree | null>(null);
|
|
169
|
+
const [viewMode, setViewMode] = useState<ViewMode>(VIEW_MODE.GRAPH);
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
<DialogueEditorV2
|
|
173
|
+
dialogue={dialogue}
|
|
174
|
+
onChange={setDialogue}
|
|
175
|
+
viewMode={viewMode}
|
|
176
|
+
onViewModeChange={setViewMode}
|
|
177
|
+
/>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Authoring dialogue data programmatically
|
|
183
|
+
|
|
184
|
+
You can build dialogue content without the editor and still take advantage of the types and constants that power the scene player. This makes it easy to script nonlinear stories, export them, or feed them directly into `ScenePlayer`.
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
import {
|
|
188
|
+
NODE_TYPE,
|
|
189
|
+
type DialogueTree,
|
|
190
|
+
type DialogueNode,
|
|
191
|
+
ScenePlayer,
|
|
192
|
+
} from '@magicborn/dialogue-forge';
|
|
193
|
+
|
|
194
|
+
const nodes: Record<string, DialogueNode> = {
|
|
195
|
+
npc_1: {
|
|
196
|
+
id: 'npc_1',
|
|
197
|
+
type: NODE_TYPE.NPC,
|
|
198
|
+
speaker: 'Merchant',
|
|
199
|
+
content: 'Welcome, traveler! Looking for supplies?',
|
|
200
|
+
nextNodeId: 'player_1',
|
|
201
|
+
x: 0,
|
|
202
|
+
y: 0,
|
|
203
|
+
},
|
|
204
|
+
player_1: {
|
|
205
|
+
id: 'player_1',
|
|
206
|
+
type: NODE_TYPE.PLAYER,
|
|
207
|
+
content: ' ',
|
|
208
|
+
choices: [
|
|
209
|
+
{ id: 'buy', text: 'Show me your wares.', nextNodeId: 'npc_2' },
|
|
210
|
+
{ id: 'chat', text: 'Any rumors?', nextNodeId: 'npc_3' },
|
|
211
|
+
],
|
|
212
|
+
x: 320,
|
|
213
|
+
y: 0,
|
|
214
|
+
},
|
|
215
|
+
npc_2: {
|
|
216
|
+
id: 'npc_2',
|
|
217
|
+
type: NODE_TYPE.NPC,
|
|
218
|
+
content: 'Take a look! Fresh stock just arrived.',
|
|
219
|
+
x: 640,
|
|
220
|
+
y: -120,
|
|
221
|
+
},
|
|
222
|
+
npc_3: {
|
|
223
|
+
id: 'npc_3',
|
|
224
|
+
type: NODE_TYPE.NPC,
|
|
225
|
+
content: 'Bandits have been spotted near the bridge—stay sharp.',
|
|
226
|
+
x: 640,
|
|
227
|
+
y: 120,
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const merchantIntro: DialogueTree = {
|
|
232
|
+
id: 'merchant_intro',
|
|
233
|
+
title: 'Merchant Greeting',
|
|
234
|
+
startNodeId: 'npc_1',
|
|
235
|
+
nodes,
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// Render or test the dialogue without the editor
|
|
239
|
+
// <ScenePlayer dialogue={merchantIntro} gameState={yourGameState} onComplete={...} />
|
|
240
|
+
```
|
|
241
|
+
|
|
159
242
|
## Complete Example
|
|
160
243
|
|
|
161
244
|
```typescript
|
package/bin/postbuild.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const srcStylesDir = path.join(__dirname, '..', 'src', 'styles');
|
|
5
|
+
const distStylesDir = path.join(__dirname, '..', 'dist', 'styles');
|
|
6
|
+
|
|
7
|
+
function copyStyles() {
|
|
8
|
+
if (!fs.existsSync(srcStylesDir)) return;
|
|
9
|
+
fs.mkdirSync(distStylesDir, { recursive: true });
|
|
10
|
+
const files = fs.readdirSync(srcStylesDir).filter((file) => file.endsWith('.css'));
|
|
11
|
+
files.forEach((file) => {
|
|
12
|
+
fs.copyFileSync(path.join(srcStylesDir, file), path.join(distStylesDir, file));
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function updateEsmEntryPoints() {
|
|
17
|
+
const esmEntries = [
|
|
18
|
+
path.join(__dirname, '..', 'dist', 'esm', 'index.js'),
|
|
19
|
+
path.join(__dirname, '..', 'dist', 'esm', 'index.d.ts')
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
esmEntries.forEach((filePath) => {
|
|
23
|
+
if (!fs.existsSync(filePath)) return;
|
|
24
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
25
|
+
const updated = content.replace(/\.\/styles\//g, '../styles/');
|
|
26
|
+
fs.writeFileSync(filePath, updated);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function verifyCssPaths() {
|
|
31
|
+
const checks = [
|
|
32
|
+
{
|
|
33
|
+
filePath: path.join(__dirname, '..', 'dist', 'index.js'),
|
|
34
|
+
cssImports: ['./styles/scrollbar.css', './styles/theme.css']
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
filePath: path.join(__dirname, '..', 'dist', 'index.d.ts'),
|
|
38
|
+
cssImports: ['./styles/scrollbar.css', './styles/theme.css']
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
filePath: path.join(__dirname, '..', 'dist', 'esm', 'index.js'),
|
|
42
|
+
cssImports: ['../styles/scrollbar.css', '../styles/theme.css']
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
filePath: path.join(__dirname, '..', 'dist', 'esm', 'index.d.ts'),
|
|
46
|
+
cssImports: ['../styles/scrollbar.css', '../styles/theme.css']
|
|
47
|
+
}
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
checks.forEach(({ filePath, cssImports }) => {
|
|
51
|
+
if (!fs.existsSync(filePath)) return;
|
|
52
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
53
|
+
|
|
54
|
+
cssImports.forEach((cssPath) => {
|
|
55
|
+
if (!content.includes(cssPath)) {
|
|
56
|
+
throw new Error(`Missing CSS import ${cssPath} in ${path.relative(process.cwd(), filePath)}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const resolved = path.resolve(path.dirname(filePath), cssPath);
|
|
60
|
+
if (!fs.existsSync(resolved)) {
|
|
61
|
+
throw new Error(`CSS asset not found for ${cssPath} referenced by ${path.relative(process.cwd(), filePath)}`);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
copyStyles();
|
|
68
|
+
updateEsmEntryPoints();
|
|
69
|
+
verifyCssPaths();
|
|
@@ -58,6 +58,7 @@ const PlayerNodeV2_1 = require("./PlayerNodeV2");
|
|
|
58
58
|
const ConditionalNodeV2_1 = require("./ConditionalNodeV2");
|
|
59
59
|
const ChoiceEdgeV2_1 = require("./ChoiceEdgeV2");
|
|
60
60
|
const NPCEdgeV2_1 = require("./NPCEdgeV2");
|
|
61
|
+
const constants_1 = require("../types/constants");
|
|
61
62
|
// Define node and edge types outside component for stability
|
|
62
63
|
const nodeTypes = {
|
|
63
64
|
npc: NPCNodeV2_1.NPCNodeV2,
|
|
@@ -68,7 +69,7 @@ const edgeTypes = {
|
|
|
68
69
|
choice: ChoiceEdgeV2_1.ChoiceEdgeV2,
|
|
69
70
|
default: NPCEdgeV2_1.NPCEdgeV2, // Use custom component for NPC edges instead of React Flow default
|
|
70
71
|
};
|
|
71
|
-
function DialogueEditorV2Internal({ dialogue, onChange, onExportYarn, onExportJSON, className = '', showTitleEditor = true, flagSchema, characters = {}, initialViewMode =
|
|
72
|
+
function DialogueEditorV2Internal({ dialogue, onChange, onExportYarn, onExportJSON, className = '', showTitleEditor = true, flagSchema, characters = {}, initialViewMode = constants_1.VIEW_MODE.GRAPH, viewMode: controlledViewMode, onViewModeChange, layoutStrategy: propLayoutStrategy = 'dagre', // Accept from parent
|
|
72
73
|
onLayoutStrategyChange, onOpenFlagManager, onOpenGuide, onLoadExampleDialogue, onLoadExampleFlags,
|
|
73
74
|
// Event hooks
|
|
74
75
|
onNodeAdd, onNodeDelete, onNodeUpdate, onConnect: onConnectHook, onDisconnect, onNodeSelect, onNodeDoubleClick: onNodeDoubleClickHook, }) {
|
|
@@ -898,7 +899,7 @@ onNodeAdd, onNodeDelete, onNodeUpdate, onConnect: onConnectHook, onDisconnect, o
|
|
|
898
899
|
}, 100);
|
|
899
900
|
}, [dialogue, onChange, reactFlowInstance, layoutDirection, layoutStrategy]);
|
|
900
901
|
return (react_1.default.createElement("div", { className: `dialogue-editor-v2 ${className} w-full h-full flex flex-col` },
|
|
901
|
-
viewMode ===
|
|
902
|
+
viewMode === constants_1.VIEW_MODE.GRAPH && (react_1.default.createElement("div", { className: "flex-1 flex overflow-hidden" },
|
|
902
903
|
react_1.default.createElement("div", { className: "flex-1 relative w-full h-full", ref: reactFlowWrapperRef, style: { minHeight: 0 } },
|
|
903
904
|
react_1.default.createElement(reactflow_1.default, { nodes: nodesWithFlags, edges: edges.map(edge => {
|
|
904
905
|
// Detect back-edges (loops) based on layout direction
|
|
@@ -1174,7 +1175,7 @@ onNodeAdd, onNodeDelete, onNodeUpdate, onConnect: onConnectHook, onDisconnect, o
|
|
|
1174
1175
|
}, 0);
|
|
1175
1176
|
}
|
|
1176
1177
|
}, onDelete: () => handleDeleteNode(selectedNode.id), onAddChoice: () => handleAddChoice(selectedNode.id), onUpdateChoice: (idx, updates) => handleUpdateChoice(selectedNode.id, idx, updates), onRemoveChoice: (idx) => handleRemoveChoice(selectedNode.id, idx), onClose: () => setSelectedNodeId(null), flagSchema: flagSchema })))),
|
|
1177
|
-
viewMode ===
|
|
1178
|
+
viewMode === constants_1.VIEW_MODE.YARN && (react_1.default.createElement(YarnView_1.YarnView, { dialogue: dialogue, onExport: () => {
|
|
1178
1179
|
const yarn = (0, yarn_converter_1.exportToYarn)(dialogue);
|
|
1179
1180
|
if (onExportYarn) {
|
|
1180
1181
|
onExportYarn(yarn);
|
|
@@ -1199,7 +1200,7 @@ onNodeAdd, onNodeDelete, onNodeUpdate, onConnect: onConnectHook, onDisconnect, o
|
|
|
1199
1200
|
alert('Failed to import Yarn file. Please check the format.');
|
|
1200
1201
|
}
|
|
1201
1202
|
} })),
|
|
1202
|
-
viewMode ===
|
|
1203
|
+
viewMode === constants_1.VIEW_MODE.PLAY && (react_1.default.createElement(PlayView_1.PlayView, { dialogue: dialogue, flagSchema: flagSchema }))));
|
|
1203
1204
|
}
|
|
1204
1205
|
function DialogueEditorV2(props) {
|
|
1205
1206
|
return (react_1.default.createElement(reactflow_1.ReactFlowProvider, null,
|
|
@@ -22,6 +22,7 @@ import { PlayerNodeV2 } from './PlayerNodeV2';
|
|
|
22
22
|
import { ConditionalNodeV2 } from './ConditionalNodeV2';
|
|
23
23
|
import { ChoiceEdgeV2 } from './ChoiceEdgeV2';
|
|
24
24
|
import { NPCEdgeV2 } from './NPCEdgeV2';
|
|
25
|
+
import { VIEW_MODE } from '../types/constants';
|
|
25
26
|
// Define node and edge types outside component for stability
|
|
26
27
|
const nodeTypes = {
|
|
27
28
|
npc: NPCNodeV2,
|
|
@@ -32,7 +33,7 @@ const edgeTypes = {
|
|
|
32
33
|
choice: ChoiceEdgeV2,
|
|
33
34
|
default: NPCEdgeV2, // Use custom component for NPC edges instead of React Flow default
|
|
34
35
|
};
|
|
35
|
-
function DialogueEditorV2Internal({ dialogue, onChange, onExportYarn, onExportJSON, className = '', showTitleEditor = true, flagSchema, characters = {}, initialViewMode =
|
|
36
|
+
function DialogueEditorV2Internal({ dialogue, onChange, onExportYarn, onExportJSON, className = '', showTitleEditor = true, flagSchema, characters = {}, initialViewMode = VIEW_MODE.GRAPH, viewMode: controlledViewMode, onViewModeChange, layoutStrategy: propLayoutStrategy = 'dagre', // Accept from parent
|
|
36
37
|
onLayoutStrategyChange, onOpenFlagManager, onOpenGuide, onLoadExampleDialogue, onLoadExampleFlags,
|
|
37
38
|
// Event hooks
|
|
38
39
|
onNodeAdd, onNodeDelete, onNodeUpdate, onConnect: onConnectHook, onDisconnect, onNodeSelect, onNodeDoubleClick: onNodeDoubleClickHook, }) {
|
|
@@ -862,7 +863,7 @@ onNodeAdd, onNodeDelete, onNodeUpdate, onConnect: onConnectHook, onDisconnect, o
|
|
|
862
863
|
}, 100);
|
|
863
864
|
}, [dialogue, onChange, reactFlowInstance, layoutDirection, layoutStrategy]);
|
|
864
865
|
return (React.createElement("div", { className: `dialogue-editor-v2 ${className} w-full h-full flex flex-col` },
|
|
865
|
-
viewMode ===
|
|
866
|
+
viewMode === VIEW_MODE.GRAPH && (React.createElement("div", { className: "flex-1 flex overflow-hidden" },
|
|
866
867
|
React.createElement("div", { className: "flex-1 relative w-full h-full", ref: reactFlowWrapperRef, style: { minHeight: 0 } },
|
|
867
868
|
React.createElement(ReactFlow, { nodes: nodesWithFlags, edges: edges.map(edge => {
|
|
868
869
|
// Detect back-edges (loops) based on layout direction
|
|
@@ -1138,7 +1139,7 @@ onNodeAdd, onNodeDelete, onNodeUpdate, onConnect: onConnectHook, onDisconnect, o
|
|
|
1138
1139
|
}, 0);
|
|
1139
1140
|
}
|
|
1140
1141
|
}, onDelete: () => handleDeleteNode(selectedNode.id), onAddChoice: () => handleAddChoice(selectedNode.id), onUpdateChoice: (idx, updates) => handleUpdateChoice(selectedNode.id, idx, updates), onRemoveChoice: (idx) => handleRemoveChoice(selectedNode.id, idx), onClose: () => setSelectedNodeId(null), flagSchema: flagSchema })))),
|
|
1141
|
-
viewMode ===
|
|
1142
|
+
viewMode === VIEW_MODE.YARN && (React.createElement(YarnView, { dialogue: dialogue, onExport: () => {
|
|
1142
1143
|
const yarn = exportToYarn(dialogue);
|
|
1143
1144
|
if (onExportYarn) {
|
|
1144
1145
|
onExportYarn(yarn);
|
|
@@ -1163,7 +1164,7 @@ onNodeAdd, onNodeDelete, onNodeUpdate, onConnect: onConnectHook, onDisconnect, o
|
|
|
1163
1164
|
alert('Failed to import Yarn file. Please check the format.');
|
|
1164
1165
|
}
|
|
1165
1166
|
} })),
|
|
1166
|
-
viewMode ===
|
|
1167
|
+
viewMode === VIEW_MODE.PLAY && (React.createElement(PlayView, { dialogue: dialogue, flagSchema: flagSchema }))));
|
|
1167
1168
|
}
|
|
1168
1169
|
export function DialogueEditorV2(props) {
|
|
1169
1170
|
return (React.createElement(ReactFlowProvider, null,
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -8,8 +8,8 @@ export { FlagManager } from './components/FlagManager';
|
|
|
8
8
|
export { CharacterSelector } from './components/CharacterSelector';
|
|
9
9
|
export { ZoomControls } from './components/ZoomControls';
|
|
10
10
|
export { ExampleLoader } from './components/ExampleLoader';
|
|
11
|
-
import '
|
|
12
|
-
import '
|
|
11
|
+
import '../styles/scrollbar.css';
|
|
12
|
+
import '../styles/theme.css';
|
|
13
13
|
export { exampleDialogues, demoFlagSchemas, getExampleDialogue, getDemoFlagSchema, listExamples, listDemoFlagSchemas } from './examples';
|
|
14
14
|
export { exampleCharacters, getExampleCharacters, getExampleCharacter, listExampleCharacterIds } from './examples';
|
|
15
15
|
export * from './types';
|
package/dist/esm/index.js
CHANGED
|
@@ -9,8 +9,8 @@ export { CharacterSelector } from './components/CharacterSelector';
|
|
|
9
9
|
export { ZoomControls } from './components/ZoomControls';
|
|
10
10
|
export { ExampleLoader } from './components/ExampleLoader';
|
|
11
11
|
// Export styles
|
|
12
|
-
import '
|
|
13
|
-
import '
|
|
12
|
+
import '../styles/scrollbar.css';
|
|
13
|
+
import '../styles/theme.css';
|
|
14
14
|
// Export examples
|
|
15
15
|
export { exampleDialogues, demoFlagSchemas, getExampleDialogue, getDemoFlagSchema, listExamples, listDemoFlagSchemas } from './examples';
|
|
16
16
|
export { exampleCharacters, getExampleCharacters, getExampleCharacter, listExampleCharacterIds } from './examples';
|
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
* Type-safe constants for Dialogue Forge
|
|
3
3
|
* Use these instead of string literals for better type safety and IDE support
|
|
4
4
|
*/
|
|
5
|
+
/**
|
|
6
|
+
* View modes for DialogueEditorV2
|
|
7
|
+
*/
|
|
8
|
+
export declare const VIEW_MODE: {
|
|
9
|
+
readonly GRAPH: "graph";
|
|
10
|
+
readonly YARN: "yarn";
|
|
11
|
+
readonly PLAY: "play";
|
|
12
|
+
};
|
|
13
|
+
export type ViewMode = typeof VIEW_MODE[keyof typeof VIEW_MODE];
|
|
5
14
|
/**
|
|
6
15
|
* Node types in a dialogue tree
|
|
7
16
|
*/
|
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
* Type-safe constants for Dialogue Forge
|
|
3
3
|
* Use these instead of string literals for better type safety and IDE support
|
|
4
4
|
*/
|
|
5
|
+
/**
|
|
6
|
+
* View modes for DialogueEditorV2
|
|
7
|
+
*/
|
|
8
|
+
export const VIEW_MODE = {
|
|
9
|
+
GRAPH: 'graph',
|
|
10
|
+
YARN: 'yarn',
|
|
11
|
+
PLAY: 'play',
|
|
12
|
+
};
|
|
5
13
|
/**
|
|
6
14
|
* Node types in a dialogue tree
|
|
7
15
|
*/
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ConditionOperator, NodeType } from './constants';
|
|
2
|
+
export type { ViewMode } from './constants';
|
|
2
3
|
export interface Choice {
|
|
3
4
|
id: string;
|
|
4
5
|
text: string;
|
|
@@ -43,7 +44,6 @@ export interface DialogueTree {
|
|
|
43
44
|
nodes: Record<string, DialogueNode>;
|
|
44
45
|
}
|
|
45
46
|
import { FlagSchema } from './flags';
|
|
46
|
-
export type ViewMode = 'graph' | 'yarn' | 'play';
|
|
47
47
|
export interface DialogueEditorProps {
|
|
48
48
|
dialogue: DialogueTree | null;
|
|
49
49
|
onChange: (dialogue: DialogueTree) => void;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/* Dialogue Forge Custom Scrollbar Styles */
|
|
2
|
+
|
|
3
|
+
/* Webkit browsers (Chrome, Safari, Edge) */
|
|
4
|
+
.dialogue-forge-scrollbar::-webkit-scrollbar {
|
|
5
|
+
width: 8px;
|
|
6
|
+
height: 8px;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.dialogue-forge-scrollbar::-webkit-scrollbar-track {
|
|
10
|
+
background: rgba(15, 15, 25, 0.5);
|
|
11
|
+
border-radius: 4px;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.dialogue-forge-scrollbar::-webkit-scrollbar-thumb {
|
|
15
|
+
background: linear-gradient(180deg, rgba(99, 102, 241, 0.5), rgba(139, 92, 246, 0.5));
|
|
16
|
+
border-radius: 4px;
|
|
17
|
+
border: 1px solid rgba(99, 102, 241, 0.3);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.dialogue-forge-scrollbar::-webkit-scrollbar-thumb:hover {
|
|
21
|
+
background: linear-gradient(180deg, rgba(99, 102, 241, 0.7), rgba(139, 92, 246, 0.7));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.dialogue-forge-scrollbar::-webkit-scrollbar-corner {
|
|
25
|
+
background: transparent;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* Firefox */
|
|
29
|
+
.dialogue-forge-scrollbar {
|
|
30
|
+
scrollbar-width: thin;
|
|
31
|
+
scrollbar-color: rgba(99, 102, 241, 0.5) rgba(15, 15, 25, 0.5);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* Thin variant for smaller containers */
|
|
35
|
+
.dialogue-forge-scrollbar-thin::-webkit-scrollbar {
|
|
36
|
+
width: 4px;
|
|
37
|
+
height: 4px;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.dialogue-forge-scrollbar-thin::-webkit-scrollbar-track {
|
|
41
|
+
background: transparent;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.dialogue-forge-scrollbar-thin::-webkit-scrollbar-thumb {
|
|
45
|
+
background: rgba(99, 102, 241, 0.4);
|
|
46
|
+
border-radius: 2px;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.dialogue-forge-scrollbar-thin::-webkit-scrollbar-thumb:hover {
|
|
50
|
+
background: rgba(99, 102, 241, 0.6);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.dialogue-forge-scrollbar-thin {
|
|
54
|
+
scrollbar-width: thin;
|
|
55
|
+
scrollbar-color: rgba(99, 102, 241, 0.4) transparent;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* Hide scrollbar but keep functionality */
|
|
59
|
+
.dialogue-forge-scrollbar-hidden {
|
|
60
|
+
-ms-overflow-style: none;
|
|
61
|
+
scrollbar-width: none;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.dialogue-forge-scrollbar-hidden::-webkit-scrollbar {
|
|
65
|
+
display: none;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* React Flow Controls Styling */
|
|
69
|
+
.react-flow__controls {
|
|
70
|
+
background: #0d0d14 !important;
|
|
71
|
+
border: 1px solid #2a2a3e !important;
|
|
72
|
+
border-radius: 8px !important;
|
|
73
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3) !important;
|
|
74
|
+
padding: 4px !important;
|
|
75
|
+
gap: 2px !important;
|
|
76
|
+
display: flex !important;
|
|
77
|
+
flex-direction: column !important;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.react-flow__controls-button {
|
|
81
|
+
background: #12121a !important;
|
|
82
|
+
border: 1px solid #2a2a3e !important;
|
|
83
|
+
border-radius: 4px !important;
|
|
84
|
+
width: 24px !important;
|
|
85
|
+
height: 24px !important;
|
|
86
|
+
padding: 4px !important;
|
|
87
|
+
display: flex !important;
|
|
88
|
+
align-items: center !important;
|
|
89
|
+
justify-content: center !important;
|
|
90
|
+
transition: all 0.15s ease !important;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.react-flow__controls-button:hover {
|
|
94
|
+
background: #1a1a2e !important;
|
|
95
|
+
border-color: #3a3a4e !important;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.react-flow__controls-button svg {
|
|
99
|
+
fill: #6b7280 !important;
|
|
100
|
+
max-width: 14px !important;
|
|
101
|
+
max-height: 14px !important;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.react-flow__controls-button:hover svg {
|
|
105
|
+
fill: #e5e7eb !important;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/* Dialogue Forge Theme - Dark Fantasy, Earthy Tones */
|
|
2
|
+
/* CSS Variables for Tailwind v3 compatibility */
|
|
3
|
+
|
|
4
|
+
:root {
|
|
5
|
+
/* Base Colors */
|
|
6
|
+
--color-df-base: oklch(0.15 0.02 250);
|
|
7
|
+
--color-df-surface: oklch(0.18 0.02 260);
|
|
8
|
+
--color-df-elevated: oklch(0.22 0.02 270);
|
|
9
|
+
|
|
10
|
+
/* Node Colors - General */
|
|
11
|
+
--color-df-node-bg: oklch(0.20 0.03 240);
|
|
12
|
+
--color-df-node-border: oklch(0.35 0.05 220);
|
|
13
|
+
--color-df-node-selected: oklch(0.45 0.12 15);
|
|
14
|
+
--color-df-node-dimmed: oklch(0.15 0.02 250);
|
|
15
|
+
|
|
16
|
+
/* NPC Node Colors - Earthy Brown/Tan (duller borders, bright when selected) */
|
|
17
|
+
--color-df-npc-bg: oklch(0.25 0.04 45);
|
|
18
|
+
--color-df-npc-border: oklch(0.35 0.05 35);
|
|
19
|
+
--color-df-npc-header: oklch(0.30 0.10 25);
|
|
20
|
+
--color-df-npc-text: oklch(0.85 0.02 250);
|
|
21
|
+
--color-df-npc-selected: oklch(0.60 0.20 15);
|
|
22
|
+
|
|
23
|
+
/* Player Node Colors - Deep Purple/Magenta (duller borders, bright when selected) */
|
|
24
|
+
--color-df-player-bg: oklch(0.22 0.08 300);
|
|
25
|
+
--color-df-player-border: oklch(0.38 0.10 310);
|
|
26
|
+
--color-df-player-header: oklch(0.28 0.12 290);
|
|
27
|
+
--color-df-player-text: oklch(0.85 0.02 250);
|
|
28
|
+
--color-df-player-selected: oklch(0.65 0.25 280);
|
|
29
|
+
|
|
30
|
+
/* Conditional Node Colors - Forest Green (duller borders, bright when selected) */
|
|
31
|
+
--color-df-conditional-bg: oklch(0.24 0.06 150);
|
|
32
|
+
--color-df-conditional-border: oklch(0.35 0.08 140);
|
|
33
|
+
--color-df-conditional-header: oklch(0.30 0.10 145);
|
|
34
|
+
--color-df-conditional-text: oklch(0.85 0.02 250);
|
|
35
|
+
|
|
36
|
+
/* Start/End Badges */
|
|
37
|
+
--color-df-start: oklch(0.55 0.15 140);
|
|
38
|
+
--color-df-start-bg: oklch(0.25 0.08 140);
|
|
39
|
+
--color-df-end: oklch(0.50 0.15 45);
|
|
40
|
+
--color-df-end-bg: oklch(0.25 0.08 45);
|
|
41
|
+
|
|
42
|
+
/* Edge Colors (duller default, bright on hover/selection) */
|
|
43
|
+
--color-df-edge-default: oklch(0.35 0.02 250);
|
|
44
|
+
--color-df-edge-default-hover: oklch(0.55 0.10 250);
|
|
45
|
+
--color-df-edge-choice-1: oklch(0.45 0.12 15);
|
|
46
|
+
--color-df-edge-choice-2: oklch(0.50 0.15 280);
|
|
47
|
+
--color-df-edge-choice-3: oklch(0.48 0.12 200);
|
|
48
|
+
--color-df-edge-choice-4: oklch(0.52 0.12 120);
|
|
49
|
+
--color-df-edge-choice-5: oklch(0.45 0.10 45);
|
|
50
|
+
--color-df-edge-loop: oklch(0.50 0.12 60);
|
|
51
|
+
--color-df-edge-dimmed: oklch(0.25 0.02 250);
|
|
52
|
+
|
|
53
|
+
/* Status Colors */
|
|
54
|
+
--color-df-error: oklch(0.55 0.22 25);
|
|
55
|
+
--color-df-error-bg: oklch(0.25 0.10 25);
|
|
56
|
+
--color-df-warning: oklch(0.65 0.18 70);
|
|
57
|
+
--color-df-warning-bg: oklch(0.25 0.10 70);
|
|
58
|
+
--color-df-success: oklch(0.60 0.18 150);
|
|
59
|
+
--color-df-success-bg: oklch(0.25 0.10 150);
|
|
60
|
+
--color-df-info: oklch(0.55 0.15 220);
|
|
61
|
+
--color-df-info-bg: oklch(0.25 0.08 220);
|
|
62
|
+
|
|
63
|
+
/* Text Colors */
|
|
64
|
+
--color-df-text-primary: oklch(0.85 0.02 250);
|
|
65
|
+
--color-df-text-secondary: oklch(0.65 0.02 250);
|
|
66
|
+
--color-df-text-tertiary: oklch(0.45 0.02 250);
|
|
67
|
+
--color-df-text-muted: oklch(0.40 0.02 250);
|
|
68
|
+
|
|
69
|
+
/* UI Control Colors */
|
|
70
|
+
--color-df-control-bg: oklch(0.18 0.02 260);
|
|
71
|
+
--color-df-control-border: oklch(0.30 0.03 250);
|
|
72
|
+
--color-df-control-hover: oklch(0.25 0.03 250);
|
|
73
|
+
--color-df-control-active: oklch(0.35 0.05 250);
|
|
74
|
+
|
|
75
|
+
/* Flag Colors */
|
|
76
|
+
--color-df-flag-dialogue: oklch(0.45 0.03 250);
|
|
77
|
+
--color-df-flag-dialogue-bg: oklch(0.20 0.02 250);
|
|
78
|
+
--color-df-flag-quest: oklch(0.50 0.15 220);
|
|
79
|
+
--color-df-flag-quest-bg: oklch(0.22 0.08 220);
|
|
80
|
+
--color-df-flag-achievement: oklch(0.60 0.18 70);
|
|
81
|
+
--color-df-flag-achievement-bg: oklch(0.25 0.10 70);
|
|
82
|
+
--color-df-flag-item: oklch(0.55 0.15 150);
|
|
83
|
+
--color-df-flag-item-bg: oklch(0.25 0.08 150);
|
|
84
|
+
--color-df-flag-stat: oklch(0.55 0.18 280);
|
|
85
|
+
--color-df-flag-stat-bg: oklch(0.25 0.10 280);
|
|
86
|
+
--color-df-flag-title: oklch(0.55 0.18 330);
|
|
87
|
+
--color-df-flag-title-bg: oklch(0.25 0.10 330);
|
|
88
|
+
--color-df-flag-global: oklch(0.50 0.15 45);
|
|
89
|
+
--color-df-flag-global-bg: oklch(0.25 0.08 45);
|
|
90
|
+
|
|
91
|
+
/* Canvas/Background */
|
|
92
|
+
--color-df-canvas-bg: oklch(0.12 0.01 250);
|
|
93
|
+
--color-df-canvas-grid: oklch(0.20 0.02 250);
|
|
94
|
+
|
|
95
|
+
/* Sidebar/Editor */
|
|
96
|
+
--color-df-sidebar-bg: oklch(0.18 0.02 260);
|
|
97
|
+
--color-df-sidebar-border: oklch(0.35 0.05 250);
|
|
98
|
+
--color-df-editor-bg: oklch(0.15 0.02 240);
|
|
99
|
+
--color-df-editor-border: oklch(0.30 0.03 250);
|
|
100
|
+
|
|
101
|
+
/* Shadows */
|
|
102
|
+
--shadow-df-sm: 0 2px 4px oklch(0 0 0 / 0.3);
|
|
103
|
+
--shadow-df-md: 0 4px 8px oklch(0 0 0 / 0.4);
|
|
104
|
+
--shadow-df-lg: 0 8px 16px oklch(0 0 0 / 0.5);
|
|
105
|
+
--shadow-df-glow: 0 0 12px oklch(0.45 0.12 15 / 0.3);
|
|
106
|
+
}
|
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
* Type-safe constants for Dialogue Forge
|
|
3
3
|
* Use these instead of string literals for better type safety and IDE support
|
|
4
4
|
*/
|
|
5
|
+
/**
|
|
6
|
+
* View modes for DialogueEditorV2
|
|
7
|
+
*/
|
|
8
|
+
export declare const VIEW_MODE: {
|
|
9
|
+
readonly GRAPH: "graph";
|
|
10
|
+
readonly YARN: "yarn";
|
|
11
|
+
readonly PLAY: "play";
|
|
12
|
+
};
|
|
13
|
+
export type ViewMode = typeof VIEW_MODE[keyof typeof VIEW_MODE];
|
|
5
14
|
/**
|
|
6
15
|
* Node types in a dialogue tree
|
|
7
16
|
*/
|
package/dist/types/constants.js
CHANGED
|
@@ -4,7 +4,15 @@
|
|
|
4
4
|
* Use these instead of string literals for better type safety and IDE support
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.QUEST_STATE = exports.FLAG_VALUE_TYPE = exports.CONDITION_OPERATOR = exports.FLAG_TYPE = exports.NODE_TYPE = void 0;
|
|
7
|
+
exports.QUEST_STATE = exports.FLAG_VALUE_TYPE = exports.CONDITION_OPERATOR = exports.FLAG_TYPE = exports.NODE_TYPE = exports.VIEW_MODE = void 0;
|
|
8
|
+
/**
|
|
9
|
+
* View modes for DialogueEditorV2
|
|
10
|
+
*/
|
|
11
|
+
exports.VIEW_MODE = {
|
|
12
|
+
GRAPH: 'graph',
|
|
13
|
+
YARN: 'yarn',
|
|
14
|
+
PLAY: 'play',
|
|
15
|
+
};
|
|
8
16
|
/**
|
|
9
17
|
* Node types in a dialogue tree
|
|
10
18
|
*/
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ConditionOperator, NodeType } from './constants';
|
|
2
|
+
export type { ViewMode } from './constants';
|
|
2
3
|
export interface Choice {
|
|
3
4
|
id: string;
|
|
4
5
|
text: string;
|
|
@@ -43,7 +44,6 @@ export interface DialogueTree {
|
|
|
43
44
|
nodes: Record<string, DialogueNode>;
|
|
44
45
|
}
|
|
45
46
|
import { FlagSchema } from './flags';
|
|
46
|
-
export type ViewMode = 'graph' | 'yarn' | 'play';
|
|
47
47
|
export interface DialogueEditorProps {
|
|
48
48
|
dialogue: DialogueTree | null;
|
|
49
49
|
onChange: (dialogue: DialogueTree) => void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@magicborn/dialogue-forge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Visual node-based dialogue editor with Yarn Spinner support",
|
|
5
5
|
"author": "Ben Garrard <b2gdevs@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,12 +24,13 @@
|
|
|
24
24
|
},
|
|
25
25
|
"files": [
|
|
26
26
|
"dist",
|
|
27
|
+
"dist/styles",
|
|
27
28
|
"demo",
|
|
28
29
|
"bin",
|
|
29
30
|
"README.md"
|
|
30
31
|
],
|
|
31
32
|
"scripts": {
|
|
32
|
-
"build": "tsc && tsc --module esnext --outDir dist/esm",
|
|
33
|
+
"build": "tsc && tsc --module esnext --outDir dist/esm && node ./bin/postbuild.js",
|
|
33
34
|
"dev": "cd demo && npm install && npm run dev",
|
|
34
35
|
"dev:watch": "tsc --watch",
|
|
35
36
|
"test": "vitest",
|