Skip to content

Commit 149d7d7

Browse files
authored
Merge pull request #465 from plexguide/dev
Dev
2 parents 4de43ea + 348b74f commit 149d7d7

File tree

7 files changed

+519
-2
lines changed

7 files changed

+519
-2
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* Huntarr - Subtle Background Pattern
3+
* Adds a modern dot grid pattern to the dashboard background
4+
*/
5+
6+
document.addEventListener('DOMContentLoaded', function() {
7+
// Add subtle background pattern styles
8+
const style = document.createElement('style');
9+
style.id = 'background-pattern-styles';
10+
11+
// Pattern style based on the user's preference for dark themes with blue accents
12+
style.textContent = `
13+
/* Subtle dot grid pattern for dark background */
14+
.dashboard-grid::before {
15+
content: "";
16+
position: absolute;
17+
top: 0;
18+
left: 0;
19+
right: 0;
20+
bottom: 0;
21+
background-image:
22+
radial-gradient(circle at 1px 1px, rgba(85, 97, 215, 0.07) 1px, transparent 0);
23+
background-size: 25px 25px;
24+
background-position: -5px -5px;
25+
pointer-events: none;
26+
z-index: 0;
27+
animation: patternFade 8s ease-in-out infinite alternate;
28+
}
29+
30+
/* Make sure all dashboard content stays above the pattern */
31+
.dashboard-grid > * {
32+
position: relative;
33+
z-index: 1;
34+
}
35+
36+
@keyframes patternFade {
37+
0% { opacity: 0.3; }
38+
100% { opacity: 0.8; }
39+
}
40+
41+
/* For mobile - smaller pattern */
42+
@media (max-width: 768px) {
43+
.dashboard-grid::before {
44+
background-size: 20px 20px;
45+
}
46+
}
47+
`;
48+
49+
document.head.appendChild(style);
50+
51+
// Make sure the container has position relative for the pattern to work
52+
const dashboardGrid = document.querySelector('.dashboard-grid');
53+
if (dashboardGrid) {
54+
dashboardGrid.style.position = 'relative';
55+
dashboardGrid.style.overflow = 'hidden';
56+
}
57+
});
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/**
2+
* Huntarr - Card Hover Effects
3+
* Adds subtle hover animations to app cards
4+
*/
5+
6+
document.addEventListener('DOMContentLoaded', function() {
7+
// Add hover effects to app cards
8+
const appCards = document.querySelectorAll('.app-stats-card');
9+
10+
appCards.forEach(card => {
11+
// Add transition properties
12+
card.style.transition = 'transform 0.3s ease, box-shadow 0.3s ease, filter 0.3s ease';
13+
14+
// Mouse enter event - elevate and highlight card
15+
card.addEventListener('mouseenter', function() {
16+
card.style.transform = 'translateY(-5px) scale(1.02)';
17+
card.style.boxShadow = '0 8px 24px rgba(0, 0, 0, 0.2)';
18+
card.style.filter = 'brightness(1.1)';
19+
20+
// Get app type from classes
21+
const appType = getAppType(card);
22+
if (appType) {
23+
// Add app-specific glow effect
24+
const glowColors = {
25+
'sonarr': '0 0 15px rgba(52, 152, 219, 0.4)',
26+
'radarr': '0 0 15px rgba(243, 156, 18, 0.4)',
27+
'lidarr': '0 0 15px rgba(46, 204, 113, 0.4)',
28+
'readarr': '0 0 15px rgba(231, 76, 60, 0.4)',
29+
'whisparr': '0 0 15px rgba(155, 89, 182, 0.4)',
30+
'eros': '0 0 15px rgba(26, 188, 156, 0.4)'
31+
};
32+
33+
if (glowColors[appType]) {
34+
card.style.boxShadow += ', ' + glowColors[appType];
35+
}
36+
}
37+
});
38+
39+
// Mouse leave event - return to normal
40+
card.addEventListener('mouseleave', function() {
41+
card.style.transform = 'translateY(0) scale(1)';
42+
card.style.boxShadow = '';
43+
card.style.filter = 'brightness(1)';
44+
});
45+
});
46+
47+
// Helper function to get app type from card classes
48+
function getAppType(card) {
49+
const classList = card.classList;
50+
const appTypes = ['sonarr', 'radarr', 'lidarr', 'readarr', 'whisparr', 'eros'];
51+
52+
for (const type of appTypes) {
53+
if (classList.contains(type)) {
54+
return type;
55+
}
56+
}
57+
58+
return null;
59+
}
60+
});
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/**
2+
* Huntarr - Circular Progress Indicators
3+
* Creates animated circular progress indicators for API usage counters
4+
*/
5+
6+
document.addEventListener('DOMContentLoaded', function() {
7+
// Create and inject SVG progress indicators for API counts
8+
const apps = ['sonarr', 'radarr', 'lidarr', 'readarr', 'whisparr', 'eros'];
9+
10+
// App-specific colors matching your existing design
11+
const appColors = {
12+
'sonarr': '#3498db', // Blue
13+
'radarr': '#f39c12', // Yellow/orange
14+
'lidarr': '#2ecc71', // Green
15+
'readarr': '#e74c3c', // Red
16+
'whisparr': '#9b59b6', // Purple
17+
'eros': '#1abc9c' // Teal
18+
};
19+
20+
// Add circular progress indicators to each API count indicator
21+
apps.forEach(app => {
22+
const capContainer = document.querySelector(`#${app}-hourly-cap`);
23+
if (!capContainer) return;
24+
25+
// Get current API count and limit
26+
const countElement = document.querySelector(`#${app}-api-count`);
27+
const limitElement = document.querySelector(`#${app}-api-limit`);
28+
29+
if (!countElement || !limitElement) return;
30+
31+
const count = parseInt(countElement.textContent);
32+
const limit = parseInt(limitElement.textContent);
33+
34+
// Create SVG container for progress circle
35+
const svgSize = 28;
36+
const circleRadius = 10;
37+
const circleStrokeWidth = 2.5;
38+
const circumference = 2 * Math.PI * circleRadius;
39+
40+
// Calculate progress percentage
41+
const percentage = Math.min(count / limit, 1);
42+
const dashOffset = circumference * (1 - percentage);
43+
44+
// Create SVG element
45+
const svgNamespace = "http://www.w3.org/2000/svg";
46+
const svg = document.createElementNS(svgNamespace, "svg");
47+
svg.setAttribute("width", svgSize);
48+
svg.setAttribute("height", svgSize);
49+
svg.setAttribute("viewBox", `0 0 ${svgSize} ${svgSize}`);
50+
svg.classList.add("api-progress-circle");
51+
52+
// Background circle
53+
const bgCircle = document.createElementNS(svgNamespace, "circle");
54+
bgCircle.setAttribute("cx", svgSize / 2);
55+
bgCircle.setAttribute("cy", svgSize / 2);
56+
bgCircle.setAttribute("r", circleRadius);
57+
bgCircle.setAttribute("fill", "none");
58+
bgCircle.setAttribute("stroke", "rgba(255, 255, 255, 0.1)");
59+
bgCircle.setAttribute("stroke-width", circleStrokeWidth);
60+
61+
// Progress circle
62+
const progressCircle = document.createElementNS(svgNamespace, "circle");
63+
progressCircle.setAttribute("cx", svgSize / 2);
64+
progressCircle.setAttribute("cy", svgSize / 2);
65+
progressCircle.setAttribute("r", circleRadius);
66+
progressCircle.setAttribute("fill", "none");
67+
progressCircle.setAttribute("stroke", appColors[app]);
68+
progressCircle.setAttribute("stroke-width", circleStrokeWidth);
69+
progressCircle.setAttribute("stroke-dasharray", circumference);
70+
progressCircle.setAttribute("stroke-dashoffset", dashOffset);
71+
progressCircle.setAttribute("transform", `rotate(-90 ${svgSize/2} ${svgSize/2})`);
72+
73+
// Add circles to SVG
74+
svg.appendChild(bgCircle);
75+
svg.appendChild(progressCircle);
76+
77+
// Add SVG before text content
78+
capContainer.insertBefore(svg, capContainer.firstChild);
79+
80+
// Style for the indicator
81+
const style = document.createElement('style');
82+
style.textContent = `
83+
.api-progress-circle {
84+
margin-right: 5px;
85+
filter: drop-shadow(0 0 3px ${appColors[app]}40);
86+
}
87+
88+
.hourly-cap-status {
89+
display: flex;
90+
align-items: center;
91+
}
92+
93+
@keyframes pulse-${app} {
94+
0% { filter: drop-shadow(0 0 3px ${appColors[app]}40); }
95+
50% { filter: drop-shadow(0 0 6px ${appColors[app]}80); }
96+
100% { filter: drop-shadow(0 0 3px ${appColors[app]}40); }
97+
}
98+
99+
.api-progress-circle circle:nth-child(2) {
100+
animation: pulse-${app} 2s infinite;
101+
transition: stroke-dashoffset 0.5s ease;
102+
}
103+
`;
104+
document.head.appendChild(style);
105+
106+
// Update progress when API counts change
107+
const updateProgressCircle = () => {
108+
const newCount = parseInt(countElement.textContent);
109+
const newLimit = parseInt(limitElement.textContent);
110+
const newPercentage = Math.min(newCount / newLimit, 1);
111+
const newDashOffset = circumference * (1 - newPercentage);
112+
113+
progressCircle.setAttribute("stroke-dashoffset", newDashOffset);
114+
115+
// Change color based on usage percentage
116+
if (newPercentage > 0.9) {
117+
progressCircle.setAttribute("stroke", "#e74c3c"); // Red when near limit
118+
} else if (newPercentage > 0.75) {
119+
progressCircle.setAttribute("stroke", "#f39c12"); // Orange/yellow for moderate usage
120+
} else {
121+
progressCircle.setAttribute("stroke", appColors[app]); // Default color
122+
}
123+
};
124+
125+
// Set up a mutation observer to watch for changes in the count value
126+
const observer = new MutationObserver((mutations) => {
127+
mutations.forEach((mutation) => {
128+
if (mutation.type === 'characterData' || mutation.type === 'childList') {
129+
updateProgressCircle();
130+
}
131+
});
132+
});
133+
134+
// Observe both count and limit elements
135+
observer.observe(countElement, { characterData: true, childList: true, subtree: true });
136+
observer.observe(limitElement, { characterData: true, childList: true, subtree: true });
137+
});
138+
});

frontend/static/js/new-main.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2918,8 +2918,25 @@ let huntarrUI = {
29182918
};
29192919

29202920
// Initialize when document is ready
2921-
document.addEventListener('DOMContentLoaded', () => {
2921+
document.addEventListener('DOMContentLoaded', function() {
29222922
huntarrUI.init();
2923+
2924+
// Initialize our enhanced UI features
2925+
if (typeof StatsTooltips !== 'undefined') {
2926+
StatsTooltips.init();
2927+
}
2928+
2929+
if (typeof CardHoverEffects !== 'undefined') {
2930+
CardHoverEffects.init();
2931+
}
2932+
2933+
if (typeof CircularProgress !== 'undefined') {
2934+
CircularProgress.init();
2935+
}
2936+
2937+
if (typeof BackgroundPattern !== 'undefined') {
2938+
BackgroundPattern.init();
2939+
}
29232940
});
29242941

29252942
// Expose huntarrUI to the global scope for access by app modules

0 commit comments

Comments
 (0)