@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,20 @@
|
|
|
1
|
+
export {
|
|
2
|
+
migrateCalcState,
|
|
3
|
+
newCalcEntry,
|
|
4
|
+
type CalcTreeEntry,
|
|
5
|
+
} from './entry';
|
|
6
|
+
export { calcStorageKey } from './keys';
|
|
7
|
+
export {
|
|
8
|
+
readCalcActive,
|
|
9
|
+
readCalcHistory,
|
|
10
|
+
resolveCalcActive,
|
|
11
|
+
readCalcStorage,
|
|
12
|
+
readCalcTabs,
|
|
13
|
+
removeCalcHistory,
|
|
14
|
+
subscribeCalcStorage,
|
|
15
|
+
updateCalcTab,
|
|
16
|
+
writeCalcActive,
|
|
17
|
+
writeCalcStorage,
|
|
18
|
+
writeCalcTabs,
|
|
19
|
+
} from './store';
|
|
20
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { DatasetRef } from '@terrafirmagreg-team/sites/location';
|
|
2
|
+
export const CALC_STATE_EVENT = 'tfg:calc-state';
|
|
3
|
+
const TAB_KEY = 'tfg:calc-tab';
|
|
4
|
+
const ACTIVE_PREFIX = 'calc:active:';
|
|
5
|
+
const HIST_PREFIX = 'calc:hist:';
|
|
6
|
+
|
|
7
|
+
let tabId: string | null = null;
|
|
8
|
+
|
|
9
|
+
function calcTabId(): string {
|
|
10
|
+
if (tabId) return tabId;
|
|
11
|
+
try {
|
|
12
|
+
tabId = window.sessionStorage.getItem(TAB_KEY);
|
|
13
|
+
if (!tabId) {
|
|
14
|
+
tabId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
15
|
+
window.sessionStorage.setItem(TAB_KEY, tabId);
|
|
16
|
+
}
|
|
17
|
+
} catch {
|
|
18
|
+
tabId ??= `tab-${Math.random().toString(36).slice(2, 8)}`;
|
|
19
|
+
}
|
|
20
|
+
return tabId;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function calcStorageKey(section: string, dataset: DatasetRef): string {
|
|
24
|
+
void calcTabId();
|
|
25
|
+
return `calc:tabs:${section}:${dataset.id}/${dataset.version ?? ''}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function activeKey(key: string): string {
|
|
29
|
+
return `${ACTIVE_PREFIX}${key}@${calcTabId()}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function historyKey(key: string): string {
|
|
33
|
+
return `${HIST_PREFIX}${key}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const emitCalcStorage = () =>
|
|
37
|
+
window.dispatchEvent(new Event(CALC_STATE_EVENT));
|
|
38
|
+
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import type { CalculatorState } from '@terrafirmagreg-team/recipe/types/calc';
|
|
2
|
+
import {
|
|
3
|
+
capHistory,
|
|
4
|
+
EMPTY_ENTRIES,
|
|
5
|
+
isCalcHistory,
|
|
6
|
+
isCalcTabs,
|
|
7
|
+
migrateCalcState,
|
|
8
|
+
newCalcEntry,
|
|
9
|
+
type CalcTreeEntry,
|
|
10
|
+
} from './entry';
|
|
11
|
+
import {
|
|
12
|
+
activeKey,
|
|
13
|
+
CALC_STATE_EVENT,
|
|
14
|
+
emitCalcStorage,
|
|
15
|
+
historyKey,
|
|
16
|
+
} from './keys';
|
|
17
|
+
|
|
18
|
+
function readJSON<T>(raw: string | null, guard: (v: unknown) => v is T): T | null {
|
|
19
|
+
if (raw === null) return null;
|
|
20
|
+
try {
|
|
21
|
+
const value = JSON.parse(raw) as unknown;
|
|
22
|
+
return guard(value) ? value : null;
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const snapshots = new Map<string, { raw: string | null; value: unknown }>();
|
|
29
|
+
|
|
30
|
+
function snap<T>(
|
|
31
|
+
storeKey: string,
|
|
32
|
+
raw: string | null,
|
|
33
|
+
guard: (v: unknown) => v is T,
|
|
34
|
+
normalize?: (value: T) => void,
|
|
35
|
+
): T | null {
|
|
36
|
+
const hit = snapshots.get(storeKey);
|
|
37
|
+
if (hit && hit.raw === raw) return hit.value as T | null;
|
|
38
|
+
const value = readJSON(raw, guard);
|
|
39
|
+
if (value) normalize?.(value);
|
|
40
|
+
snapshots.set(storeKey, { raw, value });
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const migrateEntries = (entries: CalcTreeEntry[]): void => {
|
|
45
|
+
for (const entry of entries) {
|
|
46
|
+
entry.state = migrateCalcState(entry.state);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export function readCalcTabs(key: string): CalcTreeEntry[] {
|
|
51
|
+
if (typeof window === 'undefined') return [];
|
|
52
|
+
return (
|
|
53
|
+
snap(key, window.localStorage.getItem(key), isCalcTabs, migrateEntries) ??
|
|
54
|
+
EMPTY_ENTRIES
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function readCalcActive(key: string): string | null {
|
|
59
|
+
if (typeof window === 'undefined') return null;
|
|
60
|
+
try {
|
|
61
|
+
return window.sessionStorage.getItem(activeKey(key)) || null;
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function readCalcHistory(key: string): CalcTreeEntry[] {
|
|
68
|
+
if (typeof window === 'undefined') return [];
|
|
69
|
+
const hKey = historyKey(key);
|
|
70
|
+
return (
|
|
71
|
+
snap(hKey, window.localStorage.getItem(hKey), isCalcHistory, migrateEntries) ??
|
|
72
|
+
EMPTY_ENTRIES
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function writeTabsRaw(key: string, tabs: CalcTreeEntry[]): void {
|
|
77
|
+
window.localStorage.setItem(key, JSON.stringify(tabs));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function writeHistoryRaw(key: string, entries: CalcTreeEntry[]): void {
|
|
81
|
+
window.localStorage.setItem(
|
|
82
|
+
historyKey(key),
|
|
83
|
+
JSON.stringify(capHistory(entries)),
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function syncHistory(key: string, tabs: CalcTreeEntry[]): void {
|
|
88
|
+
const live = tabs.filter((tab) => tab.state.cards.length);
|
|
89
|
+
if (!live.length) return;
|
|
90
|
+
const prev = readCalcHistory(key);
|
|
91
|
+
const ids = new Set(live.map((tab) => tab.id));
|
|
92
|
+
writeHistoryRaw(key, [...live, ...prev.filter((e) => !ids.has(e.id))]);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function writeCalcTabs(
|
|
96
|
+
key: string,
|
|
97
|
+
tabs: CalcTreeEntry[],
|
|
98
|
+
active: string | null,
|
|
99
|
+
): void {
|
|
100
|
+
if (typeof window === 'undefined') return;
|
|
101
|
+
try {
|
|
102
|
+
writeTabsRaw(key, tabs);
|
|
103
|
+
syncHistory(key, tabs);
|
|
104
|
+
window.sessionStorage.setItem(activeKey(key), active ?? '');
|
|
105
|
+
} catch {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
emitCalcStorage();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function writeCalcActive(key: string, id: string | null): void {
|
|
112
|
+
if (typeof window === 'undefined') return;
|
|
113
|
+
try {
|
|
114
|
+
window.sessionStorage.setItem(activeKey(key), id ?? '');
|
|
115
|
+
} catch {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
emitCalcStorage();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function updateCalcTab(
|
|
122
|
+
key: string,
|
|
123
|
+
id: string,
|
|
124
|
+
update: (entry: CalcTreeEntry) => CalcTreeEntry,
|
|
125
|
+
): void {
|
|
126
|
+
if (typeof window === 'undefined') return;
|
|
127
|
+
const tabs = readCalcTabs(key);
|
|
128
|
+
const index = tabs.findIndex((tab) => tab.id === id);
|
|
129
|
+
if (index < 0) return;
|
|
130
|
+
const next = tabs.slice();
|
|
131
|
+
next[index] = update(next[index]!);
|
|
132
|
+
try {
|
|
133
|
+
writeTabsRaw(key, next);
|
|
134
|
+
syncHistory(key, next);
|
|
135
|
+
} catch {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
emitCalcStorage();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function removeCalcHistory(key: string, id: string): void {
|
|
142
|
+
if (typeof window === 'undefined') return;
|
|
143
|
+
try {
|
|
144
|
+
writeHistoryRaw(
|
|
145
|
+
key,
|
|
146
|
+
readCalcHistory(key).filter((entry) => entry.id !== id),
|
|
147
|
+
);
|
|
148
|
+
const tabs = readCalcTabs(key).filter((tab) => tab.id !== id);
|
|
149
|
+
writeTabsRaw(key, tabs);
|
|
150
|
+
if (readCalcActive(key) === id) {
|
|
151
|
+
window.sessionStorage.setItem(activeKey(key), '');
|
|
152
|
+
}
|
|
153
|
+
} catch {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
emitCalcStorage();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function resolveCalcActive(
|
|
160
|
+
tabs: readonly CalcTreeEntry[],
|
|
161
|
+
raw: string | null,
|
|
162
|
+
): string | null {
|
|
163
|
+
return (tabs.find((tab) => tab.id === raw) ?? tabs[0])?.id ?? null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function readCalcStorage(key: string): CalculatorState | null {
|
|
167
|
+
const tabs = readCalcTabs(key);
|
|
168
|
+
const active = resolveCalcActive(tabs, readCalcActive(key));
|
|
169
|
+
return tabs.find((tab) => tab.id === active)?.state ?? null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function writeCalcStorage(
|
|
173
|
+
key: string,
|
|
174
|
+
state: CalculatorState,
|
|
175
|
+
): void {
|
|
176
|
+
const tabs = readCalcTabs(key);
|
|
177
|
+
const active = resolveCalcActive(tabs, readCalcActive(key));
|
|
178
|
+
const index = tabs.findIndex((tab) => tab.id === active);
|
|
179
|
+
if (index >= 0) {
|
|
180
|
+
const next = tabs.slice();
|
|
181
|
+
next[index] = { ...next[index]!, savedAt: Date.now(), state };
|
|
182
|
+
writeCalcTabs(key, next, active);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const entry = newCalcEntry(state);
|
|
186
|
+
writeCalcTabs(key, [...tabs, entry], entry.id);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function subscribeCalcStorage(listener: () => void): () => void {
|
|
190
|
+
if (typeof window === 'undefined') return () => {};
|
|
191
|
+
window.addEventListener(CALC_STATE_EVENT, listener);
|
|
192
|
+
window.addEventListener('storage', listener);
|
|
193
|
+
return () => {
|
|
194
|
+
window.removeEventListener(CALC_STATE_EVENT, listener);
|
|
195
|
+
window.removeEventListener('storage', listener);
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { CalcKind, CalcMaterial } from '@terrafirmagreg-team/recipe/types/calc';
|
|
2
|
+
|
|
3
|
+
export function materialKey(kind: CalcKind, id: string): string {
|
|
4
|
+
return `${kind}:${id}`;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function mergeAmounts(list: CalcMaterial[]): CalcMaterial[] {
|
|
8
|
+
const map = new Map<string, CalcMaterial>();
|
|
9
|
+
for (const row of list) {
|
|
10
|
+
const key = materialKey(row.kind, row.id);
|
|
11
|
+
const existing = map.get(key);
|
|
12
|
+
if (existing) existing.amount += row.amount;
|
|
13
|
+
else map.set(key, { ...row });
|
|
14
|
+
}
|
|
15
|
+
return [...map.values()];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function deduplicateMaterials(list: CalcMaterial[]): CalcMaterial[] {
|
|
19
|
+
const seen = new Set<string>();
|
|
20
|
+
const result: CalcMaterial[] = [];
|
|
21
|
+
for (const row of list) {
|
|
22
|
+
const key = materialKey(row.kind, row.id);
|
|
23
|
+
if (seen.has(key)) continue;
|
|
24
|
+
seen.add(key);
|
|
25
|
+
result.push({ ...row });
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { Recipe, RecipeIngredient } from '@terrafirmagreg-team/recipe/lib/data/dto';
|
|
2
|
+
import type {
|
|
3
|
+
CalcKind,
|
|
4
|
+
CalcMaterial,
|
|
5
|
+
CalcRecipe,
|
|
6
|
+
CalcRecipeInput,
|
|
7
|
+
} from '@terrafirmagreg-team/recipe/types/calc';
|
|
8
|
+
|
|
9
|
+
function slotKind(slot: { kind: RecipeIngredient['kind'] }): CalcKind {
|
|
10
|
+
return slot.kind === 'fluid' ? 'fluid' : 'item';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const GT_TIERS: Record<string, number> = {
|
|
14
|
+
ULV: 0,
|
|
15
|
+
LV: 1,
|
|
16
|
+
MV: 2,
|
|
17
|
+
HV: 3,
|
|
18
|
+
EV: 4,
|
|
19
|
+
IV: 5,
|
|
20
|
+
LuV: 6,
|
|
21
|
+
ZPM: 7,
|
|
22
|
+
UV: 8,
|
|
23
|
+
UHV: 9,
|
|
24
|
+
UEV: 10,
|
|
25
|
+
UIV: 11,
|
|
26
|
+
UXV: 12,
|
|
27
|
+
OpV: 13,
|
|
28
|
+
MAX: 14,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function recipeTier(recipe: Recipe): number | undefined {
|
|
32
|
+
for (const widget of recipe.widgets) {
|
|
33
|
+
if (widget.type !== 'text' || !widget.component) continue;
|
|
34
|
+
for (const part of [
|
|
35
|
+
widget.component,
|
|
36
|
+
...(widget.component.extra ?? []),
|
|
37
|
+
]) {
|
|
38
|
+
if (part.translate !== 'gtceu.recipe.eu') continue;
|
|
39
|
+
for (const arg of part.with ?? []) {
|
|
40
|
+
if (arg.text && arg.text in GT_TIERS) return GT_TIERS[arg.text];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function toCalcRecipe(recipe: Recipe): CalcRecipe {
|
|
48
|
+
const inputs: CalcRecipeInput[] = recipe.inputs.map((slot) => ({
|
|
49
|
+
id: slot.id,
|
|
50
|
+
kind: slotKind(slot),
|
|
51
|
+
amount: slot.count && slot.count > 0 ? slot.count : 1,
|
|
52
|
+
catalyst: false,
|
|
53
|
+
...(slot.chance !== undefined ? { chance: slot.chance } : {}),
|
|
54
|
+
...(slot.kind === 'tag' ? { tagId: slot.id } : {}),
|
|
55
|
+
}));
|
|
56
|
+
for (const widget of recipe.widgets) {
|
|
57
|
+
if (widget.type !== 'slot' || widget.role !== 'catalyst') continue;
|
|
58
|
+
inputs.push({
|
|
59
|
+
id: widget.id,
|
|
60
|
+
kind: widget.kind === 'fluid' ? 'fluid' : 'item',
|
|
61
|
+
amount: 1,
|
|
62
|
+
catalyst: true,
|
|
63
|
+
...(widget.chance !== undefined ? { chance: widget.chance } : {}),
|
|
64
|
+
...(widget.kind === 'tag' ? { tagId: widget.id } : {}),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
const merged: CalcRecipeInput[] = [];
|
|
68
|
+
for (const input of inputs) {
|
|
69
|
+
const key = `${input.kind}:${input.id}:${input.catalyst}:${input.tagId ?? ''}:${input.chance ?? ''}`;
|
|
70
|
+
const existing = merged.find(
|
|
71
|
+
(row) =>
|
|
72
|
+
`${row.kind}:${row.id}:${row.catalyst}:${row.tagId ?? ''}:${row.chance ?? ''}` ===
|
|
73
|
+
key,
|
|
74
|
+
);
|
|
75
|
+
if (existing) existing.amount += input.amount;
|
|
76
|
+
else merged.push({ ...input });
|
|
77
|
+
}
|
|
78
|
+
const outputs: CalcMaterial[] = recipe.outputs.map((slot) => ({
|
|
79
|
+
id: slot.id,
|
|
80
|
+
kind: slotKind(slot),
|
|
81
|
+
amount: slot.count && slot.count > 0 ? slot.count : 1,
|
|
82
|
+
...(slot.chance !== undefined ? { chance: slot.chance } : {}),
|
|
83
|
+
}));
|
|
84
|
+
const tier = recipeTier(recipe);
|
|
85
|
+
return {
|
|
86
|
+
recipeId: recipe.id,
|
|
87
|
+
category: recipe.category ?? recipe.type,
|
|
88
|
+
inputs: merged,
|
|
89
|
+
outputs,
|
|
90
|
+
...(tier !== undefined ? { tier } : {}),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function recipeDivisor(
|
|
95
|
+
recipe: CalcRecipe,
|
|
96
|
+
materialId: string,
|
|
97
|
+
kind: CalcKind,
|
|
98
|
+
): number {
|
|
99
|
+
const amount = recipe.outputs
|
|
100
|
+
.filter((out) => out.id === materialId && out.kind === kind)
|
|
101
|
+
.reduce((sum, out) => sum + out.amount * (out.chance ?? 1), 0);
|
|
102
|
+
return amount > 0 ? amount : 1;
|
|
103
|
+
}
|
|
104
|
+
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { deflateSync, inflateSync, strFromU8, strToU8 } from 'fflate';
|
|
2
|
+
import type {
|
|
3
|
+
CalcCard,
|
|
4
|
+
CalculatorState,
|
|
5
|
+
CalcWire,
|
|
6
|
+
} from '@terrafirmagreg-team/recipe/types/calc';
|
|
7
|
+
|
|
8
|
+
function encodeUrlSafe(base64: string): string {
|
|
9
|
+
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function decodeUrlSafe(encoded: string): string {
|
|
13
|
+
let result = encoded;
|
|
14
|
+
const pad = result.length % 4;
|
|
15
|
+
if (pad) result += '='.repeat(4 - pad);
|
|
16
|
+
return result.replace(/-/g, '+').replace(/_/g, '/');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function u8ToBinary(u8: Uint8Array): string {
|
|
20
|
+
let bin = '';
|
|
21
|
+
for (let i = 0; i < u8.length; i += 8192) {
|
|
22
|
+
bin += String.fromCharCode(...u8.subarray(i, i + 8192));
|
|
23
|
+
}
|
|
24
|
+
return bin;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function encodeCalcState(state: CalculatorState): string {
|
|
28
|
+
const dict = new Map<string, number>();
|
|
29
|
+
const ref = (id: string): number => {
|
|
30
|
+
const hit = dict.get(id);
|
|
31
|
+
if (hit !== undefined) return hit;
|
|
32
|
+
const idx = dict.size;
|
|
33
|
+
dict.set(id, idx);
|
|
34
|
+
return idx;
|
|
35
|
+
};
|
|
36
|
+
const indexOf = new Map<string, number>();
|
|
37
|
+
const cards = state.cards.map((card, index) => {
|
|
38
|
+
indexOf.set(card.id, index);
|
|
39
|
+
const row = [
|
|
40
|
+
ref(card.materialId),
|
|
41
|
+
card.kind === 'fluid' ? 1 : 0,
|
|
42
|
+
card.recipeId ? ref(card.recipeId) : -1,
|
|
43
|
+
card.crafts,
|
|
44
|
+
Math.round(card.pos?.x ?? 0),
|
|
45
|
+
Math.round(card.pos?.y ?? 0),
|
|
46
|
+
card.pos ? 1 : 0,
|
|
47
|
+
];
|
|
48
|
+
return row;
|
|
49
|
+
});
|
|
50
|
+
const wires = state.wires.flatMap((wire) => {
|
|
51
|
+
const from = indexOf.get(wire.from);
|
|
52
|
+
const to = indexOf.get(wire.to);
|
|
53
|
+
return from === undefined || to === undefined
|
|
54
|
+
? []
|
|
55
|
+
: [[from, to, wire.input, wire.out ?? 0, wire.amount ?? 0]];
|
|
56
|
+
});
|
|
57
|
+
const tagSelections: Record<string, number> = {};
|
|
58
|
+
for (const [tagId, memberId] of Object.entries(state.tagSelections)) {
|
|
59
|
+
if (memberId) tagSelections[String(ref(tagId))] = ref(memberId);
|
|
60
|
+
}
|
|
61
|
+
const payload = {
|
|
62
|
+
v: 2,
|
|
63
|
+
d: [...dict.keys()],
|
|
64
|
+
c: cards,
|
|
65
|
+
w: wires,
|
|
66
|
+
g: tagSelections,
|
|
67
|
+
};
|
|
68
|
+
const packed = deflateSync(strToU8(JSON.stringify(payload)));
|
|
69
|
+
return `x${encodeUrlSafe(btoa(u8ToBinary(packed)))}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function decodeCalcState(encoded: string): CalculatorState | null {
|
|
73
|
+
const text = encoded.trim();
|
|
74
|
+
if (!text.startsWith('x')) return null;
|
|
75
|
+
try {
|
|
76
|
+
const bin = atob(decodeUrlSafe(text.slice(1)));
|
|
77
|
+
const packed = Uint8Array.from(bin, (c) => c.charCodeAt(0));
|
|
78
|
+
const payload = JSON.parse(strFromU8(inflateSync(packed))) as {
|
|
79
|
+
v?: number;
|
|
80
|
+
d?: string[];
|
|
81
|
+
c?: number[][];
|
|
82
|
+
w?: number[][];
|
|
83
|
+
g?: Record<string, number>;
|
|
84
|
+
};
|
|
85
|
+
if (payload.v !== 2 || !Array.isArray(payload.d)) return null;
|
|
86
|
+
const dict = payload.d;
|
|
87
|
+
const id = (idx: unknown): string | null =>
|
|
88
|
+
typeof idx === 'number' && idx >= 0 && idx < dict.length
|
|
89
|
+
? dict[idx]!
|
|
90
|
+
: null;
|
|
91
|
+
const cards: CalcCard[] = [];
|
|
92
|
+
for (const [index, row] of (payload.c ?? []).entries()) {
|
|
93
|
+
if (!Array.isArray(row)) continue;
|
|
94
|
+
const materialId = id(row[0]);
|
|
95
|
+
if (!materialId) continue;
|
|
96
|
+
const kind = row[1] === 1 ? 'fluid' : 'item';
|
|
97
|
+
const recipeId = row[2] === -1 ? null : id(row[2]);
|
|
98
|
+
cards.push({
|
|
99
|
+
id: `c${index}`,
|
|
100
|
+
materialId,
|
|
101
|
+
kind,
|
|
102
|
+
recipeId: recipeId ?? null,
|
|
103
|
+
crafts: Number(row[3]) || 1,
|
|
104
|
+
pos:
|
|
105
|
+
row[6] === 1
|
|
106
|
+
? { x: Number(row[4]) || 0, y: Number(row[5]) || 0 }
|
|
107
|
+
: undefined,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
const wires: CalcWire[] = [];
|
|
111
|
+
for (const row of payload.w ?? []) {
|
|
112
|
+
if (!Array.isArray(row)) continue;
|
|
113
|
+
const from = cards[row[0]!];
|
|
114
|
+
const to = cards[row[1]!];
|
|
115
|
+
if (!from || !to) continue;
|
|
116
|
+
wires.push({
|
|
117
|
+
from: from.id,
|
|
118
|
+
to: to.id,
|
|
119
|
+
input: Number(row[2]) || 0,
|
|
120
|
+
out: Number(row[3]) || undefined,
|
|
121
|
+
amount: Number(row[4]) || undefined,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
const tagSelections: Record<string, string> = {};
|
|
125
|
+
for (const [tagIdx, memberIdx] of Object.entries(payload.g ?? {})) {
|
|
126
|
+
const tagId = id(Number(tagIdx));
|
|
127
|
+
const memberId = id(memberIdx);
|
|
128
|
+
if (tagId && memberId) tagSelections[tagId] = memberId;
|
|
129
|
+
}
|
|
130
|
+
return { cards, wires, tagSelections };
|
|
131
|
+
} catch {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { DatasetRef } from '@terrafirmagreg-team/sites/location';
|
|
2
|
+
import type { RecipeCompactIndex } from '@terrafirmagreg-team/recipe/types';
|
|
3
|
+
import type { FlushHandler, ResolveInput } from './protocol';
|
|
4
|
+
import { runResolve } from './resolve';
|
|
5
|
+
import { resetWorkerState, state } from './state';
|
|
6
|
+
|
|
7
|
+
export const api = {
|
|
8
|
+
async init(
|
|
9
|
+
nextDataset: DatasetRef,
|
|
10
|
+
nextCatalog: RecipeCompactIndex,
|
|
11
|
+
flushHandler: FlushHandler,
|
|
12
|
+
) {
|
|
13
|
+
resetWorkerState(nextDataset, nextCatalog);
|
|
14
|
+
state.onFlush = flushHandler;
|
|
15
|
+
},
|
|
16
|
+
async resolve(input: ResolveInput) {
|
|
17
|
+
state.resolveGen += 1;
|
|
18
|
+
await runResolve(input);
|
|
19
|
+
},
|
|
20
|
+
async invalidate() {
|
|
21
|
+
state.resolveGen += 1;
|
|
22
|
+
state.epoch += 1;
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { CalcKind, CalcRecipe, CalcRecipeSummary } from '@terrafirmagreg-team/recipe/types/calc';
|
|
2
|
+
import { fetchSlices, summarizeRecipes } from '@terrafirmagreg-team/recipe/lib/calc/resolve';
|
|
3
|
+
import type { Pending } from './flush';
|
|
4
|
+
import { state } from './state';
|
|
5
|
+
|
|
6
|
+
export function createWorkerSummaries(
|
|
7
|
+
localRecipes: ReadonlyMap<string, CalcRecipe>,
|
|
8
|
+
) {
|
|
9
|
+
const cache = new Map<string, CalcRecipeSummary[]>();
|
|
10
|
+
return {
|
|
11
|
+
get: (materialId: string, kind: CalcKind): CalcRecipeSummary[] =>
|
|
12
|
+
summarizeRecipes(
|
|
13
|
+
state.outMap,
|
|
14
|
+
localRecipes,
|
|
15
|
+
state.catalog?.types ?? [],
|
|
16
|
+
cache,
|
|
17
|
+
materialId,
|
|
18
|
+
kind,
|
|
19
|
+
),
|
|
20
|
+
clear: () => cache.clear(),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function fetchWanted(
|
|
25
|
+
pending: Pending,
|
|
26
|
+
wanted: Set<string>,
|
|
27
|
+
wantedOut: Set<string>,
|
|
28
|
+
wantedTag: Set<string>,
|
|
29
|
+
localRecipes: Map<string, CalcRecipe>,
|
|
30
|
+
): Promise<boolean> {
|
|
31
|
+
if (!state.catalog) return false;
|
|
32
|
+
const slices = await fetchSlices(state.dataset, state.catalog, {
|
|
33
|
+
recipes: wanted,
|
|
34
|
+
outs: wantedOut,
|
|
35
|
+
tags: wantedTag,
|
|
36
|
+
});
|
|
37
|
+
let progressed = false;
|
|
38
|
+
for (const id of slices.dead) {
|
|
39
|
+
if (state.deadIds.has(id)) continue;
|
|
40
|
+
state.deadIds.add(id);
|
|
41
|
+
pending.dead.add(id);
|
|
42
|
+
pending.dirty = true;
|
|
43
|
+
}
|
|
44
|
+
for (const [id, entry] of slices.recipes) {
|
|
45
|
+
localRecipes.set(id, entry.calc);
|
|
46
|
+
state.bodyMap.set(id, entry.body);
|
|
47
|
+
pending.recipes.set(id, entry.calc);
|
|
48
|
+
pending.bodies.set(id, entry.body);
|
|
49
|
+
pending.dirty = true;
|
|
50
|
+
progressed = true;
|
|
51
|
+
}
|
|
52
|
+
for (const [id, list] of slices.outs) {
|
|
53
|
+
state.outMap.set(id, list);
|
|
54
|
+
pending.out.set(id, list);
|
|
55
|
+
pending.dirty = true;
|
|
56
|
+
progressed = true;
|
|
57
|
+
}
|
|
58
|
+
for (const [id, resolution] of slices.tags) {
|
|
59
|
+
state.tagMap.set(id, resolution);
|
|
60
|
+
pending.tag.set(id, resolution);
|
|
61
|
+
pending.dirty = true;
|
|
62
|
+
progressed = true;
|
|
63
|
+
}
|
|
64
|
+
return progressed;
|
|
65
|
+
}
|
|
66
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Recipe } from '@terrafirmagreg-team/recipe/lib/data/dto';
|
|
2
|
+
import type { RecipeCompactRow } from '@terrafirmagreg-team/recipe/types';
|
|
3
|
+
import type {
|
|
4
|
+
CalcRecipe,
|
|
5
|
+
TagResolution,
|
|
6
|
+
} from '@terrafirmagreg-team/recipe/types/calc';
|
|
7
|
+
import { state } from './state';
|
|
8
|
+
|
|
9
|
+
export function createFlush() {
|
|
10
|
+
return {
|
|
11
|
+
out: new Map<string, RecipeCompactRow[]>(),
|
|
12
|
+
tag: new Map<string, TagResolution>(),
|
|
13
|
+
recipes: new Map<string, CalcRecipe>(),
|
|
14
|
+
bodies: new Map<string, Recipe>(),
|
|
15
|
+
dead: new Set<string>(),
|
|
16
|
+
dirty: false,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type Pending = ReturnType<typeof createFlush>;
|
|
21
|
+
|
|
22
|
+
export function postFlush(pending: Pending) {
|
|
23
|
+
if (!pending.dirty) return;
|
|
24
|
+
pending.dirty = false;
|
|
25
|
+
state.onFlush({
|
|
26
|
+
epoch: state.epoch,
|
|
27
|
+
out: [...pending.out],
|
|
28
|
+
tag: [...pending.tag],
|
|
29
|
+
recipes: [...pending.recipes],
|
|
30
|
+
bodies: [...pending.bodies],
|
|
31
|
+
dead: [...pending.dead],
|
|
32
|
+
});
|
|
33
|
+
pending.out.clear();
|
|
34
|
+
pending.tag.clear();
|
|
35
|
+
pending.recipes.clear();
|
|
36
|
+
pending.bodies.clear();
|
|
37
|
+
pending.dead.clear();
|
|
38
|
+
}
|
|
39
|
+
|