@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,35 @@
1
+ import type { DatasetRef } from '@terrafirmagreg-team/sites/location';
2
+ import type { Recipe } from '@terrafirmagreg-team/recipe/lib/data/dto';
3
+ import type { RecipeCompactRow } from '@terrafirmagreg-team/recipe/types';
4
+ import type {
5
+ CalcRecipe,
6
+ TagResolution,
7
+ } from '@terrafirmagreg-team/recipe/types/calc';
8
+
9
+ export type ResolveInput = {
10
+ materials?: string[];
11
+ recipes?: string[];
12
+ tags?: string[];
13
+ };
14
+
15
+ export type CalcWorkerFlush = {
16
+ epoch: number;
17
+ out: [string, RecipeCompactRow[]][];
18
+ tag: [string, TagResolution][];
19
+ recipes: [string, CalcRecipe][];
20
+ bodies: [string, Recipe][];
21
+ dead: string[];
22
+ };
23
+
24
+ export type FlushHandler = (msg: CalcWorkerFlush) => void;
25
+
26
+ export type CalcWorkerApi = {
27
+ init(
28
+ dataset: DatasetRef,
29
+ catalog: unknown,
30
+ flushHandler: FlushHandler,
31
+ ): Promise<void>;
32
+ resolve(input: ResolveInput): Promise<void>;
33
+ invalidate(): Promise<void>;
34
+ };
35
+
@@ -0,0 +1,49 @@
1
+ import { fetchWanted } from './fetch';
2
+ import { createFlush, postFlush } from './flush';
3
+ import type { ResolveInput } from './protocol';
4
+ import { state } from './state';
5
+
6
+ export async function runResolve(input: ResolveInput) {
7
+ const gen = state.resolveGen;
8
+ const pending = createFlush();
9
+ const wantedRecipes = new Set(
10
+ (input.recipes ?? []).filter(
11
+ (id) => id && !state.recipeMap.has(id) && !state.deadIds.has(id),
12
+ ),
13
+ );
14
+ const wantedOut = new Set(
15
+ (input.materials ?? []).filter((id) => id && !state.outMap.has(id)),
16
+ );
17
+ const wantedTag = new Set(
18
+ (input.tags ?? []).filter((id) => id && !state.tagMap.has(id)),
19
+ );
20
+ for (let iter = 0; iter < 50; iter += 1) {
21
+ if (gen !== state.resolveGen) return;
22
+ if (!wantedRecipes.size && !wantedOut.size && !wantedTag.size) break;
23
+ const progressed = await fetchWanted(
24
+ pending,
25
+ wantedRecipes,
26
+ wantedOut,
27
+ wantedTag,
28
+ state.recipeMap,
29
+ );
30
+ if (gen !== state.resolveGen) return;
31
+ if (!progressed) break;
32
+ for (const id of input.tags ?? []) {
33
+ if (id && !state.tagMap.has(id)) wantedTag.add(id);
34
+ }
35
+ wantedRecipes.forEach((id) => {
36
+ if (state.recipeMap.has(id) || state.deadIds.has(id)) {
37
+ wantedRecipes.delete(id);
38
+ }
39
+ });
40
+ wantedOut.forEach((id) => {
41
+ if (state.outMap.has(id)) wantedOut.delete(id);
42
+ });
43
+ wantedTag.forEach((id) => {
44
+ if (state.tagMap.has(id)) wantedTag.delete(id);
45
+ });
46
+ }
47
+ postFlush(pending);
48
+ }
49
+
@@ -0,0 +1,48 @@
1
+ import type { DatasetRef } from '@terrafirmagreg-team/sites/location';
2
+ import type { Recipe } from '@terrafirmagreg-team/recipe/lib/data/dto';
3
+ import type { RecipeCompactIndex, RecipeCompactRow } from '@terrafirmagreg-team/recipe/types';
4
+ import type { CalcRecipe, TagResolution } from '@terrafirmagreg-team/recipe/types/calc';
5
+ import type { FlushHandler } from './protocol';
6
+
7
+ export type CalcWorkerState = {
8
+ dataset: DatasetRef;
9
+ catalog: RecipeCompactIndex | null;
10
+ onFlush: FlushHandler;
11
+ outMap: Map<string, RecipeCompactRow[]>;
12
+ tagMap: Map<string, TagResolution>;
13
+ recipeMap: Map<string, CalcRecipe>;
14
+ bodyMap: Map<string, Recipe>;
15
+ deadIds: Set<string>;
16
+
17
+ epoch: number;
18
+ resolveGen: number;
19
+ };
20
+
21
+ export const state: CalcWorkerState = {
22
+ dataset: { id: '' },
23
+ catalog: null,
24
+ onFlush: () => {},
25
+ outMap: new Map(),
26
+ tagMap: new Map(),
27
+ recipeMap: new Map(),
28
+ bodyMap: new Map(),
29
+ deadIds: new Set(),
30
+ epoch: 0,
31
+ resolveGen: 0,
32
+ };
33
+
34
+ export function resetWorkerState(
35
+ nextDataset: DatasetRef,
36
+ nextCatalog: RecipeCompactIndex,
37
+ ) {
38
+ state.dataset = nextDataset;
39
+ state.catalog = nextCatalog;
40
+ state.resolveGen += 1;
41
+ state.epoch += 1;
42
+ state.outMap.clear();
43
+ state.tagMap.clear();
44
+ state.recipeMap.clear();
45
+ state.bodyMap.clear();
46
+ state.deadIds.clear();
47
+ }
48
+
@@ -0,0 +1,65 @@
1
+ import type {
2
+ RecipeSlotWidget,
3
+ RecipeTextWidget,
4
+ } from '@terrafirmagreg-team/recipe/lib/data/dto';
5
+
6
+ export type CardStripGeo = {
7
+ slots: RecipeSlotWidget[];
8
+ top: number;
9
+ bottom: number;
10
+ right: number;
11
+ overflow: boolean;
12
+ patchBottom: number;
13
+ barTop: number;
14
+ };
15
+
16
+ export function cardStripGeo(
17
+ slots: RecipeSlotWidget[],
18
+ contentGuiW: number,
19
+ innerBottomGui: number,
20
+ margin: number,
21
+ ): CardStripGeo {
22
+ const stripSlots = slots.filter((slot) => slot.role === 'renderOnly');
23
+ const top = stripSlots.length
24
+ ? Math.min(...stripSlots.map((slot) => slot.y))
25
+ : 0;
26
+ const bottom = stripSlots.length
27
+ ? Math.max(...stripSlots.map((slot) => slot.y + slot.h))
28
+ : 0;
29
+ const right = stripSlots.length
30
+ ? Math.max(...stripSlots.map((slot) => slot.x + slot.w))
31
+ : 0;
32
+ const overflow = right > contentGuiW + 1;
33
+ const patchBottom = Math.min(
34
+ bottom + margin + 16,
35
+ Math.max(innerBottomGui - 1, 0),
36
+ );
37
+ const barTop = Math.min(bottom + margin + 1, Math.max(innerBottomGui - 5, 0));
38
+ return { slots: stripSlots, top, bottom, right, overflow, patchBottom, barTop };
39
+ }
40
+
41
+ export function countCoveredText(
42
+ widget: RecipeTextWidget,
43
+ stripSlots: RecipeSlotWidget[],
44
+ ): boolean {
45
+ return stripSlots.some(
46
+ (slot) =>
47
+ slot.count !== undefined &&
48
+ String(slot.count) === widget.text &&
49
+ widget.x + widget.w > slot.x &&
50
+ widget.x < slot.x + slot.w,
51
+ );
52
+ }
53
+
54
+ export function textInStrip(
55
+ widget: RecipeTextWidget,
56
+ geo: CardStripGeo,
57
+ ): boolean {
58
+ return (
59
+ geo.overflow &&
60
+ widget.y + widget.h > geo.top &&
61
+ widget.y < geo.bottom &&
62
+ widget.x < geo.right
63
+ );
64
+ }
65
+
@@ -0,0 +1,14 @@
1
+ import type { DatasetRef } from '@terrafirmagreg-team/sites/location';
2
+ import { loadDatasetRecipeCategories } from '@terrafirmagreg-team/recipe/lib/data/category/load';
3
+ import type { DumpRecipeLayout } from '@terrafirmagreg-team/recipe/lib/data/category/dto';
4
+
5
+ export function clientRecipeLayout(
6
+ dataset: DatasetRef,
7
+ categoryId: string,
8
+ ): Promise<DumpRecipeLayout | undefined> {
9
+ return loadDatasetRecipeCategories(dataset).then(
10
+ (categories) =>
11
+ categories.find((cat) => cat.id === categoryId)?.layout,
12
+ );
13
+ }
14
+
@@ -0,0 +1,85 @@
1
+ import type { DatasetRef } from '@terrafirmagreg-team/sites/location';
2
+ import { asRecord, asString } from '@terrafirmagreg-team/dump/lib/dump/contract/read';
3
+ import { parseRecipe } from '@terrafirmagreg-team/recipe/lib/data/parse';
4
+ import type { Recipe } from '@terrafirmagreg-team/recipe/lib/data/dto';
5
+ import { dumpRelPublicUrl } from '@terrafirmagreg-team/dump/lib/dump/paths';
6
+ import {
7
+ clientDumpJson,
8
+ loadClientManifest,
9
+ } from '@terrafirmagreg-team/dump/lib/dump/client/dump';
10
+ import { clientRecipeLayout } from '@terrafirmagreg-team/recipe/lib/client/recipe-categories';
11
+ import { createDatasetCache } from '@terrafirmagreg-team/dump/lib/dump/reader';
12
+
13
+ const recipeIndex = createDatasetCache(async (dataset: DatasetRef, ns: string) => {
14
+ const manifest = await loadClientManifest(dataset);
15
+ const listed = manifest?.namespaces[ns];
16
+ if (!listed || !listed.registry.includes('recipes')) return undefined;
17
+ return asRecord(
18
+ await clientDumpJson(dataset, 'registry/recipes.json', 'high'),
19
+ ) as Record<string, string | string[]> | undefined;
20
+ });
21
+
22
+ export async function clientRecipeFileRels(
23
+ dataset: DatasetRef,
24
+ recipeId: string,
25
+ ): Promise<string[]> {
26
+ const ns = recipeId.split(':', 1)[0];
27
+ if (!ns) return [];
28
+ const index = await recipeIndex(dataset, ns);
29
+ const value = index?.[recipeId];
30
+ const categories = Array.isArray(value)
31
+ ? value
32
+ : typeof value === 'string'
33
+ ? [value]
34
+ : [];
35
+ return categories
36
+ .filter(
37
+ (category): category is string =>
38
+ typeof category === 'string' && !!category,
39
+ )
40
+ .map((category) =>
41
+ category.includes(':')
42
+ ? category.slice(category.indexOf(':') + 1)
43
+ : category,
44
+ )
45
+ .filter((path) => !!path)
46
+ .map((path) => `data/${ns}/recipes/${path}.json`);
47
+ }
48
+
49
+ async function clientRecipeFileRel(
50
+ dataset: DatasetRef,
51
+ recipeId: string,
52
+ ): Promise<string | undefined> {
53
+ return (await clientRecipeFileRels(dataset, recipeId))[0];
54
+ }
55
+
56
+ export const loadClientRecipe: (
57
+ dataset: DatasetRef,
58
+ id: string,
59
+ bucket?: string,
60
+ ) => Promise<Recipe | undefined> = createDatasetCache(
61
+ async (
62
+ dataset: DatasetRef,
63
+ id: string,
64
+ bucket?: string,
65
+ ): Promise<Recipe | undefined> => {
66
+ const rel = bucket ?? (await clientRecipeFileRel(dataset, id));
67
+ if (!rel) return undefined;
68
+ const doc = asRecord(await clientDumpJson(dataset, rel, 'high'));
69
+ const raw = doc?.[id];
70
+ if (raw === undefined) return undefined;
71
+ const ns = rel.split('/')[1] ?? '';
72
+ const category = asString(asRecord(raw)?.category);
73
+ const layout = category
74
+ ? await clientRecipeLayout(dataset, category)
75
+ : undefined;
76
+ return parseRecipe(raw, {
77
+ modId: ns,
78
+ bodyUrl: dumpRelPublicUrl(dataset, rel) ?? `/${dataset.id}`,
79
+ id,
80
+ ...(layout ? { layout } : {}),
81
+ });
82
+ },
83
+ (dataset, id, bucket) => JSON.stringify([dataset, id, bucket ?? '']),
84
+ );
85
+
@@ -0,0 +1,91 @@
1
+ import type { DatasetRef } from '@terrafirmagreg-team/sites/location';
2
+ import type { RecipeChrome, RecipeCompactIndex } from '@terrafirmagreg-team/recipe/types';
3
+ import { loadDatasetRecipeCategories } from '@terrafirmagreg-team/recipe/lib/data/category/load';
4
+ import { stackDisplayName } from '@terrafirmagreg-team/game/lib/entry/display';
5
+ import { createDatasetCache } from '@terrafirmagreg-team/dump/lib/dump/reader';
6
+ import { recipeTypeRows } from './rows';
7
+
8
+ const emptyRecipeIndex = (): RecipeCompactIndex => ({
9
+ types: [],
10
+ items: [],
11
+ names: {},
12
+ });
13
+
14
+ async function loadRecipeStructureFromDump(
15
+ dataset: DatasetRef,
16
+ chrome: RecipeChrome,
17
+ ): Promise<RecipeCompactIndex> {
18
+ const rows = await recipeTypeRows(dataset);
19
+ const typeCounts: Record<string, number> = {};
20
+ for (const row of rows) {
21
+ if (row.id) typeCounts[row.id] = row.count ?? 0;
22
+ }
23
+ return {
24
+ types: rows.map((row) => row.id),
25
+ typeCounts,
26
+ items: chrome.items,
27
+ names: chrome.names,
28
+ ...(chrome.fluids?.length ? { fluids: chrome.fluids } : {}),
29
+ ...(chrome.usedIds?.length ? { usedIds: chrome.usedIds } : {}),
30
+ ...(chrome.tabs?.length ? { tabs: chrome.tabs } : {}),
31
+ ...(chrome.tabItems ? { tabItems: chrome.tabItems } : {}),
32
+ ...(chrome.categories?.length ? { categories: chrome.categories } : {}),
33
+ ...(chrome.mods ? { mods: chrome.mods } : {}),
34
+ ...(chrome.groups?.length ? { groups: chrome.groups } : {}),
35
+ };
36
+ }
37
+
38
+ const indexCache = createDatasetCache(
39
+ async (
40
+ dataset: DatasetRef,
41
+ chrome?: RecipeChrome,
42
+ ): Promise<RecipeCompactIndex> => {
43
+ if (!chrome) return emptyRecipeIndex();
44
+ return loadRecipeStructureFromDump(dataset, chrome);
45
+ },
46
+ (dataset, chrome) =>
47
+ `${dataset.id}/${dataset.version ?? ''} ${chrome ? (Object.keys(chrome.names)[0] ?? '') : ''}`,
48
+ );
49
+
50
+ const skeletonCache = createDatasetCache(
51
+ async (dataset: DatasetRef): Promise<RecipeCompactIndex> =>
52
+ loadDatasetRecipeCategories(dataset).then((categories) =>
53
+ loadRecipeStructureFromDump(dataset, {
54
+ items: [],
55
+ names: {},
56
+ ...(categories.length ? { categories } : {}),
57
+ }),
58
+ ),
59
+ );
60
+
61
+ export function loadRecipeCompactIndex(
62
+ dataset: DatasetRef,
63
+ chrome?: RecipeChrome,
64
+ ): Promise<RecipeCompactIndex> {
65
+ return indexCache(dataset, chrome);
66
+ }
67
+
68
+ export function loadRecipeSkeletonIndex(
69
+ dataset: DatasetRef,
70
+ ): Promise<RecipeCompactIndex> {
71
+ return skeletonCache(dataset);
72
+ }
73
+
74
+ export function nonEmptyCategoryIds(
75
+ index: RecipeCompactIndex | null,
76
+ ): string[] {
77
+ const counts = index?.typeCounts;
78
+ if (!counts) return [];
79
+ return (index?.categories ?? [])
80
+ .map((row) => row.id)
81
+ .filter((id) => (counts[id] ?? 0) > 0);
82
+ }
83
+
84
+ export function recipeItemName(
85
+ names: Record<string, Record<string, string>>,
86
+ lang: string,
87
+ id: string,
88
+ ): string {
89
+ return stackDisplayName(names[lang] ?? names.en_us, id);
90
+ }
91
+
@@ -0,0 +1,180 @@
1
+ import type { DatasetRef } from '@terrafirmagreg-team/sites/location';
2
+ import type {
3
+ RecipeCategory,
4
+ RecipeChrome,
5
+ RecipeStackGroup,
6
+ } from '@terrafirmagreg-team/recipe/types';
7
+ import { clientDumpLang, loadClientManifest } from '@terrafirmagreg-team/dump/lib/dump/client/dump';
8
+ import {
9
+ creativeTabName,
10
+ dumpTextString,
11
+ } from '@terrafirmagreg-team/dump/lib/dump/assets/lang/names';
12
+ import { stackDisplayName } from '@terrafirmagreg-team/game/lib/entry/display';
13
+ import {
14
+ loadClientRegistry,
15
+ loadClientRegistryOrder,
16
+ resolveClientStackDisplay,
17
+ } from '@terrafirmagreg-team/dump/lib/dump/client/registry';
18
+ import { loadClientTagMembers } from '@terrafirmagreg-team/dump/lib/dump/client/tags';
19
+ import { loadClientTabs } from '@terrafirmagreg-team/dump/lib/dump/client/tabs';
20
+ import { loadDatasetRecipeCategories } from '@terrafirmagreg-team/recipe/lib/data/category/load';
21
+ import { loadClientRegistryGroups } from '@terrafirmagreg-team/dump/lib/dump/client/groups';
22
+ import { loadClientModNames } from '@terrafirmagreg-team/dump/lib/dump/client/mods';
23
+ import { loadClientModAtlases } from '@terrafirmagreg-team/dump/lib/dump/client/atlases';
24
+ import { lookupSpriteInBundle } from '@terrafirmagreg-team/dump/lib/dump/assets/atlas/lookup';
25
+ import { stackIdBase, stackIdFromRef } from '@terrafirmagreg-team/dump/lib/dump/contract/ref';
26
+ import type { AtlasBundle } from '@terrafirmagreg-team/dump/lib/dump/assets/atlas/dto';
27
+ import { createDatasetCache } from '@terrafirmagreg-team/dump/lib/dump/reader';
28
+
29
+ async function clientEntityNames(
30
+ dataset: DatasetRef,
31
+ lang: string,
32
+ ids: Iterable<string>,
33
+ ): Promise<Record<string, string>> {
34
+ const [map, items, fluids] = await Promise.all([
35
+ clientDumpLang(dataset, lang),
36
+ loadClientRegistry(dataset, 'items'),
37
+ loadClientRegistry(dataset, 'fluids'),
38
+ ]);
39
+ const all = [...new Set([...items.keys(), ...fluids.keys(), ...ids])];
40
+ const info = new Map<string, { baseId: string; langKey?: string }>();
41
+ const mods = new Set<string>();
42
+ for (const id of all) {
43
+ const baseId = stackIdBase(id);
44
+ const langKey = resolveClientStackDisplay(id, items, fluids)?.name;
45
+ info.set(id, { baseId, ...(langKey ? { langKey } : {}) });
46
+ if (!langKey && baseId.includes(':') && !baseId.startsWith('#')) {
47
+ const mod = baseId.split(':', 2)[0];
48
+ if (mod) mods.add(mod);
49
+ }
50
+ }
51
+ const atlases = new Map<string, AtlasBundle>();
52
+ await Promise.all(
53
+ [...mods].map(async (mod) => {
54
+ atlases.set(mod, await loadClientModAtlases(dataset, mod));
55
+ }),
56
+ );
57
+ const names: Record<string, string> = { ...map };
58
+ let processed = 0;
59
+ for (const [id, entry] of info) {
60
+ if (++processed % 2000 === 0) {
61
+ await new Promise((resolve) => setTimeout(resolve));
62
+ }
63
+ const langKey =
64
+ entry.langKey ??
65
+ lookupSpriteInBundle(
66
+ atlases.get(entry.baseId.split(':', 2)[0] ?? ''),
67
+ entry.baseId,
68
+ )?.sprite.langKey;
69
+ const kind = entry.baseId.startsWith('#')
70
+ ? undefined
71
+ : fluids.has(entry.baseId)
72
+ ? 'fluid'
73
+ : 'item';
74
+ names[id] = stackDisplayName(map, id, kind, langKey);
75
+ }
76
+ return names;
77
+ }
78
+
79
+ async function clientNamedCategories(
80
+ dataset: DatasetRef,
81
+ lang: string,
82
+ types: string[],
83
+ categories: RecipeCategory[],
84
+ ): Promise<Record<string, string>> {
85
+ const map = await clientDumpLang(dataset, lang);
86
+ const byId = new Map(categories.map((row) => [row.id, row]));
87
+ const names: Record<string, string> = {};
88
+ for (const id of types) {
89
+ names[id] = creativeTabName(map, byId.get(id)?.display?.name ?? id);
90
+ }
91
+ return names;
92
+ }
93
+
94
+ async function clientUsedIds(dataset: DatasetRef): Promise<string[]> {
95
+ const manifest = await loadClientManifest(dataset);
96
+ return manifest?.indexes.used ?? [];
97
+ }
98
+
99
+ async function clientRecipeGroups(
100
+ dataset: DatasetRef,
101
+ lang: string,
102
+ ): Promise<RecipeStackGroup[]> {
103
+ const rows = await loadClientRegistryGroups(dataset);
104
+ if (!rows.length) return [];
105
+ const map = await clientDumpLang(dataset, lang);
106
+ return rows.map((row) => ({
107
+ id: row.id,
108
+ ...(row.name ? { name: dumpTextString(map, row.name) } : {}),
109
+ members: row.members,
110
+ }));
111
+ }
112
+
113
+ const loadChrome = createDatasetCache(
114
+ async (dataset: DatasetRef, lang: string): Promise<RecipeChrome> => {
115
+ const [items, fluids, creative, categories, usedIds, itemTags, blockTags] =
116
+ await Promise.all([
117
+ loadClientRegistryOrder(dataset, 'item'),
118
+ loadClientRegistryOrder(dataset, 'fluid'),
119
+ loadClientTabs(dataset),
120
+ loadDatasetRecipeCategories(dataset),
121
+ clientUsedIds(dataset),
122
+ loadClientTagMembers(dataset, 'item'),
123
+ loadClientTagMembers(dataset, 'block'),
124
+ ]);
125
+ const tabs = creative.tabs;
126
+ const tabItems = Object.keys(creative.members).length
127
+ ? creative.members
128
+ : undefined;
129
+ const types = categories.map((row) => row.id);
130
+ const tagIds = new Set([
131
+ ...Object.keys(itemTags ?? {}),
132
+ ...Object.keys(blockTags ?? {}),
133
+ ]);
134
+ const nameIds = [
135
+ ...items,
136
+ ...fluids,
137
+ ...tagIds,
138
+ ...(usedIds ?? []),
139
+ ...Object.values(tabItems ?? {}).flat(),
140
+ ...tabs.flatMap((tab) =>
141
+ tab.icon ? [stackIdFromRef(tab.icon)] : [],
142
+ ),
143
+ ];
144
+ const [groups, mods, entityNames, categoryNames, tabNames] =
145
+ await Promise.all([
146
+ clientRecipeGroups(dataset, lang),
147
+ loadClientModNames(dataset, lang),
148
+ clientEntityNames(dataset, lang, nameIds),
149
+ clientNamedCategories(dataset, lang, types, categories),
150
+ clientDumpLang(dataset, lang).then((map) => {
151
+ const names: Record<string, string> = {};
152
+ for (const tab of tabs) {
153
+ names[tab.id] = creativeTabName(map, tab.display.name ?? tab.id);
154
+ }
155
+ return names;
156
+ }),
157
+ ]);
158
+ return {
159
+ items,
160
+ ...(usedIds.length ? { usedIds } : {}),
161
+ ...(fluids.length ? { fluids } : {}),
162
+ ...(tabs.length ? { tabs } : {}),
163
+ ...(tabItems ? { tabItems } : {}),
164
+ ...(categories.length ? { categories } : {}),
165
+ ...(groups.length ? { groups } : {}),
166
+ mods,
167
+ names: {
168
+ [lang]: { ...tabNames, ...categoryNames, ...entityNames },
169
+ },
170
+ };
171
+ },
172
+ );
173
+
174
+ export function loadRecipeChromeClient(
175
+ dataset: DatasetRef,
176
+ lang: string,
177
+ ): Promise<RecipeChrome> {
178
+ return loadChrome(dataset, lang);
179
+ }
180
+
@@ -0,0 +1,20 @@
1
+ export {
2
+ loadAllTagMembers,
3
+ loadRegistryEntrySlice,
4
+ loadTagSlice,
5
+ } from './registry';
6
+ export { loadRecipeChromeClient } from './chrome';
7
+ export {
8
+ loadCategoryRecipes,
9
+ loadTypeRows,
10
+ resolveRecipeRows,
11
+ TAG_RECIPE_TYPE,
12
+ } from './rows';
13
+ export { loadItemTooltips } from './tooltips';
14
+ export {
15
+ loadRecipeCompactIndex,
16
+ loadRecipeSkeletonIndex,
17
+ nonEmptyCategoryIds,
18
+ recipeItemName,
19
+ } from './catalog';
20
+