dynamically update title and visible durations for running records (#752)
This commit is contained in:
@@ -63,7 +63,8 @@ require('chart.js/dist/Chart.min');
|
||||
|
||||
// ------ Kimai itself ------
|
||||
require('./js/kimai.js');
|
||||
require('./js/datatable.js');
|
||||
require('./js/KimaiDatatableColumnView.js');
|
||||
require('./js/KimaiActiveRecordsDuration.js');
|
||||
require('./js/toolbar.js');
|
||||
require('./images/default_avatar.png');
|
||||
require('./images/signature.png');
|
||||
|
||||
90
assets/js/KimaiActiveRecordsDuration.js
Normal file
90
assets/js/KimaiActiveRecordsDuration.js
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* [KIMAI] KimaiActiveRecordsDuration: updates active records on your personal timesheet view
|
||||
*/
|
||||
|
||||
// Following the UMD template https://github.com/umdjs/umd/blob/master/templates/returnExportsGlobal.js
|
||||
(function (root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define(['moment'], function (moment) {
|
||||
return (root.KimaiActiveRecordsDuration = factory(moment));
|
||||
});
|
||||
} else if (typeof module === 'object' && module.exports) {
|
||||
let moment = (typeof window != 'undefined') ? window.moment : undefined;
|
||||
if (!moment) {
|
||||
moment = require('moment');
|
||||
}
|
||||
module.exports = factory(moment);
|
||||
} else {
|
||||
root.KimaiActiveRecordsDuration = factory(root.moment);
|
||||
}
|
||||
}(typeof self !== 'undefined' ? self : this, function (moment) {
|
||||
|
||||
class KimaiActiveRecordsDuration {
|
||||
constructor(selector) {
|
||||
this.selector = selector;
|
||||
}
|
||||
|
||||
registerUpdates(timeout) {
|
||||
let self = this;
|
||||
window.setTimeout(
|
||||
function() {
|
||||
self.updateRecords().registerUpdates(timeout);
|
||||
},
|
||||
timeout
|
||||
);
|
||||
}
|
||||
|
||||
updateRecords() {
|
||||
let durations = [];
|
||||
for(let record of document.querySelectorAll(this.selector)) {
|
||||
const since = record.getAttribute('data-since');
|
||||
const format = record.getAttribute('data-format');
|
||||
const duration = this.getDuration(since, format);
|
||||
if (record.getAttribute('data-title') !== null) {
|
||||
durations.push(duration);
|
||||
}
|
||||
record.textContent = duration;
|
||||
}
|
||||
|
||||
if (durations.length === 0) {
|
||||
return this;
|
||||
}
|
||||
|
||||
let title = durations.shift();
|
||||
let prefix = ' | ';
|
||||
|
||||
for (let duration of durations.slice(0, 2)) {
|
||||
title += prefix + duration;
|
||||
}
|
||||
document.title = title;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
getDuration(since, format) {
|
||||
let duration = moment.duration(moment(new Date()).diff(moment(since)));
|
||||
|
||||
let hours = parseInt(duration.asHours());
|
||||
let minutes = duration.minutes();
|
||||
let seconds = duration.seconds();
|
||||
let formatted = '';
|
||||
|
||||
// special case for hours, as they can overflow the 24h barrier - Kimai does not support days as duration unit
|
||||
if (hours.length === 1) {
|
||||
hours = '0' + hours;
|
||||
}
|
||||
|
||||
return format.replace('%h', hours).replace('%m', ('0'+minutes).substr(-2)).replace('%s', ('0'+seconds).substr(-2));
|
||||
}
|
||||
}
|
||||
|
||||
return KimaiActiveRecordsDuration;
|
||||
|
||||
}));
|
||||
10
composer.lock
generated
10
composer.lock
generated
@@ -2967,16 +2967,16 @@
|
||||
},
|
||||
{
|
||||
"name": "kevinpapst/adminlte-bundle",
|
||||
"version": "2.7",
|
||||
"version": "2.7.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/kevinpapst/AdminLTEBundle.git",
|
||||
"reference": "23bf4f114900de5d27dd4584cb65e6e5ca600938"
|
||||
"reference": "310b203eabbfcaa165fe98a6df83aefd0342458a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/kevinpapst/AdminLTEBundle/zipball/23bf4f114900de5d27dd4584cb65e6e5ca600938",
|
||||
"reference": "23bf4f114900de5d27dd4584cb65e6e5ca600938",
|
||||
"url": "https://api.github.com/repos/kevinpapst/AdminLTEBundle/zipball/310b203eabbfcaa165fe98a6df83aefd0342458a",
|
||||
"reference": "310b203eabbfcaa165fe98a6df83aefd0342458a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3024,7 +3024,7 @@
|
||||
}
|
||||
],
|
||||
"description": "Admin theme bundle for Symfony 4 based on AdminLTE 2.4.8 with FOSUserBundle support",
|
||||
"time": "2019-04-29T18:09:59+00:00"
|
||||
"time": "2019-05-04T10:43:53+00:00"
|
||||
},
|
||||
{
|
||||
"name": "kimai/kimai2-composer",
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"build/app.js": "./app.js?[contenthash]",
|
||||
"build/app.css": "./app.css?361d4520765f893b9b0fb94bc9f761c1",
|
||||
"build/app.css": "./app.css?5748431b597376d6472e58cf92e2c0f5",
|
||||
"build/fonts/fa-solid-900.woff2": "./fonts/fa-solid-900.woff2?e8a92a29",
|
||||
"build/images/fa-solid-900.svg": "./images/fa-solid-900.svg?666a82cb",
|
||||
"build/images/glyphicons-halflings-regular.svg": "./images/glyphicons-halflings-regular.svg?89889688",
|
||||
|
||||
@@ -13,6 +13,7 @@ use App\Utils\LocaleSettings;
|
||||
use DateTime;
|
||||
use Twig\Extension\AbstractExtension;
|
||||
use Twig\TwigFilter;
|
||||
use Twig\TwigFunction;
|
||||
|
||||
/**
|
||||
* Date specific twig extensions
|
||||
@@ -68,6 +69,16 @@ class DateExtensions extends AbstractExtension
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFunctions()
|
||||
{
|
||||
return [
|
||||
new TwigFunction('get_format_duration', [$this, 'getDurationFormat']),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DateTime $date
|
||||
* @return string
|
||||
@@ -165,4 +176,12 @@ class DateExtensions extends AbstractExtension
|
||||
|
||||
return $twelveHour;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDurationFormat()
|
||||
{
|
||||
return $this->localeSettings->getDurationFormat();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,6 +189,9 @@
|
||||
});
|
||||
{# $.kimai.pauseRecord('li.messages-menu ul.menu li'); #}
|
||||
});
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
new KimaiActiveRecordsDuration('[data-since]').updateRecords().registerUpdates(5000);
|
||||
});
|
||||
</script>
|
||||
{% set event = trigger(constant('App\\Event\\ThemeEvent::JAVASCRIPT')) %}
|
||||
{{ event.content|raw }}
|
||||
|
||||
@@ -19,7 +19,10 @@
|
||||
</div>
|
||||
<h4>
|
||||
<span>{{ entry.activity.name }}</span>
|
||||
<small><i class="{{ 'timesheet'|icon }}"></i> {{ entry|duration }}</small>
|
||||
<small>
|
||||
<i class="{{ 'timesheet'|icon }}"></i>
|
||||
<span data-title="true" data-since="{{ entry.begin.format(constant('DATE_ISO8601')) }}" data-format="{{ get_format_duration() }}">{{ entry|duration }}</span>
|
||||
</small>
|
||||
</h4>
|
||||
<p>{{ entry.project.name }} ({{ entry.project.customer.name }})</p>
|
||||
</a>
|
||||
|
||||
@@ -59,7 +59,9 @@
|
||||
{% if not duration_only %}
|
||||
<td class="text-nowrap {{ tables.data_table_column_class(tableName, columns, 'endtime') }}">‐</td>
|
||||
{% endif %}
|
||||
<td class="text-nowrap {{ tables.data_table_column_class(tableName, columns, 'duration') }}"><i>{{ entry|duration }}</i></td>
|
||||
<td class="text-nowrap {{ tables.data_table_column_class(tableName, columns, 'duration') }}">
|
||||
<i data-since="{{ entry.begin.format(constant('DATE_ISO8601')) }}" data-format="{{ get_format_duration() }}">{{ entry|duration }}</i>
|
||||
</td>
|
||||
{% endif %}
|
||||
<td class="text-nowrap {{ tables.data_table_column_class(tableName, columns, 'rate') }}">
|
||||
{% if not entry.end or not is_granted('view_rate', entry) %}
|
||||
|
||||
@@ -77,7 +77,9 @@
|
||||
{% if not duration_only %}
|
||||
<td class="text-nowrap {{ tables.data_table_column_class(tableName, columns, 'endtime') }}">‐</td>
|
||||
{% endif %}
|
||||
<td class="text-nowrap {{ tables.data_table_column_class(tableName, columns, 'duration') }}"><i>{{ entry|duration }}</i></td>
|
||||
<td class="text-nowrap {{ tables.data_table_column_class(tableName, columns, 'duration') }}">
|
||||
<i data-since="{{ entry.begin.format(constant('DATE_ISO8601')) }}" data-format="{{ get_format_duration() }}">{{ entry|duration }}</i>
|
||||
</td>
|
||||
{% if canSeeRate %}
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'rate') }}">‐</td>
|
||||
{% endif %}
|
||||
|
||||
@@ -16,6 +16,7 @@ use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Twig\TwigFilter;
|
||||
use Twig\TwigFunction;
|
||||
|
||||
/**
|
||||
* @covers \App\Twig\DateExtensions
|
||||
@@ -52,6 +53,20 @@ class DateExtensionsTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
public function testGetFunctions()
|
||||
{
|
||||
$functions = ['get_format_duration'];
|
||||
$sut = $this->getSut('de', []);
|
||||
$twigFunctions = $sut->getFunctions();
|
||||
$this->assertCount(count($functions), $twigFunctions);
|
||||
$i = 0;
|
||||
/** @var TwigFunction $filter */
|
||||
foreach ($twigFunctions as $filter) {
|
||||
$this->assertInstanceOf(TwigFunction::class, $filter);
|
||||
$this->assertEquals($functions[$i++], $filter->getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $locale
|
||||
* @param \DateTime $date
|
||||
|
||||
Reference in New Issue
Block a user