postrunner 0.0.6 → 0.0.7
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.
- 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
|