yarn-spinner-runner-ts 0.1.4 → 0.1.5-a
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 +44 -16
- package/dist/compile/compiler.js +2 -2
- package/dist/compile/compiler.js.map +1 -1
- package/dist/compile/ir.d.ts +1 -0
- package/dist/markup/parser.js +12 -2
- package/dist/markup/parser.js.map +1 -1
- package/dist/model/ast.d.ts +1 -0
- package/dist/parse/lexer.js +5 -5
- package/dist/parse/lexer.js.map +1 -1
- package/dist/parse/parser.js +12 -2
- package/dist/parse/parser.js.map +1 -1
- package/dist/react/DialogueExample.js +12 -10
- package/dist/react/DialogueExample.js.map +1 -1
- package/dist/react/DialogueView.d.ts +10 -4
- package/dist/react/DialogueView.js +38 -12
- package/dist/react/DialogueView.js.map +1 -1
- package/dist/react/MarkupRenderer.js +1 -1
- package/dist/react/MarkupRenderer.js.map +1 -1
- package/dist/react/useYarnRunner.js +63 -10
- package/dist/react/useYarnRunner.js.map +1 -1
- package/dist/runtime/evaluator.d.ts +3 -0
- package/dist/runtime/evaluator.js +165 -3
- package/dist/runtime/evaluator.js.map +1 -1
- package/dist/runtime/runner.d.ts +2 -0
- package/dist/runtime/runner.js +66 -10
- package/dist/runtime/runner.js.map +1 -1
- package/dist/tests/dialogue_view.test.d.ts +1 -0
- package/dist/tests/dialogue_view.test.js +18 -0
- package/dist/tests/dialogue_view.test.js.map +1 -0
- package/dist/tests/markup.test.js +7 -0
- package/dist/tests/markup.test.js.map +1 -1
- package/dist/tests/options.test.js +164 -9
- package/dist/tests/options.test.js.map +1 -1
- package/dist/tests/variables_flow_cmds.test.js +117 -10
- package/dist/tests/variables_flow_cmds.test.js.map +1 -1
- package/docs/markup.md +33 -33
- package/eslint.config.cjs +39 -39
- package/package.json +6 -6
- package/src/compile/compiler.ts +2 -2
- package/src/compile/ir.ts +1 -1
- package/src/markup/parser.ts +53 -43
- package/src/model/ast.ts +1 -0
- package/src/parse/lexer.ts +18 -18
- package/src/parse/parser.ts +33 -22
- package/src/react/DialogueExample.tsx +16 -14
- package/src/react/DialogueView.tsx +312 -275
- package/src/react/MarkupRenderer.tsx +1 -2
- package/src/react/useYarnRunner.tsx +101 -34
- package/src/runtime/evaluator.ts +224 -47
- package/src/runtime/runner.ts +102 -37
- package/src/tests/dialogue_view.test.tsx +26 -0
- package/src/tests/markup.test.ts +17 -1
- package/src/tests/options.test.ts +206 -36
- package/src/tests/variables_flow_cmds.test.ts +139 -28
package/eslint.config.cjs
CHANGED
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
// ESLint v9 flat config
|
|
2
|
-
const js = require("@eslint/js");
|
|
3
|
-
const tsParser = require("@typescript-eslint/parser");
|
|
4
|
-
const tsPlugin = require("@typescript-eslint/eslint-plugin");
|
|
5
|
-
|
|
6
|
-
module.exports = [
|
|
7
|
-
{
|
|
8
|
-
ignores: ["dist/**", "node_modules/**", "examples/**", "src/examples/**", "src/tests/**"],
|
|
9
|
-
},
|
|
10
|
-
{
|
|
11
|
-
files: ["src/**/*.ts", "src/**/*.tsx"],
|
|
12
|
-
languageOptions: {
|
|
13
|
-
parser: tsParser,
|
|
14
|
-
ecmaVersion: "latest",
|
|
15
|
-
sourceType: "module",
|
|
16
|
-
globals: {
|
|
17
|
-
console: "readonly",
|
|
18
|
-
window: "readonly",
|
|
19
|
-
setTimeout: "readonly",
|
|
20
|
-
clearTimeout: "readonly",
|
|
21
|
-
setInterval: "readonly",
|
|
22
|
-
clearInterval: "readonly",
|
|
23
|
-
requestAnimationFrame: "readonly",
|
|
24
|
-
},
|
|
25
|
-
},
|
|
26
|
-
plugins: {
|
|
27
|
-
"@typescript-eslint": tsPlugin,
|
|
28
|
-
},
|
|
29
|
-
rules: {
|
|
30
|
-
...js.configs.recommended.rules,
|
|
31
|
-
...tsPlugin.configs.recommended.rules,
|
|
32
|
-
"@typescript-eslint/explicit-module-boundary-types": "off",
|
|
33
|
-
"@typescript-eslint/no-explicit-any": "off",
|
|
34
|
-
"no-console": ["warn", { allow: ["warn", "error", "assert", "log"] }],
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
];
|
|
38
|
-
|
|
39
|
-
|
|
1
|
+
// ESLint v9 flat config
|
|
2
|
+
const js = require("@eslint/js");
|
|
3
|
+
const tsParser = require("@typescript-eslint/parser");
|
|
4
|
+
const tsPlugin = require("@typescript-eslint/eslint-plugin");
|
|
5
|
+
|
|
6
|
+
module.exports = [
|
|
7
|
+
{
|
|
8
|
+
ignores: ["dist/**", "node_modules/**", "examples/**", "src/examples/**", "src/tests/**"],
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
files: ["src/**/*.ts", "src/**/*.tsx"],
|
|
12
|
+
languageOptions: {
|
|
13
|
+
parser: tsParser,
|
|
14
|
+
ecmaVersion: "latest",
|
|
15
|
+
sourceType: "module",
|
|
16
|
+
globals: {
|
|
17
|
+
console: "readonly",
|
|
18
|
+
window: "readonly",
|
|
19
|
+
setTimeout: "readonly",
|
|
20
|
+
clearTimeout: "readonly",
|
|
21
|
+
setInterval: "readonly",
|
|
22
|
+
clearInterval: "readonly",
|
|
23
|
+
requestAnimationFrame: "readonly",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
plugins: {
|
|
27
|
+
"@typescript-eslint": tsPlugin,
|
|
28
|
+
},
|
|
29
|
+
rules: {
|
|
30
|
+
...js.configs.recommended.rules,
|
|
31
|
+
...tsPlugin.configs.recommended.rules,
|
|
32
|
+
"@typescript-eslint/explicit-module-boundary-types": "off",
|
|
33
|
+
"@typescript-eslint/no-explicit-any": "off",
|
|
34
|
+
"no-console": ["warn", { allow: ["warn", "error", "assert", "log"] }],
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yarn-spinner-runner-ts",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5-a",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "TypeScript parser, compiler, and runtime for Yarn Spinner 3.x with React adapter [NPM package](https://www.npmjs.com/package/yarn-spinner-runner-ts)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -37,20 +37,20 @@
|
|
|
37
37
|
"js-yaml": "^4.1.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@eslint/js": "^9.12.0",
|
|
41
|
-
"@types/js-yaml": "^4.0.9",
|
|
42
40
|
"@types/node": "^22.7.4",
|
|
41
|
+
"@types/js-yaml": "^4.0.9",
|
|
43
42
|
"@types/react": "^18.3.3",
|
|
44
43
|
"@types/react-dom": "^18.3.0",
|
|
45
|
-
"@
|
|
44
|
+
"@eslint/js": "^9.12.0",
|
|
46
45
|
"@typescript-eslint/parser": "^8.8.1",
|
|
46
|
+
"@typescript-eslint/eslint-plugin": "^8.8.1",
|
|
47
47
|
"@vitejs/plugin-react": "^4.3.1",
|
|
48
48
|
"eslint": "^9.12.0",
|
|
49
49
|
"react": "^18.3.1",
|
|
50
50
|
"react-dom": "^18.3.1",
|
|
51
51
|
"rimraf": "^6.0.1",
|
|
52
52
|
"typescript": "^5.6.3",
|
|
53
|
-
"vite": "^5.4.3"
|
|
54
|
-
"vitest": "^4.0.7"
|
|
53
|
+
"vite": "^5.4.3"
|
|
55
54
|
}
|
|
56
55
|
}
|
|
56
|
+
|
package/src/compile/compiler.ts
CHANGED
|
@@ -72,7 +72,7 @@ export function compile(doc: YarnDocument, opts: CompileOptions = {}): IRProgram
|
|
|
72
72
|
}
|
|
73
73
|
block.push({
|
|
74
74
|
op: "options",
|
|
75
|
-
options: s.options.map((o: Option) => ({ text: o.text, tags: ensureLineId(o.tags), css: (o as any).css, markup: o.markup, block: emitBlock(o.body) })),
|
|
75
|
+
options: s.options.map((o: Option) => ({ text: o.text, tags: ensureLineId(o.tags), css: (o as any).css, markup: o.markup, condition: o.condition, block: emitBlock(o.body) })),
|
|
76
76
|
});
|
|
77
77
|
break;
|
|
78
78
|
}
|
|
@@ -141,7 +141,7 @@ export function compile(doc: YarnDocument, opts: CompileOptions = {}): IRProgram
|
|
|
141
141
|
}
|
|
142
142
|
block.push({
|
|
143
143
|
op: "options",
|
|
144
|
-
options: s.options.map((o: Option) => ({ text: o.text, tags: ensureLineId(o.tags), css: (o as any).css, markup: o.markup, block: emitBlock(o.body) })),
|
|
144
|
+
options: s.options.map((o: Option) => ({ text: o.text, tags: ensureLineId(o.tags), css: (o as any).css, markup: o.markup, condition: o.condition, block: emitBlock(o.body) })),
|
|
145
145
|
});
|
|
146
146
|
break;
|
|
147
147
|
}
|
package/src/compile/ir.ts
CHANGED
|
@@ -22,7 +22,7 @@ export type IRInstruction =
|
|
|
22
22
|
| { op: "command"; content: string }
|
|
23
23
|
| { op: "jump"; target: string }
|
|
24
24
|
| { op: "detour"; target: string }
|
|
25
|
-
| { op: "options"; options: Array<{ text: string; tags?: string[]; css?: string; markup?: MarkupParseResult; block: IRInstruction[] }> }
|
|
25
|
+
| { op: "options"; options: Array<{ text: string; tags?: string[]; css?: string; markup?: MarkupParseResult; condition?: string; block: IRInstruction[] }> }
|
|
26
26
|
| { op: "if"; branches: Array<{ condition: string | null; block: IRInstruction[] }> }
|
|
27
27
|
| { op: "once"; id: string; block: IRInstruction[] };
|
|
28
28
|
|
package/src/markup/parser.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { MarkupParseResult, MarkupSegment, MarkupValue, MarkupWrapper } from "./types.js";
|
|
2
|
-
|
|
3
|
-
const DEFAULT_HTML_TAGS = new Set(["b", "em", "small", "strong", "sub", "sup", "ins", "del", "mark"]);
|
|
1
|
+
import type { MarkupParseResult, MarkupSegment, MarkupValue, MarkupWrapper } from "./types.js";
|
|
2
|
+
|
|
3
|
+
const DEFAULT_HTML_TAGS = new Set(["b", "em", "small", "strong", "sub", "sup", "ins", "del", "mark", "br"]);
|
|
4
|
+
const SELF_CLOSING_TAGS = new Set(["br"]);
|
|
4
5
|
|
|
5
6
|
interface StackEntry {
|
|
6
7
|
name: string;
|
|
@@ -125,8 +126,9 @@ export function parseMarkup(input: string): MarkupParseResult {
|
|
|
125
126
|
rest = rest.slice(attrMatch[0].length).trim();
|
|
126
127
|
}
|
|
127
128
|
|
|
128
|
-
|
|
129
|
-
|
|
129
|
+
const finalKind: ParsedTag["kind"] = kind === "self" || SELF_CLOSING_TAGS.has(name) ? "self" : kind;
|
|
130
|
+
return { kind: finalKind, name, properties };
|
|
131
|
+
};
|
|
130
132
|
|
|
131
133
|
const parseAttributeValue = (raw: string): MarkupValue => {
|
|
132
134
|
const trimmed = raw.trim();
|
|
@@ -142,20 +144,20 @@ export function parseMarkup(input: string): MarkupParseResult {
|
|
|
142
144
|
return trimmed;
|
|
143
145
|
};
|
|
144
146
|
|
|
145
|
-
const handleSelfClosing = (tag: ParsedTag) => {
|
|
146
|
-
const wrapper: MarkupWrapper = {
|
|
147
|
-
name: tag.name,
|
|
148
|
-
type: DEFAULT_HTML_TAGS.has(tag.name) ? "default" : "custom",
|
|
149
|
-
properties: tag.properties,
|
|
150
|
-
};
|
|
151
|
-
const position = chars.length;
|
|
152
|
-
pushSegment({
|
|
153
|
-
start: position,
|
|
154
|
-
end: position,
|
|
155
|
-
wrappers: [wrapper],
|
|
156
|
-
selfClosing: true,
|
|
157
|
-
});
|
|
158
|
-
};
|
|
147
|
+
const handleSelfClosing = (tag: ParsedTag) => {
|
|
148
|
+
const wrapper: MarkupWrapper = {
|
|
149
|
+
name: tag.name,
|
|
150
|
+
type: DEFAULT_HTML_TAGS.has(tag.name) ? "default" : "custom",
|
|
151
|
+
properties: tag.properties,
|
|
152
|
+
};
|
|
153
|
+
const position = chars.length;
|
|
154
|
+
pushSegment({
|
|
155
|
+
start: position,
|
|
156
|
+
end: position,
|
|
157
|
+
wrappers: [wrapper],
|
|
158
|
+
selfClosing: true,
|
|
159
|
+
});
|
|
160
|
+
};
|
|
159
161
|
|
|
160
162
|
let i = 0;
|
|
161
163
|
while (i < input.length) {
|
|
@@ -215,30 +217,38 @@ export function parseMarkup(input: string): MarkupParseResult {
|
|
|
215
217
|
continue;
|
|
216
218
|
}
|
|
217
219
|
|
|
218
|
-
if (parsed.kind === "self") {
|
|
219
|
-
handleSelfClosing(parsed);
|
|
220
|
-
i = closeIndex + 1;
|
|
221
|
-
continue;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// closing tag
|
|
225
|
-
if (stack.length === 0) {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
220
|
+
if (parsed.kind === "self") {
|
|
221
|
+
handleSelfClosing(parsed);
|
|
222
|
+
i = closeIndex + 1;
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// closing tag
|
|
227
|
+
if (stack.length === 0) {
|
|
228
|
+
if (SELF_CLOSING_TAGS.has(parsed.name)) {
|
|
229
|
+
i = closeIndex + 1;
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
appendLiteral(originalText);
|
|
233
|
+
i = closeIndex + 1;
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
const top = stack[stack.length - 1];
|
|
237
|
+
if (top.name === parsed.name) {
|
|
238
|
+
flushCurrentSegment();
|
|
239
|
+
stack.pop();
|
|
240
|
+
i = closeIndex + 1;
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (SELF_CLOSING_TAGS.has(parsed.name)) {
|
|
244
|
+
i = closeIndex + 1;
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
// mismatched closing; treat as literal
|
|
248
|
+
appendLiteral(originalText);
|
|
249
|
+
i = closeIndex + 1;
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
242
252
|
|
|
243
253
|
appendChar(char);
|
|
244
254
|
i += 1;
|
package/src/model/ast.ts
CHANGED
package/src/parse/lexer.ts
CHANGED
|
@@ -34,24 +34,24 @@ export function lex(input: string): Token[] {
|
|
|
34
34
|
const indent = raw.match(/^[ \t]*/)?.[0] ?? "";
|
|
35
35
|
const content = raw.slice(indent.length);
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
37
|
+
if (content.trim() === "") {
|
|
38
|
+
push("EMPTY", "", lineNum, 1);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Manage indentation tokens only within node bodies and on non-empty lines
|
|
43
|
+
if (!inHeaders) {
|
|
44
|
+
const prev = indentStack[indentStack.length - 1];
|
|
45
|
+
if (indent.length > prev) {
|
|
46
|
+
indentStack.push(indent.length);
|
|
47
|
+
push("INDENT", "", lineNum, 1);
|
|
48
|
+
} else if (indent.length < prev) {
|
|
49
|
+
while (indentStack.length && indent.length < indentStack[indentStack.length - 1]) {
|
|
50
|
+
indentStack.pop();
|
|
51
|
+
push("DEDENT", "", lineNum, 1);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
55
|
|
|
56
56
|
if (content === "---") {
|
|
57
57
|
inHeaders = false;
|
package/src/parse/parser.ts
CHANGED
|
@@ -215,24 +215,26 @@ class Parser {
|
|
|
215
215
|
// One or more OPTION lines, with bodies under INDENT
|
|
216
216
|
while (this.at("OPTION")) {
|
|
217
217
|
const raw = this.take("OPTION").text;
|
|
218
|
-
const { cleanText: textWithAttrs, tags } = this.extractTags(raw);
|
|
219
|
-
const { text:
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
218
|
+
const { cleanText: textWithAttrs, tags } = this.extractTags(raw);
|
|
219
|
+
const { text: textWithCondition, css } = this.extractCss(textWithAttrs);
|
|
220
|
+
const { text: optionText, condition } = this.extractOptionCondition(textWithCondition);
|
|
221
|
+
const markup = parseMarkup(optionText);
|
|
222
|
+
let body: Statement[] = [];
|
|
223
|
+
if (this.at("INDENT")) {
|
|
224
|
+
this.take("INDENT");
|
|
225
|
+
body = this.parseStatementsUntil("DEDENT");
|
|
225
226
|
this.take("DEDENT");
|
|
226
227
|
while (this.at("EMPTY")) this.i++;
|
|
227
228
|
}
|
|
228
|
-
options.push({
|
|
229
|
-
type: "Option",
|
|
230
|
-
text: markup.text,
|
|
231
|
-
body,
|
|
232
|
-
tags,
|
|
233
|
-
css,
|
|
234
|
-
markup: this.normalizeMarkup(markup),
|
|
235
|
-
|
|
229
|
+
options.push({
|
|
230
|
+
type: "Option",
|
|
231
|
+
text: markup.text,
|
|
232
|
+
body,
|
|
233
|
+
tags,
|
|
234
|
+
css,
|
|
235
|
+
markup: this.normalizeMarkup(markup),
|
|
236
|
+
condition,
|
|
237
|
+
});
|
|
236
238
|
// Consecutive options belong to the same group; break on non-OPTION
|
|
237
239
|
while (this.at("EMPTY")) this.i++;
|
|
238
240
|
}
|
|
@@ -283,15 +285,24 @@ class Parser {
|
|
|
283
285
|
return { cleanText: input };
|
|
284
286
|
}
|
|
285
287
|
|
|
286
|
-
private extractCss(input: string): { text: string; css?: string } {
|
|
287
|
-
const cssMatch = input.match(/\s*&css\{([^}]*)\}\s*$/);
|
|
288
|
-
if (cssMatch) {
|
|
289
|
-
const css = cssMatch[1].trim();
|
|
290
|
-
const text = input.replace(cssMatch[0], "").trimEnd();
|
|
288
|
+
private extractCss(input: string): { text: string; css?: string } {
|
|
289
|
+
const cssMatch = input.match(/\s*&css\{([^}]*)\}\s*$/);
|
|
290
|
+
if (cssMatch) {
|
|
291
|
+
const css = cssMatch[1].trim();
|
|
292
|
+
const text = input.replace(cssMatch[0], "").trimEnd();
|
|
291
293
|
return { text, css };
|
|
292
294
|
}
|
|
293
|
-
return { text: input };
|
|
294
|
-
}
|
|
295
|
+
return { text: input };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
private extractOptionCondition(input: string): { text: string; condition?: string } {
|
|
299
|
+
const match = input.match(/\s\[\s*if\s+([^\]]+)\]\s*$/i);
|
|
300
|
+
if (match) {
|
|
301
|
+
const text = input.slice(0, match.index).trimEnd();
|
|
302
|
+
return { text, condition: match[1].trim() };
|
|
303
|
+
}
|
|
304
|
+
return { text: input };
|
|
305
|
+
}
|
|
295
306
|
|
|
296
307
|
private parseStatementsUntilStop(shouldStop: () => boolean): Statement[] {
|
|
297
308
|
const out: Statement[] = [];
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import React, { useState, useMemo } from "react";
|
|
2
2
|
import { parseYarn } from "../parse/parser.js";
|
|
3
3
|
import { compile } from "../compile/compiler.js";
|
|
4
|
-
import { useYarnRunner } from "./useYarnRunner.js";
|
|
5
4
|
import { DialogueView } from "./DialogueView.js";
|
|
6
5
|
import { parseScenes } from "../scene/parser.js";
|
|
7
6
|
import type { SceneCollection } from "../scene/types.js";
|
|
@@ -9,11 +8,12 @@ import type { SceneCollection } from "../scene/types.js";
|
|
|
9
8
|
const DEFAULT_YARN = `title: Start
|
|
10
9
|
scene: scene1
|
|
11
10
|
---
|
|
12
|
-
|
|
13
|
-
Narrator: Welcome to yarn-spinner-ts!
|
|
11
|
+
<< declare $hasBadge = false >>
|
|
12
|
+
Narrator: Welcome to [b]yarn-spinner-ts[/b], {$playerName}!
|
|
13
|
+
Narrator: Current street cred: {$reputation}
|
|
14
14
|
npc: This is a dialogue system powered by Yarn Spinner.
|
|
15
15
|
Narrator: Click anywhere to continue, or choose an option below.
|
|
16
|
-
-> Start the adventure &css{backgroundColor: #4a9eff; color: white;}
|
|
16
|
+
-> Start the adventure &css{backgroundColor: #4a9eff; color: white;} [if $hasBadge]
|
|
17
17
|
Narrator: Great! Let's begin your journey.
|
|
18
18
|
<<jump NextScene>>
|
|
19
19
|
-> Learn more &css{backgroundColor: #2ecc71; color: red;}
|
|
@@ -47,7 +47,7 @@ actors:
|
|
|
47
47
|
export function DialogueExample() {
|
|
48
48
|
const [yarnText] = useState(DEFAULT_YARN);
|
|
49
49
|
const [error, setError] = useState<string | null>(null);
|
|
50
|
-
const enableTypingAnimation =
|
|
50
|
+
const enableTypingAnimation = false;
|
|
51
51
|
|
|
52
52
|
const scenes: SceneCollection = useMemo(() => {
|
|
53
53
|
try {
|
|
@@ -69,13 +69,10 @@ export function DialogueExample() {
|
|
|
69
69
|
}
|
|
70
70
|
}, [yarnText]);
|
|
71
71
|
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
variables: {},
|
|
77
|
-
}
|
|
78
|
-
);
|
|
72
|
+
const customFunctions = useMemo(() => ({
|
|
73
|
+
greet: () => {console.log('test')},
|
|
74
|
+
double: (num: unknown) => Number(num) * 2
|
|
75
|
+
}), []);
|
|
79
76
|
|
|
80
77
|
return (
|
|
81
78
|
<div
|
|
@@ -106,9 +103,10 @@ export function DialogueExample() {
|
|
|
106
103
|
)}
|
|
107
104
|
|
|
108
105
|
<DialogueView
|
|
109
|
-
|
|
110
|
-
|
|
106
|
+
program={program || { nodes: {}, enums: {} }}
|
|
107
|
+
startNode="Start"
|
|
111
108
|
scenes={scenes}
|
|
109
|
+
variables={{ playerName: "V", reputation: 3 }}
|
|
112
110
|
enableTypingAnimation={enableTypingAnimation}
|
|
113
111
|
showTypingCursor={true}
|
|
114
112
|
typingSpeed={20}
|
|
@@ -117,6 +115,10 @@ export function DialogueExample() {
|
|
|
117
115
|
autoAdvanceDelay={2000}
|
|
118
116
|
actorTransitionDuration={1000}
|
|
119
117
|
pauseBeforeAdvance={enableTypingAnimation ? 1000 : 0}
|
|
118
|
+
onStoryEnd={(info) => {
|
|
119
|
+
console.log('Story ended with variables:', info.variables);
|
|
120
|
+
}}
|
|
121
|
+
functions={customFunctions}
|
|
120
122
|
/>
|
|
121
123
|
</div>
|
|
122
124
|
</div>
|