@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
@@ -0,0 +1,226 @@
1
+ import type {
2
+ CalcCard,
3
+ CalcKind,
4
+ CalcMaterial,
5
+ CalcNode,
6
+ CalcNodeInput,
7
+ CalcOutput,
8
+ CalcRecipe,
9
+ CalcRecipeSummary,
10
+ CalcWire,
11
+ TagResolution,
12
+ } from '@terrafirmagreg-team/recipe/types/calc';
13
+ import { mergeAmounts } from './tree/materials';
14
+
15
+ export type SchemeContext = {
16
+ recipeMap: Map<string, CalcRecipe>;
17
+ getRecipesForItem: (materialId: string, kind: CalcKind) => CalcRecipeSummary[];
18
+ tagMembers: (tagId: string) => TagResolution | undefined;
19
+ tagSelections: Record<string, string>;
20
+ recipesResolved: (id: string) => boolean;
21
+ deadIds: ReadonlySet<string>;
22
+ };
23
+
24
+ function outputAmount(
25
+ recipe: CalcRecipe,
26
+ materialId: string,
27
+ kind: CalcKind,
28
+ ): number {
29
+ return recipe.outputs
30
+ .filter((out) => out.id === materialId && out.kind === kind)
31
+ .reduce((sum, out) => sum + out.amount * (out.chance ?? 1), 0);
32
+ }
33
+
34
+ function inputMember(
35
+ input: CalcRecipe['inputs'][number],
36
+ ctx: SchemeContext,
37
+ ): { id?: string; kind: CalcKind } {
38
+ if (!input.tagId) return { id: input.id, kind: input.kind };
39
+ const picked = ctx.tagSelections[input.tagId];
40
+ const resolution = ctx.tagMembers(input.tagId);
41
+ const members = resolution?.members ?? [];
42
+ const memberId =
43
+ picked && members.includes(picked) ? picked : members[0];
44
+ return { id: memberId, kind: resolution?.kind ?? input.kind };
45
+ }
46
+
47
+ export function buildScheme({
48
+ cards,
49
+ wires,
50
+ ctx,
51
+ }: {
52
+ cards: readonly CalcCard[];
53
+ wires: readonly CalcWire[];
54
+ ctx: SchemeContext;
55
+ }): { nodes: CalcNode[]; flows: (number | undefined)[] } {
56
+ const byId = new Map<string, CalcNode>();
57
+ const nodes = cards.map((card) => {
58
+ const availableRecipes = ctx.getRecipesForItem(card.materialId, card.kind);
59
+ const recipe = card.recipeId
60
+ ? (ctx.recipeMap.get(card.recipeId) ?? null)
61
+ : null;
62
+ const resolved = card.recipeId ? !!recipe : true;
63
+ const out = recipe
64
+ ? outputAmount(recipe, card.materialId, card.kind)
65
+ : 1;
66
+ const produced = card.crafts * out;
67
+ const outputs: CalcOutput[] = [];
68
+ const addOutput = (entry: CalcOutput) => {
69
+ if (!(entry.amount > 0)) return;
70
+ const same = outputs.find(
71
+ (o) => o.id === entry.id && o.kind === entry.kind,
72
+ );
73
+ if (same) {
74
+ same.amount += entry.amount;
75
+ same.remaining += entry.amount;
76
+ } else {
77
+ outputs.push(entry);
78
+ }
79
+ };
80
+ addOutput({
81
+ id: card.materialId,
82
+ kind: card.kind,
83
+ amount: produced,
84
+ allocated: 0,
85
+ remaining: produced,
86
+ });
87
+ for (const o of recipe?.outputs ?? []) {
88
+ if (o.id === card.materialId && o.kind === card.kind) continue;
89
+ addOutput({
90
+ id: o.id,
91
+ kind: o.kind,
92
+ amount: o.amount * card.crafts,
93
+ chance: o.chance,
94
+ allocated: 0,
95
+ remaining: o.amount * card.crafts,
96
+ });
97
+ }
98
+ const node: CalcNode = {
99
+ id: card.id,
100
+ materialId: card.materialId,
101
+ kind: card.kind,
102
+ crafts: card.crafts,
103
+ produced,
104
+ allocated: 0,
105
+ remaining: produced,
106
+ outputs,
107
+ recipe,
108
+ inputs: (recipe?.inputs ?? []).map((entry) => {
109
+ const member = inputMember(entry, ctx);
110
+ return {
111
+ id: member.id,
112
+ kind: member.kind,
113
+ amount: entry.catalyst
114
+ ? entry.amount
115
+ : entry.amount * card.crafts,
116
+ catalyst: entry.catalyst,
117
+ tagId: entry.tagId,
118
+ wired: false,
119
+ covered: 0,
120
+ } satisfies CalcNodeInput;
121
+ }),
122
+ availableRecipes,
123
+ cycle: false,
124
+ products: false,
125
+ loading:
126
+ !ctx.recipesResolved(card.materialId) ||
127
+ (card.recipeId ? !resolved && !ctx.deadIds.has(card.recipeId) : false),
128
+ };
129
+ byId.set(card.id, node);
130
+ return node;
131
+ });
132
+
133
+ const flows: (number | undefined)[] = new Array(wires.length);
134
+ const deg = new Map<string, number>();
135
+ const adj = new Map<string, string[]>();
136
+ wires.forEach((wire, wireIndex) => {
137
+ const src = byId.get(wire.from);
138
+ const dst = byId.get(wire.to);
139
+ if (!src || !dst || src === dst) return;
140
+ const input = dst.inputs[wire.input];
141
+ const atIndex = src.outputs[wire.out ?? 0];
142
+ const outIndex =
143
+ input && atIndex && atIndex.id === input.id && atIndex.kind === input.kind
144
+ ? (wire.out ?? 0)
145
+ : input
146
+ ? src.outputs.findIndex(
147
+ (o) => o.id === input.id && o.kind === input.kind,
148
+ )
149
+ : -1;
150
+ const srcOut = outIndex >= 0 ? src.outputs[outIndex] : undefined;
151
+ if (!input || !srcOut) return;
152
+ if (!input.id) return;
153
+ input.wired = true;
154
+ const covered = Math.min(
155
+ Math.max(srcOut.remaining, 0),
156
+ input.amount - input.covered,
157
+ wire.amount ?? Infinity,
158
+ );
159
+ input.covered += covered;
160
+ srcOut.allocated += covered;
161
+ srcOut.remaining -= covered;
162
+ if (outIndex === 0) {
163
+ src.allocated += covered;
164
+ src.remaining -= covered;
165
+ }
166
+ flows[wireIndex] = covered;
167
+ adj.set(wire.from, [...(adj.get(wire.from) ?? []), wire.to]);
168
+ deg.set(wire.to, (deg.get(wire.to) ?? 0) + 1);
169
+ });
170
+
171
+ const cyclic = (start: string): boolean => {
172
+ const seen = new Set<string>();
173
+ const walk = (id: string): boolean => {
174
+ for (const next of adj.get(id) ?? []) {
175
+ if (next === start) return true;
176
+ if (seen.has(next)) continue;
177
+ seen.add(next);
178
+ if (walk(next)) return true;
179
+ }
180
+ return false;
181
+ };
182
+ return walk(start);
183
+ };
184
+ for (const node of nodes) node.cycle = cyclic(node.id);
185
+
186
+ for (const node of nodes) {
187
+ node.products = !(adj.get(node.id)?.length);
188
+ }
189
+ return { nodes, flows };
190
+ }
191
+
192
+ export function schemeSummary(nodes: readonly CalcNode[]) {
193
+ const raw: CalcMaterial[] = [];
194
+ const catalysts: CalcMaterial[] = [];
195
+ const surplus: CalcMaterial[] = [];
196
+ const products: CalcMaterial[] = [];
197
+ for (const node of nodes) {
198
+ for (const input of node.inputs) {
199
+ if (!input.id) continue;
200
+ if (input.catalyst) {
201
+ catalysts.push({ id: input.id, kind: input.kind, amount: input.amount });
202
+ continue;
203
+ }
204
+ const deficit = input.amount - input.covered;
205
+ if (deficit > 0) raw.push({ id: input.id, kind: input.kind, amount: deficit });
206
+ }
207
+ if (!node.recipe && node.produced > 0 && !node.products) {
208
+ raw.push({ id: node.materialId, kind: node.kind, amount: node.produced });
209
+ }
210
+ if (node.products && node.produced > 0) {
211
+ products.push({ id: node.materialId, kind: node.kind, amount: node.produced });
212
+ }
213
+ for (const out of node.outputs.slice(node.products ? 1 : 0)) {
214
+ if (out.remaining > 0) {
215
+ surplus.push({ id: out.id, kind: out.kind, amount: out.remaining });
216
+ }
217
+ }
218
+ }
219
+ return {
220
+ raw: mergeAmounts(raw),
221
+ catalysts,
222
+ remainders: mergeAmounts(surplus),
223
+ products: mergeAmounts(products),
224
+ };
225
+ }
226
+
@@ -23,37 +23,44 @@ export function newCalcEntry(state: CalculatorState, name?: string): CalcTreeEnt
23
23
  };
24
24
  }
25
25
 
26
- const isTargetRow = (t: unknown): boolean =>
27
- !!t &&
28
- typeof t === 'object' &&
29
- typeof (t as { id?: unknown }).id === 'string';
26
+ const isCardRow = (row: unknown): boolean =>
27
+ !!row &&
28
+ typeof row === 'object' &&
29
+ typeof (row as { id?: unknown }).id === 'string' &&
30
+ typeof (row as { materialId?: unknown }).materialId === 'string' &&
31
+ ((row as { kind?: unknown }).kind === 'item' ||
32
+ (row as { kind?: unknown }).kind === 'fluid');
33
+
34
+ const isWireRow = (row: unknown): boolean =>
35
+ !!row &&
36
+ typeof row === 'object' &&
37
+ typeof (row as { from?: unknown }).from === 'string' &&
38
+ typeof (row as { to?: unknown }).to === 'string' &&
39
+ typeof (row as { input?: unknown }).input === 'number';
30
40
 
31
41
  export function isCalcState(value: unknown): value is CalculatorState {
32
42
  if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
33
43
  const row = value as Record<string, unknown>;
34
- const targetOk = Array.isArray(row.targets)
35
- ?
36
- row.targets.every(isTargetRow)
37
- : row.target === null || row.target === undefined || isTargetRow(row.target);
38
44
  return (
39
- targetOk &&
40
- !!row.selections &&
41
- typeof row.selections === 'object' &&
45
+ Array.isArray(row.cards) &&
46
+ row.cards.every(isCardRow) &&
47
+ Array.isArray(row.wires) &&
48
+ row.wires.every(isWireRow) &&
42
49
  !!row.tagSelections &&
43
50
  typeof row.tagSelections === 'object'
44
51
  );
45
52
  }
46
53
 
47
54
  export function migrateCalcState(state: CalculatorState): CalculatorState {
48
- const legacy = (state as CalculatorState & { targets?: unknown }).targets;
49
- if (state.target !== undefined || !Array.isArray(legacy)) {
50
- return { inventory: [], ...state, target: state.target ?? null };
51
- }
52
- const [first] = legacy as { id: string; kind: 'item' | 'fluid'; amount: number }[];
53
- const { targets: _drop, ...rest } = state as CalculatorState & {
54
- targets?: unknown;
55
+ const cards = Array.isArray(state.cards) ? state.cards.filter(isCardRow) : [];
56
+ const ids = new Set(cards.map((card) => card.id));
57
+ return {
58
+ cards,
59
+ wires: (Array.isArray(state.wires) ? state.wires.filter(isWireRow) : []).filter(
60
+ (wire) => ids.has(wire.from) && ids.has(wire.to),
61
+ ),
62
+ tagSelections: state.tagSelections,
55
63
  };
56
- return { inventory: [], ...rest, target: first ?? null };
57
64
  }
58
65
 
59
66
  function isEntry(value: unknown): value is CalcTreeEntry {
@@ -87,3 +94,4 @@ export function capHistory(entries: CalcTreeEntry[]): CalcTreeEntry[] {
87
94
  }
88
95
  return out;
89
96
  }
97
+
@@ -7,6 +7,7 @@ export { calcStorageKey } from './keys';
7
7
  export {
8
8
  readCalcActive,
9
9
  readCalcHistory,
10
+ resolveCalcActive,
10
11
  readCalcStorage,
11
12
  readCalcTabs,
12
13
  removeCalcHistory,
@@ -16,3 +17,4 @@ export {
16
17
  writeCalcStorage,
17
18
  writeCalcTabs,
18
19
  } from './store';
20
+
@@ -1,10 +1,9 @@
1
+ import type { DatasetRef } from '@terrafirmagreg-team/sites/location';
1
2
  export const CALC_STATE_EVENT = 'tfg:calc-state';
2
3
  const TAB_KEY = 'tfg:calc-tab';
3
4
  const ACTIVE_PREFIX = 'calc:active:';
4
5
  const HIST_PREFIX = 'calc:hist:';
5
6
 
6
- export const LEGACY_PREFIX = 'calc:';
7
-
8
7
  let tabId: string | null = null;
9
8
 
10
9
  function calcTabId(): string {
@@ -21,9 +20,9 @@ function calcTabId(): string {
21
20
  return tabId;
22
21
  }
23
22
 
24
- export function calcStorageKey(section: string, dataset: string): string {
23
+ export function calcStorageKey(section: string, dataset: DatasetRef): string {
25
24
  void calcTabId();
26
- return `calc:tabs:${section}:${dataset}`;
25
+ return `calc:tabs:${section}:${dataset.id}/${dataset.version ?? ''}`;
27
26
  }
28
27
 
29
28
  export function activeKey(key: string): string {
@@ -34,9 +33,6 @@ export function historyKey(key: string): string {
34
33
  return `${HIST_PREFIX}${key}`;
35
34
  }
36
35
 
37
- export function datasetTail(key: string): string {
38
- return key.slice('calc:tabs:'.length);
39
- }
40
-
41
36
  export const emitCalcStorage = () =>
42
37
  window.dispatchEvent(new Event(CALC_STATE_EVENT));
38
+
@@ -3,7 +3,6 @@ import {
3
3
  capHistory,
4
4
  EMPTY_ENTRIES,
5
5
  isCalcHistory,
6
- isCalcState,
7
6
  isCalcTabs,
8
7
  migrateCalcState,
9
8
  newCalcEntry,
@@ -12,10 +11,8 @@ import {
12
11
  import {
13
12
  activeKey,
14
13
  CALC_STATE_EVENT,
15
- datasetTail,
16
14
  emitCalcStorage,
17
15
  historyKey,
18
- LEGACY_PREFIX,
19
16
  } from './keys';
20
17
 
21
18
  function readJSON<T>(raw: string | null, guard: (v: unknown) => v is T): T | null {
@@ -50,37 +47,8 @@ const migrateEntries = (entries: CalcTreeEntry[]): void => {
50
47
  }
51
48
  };
52
49
 
53
- const migrated = new Set<string>();
54
-
55
- function migrateLegacy(hKey: string, tail: string): void {
56
- if (migrated.has(hKey) || typeof window === 'undefined') return;
57
- migrated.add(hKey);
58
- const legacyKey = `${LEGACY_PREFIX}${tail}`;
59
- try {
60
- const legacy = readJSON(
61
- window.localStorage.getItem(legacyKey),
62
- isCalcState,
63
- );
64
- const migratedState = legacy && migrateCalcState(legacy);
65
- if (migratedState?.target) {
66
- const entry = newCalcEntry(migratedState);
67
- const entries =
68
- readJSON(window.localStorage.getItem(hKey), isCalcHistory) ?? [];
69
- window.localStorage.setItem(
70
- hKey,
71
- JSON.stringify(capHistory([entry, ...entries])),
72
- );
73
- }
74
- for (let i = window.localStorage.length - 1; i >= 0; i -= 1) {
75
- const name = window.localStorage.key(i);
76
- if (name?.startsWith(legacyKey)) window.localStorage.removeItem(name);
77
- }
78
- } catch {}
79
- }
80
-
81
50
  export function readCalcTabs(key: string): CalcTreeEntry[] {
82
51
  if (typeof window === 'undefined') return [];
83
- migrateLegacy(historyKey(key), datasetTail(key));
84
52
  return (
85
53
  snap(key, window.localStorage.getItem(key), isCalcTabs, migrateEntries) ??
86
54
  EMPTY_ENTRIES
@@ -99,7 +67,6 @@ export function readCalcActive(key: string): string | null {
99
67
  export function readCalcHistory(key: string): CalcTreeEntry[] {
100
68
  if (typeof window === 'undefined') return [];
101
69
  const hKey = historyKey(key);
102
- migrateLegacy(hKey, datasetTail(key));
103
70
  return (
104
71
  snap(hKey, window.localStorage.getItem(hKey), isCalcHistory, migrateEntries) ??
105
72
  EMPTY_ENTRIES
@@ -118,7 +85,7 @@ function writeHistoryRaw(key: string, entries: CalcTreeEntry[]): void {
118
85
  }
119
86
 
120
87
  function syncHistory(key: string, tabs: CalcTreeEntry[]): void {
121
- const live = tabs.filter((tab) => tab.state.target);
88
+ const live = tabs.filter((tab) => tab.state.cards.length);
122
89
  if (!live.length) return;
123
90
  const prev = readCalcHistory(key);
124
91
  const ids = new Set(live.map((tab) => tab.id));
@@ -189,9 +156,16 @@ export function removeCalcHistory(key: string, id: string): void {
189
156
  emitCalcStorage();
190
157
  }
191
158
 
159
+ export function resolveCalcActive(
160
+ tabs: readonly CalcTreeEntry[],
161
+ raw: string | null,
162
+ ): string | null {
163
+ return (tabs.find((tab) => tab.id === raw) ?? tabs[0])?.id ?? null;
164
+ }
165
+
192
166
  export function readCalcStorage(key: string): CalculatorState | null {
193
167
  const tabs = readCalcTabs(key);
194
- const active = readCalcActive(key);
168
+ const active = resolveCalcActive(tabs, readCalcActive(key));
195
169
  return tabs.find((tab) => tab.id === active)?.state ?? null;
196
170
  }
197
171
 
@@ -200,7 +174,7 @@ export function writeCalcStorage(
200
174
  state: CalculatorState,
201
175
  ): void {
202
176
  const tabs = readCalcTabs(key);
203
- const active = readCalcActive(key);
177
+ const active = resolveCalcActive(tabs, readCalcActive(key));
204
178
  const index = tabs.findIndex((tab) => tab.id === active);
205
179
  if (index >= 0) {
206
180
  const next = tabs.slice();
@@ -221,3 +195,4 @@ export function subscribeCalcStorage(listener: () => void): () => void {
221
195
  window.removeEventListener('storage', listener);
222
196
  };
223
197
  }
198
+
@@ -1,18 +1,4 @@
1
- export {
2
- AUTO_MAX_DEPTH,
3
- AUTO_NODE_BUDGET,
4
- type BuildContext,
5
- type CalcGoal,
6
- type RemainderPool,
7
- } from './context';
8
1
  export { mergeAmounts } from './materials';
9
2
  export { recipeDivisor, toCalcRecipe } from './recipe';
10
- export { calcAutoPick, type AutoPickResult } from './pick';
11
- export { buildTree } from './build';
12
- export { buildGoal, flattenTree } from './goal';
13
- export { idealBatchMultiplier } from './ideal';
14
- export {
15
- decodeCalcState,
16
- encodeCalcShare,
17
- encodeCalcState,
18
- } from './share';
3
+ export { decodeCalcState, encodeCalcState } from './share';
4
+
@@ -26,3 +26,4 @@ export function deduplicateMaterials(list: CalcMaterial[]): CalcMaterial[] {
26
26
  }
27
27
  return result;
28
28
  }
29
+
@@ -101,3 +101,4 @@ export function recipeDivisor(
101
101
  .reduce((sum, out) => sum + out.amount * (out.chance ?? 1), 0);
102
102
  return amount > 0 ? amount : 1;
103
103
  }
104
+