@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,60 @@
1
+ 'use client';
2
+
3
+ import { Check, Copy } from 'lucide-react';
4
+ import { useTranslations } from '@terrafirmagreg-team/translate/hooks/use-translations';
5
+ import { ItemRow } from '@terrafirmagreg-team/ui/components/blocks/list/row';
6
+ import { Entry } from '@terrafirmagreg-team/game/components/entry/entry';
7
+ import { CopyIdMenu } from '@terrafirmagreg-team/game/components/entry/copy-id-menu';
8
+ import { ButtonCopy } from '@terrafirmagreg-team/recipe/components/button-copy';
9
+ import { recipeItemName } from '@terrafirmagreg-team/recipe/lib/client-index';
10
+ import { parseStackId } from '@terrafirmagreg-team/dump/lib/dump/contract/ref';
11
+ import { formatNbt } from '@terrafirmagreg-team/dump/lib/snbt';
12
+ import type { Recipe } from '@terrafirmagreg-team/recipe/lib/data/dto';
13
+ import type { RecipeSession } from '@terrafirmagreg-team/recipe/types';
14
+
15
+ export function RecipeItemHeader({
16
+ recipe,
17
+ session,
18
+ }: {
19
+ recipe: Recipe;
20
+ session: RecipeSession;
21
+ }) {
22
+ const t = useTranslations();
23
+ const output = recipe.outputs[0];
24
+ const stackId = output?.id ?? recipe.id;
25
+ const parsed = parseStackId(stackId);
26
+ const name = recipeItemName(session.names, session.lang, parsed.id);
27
+ const nbt = formatNbt(parsed.nbt);
28
+ return (
29
+ <ItemRow
30
+ layout="header"
31
+ media={
32
+ <CopyIdMenu id={parsed.id} nbt={nbt}>
33
+ <Entry
34
+ id={stackId}
35
+ kind={output?.kind === 'fluid' ? 'fluid' : 'item'}
36
+ name={name}
37
+ size="lg"
38
+ variant="chip"
39
+ href={false}
40
+ />
41
+ </CopyIdMenu>
42
+ }
43
+ title={name}
44
+ titleClassName="truncate"
45
+ code={
46
+ <span className="inline-flex max-w-full items-center gap-0.5">
47
+ <span className="truncate">{recipe.id}</span>
48
+ <ButtonCopy
49
+ value={recipe.id}
50
+ title={t('Copy recipe id', { note: 'recipe.copy_recipe_id' })}
51
+ aria-label={t('Copy recipe id', { note: 'recipe.copy_recipe_id' })}
52
+ copy={<Copy className="size-3" />}
53
+ copied={<Check className="size-3" />}
54
+ />
55
+ </span>
56
+ }
57
+ />
58
+ );
59
+ }
60
+
@@ -0,0 +1,52 @@
1
+ 'use client';
2
+
3
+ import { Settings2 } from 'lucide-react';
4
+ import { useTranslations } from '@terrafirmagreg-team/translate/hooks/use-translations';
5
+ import { useHideUnused } from '@terrafirmagreg-team/recipe/hooks/use-hide-unused';
6
+ import { useSidebarGroup } from '@terrafirmagreg-team/recipe/hooks/use-sidebar-group';
7
+ import { Button } from '@terrafirmagreg-team/ui/components/ui/button';
8
+ import { Label } from '@terrafirmagreg-team/ui/components/ui/label';
9
+ import { Switch } from '@terrafirmagreg-team/ui/components/ui/switch';
10
+ import {
11
+ Popover,
12
+ PopoverContent,
13
+ PopoverTrigger,
14
+ } from '@terrafirmagreg-team/ui/components/ui/popover';
15
+
16
+ export function RecipeSettings() {
17
+ const t = useTranslations();
18
+ const { hideUnused, setHideUnused } = useHideUnused();
19
+ const { sidebarGroup, setSidebarGroup } = useSidebarGroup();
20
+
21
+ return (
22
+ <Popover>
23
+ <PopoverTrigger
24
+ render={
25
+ <Button
26
+ variant="outline"
27
+ size="icon-sm"
28
+ aria-label={t('Settings', { note: 'recipe.settings' })}
29
+ className="rounded-lg"
30
+ />
31
+ }
32
+ >
33
+ <Settings2 className="size-4" />
34
+ </PopoverTrigger>
35
+ <PopoverContent align="start" className="flex w-56 flex-col gap-2">
36
+ <Label className="gap-1.5 text-sm font-normal">
37
+ <Switch size="sm" checked={hideUnused} onCheckedChange={setHideUnused} />
38
+ {t('Hide unused', { note: 'recipe.hide_unused' })}
39
+ </Label>
40
+ <Label className="gap-1.5 text-sm font-normal">
41
+ <Switch
42
+ size="sm"
43
+ checked={sidebarGroup === 'mod'}
44
+ onCheckedChange={(value) => setSidebarGroup(value ? 'mod' : 'flat')}
45
+ />
46
+ {t('Group sidebar by mod', { note: 'recipe.sidebar_group_mod' })}
47
+ </Label>
48
+ </PopoverContent>
49
+ </Popover>
50
+ );
51
+ }
52
+
@@ -0,0 +1,54 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import { LayoutGrid } from 'lucide-react';
5
+ import { useTranslations } from '@terrafirmagreg-team/translate/hooks/use-translations';
6
+ import { Button } from '@terrafirmagreg-team/ui/components/ui/button';
7
+ import {
8
+ Sheet,
9
+ SheetContent,
10
+ SheetHeader,
11
+ SheetTitle,
12
+ } from '@terrafirmagreg-team/ui/components/ui/sheet';
13
+ import { ItemPanel } from '@terrafirmagreg-team/recipe/components/views/browser/item-panel';
14
+ import type { RecipeSession } from '@terrafirmagreg-team/recipe/types';
15
+
16
+ export function RecipeToolbar({ session }: { session: RecipeSession }) {
17
+ const t = useTranslations();
18
+ const [itemsOpen, setItemsOpen] = useState(false);
19
+
20
+ return (
21
+ <div className="flex items-center gap-2">
22
+ <Button
23
+ variant="outline"
24
+ size="sm"
25
+ className="lg:hidden"
26
+ onClick={() => setItemsOpen(true)}
27
+ >
28
+ <LayoutGrid className="size-3.5" />
29
+ {t('Items', { note: 'recipe.items' })}
30
+ </Button>
31
+ <Sheet open={itemsOpen} onOpenChange={setItemsOpen}>
32
+ <SheetContent side="right" className="w-full gap-3 sm:max-w-md">
33
+ <SheetHeader>
34
+ <SheetTitle>{t('Items', { note: 'recipe.items' })}</SheetTitle>
35
+ </SheetHeader>
36
+ <ItemPanel
37
+ dataset={session.dataset}
38
+ lang={session.lang}
39
+ index={session.index}
40
+ indexLoading={session.indexLoading}
41
+ names={session.names}
42
+ tab={session.tab}
43
+ query={session.query}
44
+ setQuery={session.setQuery}
45
+ isCalc={session.place?.kind === 'calc'}
46
+ nav={session.nav}
47
+ className="min-h-0 w-full flex-1 px-4 pb-4"
48
+ />
49
+ </SheetContent>
50
+ </Sheet>
51
+ </div>
52
+ );
53
+ }
54
+
@@ -0,0 +1,71 @@
1
+ 'use client';
2
+ import type { DatasetRef } from '@terrafirmagreg-team/sites/location';
3
+
4
+ import { useMemo } from 'react';
5
+ import { Shapes } from 'lucide-react';
6
+ import { Entry } from '@terrafirmagreg-team/game/components/entry/entry';
7
+ import { recipeItemName } from '@terrafirmagreg-team/recipe/lib/client-index';
8
+ import { IconPager } from '@terrafirmagreg-team/ui/components/blocks/page/pager';
9
+ import type { RecipeCategory, RecipeSession } from '@terrafirmagreg-team/recipe/types';
10
+
11
+ function CategoryGlyph({
12
+ category,
13
+ dataset,
14
+ }: {
15
+ category?: RecipeCategory;
16
+ dataset: DatasetRef;
17
+ }) {
18
+ return category?.icon ? (
19
+ <Entry
20
+ dataset={dataset}
21
+ reference={category.icon}
22
+ name={category.id}
23
+ size="lg"
24
+ href={false}
25
+ tooltip={false}
26
+ missing="hide"
27
+ />
28
+ ) : (
29
+ <Shapes className="size-8" />
30
+ );
31
+ }
32
+
33
+ export function RecipeTypeTabs({
34
+ session,
35
+ ids,
36
+ value,
37
+ onChange,
38
+ className,
39
+ }: {
40
+ session: Pick<RecipeSession, 'lang' | 'names' | 'index' | 'dataset'>;
41
+ ids: string[];
42
+ value?: string;
43
+ onChange?: (id: string) => void;
44
+ className?: string;
45
+ }) {
46
+ const { lang, names, index, dataset } = session;
47
+
48
+ const options = useMemo(() => {
49
+ const byId = new Map(
50
+ (index?.categories ?? []).map((row) => [row.id, row]),
51
+ );
52
+ return ids.map((id) => {
53
+ const category = byId.get(id);
54
+ return {
55
+ id,
56
+ icon: <CategoryGlyph category={category} dataset={dataset} />,
57
+ help: recipeItemName(names, lang, id),
58
+ };
59
+ });
60
+ }, [index, ids, lang, names, dataset]);
61
+
62
+ return (
63
+ <IconPager
64
+ value={value}
65
+ options={options}
66
+ onChange={onChange ?? (() => {})}
67
+ className={className}
68
+ />
69
+ );
70
+ }
71
+
@@ -0,0 +1,184 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useRef } from 'react';
4
+ import { useTranslations } from '@terrafirmagreg-team/translate/hooks/use-translations';
5
+ import { useAsyncResult, useAsyncValue } from '@terrafirmagreg-team/ui/hooks/use-async-value';
6
+ import { RecipeFrame } from '@terrafirmagreg-team/recipe/components/views/browser/frame';
7
+ import {
8
+ RecipeCardGrid,
9
+ recipeCardDim,
10
+ } from '@terrafirmagreg-team/recipe/components/views/browser/card-grid';
11
+ import { PlaceHeader } from '@terrafirmagreg-team/recipe/components/views/browser/place-header';
12
+ import { useCategoriesById } from '@terrafirmagreg-team/recipe/hooks/use-categories-by-id';
13
+ import { useFitPages } from '@terrafirmagreg-team/ui/hooks/page/use-fit-pages';
14
+ import {
15
+ loadTypeRows,
16
+ nonEmptyCategoryIds,
17
+ resolveRecipeRows,
18
+ } from '@terrafirmagreg-team/recipe/lib/client-index';
19
+ import { RECIPE_ALL_TYPE, recipeHref } from '@terrafirmagreg-team/recipe/lib/links';
20
+ import { RecipeTypeTabs } from '@terrafirmagreg-team/recipe/components/views/browser/type-tabs';
21
+ import { WorkstationRail } from '@terrafirmagreg-team/recipe/components/views/browser/workstation-rail';
22
+ import { replaceUrlState } from '@terrafirmagreg-team/ui/lib/utils/history';
23
+ import type {
24
+ RecipeCompactRow,
25
+ RecipeSession,
26
+ } from '@terrafirmagreg-team/recipe/types';
27
+
28
+ const EMPTY_ROWS: RecipeCompactRow[] = [];
29
+
30
+ export function TypeView({ session }: { session: RecipeSession }) {
31
+ const t = useTranslations();
32
+ const {
33
+ dataset,
34
+ lang,
35
+ index,
36
+ indexLoading,
37
+ place,
38
+ nav,
39
+ recipeId,
40
+ rpage,
41
+ } = session;
42
+ const boxRef = useRef<HTMLDivElement | null>(null);
43
+
44
+ const placeId = place?.kind === 'type' ? place.id : RECIPE_ALL_TYPE;
45
+
46
+ const categoriesById = useCategoriesById(index);
47
+ const nonEmptyIds = nonEmptyCategoryIds(index);
48
+
49
+ const shown =
50
+ placeId !== RECIPE_ALL_TYPE && nonEmptyIds.includes(placeId)
51
+ ? placeId
52
+ : nonEmptyIds[0];
53
+ const rows = useAsyncValue(
54
+ () =>
55
+ index && shown
56
+ ? loadTypeRows(dataset, index, shown)
57
+ : Promise.resolve(EMPTY_ROWS),
58
+ [dataset, index, shown],
59
+ );
60
+
61
+ const allRows = rows ?? EMPTY_ROWS;
62
+ const focusIndex = recipeId
63
+ ? allRows.findIndex((row) => row.id === recipeId)
64
+ : -1;
65
+ const focusRes = useAsyncResult(
66
+ () =>
67
+ recipeId && index && rows !== undefined && focusIndex < 0
68
+ ? resolveRecipeRows(dataset, index, [recipeId])
69
+ : Promise.resolve(undefined),
70
+ [dataset, index, recipeId, rows, focusIndex],
71
+ );
72
+ const solo = recipeId != null && session.solo === '1';
73
+ const feedRows = solo
74
+ ? focusIndex >= 0
75
+ ? [allRows[focusIndex]]
76
+ : (focusRes.value ?? EMPTY_ROWS)
77
+ : allRows;
78
+ const feedFocus = solo && focusIndex >= 0 ? 0 : focusIndex;
79
+ useEffect(() => {
80
+ if (!recipeId || !index || rows === undefined || focusIndex >= 0) return;
81
+ if (focusRes.pending) return;
82
+ const row = focusRes.value?.[0];
83
+ const category = row ? index.types[row.type] : undefined;
84
+ replaceUrlState(
85
+ recipeHref(dataset, lang, {
86
+ ...nav,
87
+ ...(category && category !== shown ? { type: category } : {}),
88
+ ...(category ? {} : { id: undefined }),
89
+ rpage: undefined,
90
+ }),
91
+ );
92
+ }, [focusIndex, focusRes.pending, focusRes.value, index, nav, dataset, lang, recipeId, rows, shown]);
93
+ const list = useFitPages(
94
+ boxRef,
95
+ feedRows,
96
+ (row, box) => {
97
+ const d = recipeCardDim(index, categoriesById, row);
98
+ return d.h > box.h ? { w: d.w, h: box.h } : d;
99
+ },
100
+ rpage,
101
+ 8,
102
+ feedFocus >= 0 ? feedFocus : undefined,
103
+ true,
104
+ );
105
+
106
+ const onSwipe = (dir: -1 | 1) => {
107
+ if (recipeId) {
108
+ const next = focusIndex + dir;
109
+ const row = allRows[next];
110
+ if (focusIndex < 0 || !row) return;
111
+ replaceUrlState(
112
+ recipeHref(dataset, lang, { ...nav, id: row.id, rpage: undefined }),
113
+ );
114
+ return;
115
+ }
116
+ if (list.pages <= 1) return;
117
+ replaceUrlState(
118
+ recipeHref(dataset, lang, {
119
+ ...nav,
120
+ rpage: (list.page + dir + list.pages) % list.pages,
121
+ }),
122
+ );
123
+ };
124
+
125
+ const body = index && !index.types.length && !indexLoading ? (
126
+ <RecipeCardGrid
127
+ session={session}
128
+ rows={EMPTY_ROWS}
129
+ boxRef={boxRef}
130
+ empty={t('No recipe dump for this dataset. Run export to generate data.', { note: 'recipe.no_dump' })}
131
+ />
132
+ ) : (
133
+ <RecipeCardGrid
134
+ session={session}
135
+ rows={list.shown}
136
+ boxRef={boxRef}
137
+ maxH={list.box.h}
138
+ maxW={list.box.w}
139
+ loading={index == null || (shown != null && rows === undefined)}
140
+ empty={t('No recipes of this type.', { note: 'recipe.empty_type' })}
141
+ onSwipe={onSwipe}
142
+ />
143
+ );
144
+
145
+ return (
146
+ <RecipeFrame
147
+ session={session}
148
+ list={list}
149
+ strip={
150
+ <RecipeTypeTabs
151
+ session={session}
152
+ ids={nonEmptyIds}
153
+ value={shown}
154
+ onChange={(id) => {
155
+ if (id === shown) return;
156
+ replaceUrlState(
157
+ recipeHref(dataset, lang, {
158
+ ...nav,
159
+ type: id,
160
+ id: undefined,
161
+ solo: undefined,
162
+ rpage: undefined,
163
+ }),
164
+ );
165
+ }}
166
+ />
167
+ }
168
+ header={
169
+ <PlaceHeader session={session} overline={t('Categories', { note: 'recipe.axis_type' })} />
170
+ }
171
+ rail={
172
+ <WorkstationRail
173
+ session={session}
174
+ workstations={
175
+ shown ? categoriesById.get(shown)?.workstations : undefined
176
+ }
177
+ />
178
+ }
179
+ >
180
+ {body}
181
+ </RecipeFrame>
182
+ );
183
+ }
184
+
@@ -0,0 +1,43 @@
1
+ 'use client';
2
+
3
+ import { Factory } from 'lucide-react';
4
+ import { Entry } from '@terrafirmagreg-team/game/components/entry/entry';
5
+ import { recipeItemName } from '@terrafirmagreg-team/recipe/lib/client-index';
6
+ import { stackIdFromRef } from '@terrafirmagreg-team/dump/lib/dump/contract/ref';
7
+ import type { TaggedReference } from '@terrafirmagreg-team/dump/lib/dump/contract/ref';
8
+ import type { RecipeSession } from '@terrafirmagreg-team/recipe/types';
9
+
10
+ export function WorkstationRail({
11
+ session,
12
+ workstations,
13
+ }: {
14
+ session: RecipeSession;
15
+ workstations: TaggedReference[] | undefined;
16
+ }) {
17
+ if (!workstations?.length) return null;
18
+ const { lang, names, nav } = session;
19
+ return (
20
+ <div className="scrollbar-thin flex max-h-full flex-col items-center gap-1 self-start overflow-y-auto overflow-x-hidden rounded-lg border border-border bg-muted/60 p-1">
21
+ <Factory
22
+ className="m-1 size-3.5 shrink-0 text-muted-foreground"
23
+ aria-hidden
24
+ />
25
+ {workstations.map((ref) => {
26
+ const id = stackIdFromRef(ref);
27
+ return (
28
+ <Entry
29
+ key={id}
30
+ reference={ref}
31
+ name={recipeItemName(names, lang, id)}
32
+ variant="chip"
33
+ size="lg"
34
+ className="size-9 shrink-0"
35
+ tab={nav.tab}
36
+ search={nav.search}
37
+ />
38
+ );
39
+ })}
40
+ </div>
41
+ );
42
+ }
43
+
@@ -0,0 +1,47 @@
1
+ 'use client';
2
+
3
+ import { Share2, Trash2 } from 'lucide-react';
4
+ import { IconButton } from '@terrafirmagreg-team/ui/components/blocks/button/icon-button';
5
+ import { useTranslations } from '@terrafirmagreg-team/translate/hooks/use-translations';
6
+ import { RecipeToolbar } from '@terrafirmagreg-team/recipe/components/views/browser/toolbar';
7
+ import type { RecipeSession } from '@terrafirmagreg-team/recipe/types';
8
+ import { CalcHelp } from './help';
9
+
10
+ export function CalcActions({
11
+ session,
12
+ shared,
13
+ onShare,
14
+ hasTargets,
15
+ onClear,
16
+ }: {
17
+ session: RecipeSession;
18
+ shared: boolean;
19
+ onShare: () => void;
20
+ hasTargets: boolean;
21
+ onClear: () => void;
22
+ }) {
23
+ const t = useTranslations();
24
+ return (
25
+ <>
26
+ <RecipeToolbar session={session} />
27
+ <IconButton
28
+ overlay
29
+ label={shared ? t('Copied', { note: 'recipe.copied' }) : t('Share', { note: 'recipe.calc.share' })}
30
+ onClick={onShare}
31
+ >
32
+ <Share2 className="size-3.5" />
33
+ </IconButton>
34
+ <CalcHelp />
35
+ {hasTargets ? (
36
+ <IconButton
37
+ overlay
38
+ label={t('Clear', { note: 'recipe.calc.clear.title' })}
39
+ onClick={onClear}
40
+ >
41
+ <Trash2 className="size-3.5" />
42
+ </IconButton>
43
+ ) : null}
44
+ </>
45
+ );
46
+ }
47
+