Skip to content
Merged

Dev #465

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions frontend/static/js/background-pattern.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* Huntarr - Subtle Background Pattern
* Adds a modern dot grid pattern to the dashboard background
*/

document.addEventListener('DOMContentLoaded', function() {
// Add subtle background pattern styles
const style = document.createElement('style');
style.id = 'background-pattern-styles';

// Pattern style based on the user's preference for dark themes with blue accents
style.textContent = `
/* Subtle dot grid pattern for dark background */
.dashboard-grid::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image:
radial-gradient(circle at 1px 1px, rgba(85, 97, 215, 0.07) 1px, transparent 0);
background-size: 25px 25px;
background-position: -5px -5px;
pointer-events: none;
z-index: 0;
animation: patternFade 8s ease-in-out infinite alternate;
}

/* Make sure all dashboard content stays above the pattern */
.dashboard-grid > * {
position: relative;
z-index: 1;
}

@keyframes patternFade {
0% { opacity: 0.3; }
100% { opacity: 0.8; }
}

/* For mobile - smaller pattern */
@media (max-width: 768px) {
.dashboard-grid::before {
background-size: 20px 20px;
}
}
`;

document.head.appendChild(style);

// Make sure the container has position relative for the pattern to work
const dashboardGrid = document.querySelector('.dashboard-grid');
if (dashboardGrid) {
dashboardGrid.style.position = 'relative';
dashboardGrid.style.overflow = 'hidden';
}
});
60 changes: 60 additions & 0 deletions frontend/static/js/card-hover-effects.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* Huntarr - Card Hover Effects
* Adds subtle hover animations to app cards
*/

document.addEventListener('DOMContentLoaded', function() {
// Add hover effects to app cards
const appCards = document.querySelectorAll('.app-stats-card');

appCards.forEach(card => {
// Add transition properties
card.style.transition = 'transform 0.3s ease, box-shadow 0.3s ease, filter 0.3s ease';

// Mouse enter event - elevate and highlight card
card.addEventListener('mouseenter', function() {
card.style.transform = 'translateY(-5px) scale(1.02)';
card.style.boxShadow = '0 8px 24px rgba(0, 0, 0, 0.2)';
card.style.filter = 'brightness(1.1)';

// Get app type from classes
const appType = getAppType(card);
if (appType) {
// Add app-specific glow effect
const glowColors = {
'sonarr': '0 0 15px rgba(52, 152, 219, 0.4)',
'radarr': '0 0 15px rgba(243, 156, 18, 0.4)',
'lidarr': '0 0 15px rgba(46, 204, 113, 0.4)',
'readarr': '0 0 15px rgba(231, 76, 60, 0.4)',
'whisparr': '0 0 15px rgba(155, 89, 182, 0.4)',
'eros': '0 0 15px rgba(26, 188, 156, 0.4)'
};

if (glowColors[appType]) {
card.style.boxShadow += ', ' + glowColors[appType];
}
}
});

// Mouse leave event - return to normal
card.addEventListener('mouseleave', function() {
card.style.transform = 'translateY(0) scale(1)';
card.style.boxShadow = '';
card.style.filter = 'brightness(1)';
});
});

// Helper function to get app type from card classes
function getAppType(card) {
const classList = card.classList;
const appTypes = ['sonarr', 'radarr', 'lidarr', 'readarr', 'whisparr', 'eros'];

for (const type of appTypes) {
if (classList.contains(type)) {
return type;
}
}

return null;
}
});
138 changes: 138 additions & 0 deletions frontend/static/js/circular-progress.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/**
* Huntarr - Circular Progress Indicators
* Creates animated circular progress indicators for API usage counters
*/

document.addEventListener('DOMContentLoaded', function() {
// Create and inject SVG progress indicators for API counts
const apps = ['sonarr', 'radarr', 'lidarr', 'readarr', 'whisparr', 'eros'];

// App-specific colors matching your existing design
const appColors = {
'sonarr': '#3498db', // Blue
'radarr': '#f39c12', // Yellow/orange
'lidarr': '#2ecc71', // Green
'readarr': '#e74c3c', // Red
'whisparr': '#9b59b6', // Purple
'eros': '#1abc9c' // Teal
};

// Add circular progress indicators to each API count indicator
apps.forEach(app => {
const capContainer = document.querySelector(`#${app}-hourly-cap`);
if (!capContainer) return;

// Get current API count and limit
const countElement = document.querySelector(`#${app}-api-count`);
const limitElement = document.querySelector(`#${app}-api-limit`);

if (!countElement || !limitElement) return;

const count = parseInt(countElement.textContent);
const limit = parseInt(limitElement.textContent);

// Create SVG container for progress circle
const svgSize = 28;
const circleRadius = 10;
const circleStrokeWidth = 2.5;
const circumference = 2 * Math.PI * circleRadius;

// Calculate progress percentage
const percentage = Math.min(count / limit, 1);
const dashOffset = circumference * (1 - percentage);

// Create SVG element
const svgNamespace = "http://www.w3.org/2000/svg";
const svg = document.createElementNS(svgNamespace, "svg");
svg.setAttribute("width", svgSize);
svg.setAttribute("height", svgSize);
svg.setAttribute("viewBox", `0 0 ${svgSize} ${svgSize}`);
svg.classList.add("api-progress-circle");

// Background circle
const bgCircle = document.createElementNS(svgNamespace, "circle");
bgCircle.setAttribute("cx", svgSize / 2);
bgCircle.setAttribute("cy", svgSize / 2);
bgCircle.setAttribute("r", circleRadius);
bgCircle.setAttribute("fill", "none");
bgCircle.setAttribute("stroke", "rgba(255, 255, 255, 0.1)");
bgCircle.setAttribute("stroke-width", circleStrokeWidth);

// Progress circle
const progressCircle = document.createElementNS(svgNamespace, "circle");
progressCircle.setAttribute("cx", svgSize / 2);
progressCircle.setAttribute("cy", svgSize / 2);
progressCircle.setAttribute("r", circleRadius);
progressCircle.setAttribute("fill", "none");
progressCircle.setAttribute("stroke", appColors[app]);
progressCircle.setAttribute("stroke-width", circleStrokeWidth);
progressCircle.setAttribute("stroke-dasharray", circumference);
progressCircle.setAttribute("stroke-dashoffset", dashOffset);
progressCircle.setAttribute("transform", `rotate(-90 ${svgSize/2} ${svgSize/2})`);

// Add circles to SVG
svg.appendChild(bgCircle);
svg.appendChild(progressCircle);

// Add SVG before text content
capContainer.insertBefore(svg, capContainer.firstChild);

// Style for the indicator
const style = document.createElement('style');
style.textContent = `
.api-progress-circle {
margin-right: 5px;
filter: drop-shadow(0 0 3px ${appColors[app]}40);
}

.hourly-cap-status {
display: flex;
align-items: center;
}

@keyframes pulse-${app} {
0% { filter: drop-shadow(0 0 3px ${appColors[app]}40); }
50% { filter: drop-shadow(0 0 6px ${appColors[app]}80); }
100% { filter: drop-shadow(0 0 3px ${appColors[app]}40); }
}

.api-progress-circle circle:nth-child(2) {
animation: pulse-${app} 2s infinite;
transition: stroke-dashoffset 0.5s ease;
}
`;
document.head.appendChild(style);

// Update progress when API counts change
const updateProgressCircle = () => {
const newCount = parseInt(countElement.textContent);
const newLimit = parseInt(limitElement.textContent);
const newPercentage = Math.min(newCount / newLimit, 1);
const newDashOffset = circumference * (1 - newPercentage);

progressCircle.setAttribute("stroke-dashoffset", newDashOffset);

// Change color based on usage percentage
if (newPercentage > 0.9) {
progressCircle.setAttribute("stroke", "#e74c3c"); // Red when near limit
} else if (newPercentage > 0.75) {
progressCircle.setAttribute("stroke", "#f39c12"); // Orange/yellow for moderate usage
} else {
progressCircle.setAttribute("stroke", appColors[app]); // Default color
}
};

// Set up a mutation observer to watch for changes in the count value
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'characterData' || mutation.type === 'childList') {
updateProgressCircle();
}
});
});

// Observe both count and limit elements
observer.observe(countElement, { characterData: true, childList: true, subtree: true });
observer.observe(limitElement, { characterData: true, childList: true, subtree: true });
});
});
19 changes: 18 additions & 1 deletion frontend/static/js/new-main.js
Original file line number Diff line number Diff line change
Expand Up @@ -2918,8 +2918,25 @@ let huntarrUI = {
};

// Initialize when document is ready
document.addEventListener('DOMContentLoaded', () => {
document.addEventListener('DOMContentLoaded', function() {
huntarrUI.init();

// Initialize our enhanced UI features
if (typeof StatsTooltips !== 'undefined') {
StatsTooltips.init();
}

if (typeof CardHoverEffects !== 'undefined') {
CardHoverEffects.init();
}

if (typeof CircularProgress !== 'undefined') {
CircularProgress.init();
}

if (typeof BackgroundPattern !== 'undefined') {
BackgroundPattern.init();
}
});

// Expose huntarrUI to the global scope for access by app modules
Expand Down
Loading
Loading