diff --git a/frontend/.gitignore b/frontend/.gitignore
index 087eed2f..06eb0440 100644
--- a/frontend/.gitignore
+++ b/frontend/.gitignore
@@ -20,9 +20,10 @@ app.config.timestamp_*.js
*.launch
.settings/
-# Temp
-gitignore
-
# System Files
.DS_Store
Thumbs.db
+
+# rasterized svgs
+public/og-icons
+public/brico-face.png
\ No newline at end of file
diff --git a/frontend/public/bsatn b/frontend/public/bsatn
index d8116513..3b26e4d7 160000
--- a/frontend/public/bsatn
+++ b/frontend/public/bsatn
@@ -1 +1 @@
-Subproject commit d811651334ed6dad50ae1f7798b6aa2dfbefde54
+Subproject commit 3b26e4d7f52c3e5f171ef487cd1130749f59cac3
diff --git a/frontend/src/components/GlobalSearchInput.tsx b/frontend/src/components/GlobalSearchInput.tsx
index 778458bc..9a465e10 100644
--- a/frontend/src/components/GlobalSearchInput.tsx
+++ b/frontend/src/components/GlobalSearchInput.tsx
@@ -10,7 +10,7 @@
* - Enter navigates to full /search page
*/
-import {useNavigate} from "@solidjs/router";
+import {A, useNavigate} from "@solidjs/router";
import {TbOutlineSearch as IconSearch} from "solid-icons/tb";
import {createMemo, createSignal, For, onCleanup, onMount, Show} from "solid-js";
import {useIsMobile} from "~/components/ui/sidebar";
@@ -97,11 +97,15 @@ export default function GlobalSearchInput(props: GlobalSearchInputProps) {
tabButtonRefs.get(tabId)?.scrollIntoView({block: "nearest", inline: "nearest"});
};
- const commitSuggestion = (match: ObjectMatch) => {
+ const closeSuggestions = () => {
setQuery("");
setDropdownOpen(false);
setActiveIndex(-1);
if (inputRef) inputRef.value = "";
+ };
+
+ const commitSuggestion = (match: ObjectMatch) => {
+ closeSuggestions();
navigate(match.route);
};
@@ -260,30 +264,28 @@ export default function GlobalSearchInput(props: GlobalSearchInputProps) {
{(match, i) => {
const isActive = () => activeIndex() === i();
return (
-
{
- e.preventDefault();
- commitSuggestion(match);
- }}
- onMouseEnter={() => setActiveIndex(i())}
- >
-
- {match.label}
-
-
- {match.displayName}
-
-
- ({match.matchField}: {match.matchValue.length > 50 ? match.matchValue.slice(0, 50) + "…" : match.matchValue})
-
+
+ setActiveIndex(i())}
+ >
+
+ {match.label}
-
+
+ {match.displayName}
+
+
+ ({match.matchField}: {match.matchValue.length > 50 ? match.matchValue.slice(0, 50) + "…" : match.matchValue})
+
+
+
+
);
}}
diff --git a/frontend/src/components/data-table/table-faceted-filter.tsx b/frontend/src/components/data-table/table-faceted-filter.tsx
index 9ae3299b..8f2644a7 100644
--- a/frontend/src/components/data-table/table-faceted-filter.tsx
+++ b/frontend/src/components/data-table/table-faceted-filter.tsx
@@ -441,11 +441,13 @@ export function TableFacetedFilter(props: TableFacetedFilterProps)
}
return (
- {
- setPopoverOpen(open);
- setEditingWithNumberInputs(false);
- }}
+ {
+ setPopoverOpen(open);
+ setEditingWithNumberInputs(false);
+ }}
>
} variant="outline" size="sm" onclick={() => setPopoverOpen(!popoverOpen())}
diff --git a/frontend/src/components/fun/BricoLootBox.tsx b/frontend/src/components/fun/BricoLootBox.tsx
index 1f8173ac..029325e5 100644
--- a/frontend/src/components/fun/BricoLootBox.tsx
+++ b/frontend/src/components/fun/BricoLootBox.tsx
@@ -32,10 +32,10 @@ function rollItemList(list: ItemListDesc): LootResult | null {
if (r <= 0 && poss.items.length > 0) {
const stack = poss.items[Math.floor(Math.random() * poss.items.length)];
if (stack.itemType.tag === ItemType.Cargo.tag) {
- const cargo = cargoIdx?.get(stack.itemId);
+ const cargo = cargoIdx.get(stack.itemId);
return cargo ? { type: "cargo", cargo, quantity: stack.quantity } : null;
}
- const item = itemIdx?.get(stack.itemId);
+ const item = itemIdx.get(stack.itemId);
return item ? { type: "item", item, quantity: stack.quantity } : null;
}
}
@@ -159,9 +159,9 @@ export const BricoLootBox: Component = (props) => {
setResolvedItemId([targetItemId, targetItemType]);
if (targetListId) {
- const list = BitCraftTables.ItemListDesc.indexedBy("id")()?.get(targetListId);
+ const list = BitCraftTables.ItemListDesc.indexedBy("id")().get(targetListId);
if (list) {
- const item = BitCraftTables.ItemDesc.indexedBy("id")()?.get(targetItemId);
+ const item = BitCraftTables.ItemDesc.indexedBy("id")().get(targetItemId);
const result = rollItemList(list);
if (result && item?.itemListId === targetListId) {
// input was item list itself, just resolve to the rolled item
@@ -268,7 +268,7 @@ export const BricoLootBox: Component = (props) => {
const cargo = BitCraftTables.CargoDesc.indexedBy("id")().get(id);
return cargo ? {type: "cargo", cargo, quantity: 1} : null;
} else {
- const item = BitCraftTables.ItemDesc.indexedBy("id")()?.get(id);
+ const item = BitCraftTables.ItemDesc.indexedBy("id")().get(id);
return item ? {type: "item", item, quantity: 1} : null;
}
};
diff --git a/frontend/src/components/shared/GameIcon.tsx b/frontend/src/components/shared/GameIcon.tsx
index 5071db6f..305b64bd 100644
--- a/frontend/src/components/shared/GameIcon.tsx
+++ b/frontend/src/components/shared/GameIcon.tsx
@@ -297,7 +297,7 @@ export const ItemIcon: Component = (props) => {
const { tf2Mode } = useSettings();
const hatIcon = () => {
if (tf2Mode()) {
- const eq = BitCraftTables.EquipmentDesc.indexedBy("itemId")?.()?.get(props.item.id);
+ const eq = BitCraftTables.EquipmentDesc.indexedBy("itemId")().get(props.item.id);
if (eq && eq.slots.some(s => s.tag === "HeadClothing")) {
return `/assets/Hats/${HATS[Math.floor(Math.random() * HATS.length)]}.webp`;
}
diff --git a/frontend/src/components/shared/ItemStacks.tsx b/frontend/src/components/shared/ItemStacks.tsx
index 25f8bb4f..d0fab73e 100644
--- a/frontend/src/components/shared/ItemStacks.tsx
+++ b/frontend/src/components/shared/ItemStacks.tsx
@@ -34,9 +34,9 @@ import {cn, fixFloat} from "~/lib/utils";
/** Resolve an ItemStack's itemId + itemType to the actual ItemDesc or CargoDesc */
function resolveStack(itemId: number, itemType: ItemType): ItemDesc | CargoDesc | undefined {
if (itemType.tag === ItemType.Item.tag) {
- return BitCraftTables.ItemDesc.indexedBy("id")?.()?.get(itemId);
+ return BitCraftTables.ItemDesc.indexedBy("id")().get(itemId);
} else if (itemType.tag === ItemType.Cargo.tag) {
- return BitCraftTables.CargoDesc.indexedBy("id")?.()?.get(itemId);
+ return BitCraftTables.CargoDesc.indexedBy("id")().get(itemId);
}
return undefined;
}
@@ -159,7 +159,7 @@ export const ProbItemStackIcon: Component<{
if (!obj || !isItem(obj)) return undefined;
const listId = (obj as ItemDesc).itemListId;
if (!listId) return undefined;
- return BitCraftTables.ItemListDesc.indexedBy("id")?.()?.get(listId);
+ return BitCraftTables.ItemListDesc.indexedBy("id")().get(listId);
});
return (
@@ -239,8 +239,8 @@ function computeAveragesFlatExpanded(possibilities: ItemListPossibility[], multi
const totalWeight = possibilities.reduce((sum, p) => sum + p.probability, 0);
if (totalWeight === 0) return [];
- const itemIndex = BitCraftTables.ItemDesc.indexedBy("id")?.();
- const listIndex = BitCraftTables.ItemListDesc.indexedBy("id")?.();
+ const itemIndex = BitCraftTables.ItemDesc.indexedBy("id")();
+ const listIndex = BitCraftTables.ItemListDesc.indexedBy("id")();
for (const poss of possibilities) {
const selectProb = (poss.probability / totalWeight) * multiplier;
@@ -248,9 +248,9 @@ function computeAveragesFlatExpanded(possibilities: ItemListPossibility[], multi
// Check if this stack resolves to an inner item list
let handledAsInnerList = false;
if (stack.itemType.tag === ItemType.Item.tag) {
- const item = itemIndex?.get(stack.itemId);
+ const item = itemIndex.get(stack.itemId);
if (item?.itemListId) {
- const innerList = listIndex?.get(item.itemListId);
+ const innerList = listIndex.get(item.itemListId);
if (innerList) {
// Recurse: multiply probability by select chance × stack quantity
const innerAvgs = computeAveragesFlatExpanded(innerList.possibilities, selectProb * stack.quantity);
@@ -301,14 +301,14 @@ export const ItemListDisplay: Component<{
/** True if any possibility contains an item that resolves to an inner item list */
const hasInnerLists = createMemo(() => {
- const itemIndex = BitCraftTables.ItemDesc.indexedBy("id")?.();
- const listIndex = BitCraftTables.ItemListDesc.indexedBy("id")?.();
+ const itemIndex = BitCraftTables.ItemDesc.indexedBy("id")();
+ const listIndex = BitCraftTables.ItemListDesc.indexedBy("id")();
return sorted().some(poss =>
poss.items.some(stack => {
if (stack.itemType.tag !== ItemType.Item.tag) return false;
- const item = itemIndex?.get(stack.itemId);
+ const item = itemIndex.get(stack.itemId);
if (!item?.itemListId) return false;
- return !!listIndex?.get(item.itemListId);
+ return !!listIndex.get(item.itemListId);
})
);
});
@@ -508,7 +508,7 @@ export const QuestDropDisplay: Component<{
class?: string;
}> = (props) => {
const questStage = () => props.questDrop.requiredStageId
- ? BitCraftTables.QuestStageDesc.indexedBy("id")()?.get(props.questDrop.requiredStageId)
+ ? BitCraftTables.QuestStageDesc.indexedBy("id")().get(props.questDrop.requiredStageId)
: undefined;
return (
@@ -569,9 +569,9 @@ export function expandStack(
// Plain ItemStack — check if it resolves to an item list
const stack = input as ItemStack;
if (stack.itemType.tag === ItemType.Item.tag) {
- const item = BitCraftTables.ItemDesc.indexedBy("id")?.()?.get(stack.itemId);
+ const item = BitCraftTables.ItemDesc.indexedBy("id")().get(stack.itemId);
if (item?.itemListId) {
- const list = BitCraftTables.ItemListDesc.indexedBy("id")?.()?.get(item.itemListId);
+ const list = BitCraftTables.ItemListDesc.indexedBy("id")().get(item.itemListId);
if (list) {
return }/>;
}
diff --git a/frontend/src/components/shared/PlaceableGraph.tsx b/frontend/src/components/shared/PlaceableGraph.tsx
index 129fc699..34aabf84 100644
--- a/frontend/src/components/shared/PlaceableGraph.tsx
+++ b/frontend/src/components/shared/PlaceableGraph.tsx
@@ -786,8 +786,8 @@ export function computeExpectedPath(
function aggregateItemCosts(
costs: { itemId: number; itemType: string; quantity: number }[],
- itemIndex: Map | undefined,
- cargoIndex: Map | undefined,
+ itemIndex: Map,
+ cargoIndex: Map,
): PathResult["itemCosts"] {
const map = new Map();
for (const c of costs) {
@@ -798,8 +798,8 @@ function aggregateItemCosts(
const [iType, iIdStr] = key.split("-");
const iId = parseInt(iIdStr, 10);
const name = iType === "Cargo"
- ? cargoIndex?.get(iId)?.name ?? `Cargo #${iId}`
- : itemIndex?.get(iId)?.name ?? `Item #${iId}`;
+ ? cargoIndex.get(iId)?.name ?? `Cargo #${iId}`
+ : itemIndex.get(iId)?.name ?? `Item #${iId}`;
return {itemId: iId, itemType: iType, name, expectedQuantity: qty};
});
}
@@ -1043,7 +1043,7 @@ export function PlaceableGraph(props: PlaceableGraphProps) {
const renderIcon = () => {
if (node.type === "placeable") {
- const desc = () => placeableIndex()?.get(node.gameId);
+ const desc = () => placeableIndex().get(node.gameId);
return (
{(d) => }
@@ -1052,7 +1052,7 @@ export function PlaceableGraph(props: PlaceableGraphProps) {
} else {
const isCargo = node.itemType === "Cargo";
if (isCargo) {
- const desc = () => cargoIndex()?.get(node.gameId);
+ const desc = () => cargoIndex().get(node.gameId);
return (
{/* hardcoded to use the same size as square icons */}
@@ -1060,7 +1060,7 @@ export function PlaceableGraph(props: PlaceableGraphProps) {
);
} else {
- const desc = () => itemIndex()?.get(node.gameId);
+ const desc = () => itemIndex().get(node.gameId);
return (
{(d) => }
@@ -1285,7 +1285,7 @@ export function buildPlaceableGraph(placementId: number): PlaceableGraphData {
nodeIds.add(nodeId);
const isCargo = itemType === "Cargo";
- const desc = isCargo ? cargoIndex?.get(itemId) : itemIndex?.get(itemId);
+ const desc = isCargo ? cargoIndex.get(itemId) : itemIndex.get(itemId);
const name = desc?.name ?? `${itemType} #${itemId}`;
const pp = linkPlacement ? placementByItem.get(`${itemType}-${itemId}`) : undefined;
@@ -1308,7 +1308,7 @@ export function buildPlaceableGraph(placementId: number): PlaceableGraphData {
if (nodeIds.has(nodeId)) return nodeId;
nodeIds.add(nodeId);
- const desc = placeableIndex?.get(plcId);
+ const desc = placeableIndex.get(plcId);
nodes.push({
id: nodeId,
label: desc?.name ?? `Placeable #${plcId}`,
@@ -1388,8 +1388,8 @@ export function buildPlaceableGraph(placementId: number): PlaceableGraphData {
const tooltip = ia.consumedItemStacks.length
? ia.consumedItemStacks.map(s => {
const name = s.itemType.tag === "Cargo"
- ? cargoIndex?.get(s.itemId)?.name ?? `Cargo #${s.itemId}`
- : itemIndex?.get(s.itemId)?.name ?? `Item #${s.itemId}`;
+ ? cargoIndex.get(s.itemId)?.name ?? `Cargo #${s.itemId}`
+ : itemIndex.get(s.itemId)?.name ?? `Item #${s.itemId}`;
return `${s.quantity}× ${name} per action${effortReq}`;
}).join(", ")
: undefined;
diff --git a/frontend/src/components/shared/RecipeDisplay.tsx b/frontend/src/components/shared/RecipeDisplay.tsx
index 21d88ae5..a088e33a 100644
--- a/frontend/src/components/shared/RecipeDisplay.tsx
+++ b/frontend/src/components/shared/RecipeDisplay.tsx
@@ -129,7 +129,7 @@ const ResourceDepletionIcons: Component<{ resource: ResourceDesc, showLabel: boo
}
const depletionResource = () => {
if (!r?.onDestroyYieldResourceId) return undefined;
- return BitCraftTables.ResourceDesc.indexedBy("id")()?.get(r.onDestroyYieldResourceId);
+ return BitCraftTables.ResourceDesc.indexedBy("id")().get(r.onDestroyYieldResourceId);
}
return (
@@ -512,7 +512,7 @@ export const renderKnowledgeLockedItem = = (props) => {
- const placeable = () => BitCraftTables.PlaceableDesc.indexedBy("id")()?.get(props.placement.placedPlaceableId);
+ const placeable = () => BitCraftTables.PlaceableDesc.indexedBy("id")().get(props.placement.placedPlaceableId);
return (
;
onTogglePercent: () => void;
}> = (props) => {
- const placeable = () => BitCraftTables.PlaceableDesc.indexedBy("id")()?.get(props.placeableId);
+ const placeable = () => BitCraftTables.PlaceableDesc.indexedBy("id")().get(props.placeableId);
const pct = () => (props.probability / props.totalWeight) * 100;
return (
@@ -617,7 +617,7 @@ const GrowthOutcomeIcon: Component<{
export const GrowthPanel: Component<{ growth: PlaceableGrowthDesc }> = (props) => {
const [showPercent, setShowPercent] = createSignal(true);
- const placeable = () => BitCraftTables.PlaceableDesc.indexedBy("id")()?.get(props.growth.placeableId);
+ const placeable = () => BitCraftTables.PlaceableDesc.indexedBy("id")().get(props.growth.placeableId);
const totalWeight = () => props.growth.outcomes.reduce((sum, o) => sum + o.probability, 0);
return (
diff --git a/frontend/src/components/version-checker.tsx b/frontend/src/components/version-checker.tsx
index d9497e02..f5bee771 100644
--- a/frontend/src/components/version-checker.tsx
+++ b/frontend/src/components/version-checker.tsx
@@ -1,3 +1,4 @@
+import {A, useLocation} from "@solidjs/router";
import {TbOutlineExternalLink as IconExternal, TbOutlineInfoCircle as IconCurrent, TbOutlineInfoTriangle as IconOutdated} from "solid-icons/tb";
import {createResource, onCleanup, onMount, Show} from "solid-js";
import {Button} from "~/components/ui/button";
@@ -36,6 +37,7 @@ function hasDescription(version: VersionInfo): boolean {
export function VersionChecker() {
const [latestVersion, {refetch}] = createResource(fetchLatestVersion);
+ const location = useLocation();
onMount(() => {
let timer = setInterval(refetch, 15 * 60 * 1000); // 15 minutes
@@ -53,7 +55,7 @@ export function VersionChecker() {
return (
-
+