@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.
- package/package.json +62 -3
- package/src/components/button-copy.tsx +43 -0
- package/src/components/cards/card/index.tsx +214 -0
- package/src/components/cards/card/scene.tsx +107 -0
- package/src/components/cards/card/strip.tsx +198 -0
- package/src/components/cards/card/text.tsx +46 -0
- package/src/components/cards/card/tips.tsx +63 -0
- package/src/components/cards/default-item.tsx +47 -0
- package/src/components/cards/glare-filter.tsx +29 -0
- package/src/components/cards/paged-card.tsx +222 -0
- package/src/components/cards/slot-hitbox.tsx +213 -0
- package/src/components/cards/strip-card/buttons.tsx +106 -0
- package/src/components/cards/strip-card/index.tsx +214 -0
- package/src/components/cards/strip-card/member-cell.tsx +167 -0
- package/src/components/cards/strip-card/sprite.tsx +36 -0
- package/src/components/embed-card.tsx +44 -0
- package/src/components/mdx/recipe.tsx +129 -0
- package/src/components/tool-list.tsx +49 -0
- package/src/components/tree.tsx +98 -0
- package/src/components/views/alloy/index.tsx +112 -0
- package/src/components/views/alloy/list.tsx +85 -0
- package/src/components/views/alloy/mix/index.tsx +35 -0
- package/src/components/views/alloy/mix/ratio.tsx +94 -0
- package/src/components/views/alloy/mix/rows.tsx +145 -0
- package/src/components/views/alloy/mix/status.tsx +122 -0
- package/src/components/views/alloy/mix/total.tsx +132 -0
- package/src/components/views/alloy/range-bar.tsx +65 -0
- package/src/components/views/alloy/stack-bar.tsx +88 -0
- package/src/components/views/browser/axis-switcher.tsx +54 -0
- package/src/components/views/browser/card-grid.tsx +275 -0
- package/src/components/views/browser/favorite-button.tsx +46 -0
- package/src/components/views/browser/frame.tsx +112 -0
- package/src/components/views/browser/index.tsx +114 -0
- package/src/components/views/browser/item-panel/index.tsx +229 -0
- package/src/components/views/browser/item-panel/skeleton.tsx +26 -0
- package/src/components/views/browser/item-panel-grid/cell.tsx +178 -0
- package/src/components/views/browser/item-panel-grid/favorite-cell.tsx +129 -0
- package/src/components/views/browser/item-panel-grid/index.tsx +105 -0
- package/src/components/views/browser/item-view/index.tsx +186 -0
- package/src/components/views/browser/item-view/tags.tsx +85 -0
- package/src/components/views/browser/item-view-header.tsx +131 -0
- package/src/components/views/browser/pane-tabs.tsx +61 -0
- package/src/components/views/browser/place-header.tsx +72 -0
- package/src/components/views/browser/query-controls.tsx +105 -0
- package/src/components/views/browser/recipe-item-header.tsx +60 -0
- package/src/components/views/browser/settings.tsx +52 -0
- package/src/components/views/browser/toolbar.tsx +54 -0
- package/src/components/views/browser/type-tabs.tsx +71 -0
- package/src/components/views/browser/type-view.tsx +184 -0
- package/src/components/views/browser/workstation-rail.tsx +43 -0
- package/src/components/views/calc/calculator/actions.tsx +47 -0
- package/src/components/views/calc/calculator/help.tsx +189 -0
- package/src/components/views/calc/calculator/index.tsx +172 -0
- package/src/components/views/calc/dot-overlay.tsx +80 -0
- package/src/components/views/calc/edge.tsx +152 -0
- package/src/components/views/calc/flow/index.tsx +305 -0
- package/src/components/views/calc/flow/picker-dialogs.tsx +146 -0
- package/src/components/views/calc/flow/toolbar.tsx +60 -0
- package/src/components/views/calc/flow/view-menu.tsx +117 -0
- package/src/components/views/calc/member-picker.tsx +120 -0
- package/src/components/views/calc/node-card/actions.tsx +90 -0
- package/src/components/views/calc/node-card/badges.tsx +59 -0
- package/src/components/views/calc/node-card/chips.tsx +187 -0
- package/src/components/views/calc/node-card/far.tsx +123 -0
- package/src/components/views/calc/node-card/full.tsx +332 -0
- package/src/components/views/calc/node-card/index.tsx +27 -0
- package/src/components/views/calc/node-card/menu.tsx +282 -0
- package/src/components/views/calc/node-card/port-cells.tsx +186 -0
- package/src/components/views/calc/node-card/port-rails.tsx +251 -0
- package/src/components/views/calc/node-card/ports.tsx +86 -0
- package/src/components/views/calc/recipe-picker/body.tsx +207 -0
- package/src/components/views/calc/recipe-picker/footer.tsx +131 -0
- package/src/components/views/calc/recipe-picker/index.tsx +52 -0
- package/src/components/views/calc/sidebar.tsx +171 -0
- package/src/components/views/calc/trees/icon-picker.tsx +97 -0
- package/src/components/views/calc/trees/index.tsx +134 -0
- package/src/components/views/calc/trees/label.tsx +67 -0
- package/src/components/views/calc/trees/rows.tsx +140 -0
- package/src/components/views/forge/gui.tsx +131 -0
- package/src/components/views/forge/index.tsx +112 -0
- package/src/components/views/forge/list.tsx +113 -0
- package/src/components/views/forge/workbench/controls.tsx +103 -0
- package/src/components/views/forge/workbench/index.tsx +85 -0
- package/src/components/views/forge/workbench/panel.tsx +228 -0
- package/src/components/views/forge/workbench/ruler.tsx +133 -0
- package/src/components/views/forge/workbench/status.tsx +55 -0
- package/src/components/views/forge/workbench/tape.tsx +70 -0
- package/src/hooks/alloy/use-fluid-colors.ts +125 -0
- package/src/hooks/alloy/use-mix-state.ts +240 -0
- package/src/hooks/calc/flow/use-engage.ts +129 -0
- package/src/hooks/calc/flow/use-handlers.ts +192 -0
- package/src/hooks/calc/flow/use-layout.ts +105 -0
- package/src/hooks/calc/flow/use-mount.ts +97 -0
- package/src/hooks/calc/flow/use-nodes.ts +260 -0
- package/src/hooks/calc/flow/use-pickers.ts +57 -0
- package/src/hooks/calc/flow/use-viewport.ts +141 -0
- package/src/hooks/calc/flow/use-window.ts +87 -0
- package/src/hooks/calc/use-actions.ts +355 -0
- package/src/hooks/calc/use-amounts.ts +64 -0
- package/src/hooks/calc/use-data.ts +203 -0
- package/src/hooks/calc/use-defaults.ts +40 -0
- package/src/hooks/calc/use-far-mode.ts +37 -0
- package/src/hooks/calc/use-focus.ts +31 -0
- package/src/hooks/calc/use-maps.ts +76 -0
- package/src/hooks/calc/use-reset.ts +15 -0
- package/src/hooks/calc/use-resolver.ts +70 -0
- package/src/hooks/calc/use-session.ts +13 -0
- package/src/hooks/calc/use-share.ts +64 -0
- package/src/hooks/calc/use-storage.ts +126 -0
- package/src/hooks/calc/use-tier.ts +13 -0
- package/src/hooks/calc/use-view-mode.ts +24 -0
- package/src/hooks/calc/use-worker.ts +204 -0
- package/src/hooks/forge/use-anvil-gui.ts +16 -0
- package/src/hooks/forge/use-forge-state.ts +135 -0
- package/src/hooks/forge/use-ruler.ts +100 -0
- package/src/hooks/use-add-to-calc.ts +51 -0
- package/src/hooks/use-categories-by-id.ts +18 -0
- package/src/hooks/use-favorite-hotkey.ts +39 -0
- package/src/hooks/use-favorites.ts +135 -0
- package/src/hooks/use-group-outlines.ts +65 -0
- package/src/hooks/use-hide-unused.ts +12 -0
- package/src/hooks/use-index.ts +72 -0
- package/src/hooks/use-item-feeds.ts +46 -0
- package/src/hooks/use-item-pane.ts +106 -0
- package/src/hooks/use-item-press.ts +89 -0
- package/src/hooks/use-item-tag-groups.ts +54 -0
- package/src/hooks/use-item-tooltips.ts +18 -0
- package/src/hooks/use-left-sidebar-tree.ts +34 -0
- package/src/hooks/use-mod-names.ts +17 -0
- package/src/hooks/use-mount-limit.ts +22 -0
- package/src/hooks/use-panel-cells.ts +117 -0
- package/src/hooks/use-panel-page.ts +54 -0
- package/src/hooks/use-panel-tabs.ts +43 -0
- package/src/hooks/use-query.ts +186 -0
- package/src/hooks/use-session.ts +88 -0
- package/src/hooks/use-sidebar-group.ts +16 -0
- package/src/hooks/use-tag-members.ts +16 -0
- package/src/hooks/use-tool-recipes.ts +26 -0
- package/src/hooks/use-type-filter.ts +73 -0
- package/src/lib/alloy/mix.ts +210 -0
- package/src/lib/alloy/parse.ts +37 -0
- package/src/lib/calc/defaults.ts +47 -0
- package/src/lib/calc/flow.ts +315 -0
- package/src/lib/calc/focus.ts +15 -0
- package/src/lib/calc/meta.ts +17 -0
- package/src/lib/calc/pack.ts +77 -0
- package/src/lib/calc/ports.ts +43 -0
- package/src/lib/calc/resolve.ts +198 -0
- package/src/lib/calc/route/buckets.ts +49 -0
- package/src/lib/calc/route/geometry.ts +51 -0
- package/src/lib/calc/route/index.ts +3 -0
- package/src/lib/calc/route/polyline.ts +103 -0
- package/src/lib/calc/route/router.ts +394 -0
- package/src/lib/calc/scheme.ts +226 -0
- package/src/lib/calc/storage/entry.ts +97 -0
- package/src/lib/calc/storage/index.ts +20 -0
- package/src/lib/calc/storage/keys.ts +38 -0
- package/src/lib/calc/storage/store.ts +198 -0
- package/src/lib/calc/tree/index.ts +4 -0
- package/src/lib/calc/tree/materials.ts +29 -0
- package/src/lib/calc/tree/recipe.ts +104 -0
- package/src/lib/calc/tree/share.ts +135 -0
- package/src/lib/calc/worker/api.ts +25 -0
- package/src/lib/calc/worker/fetch.ts +66 -0
- package/src/lib/calc/worker/flush.ts +39 -0
- package/src/lib/calc/worker/index.ts +8 -0
- package/src/lib/calc/worker/protocol.ts +35 -0
- package/src/lib/calc/worker/resolve.ts +49 -0
- package/src/lib/calc/worker/state.ts +48 -0
- package/src/lib/card-model.ts +65 -0
- package/src/lib/client/recipe-categories.ts +14 -0
- package/src/lib/client/recipe.ts +85 -0
- package/src/lib/client-index/catalog.ts +91 -0
- package/src/lib/client-index/chrome.ts +180 -0
- package/src/lib/client-index/index.ts +20 -0
- package/src/lib/client-index/registry.ts +121 -0
- package/src/lib/client-index/rows.ts +260 -0
- package/src/lib/client-index/tooltips.ts +74 -0
- package/src/lib/config.ts +18 -0
- package/src/lib/data/category/dto.ts +25 -0
- package/src/lib/data/category/load.ts +47 -0
- package/src/lib/data/category/parse.ts +71 -0
- package/src/lib/data/dto.ts +152 -0
- package/src/lib/data/grid.ts +40 -0
- package/src/lib/data/parse/entry.ts +85 -0
- package/src/lib/data/parse/index.ts +3 -0
- package/src/lib/data/parse/layout.ts +138 -0
- package/src/lib/data/parse/slots.ts +85 -0
- package/src/lib/data/parse/widgets.ts +81 -0
- package/src/lib/data/scene.ts +103 -0
- package/src/lib/data/slot-io.ts +44 -0
- package/src/lib/dump.ts +6 -0
- package/src/lib/embed.ts +22 -0
- package/src/lib/favorite-hover.ts +13 -0
- package/src/lib/fluid-outputs.ts +70 -0
- package/src/lib/forge/actions.ts +59 -0
- package/src/lib/forge/data.ts +51 -0
- package/src/lib/forge/solve.ts +80 -0
- package/src/lib/group.ts +23 -0
- package/src/lib/index-store.ts +70 -0
- package/src/lib/links.ts +131 -0
- package/src/lib/lookup.ts +119 -0
- package/src/lib/panel-outline.ts +86 -0
- package/src/lib/panel.ts +105 -0
- package/src/lib/preview.ts +32 -0
- package/src/lib/recipe-slots.ts +8 -0
- package/src/lib/search.ts +139 -0
- package/src/lib/sidebar.ts +47 -0
- package/src/lib/used-items.ts +25 -0
- package/src/locales/en_us.json +172 -0
- package/src/locales/ru_ru.json +172 -0
- package/src/locales.ts +6 -0
- package/src/types/alloy.ts +20 -0
- package/src/types/calc-flow-props.ts +44 -0
- package/src/types/calc.ts +92 -0
- package/src/types/forge.ts +21 -0
- package/src/types/index.ts +136 -0
- package/styles/calc.css +308 -0
- package/styles/strip.css +7 -0
- package/styles.css +2 -0
- package/README.md +0 -3
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
ArrowUp,
|
|
5
|
+
ChevronUp,
|
|
6
|
+
ChevronsUpDown,
|
|
7
|
+
Eye,
|
|
8
|
+
Info,
|
|
9
|
+
Mountain,
|
|
10
|
+
Move,
|
|
11
|
+
Scissors,
|
|
12
|
+
Settings2,
|
|
13
|
+
SquareMousePointer,
|
|
14
|
+
} from 'lucide-react';
|
|
15
|
+
import { IconButton } from '@terrafirmagreg-team/ui/components/blocks/button/icon-button';
|
|
16
|
+
import {
|
|
17
|
+
Popover,
|
|
18
|
+
PopoverContent,
|
|
19
|
+
PopoverTrigger,
|
|
20
|
+
} from '@terrafirmagreg-team/ui/components/ui/popover';
|
|
21
|
+
import { useTranslations } from '@terrafirmagreg-team/translate/hooks/use-translations';
|
|
22
|
+
|
|
23
|
+
export function CalcHelp() {
|
|
24
|
+
const t = useTranslations();
|
|
25
|
+
return (
|
|
26
|
+
<Popover>
|
|
27
|
+
<PopoverTrigger
|
|
28
|
+
render={<IconButton overlay label={t('Calculator help', { note: 'recipe.calc.help.title' })} />}
|
|
29
|
+
>
|
|
30
|
+
<Info className="size-3.5" />
|
|
31
|
+
</PopoverTrigger>
|
|
32
|
+
<PopoverContent side="bottom" align="end" className="w-80 space-y-3 p-3">
|
|
33
|
+
<section className="space-y-1.5">
|
|
34
|
+
<h4 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/70">
|
|
35
|
+
{t('Nodes', { note: 'recipe.calc.help.sec_nodes' })}
|
|
36
|
+
</h4>
|
|
37
|
+
<ul className="space-y-1.5 text-xs text-muted-foreground">
|
|
38
|
+
<li className="flex items-center gap-2">
|
|
39
|
+
<span className="flex w-5 shrink-0 items-center justify-center">
|
|
40
|
+
<span className="size-4 rounded border border-primary/60" />
|
|
41
|
+
</span>
|
|
42
|
+
{t('Resolved with a recipe', { note: 'recipe.calc.help.recipe' })}
|
|
43
|
+
</li>
|
|
44
|
+
<li className="flex items-center gap-2">
|
|
45
|
+
<span className="flex w-5 shrink-0 items-center justify-center">
|
|
46
|
+
<span className="size-4 rounded border border-red-500/70" />
|
|
47
|
+
</span>
|
|
48
|
+
{t('Recipe cycle', { note: 'recipe.calc.help.cycle' })}
|
|
49
|
+
</li>
|
|
50
|
+
<li className="flex items-center gap-2">
|
|
51
|
+
<span className="flex w-5 shrink-0 items-center justify-center">
|
|
52
|
+
<span className="size-3.5 rounded ring-2 ring-primary/70" />
|
|
53
|
+
</span>
|
|
54
|
+
{t('Focused node', { note: 'recipe.calc.help.focus' })}
|
|
55
|
+
</li>
|
|
56
|
+
<li className="flex items-center gap-2">
|
|
57
|
+
<span className="flex w-5 shrink-0 items-center justify-center">
|
|
58
|
+
<span className="rounded border border-emerald-500/40 px-0.5 text-[10px] font-medium leading-3 text-emerald-500">
|
|
59
|
+
+N
|
|
60
|
+
</span>
|
|
61
|
+
</span>
|
|
62
|
+
{t('Covered by another card output', { note: 'recipe.calc.help.satisfied' })}
|
|
63
|
+
</li>
|
|
64
|
+
<li className="flex items-center gap-2">
|
|
65
|
+
<span className="flex w-5 shrink-0 items-center justify-center">
|
|
66
|
+
<Mountain className="size-3.5 text-muted-foreground" />
|
|
67
|
+
</span>
|
|
68
|
+
{t('No recipe; raw material', { note: 'recipe.calc.help.source' })}
|
|
69
|
+
</li>
|
|
70
|
+
</ul>
|
|
71
|
+
</section>
|
|
72
|
+
<section className="space-y-1.5">
|
|
73
|
+
<h4 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/70">
|
|
74
|
+
{t('Ports', { note: 'recipe.calc.help.sec_ports' })}
|
|
75
|
+
</h4>
|
|
76
|
+
<ul className="space-y-1.5 text-xs text-muted-foreground">
|
|
77
|
+
<li className="flex items-center gap-2">
|
|
78
|
+
<span className="flex w-5 shrink-0 items-center justify-center">
|
|
79
|
+
<span className="size-3.5 rounded-sm border border-emerald-500" />
|
|
80
|
+
</span>
|
|
81
|
+
{t('Input port: click to pick a supplier or drag a wire', { note: 'recipe.calc.help.port_in' })}
|
|
82
|
+
</li>
|
|
83
|
+
<li className="flex items-center gap-2">
|
|
84
|
+
<span className="flex w-5 shrink-0 items-center justify-center">
|
|
85
|
+
<span className="size-3.5 rounded-sm border border-red-400" />
|
|
86
|
+
</span>
|
|
87
|
+
{t('Output port: click to pick a recipe or drag a wire', { note: 'recipe.calc.help.port_out' })}
|
|
88
|
+
</li>
|
|
89
|
+
<li className="flex items-center gap-2">
|
|
90
|
+
<span className="flex w-5 shrink-0 items-center justify-center">
|
|
91
|
+
<span className="size-3.5 rounded-sm border border-dotted border-emerald-500" />
|
|
92
|
+
</span>
|
|
93
|
+
{t('Catalyst: needed but not consumed; wire it or click to pick a supplier', { note: 'recipe.calc.help.port_cat' })}
|
|
94
|
+
</li>
|
|
95
|
+
<li className="flex items-center gap-2">
|
|
96
|
+
<span className="flex w-5 shrink-0 items-center justify-center">
|
|
97
|
+
<ChevronUp className="size-3.5 text-muted-foreground" />
|
|
98
|
+
</span>
|
|
99
|
+
{t('Hidden ports - scroll the rail or click the arrow', { note: 'recipe.calc.help.port_more' })}
|
|
100
|
+
</li>
|
|
101
|
+
</ul>
|
|
102
|
+
</section>
|
|
103
|
+
<section className="space-y-1.5">
|
|
104
|
+
<h4 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/70">
|
|
105
|
+
{t('Edges', { note: 'recipe.calc.help.sec_edges' })}
|
|
106
|
+
</h4>
|
|
107
|
+
<ul className="space-y-1.5 text-xs text-muted-foreground">
|
|
108
|
+
<li className="flex items-center gap-2">
|
|
109
|
+
<span className="flex w-5 shrink-0 items-center justify-center">
|
|
110
|
+
<ArrowUp className="size-3.5 text-muted-foreground" />
|
|
111
|
+
</span>
|
|
112
|
+
{t('Arrows flow from ingredient to product', { note: 'recipe.calc.help.edges' })}
|
|
113
|
+
</li>
|
|
114
|
+
<li className="flex items-center gap-2">
|
|
115
|
+
<span className="flex w-5 shrink-0 items-center justify-center">
|
|
116
|
+
<span className="h-0.5 w-4 rounded bg-teal-500" />
|
|
117
|
+
</span>
|
|
118
|
+
{t('Outgoing edges', { note: 'recipe.calc.help.edge_out' })}
|
|
119
|
+
</li>
|
|
120
|
+
<li className="flex items-center gap-2">
|
|
121
|
+
<span className="flex w-5 shrink-0 items-center justify-center">
|
|
122
|
+
<span className="h-0.5 w-4 rounded bg-yellow-500" />
|
|
123
|
+
</span>
|
|
124
|
+
{t('Incoming edges', { note: 'recipe.calc.help.edge_in' })}
|
|
125
|
+
</li>
|
|
126
|
+
<li className="flex items-center gap-2">
|
|
127
|
+
<span className="flex w-5 shrink-0 items-center justify-center">
|
|
128
|
+
<span className="h-0 w-4 border-t border-dashed border-primary/70" />
|
|
129
|
+
</span>
|
|
130
|
+
{t('Drag port to port to link; drag a wire end to retarget', { note: 'recipe.calc.help.edge_link' })}
|
|
131
|
+
</li>
|
|
132
|
+
<li className="flex items-center gap-2">
|
|
133
|
+
<span className="flex w-5 shrink-0 items-center justify-center">
|
|
134
|
+
<span className="rounded border border-border bg-background px-0.5 text-[9px] leading-3 tabular-nums">
|
|
135
|
+
16
|
|
136
|
+
</span>
|
|
137
|
+
</span>
|
|
138
|
+
{t('Flow on a wire - shows on hover; click to cap the amount', { note: 'recipe.calc.help.edge_flow' })}
|
|
139
|
+
</li>
|
|
140
|
+
<li className="flex items-center gap-2">
|
|
141
|
+
<span className="flex w-5 shrink-0 items-center justify-center">
|
|
142
|
+
<Scissors className="size-3.5 text-muted-foreground" />
|
|
143
|
+
</span>
|
|
144
|
+
{t('Shift + hover a wire shows x - click to delete it', { note: 'recipe.calc.help.edge_cut' })}
|
|
145
|
+
</li>
|
|
146
|
+
</ul>
|
|
147
|
+
</section>
|
|
148
|
+
<section className="space-y-1.5">
|
|
149
|
+
<h4 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/70">
|
|
150
|
+
{t('Actions', { note: 'recipe.calc.help.sec_actions' })}
|
|
151
|
+
</h4>
|
|
152
|
+
<ul className="space-y-1.5 text-xs text-muted-foreground">
|
|
153
|
+
<li className="flex items-center gap-2">
|
|
154
|
+
<span className="flex w-5 shrink-0 items-center justify-center">
|
|
155
|
+
<ChevronsUpDown className="size-3.5 text-muted-foreground" />
|
|
156
|
+
</span>
|
|
157
|
+
{t('Recipe/tag member: open picker', { note: 'recipe.calc.help.chip' })}
|
|
158
|
+
</li>
|
|
159
|
+
<li className="flex items-center gap-2">
|
|
160
|
+
<span className="flex w-5 shrink-0 items-center justify-center">
|
|
161
|
+
<Eye className="size-3.5 text-muted-foreground" />
|
|
162
|
+
</span>
|
|
163
|
+
{t('Recipe preview', { note: 'recipe.calc.help.hover' })}
|
|
164
|
+
</li>
|
|
165
|
+
<li className="flex items-center gap-2">
|
|
166
|
+
<span className="flex w-5 shrink-0 items-center justify-center">
|
|
167
|
+
<SquareMousePointer className="size-3.5 text-muted-foreground" />
|
|
168
|
+
</span>
|
|
169
|
+
{t('Node context menu', { note: 'recipe.calc.help.ctx' })}
|
|
170
|
+
</li>
|
|
171
|
+
<li className="flex items-center gap-2">
|
|
172
|
+
<span className="flex w-5 shrink-0 items-center justify-center">
|
|
173
|
+
<Settings2 className="size-3.5 text-muted-foreground" />
|
|
174
|
+
</span>
|
|
175
|
+
{t('View modes', { note: 'recipe.calc.help.modes' })}
|
|
176
|
+
</li>
|
|
177
|
+
<li className="flex items-center gap-2">
|
|
178
|
+
<span className="flex w-5 shrink-0 items-center justify-center">
|
|
179
|
+
<Move className="size-3.5 text-muted-foreground" />
|
|
180
|
+
</span>
|
|
181
|
+
{t('Pan and zoom', { note: 'recipe.calc.help.nav' })}
|
|
182
|
+
</li>
|
|
183
|
+
</ul>
|
|
184
|
+
</section>
|
|
185
|
+
</PopoverContent>
|
|
186
|
+
</Popover>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useDeferredValue, useMemo } from 'react';
|
|
4
|
+
import { useTranslations } from '@terrafirmagreg-team/translate/hooks/use-translations';
|
|
5
|
+
import { EmptyState } from '@terrafirmagreg-team/ui/components/blocks/page/empty-state';
|
|
6
|
+
import { LeftSidebarPortal } from '@terrafirmagreg-team/ui/components/layouts/left-sidebar';
|
|
7
|
+
import { CalcFlow } from '@terrafirmagreg-team/recipe/components/views/calc/flow';
|
|
8
|
+
import { CalcSidebar } from '@terrafirmagreg-team/recipe/components/views/calc/sidebar';
|
|
9
|
+
import { CalcHistoryList } from '@terrafirmagreg-team/recipe/components/views/calc/trees';
|
|
10
|
+
import { CalcActions } from '@terrafirmagreg-team/recipe/components/views/calc/calculator/actions';
|
|
11
|
+
import { useCalcFocus } from '@terrafirmagreg-team/recipe/hooks/calc/use-focus';
|
|
12
|
+
import { useCalcReset } from '@terrafirmagreg-team/recipe/hooks/calc/use-reset';
|
|
13
|
+
import { useCalcAmounts } from '@terrafirmagreg-team/recipe/hooks/calc/use-amounts';
|
|
14
|
+
import { useCalcActions } from '@terrafirmagreg-team/recipe/hooks/calc/use-actions';
|
|
15
|
+
import type { RecipeSession } from '@terrafirmagreg-team/recipe/types';
|
|
16
|
+
import { useCalcData } from '@terrafirmagreg-team/recipe/hooks/calc/use-data';
|
|
17
|
+
import { useCalcViewMode } from '@terrafirmagreg-team/recipe/hooks/calc/use-view-mode';
|
|
18
|
+
import { useCalcFarMode } from '@terrafirmagreg-team/recipe/hooks/calc/use-far-mode';
|
|
19
|
+
|
|
20
|
+
export function Calculator({ session }: { session: RecipeSession }) {
|
|
21
|
+
const t = useTranslations();
|
|
22
|
+
const { dataset, lang } = session;
|
|
23
|
+
const [mode, setMode] = useCalcViewMode();
|
|
24
|
+
const [farCards, setFarCards] = useCalcFarMode();
|
|
25
|
+
const {
|
|
26
|
+
cards,
|
|
27
|
+
wires,
|
|
28
|
+
tagSelections,
|
|
29
|
+
tabs,
|
|
30
|
+
activeTabId,
|
|
31
|
+
history,
|
|
32
|
+
openTab,
|
|
33
|
+
openEntry,
|
|
34
|
+
closeTab,
|
|
35
|
+
newTab,
|
|
36
|
+
renameTab,
|
|
37
|
+
setTabIcon,
|
|
38
|
+
removeHistory,
|
|
39
|
+
recipeDefaults,
|
|
40
|
+
setRecipeDefault,
|
|
41
|
+
getRecipesForItem,
|
|
42
|
+
ensureMaterial,
|
|
43
|
+
nodes,
|
|
44
|
+
flows,
|
|
45
|
+
summary,
|
|
46
|
+
persist,
|
|
47
|
+
encodeShare,
|
|
48
|
+
recipeBody,
|
|
49
|
+
settled,
|
|
50
|
+
} = useCalcData(session);
|
|
51
|
+
const deferredNodes = useDeferredValue(nodes);
|
|
52
|
+
const deferredFlows = useDeferredValue(flows);
|
|
53
|
+
const deferredLangMap = useDeferredValue(session.langMap);
|
|
54
|
+
const { focusNodeRef, onFocusNode, focusNodeId, setFocusNodeId } =
|
|
55
|
+
useCalcFocus(cards);
|
|
56
|
+
const { resetGen } = useCalcReset(activeTabId);
|
|
57
|
+
const { displayCards, flushAmounts, onCrafts } = useCalcAmounts({
|
|
58
|
+
cards,
|
|
59
|
+
wires,
|
|
60
|
+
tagSelections,
|
|
61
|
+
persist,
|
|
62
|
+
});
|
|
63
|
+
const {
|
|
64
|
+
shared,
|
|
65
|
+
onShare,
|
|
66
|
+
flowData,
|
|
67
|
+
onMoveCards,
|
|
68
|
+
onClear,
|
|
69
|
+
} = useCalcActions({
|
|
70
|
+
dataset,
|
|
71
|
+
lang,
|
|
72
|
+
cards: displayCards,
|
|
73
|
+
wires,
|
|
74
|
+
tagSelections,
|
|
75
|
+
persist,
|
|
76
|
+
recipeDefaults,
|
|
77
|
+
setRecipeDefault,
|
|
78
|
+
recipeBody,
|
|
79
|
+
encodeShare,
|
|
80
|
+
flushAmounts,
|
|
81
|
+
setFocusNodeId,
|
|
82
|
+
nodes: deferredNodes,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const iconItems = useMemo(
|
|
86
|
+
() => [
|
|
87
|
+
...(session.index?.items ?? []).map(
|
|
88
|
+
(id) => ({ id, kind: 'item' as const }),
|
|
89
|
+
),
|
|
90
|
+
...(session.index?.fluids ?? []).map(
|
|
91
|
+
(id) => ({ id, kind: 'fluid' as const }),
|
|
92
|
+
),
|
|
93
|
+
],
|
|
94
|
+
[session.index],
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const data = useMemo(
|
|
98
|
+
() => ({ ...flowData, onCrafts, getRecipesForItem, ensureMaterial }),
|
|
99
|
+
[flowData, onCrafts, getRecipesForItem, ensureMaterial],
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<>
|
|
104
|
+
<LeftSidebarPortal>
|
|
105
|
+
<CalcSidebar
|
|
106
|
+
dataset={dataset}
|
|
107
|
+
lang={lang}
|
|
108
|
+
langMap={deferredLangMap}
|
|
109
|
+
summary={summary}
|
|
110
|
+
onFocus={onFocusNode}
|
|
111
|
+
trees={{
|
|
112
|
+
tabs,
|
|
113
|
+
activeId: activeTabId,
|
|
114
|
+
history,
|
|
115
|
+
iconItems,
|
|
116
|
+
onOpen: openTab,
|
|
117
|
+
onOpenEntry: openEntry,
|
|
118
|
+
onClose: closeTab,
|
|
119
|
+
onRename: renameTab,
|
|
120
|
+
onSetIcon: setTabIcon,
|
|
121
|
+
onNew: newTab,
|
|
122
|
+
onRemoveHistory: removeHistory,
|
|
123
|
+
}}
|
|
124
|
+
/>
|
|
125
|
+
</LeftSidebarPortal>
|
|
126
|
+
<CalcFlow
|
|
127
|
+
nodes={deferredNodes}
|
|
128
|
+
wires={wires}
|
|
129
|
+
flows={deferredFlows}
|
|
130
|
+
cards={displayCards}
|
|
131
|
+
dataset={dataset}
|
|
132
|
+
session={session}
|
|
133
|
+
data={data}
|
|
134
|
+
mode={mode}
|
|
135
|
+
onModeChange={setMode}
|
|
136
|
+
farCards={farCards}
|
|
137
|
+
onFarCardsChange={setFarCards}
|
|
138
|
+
treeReady={settled}
|
|
139
|
+
resetGen={resetGen}
|
|
140
|
+
canvasId={activeTabId ?? undefined}
|
|
141
|
+
onRelayout={onMoveCards}
|
|
142
|
+
actions={
|
|
143
|
+
<CalcActions
|
|
144
|
+
session={session}
|
|
145
|
+
shared={shared}
|
|
146
|
+
onShare={onShare}
|
|
147
|
+
hasTargets={displayCards.length > 0}
|
|
148
|
+
onClear={onClear}
|
|
149
|
+
/>
|
|
150
|
+
}
|
|
151
|
+
focusRef={focusNodeRef}
|
|
152
|
+
focusNodeId={focusNodeId}
|
|
153
|
+
onFocusedNode={() => setFocusNodeId(null)}
|
|
154
|
+
empty={
|
|
155
|
+
<div className="scrollbar-thin absolute inset-0 z-10 grid place-items-center overflow-auto p-4">
|
|
156
|
+
<div className="flex flex-col items-center gap-4">
|
|
157
|
+
<EmptyState variant="empty">
|
|
158
|
+
{t('Click an item in the catalog to add a card.', { note: 'recipe.calc.empty' })}
|
|
159
|
+
</EmptyState>
|
|
160
|
+
<CalcHistoryList
|
|
161
|
+
history={history}
|
|
162
|
+
onOpen={openEntry}
|
|
163
|
+
meta={{ dataset, lang, langMap: session.langMap }}
|
|
164
|
+
/>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
}
|
|
168
|
+
/>
|
|
169
|
+
</>
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { memo, useEffect, useRef } from 'react';
|
|
4
|
+
import { useStore } from '@terrafirmagreg-team/ui/lib/canvas/xyflow';
|
|
5
|
+
import {
|
|
6
|
+
CALC_NODE_H,
|
|
7
|
+
CALC_NODE_W,
|
|
8
|
+
type AnyCalcFlowNode,
|
|
9
|
+
} from '@terrafirmagreg-team/recipe/lib/calc/flow';
|
|
10
|
+
|
|
11
|
+
const cssVar = (name: string, fallback: string) =>
|
|
12
|
+
getComputedStyle(document.documentElement)
|
|
13
|
+
.getPropertyValue(name)
|
|
14
|
+
.trim() || fallback;
|
|
15
|
+
|
|
16
|
+
export const CalcDotOverlay = memo(function CalcDotOverlay({
|
|
17
|
+
nodes,
|
|
18
|
+
focusId,
|
|
19
|
+
}: {
|
|
20
|
+
nodes: AnyCalcFlowNode[];
|
|
21
|
+
focusId: string | null;
|
|
22
|
+
}) {
|
|
23
|
+
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
24
|
+
const tx = useStore((s) => s.transform[0]);
|
|
25
|
+
const ty = useStore((s) => s.transform[1]);
|
|
26
|
+
const zoom = useStore((s) => s.transform[2]);
|
|
27
|
+
const w = useStore((s) => s.width);
|
|
28
|
+
const h = useStore((s) => s.height);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
const canvas = canvasRef.current;
|
|
32
|
+
if (!canvas || !w || !h || !zoom) return;
|
|
33
|
+
const dpr = window.devicePixelRatio || 1;
|
|
34
|
+
const pw = Math.round(w * dpr);
|
|
35
|
+
const ph = Math.round(h * dpr);
|
|
36
|
+
if (canvas.width !== pw || canvas.height !== ph) {
|
|
37
|
+
canvas.width = pw;
|
|
38
|
+
canvas.height = ph;
|
|
39
|
+
}
|
|
40
|
+
const ctx = canvas.getContext('2d');
|
|
41
|
+
if (!ctx) return;
|
|
42
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
43
|
+
ctx.clearRect(0, 0, w, h);
|
|
44
|
+
const card = cssVar('--color-card', '#17171a');
|
|
45
|
+
const border = cssVar('--color-border', '#3f3f46');
|
|
46
|
+
const primary = cssVar('--color-primary', '#00c8c8');
|
|
47
|
+
const danger = cssVar('--color-red-500', '#ef4444');
|
|
48
|
+
ctx.lineWidth = 1;
|
|
49
|
+
for (const node of nodes) {
|
|
50
|
+
const nw = node.width ?? CALC_NODE_W;
|
|
51
|
+
const nh = node.height ?? CALC_NODE_H;
|
|
52
|
+
const x = (node.position.x - nw / 2) * zoom + tx;
|
|
53
|
+
const y = (node.position.y - nh / 2) * zoom + ty;
|
|
54
|
+
const rw = nw * zoom;
|
|
55
|
+
const rh = nh * zoom;
|
|
56
|
+
if (x + rw < 0 || x > w || y + rh < 0 || y > h) continue;
|
|
57
|
+
const { node: calc } = node.data;
|
|
58
|
+
const stroke =
|
|
59
|
+
node.id === focusId || calc.recipe
|
|
60
|
+
? primary
|
|
61
|
+
: calc.cycle
|
|
62
|
+
? danger
|
|
63
|
+
: border;
|
|
64
|
+
ctx.fillStyle = card;
|
|
65
|
+
ctx.fillRect(x, y, rw, rh);
|
|
66
|
+
ctx.strokeStyle = stroke;
|
|
67
|
+
ctx.strokeRect(x + 0.5, y + 0.5, Math.max(rw - 1, 0), Math.max(rh - 1, 0));
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<canvas
|
|
73
|
+
ref={canvasRef}
|
|
74
|
+
aria-hidden
|
|
75
|
+
className="pointer-events-none absolute inset-0 z-10"
|
|
76
|
+
style={{ width: w, height: h }}
|
|
77
|
+
/>
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, type KeyboardEvent } from 'react';
|
|
4
|
+
import {
|
|
5
|
+
createChevronEdge,
|
|
6
|
+
type ChevronEdgePathContext,
|
|
7
|
+
} from '@terrafirmagreg-team/ui/components/blocks/canvas/chevron-edge';
|
|
8
|
+
import { getSmoothStepPath } from '@terrafirmagreg-team/ui/lib/canvas/xyflow';
|
|
9
|
+
import {
|
|
10
|
+
roundedPolylinePath,
|
|
11
|
+
svgPathToPoints,
|
|
12
|
+
} from '@terrafirmagreg-team/ui/lib/canvas/edge';
|
|
13
|
+
import { Entry } from '@terrafirmagreg-team/game/components/entry/entry';
|
|
14
|
+
import type { DatasetRef } from '@terrafirmagreg-team/sites/location';
|
|
15
|
+
import { useCalcSession } from '@terrafirmagreg-team/recipe/hooks/calc/use-session';
|
|
16
|
+
import { recipeItemName } from '@terrafirmagreg-team/recipe/lib/client-index';
|
|
17
|
+
import { formatCount } from '@terrafirmagreg-team/ui/lib/utils/format';
|
|
18
|
+
import type { FlowPoint } from '@terrafirmagreg-team/ui/types/canvas';
|
|
19
|
+
import type { CalcWire } from '@terrafirmagreg-team/recipe/types/calc';
|
|
20
|
+
|
|
21
|
+
function EdgeAmountLabel({ ctx }: { ctx: ChevronEdgePathContext }) {
|
|
22
|
+
const wire = ctx.data?.wire as CalcWire | undefined;
|
|
23
|
+
const flow = ctx.data?.flow as number | undefined;
|
|
24
|
+
const matId = ctx.data?.matId as string | undefined;
|
|
25
|
+
const matKind = ctx.data?.matKind as 'item' | 'fluid' | undefined;
|
|
26
|
+
const dataset = ctx.data?.dataset as DatasetRef | undefined;
|
|
27
|
+
const onWireAmount = ctx.data?.onWireAmount as
|
|
28
|
+
| ((from: string, to: string, input: number, amount: number | null) => void)
|
|
29
|
+
| undefined;
|
|
30
|
+
const onUnwire = ctx.data?.onUnwire as
|
|
31
|
+
| ((from: string, to: string, input: number, out?: number) => void)
|
|
32
|
+
| undefined;
|
|
33
|
+
const session = useCalcSession();
|
|
34
|
+
const [edit, setEdit] = useState<string | null>(null);
|
|
35
|
+
if (!wire || flow == null) return null;
|
|
36
|
+
const cap = wire.amount;
|
|
37
|
+
|
|
38
|
+
const commit = () => {
|
|
39
|
+
if (edit == null) return;
|
|
40
|
+
const raw = edit.trim();
|
|
41
|
+
const value = raw === '' ? null : Math.max(0, Number(raw));
|
|
42
|
+
onWireAmount?.(
|
|
43
|
+
wire.from,
|
|
44
|
+
wire.to,
|
|
45
|
+
wire.input,
|
|
46
|
+
value != null && Number.isFinite(value) ? value : null,
|
|
47
|
+
);
|
|
48
|
+
setEdit(null);
|
|
49
|
+
};
|
|
50
|
+
const onKey = (e: KeyboardEvent<HTMLInputElement>) => {
|
|
51
|
+
if (e.key === 'Enter') commit();
|
|
52
|
+
if (e.key === 'Escape') setEdit(null);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
if (edit != null) {
|
|
56
|
+
return (
|
|
57
|
+
<input
|
|
58
|
+
autoFocus
|
|
59
|
+
className="calc-edge-label__input"
|
|
60
|
+
value={edit}
|
|
61
|
+
onChange={(e) => setEdit(e.target.value)}
|
|
62
|
+
onBlur={commit}
|
|
63
|
+
onKeyDown={onKey}
|
|
64
|
+
onClick={(e) => e.stopPropagation()}
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
const matName =
|
|
69
|
+
matId && session?.names
|
|
70
|
+
? recipeItemName(session.names, session.lang, matId)
|
|
71
|
+
: matId;
|
|
72
|
+
return (
|
|
73
|
+
<button
|
|
74
|
+
type="button"
|
|
75
|
+
className="calc-edge-label"
|
|
76
|
+
title={`${matName ?? ''} ${flow}${cap != null ? ` / ${cap}` : ''}`}
|
|
77
|
+
onClick={(e) => {
|
|
78
|
+
e.stopPropagation();
|
|
79
|
+
if (e.shiftKey) {
|
|
80
|
+
onUnwire?.(wire.from, wire.to, wire.input, wire.out);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
setEdit(cap != null ? String(cap) : '');
|
|
84
|
+
}}
|
|
85
|
+
>
|
|
86
|
+
{matId && matKind && dataset ? (
|
|
87
|
+
<Entry
|
|
88
|
+
id={matId}
|
|
89
|
+
kind={matKind}
|
|
90
|
+
dataset={dataset}
|
|
91
|
+
size="sm"
|
|
92
|
+
href={false}
|
|
93
|
+
tooltip={false}
|
|
94
|
+
/>
|
|
95
|
+
) : null}
|
|
96
|
+
{matName ? (
|
|
97
|
+
<span className="calc-edge-label__name">{matName}</span>
|
|
98
|
+
) : null}
|
|
99
|
+
<span className="calc-edge-label__amount">
|
|
100
|
+
×{formatCount(flow, 1_000)}
|
|
101
|
+
{cap != null ? `/${formatCount(cap, 1_000)}` : ''}
|
|
102
|
+
</span>
|
|
103
|
+
<span className="calc-edge-label__cut" aria-hidden>
|
|
104
|
+
×
|
|
105
|
+
</span>
|
|
106
|
+
</button>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export const CalcEdge = createChevronEdge({
|
|
111
|
+
renderLabel: (ctx) => <EdgeAmountLabel ctx={ctx} />,
|
|
112
|
+
labelVisibility: 'hover',
|
|
113
|
+
resolvePath: (ctx) => {
|
|
114
|
+
const points = ctx.data?.points;
|
|
115
|
+
if (Array.isArray(points) && points.length >= 2) {
|
|
116
|
+
const pts = (points as FlowPoint[]).slice();
|
|
117
|
+
const oldA = pts[0]!;
|
|
118
|
+
const oldB = pts[pts.length - 1]!;
|
|
119
|
+
pts[0] = { x: ctx.sourceX, y: ctx.sourceY };
|
|
120
|
+
pts[pts.length - 1] = { x: ctx.targetX, y: ctx.targetY };
|
|
121
|
+
if (pts.length > 2) {
|
|
122
|
+
const s2 = pts[1]!;
|
|
123
|
+
const sL = pts[pts.length - 2]!;
|
|
124
|
+
if (s2.x === oldA.x) s2.x = ctx.sourceX;
|
|
125
|
+
if (s2.y === oldA.y) s2.y = ctx.sourceY;
|
|
126
|
+
if (sL.x === oldB.x) sL.x = ctx.targetX;
|
|
127
|
+
if (sL.y === oldB.y) sL.y = ctx.targetY;
|
|
128
|
+
}
|
|
129
|
+
if (
|
|
130
|
+
pts.length === 2 &&
|
|
131
|
+
pts[0].x !== pts[1].x &&
|
|
132
|
+
pts[0].y !== pts[1].y
|
|
133
|
+
) {
|
|
134
|
+
const mid = (pts[0].x + pts[1].x) / 2;
|
|
135
|
+
pts.splice(1, 0, { x: mid, y: pts[0].y }, { x: mid, y: pts[1].y });
|
|
136
|
+
}
|
|
137
|
+
return svgPathToPoints(roundedPolylinePath(pts, 12), 8);
|
|
138
|
+
}
|
|
139
|
+
return svgPathToPoints(
|
|
140
|
+
getSmoothStepPath({
|
|
141
|
+
sourceX: ctx.sourceX,
|
|
142
|
+
sourceY: ctx.sourceY,
|
|
143
|
+
sourcePosition: ctx.sourcePosition,
|
|
144
|
+
targetX: ctx.targetX,
|
|
145
|
+
targetY: ctx.targetY,
|
|
146
|
+
targetPosition: ctx.targetPosition,
|
|
147
|
+
borderRadius: 10,
|
|
148
|
+
})[0],
|
|
149
|
+
);
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
|