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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minecraft-inventory",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "release": {
@@ -298,7 +298,7 @@ export function InventoryOverlay({
298
298
  lineHeight: 1,
299
299
  }}
300
300
  >
301
- INV 0.1.11
301
+ INV 0.1.13
302
302
  </a>
303
303
  )}
304
304
 
@@ -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
- <div className="mc-inv-item-durability-bg" style={{
200
- position: 'absolute',
201
- bottom: 1,
202
- left: 1,
203
- width: 'calc(100% - 2px)',
204
- height: Math.max(1, Math.round(renderSize * 0.063)),
205
- background: '#000',
206
- pointerEvents: 'none',
207
- }} />
208
- <div className="mc-inv-item-durability-bar" style={{
209
- position: 'absolute',
210
- bottom: 1,
211
- left: 1,
212
- width: `${(item.durability! / item.maxDurability!) * 100}%`,
213
- height: Math.max(1, Math.round(renderSize * 0.063)),
214
- background: barColor,
215
- pointerEvents: 'none',
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' && onItemClick) {
351
- onItemClick(jeiItem)
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 { InventoryBackground } from '../InventoryWindow/InventoryBackground'
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, group !== 'player' && group !== 'hotbar')
122
- const containerSlots = def.slots.filter(
123
- (s) => s.group !== 'player' && s.group !== 'hotbar',
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
- {/* ── Inventory background with container slots only ── */}
199
- <InventoryBackground definition={def} title={guide.title}>
200
- {containerSlots.map((slotDef) => {
201
- const item = slotItems.get(slotDef.index) ?? null
202
- return (
203
- <RecipeItemCell
204
- key={slotDef.index}
205
- item={item}
206
- x={slotDef.x * scale}
207
- y={slotDef.y * scale}
208
- size={slotDef.size ? slotDef.size * scale - 2 * navPx : contentSize}
209
- onHover={handleHoverItem}
210
- />
211
- )
212
- })}
213
-
214
- {/* Description for custom-type guides */}
215
- {guide.description && (
216
- <div style={{
217
- position: 'absolute',
218
- top: 24 * scale,
219
- left: 8 * scale,
220
- right: 8 * scale,
221
- fontSize: navFontSize,
222
- fontFamily: "'Minecraftia', 'Minecraft', monospace",
223
- color: '#404040',
224
- lineHeight: 1.5,
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
- whiteSpace: 'pre-wrap',
227
- wordBreak: 'break-word',
228
- }}>
229
- {guide.description}
230
- </div>
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
- </InventoryBackground>
332
+ </div>
233
333
 
234
- {/* ── Hint below ── */}
235
- <div style={{
236
- marginTop: 2 * scale,
237
- textAlign: 'center',
238
- fontSize: Math.max(5, Math.round(5.5 * scale)),
239
- fontFamily: "'Minecraftia', 'Minecraft', monospace",
240
- color: 'rgba(80,80,80,0.7)',
241
- pointerEvents: 'none',
242
- }}>
243
- Hover ingredient + R / U for nested lookup
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
  }