diff --git a/CHANGELOG.md b/CHANGELOG.md index da1d5100..daac944c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ All notable changes to this project are documented in this file. It mirrors the ## Unreleased +- **The Recipe picker shows each recipe's ingredients.** Rows in the picker are taller, with a picture of every ingredient under the Alt and tier badges. Hovering one names it and says how many a minute one building uses at 100%. - **Check your plan against your game save.** A new **Game options** button beside **Options** opens a dialog where you load a Satisfactory save. The planner reads it in your browser and keeps what it has unlocked: every recipe, every building, and the tiers reached. That list stays with the plan and syncs with it, and **Forget save** clears it. A file it cannot read is refused as not compatible, with the reason. The **Import world [WIP]** button is gone. - **Locked recipes get a padlock.** Once a save is loaded, the Recipe picker shows a padlock on each recipe and greys out the ones the save has not unlocked, standard recipes included. Clicking a padlock marks the recipe unlocked or locked by hand, and the mark survives loading a newer save. The tick beside the selected recipe is gone. - **Every alternate in one list.** Game options lists every alternate recipe with its rating, searchable and filterable by unlocked or locked. Clicking one marks it by hand. diff --git a/web/src/components/planner/products/RecipePicker.spec.ts b/web/src/components/planner/products/RecipePicker.spec.ts index dfc4e05a..37885cb4 100644 --- a/web/src/components/planner/products/RecipePicker.spec.ts +++ b/web/src/components/planner/products/RecipePicker.spec.ts @@ -135,4 +135,14 @@ describe('Component: RecipePicker', () => { expect(subject.find('[data-testid="recipe-selection-locked"]').exists()).toBe(true) }) }) + + it('lists each recipe\'s ingredients with their per-minute rate for one building', () => { + const items = mountPicker('Wire', 'Wire').findComponent(VAutocomplete).props('items') as any[] + + expect(items.find(item => item.value === 'Wire')?.ingredients).toEqual([ + { part: 'CopperIngot', label: 'Copper Ingot: 15/min' }, + ]) + expect(items.find(item => item.value === 'Alternate_FusedWire')?.ingredients.map((i: any) => i.part)) + .toEqual(['CopperIngot', 'GoldIngot']) + }) }) diff --git a/web/src/components/planner/products/RecipePicker.vue b/web/src/components/planner/products/RecipePicker.vue index e139d915..e0495f0b 100644 --- a/web/src/components/planner/products/RecipePicker.vue +++ b/web/src/components/planner/products/RecipePicker.vue @@ -7,6 +7,7 @@ hide-details :items="items" label="Recipe" + :menu-props="{ maxHeight: 440 }" :model-value="modelValue" variant="outlined" @update:focused="focused = $event" @@ -24,25 +25,40 @@ @@ -66,6 +82,8 @@ import { getRecipeRating, matchesRecipeQuery, RECIPE_RATINGS_SOURCE, RecipeTier, TIER_ORDER } from '@/utils/recipe-ratings' import { useRecipeUnlocks } from '@/composables/useRecipeUnlocks' import { isLockableRecipe } from '@/utils/recipe-unlocks' + import { getPartDisplayName } from '@/utils/helpers' + import { formatNumber } from '@/utils/numberFormatter' import type { FilterFunction } from 'vuetify' defineOptions({ inheritAttrs: false }) @@ -92,6 +110,11 @@ lockable: isLockableRecipe(recipe.id), locked: !isUnlocked(recipe.id), overridden: isOverridden(recipe.id), + // Per minute for one building at 100%, as the game's own recipe screen counts it. + ingredients: recipe.ingredients.map(ingredient => ({ + part: ingredient.part, + label: `${getPartDisplayName(ingredient.part)}: ${formatNumber(ingredient.perMin)}/min`, + })), })) .sort((a, b) => rank(a) - rank(b) || a.name.localeCompare(b.name))) @@ -128,6 +151,33 @@ width: 100%; } +.recipe-picker-meta { + display: flex; + flex-direction: column; + align-items: flex-end; + flex-shrink: 0; + gap: 4px; + padding: 4px 0; +} + +.recipe-picker-tags-line { + display: flex; + align-items: center; + gap: 6px; +} + +// Every row the same height and ruled off, so close-set ingredient pictures read as one recipe's. +// On a row with no tags, the ingredients centre in that height. +.recipe-picker-item { + min-height: 64px; + border-bottom: 1px solid rgba(255, 255, 255, 0.08); +} + +.recipe-picker-ingredients { + display: flex; + gap: 4px; +} + .recipe-picker :deep(.v-field__input) { flex-wrap: nowrap; }