watch_tower 0.0.0.1 → 0.0.1.beta1
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.
- data/.todo +3 -0
- data/.travis.yml +4 -0
- data/Gemfile +0 -4
- data/README.md +48 -2
- data/TODO +3 -0
- data/ci/travis.rb +4 -1
- data/lib/watch_tower/appscript.rb +65 -7
- data/lib/watch_tower/cli/install.rb +27 -3
- data/lib/watch_tower/cli/open.rb +2 -0
- data/lib/watch_tower/cli/start.rb +3 -1
- data/lib/watch_tower/cli.rb +2 -0
- data/lib/watch_tower/config.rb +2 -0
- data/lib/watch_tower/core_ext.rb +2 -0
- data/lib/watch_tower/editor/base_appscript.rb +28 -1
- data/lib/watch_tower/editor/base_ps.rb +25 -0
- data/lib/watch_tower/editor/textmate.rb +2 -0
- data/lib/watch_tower/editor/xcode.rb +2 -0
- data/lib/watch_tower/editor.rb +2 -0
- data/lib/watch_tower/errors.rb +2 -0
- data/lib/watch_tower/eye.rb +13 -2
- data/lib/watch_tower/file_tree.rb +78 -0
- data/lib/watch_tower/project/any_based.rb +2 -0
- data/lib/watch_tower/project/git_based.rb +2 -0
- data/lib/watch_tower/project/init.rb +2 -0
- data/lib/watch_tower/project/path_based.rb +2 -0
- data/lib/watch_tower/project.rb +2 -0
- data/lib/watch_tower/server/app.rb +37 -7
- data/lib/watch_tower/server/assets/javascripts/application.js +4 -0
- data/lib/watch_tower/server/assets/javascripts/datepicker.coffee +21 -0
- data/lib/watch_tower/server/assets/javascripts/file_tree.coffee +30 -0
- data/lib/watch_tower/server/assets/javascripts/percentage.coffee +15 -5
- data/lib/watch_tower/server/assets/stylesheets/application.css +1 -0
- data/lib/watch_tower/server/assets/stylesheets/date.sass +17 -0
- data/lib/watch_tower/server/assets/stylesheets/file_tree.sass +42 -0
- data/lib/watch_tower/server/assets/stylesheets/global.sass +7 -4
- data/lib/watch_tower/server/configurations/asset.rb +2 -0
- data/lib/watch_tower/server/configurations.rb +2 -0
- data/lib/watch_tower/server/database.rb +2 -0
- data/lib/watch_tower/server/db/migrate/001_create_projects.rb +2 -0
- data/lib/watch_tower/server/db/migrate/002_create_files.rb +2 -0
- data/lib/watch_tower/server/db/migrate/003_create_time_entries.rb +2 -0
- data/lib/watch_tower/server/db/migrate/004_create_durations.rb +2 -0
- data/lib/watch_tower/server/db/migrate/005_add_hash_to_time_entries.rb +2 -0
- data/lib/watch_tower/server/db/migrate/006_add_hash_to_files.rb +2 -0
- data/lib/watch_tower/server/db/migrate/007_add_editor_to_times_entries.rb +8 -0
- data/lib/watch_tower/server/db/migrate/008_rename_editor_to_editor_name_in_times_entries.rb +5 -0
- data/lib/watch_tower/server/db/migrate/009_remove_editor_index_from_time_entries.rb +7 -0
- data/lib/watch_tower/server/db/migrate/010_add_editor_version_to_times_entries.rb +7 -0
- data/lib/watch_tower/server/helpers/asset.rb +2 -0
- data/lib/watch_tower/server/helpers/improved_partials.rb +2 -0
- data/lib/watch_tower/server/helpers/presenters.rb +33 -0
- data/lib/watch_tower/server/helpers.rb +3 -0
- data/lib/watch_tower/server/models/duration.rb +9 -0
- data/lib/watch_tower/server/models/file.rb +17 -0
- data/lib/watch_tower/server/models/project.rb +30 -0
- data/lib/watch_tower/server/models/time_entry.rb +5 -0
- data/lib/watch_tower/server/presenters/application_presenter.rb +165 -0
- data/lib/watch_tower/server/presenters/file_presenter.rb +10 -0
- data/lib/watch_tower/server/presenters/project_presenter.rb +20 -0
- data/lib/watch_tower/server/presenters.rb +13 -0
- data/lib/watch_tower/server/public/assets/{WatchTower-4d6de11e1bd34165ad91ac46fb711bf3.jpg → WatchTower-58eff0713efffbc6054defddc879e0b1.jpg} +0 -0
- data/lib/watch_tower/server/public/assets/application-4e6971066e06aa53b0c8e52c764044d1.css +389 -0
- data/lib/watch_tower/server/public/assets/application-6a1be75d4fd6a545faceb638e47a2486.js +23778 -0
- data/lib/watch_tower/server/public/assets/calendar-379834cd6e6321a940b662ace47f3032.gif +0 -0
- data/lib/watch_tower/server/public/assets/calendar-blue-d6aa74feef7ee4287532761db99a6c0a.gif +0 -0
- data/lib/watch_tower/server/public/assets/calendar-green-3752fe2996091379c8d321f759039385.gif +0 -0
- data/lib/watch_tower/server/public/assets/jquery.datepick-9c8dfe3a4d40bcafc7b182e194c13836.css +227 -0
- data/lib/watch_tower/server/public/assets/{percentage-d8589e21a5fc85d32a445f531ff8ab95.png → percentage-d0176e99520c95e93eee63738ef5d487.png} +0 -0
- data/lib/watch_tower/server/vendor/assets/images/calendar-blue.gif +0 -0
- data/lib/watch_tower/server/vendor/assets/images/calendar-green.gif +0 -0
- data/lib/watch_tower/server/vendor/assets/images/calendar.gif +0 -0
- data/lib/watch_tower/server/vendor/assets/javascripts/jquery-datepick-ext.js +266 -0
- data/lib/watch_tower/server/vendor/assets/javascripts/jquery-datepick-validation.js +232 -0
- data/lib/watch_tower/server/vendor/assets/javascripts/jquery-datepick.js +2092 -0
- data/lib/watch_tower/server/vendor/assets/stylesheets/jquery.datepick.css +226 -0
- data/lib/watch_tower/server/views/_project.haml +10 -13
- data/lib/watch_tower/server/views/index.haml +9 -6
- data/lib/watch_tower/server/views/layout.haml +7 -4
- data/lib/watch_tower/server/views/project.haml +9 -11
- data/lib/watch_tower/server.rb +8 -1
- data/lib/watch_tower/templates/{watchtower.plist → watchtower.plist.erb} +5 -6
- data/lib/watch_tower/version.rb +12 -3
- data/lib/watch_tower.rb +20 -1
- data/spec/factories.rb +2 -0
- data/spec/watch_tower/appscript_spec.rb +36 -2
- data/spec/watch_tower/editor/base_appscript_spec.rb +83 -0
- data/spec/watch_tower/editor/textmate_spec.rb +37 -0
- data/spec/watch_tower/editor/xcode_spec.rb +6 -0
- data/spec/watch_tower/eye_spec.rb +21 -0
- data/spec/watch_tower/file_tree_spec.rb +156 -0
- data/spec/watch_tower/server/app_spec.rb +64 -20
- data/spec/watch_tower/server/models/file_spec.rb +93 -31
- data/spec/watch_tower/server/models/project_spec.rb +147 -18
- data/spec/watch_tower/server/models/time_entry_spec.rb +10 -0
- data/spec/watch_tower/server/{decorator/project_decorator_spec.rb → presenters/application_presenter_spec.rb} +24 -13
- data/spec/watch_tower/server/presenters/file_presenter_spec.rb +8 -0
- data/spec/watch_tower/server/presenters/project_presenter_spec.rb +130 -0
- data/watch_tower.gemspec +10 -4
- metadata +114 -74
- data/lib/watch_tower/server/decorator/application_decorator.rb +0 -91
- data/lib/watch_tower/server/decorator/file_decorator.rb +0 -38
- data/lib/watch_tower/server/decorator/project_decorator.rb +0 -51
- data/lib/watch_tower/server/decorator.rb +0 -21
- data/lib/watch_tower/server/public/assets/application-7829b53b5ece1a16d22dc3d00f329023.css +0 -107
- data/lib/watch_tower/server/public/assets/application-e0e6b7731aade460f680331e65cf0682.js +0 -9359
- data/lib/watch_tower/server/views/_file.haml +0 -9
@@ -0,0 +1,21 @@
|
|
1
|
+
window.date_filtering =
|
2
|
+
handleResponse: (response, textStatus, jqXHR) ->
|
3
|
+
($ '.page').html(response)
|
4
|
+
percentage.applyPercentage()
|
5
|
+
|
6
|
+
updateMetaTags: (dates) ->
|
7
|
+
($ 'meta[name="date_filtering_from"]').attr 'content', $.datepick.formatDate(dates[0])
|
8
|
+
($ 'meta[name="date_filtering_to"]').attr 'content', $.datepick.formatDate(dates[1])
|
9
|
+
|
10
|
+
handleDatePicker: (dates) ->
|
11
|
+
options = { from_date: $.datepick.formatDate(dates[0]), to_date: $.datepick.formatDate(dates[1]) }
|
12
|
+
url = window.location.pathname
|
13
|
+
date_filtering.updateMetaTags dates
|
14
|
+
$.get url, options, date_filtering.handleResponse
|
15
|
+
|
16
|
+
jQuery ->
|
17
|
+
($ '#date input').datepick
|
18
|
+
rangeSelect: true,
|
19
|
+
monthsToShow: 2,
|
20
|
+
alignment: 'bottomRight',
|
21
|
+
onClose: date_filtering.handleDatePicker
|
@@ -0,0 +1,30 @@
|
|
1
|
+
window.file_tree =
|
2
|
+
handle_button: (domId) ->
|
3
|
+
if ($ domId).hasClass "collapsed"
|
4
|
+
($ domId).removeClass "collapsed"
|
5
|
+
($ domId).addClass "expanded"
|
6
|
+
($ domId).text '-'
|
7
|
+
file_tree.show_folder domId
|
8
|
+
else
|
9
|
+
($ domId).removeClass "expanded"
|
10
|
+
($ domId).addClass "collapsed"
|
11
|
+
($ domId).text '+'
|
12
|
+
file_tree.hide_folder domId
|
13
|
+
|
14
|
+
show_folder: (button_domId) ->
|
15
|
+
($ button_domId).parent().parent().children('.nested_folder').each (index, element) ->
|
16
|
+
($ element).show()
|
17
|
+
($ button_domId).parent().parent().children('.files').each (index, element) ->
|
18
|
+
($ element).show()
|
19
|
+
|
20
|
+
|
21
|
+
hide_folder: (button_domId) ->
|
22
|
+
($ button_domId).parent().parent().children('.nested_folder').each (index, element) ->
|
23
|
+
($ element).hide()
|
24
|
+
($ button_domId).parent().parent().children('.files').each (index, element) ->
|
25
|
+
($ element).hide()
|
26
|
+
|
27
|
+
jQuery ($) ->
|
28
|
+
($ '.collapsed').each (index, element) ->
|
29
|
+
($ element).bind 'click', ->
|
30
|
+
file_tree.handle_button element
|
@@ -1,8 +1,18 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
window.percentage =
|
2
|
+
applyPercentage: ->
|
3
|
+
($ '.percentage_img').each (index, element) ->
|
4
|
+
width = ($ element).attr('data-width')
|
5
|
+
percentage.animateGrowth(element, width * 3)
|
6
|
+
|
7
|
+
animateGrowth: (domId, width, overGrow = false) ->
|
8
|
+
overGrowth = width + 50
|
9
|
+
overGrowth = 300 if overGrowth > 300
|
10
|
+
if overGrow
|
11
|
+
($ domId).effect 'size', { to: { width: width } }, 1000
|
12
|
+
else
|
13
|
+
($ domId).effect 'size', { to: { width: overGrowth } }, 1000, ->
|
14
|
+
percentage.animateGrowth domId, width, true
|
3
15
|
|
4
16
|
|
5
17
|
jQuery ->
|
6
|
-
|
7
|
-
width = ($ element).attr('data-width')
|
8
|
-
animateGrowth(element, width * 3)
|
18
|
+
percentage.applyPercentage()
|
@@ -3,5 +3,6 @@
|
|
3
3
|
* and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
|
4
4
|
* the top of the compiled file, but it's generally better to create a new file per style scope.
|
5
5
|
*= require_self
|
6
|
+
*= require jquery.datepick
|
6
7
|
*= require_tree .
|
7
8
|
*/
|
@@ -0,0 +1,17 @@
|
|
1
|
+
aside#date
|
2
|
+
float: right
|
3
|
+
width: 200px
|
4
|
+
height: 30px
|
5
|
+
padding: 1px
|
6
|
+
border: 1px solid #CDCDCD
|
7
|
+
-webkit-border-radius: 5px
|
8
|
+
-moz-border-radius: 5px
|
9
|
+
border-radius: 5px
|
10
|
+
|
11
|
+
&:hover
|
12
|
+
border: 1px solid #AAAAAA
|
13
|
+
|
14
|
+
input
|
15
|
+
border: none
|
16
|
+
width: 201px
|
17
|
+
height: 21px
|
@@ -0,0 +1,42 @@
|
|
1
|
+
.file_tree
|
2
|
+
background: #f7f7f7
|
3
|
+
padding: 10px
|
4
|
+
min-height: 16px
|
5
|
+
border: 1px solid #cdcdcd
|
6
|
+
-webkit-border-radius: 5px
|
7
|
+
-moz-border-radius: 5px
|
8
|
+
border-radius: 5px
|
9
|
+
|
10
|
+
.collapsed, .expanded
|
11
|
+
float: left
|
12
|
+
text-align: center
|
13
|
+
width: 10px
|
14
|
+
height: 9px
|
15
|
+
margin-right: 10px
|
16
|
+
background: #fff
|
17
|
+
padding: 0 3px 7px 3px
|
18
|
+
-webkit-border-radius: 29px
|
19
|
+
-moz-border-radius: 29px
|
20
|
+
border-radius: 29px
|
21
|
+
|
22
|
+
&:hover
|
23
|
+
background: #5D4BFF
|
24
|
+
color: #fff
|
25
|
+
cursor: pointer
|
26
|
+
|
27
|
+
.name
|
28
|
+
float: left
|
29
|
+
.path
|
30
|
+
float: left
|
31
|
+
width: auto !important
|
32
|
+
.elapsed_time
|
33
|
+
float: right
|
34
|
+
.files
|
35
|
+
display: none
|
36
|
+
list-style: none
|
37
|
+
margin: 0
|
38
|
+
|
39
|
+
.nested_folder
|
40
|
+
display: none
|
41
|
+
&
|
42
|
+
margin-left: 15px
|
@@ -15,7 +15,7 @@ h2
|
|
15
15
|
// Background stylesheet
|
16
16
|
display: block
|
17
17
|
// TODO: Very bad bad bad idea to hardcode assets, how can we reference an asset from sass ?
|
18
|
-
background: url('WatchTower-
|
18
|
+
background: url('WatchTower-58eff0713efffbc6054defddc879e0b1.jpg') no-repeat
|
19
19
|
width: 390px
|
20
20
|
height: 94px
|
21
21
|
|
@@ -48,13 +48,16 @@ body
|
|
48
48
|
|
49
49
|
#main
|
50
50
|
border: 1px solid #cdcdcd
|
51
|
-
-webkit-border-radius: 5px
|
52
|
-
-moz-border-radius: 5px
|
53
|
-
border-radius: 5px
|
51
|
+
-webkit-border-radius: 5px 5px 0 0
|
52
|
+
-moz-border-radius: 5px 5px 0 0
|
53
|
+
border-radius: 5px 5px 0 0
|
54
54
|
padding: 10px
|
55
55
|
min-height: 200px
|
56
56
|
background: #fff
|
57
57
|
|
58
|
+
.page
|
59
|
+
padding: 40px 0 0
|
60
|
+
|
58
61
|
#footer
|
59
62
|
width: 960px
|
60
63
|
text-align: center
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
module WatchTower
|
4
|
+
module Server
|
5
|
+
module Helpers
|
6
|
+
module Presenters
|
7
|
+
|
8
|
+
def self.included(base)
|
9
|
+
base.send :include, InstanceMethods
|
10
|
+
end
|
11
|
+
|
12
|
+
module InstanceMethods
|
13
|
+
|
14
|
+
# Define partial as a helper
|
15
|
+
helpers do
|
16
|
+
# Present an object
|
17
|
+
# Usually called with a block, the method yields the presenter into
|
18
|
+
# the block
|
19
|
+
#
|
20
|
+
# @param [ActiveRecord::Base] Object: The model to present
|
21
|
+
# @param [Nil | Object] klass: The klass to present
|
22
|
+
def present(object, klass = nil)
|
23
|
+
klass ||= "::WatchTower::Server::Presenters::#{object.class.to_s.split('::').last}Presenter".constantize
|
24
|
+
presenter = klass.new(object, self)
|
25
|
+
yield presenter if block_given?
|
26
|
+
presenter
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -1,10 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
1
3
|
module WatchTower
|
2
4
|
module Server
|
3
5
|
class Duration < ::ActiveRecord::Base
|
6
|
+
# Scopes
|
7
|
+
scope :before_date, lambda { |date| where('date <= ?', Date.strptime(date, '%m/%d/%Y')) }
|
8
|
+
scope :after_date, lambda { |date| where('date >= ?', Date.strptime(date, '%m/%d/%Y')) }
|
9
|
+
scope :date_range, lambda { |from, to| after_date(from).before_date(to) }
|
10
|
+
|
11
|
+
# Validations
|
4
12
|
validates :file_id, presence: true
|
5
13
|
validates :date, presence: true
|
6
14
|
validates :duration, presence: true
|
7
15
|
|
16
|
+
# Associations
|
8
17
|
belongs_to :file, counter_cache: true
|
9
18
|
end
|
10
19
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
1
3
|
module WatchTower
|
2
4
|
module Server
|
3
5
|
class File < ::ActiveRecord::Base
|
@@ -20,6 +22,21 @@ module WatchTower
|
|
20
22
|
(elapsed_time * 100) / project.files.sum_elapsed_time
|
21
23
|
end
|
22
24
|
|
25
|
+
# Return an ActiveRelation limited to a date range
|
26
|
+
#
|
27
|
+
# @param [String] From date
|
28
|
+
# @param [String] To date
|
29
|
+
# @return [ActiveRelation]
|
30
|
+
def self.date_range(from, to)
|
31
|
+
from = Date.strptime(from, '%m/%d/%Y')
|
32
|
+
to = Date.strptime(to, '%m/%d/%Y')
|
33
|
+
|
34
|
+
joins(:durations => :file).
|
35
|
+
where('durations.date >= ?', from).
|
36
|
+
where('durations.date <= ?', to).
|
37
|
+
select('DISTINCT files.*')
|
38
|
+
end
|
39
|
+
|
23
40
|
# Returns the sum of all elapsed time
|
24
41
|
#
|
25
42
|
# @return [Integer]
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
1
3
|
module WatchTower
|
2
4
|
module Server
|
3
5
|
class Project < ::ActiveRecord::Base
|
@@ -12,6 +14,34 @@ module WatchTower
|
|
12
14
|
# Associations
|
13
15
|
has_many :files, dependent: :destroy
|
14
16
|
has_many :time_entries, through: :files
|
17
|
+
has_many :durations, through: :files
|
18
|
+
|
19
|
+
# Return the percent of this file
|
20
|
+
def percent
|
21
|
+
(elapsed_time * 100) / self.class.sum_elapsed_time
|
22
|
+
end
|
23
|
+
|
24
|
+
# Return an ActiveRelation limited to a date range
|
25
|
+
#
|
26
|
+
# @param [String] From date
|
27
|
+
# @param [String] To date
|
28
|
+
# @return [ActiveRelation]
|
29
|
+
def self.date_range(from, to)
|
30
|
+
from = Date.strptime(from, '%m/%d/%Y')
|
31
|
+
to = Date.strptime(to, '%m/%d/%Y')
|
32
|
+
|
33
|
+
joins(:durations => :file).
|
34
|
+
where('durations.date >= ?', from).
|
35
|
+
where('durations.date <= ?', to).
|
36
|
+
select('DISTINCT projects.*')
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns the sum of all elapsed time
|
40
|
+
#
|
41
|
+
# @return [Integer]
|
42
|
+
def self.sum_elapsed_time
|
43
|
+
sum(:elapsed_time)
|
44
|
+
end
|
15
45
|
end
|
16
46
|
end
|
17
47
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
1
3
|
module WatchTower
|
2
4
|
module Server
|
3
5
|
class TimeEntry < ::ActiveRecord::Base
|
@@ -10,10 +12,13 @@ module WatchTower
|
|
10
12
|
validates :mtime, presence: true
|
11
13
|
validates_uniqueness_of :mtime, scope: :file_id
|
12
14
|
validates :file_hash, presence: true
|
15
|
+
validates :editor_name, presence: true
|
16
|
+
validates :editor_version, presence: true
|
13
17
|
|
14
18
|
|
15
19
|
# Associations
|
16
20
|
belongs_to :file, counter_cache: true
|
21
|
+
has_many :durations
|
17
22
|
|
18
23
|
# Callbacks
|
19
24
|
after_create :calculate_elapsed_time
|
@@ -0,0 +1,165 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
module WatchTower
|
4
|
+
module Server
|
5
|
+
module Presenters
|
6
|
+
class ApplicationPresenter
|
7
|
+
|
8
|
+
attr_reader :model, :template
|
9
|
+
|
10
|
+
# Presents a model
|
11
|
+
#
|
12
|
+
# @param [Symbol] name
|
13
|
+
def self.presents(name)
|
14
|
+
define_method name do
|
15
|
+
@model
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Initialise the presenter
|
20
|
+
#
|
21
|
+
# @param [ActiveRecord::Base] Model
|
22
|
+
# @param [Object] Template
|
23
|
+
def initialize(model, template)
|
24
|
+
@model = model
|
25
|
+
@template = template
|
26
|
+
end
|
27
|
+
|
28
|
+
# Overwrite Kernel#method_missing to invoke either the model or the
|
29
|
+
# template's method if present, if not Kernel#method_missing will be
|
30
|
+
# called
|
31
|
+
#
|
32
|
+
# @param [Symbol] method: The method name
|
33
|
+
# @param [Array] arguments
|
34
|
+
# @param [Block]
|
35
|
+
def method_missing(method, *args, &block)
|
36
|
+
if model.respond_to?(method)
|
37
|
+
model.send(method, *args, &block)
|
38
|
+
elsif template.respond_to?(method)
|
39
|
+
template.send(method, *args, &block)
|
40
|
+
else
|
41
|
+
super
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns a human formatted time
|
46
|
+
#
|
47
|
+
# @param [Integer] elapsed_time
|
48
|
+
# @return [String] The elapsed time formatted
|
49
|
+
def elapsed(elapsed_time = nil)
|
50
|
+
return "" if elapsed_time.nil? && !model.respond_to?(:elapsed_time)
|
51
|
+
elapsed_time ||= model.elapsed_time
|
52
|
+
humanize_time elapsed_time
|
53
|
+
end
|
54
|
+
|
55
|
+
protected
|
56
|
+
def pluralize(num, word)
|
57
|
+
if num > 1
|
58
|
+
"#{num} #{word.pluralize}"
|
59
|
+
else
|
60
|
+
"#{num} #{word}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Humanize time
|
65
|
+
#
|
66
|
+
# @param [Integer] The number of seconds
|
67
|
+
# @return [String]
|
68
|
+
def humanize_time(time)
|
69
|
+
case
|
70
|
+
when time >= 1.day
|
71
|
+
humanize_day(time)
|
72
|
+
when time >= 1.hour
|
73
|
+
humanize_hour(time)
|
74
|
+
when time >= 1.minute
|
75
|
+
humanize_minute(time)
|
76
|
+
else
|
77
|
+
pluralize time, "second"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
[:day, :hour, :minute].each do |t|
|
82
|
+
class_eval <<-END, __FILE__, __LINE__ + 1
|
83
|
+
protected
|
84
|
+
def humanize_#{t}(time)
|
85
|
+
seconds = 1.#{t}
|
86
|
+
num = (time / seconds).to_i
|
87
|
+
rest = time % seconds
|
88
|
+
|
89
|
+
time_str = pluralize num, "#{t}"
|
90
|
+
|
91
|
+
unless rest == 0
|
92
|
+
"\#{time_str}#{t == :minute ? ' and' : ','} \#{humanize_time(rest)}"
|
93
|
+
else
|
94
|
+
time_str
|
95
|
+
end
|
96
|
+
end
|
97
|
+
END
|
98
|
+
end
|
99
|
+
|
100
|
+
# Parse a file tree
|
101
|
+
#
|
102
|
+
# @param [FileTree] tree
|
103
|
+
# @param [Boolean] root
|
104
|
+
# @return [String] HTML of the file tree
|
105
|
+
def parse_file_tree(tree, root = false)
|
106
|
+
# Create the root element
|
107
|
+
if root
|
108
|
+
html = '<article class="file_tree">'
|
109
|
+
html << '<div id="root" class="folder">'
|
110
|
+
else
|
111
|
+
folder_name = ::File.basename(tree.base_path)
|
112
|
+
html = %(<div id="nested_#{folder_name}" class="nested_folder">)
|
113
|
+
end
|
114
|
+
# Open the wrapper
|
115
|
+
html << '<div>'
|
116
|
+
# Add the collapsed span
|
117
|
+
html << '<span class="collapsed">+</span>'
|
118
|
+
# Add the name
|
119
|
+
if root
|
120
|
+
html << '<span class="name">Project</span>'
|
121
|
+
else
|
122
|
+
html << %(<span class="name">#{folder_name}</span>)
|
123
|
+
end
|
124
|
+
# Add the elapsed time
|
125
|
+
html << %(<span class="elapsed_time">#{elapsed(tree.elapsed_time)}</span>)
|
126
|
+
# End with a clearfix element
|
127
|
+
html << '<div class="clearfix"></div>'
|
128
|
+
# Close the wrapper
|
129
|
+
html << '</div>'
|
130
|
+
# Add the nested_tree if available
|
131
|
+
if tree.nested_tree.any?
|
132
|
+
tree.nested_tree.each_pair do |folder, nested_tree|
|
133
|
+
html << parse_file_tree(nested_tree)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
# Add the files
|
137
|
+
if tree.files.any?
|
138
|
+
# Open the files 's ul
|
139
|
+
html << '<ul class="files">'
|
140
|
+
tree.files.each do |file|
|
141
|
+
# Open the file's li
|
142
|
+
html << '<li class="file">'
|
143
|
+
# Add the path
|
144
|
+
html << %(<span class="path">#{file[:path]}</span>)
|
145
|
+
# Add the elapsed time
|
146
|
+
html << %(<span class="elapsed_time">#{elapsed(file[:elapsed_time])}</span>)
|
147
|
+
# End with a clearfix element
|
148
|
+
html << '<div class="clearfix"></div>'
|
149
|
+
# Close the file's li
|
150
|
+
html << '</li>'
|
151
|
+
end
|
152
|
+
# Close the files 's ul
|
153
|
+
html << '</ul>'
|
154
|
+
end
|
155
|
+
# Close the root div
|
156
|
+
html << "</div>"
|
157
|
+
# Clode the article if it is the root element
|
158
|
+
html << "</article>" if root
|
159
|
+
# Finally return the whole thing
|
160
|
+
html
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
module WatchTower
|
4
|
+
module Server
|
5
|
+
module Presenters
|
6
|
+
class ProjectPresenter < ApplicationPresenter
|
7
|
+
|
8
|
+
# Return a file tree representation of a bunch of files
|
9
|
+
#
|
10
|
+
# @return [String] HTML representation of a file tree
|
11
|
+
def file_tree(files)
|
12
|
+
# Create a FileTree
|
13
|
+
file_tree = FileTree.new(files.first.project.path, files)
|
14
|
+
# Parse and return the tree
|
15
|
+
parse_file_tree(file_tree, true)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|