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 (
    - +