@rohal12/spindle 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -1
- package/dist/pkg/format.js +1 -1
- package/package.json +1 -1
- package/src/components/macros/Button.tsx +3 -28
- package/src/components/macros/Do.tsx +3 -29
- package/src/components/macros/MacroLink.tsx +6 -30
- package/src/components/macros/Set.tsx +3 -29
- package/src/execute-mutation.ts +32 -0
- package/src/hooks/use-merged-locals.ts +17 -7
package/package.json
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { useContext } from 'preact/hooks';
|
|
2
|
-
import { useStoryStore } from '../../store';
|
|
3
|
-
import { execute } from '../../expression';
|
|
4
2
|
import { renderInlineNodes, LocalsContext } from '../../markup/render';
|
|
5
|
-
import { deepClone } from '../../class-registry';
|
|
6
3
|
import { collectText } from '../../utils/extract-text';
|
|
7
4
|
import { useAction } from '../../hooks/use-action';
|
|
8
|
-
import {
|
|
5
|
+
import { stripLocalsPrefix } from '../../hooks/use-merged-locals';
|
|
9
6
|
import { useInterpolate } from '../../hooks/use-interpolate';
|
|
7
|
+
import { executeMutation } from '../../execute-mutation';
|
|
10
8
|
import { currentSourceLocation } from '../../utils/source-location';
|
|
11
9
|
import type { ASTNode } from '../../markup/ast';
|
|
12
10
|
|
|
@@ -22,38 +20,15 @@ export function Button({ rawArgs, children, className, id }: ButtonProps) {
|
|
|
22
20
|
className = resolve(className);
|
|
23
21
|
id = resolve(id);
|
|
24
22
|
const scope = useContext(LocalsContext);
|
|
25
|
-
const [, , mergedLocals] = useMergedLocals();
|
|
26
23
|
|
|
27
24
|
const handleClick = () => {
|
|
28
|
-
const state = useStoryStore.getState();
|
|
29
|
-
const vars = deepClone(state.variables);
|
|
30
|
-
const temps = deepClone(state.temporary);
|
|
31
|
-
const localsClone = { ...mergedLocals };
|
|
32
|
-
|
|
33
25
|
try {
|
|
34
|
-
|
|
26
|
+
executeMutation(rawArgs, stripLocalsPrefix(scope.values), scope.update);
|
|
35
27
|
} catch (err) {
|
|
36
28
|
console.error(
|
|
37
29
|
`spindle: Error in {button ${rawArgs}}${currentSourceLocation()}:`,
|
|
38
30
|
err,
|
|
39
31
|
);
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
for (const key of Object.keys(vars)) {
|
|
44
|
-
if (vars[key] !== state.variables[key]) {
|
|
45
|
-
state.setVariable(key, vars[key]);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
for (const key of Object.keys(temps)) {
|
|
49
|
-
if (temps[key] !== state.temporary[key]) {
|
|
50
|
-
state.setTemporary(key, temps[key]);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
for (const key of Object.keys(localsClone)) {
|
|
54
|
-
if (localsClone[key] !== mergedLocals[key]) {
|
|
55
|
-
scope.update(`@${key}`, localsClone[key]);
|
|
56
|
-
}
|
|
57
32
|
}
|
|
58
33
|
};
|
|
59
34
|
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { useLayoutEffect, useContext } from 'preact/hooks';
|
|
2
|
-
import { useStoryStore } from '../../store';
|
|
3
|
-
import { execute } from '../../expression';
|
|
4
2
|
import type { ASTNode } from '../../markup/ast';
|
|
5
|
-
import { deepClone } from '../../class-registry';
|
|
6
3
|
import { LocalsContext } from '../../markup/render';
|
|
7
|
-
import {
|
|
4
|
+
import { stripLocalsPrefix } from '../../hooks/use-merged-locals';
|
|
5
|
+
import { executeMutation } from '../../execute-mutation';
|
|
8
6
|
import { currentSourceLocation } from '../../utils/source-location';
|
|
9
7
|
|
|
10
8
|
interface DoProps {
|
|
@@ -21,36 +19,12 @@ function collectText(nodes: ASTNode[]): string {
|
|
|
21
19
|
export function Do({ children }: DoProps) {
|
|
22
20
|
const code = collectText(children);
|
|
23
21
|
const scope = useContext(LocalsContext);
|
|
24
|
-
const [, , mergedLocals] = useMergedLocals();
|
|
25
22
|
|
|
26
23
|
useLayoutEffect(() => {
|
|
27
|
-
const state = useStoryStore.getState();
|
|
28
|
-
const vars = deepClone(state.variables);
|
|
29
|
-
const temps = deepClone(state.temporary);
|
|
30
|
-
const localsClone = { ...mergedLocals };
|
|
31
|
-
|
|
32
24
|
try {
|
|
33
|
-
|
|
25
|
+
executeMutation(code, stripLocalsPrefix(scope.values), scope.update);
|
|
34
26
|
} catch (err) {
|
|
35
27
|
console.error(`spindle: Error in {do}${currentSourceLocation()}:`, err);
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Diff and apply
|
|
40
|
-
for (const key of Object.keys(vars)) {
|
|
41
|
-
if (vars[key] !== state.variables[key]) {
|
|
42
|
-
state.setVariable(key, vars[key]);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
for (const key of Object.keys(temps)) {
|
|
46
|
-
if (temps[key] !== state.temporary[key]) {
|
|
47
|
-
state.setTemporary(key, temps[key]);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
for (const key of Object.keys(localsClone)) {
|
|
51
|
-
if (localsClone[key] !== mergedLocals[key]) {
|
|
52
|
-
scope.update(`@${key}`, localsClone[key]);
|
|
53
|
-
}
|
|
54
28
|
}
|
|
55
29
|
}, []);
|
|
56
30
|
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { useContext } from 'preact/hooks';
|
|
2
2
|
import { useStoryStore } from '../../store';
|
|
3
|
-
import { execute } from '../../expression';
|
|
4
3
|
import type { ASTNode } from '../../markup/ast';
|
|
5
|
-
import { deepClone } from '../../class-registry';
|
|
6
4
|
import { LocalsContext } from '../../markup/render';
|
|
7
|
-
import {
|
|
5
|
+
import { stripLocalsPrefix } from '../../hooks/use-merged-locals';
|
|
8
6
|
import { useInterpolate } from '../../hooks/use-interpolate';
|
|
7
|
+
import { executeMutation } from '../../execute-mutation';
|
|
9
8
|
import { currentSourceLocation } from '../../utils/source-location';
|
|
10
9
|
|
|
11
10
|
interface MacroLinkProps {
|
|
@@ -47,16 +46,11 @@ function executeChildren(
|
|
|
47
46
|
mergedLocals: Record<string, unknown>,
|
|
48
47
|
scopeUpdate: (key: string, value: unknown) => void,
|
|
49
48
|
) {
|
|
50
|
-
const state = useStoryStore.getState();
|
|
51
|
-
const vars = deepClone(state.variables);
|
|
52
|
-
const temps = deepClone(state.temporary);
|
|
53
|
-
const localsClone = { ...mergedLocals };
|
|
54
|
-
|
|
55
49
|
for (const node of children) {
|
|
56
50
|
if (node.type !== 'macro') continue;
|
|
57
51
|
if (node.name === 'set') {
|
|
58
52
|
try {
|
|
59
|
-
|
|
53
|
+
executeMutation(node.rawArgs, mergedLocals, scopeUpdate);
|
|
60
54
|
} catch (err) {
|
|
61
55
|
console.error(
|
|
62
56
|
`spindle: Error in {link} child {set}${currentSourceLocation()}:`,
|
|
@@ -66,7 +60,7 @@ function executeChildren(
|
|
|
66
60
|
} else if (node.name === 'do') {
|
|
67
61
|
const code = collectText(node.children);
|
|
68
62
|
try {
|
|
69
|
-
|
|
63
|
+
executeMutation(code, mergedLocals, scopeUpdate);
|
|
70
64
|
} catch (err) {
|
|
71
65
|
console.error(
|
|
72
66
|
`spindle: Error in {link} child {do}${currentSourceLocation()}:`,
|
|
@@ -75,23 +69,6 @@ function executeChildren(
|
|
|
75
69
|
}
|
|
76
70
|
}
|
|
77
71
|
}
|
|
78
|
-
|
|
79
|
-
// Apply changes
|
|
80
|
-
for (const key of Object.keys(vars)) {
|
|
81
|
-
if (vars[key] !== state.variables[key]) {
|
|
82
|
-
state.setVariable(key, vars[key]);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
for (const key of Object.keys(temps)) {
|
|
86
|
-
if (temps[key] !== state.temporary[key]) {
|
|
87
|
-
state.setTemporary(key, temps[key]);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
for (const key of Object.keys(localsClone)) {
|
|
91
|
-
if (localsClone[key] !== mergedLocals[key]) {
|
|
92
|
-
scopeUpdate(`@${key}`, localsClone[key]);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
72
|
}
|
|
96
73
|
|
|
97
74
|
export function MacroLink({
|
|
@@ -105,11 +82,10 @@ export function MacroLink({
|
|
|
105
82
|
id = resolve(id);
|
|
106
83
|
const { display, passage } = parseArgs(rawArgs);
|
|
107
84
|
const scope = useContext(LocalsContext);
|
|
108
|
-
const [, , mergedLocals] = useMergedLocals();
|
|
109
85
|
|
|
110
86
|
const handleClick = (e: Event) => {
|
|
111
87
|
e.preventDefault();
|
|
112
|
-
executeChildren(children,
|
|
88
|
+
executeChildren(children, stripLocalsPrefix(scope.values), scope.update);
|
|
113
89
|
if (passage) {
|
|
114
90
|
useStoryStore.getState().navigate(passage);
|
|
115
91
|
}
|
|
@@ -122,7 +98,7 @@ export function MacroLink({
|
|
|
122
98
|
label: display,
|
|
123
99
|
target: passage ?? undefined,
|
|
124
100
|
perform: () => {
|
|
125
|
-
executeChildren(children,
|
|
101
|
+
executeChildren(children, stripLocalsPrefix(scope.values), scope.update);
|
|
126
102
|
if (passage) {
|
|
127
103
|
useStoryStore.getState().navigate(passage);
|
|
128
104
|
}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { useRef, useContext } from 'preact/hooks';
|
|
2
|
-
import { useStoryStore } from '../../store';
|
|
3
|
-
import { execute } from '../../expression';
|
|
4
|
-
import { deepClone } from '../../class-registry';
|
|
5
2
|
import { LocalsContext } from '../../markup/render';
|
|
6
|
-
import {
|
|
3
|
+
import { stripLocalsPrefix } from '../../hooks/use-merged-locals';
|
|
4
|
+
import { executeMutation } from '../../execute-mutation';
|
|
7
5
|
import { currentSourceLocation } from '../../utils/source-location';
|
|
8
6
|
|
|
9
7
|
interface SetProps {
|
|
@@ -12,18 +10,13 @@ interface SetProps {
|
|
|
12
10
|
|
|
13
11
|
export function Set({ rawArgs }: SetProps) {
|
|
14
12
|
const scope = useContext(LocalsContext);
|
|
15
|
-
const [, , mergedLocals] = useMergedLocals();
|
|
16
13
|
const ran = useRef(false);
|
|
17
14
|
|
|
18
15
|
if (!ran.current) {
|
|
19
16
|
ran.current = true;
|
|
20
|
-
const state = useStoryStore.getState();
|
|
21
|
-
const vars = deepClone(state.variables);
|
|
22
|
-
const temps = deepClone(state.temporary);
|
|
23
|
-
const localsClone = { ...mergedLocals };
|
|
24
17
|
|
|
25
18
|
try {
|
|
26
|
-
|
|
19
|
+
executeMutation(rawArgs, stripLocalsPrefix(scope.values), scope.update);
|
|
27
20
|
} catch (err) {
|
|
28
21
|
console.error(
|
|
29
22
|
`spindle: Error in {set ${rawArgs}}${currentSourceLocation()}:`,
|
|
@@ -31,25 +24,6 @@ export function Set({ rawArgs }: SetProps) {
|
|
|
31
24
|
);
|
|
32
25
|
return null;
|
|
33
26
|
}
|
|
34
|
-
|
|
35
|
-
// Diff and apply store changes
|
|
36
|
-
for (const key of Object.keys(vars)) {
|
|
37
|
-
if (vars[key] !== state.variables[key]) {
|
|
38
|
-
state.setVariable(key, vars[key]);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
for (const key of Object.keys(temps)) {
|
|
42
|
-
if (temps[key] !== state.temporary[key]) {
|
|
43
|
-
state.setTemporary(key, temps[key]);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Diff and apply locals changes
|
|
48
|
-
for (const key of Object.keys(localsClone)) {
|
|
49
|
-
if (localsClone[key] !== mergedLocals[key]) {
|
|
50
|
-
scope.update(`@${key}`, localsClone[key]);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
27
|
}
|
|
54
28
|
|
|
55
29
|
return null;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { useStoryStore } from './store';
|
|
2
|
+
import { execute } from './expression';
|
|
3
|
+
import { deepClone } from './class-registry';
|
|
4
|
+
|
|
5
|
+
export function executeMutation(
|
|
6
|
+
code: string,
|
|
7
|
+
mergedLocals: Record<string, unknown>,
|
|
8
|
+
scopeUpdate: (key: string, value: unknown) => void,
|
|
9
|
+
): void {
|
|
10
|
+
const state = useStoryStore.getState();
|
|
11
|
+
const vars = deepClone(state.variables);
|
|
12
|
+
const temps = deepClone(state.temporary);
|
|
13
|
+
const localsClone = { ...mergedLocals };
|
|
14
|
+
|
|
15
|
+
execute(code, vars, temps, localsClone);
|
|
16
|
+
|
|
17
|
+
for (const key of Object.keys(vars)) {
|
|
18
|
+
if (vars[key] !== state.variables[key]) {
|
|
19
|
+
state.setVariable(key, vars[key]);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
for (const key of Object.keys(temps)) {
|
|
23
|
+
if (temps[key] !== state.temporary[key]) {
|
|
24
|
+
state.setTemporary(key, temps[key]);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
for (const key of Object.keys(localsClone)) {
|
|
28
|
+
if (localsClone[key] !== mergedLocals[key]) {
|
|
29
|
+
scopeUpdate(`@${key}`, localsClone[key]);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -2,6 +2,22 @@ import { useContext, useMemo } from 'preact/hooks';
|
|
|
2
2
|
import { useStoryStore } from '../store';
|
|
3
3
|
import { LocalsContext } from '../markup/render';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Strip `@` prefix from locals scope values so they can be passed
|
|
7
|
+
* directly to evaluate/execute.
|
|
8
|
+
*/
|
|
9
|
+
export function stripLocalsPrefix(
|
|
10
|
+
values: Record<string, unknown>,
|
|
11
|
+
): Record<string, unknown> {
|
|
12
|
+
const locals: Record<string, unknown> = {};
|
|
13
|
+
for (const [key, val] of Object.entries(values)) {
|
|
14
|
+
if (key.startsWith('@')) {
|
|
15
|
+
locals[key.slice(1)] = val;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return locals;
|
|
19
|
+
}
|
|
20
|
+
|
|
5
21
|
/**
|
|
6
22
|
* Return store variables, temporary, and @-prefixed locals from context.
|
|
7
23
|
* Locals use `@` prefix keys internally; the returned locals dict has
|
|
@@ -17,12 +33,6 @@ export function useMergedLocals(): readonly [
|
|
|
17
33
|
const scope = useContext(LocalsContext);
|
|
18
34
|
|
|
19
35
|
return useMemo(() => {
|
|
20
|
-
|
|
21
|
-
for (const [key, val] of Object.entries(scope.values)) {
|
|
22
|
-
if (key.startsWith('@')) {
|
|
23
|
-
locals[key.slice(1)] = val;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
return [variables, temporary, locals] as const;
|
|
36
|
+
return [variables, temporary, stripLocalsPrefix(scope.values)] as const;
|
|
27
37
|
}, [variables, temporary, scope.values]);
|
|
28
38
|
}
|