@terrafirmagreg-team/recipe 0.0.0-stage → 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 (221) hide show
  1. package/package.json +62 -3
  2. package/src/components/button-copy.tsx +43 -0
  3. package/src/components/cards/card/index.tsx +214 -0
  4. package/src/components/cards/card/scene.tsx +107 -0
  5. package/src/components/cards/card/strip.tsx +198 -0
  6. package/src/components/cards/card/text.tsx +46 -0
  7. package/src/components/cards/card/tips.tsx +63 -0
  8. package/src/components/cards/default-item.tsx +47 -0
  9. package/src/components/cards/glare-filter.tsx +29 -0
  10. package/src/components/cards/paged-card.tsx +222 -0
  11. package/src/components/cards/slot-hitbox.tsx +213 -0
  12. package/src/components/cards/strip-card/buttons.tsx +106 -0
  13. package/src/components/cards/strip-card/index.tsx +214 -0
  14. package/src/components/cards/strip-card/member-cell.tsx +167 -0
  15. package/src/components/cards/strip-card/sprite.tsx +36 -0
  16. package/src/components/embed-card.tsx +44 -0
  17. package/src/components/mdx/recipe.tsx +129 -0
  18. package/src/components/tool-list.tsx +49 -0
  19. package/src/components/tree.tsx +98 -0
  20. package/src/components/views/alloy/index.tsx +112 -0
  21. package/src/components/views/alloy/list.tsx +85 -0
  22. package/src/components/views/alloy/mix/index.tsx +35 -0
  23. package/src/components/views/alloy/mix/ratio.tsx +94 -0
  24. package/src/components/views/alloy/mix/rows.tsx +145 -0
  25. package/src/components/views/alloy/mix/status.tsx +122 -0
  26. package/src/components/views/alloy/mix/total.tsx +132 -0
  27. package/src/components/views/alloy/range-bar.tsx +65 -0
  28. package/src/components/views/alloy/stack-bar.tsx +88 -0
  29. package/src/components/views/browser/axis-switcher.tsx +54 -0
  30. package/src/components/views/browser/card-grid.tsx +275 -0
  31. package/src/components/views/browser/favorite-button.tsx +46 -0
  32. package/src/components/views/browser/frame.tsx +112 -0
  33. package/src/components/views/browser/index.tsx +114 -0
  34. package/src/components/views/browser/item-panel/index.tsx +229 -0
  35. package/src/components/views/browser/item-panel/skeleton.tsx +26 -0
  36. package/src/components/views/browser/item-panel-grid/cell.tsx +178 -0
  37. package/src/components/views/browser/item-panel-grid/favorite-cell.tsx +129 -0
  38. package/src/components/views/browser/item-panel-grid/index.tsx +105 -0
  39. package/src/components/views/browser/item-view/index.tsx +186 -0
  40. package/src/components/views/browser/item-view/tags.tsx +85 -0
  41. package/src/components/views/browser/item-view-header.tsx +131 -0
  42. package/src/components/views/browser/pane-tabs.tsx +61 -0
  43. package/src/components/views/browser/place-header.tsx +72 -0
  44. package/src/components/views/browser/query-controls.tsx +105 -0
  45. package/src/components/views/browser/recipe-item-header.tsx +60 -0
  46. package/src/components/views/browser/settings.tsx +52 -0
  47. package/src/components/views/browser/toolbar.tsx +54 -0
  48. package/src/components/views/browser/type-tabs.tsx +71 -0
  49. package/src/components/views/browser/type-view.tsx +184 -0
  50. package/src/components/views/browser/workstation-rail.tsx +43 -0
  51. package/src/components/views/calc/calculator/actions.tsx +47 -0
  52. package/src/components/views/calc/calculator/help.tsx +189 -0
  53. package/src/components/views/calc/calculator/index.tsx +172 -0
  54. package/src/components/views/calc/dot-overlay.tsx +80 -0
  55. package/src/components/views/calc/edge.tsx +152 -0
  56. package/src/components/views/calc/flow/index.tsx +305 -0
  57. package/src/components/views/calc/flow/picker-dialogs.tsx +146 -0
  58. package/src/components/views/calc/flow/toolbar.tsx +60 -0
  59. package/src/components/views/calc/flow/view-menu.tsx +117 -0
  60. package/src/components/views/calc/member-picker.tsx +120 -0
  61. package/src/components/views/calc/node-card/actions.tsx +90 -0
  62. package/src/components/views/calc/node-card/badges.tsx +59 -0
  63. package/src/components/views/calc/node-card/chips.tsx +187 -0
  64. package/src/components/views/calc/node-card/far.tsx +123 -0
  65. package/src/components/views/calc/node-card/full.tsx +332 -0
  66. package/src/components/views/calc/node-card/index.tsx +27 -0
  67. package/src/components/views/calc/node-card/menu.tsx +282 -0
  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 +207 -0
  72. package/src/components/views/calc/recipe-picker/footer.tsx +131 -0
  73. package/src/components/views/calc/recipe-picker/index.tsx +52 -0
  74. package/src/components/views/calc/sidebar.tsx +171 -0
  75. package/src/components/views/calc/trees/icon-picker.tsx +97 -0
  76. package/src/components/views/calc/trees/index.tsx +134 -0
  77. package/src/components/views/calc/trees/label.tsx +67 -0
  78. package/src/components/views/calc/trees/rows.tsx +140 -0
  79. package/src/components/views/forge/gui.tsx +131 -0
  80. package/src/components/views/forge/index.tsx +112 -0
  81. package/src/components/views/forge/list.tsx +113 -0
  82. package/src/components/views/forge/workbench/controls.tsx +103 -0
  83. package/src/components/views/forge/workbench/index.tsx +85 -0
  84. package/src/components/views/forge/workbench/panel.tsx +228 -0
  85. package/src/components/views/forge/workbench/ruler.tsx +133 -0
  86. package/src/components/views/forge/workbench/status.tsx +55 -0
  87. package/src/components/views/forge/workbench/tape.tsx +70 -0
  88. package/src/hooks/alloy/use-fluid-colors.ts +125 -0
  89. package/src/hooks/alloy/use-mix-state.ts +240 -0
  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/use-mount.ts +97 -0
  94. package/src/hooks/calc/flow/use-nodes.ts +260 -0
  95. package/src/hooks/calc/flow/use-pickers.ts +57 -0
  96. package/src/hooks/calc/flow/use-viewport.ts +141 -0
  97. package/src/hooks/calc/flow/use-window.ts +87 -0
  98. package/src/hooks/calc/use-actions.ts +355 -0
  99. package/src/hooks/calc/use-amounts.ts +64 -0
  100. package/src/hooks/calc/use-data.ts +203 -0
  101. package/src/hooks/calc/use-defaults.ts +40 -0
  102. package/src/hooks/calc/use-far-mode.ts +37 -0
  103. package/src/hooks/calc/use-focus.ts +31 -0
  104. package/src/hooks/calc/use-maps.ts +76 -0
  105. package/src/hooks/calc/use-reset.ts +15 -0
  106. package/src/hooks/calc/use-resolver.ts +70 -0
  107. package/src/hooks/calc/use-session.ts +13 -0
  108. package/src/hooks/calc/use-share.ts +64 -0
  109. package/src/hooks/calc/use-storage.ts +126 -0
  110. package/src/hooks/calc/use-tier.ts +13 -0
  111. package/src/hooks/calc/use-view-mode.ts +24 -0
  112. package/src/hooks/calc/use-worker.ts +204 -0
  113. package/src/hooks/forge/use-anvil-gui.ts +16 -0
  114. package/src/hooks/forge/use-forge-state.ts +135 -0
  115. package/src/hooks/forge/use-ruler.ts +100 -0
  116. package/src/hooks/use-add-to-calc.ts +51 -0
  117. package/src/hooks/use-categories-by-id.ts +18 -0
  118. package/src/hooks/use-favorite-hotkey.ts +39 -0
  119. package/src/hooks/use-favorites.ts +135 -0
  120. package/src/hooks/use-group-outlines.ts +65 -0
  121. package/src/hooks/use-hide-unused.ts +12 -0
  122. package/src/hooks/use-index.ts +72 -0
  123. package/src/hooks/use-item-feeds.ts +46 -0
  124. package/src/hooks/use-item-pane.ts +106 -0
  125. package/src/hooks/use-item-press.ts +89 -0
  126. package/src/hooks/use-item-tag-groups.ts +54 -0
  127. package/src/hooks/use-item-tooltips.ts +18 -0
  128. package/src/hooks/use-left-sidebar-tree.ts +34 -0
  129. package/src/hooks/use-mod-names.ts +17 -0
  130. package/src/hooks/use-mount-limit.ts +22 -0
  131. package/src/hooks/use-panel-cells.ts +117 -0
  132. package/src/hooks/use-panel-page.ts +54 -0
  133. package/src/hooks/use-panel-tabs.ts +43 -0
  134. package/src/hooks/use-query.ts +186 -0
  135. package/src/hooks/use-session.ts +88 -0
  136. package/src/hooks/use-sidebar-group.ts +16 -0
  137. package/src/hooks/use-tag-members.ts +16 -0
  138. package/src/hooks/use-tool-recipes.ts +26 -0
  139. package/src/hooks/use-type-filter.ts +73 -0
  140. package/src/lib/alloy/mix.ts +210 -0
  141. package/src/lib/alloy/parse.ts +37 -0
  142. package/src/lib/calc/defaults.ts +47 -0
  143. package/src/lib/calc/flow.ts +315 -0
  144. package/src/lib/calc/focus.ts +15 -0
  145. package/src/lib/calc/meta.ts +17 -0
  146. package/src/lib/calc/pack.ts +77 -0
  147. package/src/lib/calc/ports.ts +43 -0
  148. package/src/lib/calc/resolve.ts +198 -0
  149. package/src/lib/calc/route/buckets.ts +49 -0
  150. package/src/lib/calc/route/geometry.ts +51 -0
  151. package/src/lib/calc/route/index.ts +3 -0
  152. package/src/lib/calc/route/polyline.ts +103 -0
  153. package/src/lib/calc/route/router.ts +394 -0
  154. package/src/lib/calc/scheme.ts +226 -0
  155. package/src/lib/calc/storage/entry.ts +97 -0
  156. package/src/lib/calc/storage/index.ts +20 -0
  157. package/src/lib/calc/storage/keys.ts +38 -0
  158. package/src/lib/calc/storage/store.ts +198 -0
  159. package/src/lib/calc/tree/index.ts +4 -0
  160. package/src/lib/calc/tree/materials.ts +29 -0
  161. package/src/lib/calc/tree/recipe.ts +104 -0
  162. package/src/lib/calc/tree/share.ts +135 -0
  163. package/src/lib/calc/worker/api.ts +25 -0
  164. package/src/lib/calc/worker/fetch.ts +66 -0
  165. package/src/lib/calc/worker/flush.ts +39 -0
  166. package/src/lib/calc/worker/index.ts +8 -0
  167. package/src/lib/calc/worker/protocol.ts +35 -0
  168. package/src/lib/calc/worker/resolve.ts +49 -0
  169. package/src/lib/calc/worker/state.ts +48 -0
  170. package/src/lib/card-model.ts +65 -0
  171. package/src/lib/client/recipe-categories.ts +14 -0
  172. package/src/lib/client/recipe.ts +85 -0
  173. package/src/lib/client-index/catalog.ts +91 -0
  174. package/src/lib/client-index/chrome.ts +180 -0
  175. package/src/lib/client-index/index.ts +20 -0
  176. package/src/lib/client-index/registry.ts +121 -0
  177. package/src/lib/client-index/rows.ts +260 -0
  178. package/src/lib/client-index/tooltips.ts +74 -0
  179. package/src/lib/config.ts +18 -0
  180. package/src/lib/data/category/dto.ts +25 -0
  181. package/src/lib/data/category/load.ts +47 -0
  182. package/src/lib/data/category/parse.ts +71 -0
  183. package/src/lib/data/dto.ts +152 -0
  184. package/src/lib/data/grid.ts +40 -0
  185. package/src/lib/data/parse/entry.ts +85 -0
  186. package/src/lib/data/parse/index.ts +3 -0
  187. package/src/lib/data/parse/layout.ts +138 -0
  188. package/src/lib/data/parse/slots.ts +85 -0
  189. package/src/lib/data/parse/widgets.ts +81 -0
  190. package/src/lib/data/scene.ts +103 -0
  191. package/src/lib/data/slot-io.ts +44 -0
  192. package/src/lib/dump.ts +6 -0
  193. package/src/lib/embed.ts +22 -0
  194. package/src/lib/favorite-hover.ts +13 -0
  195. package/src/lib/fluid-outputs.ts +70 -0
  196. package/src/lib/forge/actions.ts +59 -0
  197. package/src/lib/forge/data.ts +51 -0
  198. package/src/lib/forge/solve.ts +80 -0
  199. package/src/lib/group.ts +23 -0
  200. package/src/lib/index-store.ts +70 -0
  201. package/src/lib/links.ts +131 -0
  202. package/src/lib/lookup.ts +119 -0
  203. package/src/lib/panel-outline.ts +86 -0
  204. package/src/lib/panel.ts +105 -0
  205. package/src/lib/preview.ts +32 -0
  206. package/src/lib/recipe-slots.ts +8 -0
  207. package/src/lib/search.ts +139 -0
  208. package/src/lib/sidebar.ts +47 -0
  209. package/src/lib/used-items.ts +25 -0
  210. package/src/locales/en_us.json +172 -0
  211. package/src/locales/ru_ru.json +172 -0
  212. package/src/locales.ts +6 -0
  213. package/src/types/alloy.ts +20 -0
  214. package/src/types/calc-flow-props.ts +44 -0
  215. package/src/types/calc.ts +92 -0
  216. package/src/types/forge.ts +21 -0
  217. package/src/types/index.ts +136 -0
  218. package/styles/calc.css +308 -0
  219. package/styles/strip.css +7 -0
  220. package/styles.css +2 -0
  221. package/README.md +0 -3
@@ -0,0 +1,40 @@
1
+ 'use client';
2
+ import type { DatasetRef } from '@terrafirmagreg-team/sites/location';
3
+
4
+ import { useCallback, useEffect, useState } from 'react';
5
+ import {
6
+ readRecipeDefaults,
7
+ subscribeRecipeDefaults,
8
+ writeRecipeDefault,
9
+ } from '@terrafirmagreg-team/recipe/lib/calc/defaults';
10
+
11
+ export function useRecipeDefaults(dataset: DatasetRef) {
12
+ const [defaultsState, setDefaultsState] = useState<{
13
+ dataset: DatasetRef;
14
+ map: Record<string, string>;
15
+ }>(() => ({ dataset, map: readRecipeDefaults(dataset) }));
16
+ const defaults =
17
+ defaultsState.dataset === dataset
18
+ ? defaultsState.map
19
+ : readRecipeDefaults(dataset);
20
+ useEffect(
21
+ () =>
22
+ subscribeRecipeDefaults(() =>
23
+ setDefaultsState({ dataset, map: readRecipeDefaults(dataset) }),
24
+ ),
25
+ [dataset],
26
+ );
27
+
28
+ const setRecipeDefault = useCallback(
29
+ (materialId: string, recipeId: string | null) => {
30
+ setDefaultsState({
31
+ dataset,
32
+ map: writeRecipeDefault(dataset, materialId, recipeId),
33
+ });
34
+ },
35
+ [dataset],
36
+ );
37
+
38
+ return { defaults, setRecipeDefault };
39
+ }
40
+
@@ -0,0 +1,37 @@
1
+ 'use client';
2
+
3
+ import {
4
+ createContext,
5
+ startTransition,
6
+ useCallback,
7
+ useContext,
8
+ } from 'react';
9
+ import {
10
+ createStoredValue,
11
+ literalCodec,
12
+ } from '@terrafirmagreg-team/ui/lib/utils/stored-value';
13
+ import type { CalcFarMode } from '@terrafirmagreg-team/recipe/lib/calc/flow';
14
+
15
+ const FAR_MODES: CalcFarMode[] = ['full', 'preview', 'hidden'];
16
+ const useStoredMode = createStoredValue(
17
+ 'calc:far',
18
+ literalCodec(FAR_MODES, 'preview'),
19
+ );
20
+
21
+ const CalcFarContext = createContext<CalcFarMode>('preview');
22
+
23
+ export const CalcFarProvider = CalcFarContext.Provider;
24
+
25
+ export function useCalcFar(): CalcFarMode {
26
+ return useContext(CalcFarContext);
27
+ }
28
+
29
+ export function useCalcFarMode() {
30
+ const [mode, setStored] = useStoredMode();
31
+ const setMode = useCallback((next: CalcFarMode) => {
32
+
33
+ startTransition(() => setStored(next));
34
+ }, [setStored]);
35
+ return [mode, setMode] as const;
36
+ }
37
+
@@ -0,0 +1,31 @@
1
+ 'use client';
2
+
3
+ import { useCallback, useEffect, useRef, useState } from 'react';
4
+ import { onCalcFocusRequest } from '@terrafirmagreg-team/recipe/lib/calc/focus';
5
+ import type { CalcCard } from '@terrafirmagreg-team/recipe/types/calc';
6
+
7
+ export function useCalcFocus(cards: CalcCard[]) {
8
+ const focusNodeRef = useRef<((materialId: string) => void) | null>(null);
9
+ const onFocusNode = useCallback(
10
+ (materialId: string) => focusNodeRef.current?.(materialId),
11
+ [],
12
+ );
13
+ const [focusNodeId, setFocusNodeId] = useState<string | null>(null);
14
+ const [pendingFocus, setPendingFocus] = useState<{
15
+ id: string;
16
+ seen: CalcCard[];
17
+ } | null>(null);
18
+
19
+ useEffect(
20
+ () => onCalcFocusRequest((id) => setPendingFocus({ id, seen: cards })),
21
+ [cards],
22
+ );
23
+
24
+ if (pendingFocus && pendingFocus.seen !== cards) {
25
+ setFocusNodeId(cards[cards.length - 1]?.id ?? null);
26
+ setPendingFocus(null);
27
+ }
28
+
29
+ return { focusNodeRef, onFocusNode, focusNodeId, setFocusNodeId };
30
+ }
31
+
@@ -0,0 +1,76 @@
1
+ 'use client';
2
+
3
+ import { startTransition, useCallback, useDeferredValue, useState } from 'react';
4
+ import type { Recipe } from '@terrafirmagreg-team/recipe/lib/data/dto';
5
+ import { mergeNew, mergeSet } from '@terrafirmagreg-team/recipe/lib/calc/resolve';
6
+ import type { RecipeCompactRow } from '@terrafirmagreg-team/recipe/types';
7
+ import type { CalcRecipe, TagResolution } from '@terrafirmagreg-team/recipe/types/calc';
8
+
9
+ export type CalcMaps = {
10
+ recipeMap: Map<string, CalcRecipe>;
11
+ bodyMap: Map<string, Recipe>;
12
+ outMap: Map<string, RecipeCompactRow[]>;
13
+ tagMap: Map<string, TagResolution>;
14
+ deadIds: ReadonlySet<string>;
15
+ };
16
+
17
+ export type CalcMapsSlice = Partial<{
18
+ out: Map<string, RecipeCompactRow[]>;
19
+ tag: Map<string, TagResolution>;
20
+ recipes: Map<string, CalcRecipe>;
21
+ bodies: Map<string, Recipe>;
22
+ dead: Set<string>;
23
+ }>;
24
+
25
+ export function useCalcMaps() {
26
+ const [recipeMap, setRecipeMap] = useState<Map<string, CalcRecipe>>(
27
+ () => new Map(),
28
+ );
29
+ const [bodyMap, setBodyMap] = useState<Map<string, Recipe>>(() => new Map());
30
+ const [outMap, setOutMap] = useState<Map<string, RecipeCompactRow[]>>(
31
+ () => new Map(),
32
+ );
33
+ const [tagMap, setTagMap] = useState<Map<string, TagResolution>>(
34
+ () => new Map(),
35
+ );
36
+ const [deadIds, setDeadIds] = useState<ReadonlySet<string>>(() => new Set());
37
+
38
+ const dataOutMap = useDeferredValue(outMap);
39
+ const dataRecipeMap = useDeferredValue(recipeMap);
40
+ const dataTagMap = useDeferredValue(tagMap);
41
+ const dataDeadIds = useDeferredValue(deadIds);
42
+
43
+ const applyMaps = useCallback((src: CalcMapsSlice, extra?: () => void) => {
44
+ startTransition(() => {
45
+ if (src.out) setOutMap((prev) => mergeNew(prev, src.out!));
46
+ if (src.tag) setTagMap((prev) => mergeNew(prev, src.tag!));
47
+ if (src.recipes) setRecipeMap((prev) => mergeNew(prev, src.recipes!));
48
+ if (src.bodies) setBodyMap((prev) => mergeNew(prev, src.bodies!));
49
+ if (src.dead) setDeadIds((prev) => mergeSet(prev, src.dead!));
50
+ extra?.();
51
+ });
52
+ }, []);
53
+
54
+ const resetMaps = useCallback(() => {
55
+ startTransition(() => {
56
+ setOutMap(new Map());
57
+ setTagMap(new Map());
58
+ setRecipeMap(new Map());
59
+ setBodyMap(new Map());
60
+ setDeadIds(new Set());
61
+ });
62
+ }, []);
63
+
64
+ return {
65
+ maps: { recipeMap, bodyMap, outMap, tagMap, deadIds },
66
+ data: {
67
+ outMap: dataOutMap,
68
+ recipeMap: dataRecipeMap,
69
+ tagMap: dataTagMap,
70
+ deadIds: dataDeadIds,
71
+ },
72
+ applyMaps,
73
+ resetMaps,
74
+ };
75
+ }
76
+
@@ -0,0 +1,15 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+
5
+ export function useCalcReset(activeTabId: string | null) {
6
+ const [resetGen, setResetGen] = useState(0);
7
+
8
+ const [prevResetTab, setPrevResetTab] = useState(activeTabId);
9
+ if (prevResetTab !== activeTabId) {
10
+ setPrevResetTab(activeTabId);
11
+ setResetGen((gen) => gen + 1);
12
+ }
13
+ return { resetGen, setResetGen };
14
+ }
15
+
@@ -0,0 +1,70 @@
1
+ 'use client';
2
+
3
+ import { useAsyncEffect } from '@terrafirmagreg-team/ui/hooks/use-async-value';
4
+ import type { RecipeCompactIndex } from '@terrafirmagreg-team/recipe/types';
5
+ import type {
6
+ CalcCard,
7
+ CalcRecipe,
8
+ } from '@terrafirmagreg-team/recipe/types/calc';
9
+ import type { useCalcWorker } from './use-worker';
10
+
11
+ type WorkerSlice = ReturnType<typeof useCalcWorker>;
12
+
13
+ export function useCalcResolver({
14
+ catalog,
15
+ inputKey,
16
+ cards,
17
+ recipeMap,
18
+ worker,
19
+ }: {
20
+ catalog: RecipeCompactIndex | null;
21
+ inputKey: string;
22
+ cards: CalcCard[];
23
+ recipeMap: Map<string, CalcRecipe>;
24
+ worker: WorkerSlice;
25
+ }) {
26
+ const {
27
+ ref: workerRef,
28
+ state: workerState,
29
+ generation,
30
+ call,
31
+ isResolved,
32
+ markResolved,
33
+ } = worker;
34
+ useAsyncEffect(async () => {
35
+ if (
36
+ !catalog ||
37
+ (!catalog.items.length && !catalog.fluids?.length) ||
38
+ workerState === 'pending'
39
+ ) {
40
+ return;
41
+ }
42
+ if (!workerRef.current) return;
43
+ if (isResolved(inputKey)) return;
44
+ markResolved(inputKey);
45
+
46
+ const materials = new Set<string>();
47
+ const recipes = new Set<string>();
48
+ const tags = new Set<string>();
49
+ for (const card of cards) {
50
+ materials.add(card.materialId);
51
+ if (card.recipeId) recipes.add(card.recipeId);
52
+ }
53
+ for (const card of cards) {
54
+ if (!card.recipeId) continue;
55
+ const recipe = recipeMap.get(card.recipeId);
56
+ if (!recipe) continue;
57
+ for (const input of recipe.inputs) {
58
+ if (input.tagId) tags.add(input.tagId);
59
+ }
60
+ }
61
+ await call((api) =>
62
+ api.resolve({
63
+ materials: [...materials],
64
+ recipes: [...recipes],
65
+ tags: [...tags],
66
+ }),
67
+ ).catch(() => {});
68
+ }, [catalog, cards, recipeMap, workerState, generation, call, inputKey]);
69
+ }
70
+
@@ -0,0 +1,13 @@
1
+ 'use client';
2
+
3
+ import { createContext, useContext } from 'react';
4
+ import type { RecipeSession } from '@terrafirmagreg-team/recipe/types';
5
+
6
+ const CalcSessionContext = createContext<RecipeSession | null>(null);
7
+
8
+ export const CalcSessionProvider = CalcSessionContext.Provider;
9
+
10
+ export function useCalcSession(): RecipeSession | null {
11
+ return useContext(CalcSessionContext);
12
+ }
13
+
@@ -0,0 +1,64 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useState } from 'react';
4
+ import { decodeCalcState } from '@terrafirmagreg-team/recipe/lib/calc/tree';
5
+ import {
6
+ newCalcEntry,
7
+ readCalcTabs,
8
+ writeCalcTabs,
9
+ } from '@terrafirmagreg-team/recipe/lib/calc/storage';
10
+ import { recipeCalcHref } from '@terrafirmagreg-team/recipe/lib/links';
11
+ import { replaceUrlState } from '@terrafirmagreg-team/ui/lib/utils/history';
12
+ import type { DatasetRef } from '@terrafirmagreg-team/sites/location';
13
+
14
+ export function useCalcShare({
15
+ storageKey,
16
+ dataset,
17
+ lang,
18
+ incoming,
19
+ }: {
20
+ storageKey: string;
21
+ dataset: DatasetRef;
22
+ lang: string;
23
+ incoming: string;
24
+ }): boolean {
25
+ const isShareId = incoming.startsWith('s');
26
+ const [sharedTree, setSharedTree] = useState<string | null>(null);
27
+ useEffect(() => {
28
+ if (!isShareId) return;
29
+ let cancelled = false;
30
+ const { id: datasetId, version } = dataset;
31
+ void fetch(
32
+ `/api/share/recipe/${encodeURIComponent(datasetId)}/${encodeURIComponent(incoming.slice(1))}${version ? `?version=${encodeURIComponent(version)}` : ''}`,
33
+ )
34
+ .then((res) => (res.ok ? res.json() : null))
35
+ .then((body: { tree?: unknown } | null) => {
36
+ if (!cancelled)
37
+ setSharedTree(typeof body?.tree === 'string' ? body.tree : '');
38
+ })
39
+ .catch(() => {
40
+ if (!cancelled) setSharedTree('');
41
+ });
42
+ return () => {
43
+ cancelled = true;
44
+ };
45
+ }, [isShareId, incoming, dataset]);
46
+ const transport = isShareId ? sharedTree : incoming || null;
47
+
48
+ useEffect(() => {
49
+ if (transport === null) return;
50
+ const decoded = transport ? decodeCalcState(transport) : null;
51
+ if (decoded) {
52
+ const entry = newCalcEntry(decoded);
53
+ writeCalcTabs(
54
+ storageKey,
55
+ [...readCalcTabs(storageKey), entry],
56
+ entry.id,
57
+ );
58
+ }
59
+ if (incoming) replaceUrlState(recipeCalcHref(dataset, lang));
60
+ }, [transport, storageKey, dataset, lang, incoming]);
61
+
62
+ return isShareId && sharedTree === null;
63
+ }
64
+
@@ -0,0 +1,126 @@
1
+ 'use client';
2
+
3
+ import { useCallback, useSyncExternalStore } from 'react';
4
+ import {
5
+ newCalcEntry,
6
+ readCalcActive,
7
+ resolveCalcActive,
8
+ readCalcHistory,
9
+ readCalcStorage,
10
+ readCalcTabs,
11
+ removeCalcHistory,
12
+ subscribeCalcStorage,
13
+ updateCalcTab,
14
+ writeCalcActive,
15
+ writeCalcStorage,
16
+ writeCalcTabs,
17
+ type CalcTreeEntry,
18
+ } from '@terrafirmagreg-team/recipe/lib/calc/storage';
19
+ import type { CalculatorState } from '@terrafirmagreg-team/recipe/types/calc';
20
+
21
+ const EMPTY_TABS: CalcTreeEntry[] = [];
22
+ const EMPTY_CALC: CalculatorState = {
23
+ cards: [],
24
+ wires: [],
25
+ tagSelections: {},
26
+ };
27
+
28
+ export function useCalcStorage(storageKey: string) {
29
+ const readSnapshot = useCallback(
30
+ () => readCalcStorage(storageKey),
31
+ [storageKey],
32
+ );
33
+ const calcState = useSyncExternalStore(
34
+ subscribeCalcStorage,
35
+ readSnapshot,
36
+ () => null,
37
+ );
38
+ const tabs = useSyncExternalStore(
39
+ subscribeCalcStorage,
40
+ () => readCalcTabs(storageKey),
41
+ () => EMPTY_TABS,
42
+ );
43
+ const activeTabId = useSyncExternalStore(
44
+ subscribeCalcStorage,
45
+ () => resolveCalcActive(readCalcTabs(storageKey), readCalcActive(storageKey)),
46
+ () => null,
47
+ );
48
+ const history = useSyncExternalStore(
49
+ subscribeCalcStorage,
50
+ () => readCalcHistory(storageKey),
51
+ () => EMPTY_TABS,
52
+ );
53
+
54
+ const persist = useCallback(
55
+ (next: CalculatorState) => {
56
+ writeCalcStorage(storageKey, next);
57
+ },
58
+ [storageKey],
59
+ );
60
+
61
+ const openTab = useCallback(
62
+ (id: string | null) => writeCalcActive(storageKey, id),
63
+ [storageKey],
64
+ );
65
+
66
+ const openEntry = useCallback(
67
+ (entry: CalcTreeEntry) => {
68
+ const list = readCalcTabs(storageKey);
69
+ const existing = list.find((tab) => tab.id === entry.id);
70
+ writeCalcTabs(
71
+ storageKey,
72
+ existing ? list : [...list, entry],
73
+ entry.id,
74
+ );
75
+ },
76
+ [storageKey],
77
+ );
78
+ const closeTab = useCallback(
79
+ (id: string) => {
80
+ const list = readCalcTabs(storageKey).filter((tab) => tab.id !== id);
81
+ const active =
82
+ readCalcActive(storageKey) === id ? null : readCalcActive(storageKey);
83
+ writeCalcTabs(storageKey, list, active);
84
+ },
85
+ [storageKey],
86
+ );
87
+ const newTab = useCallback(() => {
88
+ const entry = newCalcEntry({ ...EMPTY_CALC });
89
+ writeCalcTabs(storageKey, [...readCalcTabs(storageKey), entry], entry.id);
90
+ }, [storageKey]);
91
+ const renameTab = useCallback(
92
+ (id: string, name: string) => {
93
+ updateCalcTab(storageKey, id, (tab) => ({
94
+ ...tab,
95
+ name: name.trim() || undefined,
96
+ }));
97
+ },
98
+ [storageKey],
99
+ );
100
+ const setTabIcon = useCallback(
101
+ (id: string, icon: CalcTreeEntry['icon']) => {
102
+ updateCalcTab(storageKey, id, (tab) => ({ ...tab, icon }));
103
+ },
104
+ [storageKey],
105
+ );
106
+ const removeHistory = useCallback(
107
+ (id: string) => removeCalcHistory(storageKey, id),
108
+ [storageKey],
109
+ );
110
+
111
+ return {
112
+ calcState,
113
+ tabs,
114
+ activeTabId,
115
+ history,
116
+ persist,
117
+ openTab,
118
+ openEntry,
119
+ closeTab,
120
+ newTab,
121
+ renameTab,
122
+ setTabIcon,
123
+ removeHistory,
124
+ };
125
+ }
126
+
@@ -0,0 +1,13 @@
1
+ 'use client';
2
+
3
+ import { createContext, useContext } from 'react';
4
+ import type { CalcTier } from '@terrafirmagreg-team/recipe/lib/calc/flow';
5
+
6
+ const CalcTierContext = createContext<CalcTier>('near');
7
+
8
+ export const CalcTierProvider = CalcTierContext.Provider;
9
+
10
+ export function useCalcTier(): CalcTier {
11
+ return useContext(CalcTierContext);
12
+ }
13
+
@@ -0,0 +1,24 @@
1
+ 'use client';
2
+
3
+ import { startTransition, useCallback } from 'react';
4
+ import {
5
+ createStoredValue,
6
+ literalCodec,
7
+ } from '@terrafirmagreg-team/ui/lib/utils/stored-value';
8
+ import type { CalcViewMode } from '@terrafirmagreg-team/recipe/lib/calc/flow';
9
+
10
+ const MODES: CalcViewMode[] = ['mini', 'normal', 'detailed'];
11
+ const useStoredMode = createStoredValue(
12
+ 'calc:view',
13
+ literalCodec(MODES, 'normal'),
14
+ );
15
+
16
+ export function useCalcViewMode() {
17
+ const [mode, setStored] = useStoredMode();
18
+ const setMode = useCallback(
19
+ (next: CalcViewMode) => startTransition(() => setStored(next)),
20
+ [setStored],
21
+ );
22
+ return [mode, setMode] as const;
23
+ }
24
+