minecraft-inventory 0.1.11 → 0.1.13
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
CHANGED
|
@@ -193,29 +193,40 @@ export const ItemCanvas = memo(function ItemCanvas({
|
|
|
193
193
|
/>
|
|
194
194
|
) : null /* still loading — render nothing (slot stays empty) */}
|
|
195
195
|
|
|
196
|
-
{/* Durability bar */}
|
|
197
|
-
{hasDurability && (
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
196
|
+
{/* Durability bar — vanilla layout: x=2, y=13 in 16×16 item area */}
|
|
197
|
+
{hasDurability && (() => {
|
|
198
|
+
const mcPx = Math.max(1, Math.round(pixelSize))
|
|
199
|
+
const barLeft = 2 * mcPx
|
|
200
|
+
const bgBottom = mcPx
|
|
201
|
+
const bgWidth = 13 * mcPx
|
|
202
|
+
const bgHeight = 2 * mcPx
|
|
203
|
+
const ratio = item.durability! / item.maxDurability!
|
|
204
|
+
const fillWidth = Math.round(13 * ratio) * mcPx
|
|
205
|
+
const fillHeight = mcPx
|
|
206
|
+
const fillBottom = 2 * mcPx
|
|
207
|
+
return (
|
|
208
|
+
<>
|
|
209
|
+
<div className="mc-inv-item-durability-bg" style={{
|
|
210
|
+
position: 'absolute',
|
|
211
|
+
bottom: bgBottom,
|
|
212
|
+
left: barLeft,
|
|
213
|
+
width: bgWidth,
|
|
214
|
+
height: bgHeight,
|
|
215
|
+
background: '#000',
|
|
216
|
+
pointerEvents: 'none',
|
|
217
|
+
}} />
|
|
218
|
+
<div className="mc-inv-item-durability-bar" style={{
|
|
219
|
+
position: 'absolute',
|
|
220
|
+
bottom: fillBottom,
|
|
221
|
+
left: barLeft,
|
|
222
|
+
width: fillWidth,
|
|
223
|
+
height: fillHeight,
|
|
224
|
+
background: barColor,
|
|
225
|
+
pointerEvents: 'none',
|
|
226
|
+
}} />
|
|
227
|
+
</>
|
|
228
|
+
)
|
|
229
|
+
})()}
|
|
219
230
|
|
|
220
231
|
{/* Item count */}
|
|
221
232
|
{!noCount && item.count > 1 && (
|
|
@@ -347,8 +347,12 @@ export function JEI({
|
|
|
347
347
|
size={itemSize}
|
|
348
348
|
noBackground
|
|
349
349
|
onClickOverride={(button, mode) => {
|
|
350
|
-
if (button === 'left' && mode === 'normal'
|
|
351
|
-
onItemClick
|
|
350
|
+
if (button === 'left' && mode === 'normal') {
|
|
351
|
+
if (onItemClick) {
|
|
352
|
+
onItemClick(jeiItem)
|
|
353
|
+
} else {
|
|
354
|
+
pushRecipeFrame(jeiItem, 'recipes')
|
|
355
|
+
}
|
|
352
356
|
} else if (button === 'middle' && onItemMiddleClick) {
|
|
353
357
|
onItemMiddleClick(jeiItem)
|
|
354
358
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React, { useState, useCallback, useEffect, useRef } from 'react'
|
|
2
|
-
import type { RecipeNavFrame, ItemStack } from '../../types'
|
|
2
|
+
import type { RecipeNavFrame, RecipeGuide, ItemStack } from '../../types'
|
|
3
3
|
import { ItemCanvas } from '../ItemCanvas'
|
|
4
4
|
import { Tooltip } from '../Tooltip'
|
|
5
5
|
import { useScale } from '../../context/ScaleContext'
|
|
6
|
-
import {
|
|
6
|
+
import { useTextures } from '../../context/TextureContext'
|
|
7
7
|
import { getInventoryType } from '../../registry'
|
|
8
8
|
|
|
9
9
|
interface RecipeInventoryViewProps {
|
|
@@ -113,21 +113,21 @@ export function RecipeInventoryView({
|
|
|
113
113
|
}, [onBack])
|
|
114
114
|
|
|
115
115
|
const isMultiFrame = navStack.length > 1
|
|
116
|
+
const isCustom = guide.type === 'custom'
|
|
116
117
|
|
|
117
|
-
// Determine layout type and get definition
|
|
118
|
+
// Determine layout type and get definition (not needed for custom)
|
|
118
119
|
const layoutType = guide.type === 'smelting' ? 'furnace' : 'crafting_table'
|
|
119
120
|
const def = getInventoryType(layoutType)!
|
|
120
121
|
|
|
121
|
-
// Container-only slots (no player inv / hotbar
|
|
122
|
-
const containerSlots = def.slots.filter(
|
|
123
|
-
(s) => s.group !== '
|
|
122
|
+
// Container-only slots (no player inv / hotbar)
|
|
123
|
+
const containerSlots = isCustom ? [] : def.slots.filter(
|
|
124
|
+
(s) => s.group !== 'inventory' && s.group !== 'hotbar',
|
|
124
125
|
)
|
|
125
126
|
|
|
126
127
|
// Map recipe items to slot indices
|
|
127
128
|
const slotItems = new Map<number, ItemStack | null>()
|
|
128
129
|
|
|
129
130
|
if (guide.type === 'crafting') {
|
|
130
|
-
// Slot 0 = result, slots 1-9 = 3x3 grid
|
|
131
131
|
slotItems.set(0, guide.result ?? null)
|
|
132
132
|
const ingr = guide.ingredients ?? []
|
|
133
133
|
for (let i = 0; i < 9; i++) {
|
|
@@ -156,8 +156,13 @@ export function RecipeInventoryView({
|
|
|
156
156
|
alignItems: 'center',
|
|
157
157
|
gap: 4 * scale,
|
|
158
158
|
marginBottom: 3 * scale,
|
|
159
|
+
padding: `${Math.round(2 * scale)}px ${Math.round(4 * scale)}px`,
|
|
159
160
|
fontSize: navFontSize,
|
|
160
161
|
fontFamily: "'Minecraftia', 'Minecraft', monospace",
|
|
162
|
+
background: '#c6c6c6',
|
|
163
|
+
border: `${Math.max(1, Math.round(scale))}px solid #555`,
|
|
164
|
+
boxSizing: 'border-box',
|
|
165
|
+
width: def.backgroundWidth * scale,
|
|
161
166
|
}}
|
|
162
167
|
>
|
|
163
168
|
<NavBtn scale={scale} onClick={onBack}>
|
|
@@ -195,53 +200,296 @@ export function RecipeInventoryView({
|
|
|
195
200
|
)}
|
|
196
201
|
</div>
|
|
197
202
|
|
|
198
|
-
{
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
203
|
+
{isCustom ? (
|
|
204
|
+
/* ── Compact description card for non-craftable items ── */
|
|
205
|
+
<DescriptionCard
|
|
206
|
+
guide={guide}
|
|
207
|
+
itemName={frame.item.name}
|
|
208
|
+
scale={scale}
|
|
209
|
+
/>
|
|
210
|
+
) : (
|
|
211
|
+
/* ── Cropped inventory background with container slots only (no player inv) ── */
|
|
212
|
+
<CroppedRecipeBackground
|
|
213
|
+
guide={guide}
|
|
214
|
+
containerSlots={containerSlots}
|
|
215
|
+
slotItems={slotItems}
|
|
216
|
+
scale={scale}
|
|
217
|
+
navPx={navPx}
|
|
218
|
+
contentSize={contentSize}
|
|
219
|
+
onHoverItem={handleHoverItem}
|
|
220
|
+
/>
|
|
221
|
+
)}
|
|
222
|
+
|
|
223
|
+
{/* ── Hint below ── */}
|
|
224
|
+
{!isCustom && (
|
|
225
|
+
<div style={{
|
|
226
|
+
marginTop: 2 * scale,
|
|
227
|
+
textAlign: 'center',
|
|
228
|
+
fontSize: Math.max(5, Math.round(5.5 * scale)),
|
|
229
|
+
fontFamily: "'Minecraftia', 'Minecraft', monospace",
|
|
230
|
+
color: 'rgba(80,80,80,0.7)',
|
|
231
|
+
pointerEvents: 'none',
|
|
232
|
+
}}>
|
|
233
|
+
Hover ingredient + R / U for nested lookup
|
|
234
|
+
</div>
|
|
235
|
+
)}
|
|
236
|
+
</div>
|
|
237
|
+
)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Cropped recipe background — shows only the container area (no player inventory).
|
|
242
|
+
* Crafting: crops to 80px height (title bar + 3×3 grid + result).
|
|
243
|
+
* Smelting: crops to 84px height (input + fuel + result + arrow).
|
|
244
|
+
*/
|
|
245
|
+
function CroppedRecipeBackground({
|
|
246
|
+
guide,
|
|
247
|
+
containerSlots,
|
|
248
|
+
slotItems,
|
|
249
|
+
scale,
|
|
250
|
+
navPx,
|
|
251
|
+
contentSize,
|
|
252
|
+
onHoverItem,
|
|
253
|
+
}: {
|
|
254
|
+
guide: RecipeGuide
|
|
255
|
+
containerSlots: Array<{ index: number; x: number; y: number; size?: number; group?: string }>
|
|
256
|
+
slotItems: Map<number, ItemStack | null>
|
|
257
|
+
scale: number
|
|
258
|
+
navPx: number
|
|
259
|
+
contentSize: number
|
|
260
|
+
onHoverItem: (item: ItemStack | null) => void
|
|
261
|
+
}) {
|
|
262
|
+
const textures = useTextures()
|
|
263
|
+
const layoutType = guide.type === 'smelting' ? 'furnace' : 'crafting_table'
|
|
264
|
+
const def = getInventoryType(layoutType)!
|
|
265
|
+
const bgUrl = textures.getGuiTextureUrl(def.backgroundTexture)
|
|
266
|
+
|
|
267
|
+
// Crop height: crafting=80, furnace=84 (matches old lib's CraftingTableGuide slice)
|
|
268
|
+
const CROP_H = guide.type === 'smelting' ? 84 : 80
|
|
269
|
+
const SRC_W = def.backgroundWidth
|
|
270
|
+
const fontSize = Math.max(6, Math.round(6 * scale))
|
|
271
|
+
|
|
272
|
+
return (
|
|
273
|
+
<div
|
|
274
|
+
className="mc-inv-recipe-cropped"
|
|
275
|
+
style={{
|
|
276
|
+
position: 'relative',
|
|
277
|
+
width: SRC_W * scale,
|
|
278
|
+
height: CROP_H * scale,
|
|
279
|
+
imageRendering: 'pixelated',
|
|
280
|
+
display: 'inline-block',
|
|
281
|
+
}}
|
|
282
|
+
>
|
|
283
|
+
{/* Cropped background texture */}
|
|
284
|
+
<div
|
|
285
|
+
style={{
|
|
286
|
+
position: 'absolute',
|
|
287
|
+
top: 0,
|
|
288
|
+
left: 0,
|
|
289
|
+
width: SRC_W,
|
|
290
|
+
height: CROP_H,
|
|
291
|
+
overflow: 'hidden',
|
|
292
|
+
transform: `scale(${scale})`,
|
|
293
|
+
transformOrigin: 'top left',
|
|
294
|
+
}}
|
|
295
|
+
>
|
|
296
|
+
<img
|
|
297
|
+
src={bgUrl}
|
|
298
|
+
alt=""
|
|
299
|
+
aria-hidden
|
|
300
|
+
draggable={false}
|
|
301
|
+
style={{
|
|
302
|
+
display: 'block',
|
|
303
|
+
width: SRC_W,
|
|
304
|
+
imageRendering: 'pixelated',
|
|
225
305
|
pointerEvents: 'none',
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
306
|
+
userSelect: 'none',
|
|
307
|
+
objectFit: 'none',
|
|
308
|
+
objectPosition: '0 0',
|
|
309
|
+
}}
|
|
310
|
+
/>
|
|
311
|
+
</div>
|
|
312
|
+
|
|
313
|
+
{/* Title */}
|
|
314
|
+
<div
|
|
315
|
+
style={{
|
|
316
|
+
position: 'absolute',
|
|
317
|
+
top: 6 * scale,
|
|
318
|
+
left: 8 * scale,
|
|
319
|
+
fontSize,
|
|
320
|
+
fontFamily: "'Minecraftia', 'Minecraft', monospace",
|
|
321
|
+
color: '#404040',
|
|
322
|
+
lineHeight: 1,
|
|
323
|
+
pointerEvents: 'none',
|
|
324
|
+
}}
|
|
325
|
+
>
|
|
326
|
+
{guide.title}
|
|
327
|
+
{guide.description && (
|
|
328
|
+
<span style={{ color: '#808080', marginLeft: 4 * scale }}>
|
|
329
|
+
({guide.description})
|
|
330
|
+
</span>
|
|
231
331
|
)}
|
|
232
|
-
</
|
|
332
|
+
</div>
|
|
233
333
|
|
|
234
|
-
{/*
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
334
|
+
{/* Slot items */}
|
|
335
|
+
{containerSlots.map((slotDef) => {
|
|
336
|
+
const item = slotItems.get(slotDef.index) ?? null
|
|
337
|
+
return (
|
|
338
|
+
<RecipeItemCell
|
|
339
|
+
key={slotDef.index}
|
|
340
|
+
item={item}
|
|
341
|
+
x={slotDef.x * scale}
|
|
342
|
+
y={slotDef.y * scale}
|
|
343
|
+
size={slotDef.size ? slotDef.size * scale - 2 * navPx : contentSize}
|
|
344
|
+
onHover={onHoverItem}
|
|
345
|
+
/>
|
|
346
|
+
)
|
|
347
|
+
})}
|
|
348
|
+
</div>
|
|
349
|
+
)
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Compact description card for non-craftable items (replaces full inventory background).
|
|
354
|
+
* Mirrors the old canvas-based GenericDescription layout: cropped crafting_table header,
|
|
355
|
+
* gray content area, item icon, description text, and a Minecraft Wiki link.
|
|
356
|
+
*/
|
|
357
|
+
function DescriptionCard({
|
|
358
|
+
guide,
|
|
359
|
+
itemName,
|
|
360
|
+
scale,
|
|
361
|
+
}: {
|
|
362
|
+
guide: RecipeGuide
|
|
363
|
+
itemName: string
|
|
364
|
+
scale: number
|
|
365
|
+
}) {
|
|
366
|
+
const textures = useTextures()
|
|
367
|
+
const craftingDef = getInventoryType('crafting_table')!
|
|
368
|
+
const bgUrl = textures.getGuiTextureUrl(craftingDef.backgroundTexture)
|
|
369
|
+
|
|
370
|
+
const CARD_W = 176
|
|
371
|
+
const CARD_H = 120
|
|
372
|
+
const TITLE_H = 15
|
|
373
|
+
const fontSize = Math.max(6, Math.round(6 * scale))
|
|
374
|
+
const iconSize = 16 * scale
|
|
375
|
+
|
|
376
|
+
const wikiUrl = `https://minecraft.wiki/w/${encodeURIComponent(itemName.replace(/ /g, '_'))}`
|
|
377
|
+
|
|
378
|
+
return (
|
|
379
|
+
<div
|
|
380
|
+
className="mc-inv-description-card"
|
|
381
|
+
style={{
|
|
382
|
+
position: 'relative',
|
|
383
|
+
width: CARD_W * scale,
|
|
384
|
+
height: CARD_H * scale,
|
|
385
|
+
imageRendering: 'pixelated',
|
|
386
|
+
display: 'inline-block',
|
|
387
|
+
}}
|
|
388
|
+
>
|
|
389
|
+
{/* Cropped crafting_table texture as background header */}
|
|
390
|
+
<div
|
|
391
|
+
style={{
|
|
392
|
+
position: 'absolute',
|
|
393
|
+
top: 0,
|
|
394
|
+
left: 0,
|
|
395
|
+
width: CARD_W,
|
|
396
|
+
height: CARD_H,
|
|
397
|
+
overflow: 'hidden',
|
|
398
|
+
transform: `scale(${scale})`,
|
|
399
|
+
transformOrigin: 'top left',
|
|
400
|
+
}}
|
|
401
|
+
>
|
|
402
|
+
<img
|
|
403
|
+
src={bgUrl}
|
|
404
|
+
alt=""
|
|
405
|
+
aria-hidden
|
|
406
|
+
draggable={false}
|
|
407
|
+
style={{
|
|
408
|
+
display: 'block',
|
|
409
|
+
width: CARD_W,
|
|
410
|
+
imageRendering: 'pixelated',
|
|
411
|
+
pointerEvents: 'none',
|
|
412
|
+
userSelect: 'none',
|
|
413
|
+
objectFit: 'none',
|
|
414
|
+
objectPosition: '0 0',
|
|
415
|
+
}}
|
|
416
|
+
/>
|
|
417
|
+
</div>
|
|
418
|
+
|
|
419
|
+
{/* Gray content overlay (below title bar) */}
|
|
420
|
+
<div
|
|
421
|
+
style={{
|
|
422
|
+
position: 'absolute',
|
|
423
|
+
top: TITLE_H * scale,
|
|
424
|
+
left: 3 * scale,
|
|
425
|
+
width: (CARD_W - 6) * scale,
|
|
426
|
+
height: (CARD_H - TITLE_H) * scale,
|
|
427
|
+
background: '#c6c6c6',
|
|
428
|
+
}}
|
|
429
|
+
/>
|
|
430
|
+
|
|
431
|
+
{/* Title */}
|
|
432
|
+
<div
|
|
433
|
+
style={{
|
|
434
|
+
position: 'absolute',
|
|
435
|
+
top: 6 * scale,
|
|
436
|
+
left: 8 * scale,
|
|
437
|
+
fontSize,
|
|
438
|
+
fontFamily: "'Minecraftia', 'Minecraft', monospace",
|
|
439
|
+
color: '#404040',
|
|
440
|
+
lineHeight: 1,
|
|
441
|
+
pointerEvents: 'none',
|
|
442
|
+
}}
|
|
443
|
+
>
|
|
444
|
+
{guide.title}
|
|
244
445
|
</div>
|
|
446
|
+
|
|
447
|
+
{/* Item icon */}
|
|
448
|
+
<div style={{ position: 'absolute', top: (TITLE_H + 3) * scale, left: 5 * scale }}>
|
|
449
|
+
<ItemCanvas
|
|
450
|
+
item={{ type: 0, name: itemName, displayName: guide.title, count: 1 }}
|
|
451
|
+
size={iconSize}
|
|
452
|
+
style={{ pointerEvents: 'none' }}
|
|
453
|
+
/>
|
|
454
|
+
</div>
|
|
455
|
+
|
|
456
|
+
{/* Description text */}
|
|
457
|
+
{guide.description && (
|
|
458
|
+
<div style={{
|
|
459
|
+
position: 'absolute',
|
|
460
|
+
top: (TITLE_H + 3) * scale,
|
|
461
|
+
left: (5 + 18) * scale,
|
|
462
|
+
right: 8 * scale,
|
|
463
|
+
fontSize,
|
|
464
|
+
fontFamily: "'Minecraftia', 'Minecraft', monospace",
|
|
465
|
+
color: '#404040',
|
|
466
|
+
lineHeight: 1.6,
|
|
467
|
+
pointerEvents: 'none',
|
|
468
|
+
whiteSpace: 'pre-wrap',
|
|
469
|
+
wordBreak: 'break-word',
|
|
470
|
+
}}>
|
|
471
|
+
{guide.description}
|
|
472
|
+
</div>
|
|
473
|
+
)}
|
|
474
|
+
|
|
475
|
+
{/* Minecraft Wiki link */}
|
|
476
|
+
<a
|
|
477
|
+
href={wikiUrl}
|
|
478
|
+
target="_blank"
|
|
479
|
+
rel="noopener noreferrer"
|
|
480
|
+
style={{
|
|
481
|
+
position: 'absolute',
|
|
482
|
+
bottom: 4 * scale,
|
|
483
|
+
right: 8 * scale,
|
|
484
|
+
fontSize,
|
|
485
|
+
fontFamily: "'Minecraftia', 'Minecraft', monospace",
|
|
486
|
+
color: '#3366bb',
|
|
487
|
+
textDecoration: 'underline',
|
|
488
|
+
cursor: 'pointer',
|
|
489
|
+
}}
|
|
490
|
+
>
|
|
491
|
+
Minecraft Wiki
|
|
492
|
+
</a>
|
|
245
493
|
</div>
|
|
246
494
|
)
|
|
247
495
|
}
|