improved play and stop button (#2692)
* improved play and stop button if only one record is allowed * stop button first in action menu * fix skin colors * autofocus term field in search form * fix responsive column layout in invoice listing * improve responsive header for small screens
This commit is contained in:
@@ -24,18 +24,14 @@ export default class KimaiActiveRecords extends KimaiPlugin {
|
||||
}
|
||||
|
||||
init() {
|
||||
const menu = document.querySelector(this.selector);
|
||||
this.menu = document.querySelector(this.selector);
|
||||
|
||||
// the menu can be hidden if user has no permissions to see it
|
||||
if (menu === null) {
|
||||
if (this.menu === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dropdown = menu.querySelector('ul.dropdown-menu');
|
||||
|
||||
this.attributes = dropdown.dataset;
|
||||
this.itemList = dropdown.querySelector('li > ul.menu');
|
||||
this.label = menu.querySelector('a > span.label');
|
||||
this.attributes = this.menu.dataset;
|
||||
|
||||
const self = this;
|
||||
const handle = function() { self.reloadActiveRecords(); };
|
||||
@@ -50,15 +46,10 @@ export default class KimaiActiveRecords extends KimaiPlugin {
|
||||
document.addEventListener('kimai.customerDelete', handle);
|
||||
}
|
||||
|
||||
emptyList() {
|
||||
this.itemList.innerHTML = '';
|
||||
}
|
||||
|
||||
_toggleMenu(hasEntries) {
|
||||
const menu = document.querySelector(this.selector);
|
||||
const menuEmpty = document.querySelector(this.selectorEmpty);
|
||||
this.menu.style.display = hasEntries ? 'inline-block' : 'none';
|
||||
|
||||
menu.style.display = hasEntries ? 'inline-block' : 'none';
|
||||
const menuEmpty = document.querySelector(this.selectorEmpty);
|
||||
if (menuEmpty !== null) {
|
||||
menuEmpty.style.display = !hasEntries ? 'inline-block' : 'none';
|
||||
}
|
||||
@@ -67,42 +58,52 @@ export default class KimaiActiveRecords extends KimaiPlugin {
|
||||
setEntries(entries) {
|
||||
this._toggleMenu(entries.length > 0);
|
||||
|
||||
const template = this.menu.querySelector('[data-template="active-record"]');
|
||||
|
||||
const label = this.menu.querySelector('a > span.label');
|
||||
if (label !== null) {
|
||||
label.innerText = entries.length === 0 ? '' : entries.length;
|
||||
}
|
||||
|
||||
if (entries.length === 0) {
|
||||
this.label.innerText = '';
|
||||
this.emptyList();
|
||||
return;
|
||||
}
|
||||
|
||||
let htmlToInsert = '';
|
||||
const durations = this.getContainer().getPlugin('timesheet-duration');
|
||||
|
||||
for (let timesheet of entries) {
|
||||
htmlToInsert +=
|
||||
`<li>` +
|
||||
`<a href="${ this.attributes['href'].replace('000', timesheet.id) }" data-event="kimai.timesheetStop kimai.timesheetUpdate" class="api-link" data-method="PATCH" data-msg-error="timesheet.stop.error" data-msg-success="timesheet.stop.success">` +
|
||||
`<div class="pull-left">` +
|
||||
`<i class="${ this.attributes['icon'] } fa-2x"></i>` +
|
||||
`</div>` +
|
||||
`<h4>` +
|
||||
`<span>${ timesheet.activity.name }</span>` +
|
||||
`<small>` +
|
||||
`<span data-title="true" data-since="${ timesheet.begin }">${ durations.formatDuration(timesheet.duration) }</span>` +
|
||||
`</small>` +
|
||||
`</h4>` +
|
||||
`<p>${ timesheet.project.name } (${ timesheet.project.customer.name })</p>` +
|
||||
`</a>` +
|
||||
`</li>`;
|
||||
}
|
||||
|
||||
if (this.label.dataset.warning < entries.length) {
|
||||
this.label.classList = 'label label-danger';
|
||||
if (template === null) {
|
||||
this._replaceInNode(this.menu, entries[0]);
|
||||
} else {
|
||||
this.label.classList = 'label label-warning';
|
||||
}
|
||||
this.label.innerText = entries.length;
|
||||
this.itemList.innerHTML = htmlToInsert;
|
||||
const container = template.parentElement;
|
||||
container.innerHTML = '';
|
||||
|
||||
durations.updateRecords();
|
||||
for (let timesheet of entries) {
|
||||
const newNode = template.cloneNode(true);
|
||||
container.appendChild(this._replaceInNode(newNode, timesheet));
|
||||
}
|
||||
}
|
||||
|
||||
this.getContainer().getPlugin('timesheet-duration').updateRecords();
|
||||
}
|
||||
|
||||
_replaceInNode(node, timesheet) {
|
||||
const date = this.getContainer().getPlugin('date');
|
||||
const allReplacer = node.querySelectorAll('[data-replacer]');
|
||||
for (let node of allReplacer) {
|
||||
const replacerName = node.dataset['replacer'];
|
||||
if (replacerName === 'url') {
|
||||
node.href = this.attributes['href'].replace('000', timesheet.id);
|
||||
} else if (replacerName === 'activity') {
|
||||
node.innerText = timesheet.activity.name;
|
||||
} else if (replacerName === 'project') {
|
||||
node.innerText = timesheet.project.name;
|
||||
} else if (replacerName === 'customer') {
|
||||
node.innerText = timesheet.project.customer.name;
|
||||
} else if (replacerName === 'duration') {
|
||||
node.dataset['since'] = timesheet.begin;
|
||||
node.innerText = date.formatDuration(timesheet.duration);
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
reloadActiveRecords() {
|
||||
|
||||
@@ -31,36 +31,28 @@ export default class KimaiAjaxModalForm extends KimaiReducedClickHandler {
|
||||
this.isDirty = false;
|
||||
|
||||
this.modal = jQuery('#remote_form_modal');
|
||||
this.modal.on('hide.bs.modal', function (e) {
|
||||
if (self.isDirty) {
|
||||
if (jQuery('#remote_form_modal .modal-body .remote_modal_is_dirty_warning').length === 0) {
|
||||
const msg = self.getContainer().getTranslation().get('modal.dirty');
|
||||
jQuery('#remote_form_modal .modal-body').prepend('<p class="'+(self.modal.hasClass('modal-danger') ? 'well well-sm ' : '') + 'text-danger small remote_modal_is_dirty_warning">' + msg + '</p>');
|
||||
this.modal
|
||||
.on('hide.bs.modal', function (e) {
|
||||
if (self.isDirty) {
|
||||
if (jQuery('#remote_form_modal .modal-body .remote_modal_is_dirty_warning').length === 0) {
|
||||
const msg = self.getContainer().getTranslation().get('modal.dirty');
|
||||
jQuery('#remote_form_modal .modal-body').prepend('<p class="'+(self.modal.hasClass('modal-danger') ? 'well well-sm ' : '') + 'text-danger small remote_modal_is_dirty_warning">' + msg + '</p>');
|
||||
}
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
jQuery(self._getFormIdentifier()).off('change', self._isDirtyHandler);
|
||||
self.isDirty = false;
|
||||
self.getContainer().getPlugin('event').trigger('modal-hide');
|
||||
});
|
||||
this.modal.on('hidden.bs.modal', function () {
|
||||
// kill all references, so GC can kick in
|
||||
self.getContainer().getPlugin('form').destroyForm(self._getFormIdentifier());
|
||||
jQuery('#remote_form_modal .modal-body').replaceWith('');
|
||||
});
|
||||
|
||||
this.modal.on('show.bs.modal', function () {
|
||||
self.getContainer().getPlugin('event').trigger('modal-show');
|
||||
});
|
||||
this.modal.on('shown.bs.modal', function () {
|
||||
// workaround for autofocus attribute, as the modal "steals" it
|
||||
let formAutofocus = jQuery(self._getFormIdentifier()).find('[autofocus]');
|
||||
if (formAutofocus.length < 1) {
|
||||
formAutofocus = jQuery(self._getFormIdentifier()).find('input[type=text],textarea,select');
|
||||
}
|
||||
formAutofocus.filter(':not("[data-datetimepicker=on]")').filter(':visible:first').focus().delay(1000).focus();
|
||||
});
|
||||
jQuery(self._getFormIdentifier()).off('change', self._isDirtyHandler);
|
||||
self.isDirty = false;
|
||||
self.getContainer().getPlugin('event').trigger('modal-hide');
|
||||
})
|
||||
.on('hidden.bs.modal', function () {
|
||||
// kill all references, so GC can kick in
|
||||
self.getContainer().getPlugin('form').destroyForm(self._getFormIdentifier());
|
||||
jQuery('#remote_form_modal .modal-body').replaceWith('');
|
||||
})
|
||||
.on('show.bs.modal', function () {
|
||||
self.getContainer().getPlugin('event').trigger('modal-show');
|
||||
});
|
||||
|
||||
this._addClickHandler(this.selector, function(href) {
|
||||
self.openUrlInModal(href);
|
||||
|
||||
@@ -24,6 +24,30 @@ export default class KimaiThemeInitializer extends KimaiPlugin {
|
||||
// activate all form plugins
|
||||
this.getContainer().getPlugin('form').activateForm('.content-wrapper form', 'body');
|
||||
this.getContainer().getPlugin('form').activateForm('form.searchform', 'body');
|
||||
|
||||
this.registerModalAutofocus('#modal_search');
|
||||
this.registerModalAutofocus('#remote_form_modal');
|
||||
}
|
||||
|
||||
/**
|
||||
* workaround for autofocus attribute, as the modal "steals" it
|
||||
*
|
||||
* @param {string} selector
|
||||
*/
|
||||
registerModalAutofocus(selector) {
|
||||
let modal = jQuery(selector);
|
||||
if (modal.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
modal.on('shown.bs.modal', function () {
|
||||
let form = modal.find('form');
|
||||
let formAutofocus = form.find('[autofocus]');
|
||||
if (formAutofocus.length < 1) {
|
||||
formAutofocus = form.find('input[type=text],textarea,select');
|
||||
}
|
||||
formAutofocus.filter(':not("[data-datetimepicker=on]")').filter(':visible:first').focus().delay(1000).focus();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,43 +15,21 @@ $avatar-base-size: 30;
|
||||
line-height: 1;
|
||||
position: relative;
|
||||
font-weight: bold;
|
||||
font-size: ($avatar-base-size / 2 - 2)+px;
|
||||
top: ($avatar-base-size / 4 - 1)+px;
|
||||
font-size: ceil($avatar-base-size / 2 - 2)+px;
|
||||
top: ceil($avatar-base-size / 4 - 1)+px;
|
||||
}
|
||||
|
||||
.avatar-xs {
|
||||
width: ($avatar-base-size * 0.75)+px;
|
||||
height: ($avatar-base-size * 0.75)+px;
|
||||
.initials {
|
||||
font-size: ($avatar-base-size * .75 / 2 - 2)+px;
|
||||
top: ($avatar-base-size * .75 / 4 - 1)+px;
|
||||
}
|
||||
}
|
||||
$avatarSizes: "xs" .75, "sm" 1.25, "md" 1.5, "lg" 2;
|
||||
|
||||
.avatar-sm {
|
||||
width: ($avatar-base-size * 1.25)+px;
|
||||
height: ($avatar-base-size * 1.25)+px;
|
||||
.initials {
|
||||
font-size: ($avatar-base-size * 1.25 / 2 - 2)+px;
|
||||
top: ($avatar-base-size * 1.25 / 4 - 1)+px;
|
||||
}
|
||||
}
|
||||
@each $avatarSizeName, $avatarBaseSize in $avatarSizes {
|
||||
.avatar-#{$avatarSizeName} {
|
||||
width: ceil($avatar-base-size * $avatarBaseSize)+px;
|
||||
height: ceil($avatar-base-size * $avatarBaseSize)+px;
|
||||
|
||||
.avatar-md {
|
||||
width: ($avatar-base-size * 1.5)+px;
|
||||
height: ($avatar-base-size * 1.5)+px;
|
||||
.initials {
|
||||
font-size: ($avatar-base-size * 1.5 / 2 - 2)+px;
|
||||
top: ($avatar-base-size * 1.5 / 4 - 1)+px;
|
||||
}
|
||||
}
|
||||
|
||||
.avatar-lg {
|
||||
width: ($avatar-base-size * 2)+px;
|
||||
height: ($avatar-base-size * 2)+px;
|
||||
.initials {
|
||||
font-size: ($avatar-base-size * 2 / 2 - 2)+px;
|
||||
top: ($avatar-base-size * 2 / 4 - 1)+px;
|
||||
.initials {
|
||||
font-size: ceil($avatar-base-size * $avatarBaseSize / 2 - 2)+px;
|
||||
top: ceil($avatar-base-size * $avatarBaseSize / 4 - 1)+px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,11 +37,11 @@ $avatar-base-size: 30;
|
||||
border-radius: 50%;
|
||||
border: 3px solid #fff;
|
||||
.avatar {
|
||||
width: ($avatar-base-size * 2.75)+px;
|
||||
height: ($avatar-base-size * 2.75)+px;
|
||||
width: ceil($avatar-base-size * 2.75)+px;
|
||||
height: ceil($avatar-base-size * 2.75)+px;
|
||||
.initials {
|
||||
font-size: ($avatar-base-size * 2.75 / 2 - 2)+px;
|
||||
top: ($avatar-base-size * 2.75 / 4 - 1)+px;
|
||||
font-size: ceil($avatar-base-size * 2.75 / 2 - 2)+px;
|
||||
top: ceil($avatar-base-size * 2.75 / 4 - 1)+px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,9 @@
|
||||
}
|
||||
|
||||
@media (max-width: $screen-md-min) {
|
||||
.content-header h1 {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
.content-header>.breadcrumb {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
@@ -5,22 +5,34 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
.navbar-custom-menu {
|
||||
.navbar-nav {
|
||||
li>.ddt-small {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-nav {
|
||||
img.img-circle {
|
||||
max-height: 26px;
|
||||
margin-top: -2px;
|
||||
}
|
||||
.avatar {
|
||||
max-height: 26px;
|
||||
max-width: 26px;
|
||||
margin-top: -2px;
|
||||
max-height: 30px;
|
||||
max-width: 30px;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
margin-top: 0;
|
||||
.initials {
|
||||
top: 4px;
|
||||
top: 5px;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
li>a.ddt-large {
|
||||
padding: 15px 12px 11px 10px;
|
||||
font-size: 10px;
|
||||
li>.ddt-small {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
li.messages-menu ul.menu li {
|
||||
a:hover .pull-left i {
|
||||
@@ -43,17 +55,13 @@
|
||||
width: unset !important;
|
||||
}
|
||||
.user-menu {
|
||||
margin-right: 10px;
|
||||
ul {
|
||||
li {
|
||||
a {
|
||||
color: #444;
|
||||
&:hover {
|
||||
background-color: #f4f4f4;
|
||||
i {
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
color: #444;
|
||||
i {
|
||||
padding-right: 10px;
|
||||
}
|
||||
@@ -64,9 +72,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.notifications-menu>.dropdown-menu>li .menu>li>a:hover i {
|
||||
color: $green;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $screen-sm-min) {
|
||||
@@ -80,25 +85,69 @@
|
||||
padding: 5px 25px 5px 25px
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
.ticktac:hover i.running{
|
||||
color: #4ff131;
|
||||
@keyframes ticktac-blink {
|
||||
0% { opacity: 1; }
|
||||
5% { opacity: 0.95; }
|
||||
10% { opacity: 0.9; }
|
||||
15% { opacity: 0.85; }
|
||||
20% { opacity: 0.8; }
|
||||
25% { opacity: 0.75; }
|
||||
30% { opacity: 0.7; }
|
||||
35% { opacity: 0.65; }
|
||||
40% { opacity: 0.6; }
|
||||
45% { opacity: 0.65; }
|
||||
50% { opacity: 0.7; }
|
||||
55% { opacity: 0.75; }
|
||||
60% { opacity: 0.8; }
|
||||
65% { opacity: 0.85; }
|
||||
70% { opacity: 0.9; }
|
||||
75% { opacity: 0.95; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
.ticktac-single {
|
||||
display: block;
|
||||
position: relative;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
i {
|
||||
animation: ticktac-blink 2s step-end infinite;
|
||||
}
|
||||
span {
|
||||
font-size: 1.8em;
|
||||
line-height: 1em;
|
||||
padding-left: 5px;
|
||||
padding-right: 0;
|
||||
}
|
||||
&:hover {
|
||||
background: rgba(0,0,0,.3);
|
||||
}
|
||||
}
|
||||
body.fixed {
|
||||
.navbar-nav {
|
||||
.user-menu {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
li.open .ticktac i.running{
|
||||
color: #4ff131;
|
||||
@media (max-width: $screen-sm-min) {
|
||||
.ticktac-single {
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
.navbar-custom-menu .navbar-nav li>.ddt-small {
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.ticktac i.stopped{
|
||||
color: #fff;
|
||||
@media (max-width: $screen-md-min) {
|
||||
.ticktac-single {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
.navbar-custom-menu .navbar-nav li>.ddt-small {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.ticktac:hover i.stopped{
|
||||
color: #4ff131;
|
||||
}
|
||||
.user-panel>.info {
|
||||
left: 40px;
|
||||
padding-top: 0px;
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -1,26 +1,69 @@
|
||||
|
||||
$skins: blue, blue, black, green, purple, red, yellow;
|
||||
$skins:
|
||||
"blue" $yellow #fff $yellow $red,
|
||||
"black" $yellow #000 $green $red,
|
||||
"green" $yellow #fff $yellow $red,
|
||||
"purple" $yellow #fff $yellow $red,
|
||||
"red" $yellow #fff $yellow $yellow,
|
||||
"yellow" $blue #fff $green $red
|
||||
;
|
||||
|
||||
@each $skinColor in $skins {
|
||||
@media (max-width: $screen-sm-min) {
|
||||
.skin-#{$skinColor} .main-header .navbar .dropdown-menu li.divider {
|
||||
background-color: #ccc;
|
||||
}
|
||||
.skin-#{$skinColor} .main-header .navbar .dropdown-menu li a {
|
||||
color: #444;
|
||||
}
|
||||
.skin-#{$skinColor} .main-header .navbar .dropdown-menu li a:hover {
|
||||
background: #fff;
|
||||
}
|
||||
@each $skinColor, $navHighlight, $tickTacColor, $startButton, $stopButton in $skins {
|
||||
@each $prefix in (".skin-#{$skinColor}", ".skin-#{$skinColor}-light") {
|
||||
@media (max-width: $screen-sm-min) {
|
||||
#{$prefix} {
|
||||
.main-header .navbar .dropdown-menu li.divider {
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
.skin-#{$skinColor}-light .main-header .navbar .dropdown-menu li.divider {
|
||||
background-color: #ccc;
|
||||
.main-header .navbar .dropdown-menu li a {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.main-header .navbar .dropdown-menu li a:hover {
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
.skin-#{$skinColor}-light .main-header .navbar .dropdown-menu li a {
|
||||
color: #444;
|
||||
}
|
||||
.skin-#{$skinColor}-light .main-header .navbar .dropdown-menu li a:hover {
|
||||
background: #fff;
|
||||
#{$prefix} {
|
||||
.navbar-nav {
|
||||
.user-menu {
|
||||
ul {
|
||||
li {
|
||||
a {
|
||||
&:hover {
|
||||
i {
|
||||
color: $navHighlight;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.notifications-menu > .dropdown-menu > li .menu > li > a:hover i {
|
||||
color: $navHighlight;
|
||||
}
|
||||
}
|
||||
.ticktac-single {
|
||||
i {
|
||||
color: $tickTacColor;
|
||||
}
|
||||
span {
|
||||
color: $tickTacColor;
|
||||
}
|
||||
&:hover {
|
||||
.ticktac-stop {
|
||||
i {
|
||||
color: $stopButton;
|
||||
}
|
||||
}
|
||||
.ticktac-start {
|
||||
i {
|
||||
color: $startButton;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ $green: #00a65a;
|
||||
// Info
|
||||
//$aqua: #00c0ef;
|
||||
// Warning
|
||||
//$yellow: #f39c12;
|
||||
$yellow: #f0ad4e;
|
||||
$blue: #0073b7;
|
||||
//$navy: #001F3F;
|
||||
//$teal: #39CCCC;
|
||||
|
||||
Reference in New Issue
Block a user