@magicborn/dialogue-forge 0.1.7 → 0.1.8

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.
@@ -7,6 +7,7 @@
7
7
  */
8
8
  export { VariableManager } from './variable-manager';
9
9
  export { evaluateCondition, evaluateConditions } from './condition-evaluator';
10
- export { processNode, isValidNextNode, ProcessedNodeResult } from './node-processor';
10
+ export { processNode, isValidNextNode } from './node-processor';
11
+ export type { ProcessedNodeResult } from './node-processor';
11
12
  export { executeVariableOperation, processVariableOperationsInContent } from './variable-operations';
12
13
  export type { VariableState } from './condition-evaluator';
@@ -7,6 +7,7 @@
7
7
  */
8
8
  export { VariableManager } from './variable-manager';
9
9
  export { evaluateCondition, evaluateConditions } from './condition-evaluator';
10
- export { processNode, isValidNextNode, ProcessedNodeResult } from './node-processor';
10
+ export { processNode, isValidNextNode } from './node-processor';
11
+ export type { ProcessedNodeResult } from './node-processor';
11
12
  export { executeVariableOperation, processVariableOperationsInContent } from './variable-operations';
12
13
  export type { VariableState } from './condition-evaluator';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magicborn/dialogue-forge",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Visual node-based dialogue editor with Yarn Spinner support",
5
5
  "author": "Ben Garrard <b2gdevs@gmail.com>",
6
6
  "license": "MIT",
@@ -25,19 +25,20 @@
25
25
  "files": [
26
26
  "dist",
27
27
  "dist/styles",
28
- "demo",
29
28
  "bin",
30
29
  "README.md"
31
30
  ],
32
31
  "scripts": {
33
- "build": "tsc && tsc --module esnext --outDir dist/esm && node ./bin/postbuild.js",
34
- "dev": "cd demo && npm install && npm run dev",
35
- "dev:watch": "tsc --watch",
32
+ "build": "next build",
33
+ "build:lib": "tsc -p tsconfig.lib.json && tsc -p tsconfig.lib.json --module esnext --outDir dist/esm && node ./bin/postbuild.js",
34
+ "dev": "next dev",
35
+ "dev:watch": "tsc -p tsconfig.lib.json --watch",
36
+ "start": "next start",
36
37
  "test": "vitest",
37
38
  "test:watch": "vitest --watch",
38
39
  "test:ui": "vitest --ui",
39
40
  "test:coverage": "vitest --coverage",
40
- "prepublishOnly": "npm run build",
41
+ "prepublishOnly": "npm run build:lib",
41
42
  "sync": "./sync-to-repo.sh",
42
43
  "pack": "npm pack --dry-run"
43
44
  },
@@ -65,6 +66,12 @@
65
66
  "@types/node": "^20.0.0",
66
67
  "@types/react-syntax-highlighter": "^15.5.0",
67
68
  "@vitest/ui": "^2.0.0",
69
+ "@tailwindcss/postcss": "^4.0.0",
70
+ "next": "^16.0.9",
71
+ "postcss": "^8.4.0",
72
+ "react": "19.2.1",
73
+ "react-dom": "19.2.1",
74
+ "tailwindcss": "^4.0.0",
68
75
  "typescript": "^5.0.0",
69
76
  "vitest": "^2.0.0"
70
77
  }
@@ -1,36 +0,0 @@
1
- import type { Metadata } from 'next';
2
- import 'reactflow/dist/style.css';
3
- import 'react-tooltip/dist/react-tooltip.css';
4
- import '../styles/globals.css';
5
- import '../../../../packages-shared/server-template/styles/globals.css';
6
- import { BrandedLayout } from '../../../../packages-shared/server-template/components/BrandedLayout';
7
-
8
- export const metadata: Metadata = {
9
- title: 'Dialogue Forge - Visual Dialogue Editor',
10
- description: 'Interactive demo of Dialogue Forge - a visual node-based dialogue editor with Yarn Spinner support',
11
- };
12
-
13
- // Tell Next.js this layout is static (no dynamic params/searchParams)
14
- export const dynamic = 'force-static';
15
-
16
- export default function RootLayout({
17
- children,
18
- }: {
19
- children: React.ReactNode;
20
- }) {
21
- return (
22
- <html lang="en">
23
- <body>
24
- <BrandedLayout
25
- packageName="Dialogue Forge"
26
- packageDescription="Visual node-based dialogue editor with Yarn Spinner support"
27
- packageRepo="https://github.com/MagicbornStudios/dialogue-forge"
28
- portfolioUrl="bengarrard.com"
29
- >
30
- {children}
31
- </BrandedLayout>
32
- </body>
33
- </html>
34
- );
35
- }
36
-
package/demo/app/page.tsx DELETED
@@ -1,455 +0,0 @@
1
- 'use client';
2
-
3
- import { useState, useCallback } from 'react';
4
- import { DialogueEditorV2 } from '@magicborn/dialogue-forge/src/components/DialogueEditorV2';
5
- import { FlagManager } from '@magicborn/dialogue-forge/src/components/FlagManager';
6
- import { GuidePanel } from '@magicborn/dialogue-forge/src/components/GuidePanel';
7
- import { FlagSchema, exampleFlagSchema } from '@magicborn/dialogue-forge/src/types/flags';
8
- import { DialogueTree } from '@magicborn/dialogue-forge/src/types';
9
- import { exportToYarn } from '@magicborn/dialogue-forge/src/lib/yarn-converter';
10
- import type { ViewMode } from '@magicborn/dialogue-forge/src/types';
11
- import {
12
- listExamples,
13
- getExampleDialogue,
14
- listDemoFlagSchemas,
15
- getDemoFlagSchema,
16
- getExampleCharacters
17
- } from '@magicborn/dialogue-forge/src/examples';
18
- import { Play, Layout, FileText } from 'lucide-react';
19
- import { ThemeSwitcher } from '../components/ThemeSwitcher';
20
-
21
-
22
-
23
- // Tell Next.js this page is static (no dynamic params/searchParams)
24
- export const dynamic = 'force-static';
25
-
26
- // Demo examples - these are specific to the demo app
27
- const demoDialogues: Record<string, DialogueTree> = {
28
- 'mysterious-stranger': {
29
- id: 'mysterious-stranger',
30
- title: 'Demo: The Mysterious Stranger',
31
- startNodeId: 'start',
32
- nodes: {
33
- 'start': {
34
- id: 'start',
35
- type: 'npc',
36
- characterId: 'stranger',
37
- speaker: 'Stranger', // Fallback
38
- x: 300,
39
- y: 100,
40
- content: "You find yourself at a crossroads. A cloaked figure emerges from the shadows.",
41
- nextNodeId: 'greeting',
42
- },
43
- 'greeting': {
44
- id: 'greeting',
45
- type: 'npc',
46
- characterId: 'stranger',
47
- speaker: 'Stranger', // Fallback
48
- x: 300,
49
- y: 200,
50
- content: "\"Traveler... I've been waiting for you. What brings you to these lands?\"",
51
- nextNodeId: 'first_choice',
52
- },
53
- 'first_choice': {
54
- id: 'first_choice',
55
- type: 'player',
56
- content: '',
57
- x: 300,
58
- y: 300,
59
- choices: [
60
- {
61
- id: 'choice_treasure',
62
- text: "I seek the legendary treasure.",
63
- nextNodeId: 'treasure_response',
64
- conditions: [
65
- { flag: 'reputation', operator: 'greater_equal', value: 0 },
66
- ],
67
- },
68
- {
69
- id: 'choice_knowledge',
70
- text: "I'm searching for ancient knowledge.",
71
- nextNodeId: 'knowledge_response',
72
- conditions: [
73
- { flag: 'reputation', operator: 'greater_equal', value: 0 },
74
- ],
75
- },
76
- {
77
- id: 'choice_high_rep',
78
- text: "I am a hero of this land!",
79
- nextNodeId: 'high_rep_response',
80
- conditions: [
81
- { flag: 'reputation', operator: 'greater_than', value: 50 },
82
- ],
83
- },
84
- ],
85
- },
86
- 'treasure_response': {
87
- id: 'treasure_response',
88
- type: 'npc',
89
- characterId: 'stranger',
90
- speaker: 'Stranger', // Fallback
91
- x: 200,
92
- y: 450,
93
- content: "\"Many have sought the same. Take this map—it shows the entrance to the catacombs.\"",
94
- nextNodeId: undefined,
95
- },
96
- 'knowledge_response': {
97
- id: 'knowledge_response',
98
- type: 'npc',
99
- characterId: 'stranger',
100
- speaker: 'Stranger', // Fallback
101
- x: 400,
102
- y: 450,
103
- content: "\"A seeker of truth... Take this tome. It contains the riddles you must solve.\"",
104
- nextNodeId: undefined,
105
- },
106
- 'high_rep_response': {
107
- id: 'high_rep_response',
108
- type: 'npc',
109
- characterId: 'stranger',
110
- speaker: 'Stranger', // Fallback
111
- x: 500,
112
- y: 450,
113
- content: "\"Ah, a hero! Your reputation precedes you. I have something special for you...\"",
114
- nextNodeId: undefined,
115
- setFlags: ['reputation'],
116
- },
117
- },
118
- },
119
- 'tavern-quest': {
120
- id: 'tavern-quest',
121
- title: 'Demo: Tavern Quest',
122
- startNodeId: 'enter_tavern',
123
- nodes: {
124
- 'enter_tavern': {
125
- id: 'enter_tavern',
126
- type: 'npc',
127
- speaker: 'Narrator',
128
- x: 300,
129
- y: 50,
130
- content: "You push open the heavy wooden door and enter the Rusty Dragon tavern.",
131
- nextNodeId: 'bartender_greet',
132
- },
133
- 'bartender_greet': {
134
- id: 'bartender_greet',
135
- type: 'npc',
136
- characterId: 'bartender',
137
- speaker: 'Bartender', // Fallback
138
- x: 300,
139
- y: 150,
140
- content: "\"Welcome, stranger! What can I get ya? We've got ale, mead, or if you're looking for work, I might have something.\"",
141
- nextNodeId: 'tavern_choice',
142
- },
143
- 'tavern_choice': {
144
- id: 'tavern_choice',
145
- type: 'player',
146
- content: '',
147
- x: 300,
148
- y: 280,
149
- choices: [
150
- { id: 'order_ale', text: "I'll have an ale.", nextNodeId: 'drink_ale' },
151
- { id: 'ask_work', text: "What kind of work?", nextNodeId: 'work_info' },
152
- { id: 'look_around', text: "I'll just look around.", nextNodeId: 'observe_tavern' },
153
- {
154
- id: 'vip_entrance',
155
- text: "I'm a VIP member.",
156
- nextNodeId: 'vip_response',
157
- conditions: [
158
- { flag: 'reputation', operator: 'greater_than', value: 75 },
159
- ],
160
- },
161
- ],
162
- },
163
- 'drink_ale': {
164
- id: 'drink_ale',
165
- type: 'npc',
166
- characterId: 'bartender',
167
- speaker: 'Bartender', // Fallback
168
- x: 100,
169
- y: 420,
170
- content: "\"Coming right up!\" He slides a frothy mug across the bar.",
171
- nextNodeId: undefined,
172
- },
173
- 'work_info': {
174
- id: 'work_info',
175
- type: 'npc',
176
- characterId: 'bartender',
177
- speaker: 'Bartender', // Fallback
178
- x: 300,
179
- y: 420,
180
- content: "\"Rats in the cellar. Big ones. I'll pay 10 gold if you clear 'em out.\"",
181
- nextNodeId: 'accept_quest',
182
- },
183
- 'accept_quest': {
184
- id: 'accept_quest',
185
- type: 'player',
186
- content: '',
187
- x: 300,
188
- y: 550,
189
- choices: [
190
- { id: 'accept', text: "I'll do it.", nextNodeId: 'quest_accepted' },
191
- { id: 'decline', text: "Not interested." },
192
- ],
193
- },
194
- 'quest_accepted': {
195
- id: 'quest_accepted',
196
- type: 'npc',
197
- characterId: 'bartender',
198
- speaker: 'Bartender', // Fallback
199
- x: 300,
200
- y: 680,
201
- content: "\"Great! The cellar door is in the back. Good luck!\"",
202
- nextNodeId: undefined,
203
- },
204
- 'observe_tavern': {
205
- id: 'observe_tavern',
206
- type: 'npc',
207
- characterId: 'narrator',
208
- speaker: 'Narrator', // Fallback
209
- x: 500,
210
- y: 420,
211
- content: "You notice a hooded figure in the corner, watching you intently...",
212
- nextNodeId: undefined,
213
- },
214
- 'vip_response': {
215
- id: 'vip_response',
216
- type: 'npc',
217
- characterId: 'bartender',
218
- speaker: 'Bartender', // Fallback
219
- x: 600,
220
- y: 420,
221
- content: "\"Of course! Right this way to the VIP lounge. Your reputation grants you access.\"",
222
- nextNodeId: undefined,
223
- },
224
- },
225
- },
226
- };
227
-
228
- // Enhanced flag schema with reputation for demo examples
229
- const demoFlagSchema: FlagSchema = {
230
- ...exampleFlagSchema,
231
- flags: [
232
- ...exampleFlagSchema.flags,
233
- {
234
- id: 'reputation',
235
- name: 'Reputation',
236
- type: 'stat',
237
- description: 'Player reputation score',
238
- defaultValue: 0,
239
- },
240
- ],
241
- };
242
-
243
- export default function DialogueForgeDemo() {
244
- const [dialogueTree, setDialogueTree] = useState<DialogueTree>(demoDialogues['mysterious-stranger']);
245
- const [flagSchema, setFlagSchema] = useState<FlagSchema>(demoFlagSchema);
246
- const [viewMode, setViewMode] = useState<ViewMode>('graph');
247
- const characters = getExampleCharacters(); // Get example characters
248
-
249
- // Panel states
250
- const [showFlagManager, setShowFlagManager] = useState(false);
251
- const [showGuide, setShowGuide] = useState(false);
252
- const [showExamplePicker, setShowExamplePicker] = useState(false);
253
-
254
- const handleExportYarn = useCallback(() => {
255
- const yarn = exportToYarn(dialogueTree);
256
- const blob = new Blob([yarn], { type: 'text/plain' });
257
- const url = URL.createObjectURL(blob);
258
- const a = document.createElement('a');
259
- a.href = url;
260
- a.download = `${dialogueTree.title.replace(/\s+/g, '_')}.yarn`;
261
- a.click();
262
- URL.revokeObjectURL(url);
263
- }, [dialogueTree]);
264
-
265
- const handleLoadExample = useCallback((exampleId: string) => {
266
- // Try demo examples first
267
- if (demoDialogues[exampleId]) {
268
- setDialogueTree(demoDialogues[exampleId]);
269
- setShowExamplePicker(false);
270
- return;
271
- }
272
- // Try package examples
273
- const example = getExampleDialogue(exampleId);
274
- if (example) {
275
- setDialogueTree(example);
276
- }
277
- setShowExamplePicker(false);
278
- }, []);
279
-
280
- const handleLoadFlagSchema = useCallback((schemaId: string) => {
281
- const schema = getDemoFlagSchema(schemaId);
282
- if (schema) {
283
- setFlagSchema(schema);
284
- }
285
- }, []);
286
-
287
- // Get all available examples
288
- const allExamples = [
289
- ...Object.keys(demoDialogues),
290
- ...listExamples()
291
- ].filter((v, i, a) => a.indexOf(v) === i); // unique
292
-
293
- const allFlagSchemas = listDemoFlagSchemas();
294
-
295
- return (
296
- <div className="w-full h-screen flex flex-col">
297
- {/* Header */}
298
- <div className="max-w-7xl mx-auto px-4 py-4 flex-shrink-0 w-full">
299
- <div className="flex items-center justify-between">
300
- <div>
301
- <h1 className="text-2xl font-bold text-white mb-1">Dialogue Forge Editor</h1>
302
- <p className="text-zinc-400 text-sm">
303
- Create interactive dialogues with a visual node-based editor. Export to Yarn Spinner format.
304
- </p>
305
- </div>
306
- <div className="flex items-center gap-2">
307
- <ThemeSwitcher />
308
- <div className="w-px h-6 bg-zinc-700" />
309
- <button
310
- onClick={() => setShowExamplePicker(true)}
311
- className="px-3 py-1.5 bg-indigo-600 hover:bg-indigo-700 text-white text-sm rounded-lg transition-colors"
312
- >
313
- Load Example
314
- </button>
315
- <div className="w-px h-6 bg-zinc-700" />
316
- {/* View Mode Toggle */}
317
- <div className="flex items-center gap-1 bg-[#12121a] border border-[#2a2a3e] rounded-lg p-1">
318
- <button
319
- onClick={() => setViewMode('graph')}
320
- className={`px-3 py-1.5 text-sm rounded transition-colors flex items-center gap-1.5 ${
321
- viewMode === 'graph'
322
- ? 'bg-indigo-600 text-white'
323
- : 'text-gray-400 hover:text-white'
324
- }`}
325
- title="Graph Editor"
326
- >
327
- <Layout size={14} />
328
- <span className="hidden sm:inline">Editor</span>
329
- </button>
330
- <button
331
- onClick={() => setViewMode('play')}
332
- className={`px-3 py-1.5 text-sm rounded transition-colors flex items-center gap-1.5 ${
333
- viewMode === 'play'
334
- ? 'bg-green-600 text-white'
335
- : 'text-gray-400 hover:text-white'
336
- }`}
337
- title="Play Dialogue"
338
- >
339
- <Play size={14} />
340
- <span className="hidden sm:inline">Play</span>
341
- </button>
342
- </div>
343
- <button
344
- onClick={() => setShowExamplePicker(true)}
345
- className="px-3 py-1.5 bg-indigo-600 hover:bg-indigo-700 text-white text-sm rounded-lg transition-colors"
346
- >
347
- Load Example
348
- </button>
349
- </div>
350
- </div>
351
- </div>
352
-
353
- {/* Editor/Player */}
354
- <div className="flex-1 w-full min-h-0">
355
- <DialogueEditorV2
356
- dialogue={dialogueTree}
357
- onChange={setDialogueTree}
358
- onExportYarn={handleExportYarn}
359
- flagSchema={flagSchema}
360
- characters={characters}
361
- viewMode={viewMode}
362
- onViewModeChange={setViewMode}
363
- className="w-full h-full"
364
- onOpenFlagManager={() => setShowFlagManager(true)}
365
- onOpenGuide={() => setShowGuide(true)}
366
- />
367
- </div>
368
-
369
- {/* Flag Manager Modal */}
370
- {showFlagManager && (
371
- <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
372
- <div className="bg-[#0d0d14] border border-[#2a2a3e] rounded-lg shadow-xl max-w-4xl w-full max-h-[80vh] overflow-hidden">
373
- <FlagManager
374
- flagSchema={flagSchema}
375
- dialogue={dialogueTree}
376
- onUpdate={setFlagSchema}
377
- onClose={() => setShowFlagManager(false)}
378
- />
379
- </div>
380
- </div>
381
- )}
382
-
383
- {/* Guide Panel */}
384
- <GuidePanel
385
- isOpen={showGuide}
386
- onClose={() => setShowGuide(false)}
387
- />
388
-
389
- {/* Example Picker Modal */}
390
- {showExamplePicker && (
391
- <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
392
- <div className="bg-[#0d0d14] border border-[#2a2a3e] rounded-lg shadow-xl max-w-lg w-full p-6">
393
- <div className="flex items-center justify-between mb-4">
394
- <h2 className="text-lg font-semibold text-white">Load Example Dialogue</h2>
395
- <button
396
- onClick={() => setShowExamplePicker(false)}
397
- className="text-gray-400 hover:text-white"
398
- >
399
-
400
- </button>
401
- </div>
402
-
403
- <div className="space-y-2 max-h-64 overflow-y-auto">
404
- <h3 className="text-sm font-medium text-gray-400 mb-2">Demo Examples</h3>
405
- {Object.entries(demoDialogues).map(([id, dialogue]) => (
406
- <button
407
- key={id}
408
- onClick={() => handleLoadExample(id)}
409
- className="w-full text-left px-4 py-3 bg-[#12121a] hover:bg-[#1a1a2e] border border-[#2a2a3e] rounded-lg transition-colors"
410
- >
411
- <div className="font-medium text-white">{dialogue.title}</div>
412
- <div className="text-xs text-gray-400">
413
- {Object.keys(dialogue.nodes).length} nodes
414
- </div>
415
- </button>
416
- ))}
417
-
418
- {listExamples().length > 0 && (
419
- <>
420
- <h3 className="text-sm font-medium text-gray-400 mt-4 mb-2">Package Examples</h3>
421
- {listExamples().map((id) => (
422
- <button
423
- key={id}
424
- onClick={() => handleLoadExample(id)}
425
- className="w-full text-left px-4 py-3 bg-[#12121a] hover:bg-[#1a1a2e] border border-[#2a2a3e] rounded-lg transition-colors"
426
- >
427
- <div className="font-medium text-white">{id}</div>
428
- </button>
429
- ))}
430
- </>
431
- )}
432
- </div>
433
-
434
- {allFlagSchemas.length > 0 && (
435
- <div className="mt-4 pt-4 border-t border-[#2a2a3e]">
436
- <h3 className="text-sm font-medium text-gray-400 mb-2">Flag Schemas</h3>
437
- <div className="flex flex-wrap gap-2">
438
- {allFlagSchemas.map((id) => (
439
- <button
440
- key={id}
441
- onClick={() => handleLoadFlagSchema(id)}
442
- className="px-3 py-1 text-xs bg-purple-500/20 text-purple-400 border border-purple-500/30 rounded hover:bg-purple-500/30 transition-colors"
443
- >
444
- {id}
445
- </button>
446
- ))}
447
- </div>
448
- </div>
449
- )}
450
- </div>
451
- </div>
452
- )}
453
- </div>
454
- );
455
- }