mirror of
https://github.com/rickbarrette/stopwatch.git
synced 2026-04-02 09:51:57 -04:00
show currently running timer in window title, menu item
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
# weiter
|
# weiter
|
||||||
#
|
#
|
||||||
# - update time entry row after context menu stop (reflect saved time)
|
# - update time entry row after context menu stop (reflect saved time)
|
||||||
# - show current time in menu item / window title
|
#
|
||||||
# - remember last activity, preselect that in 'new' form
|
# - remember last activity, preselect that in 'new' form
|
||||||
# - same for project, unless we are in a project context
|
# - same for project, unless we are in a project context
|
||||||
# - focus first field that needs an action, depending on above
|
# - focus first field that needs an action, depending on above
|
||||||
@@ -14,6 +14,7 @@ class StopwatchTimersController < ApplicationController
|
|||||||
before_action :authorize_log_time, only: %i(new create start stop current)
|
before_action :authorize_log_time, only: %i(new create start stop current)
|
||||||
before_action :find_time_entry, only: %i(edit update start stop)
|
before_action :find_time_entry, only: %i(edit update start stop)
|
||||||
before_action :authorize_edit_time, only: %i(edit update)
|
before_action :authorize_edit_time, only: %i(edit update)
|
||||||
|
before_action :find_timer, only: %i(new edit current)
|
||||||
|
|
||||||
def new
|
def new
|
||||||
@time_entry = new_time_entry
|
@time_entry = new_time_entry
|
||||||
@@ -55,7 +56,7 @@ class StopwatchTimersController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def stop
|
def stop
|
||||||
r = Stopwatch::StopTimer.new().call
|
r = Stopwatch::StopTimer.new.call
|
||||||
unless r.success?
|
unless r.success?
|
||||||
logger.error "unable to stop timer"
|
logger.error "unable to stop timer"
|
||||||
end
|
end
|
||||||
@@ -64,14 +65,19 @@ class StopwatchTimersController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def current
|
def current
|
||||||
timer = Stopwatch::Timer.new User.current
|
render json: @timer.to_json
|
||||||
render json: timer.to_json
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def find_timer
|
||||||
|
@timer = Stopwatch::Timer.new User.current
|
||||||
|
@timer.update
|
||||||
|
end
|
||||||
|
|
||||||
def find_time_entry
|
def find_time_entry
|
||||||
@time_entry = time_entries.find params[:id]
|
@time_entry = time_entries.find params[:id]
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def load_todays_entries
|
def load_todays_entries
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
<%= javascript_tag do %>
|
<%= javascript_tag do %>
|
||||||
Stopwatch.currentTimerUrl = '<%= j current_stopwatch_timers_url %>';
|
window.stopwatch = window.initStopwatch({
|
||||||
|
currentTimerUrl: '<%= j current_stopwatch_timers_url %>',
|
||||||
|
hourFormat: '<%= j format_hours 0.0 %>'
|
||||||
|
});
|
||||||
|
<% if User.current.logged? %>
|
||||||
|
window.stopwatch.highlightRunningTimer(
|
||||||
|
<%= raw Stopwatch::Timer.new(User.current).to_json %>
|
||||||
|
)
|
||||||
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -5,5 +5,11 @@
|
|||||||
$('#ajax-modal').html('<%= j render partial: 'new' %>');
|
$('#ajax-modal').html('<%= j render partial: 'new' %>');
|
||||||
showModal('ajax-modal', '700px');
|
showModal('ajax-modal', '700px');
|
||||||
<% end %>
|
<% end %>
|
||||||
Stopwatch.highlightTimer('<%= @started_time_entry.id if @started_time_entry %>');
|
|
||||||
|
<% if @started_time_entry %>
|
||||||
|
window.stopwatch.timerStarted('<%= @started_time_entry.id %>',
|
||||||
|
'<%= j format_hours @started_time_entry.hours %>');
|
||||||
|
<% else %>
|
||||||
|
window.stopwatch.timerStopped();
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
window.Stopwatch = {
|
window.initStopwatch = function(config){
|
||||||
highlightRunningTimer: function(){
|
var currentTimerUrl = config.currentTimerUrl;
|
||||||
$.get(Stopwatch.currentTimerUrl, function(data){
|
var hourFormat = config.hourFormat;
|
||||||
if (data.running && data.time_entry_id) {
|
|
||||||
Stopwatch.highlightTimer(data.time_entry_id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
highlightTimer: function(id){
|
var hoursRe = hourFormat.replace(/0+/g, '\\d+').replace(/\./g, '\\.');
|
||||||
|
var titleRegexp = new RegExp('^(' + hoursRe + ' - )?(.*)$');
|
||||||
|
var menuItemRegexp = new RegExp('^(.*?)( \\(' + hoursRe + '\\))?$')
|
||||||
|
|
||||||
|
var highlightTimer = function(id, timeHtml){
|
||||||
$('table.time-entries tr.time-entry.running').removeClass('running');
|
$('table.time-entries tr.time-entry.running').removeClass('running');
|
||||||
if(id && id != ''){
|
if(id && id != ''){
|
||||||
$('table.time-entries tr.time-entry').each(function(idx, el){
|
$('table.time-entries tr.time-entry').each(function(idx, el){
|
||||||
@@ -15,10 +14,68 @@ window.Stopwatch = {
|
|||||||
var trId = tr.attr('id');
|
var trId = tr.attr('id');
|
||||||
if (trId && trId == 'time-entry-'+id) {
|
if (trId && trId == 'time-entry-'+id) {
|
||||||
tr.addClass('running');
|
tr.addClass('running');
|
||||||
|
if (timeHtml) {
|
||||||
|
tr.find('td.hours').html(timeHtml)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
var updateSpentTime = function(entryId, spentTime){
|
||||||
|
// update window title
|
||||||
|
var s = ''
|
||||||
|
if (spentTime){
|
||||||
|
s = spentTime + ' - ';
|
||||||
|
}
|
||||||
|
var match = titleRegexp.exec(document.title);
|
||||||
|
document.title = s + match[2];
|
||||||
|
|
||||||
|
// update menu item
|
||||||
|
match = menuItemRegexp.exec($('a#stopwatch-menu').text());
|
||||||
|
if (spentTime){
|
||||||
|
s = ' (' + spentTime + ')';
|
||||||
|
} else {
|
||||||
|
s = '';
|
||||||
|
}
|
||||||
|
$('a#stopwatch-menu').text(match[1] + s);
|
||||||
|
};
|
||||||
|
|
||||||
|
var highlightRunningTimer = function(data){
|
||||||
|
if (data && data.running && data.time_entry_id) {
|
||||||
|
highlightTimer(data.time_entry_id, data.html_time_spent);
|
||||||
|
updateSpentTime(data.time_entry_id, data.time_spent);
|
||||||
|
window.setTimeout(function(){
|
||||||
|
fetchCurrentTimer(highlightRunningTimer);
|
||||||
|
}, 60000);
|
||||||
|
} else {
|
||||||
|
highlightTimer();
|
||||||
|
updateSpentTime();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var fetchCurrentTimer = function(successHandler){
|
||||||
|
$.ajax(currentTimerUrl, {
|
||||||
|
type: 'GET',
|
||||||
|
contentType: 'application/octet-stream', // suppress the ajax-indicator that's bound to ajax events in application.js
|
||||||
|
success: successHandler
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
//$(document).on('ready', fetchCurrentTimer(highlightRunningTimer));
|
||||||
|
|
||||||
|
return({
|
||||||
|
highlightRunningTimer: highlightRunningTimer,
|
||||||
|
timerStopped: function(){
|
||||||
|
highlightRunningTimer();
|
||||||
|
},
|
||||||
|
timerStarted: function(entryId, spentTime){
|
||||||
|
highlightRunningTimer({
|
||||||
|
running: true,
|
||||||
|
time_entry_id: entryId,
|
||||||
|
time_spent: spentTime
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$(document).on('ready', Stopwatch.highlightRunningTimer);
|
|
||||||
|
|||||||
2
init.rb
2
init.rb
@@ -14,7 +14,7 @@ Redmine::Plugin.register :stopwatch do
|
|||||||
menu :account_menu, :stopwatch,
|
menu :account_menu, :stopwatch,
|
||||||
:new_stopwatch_timer_path,
|
:new_stopwatch_timer_path,
|
||||||
caption: :button_log_time,
|
caption: :button_log_time,
|
||||||
html: { method: :get, id: 'stopwatch-toggle', 'data-remote': true },
|
html: { method: :get, id: 'stopwatch-menu', 'data-remote': true },
|
||||||
before: :my_account,
|
before: :my_account,
|
||||||
if: ->(*_){ User.current.logged? and User.current.allowed_to?(:log_time, nil, global: true) }
|
if: ->(*_){ User.current.logged? and User.current.allowed_to?(:log_time, nil, global: true) }
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,6 +2,24 @@ require 'json'
|
|||||||
|
|
||||||
module Stopwatch
|
module Stopwatch
|
||||||
class Timer
|
class Timer
|
||||||
|
|
||||||
|
class HourFormatter
|
||||||
|
include ApplicationHelper
|
||||||
|
|
||||||
|
def initialize(s)
|
||||||
|
@s = s
|
||||||
|
end
|
||||||
|
|
||||||
|
def html_hours
|
||||||
|
super format_hours
|
||||||
|
end
|
||||||
|
|
||||||
|
def format_hours
|
||||||
|
super @s
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
attr_reader :user
|
attr_reader :user
|
||||||
|
|
||||||
def initialize(user)
|
def initialize(user)
|
||||||
@@ -20,18 +38,33 @@ module Stopwatch
|
|||||||
end
|
end
|
||||||
|
|
||||||
def stop
|
def stop
|
||||||
if hours = runtime_hours and
|
update stop: true
|
||||||
time_entry = TimeEntry.find_by_id(time_entry_id)
|
end
|
||||||
|
|
||||||
time_entry.update_column :hours, time_entry.hours + hours
|
# saves currently logged time to the time entry and resets the started_at
|
||||||
|
# timestamp to either nil (stop: true) or current time.
|
||||||
|
def update(stop: false)
|
||||||
|
if hours = runtime_hours and entry = self.time_entry
|
||||||
|
time_entry.update_column :hours, entry.hours + hours
|
||||||
end
|
end
|
||||||
data[:started_at] = nil
|
data[:started_at] = stop || !running? ? nil : Time.now.to_i
|
||||||
save
|
save
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def time_spent
|
||||||
|
h = runtime_hours || 0
|
||||||
|
if e = time_entry
|
||||||
|
h += e.hours
|
||||||
|
end
|
||||||
|
h
|
||||||
|
end
|
||||||
|
|
||||||
def to_json
|
def to_json
|
||||||
|
formatter = HourFormatter.new(time_spent)
|
||||||
{
|
{
|
||||||
time_entry_id: time_entry_id,
|
time_entry_id: time_entry_id,
|
||||||
|
time_spent: formatter.format_hours,
|
||||||
|
html_time_spent: formatter.html_hours,
|
||||||
running: running?
|
running: running?
|
||||||
}.to_json
|
}.to_json
|
||||||
end
|
end
|
||||||
@@ -45,6 +78,10 @@ module Stopwatch
|
|||||||
data[:time_entry_id]
|
data[:time_entry_id]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def time_entry
|
||||||
|
TimeEntry.find_by_id(time_entry_id)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def data
|
def data
|
||||||
|
|||||||
@@ -33,6 +33,16 @@ class TimerTest < ActiveSupport::TestCase
|
|||||||
assert_equal hours + 1, @time_entry.hours
|
assert_equal hours + 1, @time_entry.hours
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "should update time entry" do
|
||||||
|
hours = @time_entry.hours
|
||||||
|
@timer.start @time_entry
|
||||||
|
@data[:started_at] = 1.hour.ago.to_i
|
||||||
|
@timer.update
|
||||||
|
|
||||||
|
@time_entry.reload
|
||||||
|
assert_equal hours + 1, @time_entry.hours
|
||||||
|
end
|
||||||
|
|
||||||
test "should save and restore" do
|
test "should save and restore" do
|
||||||
hours = @time_entry.hours
|
hours = @time_entry.hours
|
||||||
@timer.start @time_entry
|
@timer.start @time_entry
|
||||||
|
|||||||
Reference in New Issue
Block a user