watch_tower 0.0.0.1 → 0.0.1.beta1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|