source: OpenWorkouts-current/ow/templates/profile.pt @ f2c9e20

current
Last change on this file since f2c9e20 was 02b96c5, checked in by Borja Lopez <borja@…>, 5 years ago

(#7) Added calendar heatmap chart to the profile page.

The calendar shows the current month, each day without a workout represented
by a light grey square, each day with workout data in a red/pink color, picked
up from a gradient generated based on the app main colors, and calculated based
on the amount of workout time for the day.

A tooltip is shown on hover with more info (only the total workouts time for
now)

  • Property mode set to 100644
File size: 22.5 KB
Line 
1<html xmlns="http://www.w3.org/1999/xhtml"
2      xml:lang="en"
3      xmlns:tal="http://xml.zope.org/namespaces/tal"
4      xmlns:metal="http://xml.zope.org/namespaces/metal"
5      xmlns:i18n="http://xml.zope.org/namespaces/i18n"
6      i18n:domain="OpenWorkouts"
7      metal:use-macro="load: base.pt"
8      tal:attributes="lang request.locale_name">
9
10  <metal:head-title metal:fill-slot="head-title">
11    <tal:t i18n:translate="">My profile</tal:t>
12  </metal:head-title>
13
14  <metal:css metal:fill-slot="css">
15    <link rel="stylesheet" href="${request.static_url('ow:static/components/jquery-dropdown/jquery.dropdown.css')}" />
16  </metal:css>
17
18  <metal:content metal:fill-slot="content">
19
20    <div class="user-profile">
21      <div class="user-profile-account">
22        <div>
23          <tal:c tal:condition="getattr(user, 'picture', None)">
24            <img tal:attributes="src request.resource_path(user, 'picture', query={'size': 200})"
25                 width="450" />
26          </tal:c>
27          <div>
28            <h2>
29              <tal:fullname tal:content="user.fullname"></tal:fullname>
30            </h2>
31            <p>
32              <tal:has-nickname tal:condition="user.nickname">
33                <a href=""
34                   tal:attributes="href request.resource_url(request.root, 'profile', user.nickname)"
35                   tal:content="request.resource_url(request.root, 'profile', user.nickname)">
36                </a> |
37              </tal:has-nickname>
38              <span><tal:email tal:content="user.email"></tal:email></span>
39            </p>
40            <div class="profile-bio">
41              <p tal:repeat="paragraph getattr(user, 'bio', '').split('\n')"
42                 tal:content="paragraph"></p>
43            </div>
44            <ul class="workout-options">
45              <li><a href=""
46                     tal:attributes="href request.resource_url(user, 'edit')"
47                     i18n:translate="">edit profile</a></li>
48              <li><a href=""
49                     tal:attributes="href request.resource_url(user, 'passwd')"
50                     i18n:translate="">change password</a></li>
51            </ul>
52          </div>
53          <div class="calendar-heatmap js-calendar-heatmap">
54          </div>
55        </div>
56
57      </div>
58
59      <div class="workout-content">
60        <div class="workout-list">
61          <div class="total-workouts">
62            <span>
63              <tal:w tal:replace="len(workouts)"></tal:w>
64              <tal:t i18n:translate="">workouts</tal:t>
65            </span>
66            <span>
67              <tal:hms tal:define="hms timedelta_to_hms(totals['time'])">
68                <tal:h tal:content="str(hms[0]).zfill(2)"></tal:h>
69                <tal:t i18n:translate="">hours</tal:t>,
70                <tal:h tal:content="str(hms[1]).zfill(2)"></tal:h>
71                <tal:t i18n:translate="">min.</tal:t>
72              </tal:hms>
73            </span>
74            <span>
75              <tal:w tal:replace="round(totals['distance'])"></tal:w>
76              <tal:t i18n:translate="">km</tal:t>
77            </span>
78            <span>
79              <tal:w tal:replace="round(totals['elevation'])"></tal:w>
80              <tal:t i18n:translate="">m</tal:t>
81            </span>
82          </div>
83
84          <div class="month-stats js-month-stats">
85            <div class="svg-content">
86              <svg width="900" height="180" viewBox="0 0 900 180"></svg>
87            </div>
88            <div class="center">
89              <ul class="workout-options filters js-filters">
90                <li><a href="#" class="js-distance is-active" i18n:translate="">distance</a></li>
91                <li><a href="#" class="js-time" i18n:translate="">time</a></li>
92                <li><a href="#" class="js-elevation" i18n:translate="">elevation</a></li>
93              </ul>
94
95              <ul class="workout-options switcher js-switcher"
96                  tal:define="weekly 'week' in request.GET">
97                <li>
98                  <a href="#" class="" i18n:translate=""
99                     tal:attributes="class 'js-weekly is-active' if weekly else 'js-weekly'">
100                    weekly</a></li>
101                <li>
102                  <a href="#" class="" i18n:translate=""
103                     tal:attributes="class 'js-monthly is-active' if not weekly else 'js-monthly'">
104                    monthly</a></li>
105              </ul>
106            </div>
107          </div>
108
109
110          <tal:r tal:repeat="workout workouts">
111
112            <article class="workout-resume">
113
114              <h2 class="workout-title">
115                <a href="" tal:content="workout.title"
116                   tal:attributes="href request.resource_url(workout)"></a>
117              </h2>
118
119              <ul class="workout-info">
120                <li>
121                  <tal:c tal:content="workout.start_in_timezone(user.timezone)"></tal:c>
122                </li>
123                <li>
124                  <!--! use the properly formatted duration instead of the timedelta object -->
125                  <tal:c tal:content="workout._duration"></tal:c>
126                </li>
127                <li tal:condition="workout.distance">
128                  <tal:c tal:content="workout.rounded_distance"></tal:c> km
129                </li>
130                <li tal:condition="workout.uphill">
131                  +<tal:c tal:content="workout.uphill"></tal:c> m
132                </li>
133              </ul>
134
135              <ul class="workout-info" tal:define="hr workout.hr; cad workout.cad">
136                <li tal:condition="hr">
137                  <span i18n:translate="">HR (bpm)</span>:
138                  <tal:c tal:content="hr['avg']"></tal:c>
139                  <tal:t i18n:translate="">Avg.</tal:t>,
140                  <tal:c tal:content="hr['max']"></tal:c>
141                  <tal:t i18n:translate="">Max.</tal:t>
142                </li>
143                <li tal:condition="cad">
144                  <span i18n:translate="">Cad</span>:
145                  <tal:c tal:content="cad['avg']"></tal:c>
146                  <tal:t i18n:translate="">Avg.</tal:t>,
147                  <tal:c tal:content="cad['max']"></tal:c>
148                  <tal:t i18n:translate="">Max.</tal:t>
149                </li>
150              </ul>
151
152              <div class="workout-intro">
153                <p tal:repeat="paragraph workout.trimmed_notes.split('\n')"
154                   tal:content="paragraph"></p>
155              </div>
156
157              <div class="workout-map" tal:condition="workout.has_gpx">
158                <a href="" tal:attributes="href request.resource_url(workout)">
159                  <tal:has-screenshot tal:condition="workout.map_screenshot is not None">
160                    <img src="" tal:attributes="src request.static_url(workout.map_screenshot);
161                              alt workout.title; title workout.title">
162                  </tal:has-screenshot>
163                  <tal:has-not-screenshot tal:condition="workout.map_screenshot is None">
164                    <img src="" tal:attributes="src request.static_url('ow:static/media/img/no_map.gif');
165                              alt workout.title; title workout.title; class 'js-needs-map'">
166                  </tal:has-not-screenshot>
167                </a>
168              </div>
169
170            </article>
171
172          </tal:r>
173        </div>
174
175        <div class="workout-aside">
176
177          <h3 i18n:translate="">Profile info</h3>
178
179          <ul class="profile-data">
180            <li>
181              <span><tal:t i18n:translate="">Gender:</tal:t></span>
182              <tal:c tal:content="user_gender"></tal:c>
183            </li>
184            <li tal:define="birth_date getattr(user, 'birth_date', None)">
185              <span><tal:t i18n:translate="">Birth date:</tal:t></span>
186              <tal:c tal:condition="birth_date"
187                     tal:content="birth_date.strftime('%d/%m/%Y')"></tal:c>
188              <tal:c tal:condition="birth_date is None">-</tal:c>
189            </li>
190            <li>
191              <span><tal:t i18n:translate="">Height:</tal:t></span>
192              <tal:c tal:content="getattr(user, 'height', '-')"></tal:c> meters
193            </li>
194            <li>
195              <span><tal:t i18n:translate="">Weight:</tal:t></span>
196              <tal:c tal:content="getattr(user, 'weight', '-')"></tal:c> kg
197            </li>
198          </ul>
199
200          <tal:has-workouts tal:condition="profile_stats['sports']">
201
202            <h3 i18n:translate="">Workout stats</h3>
203
204            <p>
205              <a href="" data-jq-dropdown="#jq-dropdown-sports"
206                 class="profile-dropdown-sports js-jq-dropdown-sel-sports">
207                <strong tal:content="profile_stats['current_sport']"></strong>
208                <i class="arrow down"></i>
209              </a>
210            </p>
211
212            <tal:sports tal:repeat="sport profile_stats['sports']">
213              <div class="" tal:attributes="class 'js-sport-stats js-' + sport">
214
215                <a href="" data-jq-dropdown=""
216                   tal:attributes="data-jq-dropdown '#jq-dropdown-' + sport;
217                         class 'profile-dropdown-years js-jq-dropdown-sel-' + sport">
218                  <strong tal:content="profile_stats['current_year']"></strong>
219                  <i class="arrow down"></i>
220                </a>
221
222                <tal:years tal:repeat="year profile_stats['years']">
223                  <div class="" tal:attributes="class 'js-year-stats js-' + sport + '-' + str(year)">
224                    <ul class="profile-data"
225                        tal:define="sport_totals user.sport_totals(sport, year)">
226                      <li>
227                        <span>
228                          <tal:t i18n:translate="">Workouts</tal:t>
229                        </span>
230                        <tal:w tal:replace="sport_totals['workouts']"></tal:w>
231                      </li>
232                      <li>
233                        <span>
234                          <tal:t i18n:translate="">Time</tal:t>
235                        </span>
236                        <tal:hms tal:define="hms timedelta_to_hms(sport_totals['time'])">
237                          <tal:h tal:content="str(hms[0]).zfill(2)"></tal:h>
238                          <tal:t i18n:translate="">hours</tal:t>,
239                          <tal:h tal:content="str(hms[1]).zfill(2)"></tal:h>
240                          <tal:t i18n:translate="">min.</tal:t>
241                        </tal:hms>
242                      </li>
243                      <li>
244                        <span>
245                          <tal:t i18n:translate="">Distance</tal:t>
246                        </span>
247                        <tal:w tal:replace="round(sport_totals['distance'])"></tal:w>
248                        <tal:t i18n:translate="">km</tal:t>
249                      </li>
250                      <li>
251                        <span>
252                          <tal:t i18n:translate="">Elevation</tal:t>
253                        </span>
254                        <tal:w tal:replace="round(sport_totals['elevation'])"></tal:w>
255                        <tal:t i18n:translate="">m</tal:t>
256                      </li>
257                      <li><span i18n:translate="">Single workout records:</span></li>
258                      <li>
259                        <span>
260                          <tal:t i18n:translate="">Farthest distance</tal:t>
261                        </span>
262                        <tal:has-wid tal:condition="sport_totals['max_distance_wid'] is not None">
263                          <a href="" tal:attributes="href request.resource_url(user[sport_totals['max_distance_wid']])">
264                            <tal:w tal:replace="round(sport_totals['max_distance'])"></tal:w>
265                            <tal:t i18n:translate="">km</tal:t>
266                          </a>
267                        </tal:has-wid>
268                        <tal:has-not-wid tal:condition="sport_totals['max_distance_wid'] is None">
269                          <tal:w tal:replace="round(sport_totals['max_distance'])"></tal:w>
270                          <tal:t i18n:translate="">km</tal:t>
271                        </tal:has-not-wid>
272                      </li>
273                      <li>
274                        <span>
275                          <tal:t i18n:translate="">Longer workout</tal:t>
276                        </span>
277                        <tal:has-wid tal:condition="sport_totals['max_time_wid'] is not None">
278                          <a href="" tal:attributes="href request.resource_url(user[sport_totals['max_time_wid']])">
279                            <tal:hms tal:define="hms timedelta_to_hms(sport_totals['max_time'])">
280                              <tal:h tal:content="str(hms[0]).zfill(2)"></tal:h>
281                              <tal:t i18n:translate="">hours</tal:t>,
282                              <tal:h tal:content="str(hms[1]).zfill(2)"></tal:h>
283                              <tal:t i18n:translate="">min.</tal:t>
284                            </tal:hms>
285                          </a>
286                        </tal:has-wid>
287                        <tal:has-not-wid tal:condition="sport_totals['max_time_wid'] is None">
288                          <tal:hms tal:define="hms timedelta_to_hms(sport_totals['max_time'])">
289                            <tal:h tal:content="str(hms[0]).zfill(2)"></tal:h>
290                            <tal:t i18n:translate="">hours</tal:t>,
291                            <tal:h tal:content="str(hms[1]).zfill(2)"></tal:h>
292                            <tal:t i18n:translate="">min.</tal:t>
293                          </tal:hms>
294                        </tal:has-not-wid>
295                      </li>
296                      <li>
297                        <span>
298                          <tal:t i18n:translate="">Higher elevation gain</tal:t>
299                        </span>
300                        <tal:has-wid tal:condition="sport_totals['max_elevation_wid'] is not None">
301                          <a href="" tal:attributes="href request.resource_url(user[sport_totals['max_elevation_wid']])">
302                            <tal:w tal:replace="round(sport_totals['max_elevation'])"></tal:w>
303                            <tal:t i18n:translate="">m</tal:t>
304                          </a>
305                        </tal:has-wid>
306                        <tal:has-not-wid tal:condition="sport_totals['max_elevation_wid'] is None">
307                          <tal:w tal:replace="round(sport_totals['max_elevation'])"></tal:w>
308                          <tal:t i18n:translate="">m</tal:t>
309                        </tal:has-not-wid>
310                      </li>
311                    </ul>
312                  </div>
313                </tal:years>
314
315                <strong i18n:translate="">All time</strong>
316                <ul class="profile-data"
317                    tal:define="sport_totals user.sport_totals(sport)">
318                  <li>
319                    <span>
320                      <tal:t i18n:translate="">Workouts</tal:t>
321                    </span>
322                    <tal:w tal:replace="sport_totals['workouts']"></tal:w>
323                  </li>
324                  <li>
325                    <span>
326                      <tal:t i18n:translate="">Time</tal:t>
327                    </span>
328                    <tal:hms tal:define="hms timedelta_to_hms(sport_totals['time'])">
329                      <tal:h tal:content="str(hms[0]).zfill(2)"></tal:h>
330                      <tal:t i18n:translate="">hours</tal:t>,
331                      <tal:h tal:content="str(hms[1]).zfill(2)"></tal:h>
332                      <tal:t i18n:translate="">min.</tal:t>
333                    </tal:hms>
334                  </li>
335                  <li>
336                    <span>
337                      <tal:t i18n:translate="">Distance</tal:t>
338                    </span>
339                    <tal:w tal:replace="round(sport_totals['distance'])"></tal:w>
340                    <tal:t i18n:translate="">km</tal:t>
341                  </li>
342                  <li>
343                    <span>
344                      <tal:t i18n:translate="">Elevation</tal:t>
345                    </span>
346                    <tal:w tal:replace="round(sport_totals['elevation'])"></tal:w>
347                    <tal:t i18n:translate="">m</tal:t>
348                  </li>
349                  <li><span i18n:translate="">Single workout records:</span></li>
350                  <li>
351                    <span>
352                      <tal:t i18n:translate="">Farthest distance</tal:t>
353                    </span>
354                    <tal:has-wid tal:condition="sport_totals['max_distance_wid'] is not None">
355                      <a href="" tal:attributes="href request.resource_url(user[sport_totals['max_distance_wid']])">
356                        <tal:w tal:replace="round(sport_totals['max_distance'])"></tal:w>
357                        <tal:t i18n:translate="">km</tal:t>
358                      </a>
359                    </tal:has-wid>
360                    <tal:has-not-wid tal:condition="sport_totals['max_distance_wid'] is None">
361                      <tal:w tal:replace="round(sport_totals['max_distance'])"></tal:w>
362                      <tal:t i18n:translate="">km</tal:t>
363                    </tal:has-not-wid>
364                  </li>
365                  <li>
366                    <span>
367                      <tal:t i18n:translate="">Longer workout</tal:t>
368                    </span>
369                    <tal:has-wid tal:condition="sport_totals['max_time_wid'] is not None">
370                      <a href="" tal:attributes="href request.resource_url(user[sport_totals['max_time_wid']])">
371                        <tal:hms tal:define="hms timedelta_to_hms(sport_totals['max_time'])">
372                          <tal:h tal:content="str(hms[0]).zfill(2)"></tal:h>
373                          <tal:t i18n:translate="">hours</tal:t>,
374                          <tal:h tal:content="str(hms[1]).zfill(2)"></tal:h>
375                          <tal:t i18n:translate="">min.</tal:t>
376                        </tal:hms>
377                      </a>
378                    </tal:has-wid>
379                    <tal:has-not-wid tal:condition="sport_totals['max_time_wid'] is None">
380                      <tal:hms tal:define="hms timedelta_to_hms(sport_totals['max_time'])">
381                        <tal:h tal:content="str(hms[0]).zfill(2)"></tal:h>
382                        <tal:t i18n:translate="">hours</tal:t>,
383                        <tal:h tal:content="str(hms[1]).zfill(2)"></tal:h>
384                        <tal:t i18n:translate="">min.</tal:t>
385                      </tal:hms>
386                    </tal:has-not-wid>
387                  </li>
388                  <li>
389                    <span>
390                      <tal:t i18n:translate="">Higher elevation gain</tal:t>
391                    </span>
392                    <tal:has-wid tal:condition="sport_totals['max_elevation_wid'] is not None">
393                      <a href="" tal:attributes="href request.resource_url(user[sport_totals['max_elevation_wid']])">
394                        <tal:w tal:replace="round(sport_totals['max_elevation'])"></tal:w>
395                        <tal:t i18n:translate="">m</tal:t>
396                      </a>
397                    </tal:has-wid>
398                    <tal:has-not-wid tal:condition="sport_totals['max_elevation_wid'] is None">
399                      <tal:w tal:replace="round(sport_totals['max_elevation'])"></tal:w>
400                      <tal:t i18n:translate="">m</tal:t>
401                    </tal:has-not-wid>
402                  </li>
403                </ul>
404              </div>
405            </tal:sports>
406
407          </tal:has-workouts>
408
409        </div>
410      </div>
411    </div>
412
413      <div id="jq-dropdown-sports" class="jq-dropdown jq-dropdown-tip">
414        <ul class="jq-dropdown-menu">
415          <tal:sports tal:repeat="sport profile_stats['sports']">
416            <li>
417              <a href="#" class="" tal:content="sport"
418                 tal:attributes="class 'js-choose-sport-stats js-' + sport">
419              </a>
420            </li>
421          </tal:sports>
422        </ul>
423      </div>
424
425
426    <tal:sports tal:repeat="sport profile_stats['sports']">
427      <div id="" class="jq-dropdown jq-dropdown-tip"
428           tal:attributes="id 'jq-dropdown-' + sport">
429        <ul class="jq-dropdown-menu">
430          <tal:years tal:repeat="year profile_stats['years']">
431            <li>
432              <a href="#" class="" tal:content="year"
433                 tal:attributes="class 'js-choose-year-stats js-' + sport + '-' + str(year)">
434              </a>
435            </li>
436          </tal:years>
437        </ul>
438      </div>
439    </tal:sports>
440
441
442  </metal:content>
443
444  <metal:body-js metal:fill-slot="body-js">
445
446    <script src="${request.static_url('ow:static/components/jquery-dropdown/jquery.dropdown.js')}"></script>
447    <script src="${request.static_url('ow:static/components/d3/d3.min.js')}"></script>
448    <script src="${request.static_url('ow:static/js/ow.js')}"></script>
449
450    <script type="text/javascript">
451     var map_shots = owjs.map_shots({
452         img_selector: 'img.js-needs-map',
453     })
454     map_shots.run();
455
456     var sport_stats = owjs.sport_stats({
457         link_selector: 'a.js-choose-sport-stats',
458         stats_selector: 'div.js-sport-stats',
459         selected: 'div.js-sport-stats.js-${profile_stats['current_sport']}',
460         dropdown_selector: 'a.js-jq-dropdown-sel-sports',
461         current_year: '${profile_stats['current_year']}',
462         year_link_selector: 'a.js-choose-year-stats.js-'
463     })
464     sport_stats.setup();
465
466     var heatmap_chart = owjs.calendar_heatmap_chart({
467         chart_selector: 'div.js-calendar-heatmap',
468         url: "${request.resource_url(user, 'month')}",
469         // Trick to have all those shortened day names translated
470         day_names_list: "${_('Mo Tu We Th Fr Sa Su')}".split(' ')
471     });
472     heatmap_chart.render();
473
474     var year_stats = owjs.year_stats({
475         link_selector: 'a.js-choose-year-stats',
476         stats_selector: 'div.js-year-stats',
477         selected: 'div.js-year-stats.js-${profile_stats['current_sport']}-${profile_stats['current_year']}',
478         dropdown_selector: 'a.js-jq-dropdown-sel-'
479     })
480     year_stats.setup();
481
482     var y_axis_labels = {
483         "distance": "Kilometers",
484         "time": "Hours",
485         "elevation": "Meters"
486     };
487
488     var year_chart = owjs.year_chart({
489         chart_selector: '.js-month-stats svg',
490         filters_selector: '.js-month-stats .js-filters a',
491         switcher_selector: '.js-month-stats .js-switcher a',
492         is_active_class: 'is-active',
493         urls: {"monthly": "${request.resource_url(user, 'monthly')}",
494                "weekly": "${request.resource_url(user, 'weekly')}"},
495         current_month: "${current_month}",
496         current_week: "${current_week}",
497         y_axis_labels: y_axis_labels,
498         filter_by: "distance",
499         url: "${'monthly' if current_week is None else 'weekly'}",
500     });
501     year_chart.render("distance", "${'monthly' if current_week is None else 'weekly'}");
502     year_chart.filters_setup();
503     year_chart.switcher_setup();
504    </script>
505
506  </metal:body-js>
507
508</html>
Note: See TracBrowser for help on using the repository browser.