@rohal12/spindle 0.52.7 → 0.52.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.
- package/README.md +26 -0
- package/dist/pkg/format.js +1 -1
- package/dist/pkg/headless.js +710 -703
- package/dist/pkg/macro-registry.json +18 -0
- package/package.json +1 -1
- package/src/action-registry.ts +9 -0
- package/src/class-registry.ts +1 -5
- package/src/components/Passage.tsx +35 -39
- package/src/components/PassageDialog.tsx +2 -2
- package/src/components/StoryInterface.tsx +2 -2
- package/src/components/macros/Checkbox.tsx +3 -8
- package/src/components/macros/Computed.tsx +1 -1
- package/src/components/macros/Cycle.tsx +3 -6
- package/src/components/macros/ExprDisplay.tsx +12 -29
- package/src/components/macros/If.tsx +35 -41
- package/src/components/macros/Include.tsx +2 -1
- package/src/components/macros/Listbox.tsx +5 -7
- package/src/components/macros/Print.tsx +3 -5
- package/src/components/macros/Radiobutton.tsx +3 -5
- package/src/components/macros/Repeat.tsx +2 -1
- package/src/components/macros/Switch.tsx +6 -13
- package/src/components/macros/Timed.tsx +14 -18
- package/src/components/macros/Type.tsx +2 -1
- package/src/components/macros/VarDisplay.tsx +12 -32
- package/src/components/macros/Widget.tsx +49 -20
- package/src/components/macros/branches.ts +18 -0
- package/src/components/macros/display.ts +19 -0
- package/src/components/macros/input-macro.ts +33 -11
- package/src/components/macros/macro-args.ts +7 -0
- package/src/components/macros/option-utils.ts +10 -7
- package/src/define-macro.ts +4 -6
- package/src/expression.ts +58 -40
- package/src/hooks/use-action.ts +7 -15
- package/src/index.tsx +5 -15
- package/src/interpolation.ts +34 -41
- package/src/markup/ast.ts +4 -3
- package/src/markup/render.tsx +4 -12
- package/src/registry.ts +13 -12
- package/src/saves/save-manager.ts +18 -13
- package/src/saves/storage.ts +19 -20
- package/src/story-api.ts +30 -73
- package/src/structural.ts +1 -18
- package/src/types-drift-check.ts +37 -50
- package/src/utils/macro-names.ts +40 -0
- package/src/utils/namespace.ts +17 -0
- package/src/widgets/widget-registry.ts +5 -4
package/src/interpolation.ts
CHANGED
|
@@ -27,6 +27,7 @@ import { getMacro, getMacroText, isSubMacro } from './registry';
|
|
|
27
27
|
import type { MacroTextContext } from './registry';
|
|
28
28
|
import { getWidget } from './widgets/widget-registry';
|
|
29
29
|
import { splitArgs } from './components/macros/arg-utils';
|
|
30
|
+
import { display } from './components/macros/display';
|
|
30
31
|
import { splitSigilTemplate } from './markup/code-attributes';
|
|
31
32
|
import {
|
|
32
33
|
checkVariableName,
|
|
@@ -118,10 +119,6 @@ export function mapTextNodes(
|
|
|
118
119
|
/** Deep enough for any real nesting; stops a widget that includes itself. */
|
|
119
120
|
const MAX_DEPTH = 100;
|
|
120
121
|
|
|
121
|
-
function display(value: unknown): string {
|
|
122
|
-
return value == null ? '' : String(value);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
122
|
/**
|
|
126
123
|
* The value of a variable reference (`name` without its sigil, with an
|
|
127
124
|
* optional dot path), as `{$name}` shows it in passage text (see
|
|
@@ -154,6 +151,17 @@ function resolveVariable(
|
|
|
154
151
|
return value;
|
|
155
152
|
}
|
|
156
153
|
|
|
154
|
+
/** Evaluate an expression in `scope`. */
|
|
155
|
+
function evaluateIn(expr: string, scope: TextScope): unknown {
|
|
156
|
+
return evaluate(
|
|
157
|
+
expr,
|
|
158
|
+
scope.variables,
|
|
159
|
+
scope.temporary,
|
|
160
|
+
scope.locals,
|
|
161
|
+
scope.transient,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
157
165
|
class TextEvaluator {
|
|
158
166
|
readonly errors: TextError[] = [];
|
|
159
167
|
|
|
@@ -163,16 +171,6 @@ class TextEvaluator {
|
|
|
163
171
|
return text;
|
|
164
172
|
}
|
|
165
173
|
|
|
166
|
-
private evaluate(expr: string, scope: TextScope): unknown {
|
|
167
|
-
return evaluate(
|
|
168
|
-
expr,
|
|
169
|
-
scope.variables,
|
|
170
|
-
scope.temporary,
|
|
171
|
-
scope.locals,
|
|
172
|
-
scope.transient,
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
174
|
private node(node: ASTNode, scope: TextScope, depth: number): string {
|
|
177
175
|
switch (node.type) {
|
|
178
176
|
case 'text':
|
|
@@ -190,7 +188,7 @@ class TextEvaluator {
|
|
|
190
188
|
return this.variable(node.scope, node.name, scope);
|
|
191
189
|
case 'expression':
|
|
192
190
|
try {
|
|
193
|
-
return display(
|
|
191
|
+
return display(evaluateIn(node.expression, scope));
|
|
194
192
|
} catch (error) {
|
|
195
193
|
this.errors.push({ macro: 'expression', error });
|
|
196
194
|
return '';
|
|
@@ -255,7 +253,7 @@ class TextEvaluator {
|
|
|
255
253
|
const expr = argExprs[i];
|
|
256
254
|
if (expr !== undefined) {
|
|
257
255
|
try {
|
|
258
|
-
value =
|
|
256
|
+
value = evaluateIn(expr, scope);
|
|
259
257
|
} catch {
|
|
260
258
|
value = undefined;
|
|
261
259
|
}
|
|
@@ -279,7 +277,7 @@ class TextEvaluator {
|
|
|
279
277
|
}
|
|
280
278
|
|
|
281
279
|
const ctx: MacroTextContext = {
|
|
282
|
-
evaluate: (expr) =>
|
|
280
|
+
evaluate: (expr) => evaluateIn(expr, scope),
|
|
283
281
|
renderText: (nodes, locals) =>
|
|
284
282
|
this.nested(
|
|
285
283
|
node.name,
|
|
@@ -369,15 +367,7 @@ export function interpolateCode(
|
|
|
369
367
|
}
|
|
370
368
|
} else {
|
|
371
369
|
try {
|
|
372
|
-
text += display(
|
|
373
|
-
evaluate(
|
|
374
|
-
part.expr,
|
|
375
|
-
scope.variables,
|
|
376
|
-
scope.temporary,
|
|
377
|
-
scope.locals,
|
|
378
|
-
scope.transient,
|
|
379
|
-
),
|
|
380
|
-
);
|
|
370
|
+
text += display(evaluateIn(part.expr, scope));
|
|
381
371
|
} catch (error) {
|
|
382
372
|
errors.push({ macro: 'expression', error });
|
|
383
373
|
}
|
|
@@ -386,22 +376,28 @@ export function interpolateCode(
|
|
|
386
376
|
return { text, errors };
|
|
387
377
|
}
|
|
388
378
|
|
|
379
|
+
/** The namespaces of a TextScope, passed one by one. */
|
|
380
|
+
type ScopeArgs = [
|
|
381
|
+
variables: Record<string, unknown>,
|
|
382
|
+
temporary: Record<string, unknown>,
|
|
383
|
+
locals: Record<string, unknown>,
|
|
384
|
+
transient?: Record<string, unknown>,
|
|
385
|
+
];
|
|
386
|
+
|
|
387
|
+
function scopeOf(
|
|
388
|
+
...[variables, temporary, locals, transient = {}]: ScopeArgs
|
|
389
|
+
): TextScope {
|
|
390
|
+
return { variables, temporary, locals, transient };
|
|
391
|
+
}
|
|
392
|
+
|
|
389
393
|
/**
|
|
390
394
|
* Evaluate text-only markup to a string, throwing the first error met.
|
|
391
395
|
*/
|
|
392
396
|
export function interpolate(
|
|
393
397
|
template: string,
|
|
394
|
-
|
|
395
|
-
temporary: Record<string, unknown>,
|
|
396
|
-
locals: Record<string, unknown>,
|
|
397
|
-
transient: Record<string, unknown> = {},
|
|
398
|
+
...namespaces: ScopeArgs
|
|
398
399
|
): string {
|
|
399
|
-
const { text, errors } = interpolateText(template,
|
|
400
|
-
variables,
|
|
401
|
-
temporary,
|
|
402
|
-
locals,
|
|
403
|
-
transient,
|
|
404
|
-
});
|
|
400
|
+
const { text, errors } = interpolateText(template, scopeOf(...namespaces));
|
|
405
401
|
if (errors.length > 0) throw errors[0]!.error;
|
|
406
402
|
return text;
|
|
407
403
|
}
|
|
@@ -412,10 +408,7 @@ export function interpolate(
|
|
|
412
408
|
*/
|
|
413
409
|
export function interpolateExpression(
|
|
414
410
|
expr: string,
|
|
415
|
-
|
|
416
|
-
temporary: Record<string, unknown>,
|
|
417
|
-
locals: Record<string, unknown>,
|
|
418
|
-
transient: Record<string, unknown> = {},
|
|
411
|
+
...namespaces: ScopeArgs
|
|
419
412
|
): string {
|
|
420
|
-
return display(
|
|
413
|
+
return display(evaluateIn(expr, scopeOf(...namespaces)));
|
|
421
414
|
}
|
package/src/markup/ast.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
type Token,
|
|
7
7
|
type VariableScope,
|
|
8
8
|
} from './tokenizer';
|
|
9
|
+
import { NameSet } from '../utils/macro-names';
|
|
9
10
|
|
|
10
11
|
export interface TextNode {
|
|
11
12
|
type: 'text';
|
|
@@ -51,7 +52,7 @@ export type ASTNode =
|
|
|
51
52
|
| HtmlNode;
|
|
52
53
|
|
|
53
54
|
/** Macros that require a closing tag and can contain children */
|
|
54
|
-
const BLOCK_MACROS = new
|
|
55
|
+
const BLOCK_MACROS = new NameSet([
|
|
55
56
|
'if',
|
|
56
57
|
'for',
|
|
57
58
|
'do',
|
|
@@ -70,12 +71,12 @@ const BLOCK_MACROS = new Set([
|
|
|
70
71
|
|
|
71
72
|
/** Register a custom macro as a block macro so the AST builder nests children. */
|
|
72
73
|
export function registerBlockMacro(name: string): void {
|
|
73
|
-
BLOCK_MACROS.add(name
|
|
74
|
+
BLOCK_MACROS.add(name);
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
/** Unregister a custom block macro (for test cleanup). */
|
|
77
78
|
export function unregisterBlockMacro(name: string): void {
|
|
78
|
-
BLOCK_MACROS.delete(name
|
|
79
|
+
BLOCK_MACROS.delete(name);
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
/** Map from branch macro name → required parent macro name */
|
package/src/markup/render.tsx
CHANGED
|
@@ -588,12 +588,9 @@ function HtmlNodeRenderer({ node }: { node: HtmlNode }) {
|
|
|
588
588
|
|
|
589
589
|
function ChildrenSlot() {
|
|
590
590
|
const childrenAST = useContext(WidgetChildrenContext);
|
|
591
|
-
const
|
|
592
|
-
const inline = useContext(InlineContext);
|
|
593
|
-
const raw = useContext(RawTextContext);
|
|
594
|
-
const locals = useContext(LocalsValuesContext);
|
|
591
|
+
const renderOptions = useRenderOptions();
|
|
595
592
|
if (!childrenAST || childrenAST.length === 0) return null;
|
|
596
|
-
return <>{renderNodes(childrenAST,
|
|
593
|
+
return <>{renderNodes(childrenAST, renderOptions)}</>;
|
|
597
594
|
}
|
|
598
595
|
|
|
599
596
|
/**
|
|
@@ -669,10 +666,7 @@ function renderSingleNode(node: ASTNode): preact.ComponentChildren {
|
|
|
669
666
|
return (
|
|
670
667
|
<VarDisplay
|
|
671
668
|
key={key}
|
|
672
|
-
|
|
673
|
-
scope={node.scope}
|
|
674
|
-
className={node.className}
|
|
675
|
-
id={node.id}
|
|
669
|
+
node={node}
|
|
676
670
|
/>
|
|
677
671
|
);
|
|
678
672
|
|
|
@@ -680,9 +674,7 @@ function renderSingleNode(node: ASTNode): preact.ComponentChildren {
|
|
|
680
674
|
return (
|
|
681
675
|
<ExprDisplay
|
|
682
676
|
key={key}
|
|
683
|
-
|
|
684
|
-
className={node.className}
|
|
685
|
-
id={node.id}
|
|
677
|
+
node={node}
|
|
686
678
|
/>
|
|
687
679
|
);
|
|
688
680
|
|
package/src/registry.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ComponentType } from 'preact';
|
|
2
2
|
import type { ASTNode, Branch } from './markup/ast';
|
|
3
|
+
import { NameMap, NameSet } from './utils/macro-names';
|
|
3
4
|
|
|
4
5
|
export interface MacroProps {
|
|
5
6
|
rawArgs: string;
|
|
@@ -9,17 +10,17 @@ export interface MacroProps {
|
|
|
9
10
|
branches?: Branch[];
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
const registry = new
|
|
13
|
+
const registry = new NameMap<ComponentType<MacroProps>>();
|
|
13
14
|
|
|
14
15
|
export function registerMacro(
|
|
15
16
|
name: string,
|
|
16
17
|
component: ComponentType<MacroProps>,
|
|
17
18
|
): void {
|
|
18
|
-
registry.set(name
|
|
19
|
+
registry.set(name, component);
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
export function getMacro(name: string): ComponentType<MacroProps> | undefined {
|
|
22
|
-
return registry.get(name
|
|
23
|
+
return registry.get(name);
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
/**
|
|
@@ -42,29 +43,29 @@ export type MacroTextRenderer = (
|
|
|
42
43
|
ctx: MacroTextContext,
|
|
43
44
|
) => string;
|
|
44
45
|
|
|
45
|
-
const textRegistry = new
|
|
46
|
+
const textRegistry = new NameMap<MacroTextRenderer>();
|
|
46
47
|
|
|
47
48
|
/** Register (or, with undefined, remove) the text form of a macro. */
|
|
48
49
|
export function registerMacroText(
|
|
49
50
|
name: string,
|
|
50
51
|
text: MacroTextRenderer | undefined,
|
|
51
52
|
): void {
|
|
52
|
-
if (text) textRegistry.set(name
|
|
53
|
-
else textRegistry.delete(name
|
|
53
|
+
if (text) textRegistry.set(name, text);
|
|
54
|
+
else textRegistry.delete(name);
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
export function getMacroText(name: string): MacroTextRenderer | undefined {
|
|
57
|
-
return textRegistry.get(name
|
|
58
|
+
return textRegistry.get(name);
|
|
58
59
|
}
|
|
59
60
|
|
|
60
|
-
const subMacros = new
|
|
61
|
+
const subMacros = new NameSet();
|
|
61
62
|
|
|
62
63
|
export function registerSubMacro(name: string): void {
|
|
63
|
-
subMacros.add(name
|
|
64
|
+
subMacros.add(name);
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
export function isSubMacro(name: string): boolean {
|
|
67
|
-
return subMacros.has(name
|
|
68
|
+
return subMacros.has(name);
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
/**
|
|
@@ -133,13 +134,13 @@ export interface MacroMetadata {
|
|
|
133
134
|
parameters?: ParameterDef[];
|
|
134
135
|
}
|
|
135
136
|
|
|
136
|
-
const metadataRegistry = new
|
|
137
|
+
const metadataRegistry = new NameMap<MacroMetadata>();
|
|
137
138
|
|
|
138
139
|
export function registerMacroMetadata(
|
|
139
140
|
name: string,
|
|
140
141
|
metadata: MacroMetadata,
|
|
141
142
|
): void {
|
|
142
|
-
metadataRegistry.set(name
|
|
143
|
+
metadataRegistry.set(name, metadata);
|
|
143
144
|
}
|
|
144
145
|
|
|
145
146
|
export function getMacroRegistry(): MacroMetadata[] {
|
|
@@ -9,7 +9,8 @@ import type {
|
|
|
9
9
|
} from './types';
|
|
10
10
|
import { estimatePayloadBytes, isSaveExport, isSavePayload } from './types';
|
|
11
11
|
import { getBackend, resetBackend } from './storage';
|
|
12
|
-
import {
|
|
12
|
+
import { serialize, deserialize } from '../class-registry';
|
|
13
|
+
import { deepClone } from '../structural';
|
|
13
14
|
import { emit } from '../event-emitter';
|
|
14
15
|
import { withoutDraws } from '../prng';
|
|
15
16
|
|
|
@@ -101,6 +102,18 @@ function queued<A extends unknown[], R>(
|
|
|
101
102
|
return (...args) => inOrder(() => op(...args));
|
|
102
103
|
}
|
|
103
104
|
|
|
105
|
+
/** An operation passing the save `saveId`, if it is stored, to `read`. */
|
|
106
|
+
const readingSave = <R>(read: (record: SaveRecord | undefined) => R) =>
|
|
107
|
+
queued(async (saveId: string) =>
|
|
108
|
+
read(await (await getBackend()).getSave(saveId)),
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
/** An operation passing the save a slot holds, if any, to `read`. */
|
|
112
|
+
const readingSlot = <R>(read: (record: SaveRecord | undefined) => R) =>
|
|
113
|
+
queued(async (ifid: string, slot?: string) =>
|
|
114
|
+
read(await slotRecord(ifid, slot)),
|
|
115
|
+
);
|
|
116
|
+
|
|
104
117
|
// --- Playthroughs ---
|
|
105
118
|
|
|
106
119
|
/**
|
|
@@ -371,9 +384,7 @@ export function deserializePayload(payload: SavePayload): SavePayload {
|
|
|
371
384
|
const livePayload = (record: SaveRecord | undefined) =>
|
|
372
385
|
record && deserializePayload(record.payload);
|
|
373
386
|
|
|
374
|
-
export const loadSave =
|
|
375
|
-
livePayload(await (await getBackend()).getSave(saveId)),
|
|
376
|
-
);
|
|
387
|
+
export const loadSave = readingSave(livePayload);
|
|
377
388
|
|
|
378
389
|
/**
|
|
379
390
|
* Delete a save record. If the default slot or a named slot holds it, that
|
|
@@ -623,9 +634,7 @@ async function hasQuickSaveNow(ifid: string, slot?: string): Promise<boolean> {
|
|
|
623
634
|
return (await slotRecord(ifid, slot)) !== undefined;
|
|
624
635
|
}
|
|
625
636
|
|
|
626
|
-
export const loadQuickSave =
|
|
627
|
-
livePayload(await slotRecord(ifid, slot)),
|
|
628
|
-
);
|
|
637
|
+
export const loadQuickSave = readingSlot(livePayload);
|
|
629
638
|
|
|
630
639
|
/**
|
|
631
640
|
* Read the save held in a slot (the default slot when `slot` is omitted) for
|
|
@@ -753,9 +762,7 @@ function toExport(record: SaveRecord | undefined): SaveExport | undefined {
|
|
|
753
762
|
};
|
|
754
763
|
}
|
|
755
764
|
|
|
756
|
-
export const exportSave =
|
|
757
|
-
toExport(await (await getBackend()).getSave(saveId)),
|
|
758
|
-
);
|
|
765
|
+
export const exportSave = readingSave(toExport);
|
|
759
766
|
|
|
760
767
|
/**
|
|
761
768
|
* Validate an export against the running story and build the record to store:
|
|
@@ -812,9 +819,7 @@ export function importSave(
|
|
|
812
819
|
* Export the save held in a slot (default autosave slot when `slot` is omitted).
|
|
813
820
|
* Returns undefined if the slot is empty.
|
|
814
821
|
*/
|
|
815
|
-
export const exportSlotSave =
|
|
816
|
-
toExport(await slotRecord(ifid, slot)),
|
|
817
|
-
);
|
|
822
|
+
export const exportSlotSave = readingSlot(toExport);
|
|
818
823
|
|
|
819
824
|
/**
|
|
820
825
|
* Import an exported save into a slot (default autosave slot when `slot` is
|
package/src/saves/storage.ts
CHANGED
|
@@ -256,6 +256,19 @@ export function createLocalStorageBackend(): StorageBackend {
|
|
|
256
256
|
const IDB_DB_NAME = 'spindle';
|
|
257
257
|
const IDB_DB_VERSION = 1;
|
|
258
258
|
|
|
259
|
+
/** Each object store's key path, and the key path of each of its indexes. */
|
|
260
|
+
const IDB_STORES: Record<
|
|
261
|
+
TableName,
|
|
262
|
+
{ keyPath: string; indexes: Record<string, string> }
|
|
263
|
+
> = {
|
|
264
|
+
saves: {
|
|
265
|
+
keyPath: 'meta.id',
|
|
266
|
+
indexes: { ifid: 'meta.ifid', playthroughId: 'meta.playthroughId' },
|
|
267
|
+
},
|
|
268
|
+
playthroughs: { keyPath: 'id', indexes: { ifid: 'ifid' } },
|
|
269
|
+
meta: { keyPath: 'key', indexes: {} },
|
|
270
|
+
};
|
|
271
|
+
|
|
259
272
|
function createIDBBackend(): StorageBackend {
|
|
260
273
|
let dbPromise: Promise<IDBDatabase> | null = null;
|
|
261
274
|
|
|
@@ -267,26 +280,12 @@ function createIDBBackend(): StorageBackend {
|
|
|
267
280
|
|
|
268
281
|
req.onupgradeneeded = () => {
|
|
269
282
|
const db = req.result;
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
savesStore.createIndex('playthroughId', 'meta.playthroughId', {
|
|
277
|
-
unique: false,
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
if (!db.objectStoreNames.contains('playthroughs')) {
|
|
282
|
-
const ptStore = db.createObjectStore('playthroughs', {
|
|
283
|
-
keyPath: 'id',
|
|
284
|
-
});
|
|
285
|
-
ptStore.createIndex('ifid', 'ifid', { unique: false });
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
if (!db.objectStoreNames.contains('meta')) {
|
|
289
|
-
db.createObjectStore('meta', { keyPath: 'key' });
|
|
283
|
+
for (const [name, { keyPath, indexes }] of Object.entries(IDB_STORES)) {
|
|
284
|
+
if (db.objectStoreNames.contains(name)) continue;
|
|
285
|
+
const store = db.createObjectStore(name, { keyPath });
|
|
286
|
+
for (const [index, path] of Object.entries(indexes)) {
|
|
287
|
+
store.createIndex(index, path, { unique: false });
|
|
288
|
+
}
|
|
290
289
|
}
|
|
291
290
|
};
|
|
292
291
|
|
package/src/story-api.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useStoryStore, trackRuntimeUnsub } from './store';
|
|
2
|
-
import type { VariableNamespaces } from './store';
|
|
2
|
+
import type { StoryState, VariableNamespaces } from './store';
|
|
3
3
|
import {
|
|
4
4
|
on as emitterOn,
|
|
5
5
|
emit,
|
|
@@ -24,7 +24,7 @@ import { registerClass } from './class-registry';
|
|
|
24
24
|
import { frozenCopy, getActiveMutationScope } from './execute-mutation';
|
|
25
25
|
import { getByPath, setByPath } from './utils/object-path';
|
|
26
26
|
import { changedNames, checkVariableName, ownValue } from './utils/namespace';
|
|
27
|
-
import {
|
|
27
|
+
import { historyQueries } from './expression';
|
|
28
28
|
import { defineMacro } from './define-macro';
|
|
29
29
|
import type { MacroDefinition } from './define-macro';
|
|
30
30
|
import { getMacroRegistry as _getMacroRegistry } from './registry';
|
|
@@ -259,9 +259,15 @@ function setOne(draft: VariableNamespaces, name: string, value: unknown): void {
|
|
|
259
259
|
}
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
+
/** A method calling the store action `name`, as the store holds it then. */
|
|
263
|
+
function storeAction<K extends keyof StoryState>(name: K): StoryState[K] {
|
|
264
|
+
return ((...args: unknown[]) =>
|
|
265
|
+
(useStoryStore.getState()[name] as (...args: unknown[]) => unknown)(
|
|
266
|
+
...args,
|
|
267
|
+
)) as StoryState[K];
|
|
268
|
+
}
|
|
269
|
+
|
|
262
270
|
function createStoryAPI(): StoryAPI {
|
|
263
|
-
const visits = countQueries(() => useStoryStore.getState().visitCounts);
|
|
264
|
-
const renders = countQueries(() => useStoryStore.getState().renderCounts);
|
|
265
271
|
return {
|
|
266
272
|
get(name: string): unknown {
|
|
267
273
|
const { isTransient, key } = parseName(name);
|
|
@@ -300,64 +306,23 @@ function createStoryAPI(): StoryAPI {
|
|
|
300
306
|
// Called from running mutation code, these store actions commit the
|
|
301
307
|
// code's writes so far before they record, replace or save state, and
|
|
302
308
|
// the code goes on from the state they leave (see storyStateGuard).
|
|
303
|
-
goto
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
return useStoryStore.getState().save(slot, custom);
|
|
321
|
-
},
|
|
322
|
-
|
|
323
|
-
load(slot?: string): Promise<void> {
|
|
324
|
-
return useStoryStore.getState().load(slot);
|
|
325
|
-
},
|
|
326
|
-
|
|
327
|
-
hasSave(slot?: string): boolean {
|
|
328
|
-
return useStoryStore.getState().hasSave(slot);
|
|
329
|
-
},
|
|
330
|
-
|
|
331
|
-
getSaveInfo(slot?: string): Promise<SaveInfo | null> {
|
|
332
|
-
return useStoryStore.getState().getSaveInfo(slot);
|
|
333
|
-
},
|
|
334
|
-
|
|
335
|
-
listSaves(): Promise<SaveInfo[]> {
|
|
336
|
-
return useStoryStore.getState().listSaves();
|
|
337
|
-
},
|
|
338
|
-
|
|
339
|
-
deleteSave(slot?: string): Promise<void> {
|
|
340
|
-
return useStoryStore.getState().deleteSave(slot);
|
|
341
|
-
},
|
|
342
|
-
|
|
343
|
-
exportSave(slot?: string): Promise<SaveExport | null> {
|
|
344
|
-
return useStoryStore.getState().exportSave(slot);
|
|
345
|
-
},
|
|
346
|
-
|
|
347
|
-
importSave(data: unknown, slot?: string): Promise<SaveInfo> {
|
|
348
|
-
return useStoryStore.getState().importSave(data, slot);
|
|
349
|
-
},
|
|
350
|
-
|
|
351
|
-
visited: visits.count,
|
|
352
|
-
hasVisited: visits.has,
|
|
353
|
-
hasVisitedAny: visits.any,
|
|
354
|
-
hasVisitedAll: visits.all,
|
|
355
|
-
rendered: renders.count,
|
|
356
|
-
hasRendered: renders.has,
|
|
357
|
-
hasRenderedAny: renders.any,
|
|
358
|
-
hasRenderedAll: renders.all,
|
|
359
|
-
currentPassage,
|
|
360
|
-
previousPassage,
|
|
309
|
+
goto: storeAction('navigate'),
|
|
310
|
+
back: storeAction('goBack'),
|
|
311
|
+
forward: storeAction('goForward'),
|
|
312
|
+
restart: storeAction('restart'),
|
|
313
|
+
save: storeAction('save'),
|
|
314
|
+
load: storeAction('load'),
|
|
315
|
+
hasSave: storeAction('hasSave'),
|
|
316
|
+
getSaveInfo: storeAction('getSaveInfo'),
|
|
317
|
+
listSaves: storeAction('listSaves'),
|
|
318
|
+
deleteSave: storeAction('deleteSave'),
|
|
319
|
+
exportSave: storeAction('exportSave'),
|
|
320
|
+
importSave: storeAction('importSave'),
|
|
321
|
+
|
|
322
|
+
...historyQueries(
|
|
323
|
+
() => useStoryStore.getState().visitCounts,
|
|
324
|
+
() => useStoryStore.getState().renderCounts,
|
|
325
|
+
),
|
|
361
326
|
|
|
362
327
|
get title(): string {
|
|
363
328
|
return useStoryStore.getState().storyData?.name || '';
|
|
@@ -416,17 +381,9 @@ function createStoryAPI(): StoryAPI {
|
|
|
416
381
|
return { usage: 0, quota: 0, estimateSupported: false };
|
|
417
382
|
},
|
|
418
383
|
|
|
419
|
-
clearGameData()
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
clearAllData(): Promise<void> {
|
|
424
|
-
return useStoryStore.getState().clearAllData();
|
|
425
|
-
},
|
|
426
|
-
|
|
427
|
-
deletePlaythrough(playthroughId: string): Promise<void> {
|
|
428
|
-
return useStoryStore.getState().deletePlaythrough(playthroughId);
|
|
429
|
-
},
|
|
384
|
+
clearGameData: storeAction('clearGameData'),
|
|
385
|
+
clearAllData: storeAction('clearAllData'),
|
|
386
|
+
deletePlaythrough: storeAction('deletePlaythrough'),
|
|
430
387
|
|
|
431
388
|
get backend() {
|
|
432
389
|
return getBackendType();
|
package/src/structural.ts
CHANGED
|
@@ -4,26 +4,9 @@
|
|
|
4
4
|
// what a change is.
|
|
5
5
|
|
|
6
6
|
import { registeredClassName } from './class-registry';
|
|
7
|
-
import { hasOwn } from './utils/namespace';
|
|
7
|
+
import { hasOwn, setOwn } from './utils/namespace';
|
|
8
8
|
import { deleteByPath, getByPath, setByPath } from './utils/object-path';
|
|
9
9
|
|
|
10
|
-
/**
|
|
11
|
-
* Set an own enumerable property. A "__proto__" key is defined as an own
|
|
12
|
-
* property (as JSON.parse does) instead of replacing the prototype.
|
|
13
|
-
*/
|
|
14
|
-
export function setOwn(target: object, key: string, value: unknown): void {
|
|
15
|
-
if (key === '__proto__') {
|
|
16
|
-
Object.defineProperty(target, key, {
|
|
17
|
-
value,
|
|
18
|
-
enumerable: true,
|
|
19
|
-
writable: true,
|
|
20
|
-
configurable: true,
|
|
21
|
-
});
|
|
22
|
-
} else {
|
|
23
|
-
(target as Record<string, unknown>)[key] = value;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
10
|
// --- Deep Clone ---
|
|
28
11
|
|
|
29
12
|
function isPlainObject(value: unknown): value is Record<string, unknown> {
|