geo-activity-playground 0.38.1__py3-none-any.whl → 0.39.0__py3-none-any.whl

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.
Files changed (113) hide show
  1. geo_activity_playground/__main__.py +5 -47
  2. geo_activity_playground/alembic/README +1 -0
  3. geo_activity_playground/alembic/env.py +76 -0
  4. geo_activity_playground/alembic/script.py.mako +26 -0
  5. geo_activity_playground/alembic/versions/451e7836b53d_add_square_planner_bookmark.py +33 -0
  6. geo_activity_playground/alembic/versions/63d3b7f6f93c_initial_version.py +73 -0
  7. geo_activity_playground/alembic/versions/ab83b9d23127_add_upstream_id.py +28 -0
  8. geo_activity_playground/alembic/versions/b03491c593f6_add_crop_indices.py +30 -0
  9. geo_activity_playground/alembic/versions/e02e27876deb_add_square_planner_bookmark_name.py +28 -0
  10. geo_activity_playground/alembic/versions/script.py.mako +28 -0
  11. geo_activity_playground/core/activities.py +50 -136
  12. geo_activity_playground/core/config.py +3 -3
  13. geo_activity_playground/core/datamodel.py +257 -0
  14. geo_activity_playground/core/enrichment.py +90 -92
  15. geo_activity_playground/core/heart_rate.py +1 -2
  16. geo_activity_playground/core/paths.py +6 -7
  17. geo_activity_playground/core/raster_map.py +43 -4
  18. geo_activity_playground/core/similarity.py +1 -2
  19. geo_activity_playground/core/tasks.py +2 -2
  20. geo_activity_playground/core/test_meta_search.py +3 -3
  21. geo_activity_playground/core/test_summary_stats.py +1 -1
  22. geo_activity_playground/explorer/grid_file.py +2 -2
  23. geo_activity_playground/explorer/tile_visits.py +8 -10
  24. geo_activity_playground/heatmap_video.py +7 -8
  25. geo_activity_playground/importers/activity_parsers.py +2 -2
  26. geo_activity_playground/importers/directory.py +9 -10
  27. geo_activity_playground/importers/strava_api.py +9 -9
  28. geo_activity_playground/importers/strava_checkout.py +12 -13
  29. geo_activity_playground/importers/test_csv_parser.py +3 -3
  30. geo_activity_playground/importers/test_directory.py +1 -1
  31. geo_activity_playground/importers/test_strava_api.py +1 -1
  32. geo_activity_playground/webui/app.py +94 -86
  33. geo_activity_playground/webui/authenticator.py +1 -1
  34. geo_activity_playground/webui/{activity/controller.py → blueprints/activity_blueprint.py} +246 -108
  35. geo_activity_playground/webui/{auth_blueprint.py → blueprints/auth_blueprint.py} +1 -1
  36. geo_activity_playground/webui/blueprints/bubble_chart_blueprint.py +61 -0
  37. geo_activity_playground/webui/{calendar/controller.py → blueprints/calendar_blueprint.py} +19 -19
  38. geo_activity_playground/webui/{eddington_blueprint.py → blueprints/eddington_blueprint.py} +9 -5
  39. geo_activity_playground/webui/blueprints/entry_views.py +68 -0
  40. geo_activity_playground/webui/{equipment_blueprint.py → blueprints/equipment_blueprint.py} +58 -4
  41. geo_activity_playground/webui/{explorer/controller.py → blueprints/explorer_blueprint.py} +88 -54
  42. geo_activity_playground/webui/blueprints/heatmap_blueprint.py +233 -0
  43. geo_activity_playground/webui/{search_blueprint.py → blueprints/search_blueprint.py} +7 -11
  44. geo_activity_playground/webui/blueprints/settings_blueprint.py +446 -0
  45. geo_activity_playground/webui/{square_planner_blueprint.py → blueprints/square_planner_blueprint.py} +31 -6
  46. geo_activity_playground/webui/{summary_blueprint.py → blueprints/summary_blueprint.py} +21 -26
  47. geo_activity_playground/webui/blueprints/tile_blueprint.py +27 -0
  48. geo_activity_playground/webui/{upload_blueprint.py → blueprints/upload_blueprint.py} +13 -18
  49. geo_activity_playground/webui/flasher.py +26 -0
  50. geo_activity_playground/webui/plot_util.py +1 -1
  51. geo_activity_playground/webui/search_util.py +4 -6
  52. geo_activity_playground/webui/static/images/layers-2x.png +0 -0
  53. geo_activity_playground/webui/static/images/layers.png +0 -0
  54. geo_activity_playground/webui/static/images/marker-icon-2x.png +0 -0
  55. geo_activity_playground/webui/static/images/marker-icon.png +0 -0
  56. geo_activity_playground/webui/static/images/marker-shadow.png +0 -0
  57. geo_activity_playground/webui/templates/activity/day.html.j2 +81 -0
  58. geo_activity_playground/webui/templates/activity/edit.html.j2 +38 -0
  59. geo_activity_playground/webui/{activity/templates → templates}/activity/name.html.j2 +29 -27
  60. geo_activity_playground/webui/{activity/templates → templates}/activity/show.html.j2 +60 -36
  61. geo_activity_playground/webui/templates/activity/trim.html.j2 +68 -0
  62. geo_activity_playground/webui/templates/bubble_chart/index.html.j2 +26 -0
  63. geo_activity_playground/webui/templates/calendar/index.html.j2 +48 -0
  64. geo_activity_playground/webui/templates/calendar/month.html.j2 +57 -0
  65. geo_activity_playground/webui/templates/equipment/index.html.j2 +7 -0
  66. geo_activity_playground/webui/templates/home.html.j2 +6 -6
  67. geo_activity_playground/webui/templates/page.html.j2 +2 -1
  68. geo_activity_playground/webui/{settings/templates → templates}/settings/index.html.j2 +9 -20
  69. geo_activity_playground/webui/templates/settings/manage-equipments.html.j2 +49 -0
  70. geo_activity_playground/webui/templates/settings/manage-kinds.html.j2 +48 -0
  71. geo_activity_playground/webui/{settings/templates → templates}/settings/metadata-extraction.html.j2 +1 -1
  72. geo_activity_playground/webui/{settings/templates → templates}/settings/privacy-zones.html.j2 +2 -0
  73. geo_activity_playground/webui/{settings/templates → templates}/settings/strava.html.j2 +2 -0
  74. geo_activity_playground/webui/templates/square_planner/index.html.j2 +63 -13
  75. {geo_activity_playground-0.38.1.dist-info → geo_activity_playground-0.39.0.dist-info}/METADATA +5 -1
  76. geo_activity_playground-0.39.0.dist-info/RECORD +133 -0
  77. geo_activity_playground/__init__.py +0 -0
  78. geo_activity_playground/core/__init__.py +0 -0
  79. geo_activity_playground/explorer/__init__.py +0 -0
  80. geo_activity_playground/importers/__init__.py +0 -0
  81. geo_activity_playground/webui/__init__.py +0 -0
  82. geo_activity_playground/webui/activity/__init__.py +0 -0
  83. geo_activity_playground/webui/activity/blueprint.py +0 -109
  84. geo_activity_playground/webui/activity/templates/activity/day.html.j2 +0 -80
  85. geo_activity_playground/webui/activity/templates/activity/edit.html.j2 +0 -42
  86. geo_activity_playground/webui/calendar/__init__.py +0 -0
  87. geo_activity_playground/webui/calendar/blueprint.py +0 -23
  88. geo_activity_playground/webui/calendar/templates/calendar/index.html.j2 +0 -46
  89. geo_activity_playground/webui/calendar/templates/calendar/month.html.j2 +0 -55
  90. geo_activity_playground/webui/entry_controller.py +0 -63
  91. geo_activity_playground/webui/explorer/__init__.py +0 -0
  92. geo_activity_playground/webui/explorer/blueprint.py +0 -62
  93. geo_activity_playground/webui/heatmap/__init__.py +0 -0
  94. geo_activity_playground/webui/heatmap/blueprint.py +0 -51
  95. geo_activity_playground/webui/heatmap/heatmap_controller.py +0 -216
  96. geo_activity_playground/webui/settings/blueprint.py +0 -262
  97. geo_activity_playground/webui/settings/controller.py +0 -272
  98. geo_activity_playground/webui/settings/templates/settings/equipment-offsets.html.j2 +0 -44
  99. geo_activity_playground/webui/settings/templates/settings/kind-renames.html.j2 +0 -25
  100. geo_activity_playground/webui/settings/templates/settings/kinds-without-achievements.html.j2 +0 -30
  101. geo_activity_playground/webui/tile_blueprint.py +0 -42
  102. geo_activity_playground-0.38.1.dist-info/RECORD +0 -129
  103. /geo_activity_playground/webui/{activity/templates → templates}/activity/lines.html.j2 +0 -0
  104. /geo_activity_playground/webui/{explorer/templates → templates}/explorer/index.html.j2 +0 -0
  105. /geo_activity_playground/webui/{heatmap/templates → templates}/heatmap/index.html.j2 +0 -0
  106. /geo_activity_playground/webui/{settings/templates → templates}/settings/admin-password.html.j2 +0 -0
  107. /geo_activity_playground/webui/{settings/templates → templates}/settings/color-schemes.html.j2 +0 -0
  108. /geo_activity_playground/webui/{settings/templates → templates}/settings/heart-rate.html.j2 +0 -0
  109. /geo_activity_playground/webui/{settings/templates → templates}/settings/segmentation.html.j2 +0 -0
  110. /geo_activity_playground/webui/{settings/templates → templates}/settings/sharepic.html.j2 +0 -0
  111. {geo_activity_playground-0.38.1.dist-info → geo_activity_playground-0.39.0.dist-info}/LICENSE +0 -0
  112. {geo_activity_playground-0.38.1.dist-info → geo_activity_playground-0.39.0.dist-info}/WHEEL +0 -0
  113. {geo_activity_playground-0.38.1.dist-info → geo_activity_playground-0.39.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,26 @@
1
+ import abc
2
+ from enum import Enum
3
+
4
+ import flask
5
+
6
+
7
+ class FlashTypes(Enum):
8
+ PRIMARY = "primary"
9
+ SECONDARY = "secondary"
10
+ SUCCESS = "success"
11
+ DANGER = "danger"
12
+ WARNING = "warning"
13
+ INFO = "info"
14
+ LIGHT = "light"
15
+ DARK = "dark"
16
+
17
+
18
+ class Flasher(abc.ABC):
19
+ @abc.abstractmethod
20
+ def flash_message(self, message: str, type: FlashTypes):
21
+ pass
22
+
23
+
24
+ class FlaskFlasher(Flasher):
25
+ def flash_message(self, message, type):
26
+ flask.flash(message, category=type.value)
@@ -1,7 +1,7 @@
1
1
  import altair as alt
2
2
  import pandas as pd
3
3
 
4
- from geo_activity_playground.core.config import Config
4
+ from ..core.config import Config
5
5
 
6
6
 
7
7
  def make_kind_scale(meta: pd.DataFrame, config: Config) -> alt.Scale:
@@ -1,11 +1,9 @@
1
- from typing import Optional
2
-
3
1
  from werkzeug.datastructures import MultiDict
4
2
 
5
- from geo_activity_playground.core.config import ConfigAccessor
6
- from geo_activity_playground.core.meta_search import _parse_date_or_none
7
- from geo_activity_playground.core.meta_search import SearchQuery
8
- from geo_activity_playground.webui.authenticator import Authenticator
3
+ from ..core.config import ConfigAccessor
4
+ from ..core.meta_search import _parse_date_or_none
5
+ from ..core.meta_search import SearchQuery
6
+ from .authenticator import Authenticator
9
7
 
10
8
 
11
9
  def search_query_from_form(args: MultiDict) -> SearchQuery:
@@ -0,0 +1,81 @@
1
+ {% extends "page.html.j2" %}
2
+
3
+ {% block container %}
4
+ <div class="row mb-3">
5
+ <div class="col">
6
+ <h1>{{ date }}</h1>
7
+ </div>
8
+ </div>
9
+
10
+
11
+ <div class="row mb-3">
12
+ <div class="col-12">
13
+ <div id="activity-map" style="height: 500px;"></div>
14
+ <script>
15
+ var map = L.map('activity-map', {
16
+ fullscreenControl: true
17
+ });
18
+ L.tileLayer('/tile/grayscale/{z}/{x}/{y}.png', {
19
+ maxZoom: 19,
20
+ attribution: '{{ map_tile_attribution|safe }}'
21
+ }).addTo(map);
22
+
23
+ let geojson = L.geoJSON({{ geojson| safe }}, {
24
+ style: function (feature) { return { color: feature.properties.color } }
25
+ }).addTo(map)
26
+ map.fitBounds(geojson.getBounds());
27
+ </script>
28
+ </div>
29
+ </div>
30
+
31
+ <div class="row mb-3">
32
+ <div class="col">
33
+ <h2>Activities</h2>
34
+ <div class="table-responsive">
35
+ <table class="table table-sort table-arrows">
36
+ <thead>
37
+ <tr>
38
+ <th>Name</th>
39
+ <th>Date</th>
40
+ <th>Distance / km</th>
41
+ <th>Elapsed time</th>
42
+ <th>Speed / km/h</th>
43
+ <th>Equipment</th>
44
+ <th>Kind</th>
45
+ </tr>
46
+ </thead>
47
+ <tbody>
48
+ {% for activity in activities %}
49
+ <tr>
50
+ <td><span style="color: {{ activity['color'] }};">█</span> <a
51
+ href="{{ url_for('activity.show', id=activity.id) }}">{{
52
+ activity.name }}</a></td>
53
+ <td>{{ activity.start|dt }}</td>
54
+ <td>{{ activity.distance_km | round(1) }}</td>
55
+ <td>{{ activity.elapsed_time|td }}</td>
56
+ <td>{{ activity.average_speed_moving_kmh|round(1) }}</td>
57
+ <td>{{ activity["equipment"] }}</td>
58
+ <td>{{ activity["kind"] }}</td>
59
+ </tr>
60
+ {% endfor %}
61
+ {% if activities|length > 1 %}
62
+ <tr>
63
+ <td><b>Total</b></td>
64
+ <td></td>
65
+ <td><b>{{ total_distance | round(1) }}</b></td>
66
+ <td><b>{{ total_elapsed_time|td }}</b></td>
67
+ <td></td>
68
+ <td></td>
69
+ </tr>
70
+ {% endif %}
71
+ </tbody>
72
+ </table>
73
+ </div>
74
+ </div>
75
+ </div>
76
+
77
+ <h2>Share picture</h2>
78
+
79
+ <p><img class="img-fluid" src="{{ url_for('.day_sharepic', year=year, month=month, day=day) }}" /></p>
80
+
81
+ {% endblock %}
@@ -0,0 +1,38 @@
1
+ {% extends "page.html.j2" %}
2
+
3
+ {% block container %}
4
+
5
+ <h1 class="mb-3">Edit Activity</h1>
6
+
7
+ <form method="POST">
8
+ <div class="mb-3">
9
+ <label for="name" class="form-label">Name</label>
10
+ <input type="text" class="form-control" id="name" name="name" value="{{ activity.name }}" />
11
+ </div>
12
+
13
+ <div class="mb-3">
14
+ <label for="kind" class="form-label">Kind</label>
15
+ <select class="form-control" id="kind" name="kind">
16
+ <option value="null" {% if activity.kind==None %} selected {% endif %}>None</option>
17
+ {% for kind in kinds %}
18
+ <option value="{{ kind.id }}" {% if activity.kind==kind %} selected {% endif %}>{{ kind.name }}</option>
19
+ {% endfor %}
20
+ </select>
21
+ </div>
22
+
23
+ <div class="mb-3">
24
+ <label for="equipment" class="form-label">Equipment</label>
25
+ <select class="form-control" id="equipment" name="equipment">
26
+ <option value="null" {% if activity.equipment==None %} selected {% endif %}>None</option>
27
+ {% for equipment in equipments %}
28
+ <option value="{{ equipment.id }}" {% if activity.equipment==equipment %} selected {% endif %}>
29
+ {{ equipment.name }}
30
+ </option>
31
+ {% endfor %}
32
+ </select>
33
+ </div>
34
+
35
+ <button type="submit" class="btn btn-primary">Save</button>
36
+ </form>
37
+
38
+ {% endblock %}
@@ -50,33 +50,35 @@
50
50
  <div class="col">
51
51
  <h2>Activities</h2>
52
52
 
53
- <table class="table table-sort table-arrows">
54
- <thead>
55
- <tr>
56
- <th>Name</th>
57
- <th>Date</th>
58
- <th class="numeric-sort">Distance / km</th>
59
- <th>Elapsed time</th>
60
- <th>Speed / km/h</th>
61
- <th>Equipment</th>
62
- <th>Kind</th>
63
- </tr>
64
- </thead>
65
- <tbody>
66
- {% for activity in activities %}
67
- <tr>
68
- <td><span style="color: {{ activity['color'] }};">█</span> <a
69
- href="{{ url_for('activity.show', id=activity.id) }}">{{
70
- activity.name }}</a></td>
71
- <td>{{ activity.start|dt }}</td>
72
- <td>{{ activity.distance_km | round(1) }}</td>
73
- <td>{{ activity.elapsed_time|td }}</td>
74
- <td>{{ activity.average_speed_moving_kmh|round(1) }}</td>
75
- <td>{{ activity["equipment"] }}</td>
76
- <td>{{ activity["kind"] }}</td>
77
- </tr>
78
- {% endfor %}
79
- </tbody>
53
+ <div class="table-responsive">
54
+ <table class="table table-sort table-arrows">
55
+ <thead>
56
+ <tr>
57
+ <th>Name</th>
58
+ <th>Date</th>
59
+ <th class="numeric-sort">Distance / km</th>
60
+ <th>Elapsed time</th>
61
+ <th>Speed / km/h</th>
62
+ <th>Equipment</th>
63
+ <th>Kind</th>
64
+ </tr>
65
+ </thead>
66
+ <tbody>
67
+ {% for activity in activities %}
68
+ <tr>
69
+ <td><span style="color: {{ activity['color'] }};">█</span> <a
70
+ href="{{ url_for('activity.show', id=activity.id) }}">{{
71
+ activity.name }}</a></td>
72
+ <td>{{ activity.start|dt }}</td>
73
+ <td>{{ activity.distance_km | round(1) }}</td>
74
+ <td>{{ activity.elapsed_time|td }}</td>
75
+ <td>{{ activity.average_speed_moving_kmh|round(1) }}</td>
76
+ <td>{{ activity["equipment"] }}</td>
77
+ <td>{{ activity["kind"] }}</td>
78
+ </tr>
79
+ {% endfor %}
80
+ </tbody>
81
+ </table>
80
82
  </table>
81
83
  </div>
82
84
  </div>
@@ -8,20 +8,23 @@
8
8
  </div>
9
9
 
10
10
  <div class="row mb-3">
11
- <div class="col-4">
11
+ <div class="col-sm-12 col-md-4">
12
12
  <dl>
13
13
  <dt>Name</dt>
14
14
  <dd>{{ activity["name"] }}</dd>
15
+
16
+ {% if activity.kind %}
15
17
  <dt>Kind</dt>
16
- <dd>{{ activity.kind }}</dd>
17
- <dt>Commute</dt>
18
- <dd>{{ activity.commute }}</dd>
18
+ <dd>{{ activity.kind.name }}</dd>
19
+ {% endif %}
20
+
19
21
  <dt>Distance</dt>
20
22
  <dd>{{ activity.distance_km|round(1) }} km</dd>
21
- <dt>Elapsed time</dt>
22
- <dd>{{ activity.elapsed_time|td }}</dd>
23
- <dt>Moving time</dt>
24
- <dd>{{ activity.moving_time|td }}</dd>
23
+
24
+ {% if activity.elapsed_time %}
25
+ <dt>Duration</dt>
26
+ <dd>{{ activity.elapsed_time|td }} elapsed, {{ activity.moving_time|td }} moving</dd>
27
+
25
28
  <dt>Average moving speed</dt>
26
29
  <dd>{{ activity.average_speed_moving_kmh|round(1) }} km/h = {{
27
30
  (60/activity.average_speed_moving_kmh)|round(1) }} min/km</dd>
@@ -32,29 +35,48 @@
32
35
  <dd><a href="{{ url_for('activity.day', year=date.year, month=date.month, day=date.day) }}">{{ date }}</a>
33
36
  {{ time }}
34
37
  </dd>
38
+ {% endif %}
39
+
40
+ {% if activity.calories %}
35
41
  <dt>Calories</dt>
36
42
  <dd>{{ activity.calories }}</dd>
43
+ {% endif %}
44
+
45
+ {% if activity.steps %}
37
46
  <dt>Steps</dt>
38
47
  <dd>{{ activity.steps }}</dd>
39
- {% if activity.elevation_gain is defined %}
48
+ {% endif %}
49
+
50
+ {% if activity.elevation_gain %}
40
51
  <dt>Elevation gain</dt>
41
52
  <dd>{{ activity.elevation_gain|round(0) }} m</dd>
42
53
  {% endif %}
54
+
55
+ {% if activity.equipment %}
43
56
  <dt>Equipment</dt>
44
- <dd>{{ activity['equipment'] }}</dd>
57
+ <dd>{{ activity.equipment.name }}</dd>
58
+ {% endif %}
59
+
60
+ {% if new_tiles[14] %}
45
61
  <dt>New Explorer Tiles</dt>
46
62
  <dd>{{ new_tiles[14] }}</dd>
63
+ {% endif %}
64
+
65
+ {% if new_tiles[17] %}
47
66
  <dt>New Squadratinhos</dt>
48
67
  <dd>{{ new_tiles[17] }}</dd>
68
+ {% endif %}
69
+
49
70
  <dt>ID</dt>
50
71
  <dd>{{ activity.id }}</dd>
51
72
  <dt>Source path</dt>
52
73
  <dd>{{ activity.path }}</dd>
53
74
  </dl>
54
75
 
55
- <a href="{{ url_for('.edit', id=activity['id']) }}" class="btn btn-secondary btn-small">Edit metadata</a>
76
+ <a href="{{ url_for('.edit', id=activity['id']) }}" class="btn btn-secondary btn-small">Edit</a>
77
+ <a href="{{ url_for('.trim', id=activity['id']) }}" class="btn btn-secondary btn-small">Trim</a>
56
78
  </div>
57
- <div class="col-8">
79
+ <div class="col-sm-12 col-md-8">
58
80
  <div id="activity-map" style="height: 500px;" class="mb-3"></div>
59
81
  <script>
60
82
  var map = L.map('activity-map', {
@@ -161,7 +183,7 @@
161
183
 
162
184
  <h2>Share picture</h2>
163
185
 
164
- <p><img src="{{ url_for('.sharepic', id=activity.id) }}" /></p>
186
+ <p><img class="img-fluid" src="{{ url_for('.sharepic', id=activity.id) }}" /></p>
165
187
 
166
188
  <p>Not happy with the displayed data? <a href="{{ url_for('settings.sharepic') }}">Change share picture
167
189
  settings</a>.</p>
@@ -207,29 +229,31 @@
207
229
 
208
230
  <p><a href="{{ url_for('.name', name=activity['name']) }}">Overview over these activities</a></p>
209
231
 
210
- <table class="table table-sort table-arrows">
211
- <thead>
212
- <tr>
213
- <th>Date</th>
214
- <th class="numeric-sort">Distance / km</th>
215
- <th>Elapsed time</th>
216
- <th>Equipment</th>
217
- <th>Kind</th>
218
- </tr>
219
- </thead>
220
- <tbody>
221
- {% for other_activity in similar_activites %}
222
- <tr>
223
- <td><a href="{{ url_for('.show', id=other_activity.id) }}">{{ other_activity.start|dt
224
- }}</a></td>
225
- <td>{{ other_activity.distance_km | round(1) }}</td>
226
- <td>{{ other_activity.elapsed_time|td }}</td>
227
- <td>{{ other_activity["equipment"] }}</td>
228
- <td>{{ other_activity["kind"] }}</td>
229
- </tr>
230
- {% endfor %}
231
- </tbody>
232
- </table>
232
+ <div class="table-responsive">
233
+ <table class="table table-sort table-arrows">
234
+ <thead>
235
+ <tr>
236
+ <th>Date</th>
237
+ <th class="numeric-sort">Distance / km</th>
238
+ <th>Elapsed time</th>
239
+ <th>Equipment</th>
240
+ <th>Kind</th>
241
+ </tr>
242
+ </thead>
243
+ <tbody>
244
+ {% for other_activity in similar_activites %}
245
+ <tr>
246
+ <td><a href="{{ url_for('.show', id=other_activity.id) }}">{{ other_activity.start|dt
247
+ }}</a></td>
248
+ <td>{{ other_activity.distance_km | round(1) }}</td>
249
+ <td>{{ other_activity.elapsed_time|td }}</td>
250
+ <td>{{ other_activity["equipment"] }}</td>
251
+ <td>{{ other_activity["kind"] }}</td>
252
+ </tr>
253
+ {% endfor %}
254
+ </tbody>
255
+ </table>
256
+ </div>
233
257
  </div>
234
258
  </div>
235
259
  {% endif %}
@@ -0,0 +1,68 @@
1
+ {% extends "page.html.j2" %}
2
+
3
+ {% block container %}
4
+ <div class="row mb-3">
5
+ <div class="col">
6
+ <h1>{{ activity["name"] }}</h1>
7
+ </div>
8
+ </div>
9
+
10
+ <a href="{{ url_for('.show', id=activity['id']) }}" class="btn btn-primary btn-small">Back</a>
11
+
12
+
13
+ <div id="activity-map" style="height: 600px;" class="mb-3"></div>
14
+ <script>
15
+ var map = L.map('activity-map', {
16
+ fullscreenControl: true
17
+ });
18
+ L.tileLayer('/tile/pastel/{z}/{x}/{y}.png', {
19
+ maxZoom: 19,
20
+ attribution: '{{ map_tile_attribution|safe }}'
21
+ }).addTo(map);
22
+
23
+
24
+ let layer = L.geoJSON({{ color_line_geojson| safe }}, {
25
+ pointToLayer: function (feature, lat_lon) {
26
+ let p = feature.properties
27
+
28
+ let marker = null
29
+ if (p.markerType == "circle") {
30
+ marker = L.circleMarker(lat_lon, p.markerStyle)
31
+ } else {
32
+ marker = L.marker(lat_lon)
33
+ }
34
+
35
+ let text = ''
36
+ if (p.name) {
37
+ text += `<p><b>${p.name}</b></p>`
38
+ }
39
+ if (p.description) {
40
+ text += p.description
41
+ }
42
+ if (text) {
43
+ marker = marker.bindPopup(text)
44
+ }
45
+
46
+ return marker
47
+ }
48
+ })
49
+
50
+ layer.addTo(map)
51
+ map.fitBounds(layer.getBounds());
52
+ </script>
53
+
54
+ <form method="POST">
55
+ <div class="mb-3">
56
+ <label for="begin" class="form-label">Begin</label>
57
+ <input type="number" class="form-control" id="begin" name="begin" value="{{ activity.index_begin }}" />
58
+ </div>
59
+
60
+ <div class="mb-3">
61
+ <label for="end" class="form-label">End</label>
62
+ <input type="number" class="form-control" id="end" name="end" value="{{ activity.index_end }}" />
63
+ </div>
64
+
65
+ <button type="submit" class="btn btn-primary">Save</button>
66
+ </form>
67
+
68
+ {% endblock %}
@@ -0,0 +1,26 @@
1
+ <!-- filepath: /home/michael/Nextcloud/Python/Geoactivity-Fork/geo-activity-playground/geo_activity_playground/webui/templates/bubble_chart/index.html.j2 -->
2
+ {% extends "page.html.j2" %}
3
+
4
+ {% block container %}
5
+ <div class="row mb-3">
6
+ <div class="col">
7
+ <h1>Bubble Chart</h1>
8
+ <div id="bubble-chart"></div>
9
+ <script src="https://cdn.jsdelivr.net/npm/vega@5"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/vega-lite@5"></script>
11
+ <script src="https://cdn.jsdelivr.net/npm/vega-embed@6"></script>
12
+ <script>
13
+ const chartSpec = {{ bubble_chart | safe }};
14
+ vegaEmbed('#bubble-chart', chartSpec).then((result) => {
15
+ // Add a click event listener to the chart
16
+ result.view.addEventListener('click', (event, item) => {
17
+ if (item && item.datum && item.datum.activity_url) {
18
+ // Redirect to the activity URL
19
+ window.location.href = item.datum.activity_url;
20
+ }
21
+ });
22
+ }).catch(console.error);
23
+ </script>
24
+ </div>
25
+ </div>
26
+ {% endblock %}
@@ -0,0 +1,48 @@
1
+ {% extends "page.html.j2" %}
2
+
3
+ {% block container %}
4
+ <div class="row mb-1">
5
+ <div class="col">
6
+ <h1>Calendar</h1>
7
+ </div>
8
+ </div>
9
+
10
+ <div class="row mb-1">
11
+ <div class="col">
12
+ <div class="table-responsive">
13
+ <table class="table">
14
+ <thead>
15
+ <tr>
16
+ <th>Year</th>
17
+ {% for i in range(1, 13) %}
18
+ <th style="text-align: right;">{{ i }}</th>
19
+ {% endfor %}
20
+ <th style="text-align: right;">Total</th>
21
+ </tr>
22
+ </thead>
23
+ <tbody>
24
+ {% for year, month_data in monthly_distances.items() %}
25
+ {% set year_int = year|round(0)|int %}
26
+ <tr>
27
+ <td>{{ year_int }}</td>
28
+ {% for month in range(1, 13) %}
29
+ <td align="right">
30
+ {% set distance = month_data[month] %}
31
+ {% if distance %}
32
+ <a href="{{ url_for(".month", year=year_int, month=month) }}">{{ distance|int() }} km</a>
33
+ {% else %}
34
+ 0 km
35
+ {% endif %}
36
+ </td>
37
+ {% endfor %}
38
+ <td align="right">{{ yearly_distances[year]|int() }} km</td>
39
+ </tr>
40
+ {% endfor %}
41
+ </tbody>
42
+ </table>
43
+ </div>
44
+ </div>
45
+ </div>
46
+
47
+
48
+ {% endblock %}
@@ -0,0 +1,57 @@
1
+ {% extends "page.html.j2" %}
2
+
3
+ {% block container %}
4
+ <div class="row mb-1">
5
+ <div class="col">
6
+ <h1>Calendar {{ year }}-{{ "{0:02d}".format(month) }}</h1>
7
+ </div>
8
+ </div>
9
+
10
+ <div class="row mb-1">
11
+ <div class="col">
12
+ <div class="table-responsive">
13
+ <table class="table">
14
+ <thead>
15
+ <tr>
16
+ <th>Week</th>
17
+ <th>Monday</th>
18
+ <th>Tuesday</th>
19
+ <th>Wednesday</th>
20
+ <th>Thursday</th>
21
+ <th>Friday</th>
22
+ <th>Saturday</th>
23
+ <th>Sunday</th>
24
+ </tr>
25
+ </thead>
26
+ <tbody>
27
+ {% for week, week_data in weeks.items() %}
28
+ <tr>
29
+ <td>{{ week }}</td>
30
+ {% for day in range(1, 8) %}
31
+ <td>
32
+ {% if weeks[week][day] %}
33
+ <a href="{{ url_for('activity.day', year=year, month=month, day=day_of_month[week][day]) }}"><b>{{
34
+ day_of_month[week][day] }}.</b></a>
35
+ {% elif day_of_month[week][day] %}
36
+ <b>{{ day_of_month[week][day] }}.</b>
37
+ {% endif %}
38
+
39
+ {% if weeks[week][day] %}
40
+ <ul>
41
+ {% for activity in weeks[week][day] %}
42
+ <li><a href="{{ url_for('activity.show', id=activity.id) }}">{{ activity.name }}</a></li>
43
+ {% endfor %}
44
+ </ul>
45
+ {% endif %}
46
+ </td>
47
+ {% endfor %}
48
+ </tr>
49
+ {% endfor %}
50
+ </tbody>
51
+ </table>
52
+ </div>
53
+ </div>
54
+ </div>
55
+
56
+
57
+ {% endblock %}
@@ -32,6 +32,13 @@
32
32
  </div>
33
33
  </div>
34
34
 
35
+ <div class="row mb-3">
36
+ <div class="col">
37
+ <h2>Monthly Equipment Usage</h2>
38
+ {{ vega_direct("stacked_area_chart", stacked_area_chart) }}
39
+ </div>
40
+ </div>
41
+
35
42
  <div class="row mb-3">
36
43
  <div class="col">
37
44
  <h2>Details for each equipment</h2>
@@ -13,11 +13,6 @@
13
13
  </div>
14
14
 
15
15
 
16
- <div class="row mb-3">
17
- <div class="col">
18
- <h2>Latest activities</h2>
19
- </div>
20
- </div>
21
16
 
22
17
  <script>
23
18
  function add_map(id, geojson) {
@@ -35,7 +30,11 @@
35
30
  }
36
31
  </script>
37
32
 
38
- {% for activity_batch in latest_activities|batch(3) %}
33
+ <h2>Latest activities</h2>
34
+
35
+ {% for date, date_data in latest_activities.items() %}
36
+ <h3>{{ date }}</h3>
37
+ {% for activity_batch in date_data|batch(3) %}
39
38
  <div class="row row-cols-1 row-cols-md-3 g-4 mb-3">
40
39
  {% for elem in activity_batch %}
41
40
  <div class="col">
@@ -57,6 +56,7 @@
57
56
  {% endfor %}
58
57
  </div>
59
58
  {% endfor %}
59
+ {% endfor %}
60
60
  {% else %}
61
61
  <p>You don't have activities yet. Either put some files into a directory <tt>Activities</tt>, <a
62
62
  href="{{ url_for('upload.index') }}">upload activities</a> or <a href="{{ url_for('settings.strava') }}">set up