@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rohal12/spindle",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "type": "module",
5
5
  "description": "A Preact-based story format for Twine 2.",
6
6
  "license": "Unlicense",
@@ -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 { useMergedLocals } from '../../hooks/use-merged-locals';
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
- execute(rawArgs, vars, temps, localsClone);
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 { useMergedLocals } from '../../hooks/use-merged-locals';
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
- execute(code, vars, temps, localsClone);
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 { useMergedLocals } from '../../hooks/use-merged-locals';
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
- execute(node.rawArgs, vars, temps, localsClone);
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
- execute(code, vars, temps, localsClone);
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, mergedLocals, scope.update);
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, mergedLocals, scope.update);
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 { useMergedLocals } from '../../hooks/use-merged-locals';
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
- execute(rawArgs, vars, temps, localsClone);
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
- const locals: Record<string, unknown> = {};
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
  }