@terrafirmagreg-team/recipe 0.1.0 → 1.2.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.
Files changed (240) hide show
  1. package/package.json +16 -16
  2. package/src/components/button-copy.tsx +1 -0
  3. package/src/components/cards/card/index.tsx +5 -3
  4. package/src/components/cards/card/scene.tsx +4 -2
  5. package/src/components/cards/card/strip.tsx +4 -2
  6. package/src/components/cards/card/text.tsx +2 -1
  7. package/src/components/cards/card/tips.tsx +1 -0
  8. package/src/components/cards/default-item.tsx +6 -2
  9. package/src/components/cards/glare-filter.tsx +1 -0
  10. package/src/components/cards/paged-card.tsx +1 -0
  11. package/src/components/cards/slot-hitbox.tsx +4 -2
  12. package/src/components/cards/strip-card/buttons.tsx +7 -2
  13. package/src/components/cards/strip-card/index.tsx +3 -1
  14. package/src/components/cards/strip-card/member-cell.tsx +4 -2
  15. package/src/components/cards/strip-card/sprite.tsx +1 -0
  16. package/src/components/embed-card.tsx +44 -0
  17. package/src/components/mdx/recipe.tsx +34 -16
  18. package/src/components/tool-list.tsx +1 -0
  19. package/src/{lib/tree.ts → components/tree.tsx} +20 -12
  20. package/src/components/views/alloy/index.tsx +14 -7
  21. package/src/components/views/alloy/list.tsx +3 -2
  22. package/src/components/views/alloy/mix/index.tsx +1 -0
  23. package/src/components/views/alloy/mix/ratio.tsx +7 -6
  24. package/src/components/views/alloy/mix/rows.tsx +6 -7
  25. package/src/components/views/alloy/mix/status.tsx +8 -7
  26. package/src/components/views/alloy/mix/total.tsx +13 -12
  27. package/src/components/views/alloy/range-bar.tsx +2 -1
  28. package/src/components/views/alloy/stack-bar.tsx +4 -2
  29. package/src/components/views/browser/axis-switcher.tsx +13 -12
  30. package/src/components/views/browser/card-grid.tsx +2 -1
  31. package/src/components/views/browser/favorite-button.tsx +2 -1
  32. package/src/components/views/browser/frame.tsx +3 -2
  33. package/src/components/views/browser/index.tsx +20 -2
  34. package/src/components/views/browser/item-panel/index.tsx +11 -9
  35. package/src/components/views/browser/item-panel/skeleton.tsx +1 -0
  36. package/src/components/views/browser/item-panel-grid/cell.tsx +5 -3
  37. package/src/components/views/browser/item-panel-grid/favorite-cell.tsx +4 -2
  38. package/src/components/views/browser/item-panel-grid/index.tsx +4 -2
  39. package/src/components/views/browser/item-view/index.tsx +7 -12
  40. package/src/components/views/browser/item-view/tags.tsx +7 -5
  41. package/src/components/views/browser/item-view-header.tsx +10 -9
  42. package/src/components/views/browser/pane-tabs.tsx +1 -0
  43. package/src/components/views/browser/place-header.tsx +4 -3
  44. package/src/components/views/browser/query-controls.tsx +3 -1
  45. package/src/components/views/browser/recipe-item-header.tsx +5 -4
  46. package/src/components/views/browser/settings.tsx +4 -3
  47. package/src/components/views/browser/toolbar.tsx +3 -2
  48. package/src/components/views/browser/type-tabs.tsx +5 -3
  49. package/src/components/views/browser/type-view.tsx +7 -12
  50. package/src/components/views/browser/workstation-rail.tsx +4 -3
  51. package/src/components/views/calc/calculator/actions.tsx +4 -14
  52. package/src/components/views/calc/calculator/help.tsx +69 -28
  53. package/src/components/views/calc/calculator/index.tsx +34 -82
  54. package/src/components/views/calc/dot-overlay.tsx +1 -0
  55. package/src/components/views/calc/edge.tsx +134 -32
  56. package/src/components/views/calc/flow/index.tsx +92 -54
  57. package/src/components/views/calc/flow/picker-dialogs.tsx +84 -6
  58. package/src/components/views/calc/flow/toolbar.tsx +9 -21
  59. package/src/components/views/calc/flow/view-menu.tsx +16 -120
  60. package/src/components/views/calc/member-picker.tsx +8 -6
  61. package/src/components/views/calc/node-card/actions.tsx +28 -106
  62. package/src/components/views/calc/node-card/badges.tsx +10 -28
  63. package/src/components/views/calc/node-card/chips.tsx +8 -40
  64. package/src/components/views/calc/node-card/far.tsx +13 -65
  65. package/src/components/views/calc/node-card/full.tsx +133 -83
  66. package/src/components/views/calc/node-card/index.tsx +1 -0
  67. package/src/components/views/calc/node-card/menu.tsx +93 -22
  68. package/src/components/views/calc/node-card/port-cells.tsx +186 -0
  69. package/src/components/views/calc/node-card/port-rails.tsx +251 -0
  70. package/src/components/views/calc/node-card/ports.tsx +86 -0
  71. package/src/components/views/calc/recipe-picker/body.tsx +17 -14
  72. package/src/components/views/calc/recipe-picker/footer.tsx +5 -3
  73. package/src/components/views/calc/recipe-picker/index.tsx +5 -0
  74. package/src/components/views/calc/sidebar.tsx +19 -10
  75. package/src/components/views/calc/trees/icon-picker.tsx +3 -2
  76. package/src/components/views/calc/trees/index.tsx +5 -4
  77. package/src/components/views/calc/trees/label.tsx +6 -4
  78. package/src/components/views/calc/trees/rows.tsx +6 -5
  79. package/src/components/views/forge/gui.tsx +2 -1
  80. package/src/components/views/forge/index.tsx +14 -7
  81. package/src/components/views/forge/list.tsx +3 -2
  82. package/src/components/views/forge/workbench/controls.tsx +13 -6
  83. package/src/components/views/forge/workbench/index.tsx +3 -2
  84. package/src/components/views/forge/workbench/panel.tsx +1 -0
  85. package/src/components/views/forge/workbench/ruler.tsx +5 -4
  86. package/src/components/views/forge/workbench/status.tsx +6 -5
  87. package/src/components/views/forge/workbench/tape.tsx +5 -4
  88. package/src/hooks/alloy/use-fluid-colors.ts +12 -9
  89. package/src/hooks/alloy/use-mix-state.ts +3 -2
  90. package/src/hooks/calc/flow/use-engage.ts +129 -0
  91. package/src/hooks/calc/flow/use-handlers.ts +192 -0
  92. package/src/hooks/calc/flow/use-layout.ts +105 -0
  93. package/src/hooks/calc/flow/{mount.ts → use-mount.ts} +1 -0
  94. package/src/hooks/calc/flow/{nodes.ts → use-nodes.ts} +46 -37
  95. package/src/hooks/calc/flow/use-pickers.ts +57 -0
  96. package/src/hooks/calc/flow/{viewport.ts → use-viewport.ts} +13 -26
  97. package/src/hooks/calc/flow/{window.ts → use-window.ts} +1 -0
  98. package/src/hooks/calc/use-actions.ts +208 -229
  99. package/src/hooks/calc/use-amounts.ts +23 -60
  100. package/src/hooks/calc/use-data.ts +106 -192
  101. package/src/hooks/calc/use-defaults.ts +4 -2
  102. package/src/hooks/calc/use-far-mode.ts +1 -0
  103. package/src/hooks/calc/use-focus.ts +8 -7
  104. package/src/hooks/calc/use-maps.ts +1 -0
  105. package/src/hooks/calc/use-reset.ts +1 -0
  106. package/src/hooks/calc/use-resolver.ts +32 -196
  107. package/src/hooks/calc/use-session.ts +1 -0
  108. package/src/hooks/calc/use-share.ts +5 -2
  109. package/src/hooks/calc/use-storage.ts +5 -4
  110. package/src/hooks/calc/use-tier.ts +1 -0
  111. package/src/hooks/calc/use-view-mode.ts +1 -0
  112. package/src/hooks/calc/use-worker.ts +56 -121
  113. package/src/hooks/forge/use-anvil-gui.ts +4 -2
  114. package/src/hooks/forge/use-forge-state.ts +1 -0
  115. package/src/hooks/forge/use-ruler.ts +1 -0
  116. package/src/hooks/use-add-to-calc.ts +23 -19
  117. package/src/hooks/use-categories-by-id.ts +18 -0
  118. package/src/hooks/use-favorite-hotkey.ts +1 -0
  119. package/src/hooks/use-favorites.ts +1 -0
  120. package/src/hooks/use-group-outlines.ts +1 -0
  121. package/src/hooks/use-hide-unused.ts +2 -1
  122. package/src/hooks/use-index.ts +6 -2
  123. package/src/hooks/use-item-feeds.ts +3 -1
  124. package/src/hooks/use-item-pane.ts +6 -4
  125. package/src/hooks/use-item-press.ts +27 -23
  126. package/src/hooks/use-item-tag-groups.ts +4 -2
  127. package/src/hooks/use-item-tooltips.ts +3 -1
  128. package/src/hooks/use-left-sidebar-tree.ts +11 -3
  129. package/src/hooks/use-mod-names.ts +4 -2
  130. package/src/hooks/use-mount-limit.ts +1 -0
  131. package/src/hooks/use-panel-cells.ts +3 -1
  132. package/src/hooks/use-panel-page.ts +9 -6
  133. package/src/hooks/use-panel-tabs.ts +2 -1
  134. package/src/hooks/use-query.ts +8 -6
  135. package/src/hooks/use-session.ts +6 -2
  136. package/src/hooks/use-sidebar-group.ts +3 -7
  137. package/src/hooks/use-tag-members.ts +3 -1
  138. package/src/hooks/use-tool-recipes.ts +1 -0
  139. package/src/hooks/use-type-filter.ts +1 -0
  140. package/src/lib/alloy/mix.ts +1 -0
  141. package/src/lib/alloy/parse.ts +1 -0
  142. package/src/lib/calc/defaults.ts +6 -4
  143. package/src/lib/calc/flow.ts +143 -107
  144. package/src/lib/calc/focus.ts +1 -0
  145. package/src/lib/calc/meta.ts +3 -1
  146. package/src/lib/calc/pack.ts +47 -154
  147. package/src/lib/calc/ports.ts +43 -0
  148. package/src/lib/calc/resolve.ts +6 -38
  149. package/src/lib/calc/route/buckets.ts +1 -0
  150. package/src/lib/calc/route/geometry.ts +13 -24
  151. package/src/lib/calc/route/index.ts +1 -0
  152. package/src/lib/calc/route/polyline.ts +3 -3
  153. package/src/lib/calc/route/router.ts +181 -57
  154. package/src/lib/calc/scheme.ts +226 -0
  155. package/src/lib/calc/storage/entry.ts +27 -19
  156. package/src/lib/calc/storage/index.ts +2 -0
  157. package/src/lib/calc/storage/keys.ts +4 -8
  158. package/src/lib/calc/storage/store.ts +11 -36
  159. package/src/lib/calc/tree/index.ts +2 -16
  160. package/src/lib/calc/tree/materials.ts +1 -0
  161. package/src/lib/calc/tree/recipe.ts +1 -0
  162. package/src/lib/calc/tree/share.ts +86 -149
  163. package/src/lib/calc/worker/api.ts +3 -11
  164. package/src/lib/calc/worker/fetch.ts +1 -0
  165. package/src/lib/calc/worker/flush.ts +7 -20
  166. package/src/lib/calc/worker/index.ts +1 -1
  167. package/src/lib/calc/worker/protocol.ts +16 -13
  168. package/src/lib/calc/worker/resolve.ts +32 -84
  169. package/src/lib/calc/worker/state.ts +5 -6
  170. package/src/{components/cards/card/model.ts → lib/card-model.ts} +1 -0
  171. package/src/lib/client/recipe-categories.ts +11 -93
  172. package/src/lib/client/recipe.ts +13 -11
  173. package/src/lib/client-index/catalog.ts +33 -27
  174. package/src/lib/client-index/chrome.ts +30 -30
  175. package/src/lib/client-index/index.ts +1 -0
  176. package/src/lib/client-index/registry.ts +70 -59
  177. package/src/lib/client-index/rows.ts +63 -57
  178. package/src/lib/client-index/tooltips.ts +28 -34
  179. package/src/lib/config.ts +2 -1
  180. package/src/lib/data/category/dto.ts +4 -3
  181. package/src/lib/data/category/load.ts +9 -8
  182. package/src/lib/data/category/parse.ts +4 -3
  183. package/src/lib/data/dto.ts +4 -3
  184. package/src/lib/data/grid.ts +2 -1
  185. package/src/lib/data/parse/entry.ts +3 -2
  186. package/src/lib/data/parse/index.ts +1 -0
  187. package/src/lib/data/parse/layout.ts +5 -2
  188. package/src/lib/data/parse/slots.ts +3 -2
  189. package/src/lib/data/parse/widgets.ts +3 -2
  190. package/src/lib/data/scene.ts +1 -1
  191. package/src/lib/data/slot-io.ts +1 -0
  192. package/src/lib/dump.ts +4 -3
  193. package/src/lib/embed.ts +22 -0
  194. package/src/lib/favorite-hover.ts +1 -0
  195. package/src/lib/fluid-outputs.ts +10 -12
  196. package/src/lib/forge/actions.ts +2 -1
  197. package/src/lib/forge/data.ts +2 -1
  198. package/src/lib/forge/solve.ts +1 -0
  199. package/src/lib/group.ts +1 -0
  200. package/src/lib/index-store.ts +15 -9
  201. package/src/lib/links.ts +15 -9
  202. package/src/lib/lookup.ts +10 -8
  203. package/src/lib/panel-outline.ts +1 -0
  204. package/src/lib/panel.ts +3 -2
  205. package/src/lib/preview.ts +12 -8
  206. package/src/lib/recipe-slots.ts +1 -0
  207. package/src/lib/search.ts +1 -0
  208. package/src/lib/sidebar.ts +1 -0
  209. package/src/lib/used-items.ts +1 -0
  210. package/src/locales/en_us.json +170 -226
  211. package/src/locales/ru_ru.json +170 -226
  212. package/src/locales.ts +1 -0
  213. package/src/types/alloy.ts +1 -0
  214. package/src/{hooks/calc/flow/props.ts → types/calc-flow-props.ts} +18 -14
  215. package/src/types/calc.ts +41 -44
  216. package/src/types/forge.ts +1 -0
  217. package/src/types/index.ts +4 -2
  218. package/styles/calc.css +308 -47
  219. package/src/components/cards/fit-card.tsx +0 -26
  220. package/src/components/views/calc/target.tsx +0 -271
  221. package/src/hooks/calc/flow/engage.ts +0 -89
  222. package/src/hooks/calc/flow/handlers.ts +0 -115
  223. package/src/hooks/calc/flow/layout.ts +0 -149
  224. package/src/hooks/calc/flow/pickers.ts +0 -50
  225. package/src/hooks/calc/use-calc-fill.ts +0 -91
  226. package/src/hooks/calc/use-density.ts +0 -28
  227. package/src/hooks/calc/use-derived.ts +0 -258
  228. package/src/hooks/calc/use-direction.ts +0 -23
  229. package/src/hooks/calc/use-edge-style.ts +0 -40
  230. package/src/hooks/calc/use-fill.ts +0 -213
  231. package/src/lib/alloy/alloy.test.ts +0 -186
  232. package/src/lib/calc/tree/build.ts +0 -196
  233. package/src/lib/calc/tree/context.ts +0 -42
  234. package/src/lib/calc/tree/goal.ts +0 -64
  235. package/src/lib/calc/tree/ideal.ts +0 -36
  236. package/src/lib/calc/tree/pick.ts +0 -68
  237. package/src/lib/calc/tree/tree.test.ts +0 -169
  238. package/src/lib/calc/worker/fill.ts +0 -123
  239. package/src/lib/client-index/cache.ts +0 -13
  240. package/src/lib/forge/forge.test.ts +0 -123
@@ -1,4 +1,5 @@
1
1
  'use client';
2
+ import type { DatasetRef } from '@terrafirmagreg-team/sites/location';
2
3
 
3
4
  import {
4
5
  useCallback,
@@ -10,24 +11,16 @@ import {
10
11
  import { useReactFlow } from '@terrafirmagreg-team/ui/lib/canvas/xyflow';
11
12
  import type {
12
13
  AnyCalcFlowNode,
13
- CalcDensity,
14
14
  CalcFlowNode,
15
15
  CalcViewMode,
16
16
  } from '@terrafirmagreg-team/recipe/lib/calc/flow';
17
- import type { CalcDirection } from '@terrafirmagreg-team/recipe/lib/calc/flow';
18
- import type { CalcGoal } from '@terrafirmagreg-team/recipe/lib/calc/tree';
19
17
 
20
18
  export function useFlowViewport({
21
- goals,
22
19
  mergedNodes,
23
20
  dataset,
24
- direction,
25
21
  resetGen,
26
22
  treeReady,
27
- layoutKey,
28
- layout,
29
23
  mode,
30
- density,
31
24
  applyFocus,
32
25
  focusSeqRef,
33
26
  focusRef,
@@ -35,16 +28,11 @@ export function useFlowViewport({
35
28
  onFocusedNode,
36
29
  focusId,
37
30
  }: {
38
- goals: CalcGoal[];
39
31
  mergedNodes: AnyCalcFlowNode[];
40
- dataset: string;
41
- direction: CalcDirection;
32
+ dataset: DatasetRef;
42
33
  resetGen: number;
43
34
  treeReady: boolean;
44
- layoutKey: string;
45
- layout: { key: string } | null;
46
35
  mode: CalcViewMode;
47
- density: CalcDensity;
48
36
  applyFocus: (id: string | null) => void;
49
37
  focusSeqRef: RefObject<{ id: string; index: number } | null>;
50
38
  focusRef?: RefObject<((materialId: string) => void) | null>;
@@ -54,10 +42,9 @@ export function useFlowViewport({
54
42
  }) {
55
43
  const { setCenter, getZoom } = useReactFlow();
56
44
  const userMovedRef = useRef(false);
57
- const storageKey = `calc:${dataset}`;
45
+ const storageKey = `calc:${dataset.id}/${dataset.version ?? ''}`;
58
46
 
59
- const layoutReady = !goals.length || layout?.key === layoutKey;
60
- const ready = treeReady && layoutReady;
47
+ const ready = treeReady;
61
48
 
62
49
  const centerOnNode = useCallback(
63
50
  (node: AnyCalcFlowNode) => {
@@ -106,16 +93,15 @@ export function useFlowViewport({
106
93
  }, [mergedNodes, focusNodeId, applyFocus, centerOnNode, onFocusedNode]);
107
94
 
108
95
  const recenterRef = useRef(false);
109
- const viewSigRef = useRef(`${mode}|${density}`);
96
+ const viewSigRef = useRef(mode);
110
97
  useEffect(() => {
111
- const sig = `${mode}|${density}`;
112
- if (viewSigRef.current === sig) return;
113
- viewSigRef.current = sig;
98
+ if (viewSigRef.current === mode) return;
99
+ viewSigRef.current = mode;
114
100
  recenterRef.current = true;
115
- }, [mode, density]);
101
+ }, [mode]);
116
102
  useEffect(() => {
117
103
  if (!ready || !recenterRef.current) return;
118
- const root = mergedNodes.find((entry) => entry.id === 'g0');
104
+ const root = mergedNodes[0];
119
105
  if (root) centerOnNode(root as AnyCalcFlowNode);
120
106
  recenterRef.current = false;
121
107
  }, [ready, mergedNodes, centerOnNode]);
@@ -127,16 +113,16 @@ export function useFlowViewport({
127
113
  const viewportConfig = useMemo(
128
114
  () => ({
129
115
  storageKey,
130
- resetKey: `${storageKey}:${direction}:${resetGen}`,
116
+ resetKey: `${storageKey}:${resetGen}`,
131
117
  selectedId: focusId,
132
118
  userMovedRef,
133
- initFocusId: goals.length ? 'g0' : null,
119
+ initFocusId: null,
134
120
  focusZoom: 1,
135
121
  minZoom: 0.02,
136
122
  restoreViewport: false,
137
123
  ready,
138
124
  }),
139
- [storageKey, direction, resetGen, focusId, goals.length, ready],
125
+ [storageKey, resetGen, focusId, ready],
140
126
  );
141
127
  const shellProps = useMemo(
142
128
  () => ({ 'data-ready': ready ? ('true' as const) : ('false' as const) }),
@@ -152,3 +138,4 @@ export function useFlowViewport({
152
138
  shellProps,
153
139
  };
154
140
  }
141
+
@@ -84,3 +84,4 @@ export function useFlowWindow({
84
84
 
85
85
  return { computed, extraRects };
86
86
  }
87
+
@@ -7,33 +7,29 @@ import {
7
7
  type SetStateAction,
8
8
  } from 'react';
9
9
  import { useCopyButton } from '@terrafirmagreg-team/ui/hooks/use-copy-button';
10
- import type { CalcGoal } from '@terrafirmagreg-team/recipe/lib/calc/tree';
10
+ import type { DatasetRef } from '@terrafirmagreg-team/sites/location';
11
11
  import { recipeCalcHref, recipeStackHref } from '@terrafirmagreg-team/recipe/lib/links';
12
12
  import type { Recipe } from '@terrafirmagreg-team/recipe/lib/data/dto';
13
13
  import type { Lang } from '@terrafirmagreg-team/translate/types/locale';
14
14
  import type {
15
+ CalcCard,
15
16
  CalcKind,
16
- CalcMaterial,
17
17
  CalcNode,
18
18
  CalculatorState,
19
- CalculatorTarget,
19
+ CalcWire,
20
20
  } from '@terrafirmagreg-team/recipe/types/calc';
21
21
  import type { CalcFlowData } from '@terrafirmagreg-team/recipe/lib/calc/flow';
22
22
 
23
- const treeKeys = (selections: Record<string, string>) =>
24
- Object.fromEntries(
25
- Object.entries(selections).filter(([key]) => !/^g\d/.test(key)),
26
- );
23
+ let cardSeq = 0;
24
+ const newCardId = () =>
25
+ `c${Date.now().toString(36)}${(cardSeq += 1).toString(36)}`;
27
26
 
28
27
  export function useCalcActions({
29
28
  dataset,
30
29
  lang,
31
- selections,
30
+ cards,
31
+ wires,
32
32
  tagSelections,
33
- collapsed,
34
- auto,
35
- goal,
36
- ideal,
37
33
  persist,
38
34
  recipeDefaults,
39
35
  setRecipeDefault,
@@ -41,33 +37,28 @@ export function useCalcActions({
41
37
  encodeShare,
42
38
  flushAmounts,
43
39
  setFocusNodeId,
44
- onFillNode,
40
+ nodes,
45
41
  }: {
46
- dataset: string;
42
+ dataset: DatasetRef;
47
43
  lang: Lang;
48
- selections: Record<string, string>;
44
+ cards: CalcCard[];
45
+ wires: CalcWire[];
49
46
  tagSelections: Record<string, string>;
50
- collapsed: string[];
51
- auto: string[];
52
- goal: CalcGoal | null;
53
- ideal: number;
54
47
  persist: (next: CalculatorState) => void;
55
48
  recipeDefaults: Record<string, string>;
56
49
  setRecipeDefault: (materialId: string, recipeId: string | null) => void;
57
50
  recipeBody: (recipeId: string) => Recipe | undefined;
58
51
  encodeShare: () => string;
59
- flushAmounts: () => {
60
- target: CalculatorTarget | null;
61
- inventory: CalcMaterial[];
62
- };
52
+ flushAmounts: () => CalcCard[];
63
53
  setFocusNodeId: Dispatch<SetStateAction<string | null>>;
64
- onFillNode: (path: string) => void;
54
+ nodes: CalcNode[];
65
55
  }) {
66
56
  const { checked: shared, onClick: onShare } = useCopyButton(async () => {
67
57
  const tree = encodeShare();
58
+ const { id: datasetId, version } = dataset;
68
59
  try {
69
60
  const res = (await fetch(
70
- `/api/share/recipe/${encodeURIComponent(dataset)}`,
61
+ `/api/share/recipe/${encodeURIComponent(datasetId)}${version ? `?version=${encodeURIComponent(version)}` : ''}`,
71
62
  {
72
63
  method: 'POST',
73
64
  headers: { 'content-type': 'application/json' },
@@ -75,215 +66,110 @@ export function useCalcActions({
75
66
  },
76
67
  ).then((r) => (r.ok ? r.json() : null))) as {
77
68
  id?: string;
69
+ dataset?: DatasetRef;
78
70
  } | null;
79
71
  if (res?.id) {
80
72
  await navigator.clipboard.writeText(
81
- `${window.location.origin}${recipeCalcHref(dataset, undefined, `s${res.id}`)}`,
73
+ `${window.location.origin}${recipeCalcHref(res.dataset ?? dataset, undefined, `s${res.id}`)}`,
82
74
  );
83
75
  return;
84
76
  }
85
77
  } catch {
86
-
87
78
  }
88
79
  await navigator.clipboard.writeText(
89
80
  `${window.location.origin}${recipeCalcHref(dataset, undefined, tree)}`,
90
81
  );
91
82
  });
92
83
 
93
- const onSelectRecipe = useCallback(
94
- (key: string, recipeId: string | null) => {
95
- const next = { ...selections };
96
- const changed = selections[key] !== recipeId;
97
- if (recipeId) next[key] = recipeId;
98
- else delete next[key];
99
- const descendant = changed && /^g\d/.test(key);
100
- if (descendant) {
101
- for (const path of Object.keys(next)) {
102
- if (path.startsWith(`${key}/`)) delete next[path];
103
- }
104
- }
84
+ const write = useCallback(
85
+ (next: Partial<Pick<CalculatorState, 'cards' | 'wires' | 'tagSelections'>>) => {
105
86
  persist({
106
- ...flushAmounts(),
107
- selections: next,
108
- tagSelections,
109
- collapsed: collapsed.filter(
110
- (path) =>
111
- path !== key && !(descendant && path.startsWith(`${key}/`)),
112
- ),
113
- auto: descendant
114
- ? auto.filter((path) => !path.startsWith(`${key}/`))
115
- : auto,
87
+ cards: next.cards ?? flushAmounts(),
88
+ wires: next.wires ?? wires,
89
+ tagSelections: next.tagSelections ?? tagSelections,
116
90
  });
117
91
  },
118
- [
119
- selections,
120
- persist,
121
- tagSelections,
122
- collapsed,
123
- auto,
124
- flushAmounts,
125
- ],
92
+ [flushAmounts, persist, wires, tagSelections],
126
93
  );
127
94
 
128
- const onCollapseNode = useCallback(
129
- (path: string) => {
130
- const next = { ...selections };
131
- delete next[path];
132
- for (const key of Object.keys(next)) {
133
- if (key.startsWith(`${path}/`)) delete next[key];
134
- }
135
- persist({
136
- ...flushAmounts(),
137
- selections: next,
138
- tagSelections,
139
- collapsed: collapsed.includes(path)
140
- ? collapsed
141
- : [...collapsed, path],
142
- auto,
143
- });
95
+ const onSelectRecipe = useCallback(
96
+ (cardId: string, recipeId: string | null) => {
97
+ if (!cards.some((row) => row.id === cardId)) return;
98
+ const nextCards = cards.map((row) =>
99
+ row.id === cardId ? { ...row, recipeId } : row,
100
+ );
101
+ const nextWires = recipeId
102
+ ? wires
103
+ : wires.filter((wire) => wire.to !== cardId);
104
+ write({ cards: nextCards, wires: nextWires });
144
105
  },
145
- [selections, persist, tagSelections, collapsed, auto, flushAmounts],
106
+ [cards, wires, write],
146
107
  );
147
108
 
148
109
  const onSelectTagMember = useCallback(
149
110
  (tagId: string, memberId: string) => {
150
- const next = { ...tagSelections, [tagId]: memberId };
151
-
152
- const paths: string[] = [];
153
- const walk = (n: CalcNode) => {
154
- if (n.tagId === tagId && n.path) paths.push(n.path);
155
- n.children.forEach(walk);
156
- };
157
- if (goal) walk(goal.node);
158
- const hitsSelf = (key: string) =>
159
- paths.some((p) => key === p || key.startsWith(`${p}/`));
160
- const hitsDescendant = (key: string) =>
161
- paths.some((p) => key.startsWith(`${p}/`));
162
- persist({
163
- ...flushAmounts(),
164
- selections: paths.length
165
- ? Object.fromEntries(
166
- Object.entries(selections).filter(
167
- ([key]) => !hitsSelf(key),
168
- ),
169
- )
170
- : selections,
171
- tagSelections: next,
172
- collapsed: paths.length
173
- ? collapsed.filter((path) => !hitsDescendant(path))
174
- : collapsed,
175
- auto: paths.length
176
- ? auto.filter((path) => !hitsDescendant(path))
177
- : auto,
178
- });
111
+ write({ tagSelections: { ...tagSelections, [tagId]: memberId } });
179
112
  },
180
- [
181
- tagSelections,
182
- persist,
183
- selections,
184
- collapsed,
185
- auto,
186
- flushAmounts,
187
- goal,
188
- ],
113
+ [tagSelections, write],
189
114
  );
190
115
 
191
- const onSetTarget = useCallback(
192
- (materialId: string, kind: CalcKind) => {
193
- const cur = flushAmounts();
194
- setFocusNodeId('g0');
195
- if (cur.target?.id === materialId && cur.target.kind === kind) {
196
- return;
197
- }
116
+ const onAddCard = useCallback(
117
+ (materialId: string, kind: CalcKind, pos?: { x: number; y: number }) => {
118
+ const id = newCardId();
198
119
  persist({
199
- target: { id: materialId, kind, amount: 1 },
200
- inventory: cur.inventory,
201
- selections: treeKeys(selections),
120
+ cards: [
121
+ ...flushAmounts(),
122
+ { id, materialId, kind, recipeId: null, crafts: 1, pos },
123
+ ],
124
+ wires,
202
125
  tagSelections,
203
- collapsed: [],
204
- auto: [],
205
126
  });
127
+ setFocusNodeId(id);
128
+ return id;
206
129
  },
207
- [persist, selections, tagSelections, flushAmounts, setFocusNodeId],
130
+ [flushAmounts, persist, wires, tagSelections, setFocusNodeId],
208
131
  );
209
132
 
210
- const onAddStock = useCallback(
211
- (materialId: string, kind: CalcKind) => {
212
- const cur = flushAmounts();
213
- const index = cur.inventory.findIndex(
214
- (row) => row.id === materialId && row.kind === kind,
215
- );
216
- const inventory =
217
- index >= 0
218
- ? cur.inventory.map((row, i) =>
219
- i === index ? { ...row, amount: row.amount + 1 } : row,
220
- )
221
- : [...cur.inventory, { id: materialId, kind, amount: 1 }];
222
- persist({
223
- target: cur.target,
224
- inventory,
225
- selections,
226
- tagSelections,
227
- collapsed,
228
- auto,
229
- });
230
- },
231
- [
232
- persist,
233
- selections,
234
- tagSelections,
235
- collapsed,
236
- auto,
237
- flushAmounts,
238
- ],
239
- );
240
-
241
- const onRemoveStock = useCallback(
242
- (index: number) => {
243
- const cur = flushAmounts();
133
+ const onSupplyInput = useCallback(
134
+ (cardId: string, inputIndex: number, recipeId: string | null, outputAmount?: number) => {
135
+ const consumerNode = nodes.find((n) => n.id === cardId);
136
+ const consumerCard = cards.find((row) => row.id === cardId);
137
+ const input = consumerNode?.inputs[inputIndex];
138
+ if (!consumerNode || !consumerCard || !input?.id) return null;
139
+ const need = Math.max(1, input.amount - input.covered);
140
+ const crafts = recipeId && outputAmount
141
+ ? Math.max(1, Math.ceil(need / outputAmount))
142
+ : need;
143
+ const base = consumerCard.pos;
144
+ const id = newCardId();
145
+ const nextWires = [
146
+ ...wires.filter(
147
+ (wire) => !(wire.to === cardId && wire.input === inputIndex),
148
+ ),
149
+ { from: id, to: cardId, input: inputIndex },
150
+ ];
244
151
  persist({
245
- target: cur.target,
246
- inventory: cur.inventory.filter((_, i) => i !== index),
247
- selections,
152
+ cards: [
153
+ ...flushAmounts(),
154
+ {
155
+ id,
156
+ materialId: input.id,
157
+ kind: input.kind,
158
+ recipeId,
159
+ crafts,
160
+ pos: base
161
+ ? { x: base.x - 40, y: base.y + 140 + inputIndex * 24 }
162
+ : undefined,
163
+ },
164
+ ],
165
+ wires: nextWires,
248
166
  tagSelections,
249
- collapsed,
250
- auto,
251
167
  });
168
+ return id;
252
169
  },
253
- [
254
- persist,
255
- selections,
256
- tagSelections,
257
- collapsed,
258
- auto,
259
- flushAmounts,
260
- ],
170
+ [nodes, cards, wires, flushAmounts, persist, tagSelections],
261
171
  );
262
172
 
263
- const onApplyIdeal = useCallback(() => {
264
- const cur = flushAmounts();
265
- if (!cur.target || !Number.isFinite(ideal) || ideal <= 1) return;
266
- persist({
267
- target: {
268
- ...cur.target,
269
- amount: Math.ceil(cur.target.amount * ideal),
270
- },
271
- inventory: cur.inventory,
272
- selections,
273
- tagSelections,
274
- collapsed,
275
- auto,
276
- });
277
- }, [
278
- persist,
279
- selections,
280
- tagSelections,
281
- collapsed,
282
- auto,
283
- flushAmounts,
284
- ideal,
285
- ]);
286
-
287
173
  const onOpenItem = useCallback(
288
174
  (materialId: string, kind: CalcKind) => {
289
175
  window.open(
@@ -295,32 +181,115 @@ export function useCalcActions({
295
181
  [dataset, lang],
296
182
  );
297
183
 
298
- const onRemoveTarget = useCallback(
299
- (_flowId?: string) => {
300
- const cur = flushAmounts();
301
- persist({
302
- target: null,
303
- inventory: cur.inventory,
304
- selections: treeKeys(selections),
305
- tagSelections,
306
- collapsed: [],
307
- auto: [],
184
+ const onRemoveCard = useCallback(
185
+ (cardId: string) => {
186
+ write({
187
+ cards: flushAmounts().filter((row) => row.id !== cardId),
188
+ wires: wires.filter(
189
+ (wire) => wire.from !== cardId && wire.to !== cardId,
190
+ ),
191
+ });
192
+ },
193
+ [flushAmounts, write, wires],
194
+ );
195
+
196
+ const onMoveCard = useCallback(
197
+ (cardId: string, pos: { x: number; y: number }) => {
198
+ write({
199
+ cards: flushAmounts().map((row) =>
200
+ row.id === cardId ? { ...row, pos } : row,
201
+ ),
308
202
  });
309
203
  },
310
- [persist, selections, tagSelections, flushAmounts],
204
+ [flushAmounts, write],
205
+ );
206
+
207
+ const onMoveCards = useCallback(
208
+ (positions: Map<string, { x: number; y: number }>) => {
209
+ write({
210
+ cards: flushAmounts().map((row) => {
211
+ const pos = positions.get(row.id);
212
+ return pos ? { ...row, pos } : row;
213
+ }),
214
+ });
215
+ },
216
+ [flushAmounts, write],
217
+ );
218
+
219
+ const onWire = useCallback(
220
+ (from: string, to: string, input: number, out?: number) => {
221
+ if (from === to) return;
222
+ write({
223
+ wires: [
224
+ ...wires.filter(
225
+ (wire) => !(wire.to === to && wire.input === input),
226
+ ),
227
+ { from, to, input, out: out || undefined },
228
+ ],
229
+ });
230
+ },
231
+ [wires, write],
232
+ );
233
+
234
+ const onUnwire = useCallback(
235
+ (from: string, to: string, input: number, out?: number) => {
236
+ write({
237
+ wires: wires.filter(
238
+ (wire) =>
239
+ !(
240
+ wire.from === from &&
241
+ wire.to === to &&
242
+ wire.input === input &&
243
+ (wire.out ?? 0) === (out ?? 0)
244
+ ),
245
+ ),
246
+ });
247
+ },
248
+ [wires, write],
249
+ );
250
+
251
+ const onRewire = useCallback(
252
+ (
253
+ from: string,
254
+ to: string,
255
+ input: number,
256
+ next: { from: string; to: string; input: number; out?: number },
257
+ ) => {
258
+ if (next.from === next.to) return;
259
+ write({
260
+ wires: [
261
+ ...wires.filter(
262
+ (wire) =>
263
+ !(wire.from === from && wire.to === to && wire.input === input) &&
264
+ !(wire.to === next.to && wire.input === next.input),
265
+ ),
266
+ next,
267
+ ],
268
+ });
269
+ },
270
+ [wires, write],
271
+ );
272
+
273
+ const onWireAmount = useCallback(
274
+ (from: string, to: string, input: number, amount: number | null) => {
275
+ write({
276
+ wires: wires.map((wire) =>
277
+ wire.from === from && wire.to === to && wire.input === input
278
+ ? { ...wire, amount: amount ?? undefined }
279
+ : wire,
280
+ ),
281
+ });
282
+ },
283
+ [wires, write],
311
284
  );
312
285
 
313
286
  const onClear = useCallback(() => {
314
- const cur = flushAmounts();
315
287
  persist({
316
- target: null,
317
- inventory: cur.inventory,
318
- selections: {},
288
+ cards: [],
289
+ wires: [],
319
290
  tagSelections: {},
320
- collapsed: [],
321
- auto: [],
322
291
  });
323
- }, [flushAmounts, persist]);
292
+ }, [persist]);
324
293
 
325
294
  const flowData = useMemo<
326
295
  Omit<
@@ -330,6 +299,10 @@ export function useCalcActions({
330
299
  | 'direction'
331
300
  | 'onRequestPicker'
332
301
  | 'onRequestMemberPicker'
302
+ | 'onRequestSupply'
303
+ | 'onCrafts'
304
+ | 'getRecipesForItem'
305
+ | 'ensureMaterial'
333
306
  >
334
307
  >(
335
308
  () => ({
@@ -337,28 +310,34 @@ export function useCalcActions({
337
310
  recipeBody,
338
311
  onSelectRecipe,
339
312
  onSelectTagMember,
340
- onSetTarget,
341
- onAddStock,
313
+ onSupplyInput,
314
+ onAddCard,
342
315
  onOpenItem,
316
+ onRemoveCard,
317
+ onMoveCard,
318
+ onWire,
319
+ onUnwire,
320
+ onRewire,
321
+ onWireAmount,
343
322
  recipeDefaults,
344
323
  onSetRecipeDefault: setRecipeDefault,
345
- onFillNode,
346
- onCollapseNode,
347
- onRemoveTarget,
348
324
  }),
349
325
  [
350
326
  dataset,
351
327
  recipeBody,
352
328
  onSelectRecipe,
353
329
  onSelectTagMember,
354
- onSetTarget,
355
- onAddStock,
330
+ onSupplyInput,
331
+ onAddCard,
356
332
  onOpenItem,
333
+ onRemoveCard,
334
+ onMoveCard,
335
+ onWire,
336
+ onUnwire,
337
+ onRewire,
338
+ onWireAmount,
357
339
  recipeDefaults,
358
340
  setRecipeDefault,
359
- onFillNode,
360
- onCollapseNode,
361
- onRemoveTarget,
362
341
  ],
363
342
  );
364
343
 
@@ -366,11 +345,11 @@ export function useCalcActions({
366
345
  shared,
367
346
  onShare,
368
347
  flowData,
369
- onSetTarget,
370
- onAddStock,
371
- onRemoveStock,
372
- onApplyIdeal,
373
- onRemoveTarget,
348
+ onAddCard,
349
+ onRemoveCard,
350
+ onMoveCards,
351
+ onSupplyInput,
374
352
  onClear,
375
353
  };
376
354
  }
355
+