dynamically update title and visible durations for running records (#752)

This commit is contained in:
Kevin Papst
2019-05-05 03:01:25 +02:00
committed by GitHub
parent 65df1ff909
commit aa113cc300
14 changed files with 486 additions and 281 deletions

View File

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

View 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
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -59,7 +59,9 @@
{% if not duration_only %}
<td class="text-nowrap {{ tables.data_table_column_class(tableName, columns, 'endtime') }}">&dash;</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) %}

View File

@@ -77,7 +77,9 @@
{% if not duration_only %}
<td class="text-nowrap {{ tables.data_table_column_class(tableName, columns, 'endtime') }}">&dash;</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') }}">&dash;</td>
{% endif %}

View File

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

595
yarn.lock

File diff suppressed because it is too large Load Diff