Major UI overhaul: bigger icons, creative inventory tabs, grouped smelting, alerts popup, analytics, mod icons
- Inventory: Bigger item icons (48px) filling their slots, MC-style hover tooltips - Creative inventory category tabs (All/Blocks/Tools/Combat/Food/Redstone/Materials/Misc) - Smelting recipes grouped by output item with collapsible sections - Alerts moved from full tab to popup overlay triggered from header bell icon - New Analytics tab with SVG charts (storage over time, top items, item trend lookup) - Mod icon support for ComputerCraft (CC:Tweaked) via GitHub CDN fallback - Server: new /api/history-summary endpoint for aggregate storage history - Store: fetchHistorySummary and fetchItemHistory for analytics data
This commit is contained in:
@@ -5,6 +5,12 @@ import './ItemIcon.css';
|
||||
// Minecraft assets CDN - actual game textures (16x16 pixel art)
|
||||
const MC_ASSETS_BASE = 'https://cdn.jsdelivr.net/gh/InventivetalentDev/minecraft-assets@1.21.4/assets/minecraft/textures';
|
||||
|
||||
// Mod texture CDN bases (GitHub raw URLs for known mod repos)
|
||||
const MOD_TEXTURE_BASES = {
|
||||
computercraft: 'https://raw.githubusercontent.com/cc-tweaked/CC-Tweaked/mc-1.20.x/projects/common/src/main/resources/assets/computercraft/textures',
|
||||
create: 'https://raw.githubusercontent.com/Creators-of-Create/Create/mc1.20.1/dev/src/main/resources/assets/create/textures',
|
||||
};
|
||||
|
||||
// Some items have texture names that differ from their registry name
|
||||
const TEXTURE_ALIASES = {
|
||||
// Crops / seeds
|
||||
@@ -102,26 +108,37 @@ const BLOCK_TEXTURE_SUFFIXES = {
|
||||
|
||||
/**
|
||||
* Attempt multiple texture URLs in order.
|
||||
* 1. item/{name}.png
|
||||
* 2. block/{name}.png (with optional suffix)
|
||||
* 3. emoji fallback
|
||||
* For vanilla: item/{name}.png → block/{name}.png
|
||||
* For mods: mod repo item/ → mod repo block/ → vanilla fallback
|
||||
*/
|
||||
function getTextureUrls(shortName) {
|
||||
function getTextureUrls(fullItemName) {
|
||||
// Parse namespace and short name
|
||||
const colonIdx = (fullItemName || '').indexOf(':');
|
||||
const namespace = colonIdx >= 0 ? fullItemName.substring(0, colonIdx) : 'minecraft';
|
||||
const shortName = colonIdx >= 0 ? fullItemName.substring(colonIdx + 1) : fullItemName;
|
||||
const alias = TEXTURE_ALIASES[shortName] || shortName;
|
||||
const urls = [];
|
||||
|
||||
// For non-minecraft mods, try mod-specific URLs first
|
||||
if (namespace !== 'minecraft') {
|
||||
const modBase = MOD_TEXTURE_BASES[namespace];
|
||||
if (modBase) {
|
||||
urls.push(`${modBase}/item/${shortName}.png`);
|
||||
urls.push(`${modBase}/block/${shortName}.png`);
|
||||
urls.push(`${modBase}/block/${shortName}_front.png`);
|
||||
urls.push(`${modBase}/block/${shortName}_side.png`);
|
||||
}
|
||||
}
|
||||
|
||||
// Vanilla texture URLs
|
||||
if (BLOCK_TEXTURES.has(shortName)) {
|
||||
// Known block — try block texture first
|
||||
const suffix = BLOCK_TEXTURE_SUFFIXES[shortName] || '';
|
||||
urls.push(`${MC_ASSETS_BASE}/block/${alias}${suffix}.png`);
|
||||
// Also try without suffix
|
||||
if (suffix) urls.push(`${MC_ASSETS_BASE}/block/${alias}.png`);
|
||||
}
|
||||
|
||||
// Always try item texture
|
||||
urls.push(`${MC_ASSETS_BASE}/item/${alias}.png`);
|
||||
|
||||
// If not a known block, also try block as fallback
|
||||
if (!BLOCK_TEXTURES.has(shortName)) {
|
||||
urls.push(`${MC_ASSETS_BASE}/block/${alias}.png`);
|
||||
}
|
||||
@@ -134,7 +151,7 @@ const iconCache = new Map();
|
||||
|
||||
/**
|
||||
* Renders a Minecraft item icon using the official game textures.
|
||||
* Cascading fallback: item texture → block texture → emoji
|
||||
* Cascading fallback: mod texture → item texture → block texture → emoji
|
||||
*/
|
||||
function ItemIcon({ itemName, size = 32 }) {
|
||||
const [urlIndex, setUrlIndex] = useState(0);
|
||||
@@ -144,11 +161,11 @@ function ItemIcon({ itemName, size = 32 }) {
|
||||
return <span className="item-icon-emoji" style={{ fontSize: size * 0.7 }}>📦</span>;
|
||||
}
|
||||
|
||||
// Strip namespace (minecraft:diamond → diamond)
|
||||
const shortName = itemName.replace(/^[a-z0-9_.-]+:/, '');
|
||||
// Use full item name as cache key (handles mod namespaces correctly)
|
||||
const cacheKey = itemName.replace(/^minecraft:/, '');
|
||||
|
||||
// Check if we already know this item has no texture
|
||||
if (iconCache.get(shortName) === 'none' || allFailed) {
|
||||
if (iconCache.get(cacheKey) === 'none' || allFailed) {
|
||||
return (
|
||||
<span className="item-icon-emoji" style={{ fontSize: size * 0.7 }}>
|
||||
{getItemEmoji(itemName)}
|
||||
@@ -157,13 +174,13 @@ function ItemIcon({ itemName, size = 32 }) {
|
||||
}
|
||||
|
||||
// If we have a cached URL, use it directly
|
||||
const cachedUrl = iconCache.get(shortName);
|
||||
const cachedUrl = iconCache.get(cacheKey);
|
||||
if (cachedUrl && cachedUrl !== 'none') {
|
||||
return (
|
||||
<img
|
||||
className="item-icon-img"
|
||||
src={cachedUrl}
|
||||
alt={shortName}
|
||||
alt={cacheKey}
|
||||
width={size}
|
||||
height={size}
|
||||
loading="lazy"
|
||||
@@ -171,11 +188,11 @@ function ItemIcon({ itemName, size = 32 }) {
|
||||
);
|
||||
}
|
||||
|
||||
const urls = getTextureUrls(shortName);
|
||||
const urls = getTextureUrls(itemName);
|
||||
const currentUrl = urls[urlIndex];
|
||||
|
||||
if (!currentUrl) {
|
||||
iconCache.set(shortName, 'none');
|
||||
iconCache.set(cacheKey, 'none');
|
||||
return (
|
||||
<span className="item-icon-emoji" style={{ fontSize: size * 0.7 }}>
|
||||
{getItemEmoji(itemName)}
|
||||
@@ -187,18 +204,18 @@ function ItemIcon({ itemName, size = 32 }) {
|
||||
<img
|
||||
className="item-icon-img"
|
||||
src={currentUrl}
|
||||
alt={shortName}
|
||||
alt={cacheKey}
|
||||
width={size}
|
||||
height={size}
|
||||
loading="lazy"
|
||||
onLoad={() => {
|
||||
iconCache.set(shortName, currentUrl);
|
||||
iconCache.set(cacheKey, currentUrl);
|
||||
}}
|
||||
onError={() => {
|
||||
if (urlIndex + 1 < urls.length) {
|
||||
setUrlIndex(urlIndex + 1);
|
||||
} else {
|
||||
iconCache.set(shortName, 'none');
|
||||
iconCache.set(cacheKey, 'none');
|
||||
setAllFailed(true);
|
||||
}
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user