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
|
||||
#
|
||||
# - 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
|
||||
# - same for project, unless we are in a project context
|
||||
# - 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 :find_time_entry, only: %i(edit update start stop)
|
||||
before_action :authorize_edit_time, only: %i(edit update)
|
||||
before_action :find_timer, only: %i(new edit current)
|
||||
|
||||
def new
|
||||
@time_entry = new_time_entry
|
||||
@@ -55,7 +56,7 @@ class StopwatchTimersController < ApplicationController
|
||||
end
|
||||
|
||||
def stop
|
||||
r = Stopwatch::StopTimer.new().call
|
||||
r = Stopwatch::StopTimer.new.call
|
||||
unless r.success?
|
||||
logger.error "unable to stop timer"
|
||||
end
|
||||
@@ -64,14 +65,19 @@ class StopwatchTimersController < ApplicationController
|
||||
end
|
||||
|
||||
def current
|
||||
timer = Stopwatch::Timer.new User.current
|
||||
render json: timer.to_json
|
||||
render json: @timer.to_json
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_timer
|
||||
@timer = Stopwatch::Timer.new User.current
|
||||
@timer.update
|
||||
end
|
||||
|
||||
def find_time_entry
|
||||
@time_entry = time_entries.find params[:id]
|
||||
|
||||
end
|
||||
|
||||
def load_todays_entries
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
<%= 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 %>
|
||||
|
||||
@@ -5,5 +5,11 @@
|
||||
$('#ajax-modal').html('<%= j render partial: 'new' %>');
|
||||
showModal('ajax-modal', '700px');
|
||||
<% 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 = {
|
||||
highlightRunningTimer: function(){
|
||||
$.get(Stopwatch.currentTimerUrl, function(data){
|
||||
if (data.running && data.time_entry_id) {
|
||||
Stopwatch.highlightTimer(data.time_entry_id);
|
||||
}
|
||||
});
|
||||
},
|
||||
window.initStopwatch = function(config){
|
||||
var currentTimerUrl = config.currentTimerUrl;
|
||||
var hourFormat = config.hourFormat;
|
||||
|
||||
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');
|
||||
if(id && id != ''){
|
||||
$('table.time-entries tr.time-entry').each(function(idx, el){
|
||||
@@ -15,10 +14,68 @@ window.Stopwatch = {
|
||||
var trId = tr.attr('id');
|
||||
if (trId && trId == 'time-entry-'+id) {
|
||||
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,
|
||||
:new_stopwatch_timer_path,
|
||||
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,
|
||||
if: ->(*_){ User.current.logged? and User.current.allowed_to?(:log_time, nil, global: true) }
|
||||
end
|
||||
|
||||
@@ -2,6 +2,24 @@ require 'json'
|
||||
|
||||
module Stopwatch
|
||||
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
|
||||
|
||||
def initialize(user)
|
||||
@@ -20,18 +38,33 @@ module Stopwatch
|
||||
end
|
||||
|
||||
def stop
|
||||
if hours = runtime_hours and
|
||||
time_entry = TimeEntry.find_by_id(time_entry_id)
|
||||
|
||||
time_entry.update_column :hours, time_entry.hours + hours
|
||||
update stop: true
|
||||
end
|
||||
data[:started_at] = nil
|
||||
|
||||
# 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
|
||||
data[:started_at] = stop || !running? ? nil : Time.now.to_i
|
||||
save
|
||||
end
|
||||
|
||||
def time_spent
|
||||
h = runtime_hours || 0
|
||||
if e = time_entry
|
||||
h += e.hours
|
||||
end
|
||||
h
|
||||
end
|
||||
|
||||
def to_json
|
||||
formatter = HourFormatter.new(time_spent)
|
||||
{
|
||||
time_entry_id: time_entry_id,
|
||||
time_spent: formatter.format_hours,
|
||||
html_time_spent: formatter.html_hours,
|
||||
running: running?
|
||||
}.to_json
|
||||
end
|
||||
@@ -45,6 +78,10 @@ module Stopwatch
|
||||
data[:time_entry_id]
|
||||
end
|
||||
|
||||
def time_entry
|
||||
TimeEntry.find_by_id(time_entry_id)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def data
|
||||
|
||||
@@ -33,6 +33,16 @@ class TimerTest < ActiveSupport::TestCase
|
||||
assert_equal hours + 1, @time_entry.hours
|
||||
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
|
||||
hours = @time_entry.hours
|
||||
@timer.start @time_entry
|
||||
|
||||
Reference in New Issue
Block a user