@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,121 @@
1
+ import type { DatasetRef } from '@terrafirmagreg-team/sites/location';
2
+ import { clientDumpJson } from '@terrafirmagreg-team/dump/lib/dump/client/dump';
3
+ import { loadClientTagMembers } from '@terrafirmagreg-team/dump/lib/dump/client/tags';
4
+ import {
5
+ asRecord,
6
+ asStringList,
7
+ resolveEdgeRows,
8
+ } from '@terrafirmagreg-team/dump/lib/dump/contract/read';
9
+ import { stackIdBase } from '@terrafirmagreg-team/dump/lib/dump/contract/ref';
10
+ import { createDatasetCache } from '@terrafirmagreg-team/dump/lib/dump/reader';
11
+
12
+ const REGISTRY_FIELD: Record<
13
+ string,
14
+ 'items' | 'blocks' | 'fluids' | 'entities'
15
+ > = {
16
+ item: 'items',
17
+ block: 'blocks',
18
+ fluid: 'fluids',
19
+ entity: 'entities',
20
+ };
21
+
22
+ function registryField(kind: string) {
23
+ return REGISTRY_FIELD[kind] ?? 'items';
24
+ }
25
+
26
+ const registrySliceCache = createDatasetCache(
27
+ async (
28
+ dataset: DatasetRef,
29
+ kind: string,
30
+ ): Promise<Record<string, Record<string, unknown>>> => {
31
+ const field = registryField(kind);
32
+ const raw = await clientDumpJson(dataset, `registry/${field}.json`);
33
+ const map = asRecord(raw);
34
+ return map ? resolveEdgeRows(map) : {};
35
+ },
36
+ (dataset, kind) => `${dataset.id}/${dataset.version ?? ''} ${registryField(kind)}`,
37
+ );
38
+
39
+ function loadRegistryFile(
40
+ dataset: DatasetRef,
41
+ kind: string,
42
+ ): Promise<Record<string, Record<string, unknown>>> {
43
+ return registrySliceCache(dataset, kind);
44
+ }
45
+
46
+ export async function loadRegistryEntrySlice(
47
+ dataset: DatasetRef,
48
+ kind: string,
49
+ id: string,
50
+ ): Promise<Record<string, unknown> | undefined> {
51
+ const base = stackIdBase(id);
52
+ const map = await loadRegistryFile(dataset, kind);
53
+ return map[id] ?? map[base];
54
+ }
55
+
56
+ const tagSliceCache = createDatasetCache(
57
+ async (
58
+ dataset: DatasetRef,
59
+ kind: string,
60
+ ns: string,
61
+ ): Promise<Record<string, string[]>> => {
62
+ const prefix = `${ns}:`;
63
+ const map = await loadRegistryFile(dataset, kind);
64
+ const out: Record<string, string[]> = {};
65
+ for (const [id, entry] of Object.entries(map)) {
66
+ if (!id.startsWith(prefix)) continue;
67
+ const tags = asStringList(entry.tags);
68
+ if (tags.length) out[id] = tags;
69
+ }
70
+ return out;
71
+ },
72
+ );
73
+
74
+ export function loadTagSlice(
75
+ dataset: DatasetRef,
76
+ kind: string,
77
+ ns: string,
78
+ ): Promise<Record<string, string[]>> {
79
+ return tagSliceCache(dataset, kind, ns);
80
+ }
81
+
82
+ const tagMembersByKindCache = createDatasetCache(
83
+ async (
84
+ dataset: DatasetRef,
85
+ kind: string,
86
+ ): Promise<Record<string, string[]>> => {
87
+ const memberKind = registryField(kind) === 'blocks' ? 'block' : 'item';
88
+ return (await loadClientTagMembers(dataset, memberKind)) ?? {};
89
+ },
90
+ (dataset, kind) =>
91
+ `${dataset.id}/${dataset.version ?? ''} ${registryField(kind) === 'blocks' ? 'block' : 'item'}`,
92
+ );
93
+
94
+ const allTagMembersCache = createDatasetCache(
95
+ async (dataset: DatasetRef): Promise<Record<string, string[]>> => {
96
+ const [items, blocks] = await Promise.all([
97
+ loadTagMembersByKind(dataset, 'item'),
98
+ loadTagMembersByKind(dataset, 'block'),
99
+ ]);
100
+ const merged: Record<string, string[]> = { ...items };
101
+ for (const [tag, ids] of Object.entries(blocks)) {
102
+ merged[tag] = merged[tag] ? [...merged[tag], ...ids] : ids;
103
+ }
104
+ return merged;
105
+ },
106
+ (dataset) => `${dataset.id}/${dataset.version ?? ''} all`,
107
+ );
108
+
109
+ function loadTagMembersByKind(
110
+ dataset: DatasetRef,
111
+ kind: string,
112
+ ): Promise<Record<string, string[]>> {
113
+ return tagMembersByKindCache(dataset, kind);
114
+ }
115
+
116
+ export function loadAllTagMembers(
117
+ dataset: DatasetRef,
118
+ ): Promise<Record<string, string[]>> {
119
+ return allTagMembersCache(dataset);
120
+ }
121
+
@@ -0,0 +1,260 @@
1
+ import type { DatasetRef } from '@terrafirmagreg-team/sites/location';
2
+ import type {
3
+ RecipeCompactIndex,
4
+ RecipeCompactRow,
5
+ } from '@terrafirmagreg-team/recipe/types';
6
+ import { clientDumpJson, loadClientManifest } from '@terrafirmagreg-team/dump/lib/dump/client/dump';
7
+ import { dumpRelPublicUrl } from '@terrafirmagreg-team/dump/lib/dump/paths';
8
+ import { asNumber, asRecord, asString } from '@terrafirmagreg-team/dump/lib/dump/contract/read';
9
+ import {
10
+ asTaggedReference,
11
+ stackIdFromRef,
12
+ } from '@terrafirmagreg-team/dump/lib/dump/contract/ref';
13
+ import type { RecipeTypeRow } from '@terrafirmagreg-team/dump/lib/dump/manifest/dto';
14
+ import {
15
+ clientRecipeFileRels,
16
+ loadClientRecipe,
17
+ } from '@terrafirmagreg-team/recipe/lib/client/recipe';
18
+ import {
19
+ outputBoundary,
20
+ slotIoRole,
21
+ } from '@terrafirmagreg-team/recipe/lib/data/slot-io';
22
+ import type { DumpRecipeLayout } from '@terrafirmagreg-team/recipe/lib/data/category/dto';
23
+ import type {
24
+ Recipe,
25
+ RecipeSlotType,
26
+ } from '@terrafirmagreg-team/recipe/lib/data/dto';
27
+ import { createDatasetCache } from '@terrafirmagreg-team/dump/lib/dump/reader';
28
+
29
+ const typeRowsCache = createDatasetCache(
30
+ async (
31
+ dataset: DatasetRef,
32
+ index: RecipeCompactIndex,
33
+ typeId: string,
34
+ ): Promise<RecipeCompactRow[]> => {
35
+ const typeIndex = index.types.indexOf(typeId);
36
+ if (typeIndex < 0) return [];
37
+ const row = (await recipeTypeRows(dataset)).find(
38
+ (entry) => entry.id === typeId,
39
+ );
40
+ if (!row?.files) return [];
41
+ const layout = categoryLayout(index, typeId);
42
+ const buckets = await Promise.all(
43
+ Object.values(row.files).map(async (rel) => {
44
+ if (!rel) return [];
45
+ const bucket = await loadBucket(dataset, rel);
46
+ return Object.entries(bucket).flatMap(([id, raw]) => {
47
+ const parsed = rowFromBucketEntry(
48
+ dataset,
49
+ id,
50
+ raw,
51
+ typeIndex,
52
+ rel,
53
+ layout,
54
+ );
55
+ return parsed ? [parsed] : [];
56
+ });
57
+ }),
58
+ );
59
+ return buckets
60
+ .flat()
61
+ .sort(
62
+ (a, b) =>
63
+ (a.o ?? Number.MAX_SAFE_INTEGER) - (b.o ?? Number.MAX_SAFE_INTEGER),
64
+ );
65
+ },
66
+ (dataset, _index, typeId) => `${dataset.id}/${dataset.version ?? ''} ${typeId}`,
67
+ );
68
+
69
+ const bucketCache = createDatasetCache(
70
+ async (dataset: DatasetRef, rel: string): Promise<Record<string, unknown>> => {
71
+ const raw = await clientDumpJson(dataset, rel, 'high');
72
+ return asRecord(raw) ?? {};
73
+ },
74
+ (dataset, rel) => `${dataset.id}/${dataset.version ?? ''} ${rel}`,
75
+ );
76
+
77
+ export async function recipeTypeRows(
78
+ dataset: DatasetRef,
79
+ ): Promise<RecipeTypeRow[]> {
80
+ const manifest = await loadClientManifest(dataset);
81
+ return manifest?.indexes.recipes.types ?? [];
82
+ }
83
+
84
+ const categoryLayoutCache = new WeakMap<
85
+ RecipeCompactIndex,
86
+ Map<string, DumpRecipeLayout>
87
+ >();
88
+
89
+ function categoryLayout(
90
+ index: RecipeCompactIndex,
91
+ typeId: string | undefined,
92
+ ): DumpRecipeLayout | undefined {
93
+ if (!typeId) return undefined;
94
+ let map = categoryLayoutCache.get(index);
95
+ if (!map) {
96
+ map = new Map();
97
+ for (const cat of index.categories ?? []) {
98
+ if (cat.layout) map.set(cat.id, cat.layout);
99
+ }
100
+ categoryLayoutCache.set(index, map);
101
+ }
102
+ return map.get(typeId);
103
+ }
104
+
105
+ function slotRefIds(
106
+ raw: unknown,
107
+ role: 'input' | 'output',
108
+ layout?: DumpRecipeLayout,
109
+ ): string[] {
110
+ const slots = asRecord(raw);
111
+ if (!slots) return [];
112
+ const boundary = outputBoundary(
113
+ slots as Record<string, { type?: unknown; x?: unknown } | undefined>,
114
+ layout,
115
+ );
116
+ const laid = layout
117
+ ? new Map(layout.slots.map((slot) => [slot.id, slot]))
118
+ : undefined;
119
+ const out: string[] = [];
120
+ for (const [slotId, slot] of Object.entries(slots)) {
121
+ const row = asRecord(slot);
122
+ const ref = asTaggedReference(row?.ref);
123
+ if (!ref) continue;
124
+ const slotType = asString(row?.type) ?? 'input';
125
+ const x = asNumber(row?.x) ?? laid?.get(slotId)?.x;
126
+ if (slotIoRole(slotType as RecipeSlotType, x, boundary) === role) {
127
+ out.push(stackIdFromRef(ref));
128
+ }
129
+ }
130
+ return out;
131
+ }
132
+
133
+ function rowFromBucketEntry(
134
+ dataset: DatasetRef,
135
+ id: string,
136
+ raw: unknown,
137
+ typeIndex: number,
138
+ rel: string,
139
+ layout?: DumpRecipeLayout,
140
+ ): RecipeCompactRow | undefined {
141
+ const recipe = asRecord(raw);
142
+ if (!recipe) return undefined;
143
+ const w = asNumber(recipe.w);
144
+ const h = asNumber(recipe.h);
145
+ const image = asString(recipe.image);
146
+ const order = asNumber(recipe.order);
147
+ return {
148
+ id,
149
+ type: typeIndex,
150
+ ...(order != null ? { o: order } : {}),
151
+ in: slotRefIds(recipe.slots, 'input', layout),
152
+ out: slotRefIds(recipe.slots, 'output', layout),
153
+ bodyUrl: dumpRelPublicUrl(dataset, rel),
154
+ ...(image ? { image } : {}),
155
+ bucket: rel,
156
+ ...(w != null && h != null ? { d: { w, h } } : {}),
157
+ };
158
+ }
159
+
160
+ function loadBucket(
161
+ dataset: DatasetRef,
162
+ rel: string,
163
+ ): Promise<Record<string, unknown>> {
164
+ return bucketCache(dataset, rel);
165
+ }
166
+
167
+ export function loadTypeRows(
168
+ dataset: DatasetRef,
169
+ index: RecipeCompactIndex,
170
+ typeId: string,
171
+ ): Promise<RecipeCompactRow[]> {
172
+ return typeRowsCache(dataset, index, typeId);
173
+ }
174
+
175
+ export async function loadCategoryRecipes(
176
+ dataset: DatasetRef,
177
+ index: RecipeCompactIndex,
178
+ typeId: string,
179
+ ): Promise<Recipe[]> {
180
+ const rows = await loadTypeRows(dataset, index, typeId);
181
+ const seen = new Set<string>();
182
+ const recipes = await Promise.all(
183
+ rows.map(async (row) => {
184
+ if (seen.has(row.id)) return undefined;
185
+ seen.add(row.id);
186
+ const recipe = await loadClientRecipe(dataset, row.id, row.bucket);
187
+ if (!recipe || recipe.type !== typeId) return undefined;
188
+ return recipe;
189
+ }),
190
+ );
191
+ return recipes.filter((recipe) => recipe !== undefined);
192
+ }
193
+
194
+ export const TAG_RECIPE_TYPE = 'emi:tag';
195
+
196
+ export async function resolveRecipeRows(
197
+ dataset: DatasetRef,
198
+ index: RecipeCompactIndex,
199
+ ids: Iterable<string>,
200
+ opts?: { includeTagRows?: boolean },
201
+ ): Promise<RecipeCompactRow[]> {
202
+ const includeTagRows = opts?.includeTagRows ?? true;
203
+ const wanted = new Set<string>();
204
+ const byBucket = new Map<string, Set<string>>();
205
+ for (const id of ids) {
206
+ if (!id || wanted.has(id)) continue;
207
+ wanted.add(id);
208
+ const rels = await clientRecipeFileRels(dataset, id);
209
+ for (const rel of rels) {
210
+ const set = byBucket.get(rel);
211
+ if (set) set.add(id);
212
+ else byBucket.set(rel, new Set([id]));
213
+ }
214
+ }
215
+ const out: RecipeCompactRow[] = [];
216
+ const seen = new Set<string>();
217
+ await Promise.all(
218
+ [...byBucket.entries()].map(async ([rel, idsInBucket]) => {
219
+ const bucket = await loadBucket(dataset, rel);
220
+ for (const id of idsInBucket) {
221
+ const raw = bucket[id];
222
+ const category = asString(asRecord(raw)?.category);
223
+ const typeIndex = category ? index.types.indexOf(category) : -1;
224
+ if (!includeTagRows && category === TAG_RECIPE_TYPE) continue;
225
+ const row = rowFromBucketEntry(
226
+ dataset,
227
+ id,
228
+ raw,
229
+ typeIndex < 0 ? 0 : typeIndex,
230
+ rel,
231
+ categoryLayout(index, category),
232
+ );
233
+ if (row && typeIndex < 0 && category) row.cat = category;
234
+ if (row && !seen.has(row.id)) {
235
+ seen.add(row.id);
236
+ out.push(row);
237
+ }
238
+ }
239
+ }),
240
+ );
241
+
242
+ const byBase = new Map<string, RecipeCompactRow>();
243
+ for (const row of out) {
244
+ const base = row.id.replace(/\/dup_\d+$/, '');
245
+ const key = `${row.type}:${base}`;
246
+ const prev = byBase.get(key);
247
+ if (
248
+ !prev ||
249
+ (/\/dup_\d+$/.test(prev.id) && !/\/dup_\d+$/.test(row.id))
250
+ ) {
251
+ byBase.set(key, row);
252
+ }
253
+ }
254
+ const deduped = [...byBase.values()];
255
+ deduped.sort(
256
+ (a, b) => (a.o ?? Number.MAX_SAFE_INTEGER) - (b.o ?? Number.MAX_SAFE_INTEGER),
257
+ );
258
+ return deduped;
259
+ }
260
+
@@ -0,0 +1,74 @@
1
+ import type { DatasetRef } from '@terrafirmagreg-team/sites/location';
2
+ import { clientDumpLang } from '@terrafirmagreg-team/dump/lib/dump/client/dump';
3
+ import { loadClientKeybinds } from '@terrafirmagreg-team/dump/lib/dump/client/keybinds';
4
+ import {
5
+ loadClientRegistryTooltips,
6
+ loadClientRegistryTooltipsMod,
7
+ } from '@terrafirmagreg-team/dump/lib/dump/client/registry';
8
+ import { dumpTextLines } from '@terrafirmagreg-team/dump/lib/dump/assets/lang/names';
9
+ import { createDatasetCache } from '@terrafirmagreg-team/dump/lib/dump/reader';
10
+
11
+ const TOOLTIP_MODS = ['shift', 'ctrl', 'alt'] as const;
12
+
13
+ const tooltipsApiCache = createDatasetCache(
14
+ async (
15
+ dataset: DatasetRef,
16
+ lang: string,
17
+ ): Promise<{
18
+ lines: Record<string, string[]>;
19
+ mods: Record<string, Record<string, string[]>>;
20
+ }> => {
21
+ const kinds = ['item', 'block', 'fluid', 'entity'] as const;
22
+ const [map, keybinds, shards, modShards] = await Promise.all([
23
+ clientDumpLang(dataset, lang),
24
+ loadClientKeybinds(dataset),
25
+ Promise.all(
26
+ kinds.map((kind) => loadClientRegistryTooltips(dataset, kind)),
27
+ ),
28
+ Promise.all(
29
+ kinds.flatMap((kind, kindIndex) =>
30
+ TOOLTIP_MODS.map(async (mod) => ({
31
+ kindIndex,
32
+ mod,
33
+ shard: await loadClientRegistryTooltipsMod(dataset, kind, mod),
34
+ })),
35
+ ),
36
+ ),
37
+ ]);
38
+ const lines: Record<string, string[]> = {};
39
+ const mods: Record<string, Record<string, string[]>> = {};
40
+ for (const shard of shards) {
41
+ for (const [id, raw] of Object.entries(shard ?? {})) {
42
+ if (id in lines) continue;
43
+ const resolved = dumpTextLines(map, raw, keybinds);
44
+ if (resolved.length) lines[id] = resolved;
45
+ }
46
+ }
47
+ for (const { mod, shard } of modShards) {
48
+ const table = (mods[mod] ??= {});
49
+ for (const [id, raw] of Object.entries(shard ?? {})) {
50
+ if (id in table) continue;
51
+ const resolved = dumpTextLines(map, raw, keybinds);
52
+ if (resolved.length) table[id] = resolved;
53
+ }
54
+ }
55
+ return { lines, mods };
56
+ },
57
+ );
58
+
59
+ const tooltipsCache = createDatasetCache(
60
+ async (
61
+ dataset: DatasetRef,
62
+ lang: string,
63
+ ): Promise<Record<string, string[]>> =>
64
+ (await tooltipsApiCache(dataset, lang)).lines,
65
+ (dataset, lang) => `${dataset.id}/${dataset.version ?? ''} ${lang}`,
66
+ );
67
+
68
+ export async function loadItemTooltips(
69
+ dataset: DatasetRef,
70
+ lang: string,
71
+ ): Promise<Record<string, string[]>> {
72
+ return tooltipsCache(dataset, lang);
73
+ }
74
+
@@ -0,0 +1,18 @@
1
+ import { createConfigRegistry, uiConfig } from '@terrafirmagreg-team/ui/lib/config';
2
+ import { registerMdxComponents } from '@terrafirmagreg-team/mdx/components/mdx-components';
3
+ import { Recipe } from '@terrafirmagreg-team/recipe/components/mdx/recipe';
4
+ import '@terrafirmagreg-team/recipe/lib/preview';
5
+
6
+ export type RecipeConfigInput = {
7
+ appName?: string;
8
+ };
9
+
10
+ const { register: registerRecipeConfig, get: recipeConfig } =
11
+ createConfigRegistry<RecipeConfigInput, { appName: string }>((overrides) => ({
12
+ appName: overrides.appName ?? uiConfig().brand?.name ?? '',
13
+ }));
14
+
15
+ export { registerRecipeConfig, recipeConfig };
16
+
17
+ registerMdxComponents({ Recipe });
18
+
@@ -0,0 +1,25 @@
1
+ import type { DumpDisplay } from '@terrafirmagreg-team/dump/lib/dump/contract/text';
2
+ import type { TaggedReference } from '@terrafirmagreg-team/dump/lib/dump/contract/ref';
3
+ import type {
4
+ RecipeCategory,
5
+ RecipeCategoryLayout,
6
+ RecipeLayoutSlot,
7
+ } from '@terrafirmagreg-team/dump/lib/dump/gen';
8
+
9
+ export type DumpLayoutSlot = RecipeLayoutSlot;
10
+
11
+ export type DumpRecipeLayout = Omit<RecipeCategoryLayout, 'slots'> & {
12
+ slots: DumpLayoutSlot[];
13
+ };
14
+
15
+ export type DumpRecipeCategory = Omit<
16
+ RecipeCategory,
17
+ 'display' | 'icon' | 'layout' | 'workstations'
18
+ > & {
19
+ id: string;
20
+ display?: DumpDisplay;
21
+ icon?: TaggedReference;
22
+ workstations?: TaggedReference[];
23
+ layout?: DumpRecipeLayout;
24
+ };
25
+
@@ -0,0 +1,47 @@
1
+ import type { DatasetRef } from '@terrafirmagreg-team/sites/location';
2
+ import { asCategory } from '@terrafirmagreg-team/recipe/lib/data/category/parse';
3
+ import type { DumpRecipeCategory } from '@terrafirmagreg-team/recipe/lib/data/category/dto';
4
+ import { loadDatasetDump } from '@terrafirmagreg-team/dump/lib/dump/mods/load';
5
+ import { clientDumpJson } from '@terrafirmagreg-team/dump/lib/dump/client/dump';
6
+ import { asRecord } from '@terrafirmagreg-team/dump/lib/dump/contract/read';
7
+ import { createDatasetCache } from '@terrafirmagreg-team/dump/lib/dump/reader';
8
+
9
+ const cache = createDatasetCache(
10
+ async (dataset: DatasetRef): Promise<DumpRecipeCategory[]> => {
11
+ const out: DumpRecipeCategory[] = [];
12
+ const dump = await loadDatasetDump(dataset);
13
+ if (dump) {
14
+ const seen = new Set<string>();
15
+ const rows = await Promise.all(
16
+ dump.mods.flatMap((ns) => {
17
+ if (!ns.recipeCategories || seen.has(ns.recipeCategories)) {
18
+ return [];
19
+ }
20
+ seen.add(ns.recipeCategories);
21
+ return [clientDumpJson(dataset, ns.recipeCategories)];
22
+ }),
23
+ );
24
+ for (const row of rows) {
25
+ const entries = asRecord(row);
26
+ if (!entries) continue;
27
+ for (const [catalogKey, raw] of Object.entries(entries)) {
28
+ const cat = asCategory(
29
+ raw && typeof raw === 'object' && !Array.isArray(raw) && !('id' in raw)
30
+ ? { ...raw, id: catalogKey }
31
+ : raw,
32
+ );
33
+ if (cat) out.push(cat);
34
+ }
35
+ }
36
+ }
37
+ out.sort((a, b) => a.order - b.order || a.id.localeCompare(b.id));
38
+ return out;
39
+ },
40
+ );
41
+
42
+ export function loadDatasetRecipeCategories(
43
+ dataset: DatasetRef,
44
+ ): Promise<DumpRecipeCategory[]> {
45
+ return cache(dataset);
46
+ }
47
+
@@ -0,0 +1,71 @@
1
+ import { asNumber, asRecord, asString } from '@terrafirmagreg-team/dump/lib/dump/contract/read';
2
+ import { asTaggedReference } from '@terrafirmagreg-team/dump/lib/dump/contract/ref';
3
+ import { asDisplay } from '@terrafirmagreg-team/dump/lib/dump/contract/text';
4
+ import type {
5
+ DumpLayoutSlot,
6
+ DumpRecipeCategory,
7
+ DumpRecipeLayout,
8
+ } from '@terrafirmagreg-team/recipe/lib/data/category/dto';
9
+
10
+ function asLayoutSlot(value: unknown, key: string): DumpLayoutSlot | undefined {
11
+ const row = asRecord(value);
12
+ if (!row) return undefined;
13
+ const id = asString(row.id) ?? key;
14
+ if (!id) return undefined;
15
+ const x = asNumber(row.x) ?? 0;
16
+ const y = asNumber(row.y) ?? 0;
17
+ const w = asNumber(row.w) ?? 18;
18
+ const h = asNumber(row.h) ?? 18;
19
+ const role = asString(row.role) ?? 'input';
20
+ return { id, x, y, w, h, role };
21
+ }
22
+
23
+ function asLayout(value: unknown): DumpRecipeLayout | undefined {
24
+ const row = asRecord(value);
25
+ if (!row) return undefined;
26
+ const w = asNumber(row.w);
27
+ const h = asNumber(row.h);
28
+ if (w === undefined || h === undefined) return undefined;
29
+ const scale = asNumber(row.scale);
30
+ const margin = asNumber(row.margin);
31
+ const image = asString(row.image);
32
+ const slotsRow = asRecord(row.slots);
33
+ const slots = Object.entries(slotsRow ?? {}).flatMap(([key, item]) => {
34
+ const slot = asLayoutSlot(item, key);
35
+ return slot ? [slot] : [];
36
+ });
37
+ return {
38
+ w,
39
+ h,
40
+ ...(scale !== undefined ? { scale } : {}),
41
+ ...(margin !== undefined ? { margin } : {}),
42
+ ...(image ? { image } : {}),
43
+ slots,
44
+ };
45
+ }
46
+
47
+ export function asCategory(value: unknown): DumpRecipeCategory | undefined {
48
+ const row = asRecord(value);
49
+ if (!row) return undefined;
50
+ const id = asString(row.id);
51
+ if (!id) return undefined;
52
+ const display = asDisplay(row.display);
53
+ const order = asNumber(row.order) ?? 0;
54
+ const icon = asTaggedReference(row.icon);
55
+ const workstations = Array.isArray(row.workstations)
56
+ ? row.workstations.flatMap((item) => {
57
+ const ref = asTaggedReference(item);
58
+ return ref ? [ref] : [];
59
+ })
60
+ : undefined;
61
+ const layout = asLayout(row.layout);
62
+ return {
63
+ id,
64
+ order,
65
+ ...(display ? { display } : {}),
66
+ ...(icon ? { icon } : {}),
67
+ ...(workstations && workstations.length > 0 ? { workstations } : {}),
68
+ ...(layout ? { layout } : {}),
69
+ };
70
+ }
71
+