show currently running timer in window title, menu item

This commit is contained in:
Jens Kraemer
2020-04-25 17:09:38 +08:00
parent 797e40384a
commit 30bcd5ff8c
7 changed files with 146 additions and 22 deletions

View File

@@ -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

View File

@@ -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 %>

View File

@@ -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 %>

View File

@@ -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);

View File

@@ -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

View File

@@ -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

View File

@@ -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