Merge pull request #7 from rangermix/feat/inventory-benefit-display

feat: Display benefits as individual lines with icon, name, and type
This commit is contained in:
Fengqing Liu
2025-11-08 16:49:21 +11:00
committed by GitHub
6 changed files with 108 additions and 10 deletions

View File

@@ -40,6 +40,7 @@ COPY web/ ./web/
# Create data directory for persistent storage
RUN mkdir -p /app/data && chmod 777 /app/data
RUN mkdir -p /app/logs && chmod 777 /app/logs
# Expose web port
EXPOSE 8080

View File

@@ -1,4 +1,5 @@
from __future__ import annotations
import argparse
import asyncio
import logging

View File

@@ -9,6 +9,7 @@ from datetime import timedelta
from enum import Enum, auto
from typing import TYPE_CHECKING, Any, Literal, NewType
if TYPE_CHECKING:
from collections import abc # noqa
from typing import TypeAlias
@@ -57,7 +58,6 @@ ONLINE_DELAY = timedelta(seconds=120)
WATCH_INTERVAL = timedelta(seconds=59)
class State(Enum):
"""Application state machine states."""

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;