@rmdes/indiekit-endpoint-funkwhale 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/views/favorites.njk +82 -18
- package/views/funkwhale.njk +189 -59
- package/views/listenings.njk +82 -18
- package/views/stats.njk +349 -47
package/package.json
CHANGED
package/views/favorites.njk
CHANGED
|
@@ -1,27 +1,93 @@
|
|
|
1
1
|
{% extends "document.njk" %}
|
|
2
2
|
|
|
3
3
|
{% block content %}
|
|
4
|
+
<style>
|
|
5
|
+
.fw-section { margin-bottom: 2rem; }
|
|
6
|
+
.fw-list {
|
|
7
|
+
list-style: none;
|
|
8
|
+
padding: 0;
|
|
9
|
+
margin: 0 0 1rem 0;
|
|
10
|
+
}
|
|
11
|
+
.fw-list-item {
|
|
12
|
+
display: flex;
|
|
13
|
+
align-items: center;
|
|
14
|
+
gap: 0.75rem;
|
|
15
|
+
padding: 0.75rem 0;
|
|
16
|
+
border-bottom: 1px solid var(--color-border, #eee);
|
|
17
|
+
}
|
|
18
|
+
.fw-list-item:last-child { border-bottom: none; }
|
|
19
|
+
.fw-list-item img {
|
|
20
|
+
width: 48px;
|
|
21
|
+
height: 48px;
|
|
22
|
+
object-fit: cover;
|
|
23
|
+
border-radius: 0.25rem;
|
|
24
|
+
flex-shrink: 0;
|
|
25
|
+
}
|
|
26
|
+
.fw-list-item-placeholder {
|
|
27
|
+
width: 48px;
|
|
28
|
+
height: 48px;
|
|
29
|
+
background: var(--color-border, #ddd);
|
|
30
|
+
border-radius: 0.25rem;
|
|
31
|
+
flex-shrink: 0;
|
|
32
|
+
}
|
|
33
|
+
.fw-list-info {
|
|
34
|
+
flex: 1;
|
|
35
|
+
min-width: 0;
|
|
36
|
+
}
|
|
37
|
+
.fw-list-title {
|
|
38
|
+
display: block;
|
|
39
|
+
font-weight: 500;
|
|
40
|
+
text-decoration: none;
|
|
41
|
+
color: inherit;
|
|
42
|
+
white-space: nowrap;
|
|
43
|
+
overflow: hidden;
|
|
44
|
+
text-overflow: ellipsis;
|
|
45
|
+
}
|
|
46
|
+
.fw-list-title:hover { text-decoration: underline; }
|
|
47
|
+
.fw-list-album {
|
|
48
|
+
margin: 0.25rem 0;
|
|
49
|
+
color: var(--color-text-secondary, #666);
|
|
50
|
+
font-size: 0.875rem;
|
|
51
|
+
}
|
|
52
|
+
.fw-meta {
|
|
53
|
+
display: flex;
|
|
54
|
+
gap: 0.5rem;
|
|
55
|
+
color: var(--color-text-secondary, #666);
|
|
56
|
+
font-size: 0.75rem;
|
|
57
|
+
}
|
|
58
|
+
.fw-pagination {
|
|
59
|
+
display: flex;
|
|
60
|
+
align-items: center;
|
|
61
|
+
justify-content: center;
|
|
62
|
+
gap: 1rem;
|
|
63
|
+
margin-top: 1.5rem;
|
|
64
|
+
}
|
|
65
|
+
.fw-pagination-info {
|
|
66
|
+
color: var(--color-text-secondary, #666);
|
|
67
|
+
}
|
|
68
|
+
</style>
|
|
69
|
+
|
|
4
70
|
{% if error %}
|
|
5
71
|
{{ prose({ text: error.message }) }}
|
|
6
72
|
{% else %}
|
|
7
|
-
<section class="
|
|
73
|
+
<section class="fw-section">
|
|
8
74
|
{% if favorites and favorites.length > 0 %}
|
|
9
|
-
<ul class="
|
|
75
|
+
<ul class="fw-list">
|
|
10
76
|
{% for favorite in favorites %}
|
|
11
|
-
<li class="
|
|
77
|
+
<li class="fw-list-item">
|
|
12
78
|
{% if favorite.coverUrl %}
|
|
13
|
-
<img src="{{ favorite.coverUrl }}" alt=""
|
|
79
|
+
<img src="{{ favorite.coverUrl }}" alt="" loading="lazy">
|
|
14
80
|
{% else %}
|
|
15
|
-
<div class="
|
|
81
|
+
<div class="fw-list-item-placeholder"></div>
|
|
16
82
|
{% endif %}
|
|
17
|
-
<div class="
|
|
18
|
-
<a href="{{ favorite.trackUrl }}" class="
|
|
83
|
+
<div class="fw-list-info">
|
|
84
|
+
<a href="{{ favorite.trackUrl }}" class="fw-list-title" target="_blank" rel="noopener">
|
|
19
85
|
{{ favorite.artist }} - {{ favorite.track }}
|
|
20
86
|
</a>
|
|
21
87
|
{% if favorite.album %}
|
|
22
|
-
<p class="
|
|
88
|
+
<p class="fw-list-album">{{ favorite.album }}</p>
|
|
23
89
|
{% endif %}
|
|
24
|
-
<small class="
|
|
90
|
+
<small class="fw-meta">
|
|
25
91
|
<span>{{ favorite.duration }}</span>
|
|
26
92
|
<span>Favorited {{ favorite.relativeTime }}</span>
|
|
27
93
|
</small>
|
|
@@ -32,11 +98,11 @@
|
|
|
32
98
|
|
|
33
99
|
{# Pagination #}
|
|
34
100
|
{% if pagination %}
|
|
35
|
-
<nav class="
|
|
101
|
+
<nav class="fw-pagination">
|
|
36
102
|
{% if pagination.hasPrev %}
|
|
37
103
|
<a href="?page={{ pagination.current - 1 }}" class="button button--secondary">Previous</a>
|
|
38
104
|
{% endif %}
|
|
39
|
-
<span class="
|
|
105
|
+
<span class="fw-pagination-info">
|
|
40
106
|
Page {{ pagination.current }} of {{ pagination.total }}
|
|
41
107
|
</span>
|
|
42
108
|
{% if pagination.hasNext %}
|
|
@@ -49,12 +115,10 @@
|
|
|
49
115
|
{% endif %}
|
|
50
116
|
</section>
|
|
51
117
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}) }}
|
|
58
|
-
</div>
|
|
118
|
+
{{ button({
|
|
119
|
+
classes: "button--secondary",
|
|
120
|
+
href: mountPath,
|
|
121
|
+
text: "Back to Dashboard"
|
|
122
|
+
}) }}
|
|
59
123
|
{% endif %}
|
|
60
124
|
{% endblock %}
|
package/views/funkwhale.njk
CHANGED
|
@@ -1,16 +1,154 @@
|
|
|
1
1
|
{% extends "document.njk" %}
|
|
2
2
|
|
|
3
3
|
{% block content %}
|
|
4
|
+
<style>
|
|
5
|
+
/* Inline styles to ensure proper rendering */
|
|
6
|
+
.fw-section { margin-bottom: 2rem; }
|
|
7
|
+
.fw-section h2 { margin-bottom: 1rem; }
|
|
8
|
+
|
|
9
|
+
/* Stats grid */
|
|
10
|
+
.fw-stats-grid {
|
|
11
|
+
display: grid;
|
|
12
|
+
grid-template-columns: repeat(3, 1fr);
|
|
13
|
+
gap: 1rem;
|
|
14
|
+
margin-bottom: 1rem;
|
|
15
|
+
}
|
|
16
|
+
.fw-stat {
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
align-items: center;
|
|
20
|
+
padding: 1rem;
|
|
21
|
+
background: var(--color-offset, #f5f5f5);
|
|
22
|
+
border-radius: 0.5rem;
|
|
23
|
+
text-align: center;
|
|
24
|
+
}
|
|
25
|
+
.fw-stat-value {
|
|
26
|
+
font-size: 1.5rem;
|
|
27
|
+
font-weight: 700;
|
|
28
|
+
color: var(--color-accent, #3b82f6);
|
|
29
|
+
}
|
|
30
|
+
.fw-stat-label {
|
|
31
|
+
font-size: 0.75rem;
|
|
32
|
+
color: var(--color-text-secondary, #666);
|
|
33
|
+
text-transform: uppercase;
|
|
34
|
+
letter-spacing: 0.05em;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* Featured track */
|
|
38
|
+
.fw-featured {
|
|
39
|
+
display: flex;
|
|
40
|
+
gap: 1rem;
|
|
41
|
+
padding: 1rem;
|
|
42
|
+
background: var(--color-offset, #f5f5f5);
|
|
43
|
+
border-radius: 0.5rem;
|
|
44
|
+
align-items: flex-start;
|
|
45
|
+
}
|
|
46
|
+
.fw-featured img {
|
|
47
|
+
width: 80px;
|
|
48
|
+
height: 80px;
|
|
49
|
+
object-fit: cover;
|
|
50
|
+
border-radius: 0.25rem;
|
|
51
|
+
flex-shrink: 0;
|
|
52
|
+
}
|
|
53
|
+
.fw-featured-info { flex: 1; }
|
|
54
|
+
.fw-featured-title {
|
|
55
|
+
font-weight: 600;
|
|
56
|
+
text-decoration: none;
|
|
57
|
+
color: inherit;
|
|
58
|
+
display: block;
|
|
59
|
+
}
|
|
60
|
+
.fw-featured-title:hover { text-decoration: underline; }
|
|
61
|
+
.fw-featured-album {
|
|
62
|
+
margin: 0.25rem 0;
|
|
63
|
+
color: var(--color-text-secondary, #666);
|
|
64
|
+
font-size: 0.875rem;
|
|
65
|
+
}
|
|
66
|
+
.fw-meta {
|
|
67
|
+
display: flex;
|
|
68
|
+
gap: 0.5rem;
|
|
69
|
+
color: var(--color-text-secondary, #666);
|
|
70
|
+
font-size: 0.75rem;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* Now playing animation */
|
|
74
|
+
.fw-playing-status {
|
|
75
|
+
display: inline-flex;
|
|
76
|
+
align-items: center;
|
|
77
|
+
gap: 0.5rem;
|
|
78
|
+
color: #22c55e;
|
|
79
|
+
}
|
|
80
|
+
.fw-bars {
|
|
81
|
+
display: flex;
|
|
82
|
+
gap: 2px;
|
|
83
|
+
align-items: flex-end;
|
|
84
|
+
height: 16px;
|
|
85
|
+
}
|
|
86
|
+
.fw-bars span {
|
|
87
|
+
width: 3px;
|
|
88
|
+
background: currentColor;
|
|
89
|
+
animation: fw-bar 0.5s infinite ease-in-out alternate;
|
|
90
|
+
}
|
|
91
|
+
.fw-bars span:nth-child(2) { animation-delay: 0.2s; }
|
|
92
|
+
.fw-bars span:nth-child(3) { animation-delay: 0.4s; }
|
|
93
|
+
@keyframes fw-bar {
|
|
94
|
+
from { height: 4px; }
|
|
95
|
+
to { height: 16px; }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* List */
|
|
99
|
+
.fw-list {
|
|
100
|
+
list-style: none;
|
|
101
|
+
padding: 0;
|
|
102
|
+
margin: 0 0 1rem 0;
|
|
103
|
+
}
|
|
104
|
+
.fw-list-item {
|
|
105
|
+
display: flex;
|
|
106
|
+
align-items: center;
|
|
107
|
+
gap: 0.75rem;
|
|
108
|
+
padding: 0.75rem 0;
|
|
109
|
+
border-bottom: 1px solid var(--color-border, #eee);
|
|
110
|
+
}
|
|
111
|
+
.fw-list-item:last-child { border-bottom: none; }
|
|
112
|
+
.fw-list-item img {
|
|
113
|
+
width: 48px;
|
|
114
|
+
height: 48px;
|
|
115
|
+
object-fit: cover;
|
|
116
|
+
border-radius: 0.25rem;
|
|
117
|
+
flex-shrink: 0;
|
|
118
|
+
}
|
|
119
|
+
.fw-list-item-placeholder {
|
|
120
|
+
width: 48px;
|
|
121
|
+
height: 48px;
|
|
122
|
+
background: var(--color-border, #ddd);
|
|
123
|
+
border-radius: 0.25rem;
|
|
124
|
+
flex-shrink: 0;
|
|
125
|
+
}
|
|
126
|
+
.fw-list-info {
|
|
127
|
+
flex: 1;
|
|
128
|
+
min-width: 0;
|
|
129
|
+
}
|
|
130
|
+
.fw-list-title {
|
|
131
|
+
display: block;
|
|
132
|
+
font-weight: 500;
|
|
133
|
+
text-decoration: none;
|
|
134
|
+
color: inherit;
|
|
135
|
+
white-space: nowrap;
|
|
136
|
+
overflow: hidden;
|
|
137
|
+
text-overflow: ellipsis;
|
|
138
|
+
}
|
|
139
|
+
.fw-list-title:hover { text-decoration: underline; }
|
|
140
|
+
</style>
|
|
141
|
+
|
|
4
142
|
{% if error %}
|
|
5
143
|
{{ prose({ text: error.message }) }}
|
|
6
144
|
{% else %}
|
|
7
145
|
{# Now Playing / Recently Played #}
|
|
8
146
|
{% if nowPlaying %}
|
|
9
|
-
<section class="
|
|
147
|
+
<section class="fw-section">
|
|
10
148
|
<h2>
|
|
11
149
|
{% if nowPlaying.status == "now-playing" %}
|
|
12
|
-
<span class="
|
|
13
|
-
<span class="
|
|
150
|
+
<span class="fw-playing-status">
|
|
151
|
+
<span class="fw-bars">
|
|
14
152
|
<span></span><span></span><span></span>
|
|
15
153
|
</span>
|
|
16
154
|
{{ __("funkwhale.nowPlaying") }}
|
|
@@ -19,20 +157,18 @@
|
|
|
19
157
|
{{ __("funkwhale.recentlyPlayed") }}
|
|
20
158
|
{% endif %}
|
|
21
159
|
</h2>
|
|
22
|
-
<article class="
|
|
160
|
+
<article class="fw-featured">
|
|
23
161
|
{% if nowPlaying.coverUrl %}
|
|
24
|
-
<img src="{{ nowPlaying.coverUrl }}" alt=""
|
|
25
|
-
{% else %}
|
|
26
|
-
<div class="funkwhale-track__cover funkwhale-track__cover--placeholder"></div>
|
|
162
|
+
<img src="{{ nowPlaying.coverUrl }}" alt="" loading="lazy">
|
|
27
163
|
{% endif %}
|
|
28
|
-
<div class="
|
|
29
|
-
<a href="{{ nowPlaying.trackUrl }}" class="
|
|
164
|
+
<div class="fw-featured-info">
|
|
165
|
+
<a href="{{ nowPlaying.trackUrl }}" class="fw-featured-title" target="_blank" rel="noopener">
|
|
30
166
|
{{ nowPlaying.artist }} - {{ nowPlaying.track }}
|
|
31
167
|
</a>
|
|
32
168
|
{% if nowPlaying.album %}
|
|
33
|
-
<p class="
|
|
169
|
+
<p class="fw-featured-album">{{ nowPlaying.album }}</p>
|
|
34
170
|
{% endif %}
|
|
35
|
-
<small class="
|
|
171
|
+
<small class="fw-meta">
|
|
36
172
|
<span>{{ nowPlaying.duration }}</span>
|
|
37
173
|
<span>{{ nowPlaying.relativeTime }}</span>
|
|
38
174
|
</small>
|
|
@@ -43,49 +179,47 @@
|
|
|
43
179
|
|
|
44
180
|
{# Quick Stats Summary #}
|
|
45
181
|
{% if summary %}
|
|
46
|
-
<section class="
|
|
182
|
+
<section class="fw-section">
|
|
47
183
|
<h2>{{ __("funkwhale.stats") }}</h2>
|
|
48
|
-
<div class="
|
|
49
|
-
<div class="
|
|
50
|
-
<span class="
|
|
51
|
-
<span class="
|
|
184
|
+
<div class="fw-stats-grid">
|
|
185
|
+
<div class="fw-stat">
|
|
186
|
+
<span class="fw-stat-value">{{ summary.totalPlays | default(0) }}</span>
|
|
187
|
+
<span class="fw-stat-label">{{ __("funkwhale.plays") }}</span>
|
|
52
188
|
</div>
|
|
53
|
-
<div class="
|
|
54
|
-
<span class="
|
|
55
|
-
<span class="
|
|
189
|
+
<div class="fw-stat">
|
|
190
|
+
<span class="fw-stat-value">{{ summary.uniqueTracks | default(0) }}</span>
|
|
191
|
+
<span class="fw-stat-label">{{ __("funkwhale.tracks") }}</span>
|
|
56
192
|
</div>
|
|
57
|
-
<div class="
|
|
58
|
-
<span class="
|
|
59
|
-
<span class="
|
|
193
|
+
<div class="fw-stat">
|
|
194
|
+
<span class="fw-stat-value">{{ summary.uniqueArtists | default(0) }}</span>
|
|
195
|
+
<span class="fw-stat-label">{{ __("funkwhale.artists") }}</span>
|
|
60
196
|
</div>
|
|
61
197
|
</div>
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}) }}
|
|
68
|
-
</div>
|
|
198
|
+
{{ button({
|
|
199
|
+
classes: "button--secondary",
|
|
200
|
+
href: mountPath + "/stats",
|
|
201
|
+
text: __("funkwhale.viewStats")
|
|
202
|
+
}) }}
|
|
69
203
|
</section>
|
|
70
204
|
{% endif %}
|
|
71
205
|
|
|
72
206
|
{# Recent Listenings #}
|
|
73
|
-
<section class="
|
|
207
|
+
<section class="fw-section">
|
|
74
208
|
<h2>{{ __("funkwhale.listenings") }}</h2>
|
|
75
209
|
{% if listenings and listenings.length > 0 %}
|
|
76
|
-
<ul class="
|
|
210
|
+
<ul class="fw-list">
|
|
77
211
|
{% for listening in listenings %}
|
|
78
|
-
<li class="
|
|
212
|
+
<li class="fw-list-item">
|
|
79
213
|
{% if listening.coverUrl %}
|
|
80
|
-
<img src="{{ listening.coverUrl }}" alt=""
|
|
214
|
+
<img src="{{ listening.coverUrl }}" alt="" loading="lazy">
|
|
81
215
|
{% else %}
|
|
82
|
-
<div class="
|
|
216
|
+
<div class="fw-list-item-placeholder"></div>
|
|
83
217
|
{% endif %}
|
|
84
|
-
<div class="
|
|
85
|
-
<a href="{{ listening.trackUrl }}" class="
|
|
218
|
+
<div class="fw-list-info">
|
|
219
|
+
<a href="{{ listening.trackUrl }}" class="fw-list-title" target="_blank" rel="noopener">
|
|
86
220
|
{{ listening.artist }} - {{ listening.track }}
|
|
87
221
|
</a>
|
|
88
|
-
<small class="
|
|
222
|
+
<small class="fw-meta">{{ listening.relativeTime }}</small>
|
|
89
223
|
</div>
|
|
90
224
|
{% if listening.status == "now-playing" %}
|
|
91
225
|
{{ badge({ color: "green", text: __("funkwhale.nowPlaying") }) }}
|
|
@@ -95,48 +229,44 @@
|
|
|
95
229
|
</li>
|
|
96
230
|
{% endfor %}
|
|
97
231
|
</ul>
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}) }}
|
|
104
|
-
</div>
|
|
232
|
+
{{ button({
|
|
233
|
+
classes: "button--secondary",
|
|
234
|
+
href: mountPath + "/listenings",
|
|
235
|
+
text: __("funkwhale.viewAll")
|
|
236
|
+
}) }}
|
|
105
237
|
{% else %}
|
|
106
238
|
{{ prose({ text: __("funkwhale.noRecentPlays") }) }}
|
|
107
239
|
{% endif %}
|
|
108
240
|
</section>
|
|
109
241
|
|
|
110
242
|
{# Favorites #}
|
|
111
|
-
<section class="
|
|
243
|
+
<section class="fw-section">
|
|
112
244
|
<h2>{{ __("funkwhale.favorites") }}</h2>
|
|
113
245
|
{% if favorites and favorites.length > 0 %}
|
|
114
|
-
<ul class="
|
|
246
|
+
<ul class="fw-list">
|
|
115
247
|
{% for favorite in favorites %}
|
|
116
|
-
<li class="
|
|
248
|
+
<li class="fw-list-item">
|
|
117
249
|
{% if favorite.coverUrl %}
|
|
118
|
-
<img src="{{ favorite.coverUrl }}" alt=""
|
|
250
|
+
<img src="{{ favorite.coverUrl }}" alt="" loading="lazy">
|
|
119
251
|
{% else %}
|
|
120
|
-
<div class="
|
|
252
|
+
<div class="fw-list-item-placeholder"></div>
|
|
121
253
|
{% endif %}
|
|
122
|
-
<div class="
|
|
123
|
-
<a href="{{ favorite.trackUrl }}" class="
|
|
254
|
+
<div class="fw-list-info">
|
|
255
|
+
<a href="{{ favorite.trackUrl }}" class="fw-list-title" target="_blank" rel="noopener">
|
|
124
256
|
{{ favorite.artist }} - {{ favorite.track }}
|
|
125
257
|
</a>
|
|
126
258
|
{% if favorite.album %}
|
|
127
|
-
<small class="
|
|
259
|
+
<small class="fw-meta">{{ favorite.album }}</small>
|
|
128
260
|
{% endif %}
|
|
129
261
|
</div>
|
|
130
262
|
</li>
|
|
131
263
|
{% endfor %}
|
|
132
264
|
</ul>
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}) }}
|
|
139
|
-
</div>
|
|
265
|
+
{{ button({
|
|
266
|
+
classes: "button--secondary",
|
|
267
|
+
href: mountPath + "/favorites",
|
|
268
|
+
text: __("funkwhale.viewAll")
|
|
269
|
+
}) }}
|
|
140
270
|
{% else %}
|
|
141
271
|
{{ prose({ text: __("funkwhale.noFavorites") }) }}
|
|
142
272
|
{% endif %}
|
package/views/listenings.njk
CHANGED
|
@@ -1,27 +1,93 @@
|
|
|
1
1
|
{% extends "document.njk" %}
|
|
2
2
|
|
|
3
3
|
{% block content %}
|
|
4
|
+
<style>
|
|
5
|
+
.fw-section { margin-bottom: 2rem; }
|
|
6
|
+
.fw-list {
|
|
7
|
+
list-style: none;
|
|
8
|
+
padding: 0;
|
|
9
|
+
margin: 0 0 1rem 0;
|
|
10
|
+
}
|
|
11
|
+
.fw-list-item {
|
|
12
|
+
display: flex;
|
|
13
|
+
align-items: center;
|
|
14
|
+
gap: 0.75rem;
|
|
15
|
+
padding: 0.75rem 0;
|
|
16
|
+
border-bottom: 1px solid var(--color-border, #eee);
|
|
17
|
+
}
|
|
18
|
+
.fw-list-item:last-child { border-bottom: none; }
|
|
19
|
+
.fw-list-item img {
|
|
20
|
+
width: 48px;
|
|
21
|
+
height: 48px;
|
|
22
|
+
object-fit: cover;
|
|
23
|
+
border-radius: 0.25rem;
|
|
24
|
+
flex-shrink: 0;
|
|
25
|
+
}
|
|
26
|
+
.fw-list-item-placeholder {
|
|
27
|
+
width: 48px;
|
|
28
|
+
height: 48px;
|
|
29
|
+
background: var(--color-border, #ddd);
|
|
30
|
+
border-radius: 0.25rem;
|
|
31
|
+
flex-shrink: 0;
|
|
32
|
+
}
|
|
33
|
+
.fw-list-info {
|
|
34
|
+
flex: 1;
|
|
35
|
+
min-width: 0;
|
|
36
|
+
}
|
|
37
|
+
.fw-list-title {
|
|
38
|
+
display: block;
|
|
39
|
+
font-weight: 500;
|
|
40
|
+
text-decoration: none;
|
|
41
|
+
color: inherit;
|
|
42
|
+
white-space: nowrap;
|
|
43
|
+
overflow: hidden;
|
|
44
|
+
text-overflow: ellipsis;
|
|
45
|
+
}
|
|
46
|
+
.fw-list-title:hover { text-decoration: underline; }
|
|
47
|
+
.fw-list-album {
|
|
48
|
+
margin: 0.25rem 0;
|
|
49
|
+
color: var(--color-text-secondary, #666);
|
|
50
|
+
font-size: 0.875rem;
|
|
51
|
+
}
|
|
52
|
+
.fw-meta {
|
|
53
|
+
display: flex;
|
|
54
|
+
gap: 0.5rem;
|
|
55
|
+
color: var(--color-text-secondary, #666);
|
|
56
|
+
font-size: 0.75rem;
|
|
57
|
+
}
|
|
58
|
+
.fw-pagination {
|
|
59
|
+
display: flex;
|
|
60
|
+
align-items: center;
|
|
61
|
+
justify-content: center;
|
|
62
|
+
gap: 1rem;
|
|
63
|
+
margin-top: 1.5rem;
|
|
64
|
+
}
|
|
65
|
+
.fw-pagination-info {
|
|
66
|
+
color: var(--color-text-secondary, #666);
|
|
67
|
+
}
|
|
68
|
+
</style>
|
|
69
|
+
|
|
4
70
|
{% if error %}
|
|
5
71
|
{{ prose({ text: error.message }) }}
|
|
6
72
|
{% else %}
|
|
7
|
-
<section class="
|
|
73
|
+
<section class="fw-section">
|
|
8
74
|
{% if listenings and listenings.length > 0 %}
|
|
9
|
-
<ul class="
|
|
75
|
+
<ul class="fw-list">
|
|
10
76
|
{% for listening in listenings %}
|
|
11
|
-
<li class="
|
|
77
|
+
<li class="fw-list-item">
|
|
12
78
|
{% if listening.coverUrl %}
|
|
13
|
-
<img src="{{ listening.coverUrl }}" alt=""
|
|
79
|
+
<img src="{{ listening.coverUrl }}" alt="" loading="lazy">
|
|
14
80
|
{% else %}
|
|
15
|
-
<div class="
|
|
81
|
+
<div class="fw-list-item-placeholder"></div>
|
|
16
82
|
{% endif %}
|
|
17
|
-
<div class="
|
|
18
|
-
<a href="{{ listening.trackUrl }}" class="
|
|
83
|
+
<div class="fw-list-info">
|
|
84
|
+
<a href="{{ listening.trackUrl }}" class="fw-list-title" target="_blank" rel="noopener">
|
|
19
85
|
{{ listening.artist }} - {{ listening.track }}
|
|
20
86
|
</a>
|
|
21
87
|
{% if listening.album %}
|
|
22
|
-
<p class="
|
|
88
|
+
<p class="fw-list-album">{{ listening.album }}</p>
|
|
23
89
|
{% endif %}
|
|
24
|
-
<small class="
|
|
90
|
+
<small class="fw-meta">
|
|
25
91
|
<span>{{ listening.duration }}</span>
|
|
26
92
|
<span>{{ listening.relativeTime }}</span>
|
|
27
93
|
</small>
|
|
@@ -37,11 +103,11 @@
|
|
|
37
103
|
|
|
38
104
|
{# Pagination #}
|
|
39
105
|
{% if pagination %}
|
|
40
|
-
<nav class="
|
|
106
|
+
<nav class="fw-pagination">
|
|
41
107
|
{% if pagination.hasPrev %}
|
|
42
108
|
<a href="?page={{ pagination.current - 1 }}" class="button button--secondary">Previous</a>
|
|
43
109
|
{% endif %}
|
|
44
|
-
<span class="
|
|
110
|
+
<span class="fw-pagination-info">
|
|
45
111
|
Page {{ pagination.current }} of {{ pagination.total }}
|
|
46
112
|
</span>
|
|
47
113
|
{% if pagination.hasNext %}
|
|
@@ -54,12 +120,10 @@
|
|
|
54
120
|
{% endif %}
|
|
55
121
|
</section>
|
|
56
122
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}) }}
|
|
63
|
-
</div>
|
|
123
|
+
{{ button({
|
|
124
|
+
classes: "button--secondary",
|
|
125
|
+
href: mountPath,
|
|
126
|
+
text: "Back to Dashboard"
|
|
127
|
+
}) }}
|
|
64
128
|
{% endif %}
|
|
65
129
|
{% endblock %}
|
package/views/stats.njk
CHANGED
|
@@ -1,87 +1,391 @@
|
|
|
1
1
|
{% extends "document.njk" %}
|
|
2
2
|
|
|
3
3
|
{% block content %}
|
|
4
|
+
<style>
|
|
5
|
+
.fw-section { margin-bottom: 2rem; }
|
|
6
|
+
|
|
7
|
+
/* Stats grid */
|
|
8
|
+
.fw-stats-grid {
|
|
9
|
+
display: grid;
|
|
10
|
+
grid-template-columns: repeat(4, 1fr);
|
|
11
|
+
gap: 1rem;
|
|
12
|
+
margin-bottom: 1.5rem;
|
|
13
|
+
}
|
|
14
|
+
@media (max-width: 600px) {
|
|
15
|
+
.fw-stats-grid { grid-template-columns: repeat(2, 1fr); }
|
|
16
|
+
}
|
|
17
|
+
.fw-stat {
|
|
18
|
+
display: flex;
|
|
19
|
+
flex-direction: column;
|
|
20
|
+
align-items: center;
|
|
21
|
+
padding: 1rem;
|
|
22
|
+
background: var(--color-offset, #f5f5f5);
|
|
23
|
+
border-radius: 0.5rem;
|
|
24
|
+
text-align: center;
|
|
25
|
+
}
|
|
26
|
+
.fw-stat-value {
|
|
27
|
+
font-size: 1.5rem;
|
|
28
|
+
font-weight: 700;
|
|
29
|
+
color: var(--color-accent, #3b82f6);
|
|
30
|
+
}
|
|
31
|
+
.fw-stat-label {
|
|
32
|
+
font-size: 0.75rem;
|
|
33
|
+
color: var(--color-text-secondary, #666);
|
|
34
|
+
text-transform: uppercase;
|
|
35
|
+
letter-spacing: 0.05em;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* Tabs */
|
|
39
|
+
.fw-tabs {
|
|
40
|
+
display: flex;
|
|
41
|
+
gap: 0.25rem;
|
|
42
|
+
margin-bottom: 1.5rem;
|
|
43
|
+
border-bottom: 1px solid var(--color-border, #ddd);
|
|
44
|
+
}
|
|
45
|
+
.fw-tab {
|
|
46
|
+
padding: 0.75rem 1rem;
|
|
47
|
+
border: none;
|
|
48
|
+
background: none;
|
|
49
|
+
color: var(--color-text-secondary, #666);
|
|
50
|
+
cursor: pointer;
|
|
51
|
+
border-bottom: 2px solid transparent;
|
|
52
|
+
margin-bottom: -1px;
|
|
53
|
+
font-size: 0.875rem;
|
|
54
|
+
font-weight: 500;
|
|
55
|
+
}
|
|
56
|
+
.fw-tab:hover { color: inherit; }
|
|
57
|
+
.fw-tab--active {
|
|
58
|
+
color: var(--color-accent, #3b82f6);
|
|
59
|
+
border-bottom-color: var(--color-accent, #3b82f6);
|
|
60
|
+
}
|
|
61
|
+
.fw-tab-content { display: none; }
|
|
62
|
+
.fw-tab-content--active { display: block; }
|
|
63
|
+
|
|
64
|
+
/* Top list */
|
|
65
|
+
.fw-top-list {
|
|
66
|
+
list-style: none;
|
|
67
|
+
padding: 0;
|
|
68
|
+
margin: 0 0 1.5rem 0;
|
|
69
|
+
}
|
|
70
|
+
.fw-top-list li {
|
|
71
|
+
display: flex;
|
|
72
|
+
align-items: center;
|
|
73
|
+
gap: 0.75rem;
|
|
74
|
+
padding: 0.5rem 0;
|
|
75
|
+
border-bottom: 1px solid var(--color-border, #eee);
|
|
76
|
+
}
|
|
77
|
+
.fw-top-list li:last-child { border-bottom: none; }
|
|
78
|
+
.fw-rank {
|
|
79
|
+
width: 1.5rem;
|
|
80
|
+
height: 1.5rem;
|
|
81
|
+
display: flex;
|
|
82
|
+
align-items: center;
|
|
83
|
+
justify-content: center;
|
|
84
|
+
font-size: 0.75rem;
|
|
85
|
+
font-weight: 600;
|
|
86
|
+
background: var(--color-offset, #f5f5f5);
|
|
87
|
+
border-radius: 50%;
|
|
88
|
+
color: var(--color-text-secondary, #666);
|
|
89
|
+
flex-shrink: 0;
|
|
90
|
+
}
|
|
91
|
+
.fw-name { flex: 1; font-weight: 500; }
|
|
92
|
+
.fw-count {
|
|
93
|
+
font-size: 0.875rem;
|
|
94
|
+
color: var(--color-text-secondary, #666);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* Album grid */
|
|
98
|
+
.fw-album-grid {
|
|
99
|
+
display: grid;
|
|
100
|
+
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
|
101
|
+
gap: 1rem;
|
|
102
|
+
list-style: none;
|
|
103
|
+
padding: 0;
|
|
104
|
+
margin: 0 0 1.5rem 0;
|
|
105
|
+
}
|
|
106
|
+
.fw-album {
|
|
107
|
+
display: flex;
|
|
108
|
+
flex-direction: column;
|
|
109
|
+
gap: 0.5rem;
|
|
110
|
+
}
|
|
111
|
+
.fw-album img {
|
|
112
|
+
width: 100%;
|
|
113
|
+
aspect-ratio: 1;
|
|
114
|
+
object-fit: cover;
|
|
115
|
+
border-radius: 0.25rem;
|
|
116
|
+
}
|
|
117
|
+
.fw-album-placeholder {
|
|
118
|
+
width: 100%;
|
|
119
|
+
aspect-ratio: 1;
|
|
120
|
+
background: var(--color-border, #ddd);
|
|
121
|
+
border-radius: 0.25rem;
|
|
122
|
+
}
|
|
123
|
+
.fw-album-title {
|
|
124
|
+
font-weight: 500;
|
|
125
|
+
font-size: 0.875rem;
|
|
126
|
+
white-space: nowrap;
|
|
127
|
+
overflow: hidden;
|
|
128
|
+
text-overflow: ellipsis;
|
|
129
|
+
}
|
|
130
|
+
.fw-album-artist {
|
|
131
|
+
font-size: 0.75rem;
|
|
132
|
+
color: var(--color-text-secondary, #666);
|
|
133
|
+
white-space: nowrap;
|
|
134
|
+
overflow: hidden;
|
|
135
|
+
text-overflow: ellipsis;
|
|
136
|
+
}
|
|
137
|
+
.fw-album-plays {
|
|
138
|
+
font-size: 0.75rem;
|
|
139
|
+
color: var(--color-text-secondary, #666);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/* Chart */
|
|
143
|
+
.fw-chart { margin: 1rem 0 1.5rem 0; }
|
|
144
|
+
.fw-chart-bars {
|
|
145
|
+
display: flex;
|
|
146
|
+
align-items: flex-end;
|
|
147
|
+
gap: 2px;
|
|
148
|
+
height: 120px;
|
|
149
|
+
padding: 0.5rem 0;
|
|
150
|
+
}
|
|
151
|
+
.fw-chart-bar-wrapper {
|
|
152
|
+
flex: 1;
|
|
153
|
+
height: 100%;
|
|
154
|
+
display: flex;
|
|
155
|
+
align-items: flex-end;
|
|
156
|
+
}
|
|
157
|
+
.fw-chart-bar {
|
|
158
|
+
width: 100%;
|
|
159
|
+
background: var(--color-accent, #3b82f6);
|
|
160
|
+
border-radius: 2px 2px 0 0;
|
|
161
|
+
min-height: 2px;
|
|
162
|
+
transition: height 0.3s ease;
|
|
163
|
+
}
|
|
164
|
+
.fw-chart-bar-wrapper:hover .fw-chart-bar {
|
|
165
|
+
background: var(--color-accent-hover, #2563eb);
|
|
166
|
+
}
|
|
167
|
+
.fw-chart-labels {
|
|
168
|
+
display: flex;
|
|
169
|
+
justify-content: space-between;
|
|
170
|
+
font-size: 0.75rem;
|
|
171
|
+
color: var(--color-text-secondary, #666);
|
|
172
|
+
margin-top: 0.5rem;
|
|
173
|
+
}
|
|
174
|
+
</style>
|
|
175
|
+
|
|
4
176
|
{% if error %}
|
|
5
177
|
{{ prose({ text: error.message }) }}
|
|
6
178
|
{% else %}
|
|
7
|
-
<section class="
|
|
179
|
+
<section class="fw-section">
|
|
8
180
|
{# Tabs #}
|
|
9
|
-
<div class="
|
|
10
|
-
<button class="
|
|
181
|
+
<div class="fw-tabs" role="tablist">
|
|
182
|
+
<button class="fw-tab fw-tab--active" role="tab" data-tab="all" aria-selected="true">
|
|
11
183
|
{{ __("funkwhale.allTime") }}
|
|
12
184
|
</button>
|
|
13
|
-
<button class="
|
|
185
|
+
<button class="fw-tab" role="tab" data-tab="month" aria-selected="false">
|
|
14
186
|
{{ __("funkwhale.thisMonth") }}
|
|
15
187
|
</button>
|
|
16
|
-
<button class="
|
|
188
|
+
<button class="fw-tab" role="tab" data-tab="week" aria-selected="false">
|
|
17
189
|
{{ __("funkwhale.thisWeek") }}
|
|
18
190
|
</button>
|
|
19
|
-
<button class="
|
|
191
|
+
<button class="fw-tab" role="tab" data-tab="trends" aria-selected="false">
|
|
20
192
|
{{ __("funkwhale.trends") }}
|
|
21
193
|
</button>
|
|
22
194
|
</div>
|
|
23
195
|
|
|
24
196
|
{# All Time Tab #}
|
|
25
|
-
<div class="
|
|
26
|
-
{% set
|
|
27
|
-
|
|
197
|
+
<div class="fw-tab-content fw-tab-content--active" id="tab-all" role="tabpanel">
|
|
198
|
+
{% set s = stats.summary.all %}
|
|
199
|
+
<div class="fw-stats-grid">
|
|
200
|
+
<div class="fw-stat">
|
|
201
|
+
<span class="fw-stat-value">{{ s.totalPlays | default(0) }}</span>
|
|
202
|
+
<span class="fw-stat-label">{{ __("funkwhale.plays") }}</span>
|
|
203
|
+
</div>
|
|
204
|
+
<div class="fw-stat">
|
|
205
|
+
<span class="fw-stat-value">{{ s.uniqueTracks | default(0) }}</span>
|
|
206
|
+
<span class="fw-stat-label">{{ __("funkwhale.tracks") }}</span>
|
|
207
|
+
</div>
|
|
208
|
+
<div class="fw-stat">
|
|
209
|
+
<span class="fw-stat-value">{{ s.uniqueArtists | default(0) }}</span>
|
|
210
|
+
<span class="fw-stat-label">{{ __("funkwhale.artists") }}</span>
|
|
211
|
+
</div>
|
|
212
|
+
<div class="fw-stat">
|
|
213
|
+
<span class="fw-stat-value">{{ s.totalDurationFormatted | default("0m") }}</span>
|
|
214
|
+
<span class="fw-stat-label">{{ __("funkwhale.listeningTime") }}</span>
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
28
217
|
|
|
29
218
|
<h3>{{ __("funkwhale.topArtists") }}</h3>
|
|
30
|
-
{%
|
|
31
|
-
|
|
219
|
+
{% if stats.topArtists.all and stats.topArtists.all.length > 0 %}
|
|
220
|
+
<ol class="fw-top-list">
|
|
221
|
+
{% for artist in stats.topArtists.all %}
|
|
222
|
+
<li>
|
|
223
|
+
<span class="fw-rank">{{ loop.index }}</span>
|
|
224
|
+
<span class="fw-name">{{ artist.name }}</span>
|
|
225
|
+
<span class="fw-count">{{ artist.playCount }} {{ __("funkwhale.plays") }}</span>
|
|
226
|
+
</li>
|
|
227
|
+
{% endfor %}
|
|
228
|
+
</ol>
|
|
229
|
+
{% else %}
|
|
230
|
+
{{ prose({ text: "No data yet" }) }}
|
|
231
|
+
{% endif %}
|
|
32
232
|
|
|
33
233
|
<h3>{{ __("funkwhale.topAlbums") }}</h3>
|
|
34
|
-
{%
|
|
35
|
-
|
|
234
|
+
{% if stats.topAlbums.all and stats.topAlbums.all.length > 0 %}
|
|
235
|
+
<ul class="fw-album-grid">
|
|
236
|
+
{% for album in stats.topAlbums.all %}
|
|
237
|
+
<li class="fw-album">
|
|
238
|
+
{% if album.coverUrl %}
|
|
239
|
+
<img src="{{ album.coverUrl }}" alt="" loading="lazy">
|
|
240
|
+
{% else %}
|
|
241
|
+
<div class="fw-album-placeholder"></div>
|
|
242
|
+
{% endif %}
|
|
243
|
+
<span class="fw-album-title">{{ album.title }}</span>
|
|
244
|
+
<span class="fw-album-artist">{{ album.artist }}</span>
|
|
245
|
+
<span class="fw-album-plays">{{ album.playCount }} {{ __("funkwhale.plays") }}</span>
|
|
246
|
+
</li>
|
|
247
|
+
{% endfor %}
|
|
248
|
+
</ul>
|
|
249
|
+
{% else %}
|
|
250
|
+
{{ prose({ text: "No data yet" }) }}
|
|
251
|
+
{% endif %}
|
|
36
252
|
</div>
|
|
37
253
|
|
|
38
254
|
{# This Month Tab #}
|
|
39
|
-
<div class="
|
|
40
|
-
{% set
|
|
41
|
-
|
|
255
|
+
<div class="fw-tab-content" id="tab-month" role="tabpanel" hidden>
|
|
256
|
+
{% set s = stats.summary.month %}
|
|
257
|
+
<div class="fw-stats-grid">
|
|
258
|
+
<div class="fw-stat">
|
|
259
|
+
<span class="fw-stat-value">{{ s.totalPlays | default(0) }}</span>
|
|
260
|
+
<span class="fw-stat-label">{{ __("funkwhale.plays") }}</span>
|
|
261
|
+
</div>
|
|
262
|
+
<div class="fw-stat">
|
|
263
|
+
<span class="fw-stat-value">{{ s.uniqueTracks | default(0) }}</span>
|
|
264
|
+
<span class="fw-stat-label">{{ __("funkwhale.tracks") }}</span>
|
|
265
|
+
</div>
|
|
266
|
+
<div class="fw-stat">
|
|
267
|
+
<span class="fw-stat-value">{{ s.uniqueArtists | default(0) }}</span>
|
|
268
|
+
<span class="fw-stat-label">{{ __("funkwhale.artists") }}</span>
|
|
269
|
+
</div>
|
|
270
|
+
<div class="fw-stat">
|
|
271
|
+
<span class="fw-stat-value">{{ s.totalDurationFormatted | default("0m") }}</span>
|
|
272
|
+
<span class="fw-stat-label">{{ __("funkwhale.listeningTime") }}</span>
|
|
273
|
+
</div>
|
|
274
|
+
</div>
|
|
42
275
|
|
|
43
276
|
<h3>{{ __("funkwhale.topArtists") }}</h3>
|
|
44
|
-
{%
|
|
45
|
-
|
|
277
|
+
{% if stats.topArtists.month and stats.topArtists.month.length > 0 %}
|
|
278
|
+
<ol class="fw-top-list">
|
|
279
|
+
{% for artist in stats.topArtists.month %}
|
|
280
|
+
<li>
|
|
281
|
+
<span class="fw-rank">{{ loop.index }}</span>
|
|
282
|
+
<span class="fw-name">{{ artist.name }}</span>
|
|
283
|
+
<span class="fw-count">{{ artist.playCount }} {{ __("funkwhale.plays") }}</span>
|
|
284
|
+
</li>
|
|
285
|
+
{% endfor %}
|
|
286
|
+
</ol>
|
|
287
|
+
{% else %}
|
|
288
|
+
{{ prose({ text: "No data yet" }) }}
|
|
289
|
+
{% endif %}
|
|
46
290
|
|
|
47
291
|
<h3>{{ __("funkwhale.topAlbums") }}</h3>
|
|
48
|
-
{%
|
|
49
|
-
|
|
292
|
+
{% if stats.topAlbums.month and stats.topAlbums.month.length > 0 %}
|
|
293
|
+
<ul class="fw-album-grid">
|
|
294
|
+
{% for album in stats.topAlbums.month %}
|
|
295
|
+
<li class="fw-album">
|
|
296
|
+
{% if album.coverUrl %}
|
|
297
|
+
<img src="{{ album.coverUrl }}" alt="" loading="lazy">
|
|
298
|
+
{% else %}
|
|
299
|
+
<div class="fw-album-placeholder"></div>
|
|
300
|
+
{% endif %}
|
|
301
|
+
<span class="fw-album-title">{{ album.title }}</span>
|
|
302
|
+
<span class="fw-album-artist">{{ album.artist }}</span>
|
|
303
|
+
<span class="fw-album-plays">{{ album.playCount }} {{ __("funkwhale.plays") }}</span>
|
|
304
|
+
</li>
|
|
305
|
+
{% endfor %}
|
|
306
|
+
</ul>
|
|
307
|
+
{% else %}
|
|
308
|
+
{{ prose({ text: "No data yet" }) }}
|
|
309
|
+
{% endif %}
|
|
50
310
|
</div>
|
|
51
311
|
|
|
52
312
|
{# This Week Tab #}
|
|
53
|
-
<div class="
|
|
54
|
-
{% set
|
|
55
|
-
|
|
313
|
+
<div class="fw-tab-content" id="tab-week" role="tabpanel" hidden>
|
|
314
|
+
{% set s = stats.summary.week %}
|
|
315
|
+
<div class="fw-stats-grid">
|
|
316
|
+
<div class="fw-stat">
|
|
317
|
+
<span class="fw-stat-value">{{ s.totalPlays | default(0) }}</span>
|
|
318
|
+
<span class="fw-stat-label">{{ __("funkwhale.plays") }}</span>
|
|
319
|
+
</div>
|
|
320
|
+
<div class="fw-stat">
|
|
321
|
+
<span class="fw-stat-value">{{ s.uniqueTracks | default(0) }}</span>
|
|
322
|
+
<span class="fw-stat-label">{{ __("funkwhale.tracks") }}</span>
|
|
323
|
+
</div>
|
|
324
|
+
<div class="fw-stat">
|
|
325
|
+
<span class="fw-stat-value">{{ s.uniqueArtists | default(0) }}</span>
|
|
326
|
+
<span class="fw-stat-label">{{ __("funkwhale.artists") }}</span>
|
|
327
|
+
</div>
|
|
328
|
+
<div class="fw-stat">
|
|
329
|
+
<span class="fw-stat-value">{{ s.totalDurationFormatted | default("0m") }}</span>
|
|
330
|
+
<span class="fw-stat-label">{{ __("funkwhale.listeningTime") }}</span>
|
|
331
|
+
</div>
|
|
332
|
+
</div>
|
|
56
333
|
|
|
57
334
|
<h3>{{ __("funkwhale.topArtists") }}</h3>
|
|
58
|
-
{%
|
|
59
|
-
|
|
335
|
+
{% if stats.topArtists.week and stats.topArtists.week.length > 0 %}
|
|
336
|
+
<ol class="fw-top-list">
|
|
337
|
+
{% for artist in stats.topArtists.week %}
|
|
338
|
+
<li>
|
|
339
|
+
<span class="fw-rank">{{ loop.index }}</span>
|
|
340
|
+
<span class="fw-name">{{ artist.name }}</span>
|
|
341
|
+
<span class="fw-count">{{ artist.playCount }} {{ __("funkwhale.plays") }}</span>
|
|
342
|
+
</li>
|
|
343
|
+
{% endfor %}
|
|
344
|
+
</ol>
|
|
345
|
+
{% else %}
|
|
346
|
+
{{ prose({ text: "No data yet" }) }}
|
|
347
|
+
{% endif %}
|
|
60
348
|
|
|
61
349
|
<h3>{{ __("funkwhale.topAlbums") }}</h3>
|
|
62
|
-
{%
|
|
63
|
-
|
|
350
|
+
{% if stats.topAlbums.week and stats.topAlbums.week.length > 0 %}
|
|
351
|
+
<ul class="fw-album-grid">
|
|
352
|
+
{% for album in stats.topAlbums.week %}
|
|
353
|
+
<li class="fw-album">
|
|
354
|
+
{% if album.coverUrl %}
|
|
355
|
+
<img src="{{ album.coverUrl }}" alt="" loading="lazy">
|
|
356
|
+
{% else %}
|
|
357
|
+
<div class="fw-album-placeholder"></div>
|
|
358
|
+
{% endif %}
|
|
359
|
+
<span class="fw-album-title">{{ album.title }}</span>
|
|
360
|
+
<span class="fw-album-artist">{{ album.artist }}</span>
|
|
361
|
+
<span class="fw-album-plays">{{ album.playCount }} {{ __("funkwhale.plays") }}</span>
|
|
362
|
+
</li>
|
|
363
|
+
{% endfor %}
|
|
364
|
+
</ul>
|
|
365
|
+
{% else %}
|
|
366
|
+
{{ prose({ text: "No data yet" }) }}
|
|
367
|
+
{% endif %}
|
|
64
368
|
</div>
|
|
65
369
|
|
|
66
370
|
{# Trends Tab #}
|
|
67
|
-
<div class="
|
|
371
|
+
<div class="fw-tab-content" id="tab-trends" role="tabpanel" hidden>
|
|
68
372
|
<h3>{{ __("funkwhale.listeningTrend") }}</h3>
|
|
69
373
|
{% if stats.trends and stats.trends.length > 0 %}
|
|
70
|
-
<div class="
|
|
374
|
+
<div class="fw-chart">
|
|
71
375
|
{% set maxCount = 0 %}
|
|
72
376
|
{% for day in stats.trends %}
|
|
73
377
|
{% if day.count > maxCount %}
|
|
74
378
|
{% set maxCount = day.count %}
|
|
75
379
|
{% endif %}
|
|
76
380
|
{% endfor %}
|
|
77
|
-
<div class="
|
|
381
|
+
<div class="fw-chart-bars">
|
|
78
382
|
{% for day in stats.trends %}
|
|
79
|
-
<div class="
|
|
80
|
-
<div class="
|
|
383
|
+
<div class="fw-chart-bar-wrapper" title="{{ day.date }}: {{ day.count }} plays">
|
|
384
|
+
<div class="fw-chart-bar" style="height: {{ (day.count / maxCount * 100) if maxCount > 0 else 0 }}%"></div>
|
|
81
385
|
</div>
|
|
82
386
|
{% endfor %}
|
|
83
387
|
</div>
|
|
84
|
-
<div class="
|
|
388
|
+
<div class="fw-chart-labels">
|
|
85
389
|
<span>{{ stats.trends[0].date }}</span>
|
|
86
390
|
<span>{{ stats.trends[stats.trends.length - 1].date }}</span>
|
|
87
391
|
</div>
|
|
@@ -92,35 +396,33 @@
|
|
|
92
396
|
</div>
|
|
93
397
|
</section>
|
|
94
398
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}) }}
|
|
101
|
-
</div>
|
|
399
|
+
{{ button({
|
|
400
|
+
classes: "button--secondary",
|
|
401
|
+
href: mountPath,
|
|
402
|
+
text: "Back to Dashboard"
|
|
403
|
+
}) }}
|
|
102
404
|
|
|
103
405
|
<script>
|
|
104
406
|
// Simple tab switching
|
|
105
|
-
document.querySelectorAll('.
|
|
407
|
+
document.querySelectorAll('.fw-tab').forEach(tab => {
|
|
106
408
|
tab.addEventListener('click', () => {
|
|
107
409
|
// Update tabs
|
|
108
|
-
document.querySelectorAll('.
|
|
109
|
-
t.classList.remove('
|
|
410
|
+
document.querySelectorAll('.fw-tab').forEach(t => {
|
|
411
|
+
t.classList.remove('fw-tab--active');
|
|
110
412
|
t.setAttribute('aria-selected', 'false');
|
|
111
413
|
});
|
|
112
|
-
tab.classList.add('
|
|
414
|
+
tab.classList.add('fw-tab--active');
|
|
113
415
|
tab.setAttribute('aria-selected', 'true');
|
|
114
416
|
|
|
115
417
|
// Update content
|
|
116
|
-
document.querySelectorAll('.
|
|
117
|
-
content.classList.remove('
|
|
418
|
+
document.querySelectorAll('.fw-tab-content').forEach(content => {
|
|
419
|
+
content.classList.remove('fw-tab-content--active');
|
|
118
420
|
content.hidden = true;
|
|
119
421
|
});
|
|
120
422
|
const targetId = 'tab-' + tab.dataset.tab;
|
|
121
423
|
const target = document.getElementById(targetId);
|
|
122
424
|
if (target) {
|
|
123
|
-
target.classList.add('
|
|
425
|
+
target.classList.add('fw-tab-content--active');
|
|
124
426
|
target.hidden = false;
|
|
125
427
|
}
|
|
126
428
|
});
|