Skip to content

Commit 676a379

Browse files
committed
Refactor DetailsPane and enhance Factoriopedia science pack functionality
- Streamlined the DetailsPane component by consolidating conditional rendering for improved readability. - Introduced a new science pack dependency map in useFactorioData to manage science pack relationships. - Enhanced science pack selection logic in Factoriopedia to dynamically include dependencies, ensuring accurate filtering of available options. - Updated related functions to maintain consistent ordering and accessibility of science packs based on user selections.
1 parent e936587 commit 676a379

5 files changed

Lines changed: 354 additions & 61 deletions

File tree

docs/.vitepress/components/DetailsPane.vue

Lines changed: 12 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
<template>
22
<div :class="$style.detailsPane">
3-
<div
4-
v-if="selectedItem"
5-
:class="$style.itemDetails"
6-
>
3+
<div v-if="selectedItem" :class="$style.itemDetails">
74
<!-- Item Header with Controls and Tabs -->
85
<div :class="$style.itemHeader">
96
<div :class="$style.headerTitle">
@@ -22,10 +19,7 @@
2219
>
2320
Details
2421
</button>
25-
<button
26-
:class="{ [$style.active]: activeTab === 'raws' }"
27-
@click="activeTab = 'raws'"
28-
>
22+
<button :class="{ [$style.active]: activeTab === 'raws' }" @click="activeTab = 'raws'">
2923
Raws
3024
</button>
3125
</div>
@@ -54,14 +48,8 @@
5448
>
5549
History
5650
</button>
57-
<div
58-
v-if="showHistoryDropdown"
59-
:class="$style.historyDropdown"
60-
>
61-
<div
62-
v-if="historyItems.length === 0"
63-
:class="$style.historyEmpty"
64-
>
51+
<div v-if="showHistoryDropdown" :class="$style.historyDropdown">
52+
<div v-if="historyItems.length === 0" :class="$style.historyEmpty">
6553
No recent items
6654
</div>
6755
<div
@@ -73,10 +61,10 @@
7361
<SpriteIcon
7462
v-if="
7563
item.type === 'item' ||
76-
item.type === 'recipe' ||
77-
item.type === 'technology' ||
78-
item.type === 'fluid' ||
79-
item.type === 'tile'
64+
item.type === 'recipe' ||
65+
item.type === 'technology' ||
66+
item.type === 'fluid' ||
67+
item.type === 'tile'
8068
"
8169
:sprite-key="`${item.type}-${item.name}`"
8270
:size="32"
@@ -91,10 +79,7 @@
9179
</div>
9280

9381
<!-- Large Item Image (for entities and items) -->
94-
<div
95-
v-if="selectedItem.entity"
96-
:class="$style.itemImageContainer"
97-
>
82+
<div v-if="selectedItem.entity" :class="$style.itemImageContainer">
9883
<FactorioSprite
9984
v-if="selectedItem.entity"
10085
:key="type + name"
@@ -111,10 +96,7 @@
11196
v-if="selectedItem?.description || detailsData.statistics?.length > 0"
11297
:class="$style.detailsInfoBox"
11398
>
114-
<div
115-
v-if="selectedItem?.description"
116-
:class="$style.detailsDescription"
117-
>
99+
<div v-if="selectedItem?.description" :class="$style.detailsDescription">
118100
<FactorioRichText :text="selectedItem.description" />
119101
</div>
120102
<!-- Statistics -->
@@ -133,10 +115,7 @@
133115
</template>
134116
</div>
135117
<!-- Raws Tab Content -->
136-
<div
137-
v-if="activeTab === 'raws'"
138-
:class="$style.rawsContent"
139-
>
118+
<div v-if="activeTab === 'raws'" :class="$style.rawsContent">
140119
<RawSection
141120
v-if="selectedItem?.recipe"
142121
title="Recipe Data"
@@ -202,10 +181,7 @@
202181
</div>
203182

204183
<!-- No Selection State -->
205-
<div
206-
v-if="!selectedItem"
207-
:class="$style.noSelection"
208-
>
184+
<div v-if="!selectedItem" :class="$style.noSelection">
209185
<div :class="$style.noSelectionContent">
210186
<h3>Select an item to view details</h3>
211187
<p>

docs/.vitepress/components/Factoriopedia.vue

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ const {
208208
precomputeCategoryStructure,
209209
createUnifiedSelectionObject,
210210
getSciencePackNamesFromTechnologies,
211+
getSciencePackDependencyMap,
211212
createSciencePackVisibility
212213
} = useFactorioData()
213214
const selectedItem = ref(null)
@@ -298,20 +299,71 @@ const sciencePackOptions = computed(() => {
298299
})
299300
})
300301
302+
const sciencePackDependencyMap = computed(() => getSciencePackDependencyMap())
303+
304+
const sciencePackOrderLookup = computed(() => {
305+
const lookup = new Map()
306+
sciencePackOptions.value.forEach((pack, index) => {
307+
lookup.set(pack.name, index)
308+
})
309+
return lookup
310+
})
311+
301312
function clearSciencePackFilters() {
302313
selectedSciencePacks.value = []
303314
}
304315
316+
function collectSciencePackDependencies(packName, dependencyMap, collected = new Set()) {
317+
const dependencies = dependencyMap?.[packName] || []
318+
dependencies.forEach(dependencyName => {
319+
if (!collected.has(dependencyName)) {
320+
collected.add(dependencyName)
321+
collectSciencePackDependencies(dependencyName, dependencyMap, collected)
322+
}
323+
})
324+
return collected
325+
}
326+
327+
function pruneInaccessibleSciencePacks(selectedSet, dependencyMap) {
328+
let changed = true
329+
while (changed) {
330+
changed = false
331+
for (const packName of Array.from(selectedSet)) {
332+
const dependencies = dependencyMap?.[packName] || []
333+
const hasMissingDependency = dependencies.some(dependencyName => !selectedSet.has(dependencyName))
334+
if (hasMissingDependency) {
335+
selectedSet.delete(packName)
336+
changed = true
337+
}
338+
}
339+
}
340+
}
341+
342+
function toOrderedSciencePackArray(selectedSet) {
343+
const orderLookup = sciencePackOrderLookup.value
344+
return Array.from(selectedSet).sort((left, right) => {
345+
const leftOrder = orderLookup.get(left) ?? Number.MAX_SAFE_INTEGER
346+
const rightOrder = orderLookup.get(right) ?? Number.MAX_SAFE_INTEGER
347+
if (leftOrder !== rightOrder) return leftOrder - rightOrder
348+
return left.localeCompare(right)
349+
})
350+
}
351+
305352
function toggleSciencePack(packName) {
306353
if (!packName) return
307354
355+
const dependencyMap = sciencePackDependencyMap.value
308356
const selected = new Set(selectedSciencePacks.value)
309357
if (selected.has(packName)) {
310358
selected.delete(packName)
359+
pruneInaccessibleSciencePacks(selected, dependencyMap)
311360
} else {
312361
selected.add(packName)
362+
collectSciencePackDependencies(packName, dependencyMap).forEach(dependencyName => {
363+
selected.add(dependencyName)
364+
})
313365
}
314-
selectedSciencePacks.value = Array.from(selected)
366+
selectedSciencePacks.value = toOrderedSciencePackArray(selected)
315367
}
316368
317369
// Computed property for filtered and grouped recipes

src/composables/__tests__/useUnifiedObjects.test.js

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,4 +155,164 @@ describe('useUnifiedObjects.createUnifiedObjectByKey', () => {
155155
expect(recipeObject.subgroup).toBe('chemicals')
156156
expect(recipeObject.order).toBe('r-1')
157157
})
158+
159+
it('ignores same-key prototypes with mismatched internal names', () => {
160+
const { createUnifiedObjectByKey } = useUnifiedObjects()
161+
const factorioData = {
162+
item: {
163+
foo: {
164+
type: 'item',
165+
name: 'foo',
166+
subgroup: 'misc',
167+
order: 'a',
168+
displayName: 'Foo'
169+
}
170+
},
171+
recipe: {
172+
foo: {
173+
type: 'recipe',
174+
name: 'bar',
175+
subgroup: 'misc',
176+
order: 'b',
177+
displayName: 'Bar Recipe',
178+
main_product: 'bar',
179+
results: [{ name: 'bar' }]
180+
}
181+
},
182+
entity: {},
183+
fluid: {},
184+
tile: {},
185+
equipment: {},
186+
'item-subgroup': {},
187+
'item-group': {}
188+
}
189+
190+
const unified = createUnifiedObjectByKey('foo', factorioData)
191+
const fooItem = unified.find(entry => entry.source === 'item')
192+
const recipeObject = unified.find(entry => entry.source === 'recipe')
193+
194+
expect(fooItem).toBeDefined()
195+
expect(fooItem.recipe).toBeUndefined()
196+
expect(recipeObject).toBeUndefined()
197+
})
198+
199+
it('keeps intentional cross-name place_as_tile relationships', () => {
200+
const { createUnifiedObjectByKey } = useUnifiedObjects()
201+
const factorioData = {
202+
item: {
203+
landfill: {
204+
type: 'item',
205+
name: 'landfill',
206+
subgroup: 'terrain',
207+
order: 'a',
208+
displayName: 'Landfill',
209+
place_as_tile: { result: 'landfill-tile' }
210+
}
211+
},
212+
recipe: {},
213+
entity: {},
214+
fluid: {},
215+
tile: {
216+
'landfill-tile': {
217+
type: 'tile',
218+
name: 'landfill-tile',
219+
subgroup: 'terrain',
220+
order: 'a',
221+
displayName: 'Landfill Tile'
222+
}
223+
},
224+
equipment: {},
225+
'item-subgroup': {},
226+
'item-group': {}
227+
}
228+
229+
const unified = createUnifiedObjectByKey('landfill', factorioData)
230+
const landfillItem = unified.find(entry => entry.source === 'item')
231+
232+
expect(landfillItem).toBeDefined()
233+
expect(landfillItem.types).toContain('tile')
234+
expect(landfillItem.tile?.name).toBe('landfill-tile')
235+
})
236+
237+
it('merges cross-name item/entity via place_result and minable output', () => {
238+
const { createUnifiedObjectByKey } = useUnifiedObjects()
239+
const factorioData = {
240+
item: {
241+
'long-handed-inserter': {
242+
type: 'item',
243+
name: 'long-handed-inserter',
244+
subgroup: 'inserter',
245+
order: 'a',
246+
displayName: 'Long-handed inserter',
247+
place_result: 'bob-red-inserter'
248+
}
249+
},
250+
recipe: {},
251+
entity: {
252+
'bob-red-inserter': {
253+
type: 'inserter',
254+
name: 'bob-red-inserter',
255+
subgroup: 'inserter',
256+
order: 'a',
257+
displayName: 'Bob red inserter',
258+
minable: {
259+
results: [{ name: 'long-handed-inserter' }]
260+
}
261+
}
262+
},
263+
fluid: {},
264+
tile: {},
265+
equipment: {},
266+
'item-subgroup': {},
267+
'item-group': {}
268+
}
269+
270+
const unified = createUnifiedObjectByKey('long-handed-inserter', factorioData)
271+
const itemObject = unified.find(entry => entry.source === 'item')
272+
273+
expect(itemObject).toBeDefined()
274+
expect(itemObject.types).toContain('entity')
275+
expect(itemObject.entity?.name).toBe('bob-red-inserter')
276+
})
277+
278+
it('merges cross-name entity/item when looked up by entity key', () => {
279+
const { createUnifiedObjectByKey } = useUnifiedObjects()
280+
const factorioData = {
281+
item: {
282+
'long-handed-inserter': {
283+
type: 'item',
284+
name: 'long-handed-inserter',
285+
subgroup: 'inserter',
286+
order: 'a',
287+
displayName: 'Long-handed inserter',
288+
place_result: 'bob-red-inserter'
289+
}
290+
},
291+
recipe: {},
292+
entity: {
293+
'bob-red-inserter': {
294+
type: 'inserter',
295+
name: 'bob-red-inserter',
296+
subgroup: 'inserter',
297+
order: 'a',
298+
displayName: 'Bob red inserter',
299+
minable: {
300+
results: [{ name: 'long-handed-inserter' }]
301+
}
302+
}
303+
},
304+
fluid: {},
305+
tile: {},
306+
equipment: {},
307+
'item-subgroup': {},
308+
'item-group': {}
309+
}
310+
311+
const unified = createUnifiedObjectByKey('bob-red-inserter', factorioData)
312+
const entityObject = unified.find(entry => entry.source === 'entity')
313+
314+
expect(entityObject).toBeDefined()
315+
expect(entityObject.types).toContain('item')
316+
expect(entityObject.item?.name).toBe('long-handed-inserter')
317+
})
158318
})

0 commit comments

Comments
 (0)