@terrafirmagreg-team/recipe 0.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (240) hide show
  1. package/package.json +16 -16
  2. package/src/components/button-copy.tsx +1 -0
  3. package/src/components/cards/card/index.tsx +5 -3
  4. package/src/components/cards/card/scene.tsx +4 -2
  5. package/src/components/cards/card/strip.tsx +4 -2
  6. package/src/components/cards/card/text.tsx +2 -1
  7. package/src/components/cards/card/tips.tsx +1 -0
  8. package/src/components/cards/default-item.tsx +6 -2
  9. package/src/components/cards/glare-filter.tsx +1 -0
  10. package/src/components/cards/paged-card.tsx +1 -0
  11. package/src/components/cards/slot-hitbox.tsx +4 -2
  12. package/src/components/cards/strip-card/buttons.tsx +7 -2
  13. package/src/components/cards/strip-card/index.tsx +3 -1
  14. package/src/components/cards/strip-card/member-cell.tsx +4 -2
  15. package/src/components/cards/strip-card/sprite.tsx +1 -0
  16. package/src/components/embed-card.tsx +44 -0
  17. package/src/components/mdx/recipe.tsx +34 -16
  18. package/src/components/tool-list.tsx +1 -0
  19. package/src/{lib/tree.ts → components/tree.tsx} +20 -12
  20. package/src/components/views/alloy/index.tsx +14 -7
  21. package/src/components/views/alloy/list.tsx +3 -2
  22. package/src/components/views/alloy/mix/index.tsx +1 -0
  23. package/src/components/views/alloy/mix/ratio.tsx +7 -6
  24. package/src/components/views/alloy/mix/rows.tsx +6 -7
  25. package/src/components/views/alloy/mix/status.tsx +8 -7
  26. package/src/components/views/alloy/mix/total.tsx +13 -12
  27. package/src/components/views/alloy/range-bar.tsx +2 -1
  28. package/src/components/views/alloy/stack-bar.tsx +4 -2
  29. package/src/components/views/browser/axis-switcher.tsx +13 -12
  30. package/src/components/views/browser/card-grid.tsx +2 -1
  31. package/src/components/views/browser/favorite-button.tsx +2 -1
  32. package/src/components/views/browser/frame.tsx +3 -2
  33. package/src/components/views/browser/index.tsx +20 -2
  34. package/src/components/views/browser/item-panel/index.tsx +11 -9
  35. package/src/components/views/browser/item-panel/skeleton.tsx +1 -0
  36. package/src/components/views/browser/item-panel-grid/cell.tsx +5 -3
  37. package/src/components/views/browser/item-panel-grid/favorite-cell.tsx +4 -2
  38. package/src/components/views/browser/item-panel-grid/index.tsx +4 -2
  39. package/src/components/views/browser/item-view/index.tsx +7 -12
  40. package/src/components/views/browser/item-view/tags.tsx +7 -5
  41. package/src/components/views/browser/item-view-header.tsx +10 -9
  42. package/src/components/views/browser/pane-tabs.tsx +1 -0
  43. package/src/components/views/browser/place-header.tsx +4 -3
  44. package/src/components/views/browser/query-controls.tsx +3 -1
  45. package/src/components/views/browser/recipe-item-header.tsx +5 -4
  46. package/src/components/views/browser/settings.tsx +4 -3
  47. package/src/components/views/browser/toolbar.tsx +3 -2
  48. package/src/components/views/browser/type-tabs.tsx +5 -3
  49. package/src/components/views/browser/type-view.tsx +7 -12
  50. package/src/components/views/browser/workstation-rail.tsx +4 -3
  51. package/src/components/views/calc/calculator/actions.tsx +4 -14
  52. package/src/components/views/calc/calculator/help.tsx +69 -28
  53. package/src/components/views/calc/calculator/index.tsx +34 -82
  54. package/src/components/views/calc/dot-overlay.tsx +1 -0
  55. package/src/components/views/calc/edge.tsx +134 -32
  56. package/src/components/views/calc/flow/index.tsx +92 -54
  57. package/src/components/views/calc/flow/picker-dialogs.tsx +84 -6
  58. package/src/components/views/calc/flow/toolbar.tsx +9 -21
  59. package/src/components/views/calc/flow/view-menu.tsx +16 -120
  60. package/src/components/views/calc/member-picker.tsx +8 -6
  61. package/src/components/views/calc/node-card/actions.tsx +28 -106
  62. package/src/components/views/calc/node-card/badges.tsx +10 -28
  63. package/src/components/views/calc/node-card/chips.tsx +8 -40
  64. package/src/components/views/calc/node-card/far.tsx +13 -65
  65. package/src/components/views/calc/node-card/full.tsx +133 -83
  66. package/src/components/views/calc/node-card/index.tsx +1 -0
  67. package/src/components/views/calc/node-card/menu.tsx +93 -22
  68. package/src/components/views/calc/node-card/port-cells.tsx +186 -0
  69. package/src/components/views/calc/node-card/port-rails.tsx +251 -0
  70. package/src/components/views/calc/node-card/ports.tsx +86 -0
  71. package/src/components/views/calc/recipe-picker/body.tsx +17 -14
  72. package/src/components/views/calc/recipe-picker/footer.tsx +5 -3
  73. package/src/components/views/calc/recipe-picker/index.tsx +5 -0
  74. package/src/components/views/calc/sidebar.tsx +19 -10
  75. package/src/components/views/calc/trees/icon-picker.tsx +3 -2
  76. package/src/components/views/calc/trees/index.tsx +5 -4
  77. package/src/components/views/calc/trees/label.tsx +6 -4
  78. package/src/components/views/calc/trees/rows.tsx +6 -5
  79. package/src/components/views/forge/gui.tsx +2 -1
  80. package/src/components/views/forge/index.tsx +14 -7
  81. package/src/components/views/forge/list.tsx +3 -2
  82. package/src/components/views/forge/workbench/controls.tsx +13 -6
  83. package/src/components/views/forge/workbench/index.tsx +3 -2
  84. package/src/components/views/forge/workbench/panel.tsx +1 -0
  85. package/src/components/views/forge/workbench/ruler.tsx +5 -4
  86. package/src/components/views/forge/workbench/status.tsx +6 -5
  87. package/src/components/views/forge/workbench/tape.tsx +5 -4
  88. package/src/hooks/alloy/use-fluid-colors.ts +12 -9
  89. package/src/hooks/alloy/use-mix-state.ts +3 -2
  90. package/src/hooks/calc/flow/use-engage.ts +129 -0
  91. package/src/hooks/calc/flow/use-handlers.ts +192 -0
  92. package/src/hooks/calc/flow/use-layout.ts +105 -0
  93. package/src/hooks/calc/flow/{mount.ts → use-mount.ts} +1 -0
  94. package/src/hooks/calc/flow/{nodes.ts → use-nodes.ts} +46 -37
  95. package/src/hooks/calc/flow/use-pickers.ts +57 -0
  96. package/src/hooks/calc/flow/{viewport.ts → use-viewport.ts} +13 -26
  97. package/src/hooks/calc/flow/{window.ts → use-window.ts} +1 -0
  98. package/src/hooks/calc/use-actions.ts +208 -229
  99. package/src/hooks/calc/use-amounts.ts +23 -60
  100. package/src/hooks/calc/use-data.ts +106 -192
  101. package/src/hooks/calc/use-defaults.ts +4 -2
  102. package/src/hooks/calc/use-far-mode.ts +1 -0
  103. package/src/hooks/calc/use-focus.ts +8 -7
  104. package/src/hooks/calc/use-maps.ts +1 -0
  105. package/src/hooks/calc/use-reset.ts +1 -0
  106. package/src/hooks/calc/use-resolver.ts +32 -196
  107. package/src/hooks/calc/use-session.ts +1 -0
  108. package/src/hooks/calc/use-share.ts +5 -2
  109. package/src/hooks/calc/use-storage.ts +5 -4
  110. package/src/hooks/calc/use-tier.ts +1 -0
  111. package/src/hooks/calc/use-view-mode.ts +1 -0
  112. package/src/hooks/calc/use-worker.ts +56 -121
  113. package/src/hooks/forge/use-anvil-gui.ts +4 -2
  114. package/src/hooks/forge/use-forge-state.ts +1 -0
  115. package/src/hooks/forge/use-ruler.ts +1 -0
  116. package/src/hooks/use-add-to-calc.ts +23 -19
  117. package/src/hooks/use-categories-by-id.ts +18 -0
  118. package/src/hooks/use-favorite-hotkey.ts +1 -0
  119. package/src/hooks/use-favorites.ts +1 -0
  120. package/src/hooks/use-group-outlines.ts +1 -0
  121. package/src/hooks/use-hide-unused.ts +2 -1
  122. package/src/hooks/use-index.ts +6 -2
  123. package/src/hooks/use-item-feeds.ts +3 -1
  124. package/src/hooks/use-item-pane.ts +6 -4
  125. package/src/hooks/use-item-press.ts +27 -23
  126. package/src/hooks/use-item-tag-groups.ts +4 -2
  127. package/src/hooks/use-item-tooltips.ts +3 -1
  128. package/src/hooks/use-left-sidebar-tree.ts +11 -3
  129. package/src/hooks/use-mod-names.ts +4 -2
  130. package/src/hooks/use-mount-limit.ts +1 -0
  131. package/src/hooks/use-panel-cells.ts +3 -1
  132. package/src/hooks/use-panel-page.ts +9 -6
  133. package/src/hooks/use-panel-tabs.ts +2 -1
  134. package/src/hooks/use-query.ts +8 -6
  135. package/src/hooks/use-session.ts +6 -2
  136. package/src/hooks/use-sidebar-group.ts +3 -7
  137. package/src/hooks/use-tag-members.ts +3 -1
  138. package/src/hooks/use-tool-recipes.ts +1 -0
  139. package/src/hooks/use-type-filter.ts +1 -0
  140. package/src/lib/alloy/mix.ts +1 -0
  141. package/src/lib/alloy/parse.ts +1 -0
  142. package/src/lib/calc/defaults.ts +6 -4
  143. package/src/lib/calc/flow.ts +143 -107
  144. package/src/lib/calc/focus.ts +1 -0
  145. package/src/lib/calc/meta.ts +3 -1
  146. package/src/lib/calc/pack.ts +47 -154
  147. package/src/lib/calc/ports.ts +43 -0
  148. package/src/lib/calc/resolve.ts +6 -38
  149. package/src/lib/calc/route/buckets.ts +1 -0
  150. package/src/lib/calc/route/geometry.ts +13 -24
  151. package/src/lib/calc/route/index.ts +1 -0
  152. package/src/lib/calc/route/polyline.ts +3 -3
  153. package/src/lib/calc/route/router.ts +181 -57
  154. package/src/lib/calc/scheme.ts +226 -0
  155. package/src/lib/calc/storage/entry.ts +27 -19
  156. package/src/lib/calc/storage/index.ts +2 -0
  157. package/src/lib/calc/storage/keys.ts +4 -8
  158. package/src/lib/calc/storage/store.ts +11 -36
  159. package/src/lib/calc/tree/index.ts +2 -16
  160. package/src/lib/calc/tree/materials.ts +1 -0
  161. package/src/lib/calc/tree/recipe.ts +1 -0
  162. package/src/lib/calc/tree/share.ts +86 -149
  163. package/src/lib/calc/worker/api.ts +3 -11
  164. package/src/lib/calc/worker/fetch.ts +1 -0
  165. package/src/lib/calc/worker/flush.ts +7 -20
  166. package/src/lib/calc/worker/index.ts +1 -1
  167. package/src/lib/calc/worker/protocol.ts +16 -13
  168. package/src/lib/calc/worker/resolve.ts +32 -84
  169. package/src/lib/calc/worker/state.ts +5 -6
  170. package/src/{components/cards/card/model.ts → lib/card-model.ts} +1 -0
  171. package/src/lib/client/recipe-categories.ts +11 -93
  172. package/src/lib/client/recipe.ts +13 -11
  173. package/src/lib/client-index/catalog.ts +33 -27
  174. package/src/lib/client-index/chrome.ts +30 -30
  175. package/src/lib/client-index/index.ts +1 -0
  176. package/src/lib/client-index/registry.ts +70 -59
  177. package/src/lib/client-index/rows.ts +63 -57
  178. package/src/lib/client-index/tooltips.ts +28 -34
  179. package/src/lib/config.ts +2 -1
  180. package/src/lib/data/category/dto.ts +4 -3
  181. package/src/lib/data/category/load.ts +9 -8
  182. package/src/lib/data/category/parse.ts +4 -3
  183. package/src/lib/data/dto.ts +4 -3
  184. package/src/lib/data/grid.ts +2 -1
  185. package/src/lib/data/parse/entry.ts +3 -2
  186. package/src/lib/data/parse/index.ts +1 -0
  187. package/src/lib/data/parse/layout.ts +5 -2
  188. package/src/lib/data/parse/slots.ts +3 -2
  189. package/src/lib/data/parse/widgets.ts +3 -2
  190. package/src/lib/data/scene.ts +1 -1
  191. package/src/lib/data/slot-io.ts +1 -0
  192. package/src/lib/dump.ts +4 -3
  193. package/src/lib/embed.ts +22 -0
  194. package/src/lib/favorite-hover.ts +1 -0
  195. package/src/lib/fluid-outputs.ts +10 -12
  196. package/src/lib/forge/actions.ts +2 -1
  197. package/src/lib/forge/data.ts +2 -1
  198. package/src/lib/forge/solve.ts +1 -0
  199. package/src/lib/group.ts +1 -0
  200. package/src/lib/index-store.ts +15 -9
  201. package/src/lib/links.ts +15 -9
  202. package/src/lib/lookup.ts +10 -8
  203. package/src/lib/panel-outline.ts +1 -0
  204. package/src/lib/panel.ts +3 -2
  205. package/src/lib/preview.ts +12 -8
  206. package/src/lib/recipe-slots.ts +1 -0
  207. package/src/lib/search.ts +1 -0
  208. package/src/lib/sidebar.ts +1 -0
  209. package/src/lib/used-items.ts +1 -0
  210. package/src/locales/en_us.json +170 -226
  211. package/src/locales/ru_ru.json +170 -226
  212. package/src/locales.ts +1 -0
  213. package/src/types/alloy.ts +1 -0
  214. package/src/{hooks/calc/flow/props.ts → types/calc-flow-props.ts} +18 -14
  215. package/src/types/calc.ts +41 -44
  216. package/src/types/forge.ts +1 -0
  217. package/src/types/index.ts +4 -2
  218. package/styles/calc.css +308 -47
  219. package/src/components/cards/fit-card.tsx +0 -26
  220. package/src/components/views/calc/target.tsx +0 -271
  221. package/src/hooks/calc/flow/engage.ts +0 -89
  222. package/src/hooks/calc/flow/handlers.ts +0 -115
  223. package/src/hooks/calc/flow/layout.ts +0 -149
  224. package/src/hooks/calc/flow/pickers.ts +0 -50
  225. package/src/hooks/calc/use-calc-fill.ts +0 -91
  226. package/src/hooks/calc/use-density.ts +0 -28
  227. package/src/hooks/calc/use-derived.ts +0 -258
  228. package/src/hooks/calc/use-direction.ts +0 -23
  229. package/src/hooks/calc/use-edge-style.ts +0 -40
  230. package/src/hooks/calc/use-fill.ts +0 -213
  231. package/src/lib/alloy/alloy.test.ts +0 -186
  232. package/src/lib/calc/tree/build.ts +0 -196
  233. package/src/lib/calc/tree/context.ts +0 -42
  234. package/src/lib/calc/tree/goal.ts +0 -64
  235. package/src/lib/calc/tree/ideal.ts +0 -36
  236. package/src/lib/calc/tree/pick.ts +0 -68
  237. package/src/lib/calc/tree/tree.test.ts +0 -169
  238. package/src/lib/calc/worker/fill.ts +0 -123
  239. package/src/lib/client-index/cache.ts +0 -13
  240. package/src/lib/forge/forge.test.ts +0 -123
@@ -1,36 +1,23 @@
1
1
  'use client';
2
2
 
3
- import { useState } from 'react';
4
3
  import {
5
- ArrowDownUp,
6
- ArrowRightLeft,
7
- CircuitBoard,
8
- CornerRightUp,
9
4
  GalleryHorizontal,
10
5
  LayoutGrid,
11
- MoveUpRight,
12
6
  RectangleHorizontal,
13
7
  Settings2,
14
- Spline,
15
- Waypoints,
16
8
  } from 'lucide-react';
17
9
  import { useTranslations } from '@terrafirmagreg-team/translate/hooks/use-translations';
18
10
  import { IconButton } from '@terrafirmagreg-team/ui/components/blocks/button/icon-button';
19
11
  import {
20
12
  DropdownMenu,
21
13
  DropdownMenuContent,
22
- DropdownMenuGroup,
23
14
  DropdownMenuLabel,
24
15
  DropdownMenuRadioGroup,
25
16
  DropdownMenuRadioItem,
26
17
  DropdownMenuSeparator,
27
18
  DropdownMenuTrigger,
28
19
  } from '@terrafirmagreg-team/ui/components/ui/dropdown-menu';
29
- import { Slider } from '@terrafirmagreg-team/ui/components/ui/slider';
30
20
  import type {
31
- CalcDensity,
32
- CalcDirection,
33
- CalcEdgeStyle,
34
21
  CalcFarMode,
35
22
  CalcViewMode,
36
23
  } from '@terrafirmagreg-team/recipe/lib/calc/flow';
@@ -41,59 +28,35 @@ const MODE_ICONS = {
41
28
  detailed: GalleryHorizontal,
42
29
  } as const;
43
30
 
44
- const EDGE_ICONS = {
45
- default: Spline,
46
- straight: MoveUpRight,
47
- step: Waypoints,
48
- smoothstep: CornerRightUp,
49
- circuit: CircuitBoard,
50
- } as const;
51
-
52
- const DIR_ICONS = {
53
- vertical: ArrowDownUp,
54
- horizontal: ArrowRightLeft,
55
- } as const;
56
-
57
31
  const FAR_MODES: CalcFarMode[] = ['full', 'preview', 'hidden'];
58
32
 
59
33
  export function CalcViewMenu({
60
34
  mode,
61
35
  onModeChange,
62
- direction,
63
- onDirectionChange,
64
- edgeStyle,
65
- onEdgeStyleChange,
66
36
  farCards,
67
37
  onFarCardsChange,
68
- density,
69
- onDensityChange,
70
38
  }: {
71
39
  mode: CalcViewMode;
72
40
  onModeChange: (mode: CalcViewMode) => void;
73
- direction: CalcDirection;
74
- onDirectionChange: (direction: CalcDirection) => void;
75
- edgeStyle: CalcEdgeStyle;
76
- onEdgeStyleChange: (style: CalcEdgeStyle) => void;
77
41
  farCards: CalcFarMode;
78
42
  onFarCardsChange: (mode: CalcFarMode) => void;
79
- density: CalcDensity;
80
- onDensityChange: (density: CalcDensity) => void;
81
43
  }) {
82
44
  const t = useTranslations();
83
-
84
- const [densityDraft, setDensityDraft] = useState(() =>
85
- Math.round(density * 100),
86
- );
87
- const [prevDensity, setPrevDensity] = useState(density);
88
- if (prevDensity !== density) {
89
- setPrevDensity(density);
90
- setDensityDraft(Math.round(density * 100));
91
- }
45
+ const modeLabels: Record<CalcViewMode, string> = {
46
+ mini: t('Compact view', { note: 'recipe.calc.mode.mini' }),
47
+ normal: t('Card view', { note: 'recipe.calc.mode.normal' }),
48
+ detailed: t('Recipe view', { note: 'recipe.calc.mode.detailed' }),
49
+ };
50
+ const farLabels: Record<CalcFarMode, string> = {
51
+ full: t('Full card', { note: 'recipe.calc.far.full' }),
52
+ preview: t('Preview', { note: 'recipe.calc.far.preview' }),
53
+ hidden: t('Hide', { note: 'recipe.calc.far.hidden' }),
54
+ };
92
55
 
93
56
  return (
94
57
  <DropdownMenu>
95
58
  <DropdownMenuTrigger
96
- render={<IconButton overlay label={t('recipe.calc.view.title')} />}
59
+ render={<IconButton overlay label={t('View settings', { note: 'recipe.calc.view.title' })} />}
97
60
  >
98
61
  <Settings2 className="size-3.5" />
99
62
  </DropdownMenuTrigger>
@@ -107,7 +70,7 @@ export function CalcViewMenu({
107
70
  onValueChange={(value) => onModeChange(value as CalcViewMode)}
108
71
  >
109
72
  <DropdownMenuLabel className="whitespace-nowrap">
110
- {t('recipe.calc.view.modes')}
73
+ {t('Cards', { note: 'recipe.calc.view.modes' })}
111
74
  </DropdownMenuLabel>
112
75
  {(Object.keys(MODE_ICONS) as CalcViewMode[]).map((value) => {
113
76
  const ModeIcon = MODE_ICONS[value];
@@ -118,79 +81,11 @@ export function CalcViewMenu({
118
81
  className="whitespace-nowrap"
119
82
  >
120
83
  <ModeIcon className="size-4" />
121
- {t(`recipe.calc.mode.${value}`)}
84
+ {modeLabels[value]}
122
85
  </DropdownMenuRadioItem>
123
86
  );
124
87
  })}
125
88
  </DropdownMenuRadioGroup>
126
- <DropdownMenuSeparator />
127
- <DropdownMenuRadioGroup
128
- value={direction}
129
- onValueChange={(value) => onDirectionChange(value as CalcDirection)}
130
- >
131
- <DropdownMenuLabel className="whitespace-nowrap">
132
- {t('recipe.calc.view.dir')}
133
- </DropdownMenuLabel>
134
- {(Object.keys(DIR_ICONS) as CalcDirection[]).map((value) => {
135
- const DirIcon = DIR_ICONS[value];
136
- return (
137
- <DropdownMenuRadioItem
138
- key={value}
139
- value={value}
140
- className="whitespace-nowrap"
141
- >
142
- <DirIcon className="size-4" />
143
- {t(`recipe.calc.dir.${value === 'vertical' ? 'v' : 'h'}`)}
144
- </DropdownMenuRadioItem>
145
- );
146
- })}
147
- </DropdownMenuRadioGroup>
148
- <DropdownMenuSeparator />
149
- <DropdownMenuRadioGroup
150
- value={edgeStyle}
151
- onValueChange={(value) =>
152
- onEdgeStyleChange(value as CalcEdgeStyle)
153
- }
154
- >
155
- <DropdownMenuLabel className="whitespace-nowrap">
156
- {t('recipe.calc.view.edge')}
157
- </DropdownMenuLabel>
158
- {(Object.keys(EDGE_ICONS) as CalcEdgeStyle[]).map((value) => {
159
- const EdgeIcon = EDGE_ICONS[value];
160
- return (
161
- <DropdownMenuRadioItem
162
- key={value}
163
- value={value}
164
- className="whitespace-nowrap"
165
- >
166
- <EdgeIcon className="size-4" />
167
- {t(`recipe.calc.edge.${value}`)}
168
- </DropdownMenuRadioItem>
169
- );
170
- })}
171
- </DropdownMenuRadioGroup>
172
- <DropdownMenuSeparator />
173
- <DropdownMenuGroup>
174
- <DropdownMenuLabel className="whitespace-nowrap">
175
- {t('recipe.calc.density.title')}
176
- </DropdownMenuLabel>
177
- <div className="w-44 px-2.5 pb-2 pt-0.5">
178
- <Slider
179
- value={[densityDraft]}
180
- min={0}
181
- max={100}
182
- step={25}
183
- onValueChange={(value) => {
184
- const next = Array.isArray(value) ? value[0] : value;
185
- if (next !== undefined) setDensityDraft(next);
186
- }}
187
- onValueCommitted={(value) => {
188
- const next = Array.isArray(value) ? value[0] : value;
189
- if (next !== undefined) onDensityChange(next / 100);
190
- }}
191
- />
192
- </div>
193
- </DropdownMenuGroup>
194
89
  {mode === 'detailed' ? (
195
90
  <>
196
91
  <DropdownMenuSeparator />
@@ -201,7 +96,7 @@ export function CalcViewMenu({
201
96
  }
202
97
  >
203
98
  <DropdownMenuLabel className="whitespace-nowrap">
204
- {t('recipe.calc.far.title')}
99
+ {t('Cards when zoomed out', { note: 'recipe.calc.far.title' })}
205
100
  </DropdownMenuLabel>
206
101
  {FAR_MODES.map((value) => (
207
102
  <DropdownMenuRadioItem
@@ -209,7 +104,7 @@ export function CalcViewMenu({
209
104
  value={value}
210
105
  className="whitespace-nowrap"
211
106
  >
212
- {t(`recipe.calc.far.${value}`)}
107
+ {farLabels[value]}
213
108
  </DropdownMenuRadioItem>
214
109
  ))}
215
110
  </DropdownMenuRadioGroup>
@@ -219,3 +114,4 @@ export function CalcViewMenu({
219
114
  </DropdownMenu>
220
115
  );
221
116
  }
117
+
@@ -1,4 +1,5 @@
1
1
  'use client';
2
+ import type { DatasetRef } from '@terrafirmagreg-team/sites/location';
2
3
 
3
4
  import { useTranslations } from '@terrafirmagreg-team/translate/hooks/use-translations';
4
5
  import {
@@ -12,7 +13,7 @@ import {
12
13
  } from '@terrafirmagreg-team/ui/components/ui/command';
13
14
  import { Entry } from '@terrafirmagreg-team/game/components/entry/entry';
14
15
  import { stackDisplayName } from '@terrafirmagreg-team/game/lib/entry/display';
15
- import type { ReferenceKind } from '@terrafirmagreg-team/game/lib/dump/contract/ref';
16
+ import type { ReferenceKind } from '@terrafirmagreg-team/dump/lib/dump/contract/ref';
16
17
 
17
18
  export function CalcMemberList({
18
19
  tagId,
@@ -28,7 +29,7 @@ export function CalcMemberList({
28
29
  members: string[];
29
30
  selected?: string;
30
31
  memberKind?: ReferenceKind;
31
- dataset: string;
32
+ dataset: DatasetRef;
32
33
  lang: string;
33
34
  langMap?: Record<string, string>;
34
35
  onSelect: (tagId: string, memberId: string) => void;
@@ -36,9 +37,9 @@ export function CalcMemberList({
36
37
  const t = useTranslations();
37
38
  return (
38
39
  <Command label={tagId}>
39
- <CommandInput placeholder={t('recipe.calc.member.search')} />
40
+ <CommandInput placeholder={t('Search member…', { note: 'recipe.calc.member.search' })} />
40
41
  <CommandList>
41
- <CommandEmpty>{t('recipe.search_empty')}</CommandEmpty>
42
+ <CommandEmpty>{t('No results found', { note: 'recipe.search_empty' })}</CommandEmpty>
42
43
  <CommandGroup>
43
44
  {members.map((id) => (
44
45
  <CommandItem
@@ -85,7 +86,7 @@ export function CalcMemberPicker({
85
86
  members: string[];
86
87
  selected?: string;
87
88
  memberKind?: ReferenceKind;
88
- dataset: string;
89
+ dataset: DatasetRef;
89
90
  lang: string;
90
91
  langMap?: Record<string, string>;
91
92
  onSelect: (tagId: string, memberId: string) => void;
@@ -95,7 +96,7 @@ export function CalcMemberPicker({
95
96
  <CommandDialog
96
97
  open={open}
97
98
  onOpenChange={onOpenChange}
98
- title={t('recipe.calc.member.title')}
99
+ title={t('Choose tag member', { note: 'recipe.calc.member.title' })}
99
100
  description={tagId}
100
101
  className="sm:max-w-md"
101
102
  commandProps={{ label: tagId }}
@@ -116,3 +117,4 @@ export function CalcMemberPicker({
116
117
  </CommandDialog>
117
118
  );
118
119
  }
120
+
@@ -5,18 +5,10 @@ import {
5
5
  Check,
6
6
  Copy,
7
7
  ExternalLink,
8
- Play,
9
- Plus,
8
+ Layers,
10
9
  X,
11
10
  } from 'lucide-react';
12
11
  import { useTranslations } from '@terrafirmagreg-team/translate/hooks/use-translations';
13
- import {
14
- DropdownMenu,
15
- DropdownMenuContent,
16
- DropdownMenuItem,
17
- DropdownMenuTrigger,
18
- } from '@terrafirmagreg-team/ui/components/ui/dropdown-menu';
19
- import { Entry } from '@terrafirmagreg-team/game/components/entry/entry';
20
12
  import { Hint } from '@terrafirmagreg-team/ui/components/blocks/tooltip/hint';
21
13
  import { cn } from '@terrafirmagreg-team/ui/lib/utils/cn';
22
14
  import type { CalcFlowNode } from '@terrafirmagreg-team/recipe/lib/calc/flow';
@@ -27,87 +19,33 @@ export const CORNER_BUTTON =
27
19
  export function NodeActions({
28
20
  data,
29
21
  nodeId,
30
- onPickMissing,
31
22
  }: {
32
23
  data: CalcFlowNode['data'];
33
24
  nodeId: string;
34
- onPickMissing: (node: CalcFlowNode['data']['node']) => void;
35
25
  }) {
36
- const {
37
- node,
38
- missing,
39
- onFillNode,
40
- onCollapseNode,
41
- onRemoveTarget,
42
- onOpenItem,
43
- } = data;
26
+ const { node, onRemoveCard, onOpenItem, onRequestPicker } = data;
44
27
  const t = useTranslations();
45
28
  const [copied, setCopied] = useState(false);
46
- const canFill =
47
- node.expandable && !node.collapsed && !node.recipe && node.need > 0;
48
- const canClose = node.path != null && !node.collapsed;
29
+ const canPick = node.recipe != null || node.availableRecipes.length > 0;
49
30
 
50
- const isTarget = node.path != null && !node.path.includes('/');
51
- const missingButton = missing?.length ? (
52
- <Hint label={t('recipe.calc.add.node')} side="top">
53
- <button
54
- type="button"
55
- aria-label={t('recipe.calc.add.node')}
56
- onClick={
57
- missing.length === 1
58
- ? () => onPickMissing(missing[0]!)
59
- : undefined
60
- }
61
- className={CORNER_BUTTON}
62
- >
63
- <Plus className="size-2.5" />
64
- </button>
65
- </Hint>
66
- ) : null;
67
31
  return (
68
- <span className="pointer-events-auto absolute -top-2 right-1 z-30 flex gap-0.5">
69
- {missing?.length === 1 ? (
70
- missingButton
71
- ) : missing?.length ? (
72
- <DropdownMenu>
73
- <DropdownMenuTrigger render={missingButton!} />
74
- <DropdownMenuContent side="bottom" align="end" className="w-fit">
75
- {missing.map((child) => (
76
- <DropdownMenuItem
77
- key={child.path ?? child.materialId}
78
- className="whitespace-nowrap"
79
- onClick={() => onPickMissing(child)}
80
- >
81
- <Entry
82
- id={child.materialId}
83
- kind={child.kind}
84
- dataset={data.dataset}
85
- size="sm"
86
- labeled
87
- href={false}
88
- tooltip={false}
89
- />
90
- </DropdownMenuItem>
91
- ))}
92
- </DropdownMenuContent>
93
- </DropdownMenu>
94
- ) : null}
95
- {canFill ? (
96
- <Hint label={t('recipe.calc.auto.build')} side="top">
32
+ <span className="pointer-events-auto absolute -top-2 right-3 z-30 flex gap-0.5">
33
+ {canPick ? (
34
+ <Hint label={t('Pick a recipe', { note: 'recipe.calc.pick.title' })} side="top">
97
35
  <button
98
36
  type="button"
99
- aria-label={t('recipe.calc.auto.build')}
100
- onClick={() => node.path && onFillNode(node.path!)}
37
+ aria-label={t('Pick a recipe', { note: 'recipe.calc.pick.title' })}
38
+ onClick={() => onRequestPicker(node)}
101
39
  className={CORNER_BUTTON}
102
40
  >
103
- <Play className="size-2.5" />
41
+ <Layers className="size-2.5" />
104
42
  </button>
105
43
  </Hint>
106
44
  ) : null}
107
- <Hint label={t('recipe.calc.copy_id')} side="top">
45
+ <Hint label={t('Copy id', { note: 'recipe.calc.copy_id' })} side="top">
108
46
  <button
109
47
  type="button"
110
- aria-label={t('recipe.calc.copy_id')}
48
+ aria-label={t('Copy id', { note: 'recipe.calc.copy_id' })}
111
49
  onClick={() => {
112
50
  void navigator.clipboard.writeText(node.materialId);
113
51
  setCopied(true);
@@ -122,47 +60,31 @@ export function NodeActions({
122
60
  )}
123
61
  </button>
124
62
  </Hint>
125
- <Hint label={t('recipe.calc.open.item')} side="top">
63
+ <Hint label={t('Open item page', { note: 'recipe.calc.open.item' })} side="top">
126
64
  <button
127
65
  type="button"
128
- aria-label={t('recipe.calc.open.item')}
66
+ aria-label={t('Open item page', { note: 'recipe.calc.open.item' })}
129
67
  onClick={() => onOpenItem(node.materialId, node.kind)}
130
68
  className={CORNER_BUTTON}
131
69
  >
132
70
  <ExternalLink className="size-2.5" />
133
71
  </button>
134
72
  </Hint>
135
- {isTarget ? (
136
- <Hint label={t('recipe.calc.remove.title')} side="top">
137
- <button
138
- type="button"
139
- aria-label={t('recipe.calc.remove.title')}
140
- onPointerDown={(event) => event.stopPropagation()}
141
- onClick={(event) => {
142
- event.stopPropagation();
143
- onRemoveTarget(nodeId);
144
- }}
145
- className={cn(CORNER_BUTTON, 'hover:text-destructive')}
146
- >
147
- <X className="size-3" />
148
- </button>
149
- </Hint>
150
- ) : canClose ? (
151
- <Hint label={t('recipe.calc.collapse')} side="top">
152
- <button
153
- type="button"
154
- aria-label={t('recipe.calc.collapse')}
155
- onPointerDown={(event) => event.stopPropagation()}
156
- onClick={(event) => {
157
- event.stopPropagation();
158
- onCollapseNode(node.path!);
159
- }}
160
- className={cn(CORNER_BUTTON, 'hover:text-destructive')}
161
- >
162
- <X className="size-3" />
163
- </button>
164
- </Hint>
165
- ) : null}
73
+ <Hint label={t('Remove card', { note: 'recipe.calc.remove.title' })} side="top">
74
+ <button
75
+ type="button"
76
+ aria-label={t('Remove card', { note: 'recipe.calc.remove.title' })}
77
+ onPointerDown={(event) => event.stopPropagation()}
78
+ onClick={(event) => {
79
+ event.stopPropagation();
80
+ onRemoveCard(nodeId);
81
+ }}
82
+ className={cn(CORNER_BUTTON, 'hover:text-destructive')}
83
+ >
84
+ <X className="size-3" />
85
+ </button>
86
+ </Hint>
166
87
  </span>
167
88
  );
168
89
  }
90
+
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import type { ReactNode } from 'react';
4
- import { FlaskConical, Mountain, Percent, Repeat } from 'lucide-react';
4
+ import { Mountain, Repeat } from 'lucide-react';
5
5
  import { useTranslations } from '@terrafirmagreg-team/translate/hooks/use-translations';
6
6
  import { Hint } from '@terrafirmagreg-team/ui/components/blocks/tooltip/hint';
7
7
  import { cn } from '@terrafirmagreg-team/ui/lib/utils/cn';
@@ -20,7 +20,7 @@ export function NodeBadges({
20
20
  const chips: ReactNode[] = [];
21
21
  if (node.cycle) {
22
22
  chips.push(
23
- <Hint key="cycle" label={t('recipe.calc.hint.cycle')} side="top">
23
+ <Hint key="cycle" label={t('Recipe cycle: the recipe needs itself upstream', { note: 'recipe.calc.hint.cycle' })} side="top">
24
24
  <span
25
25
  className={cn(CORNER_CHIP, 'border-red-500/40 text-red-500')}
26
26
  >
@@ -29,49 +29,31 @@ export function NodeBadges({
29
29
  </Hint>,
30
30
  );
31
31
  }
32
- if (node.catalyst) {
32
+ if (!node.recipe && !node.availableRecipes.length && !node.loading) {
33
33
  chips.push(
34
- <Hint key="catalyst" label={t('recipe.calc.hint.catalyst')} side="top">
35
- <span className={CORNER_CHIP}>
36
- <FlaskConical className="size-2.5" />
37
- </span>
38
- </Hint>,
39
- );
40
- }
41
- if (!node.recipe && !node.expandable && !node.loading) {
42
- chips.push(
43
- <Hint key="source" label={t('recipe.calc.hint.source')} side="top">
34
+ <Hint key="source" label={t('No recipe: taken from remainders or gathered externally', { note: 'recipe.calc.hint.source' })} side="top">
44
35
  <span className={CORNER_CHIP}>
45
36
  <Mountain className="size-2.5" />
46
37
  </span>
47
38
  </Hint>,
48
39
  );
49
40
  }
50
- if (node.satisfied > 0) {
41
+ if (node.remaining < 0) {
51
42
  chips.push(
52
- <Hint key="satisfied" label={t('recipe.calc.satisfied')} side="top">
43
+ <Hint key="over" label={t('Overdrawn: more is wired out than produced', { note: 'recipe.calc.overdrawn' })} side="top">
53
44
  <span
54
- className={cn(CORNER_CHIP, 'border-emerald-500/40 text-emerald-500')}
45
+ className={cn(CORNER_CHIP, 'border-red-500/40 text-red-500')}
55
46
  >
56
- +{formatCount(node.satisfied)}
57
- </span>
58
- </Hint>,
59
- );
60
- }
61
- if (node.consumeChance != null && node.consumeChance < 1) {
62
- chips.push(
63
- <Hint key="chance" label={t('recipe.calc.chance')} side="top">
64
- <span className={CORNER_CHIP}>
65
- <Percent className="size-2.5" />
66
- {Math.round(node.consumeChance * 100)}
47
+ {formatCount(node.remaining)}
67
48
  </span>
68
49
  </Hint>,
69
50
  );
70
51
  }
71
52
  if (!chips.length) return null;
72
53
  return (
73
- <span className="pointer-events-auto absolute -right-1.5 -bottom-1.5 z-30 flex flex-col-reverse items-end gap-0.5">
54
+ <span className="pointer-events-auto absolute right-3 -bottom-1.5 z-30 flex flex-col-reverse items-end gap-0.5">
74
55
  {chips}
75
56
  </span>
76
57
  );
77
58
  }
59
+
@@ -1,10 +1,10 @@
1
1
  'use client';
2
+ import type { DatasetRef } from '@terrafirmagreg-team/sites/location';
2
3
 
3
4
  import type { ReactNode } from 'react';
4
5
  import {
5
6
  ChevronDown,
6
7
  ListFilter,
7
- Plus,
8
8
  Shapes,
9
9
  } from 'lucide-react';
10
10
  import { useTranslations } from '@terrafirmagreg-team/translate/hooks/use-translations';
@@ -89,36 +89,14 @@ export function NodeChips({
89
89
  name: string;
90
90
  category: RecipeCategory | undefined;
91
91
  hasAlternatives: boolean;
92
- dataset: string;
92
+ dataset: DatasetRef;
93
93
  lang: string;
94
94
  langMap?: Record<string, string>;
95
95
  onRequestPicker: CalcFlowData['onRequestPicker'];
96
96
  onRequestMemberPicker: CalcFlowData['onRequestMemberPicker'];
97
97
  }) {
98
98
  const t = useTranslations();
99
- const recipes = node.availableRecipes;
100
99
  const members = node.members;
101
- const pickChip =
102
- node.expandable && !node.recipe ? (
103
- <CycleChip
104
- className="min-w-0 flex-1 border-dashed"
105
- title={`${
106
- node.collapsed
107
- ? t('recipe.calc.hint.collapsed')
108
- : t('recipe.calc.pick.title')
109
- } ×${formatCount(recipes.length)}`}
110
- openLabel={t('recipe.calc.pick.title')}
111
- onOpen={() => onRequestPicker(node)}
112
- centerClassName="py-0.5"
113
- >
114
- <Plus className="size-3 shrink-0" />
115
- {mini ? null : (
116
- <span className="shrink-0 tabular-nums">
117
- ×{formatCount(recipes.length)}
118
- </span>
119
- )}
120
- </CycleChip>
121
- ) : null;
122
100
  const loadingChip =
123
101
  node.loading && !node.recipe ? (
124
102
  <Skeleton className="h-5 min-w-0 flex-1 rounded-md" />
@@ -130,27 +108,21 @@ export function NodeChips({
130
108
  mini && 'h-5 w-full',
131
109
  )}
132
110
  >
133
- {pickChip}
134
111
  {loadingChip}
135
- {!node.recipe && !pickChip && !loadingChip ? (
112
+ {!node.recipe && !loadingChip ? (
136
113
  <CycleChip
137
114
  className="min-w-0 flex-1 border-dashed opacity-60"
138
- title={t('recipe.calc.hint.source')}
115
+ title={t('No recipe: taken from remainders or gathered externally', { note: 'recipe.calc.hint.source' })}
139
116
  centerClassName="py-0.5"
140
117
  >
141
118
  <ListFilter className="size-3 shrink-0" />
142
119
  </CycleChip>
143
120
  ) : null}
144
121
  {node.recipe ? (
145
-
146
122
  <CycleChip
147
123
  className="min-w-0 flex-1"
148
- title={
149
- mini && node.batches > 1
150
- ? `${node.recipe.recipeId} ×${formatCount(node.batches)}`
151
- : node.recipe.recipeId
152
- }
153
- openLabel={t('recipe.calc.select')}
124
+ title={node.recipe.recipeId}
125
+ openLabel={t('Choose recipe', { note: 'recipe.calc.select' })}
154
126
  onOpen={() => onRequestPicker(node)}
155
127
  >
156
128
  {category?.icon ? (
@@ -170,11 +142,6 @@ export function NodeChips({
170
142
  className={mini ? 'size-3.5 shrink-0' : 'size-4'}
171
143
  />
172
144
  )}
173
- {!mini && node.batches > 1 ? (
174
- <span className="shrink-0 tabular-nums">
175
- ×{formatCount(node.batches)}
176
- </span>
177
- ) : null}
178
145
  {hasAlternatives ? (
179
146
  <ChevronDown
180
147
  className={mini ? 'size-2.5 shrink-0' : 'size-3 shrink-0'}
@@ -191,7 +158,7 @@ export function NodeChips({
191
158
  ? `${node.tagId} ×${formatCount(members.length)}`
192
159
  : node.tagId
193
160
  }
194
- openLabel={t('recipe.calc.member.title')}
161
+ openLabel={t('Choose tag member', { note: 'recipe.calc.member.title' })}
195
162
  onOpen={() => onRequestMemberPicker(node)}
196
163
  centerClassName="px-0.5"
197
164
  >
@@ -217,3 +184,4 @@ export function NodeChips({
217
184
  </div>
218
185
  );
219
186
  }
187
+