postrunner 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/postrunner/ActivitiesDB.rb +86 -31
- data/lib/postrunner/Activity.rb +222 -30
- data/lib/postrunner/ActivityLink.rb +59 -0
- data/lib/postrunner/ActivityListView.rb +37 -90
- data/lib/postrunner/ActivitySummary.rb +12 -12
- data/lib/postrunner/ActivityView.rb +49 -72
- data/lib/postrunner/BackedUpFile.rb +56 -0
- data/lib/postrunner/ChartView.rb +14 -16
- data/lib/postrunner/DeviceList.rb +3 -7
- data/lib/postrunner/FlexiTable.rb +28 -1
- data/lib/postrunner/HTMLBuilder.rb +64 -16
- data/lib/postrunner/Main.rb +63 -19
- data/lib/postrunner/NavButtonRow.rb +103 -0
- data/lib/postrunner/PagingButtons.rb +77 -0
- data/lib/postrunner/PersonalRecords.rb +338 -79
- data/lib/postrunner/RecordListPageView.rb +69 -0
- data/lib/postrunner/RuntimeConfig.rb +5 -3
- data/lib/postrunner/TrackView.rb +14 -16
- data/lib/postrunner/UserProfileView.rb +3 -7
- data/lib/postrunner/View.rb +97 -0
- data/lib/postrunner/ViewBottom.rb +54 -0
- data/lib/postrunner/ViewButtons.rb +68 -0
- data/lib/postrunner/ViewFrame.rb +93 -0
- data/lib/postrunner/ViewTop.rb +80 -0
- data/lib/postrunner/version.rb +1 -1
- data/misc/icons/activities.png +0 -0
- data/misc/icons/activities.svg +1582 -0
- data/misc/icons/record-small.png +0 -0
- data/misc/icons/record.png +0 -0
- data/misc/icons/record.svg +15712 -0
- data/spec/ActivitySummary_spec.rb +3 -1
- data/spec/PostRunner_spec.rb +45 -0
- data/spec/View_spec.rb +61 -0
- data/spec/spec_helper.rb +21 -7
- metadata +19 -3
- data/lib/postrunner/ViewWidgets.rb +0 -153
@@ -0,0 +1,59 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
# encoding: UTF-8
|
3
|
+
#
|
4
|
+
# = ActivityLink.rb -- PostRunner - Manage the data from your Garmin sport devices.
|
5
|
+
#
|
6
|
+
# Copyright (c) 2014, 2015 by Chris Schlaeger <cs@taskjuggler.org>
|
7
|
+
#
|
8
|
+
# This program is free software; you can redistribute it and/or modify
|
9
|
+
# it under the terms of version 2 of the GNU General Public License as
|
10
|
+
# published by the Free Software Foundation.
|
11
|
+
#
|
12
|
+
|
13
|
+
require 'postrunner/HTMLBuilder'
|
14
|
+
|
15
|
+
module PostRunner
|
16
|
+
|
17
|
+
# Generates the name of an Activity with a link to the ActivityReport.
|
18
|
+
# Optionally, an icon can be shown for Activities that contain a current
|
19
|
+
# personal record.
|
20
|
+
class ActivityLink
|
21
|
+
|
22
|
+
def initialize(activity, show_record_icon = false)
|
23
|
+
@activity = activity
|
24
|
+
@show_record_icon = show_record_icon
|
25
|
+
end
|
26
|
+
|
27
|
+
# Add the ActivityLink as HTML Elements to the document.
|
28
|
+
# @param doc [HTMLBuilder] XML Document
|
29
|
+
def to_html(doc)
|
30
|
+
doc.unique(:activitylink_style) { doc.style(style) }
|
31
|
+
|
32
|
+
doc.a(@activity.name, { :class => 'activity_link',
|
33
|
+
:href => @activity.fit_file[0..-5] + '.html' })
|
34
|
+
if @show_record_icon && @activity.has_records?
|
35
|
+
doc.img(nil, { :src => 'icons/record-small.png',
|
36
|
+
:style => 'vertical-align:middle' })
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Convert the ActivityLink into a plain text form. Return the first 20
|
41
|
+
# characters of the Activity name.
|
42
|
+
def to_s
|
43
|
+
@activity.name[0..19]
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def style
|
49
|
+
<<EOT
|
50
|
+
.activity_link {
|
51
|
+
padding: 0px 3px 0px 3px;
|
52
|
+
}
|
53
|
+
EOT
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
@@ -14,31 +14,15 @@ require 'fit4ruby'
|
|
14
14
|
|
15
15
|
require 'postrunner/FlexiTable'
|
16
16
|
require 'postrunner/HTMLBuilder'
|
17
|
-
require 'postrunner/
|
17
|
+
require 'postrunner/ActivityLink'
|
18
18
|
|
19
19
|
module PostRunner
|
20
20
|
|
21
|
+
# Generates a paged list of all Activity objects in the database. HTML and
|
22
|
+
# plain text output are supported.
|
21
23
|
class ActivityListView
|
22
24
|
|
23
|
-
class ActivityLink
|
24
|
-
|
25
|
-
def initialize(activity)
|
26
|
-
@activity = activity
|
27
|
-
end
|
28
|
-
|
29
|
-
def to_html(doc)
|
30
|
-
doc.a(@activity.name, { :class => 'activity_link',
|
31
|
-
:href => @activity.fit_file[0..-5] + '.html' })
|
32
|
-
end
|
33
|
-
|
34
|
-
def to_s
|
35
|
-
@activity.name[0..19]
|
36
|
-
end
|
37
|
-
|
38
|
-
end
|
39
|
-
|
40
25
|
include Fit4Ruby::Converters
|
41
|
-
include ViewWidgets
|
42
26
|
|
43
27
|
def initialize(db)
|
44
28
|
@db = db
|
@@ -48,85 +32,42 @@ module PostRunner
|
|
48
32
|
@last_page = (@db.activities.length - 1) / @page_size
|
49
33
|
end
|
50
34
|
|
51
|
-
def
|
35
|
+
def update_index_pages
|
52
36
|
0.upto(@last_page) do |page_no|
|
53
37
|
@page_no = page_no
|
54
|
-
generate_html_index_page
|
38
|
+
generate_html_index_page(page_no)
|
55
39
|
end
|
56
40
|
end
|
57
41
|
|
58
|
-
def to_html(doc)
|
59
|
-
generate_table.to_html(doc)
|
60
|
-
end
|
61
|
-
|
62
42
|
def to_s
|
63
43
|
generate_table.to_s
|
64
44
|
end
|
65
45
|
|
66
46
|
private
|
67
47
|
|
68
|
-
def generate_html_index_page
|
69
|
-
|
48
|
+
def generate_html_index_page(page_index)
|
49
|
+
views = @db.views
|
50
|
+
views.current_page = 'index.html'
|
70
51
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
end
|
52
|
+
pages = PagingButtons.new((0..@last_page).map do |i|
|
53
|
+
"index#{i == 0 ? '' : "-#{i}"}.html"
|
54
|
+
end)
|
55
|
+
pages.current_page =
|
56
|
+
"index#{page_index == 0 ? '' : "-#{page_index}"}.html"
|
57
|
+
@view = View.new("PostRunner Activities", views, pages)
|
78
58
|
|
79
|
-
|
80
|
-
doc
|
81
|
-
doc.meta({ 'http-equiv' => 'Content-Type',
|
82
|
-
'content' => 'text/html; charset=utf-8' })
|
83
|
-
doc.title("PostRunner Activities")
|
84
|
-
style(doc)
|
85
|
-
}
|
86
|
-
end
|
59
|
+
@view.doc.head { @view.doc.style(style) }
|
60
|
+
body(@view.doc)
|
87
61
|
|
88
|
-
|
89
|
-
|
90
|
-
doc.style(<<EOT
|
91
|
-
body {
|
92
|
-
font-family: verdana,arial,sans-serif;
|
93
|
-
margin: 0px;
|
94
|
-
}
|
95
|
-
.main {
|
96
|
-
text-align: center;
|
97
|
-
}
|
98
|
-
.widget_frame {
|
99
|
-
width: 900px;
|
100
|
-
}
|
101
|
-
.activity_link {
|
102
|
-
padding: 0px 3px 0px 3px;
|
103
|
-
}
|
104
|
-
.ft_cell {
|
105
|
-
height: 30px
|
106
|
-
}
|
107
|
-
EOT
|
108
|
-
)
|
62
|
+
output_file = File.join(@db.cfg[:html_dir], pages.current_page)
|
63
|
+
@view.write(output_file)
|
109
64
|
end
|
110
65
|
|
111
66
|
def body(doc)
|
112
|
-
|
113
|
-
first_page = @page_no == 0 ? nil: 'index.html'
|
114
|
-
prev_page = @page_no == 0 ? nil :
|
115
|
-
@page_no == 1 ? 'index.html' :
|
116
|
-
"index#{@page_no - 1}.html"
|
117
|
-
prev_page = @page_no == 0 ? nil :
|
118
|
-
@page_no == 1 ? 'index.html' :
|
119
|
-
"index#{@page_no - 1}.html"
|
120
|
-
next_page = @page_no < @last_page ? "index#{@page_no + 1}.html" : nil
|
121
|
-
last_page = @page_no == @last_page ? nil : "index#{@last_page}.html"
|
122
|
-
titlebar(doc, first_page, prev_page, nil, next_page, last_page)
|
123
|
-
|
67
|
+
@view.body {
|
124
68
|
doc.div({ :class => 'main' }) {
|
125
|
-
|
126
|
-
generate_table.to_html(doc)
|
127
|
-
}
|
69
|
+
ViewFrame.new('Activities', 900, generate_table).to_html(doc)
|
128
70
|
}
|
129
|
-
footer(doc)
|
130
71
|
}
|
131
72
|
end
|
132
73
|
|
@@ -134,11 +75,11 @@ EOT
|
|
134
75
|
i = @page_no < 0 ? 0 : @page_no * @page_size
|
135
76
|
t = FlexiTable.new
|
136
77
|
t.head
|
137
|
-
t.row(%w( Ref. Activity Start Distance Duration Speed/Pace ),
|
78
|
+
t.row(%w( Ref. Activity Type Start Distance Duration Speed/Pace ),
|
138
79
|
{ :halign => :left })
|
139
80
|
t.set_column_attributes([
|
140
81
|
{ :halign => :right },
|
141
|
-
{}, {},
|
82
|
+
{}, {}, {},
|
142
83
|
{ :halign => :right },
|
143
84
|
{ :halign => :right },
|
144
85
|
{ :halign => :right }
|
@@ -150,7 +91,8 @@ EOT
|
|
150
91
|
activities.each do |a|
|
151
92
|
t.row([
|
152
93
|
i += 1,
|
153
|
-
ActivityLink.new(a),
|
94
|
+
ActivityLink.new(a, true),
|
95
|
+
a.activity_type,
|
154
96
|
a.timestamp.strftime("%a, %Y %b %d %H:%M"),
|
155
97
|
local_value(a.total_distance, 'm', '%.2f',
|
156
98
|
{ :metric => 'km', :statute => 'mi' }),
|
@@ -163,14 +105,19 @@ EOT
|
|
163
105
|
t
|
164
106
|
end
|
165
107
|
|
166
|
-
def
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
108
|
+
def style
|
109
|
+
<<EOT
|
110
|
+
body {
|
111
|
+
font-family: verdana,arial,sans-serif;
|
112
|
+
margin: 0px;
|
113
|
+
}
|
114
|
+
.main {
|
115
|
+
text-align: center;
|
116
|
+
}
|
117
|
+
.ft_cell {
|
118
|
+
height: 30px
|
119
|
+
}
|
120
|
+
EOT
|
174
121
|
end
|
175
122
|
|
176
123
|
def local_value(value, from_unit, format, units)
|
@@ -13,18 +13,19 @@
|
|
13
13
|
require 'fit4ruby'
|
14
14
|
|
15
15
|
require 'postrunner/FlexiTable'
|
16
|
-
require 'postrunner/
|
16
|
+
require 'postrunner/ViewFrame'
|
17
17
|
|
18
18
|
module PostRunner
|
19
19
|
|
20
20
|
class ActivitySummary
|
21
21
|
|
22
22
|
include Fit4Ruby::Converters
|
23
|
-
include ViewWidgets
|
24
23
|
|
25
|
-
def initialize(fit_activity,
|
24
|
+
def initialize(fit_activity, unit_system, custom_fields)
|
26
25
|
@fit_activity = fit_activity
|
27
|
-
@name = name
|
26
|
+
@name = custom_fields[:name]
|
27
|
+
@type = custom_fields[:type]
|
28
|
+
@sub_type = custom_fields[:sub_type]
|
28
29
|
@unit_system = unit_system
|
29
30
|
end
|
30
31
|
|
@@ -33,12 +34,9 @@ module PostRunner
|
|
33
34
|
end
|
34
35
|
|
35
36
|
def to_html(doc)
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
frame(doc, 'Laps') {
|
40
|
-
laps.to_html(doc)
|
41
|
-
}
|
37
|
+
width = 600
|
38
|
+
ViewFrame.new("Activity: #{@name}", width, summary).to_html(doc)
|
39
|
+
ViewFrame.new('Laps', width, laps).to_html(doc)
|
42
40
|
end
|
43
41
|
|
44
42
|
private
|
@@ -49,7 +47,9 @@ module PostRunner
|
|
49
47
|
t = FlexiTable.new
|
50
48
|
t.enable_frame(false)
|
51
49
|
t.body
|
52
|
-
t.row([ '
|
50
|
+
t.row([ 'Type:', @type ])
|
51
|
+
t.row([ 'Sub Type:', @sub_type ])
|
52
|
+
t.row([ 'Date:', session.timestamp ])
|
53
53
|
t.row([ 'Distance:',
|
54
54
|
local_value(session, 'total_distance', '%.2f %s',
|
55
55
|
{ :metric => 'km', :statute => 'mi'}) ])
|
@@ -76,7 +76,7 @@ module PostRunner
|
|
76
76
|
session.total_training_effect : '-' ])
|
77
77
|
t.row([ 'Avg. Run Cadence:',
|
78
78
|
session.avg_running_cadence ?
|
79
|
-
"#{session.avg_running_cadence.round} spm" : '-' ])
|
79
|
+
"#{(2 * session.avg_running_cadence).round} spm" : '-' ])
|
80
80
|
t.row([ 'Avg. Vertical Oscillation:',
|
81
81
|
local_value(session, 'avg_vertical_oscillation', '%.1f %s',
|
82
82
|
{ :metric => 'cm', :statute => 'in' }) ])
|
@@ -3,7 +3,7 @@
|
|
3
3
|
#
|
4
4
|
# = ActivityView.rb -- PostRunner - Manage the data from your Garmin sport devices.
|
5
5
|
#
|
6
|
-
# Copyright (c) 2014 by Chris Schlaeger <cs@taskjuggler.org>
|
6
|
+
# Copyright (c) 2014, 2015 by Chris Schlaeger <cs@taskjuggler.org>
|
7
7
|
#
|
8
8
|
# This program is free software; you can redistribute it and/or modify
|
9
9
|
# it under the terms of version 2 of the GNU General Public License as
|
@@ -12,67 +12,75 @@
|
|
12
12
|
|
13
13
|
require 'fit4ruby'
|
14
14
|
|
15
|
-
require 'postrunner/
|
15
|
+
require 'postrunner/View'
|
16
16
|
require 'postrunner/ActivitySummary'
|
17
17
|
require 'postrunner/DeviceList'
|
18
18
|
require 'postrunner/UserProfileView'
|
19
|
-
require 'postrunner/ViewWidgets'
|
20
19
|
require 'postrunner/TrackView'
|
21
20
|
require 'postrunner/ChartView'
|
22
21
|
|
23
22
|
module PostRunner
|
24
23
|
|
25
|
-
class ActivityView
|
24
|
+
class ActivityView < View
|
26
25
|
|
27
|
-
|
28
|
-
|
29
|
-
def initialize(activity, unit_system, predecessor, successor)
|
26
|
+
def initialize(activity, unit_system)
|
30
27
|
@activity = activity
|
28
|
+
db = @activity.db
|
31
29
|
@unit_system = unit_system
|
32
|
-
@predecessor = predecessor
|
33
|
-
@successor = successor
|
34
|
-
@output_dir = activity.html_dir
|
35
|
-
@output_file = nil
|
36
30
|
|
37
|
-
|
31
|
+
views = db.views
|
32
|
+
views.current_page = nil
|
33
|
+
|
34
|
+
# Sort activities in reverse order so the newest one is considered the
|
35
|
+
# last report by the pagin buttons.
|
36
|
+
activities = db.activities.sort do |a1, a2|
|
37
|
+
a1.timestamp <=> a2.timestamp
|
38
|
+
end
|
39
|
+
|
40
|
+
pages = PagingButtons.new(activities.map do |a|
|
41
|
+
"#{a.fit_file[0..-5]}.html"
|
42
|
+
end, false)
|
43
|
+
pages.current_page = "#{@activity.fit_file[0..-5]}.html"
|
44
|
+
|
45
|
+
super("PostRunner Activity: #{@activity.name}", views, pages)
|
38
46
|
generate_html(@doc)
|
39
|
-
|
47
|
+
write(File.join(db.cfg[:html_dir], pages.current_page))
|
40
48
|
end
|
41
49
|
|
42
50
|
private
|
43
51
|
|
44
52
|
def generate_html(doc)
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
53
|
+
doc.head { doc.style(style) }
|
54
|
+
#doc.meta({ 'name' => 'viewport',
|
55
|
+
# 'content' => 'width=device-width, ' +
|
56
|
+
# 'initial-scale=1.0, maximum-scale=1.0, ' +
|
57
|
+
# 'user-scalable=0' })
|
58
|
+
|
59
|
+
body {
|
60
|
+
doc.body({ :onload => 'init()' }) {
|
61
|
+
# The main area with the 2 column layout.
|
62
|
+
doc.div({ :class => 'main' }) {
|
63
|
+
doc.div({ :class => 'left_col' }) {
|
64
|
+
ActivitySummary.new(@activity.fit_activity, @unit_system,
|
65
|
+
{ :name => @activity.name,
|
66
|
+
:type => @activity.activity_type,
|
67
|
+
:sub_type => @activity.activity_sub_type
|
68
|
+
}).to_html(doc)
|
69
|
+
TrackView.new(@activity).to_html(doc)
|
70
|
+
DeviceList.new(@activity.fit_activity).to_html(doc)
|
71
|
+
UserProfileView.new(@activity.fit_activity, @unit_system).
|
72
|
+
to_html(doc)
|
73
|
+
}
|
74
|
+
doc.div({ :class => 'right_col' }) {
|
75
|
+
ChartView.new(@activity, @unit_system).to_html(doc)
|
76
|
+
}
|
77
|
+
}
|
78
|
+
}
|
71
79
|
}
|
72
80
|
end
|
73
81
|
|
74
|
-
def style
|
75
|
-
|
82
|
+
def style
|
83
|
+
<<EOT
|
76
84
|
body {
|
77
85
|
font-family: verdana,arial,sans-serif;
|
78
86
|
margin: 0px;
|
@@ -90,37 +98,6 @@ body {
|
|
90
98
|
width: 600px;
|
91
99
|
}
|
92
100
|
EOT
|
93
|
-
)
|
94
|
-
end
|
95
|
-
|
96
|
-
def body(doc)
|
97
|
-
doc.body({ :onload => 'init()' }) {
|
98
|
-
prev_page = @predecessor ? @predecessor.fit_file[0..-5] + '.html' : nil
|
99
|
-
next_page = @successor ? @successor.fit_file[0..-5] + '.html' : nil
|
100
|
-
titlebar(doc, nil, prev_page, 'index.html', next_page)
|
101
|
-
# The main area with the 2 column layout.
|
102
|
-
doc.div({ :class => 'main' }) {
|
103
|
-
doc.div({ :class => 'left_col' }) {
|
104
|
-
@report.to_html(doc)
|
105
|
-
@track_view.div(doc)
|
106
|
-
@device_list.to_html(doc)
|
107
|
-
@user_profile.to_html(doc)
|
108
|
-
}
|
109
|
-
doc.div({ :class => 'right_col' }) {
|
110
|
-
@chart_view.div(doc)
|
111
|
-
}
|
112
|
-
}
|
113
|
-
footer(doc)
|
114
|
-
}
|
115
|
-
end
|
116
|
-
|
117
|
-
def write_file
|
118
|
-
@output_file = File.join(@output_dir, "#{@activity.fit_file[0..-5]}.html")
|
119
|
-
begin
|
120
|
-
File.write(@output_file, @doc.to_html)
|
121
|
-
rescue IOError
|
122
|
-
Log.fatal "Cannot write activity view file '#{@output_file}: #{$!}"
|
123
|
-
end
|
124
101
|
end
|
125
102
|
|
126
103
|
end
|