feat: display benefits as individual lines with icon, name, and type

Enhanced the inventory tab to show each benefit on its own line instead of
an icon grid. Each benefit now displays with:
- Icon (40x40px) on the left
- Benefit name and type on the right in format: "Name (TYPE)"

Changes:
- Backend: Send full benefit data (name, type, image_url) instead of just URLs
- Frontend: Render benefits as vertical list of horizontal lines
- CSS: Add new styles for benefit items (.benefit-item, .benefit-icon, .benefit-info)
- Removed: Icon grid layout (chunks of 3) and rewards text field

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
github-actions[bot]
2025-11-08 16:27:10 +11:00
parent 62c0dd5663
commit 3d5080b2b2
3 changed files with 105 additions and 9 deletions

View File

@@ -3,6 +3,7 @@
from __future__ import annotations
import asyncio
import logging
from typing import TYPE_CHECKING, Any
@@ -12,6 +13,9 @@ if TYPE_CHECKING:
from src.web.managers.cache import ImageCache
logger = logging.getLogger("TwitchDrops")
class InventoryManager:
"""Manages drop campaign inventory display in the web interface.
@@ -41,6 +45,16 @@ class InventoryManager:
drops_data = []
for drop in campaign.drops:
# Collect full benefit data (filter out benefits without images)
benefits_data = [
{
"name": benefit.name,
"type": benefit.type.name,
"image_url": str(benefit.image_url),
}
for benefit in drop.benefits
if benefit.image_url
]
drops_data.append(
{
"id": drop.id,
@@ -50,7 +64,7 @@ class InventoryManager:
"progress": drop.progress,
"is_claimed": drop.is_claimed,
"can_claim": drop.can_claim,
"rewards": drop.rewards_text(),
"benefits": benefits_data,
"starts_at": drop.starts_at.isoformat(),
"ends_at": drop.ends_at.isoformat(),
}

View File

@@ -494,14 +494,36 @@ function renderInventory() {
}
const claimedText = t.gui?.inventory?.status?.claimed || 'Claimed';
const dropsHtml = campaign.drops.map(drop => `
<div class="drop-item ${drop.is_claimed ? 'claimed' : ''} ${drop.can_claim ? 'active' : ''}">
<div><strong>${drop.name}</strong></div>
<div>${drop.rewards}</div>
<div>${drop.current_minutes} / ${drop.required_minutes} minutes (${Math.round(drop.progress * 100)}%)</div>
${drop.is_claimed ? `<div>✓ ${claimedText}</div>` : ''}
</div>
`).join('');
const dropsHtml = campaign.drops.map(drop => {
// Generate HTML for each benefit as its own line
let benefitsHtml = '';
if (drop.benefits && drop.benefits.length > 0) {
benefitsHtml = drop.benefits.map(benefit =>
`<div class="benefit-item">
<img src="${benefit.image_url}" alt="${benefit.name}" class="benefit-icon" onerror="this.style.display='none'">
<div class="benefit-info">
<span class="benefit-name">${benefit.name}</span>
<span class="benefit-type">(${benefit.type})</span>
</div>
</div>`
).join('');
}
return `
<div class="drop-item ${drop.is_claimed ? 'claimed' : ''} ${drop.can_claim ? 'active' : ''}">
<div class="drop-item-header">
<div class="drop-item-info">
<div><strong>${drop.name}</strong></div>
</div>
</div>
<div class="benefits-list">
${benefitsHtml}
</div>
<div>${drop.current_minutes} / ${drop.required_minutes} minutes (${Math.round(drop.progress * 100)}%)</div>
${drop.is_claimed ? `<div>✓ ${claimedText}</div>` : ''}
</div>
`;
}).join('');
// Make campaign name clickable if link_url is available
const campaignNameHtml = campaign.link_url

View File

@@ -500,6 +500,66 @@ header h1 {
border-left-color: var(--accent-color);
}
.drop-item-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 8px;
}
.drop-item-info {
flex: 1;
min-width: 0;
}
.benefits-list {
margin-top: 8px;
margin-bottom: 8px;
display: flex;
flex-direction: column;
gap: 8px;
}
.benefit-item {
display: flex;
align-items: center;
gap: 10px;
padding: 8px;
background: var(--bg-panel);
border-radius: 4px;
border: 1px solid var(--border-color);
}
.benefit-icon {
width: 40px;
height: 40px;
border-radius: 4px;
object-fit: cover;
border: 2px solid var(--border-color);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
flex-shrink: 0;
}
.benefit-info {
flex: 1;
display: flex;
align-items: center;
gap: 6px;
min-width: 0;
}
.benefit-name {
font-weight: 500;
font-size: 14px;
color: var(--text-primary);
}
.benefit-type {
font-size: 12px;
color: var(--text-secondary);
font-style: italic;
}
/* Settings */
.settings-container {
max-width: 800px;