Elastic: Calendar - Availability finder dialog

This commit is contained in:
Aleksander Machniak 2018-03-30 10:49:07 +02:00
parent a444b5b801
commit 3346ad8aa6
5 changed files with 450 additions and 124 deletions

View file

@ -1221,7 +1221,9 @@ function rcube_calendar_ui(settings)
});
// enable/disable buttons
$('#schedule-find-prev').button('option', 'disabled', (fb_start.getTime() < now.getTime()));
// FIXME: .button() does nothing in Elastic skin
var disabled = fb_start.getTime() < now.getTime();
$('#schedule-find-prev').button('option', 'disabled', disabled).prop('disabled', disabled);
// dialog buttons
var buttons = [
@ -1338,12 +1340,12 @@ function rcube_calendar_ui(settings)
for (var i=0; i < event_attendees.length; i++) {
data = event_attendees[i];
domid = String(data.email).replace(rcmail.identifier_expr, '');
times_html += '<tr id="fbrow' + domid + '">' + slots_row + '</tr>';
times_html += '<tr id="fbrow' + domid + '" class="fbcontent">' + slots_row + '</tr>';
}
// add line for all/required attendees
times_html += '<tr class="spacer"><td colspan="' + (dayslots * freebusy_ui.numdays) + '"></td>';
times_html += '<tr id="fbrowall">' + slots_row + '</tr>';
times_html += '<tr id="fbrowall" class="fbcontent">' + slots_row + '</tr>';
var table = $('#schedule-freebusy-times');
table.children('thead').html(dates_row + times_row);
@ -1405,8 +1407,15 @@ function rcube_calendar_ui(settings)
slotnum = freebusy_ui.interval > 60 ? 1 : (60 / freebusy_ui.interval),
cells = table.children('thead').find('td'),
cell_width = cells.first().get(0).offsetWidth,
h_margin = table.parents('table').data('h-margin'),
v_margin = table.parents('table').data('v-margin'),
slotend;
if (h_margin === undefined)
h_margin = 4;
if (v_margin === undefined)
v_margin = 4;
// iterate through slots to determine position and size of the overlay
for (i=0; i < cells.length; i++) {
for (n=0; n < slotnum; n++) {
@ -1428,8 +1437,13 @@ function rcube_calendar_ui(settings)
// overlay is visible
if (width > 0) {
overlay.css({ width: (width-4)+'px', height:(table.children('tbody').height() - 4)+'px', left:pos.left+'px', top:pos.top+'px' }).show();
overlay.css({
width: (width - h_margin) + 'px',
height: (table.children('tbody').height() - v_margin) + 'px',
left: pos.left + 'px',
top: pos.top + 'px'
}).show();
// configure draggable
if (!overlay.data('isdraggable')) {
overlay.draggable({
@ -1612,7 +1626,7 @@ function rcube_calendar_ui(settings)
// also update total row if all data was loaded
if (!freebusy_ui.loading && freebusy_data.all[ts] && all_cell) {
var all_status = freebusy_data.all[ts][2] ? 'busy' : 'unknown',
var w, all_status = freebusy_data.all[ts][2] ? 'busy' : 'unknown',
req_status = freebusy_data.required[ts][2] ? 'busy' : 'free';
for (j=1; j < status_classes.length; j++) {
@ -1625,10 +1639,12 @@ function rcube_calendar_ui(settings)
attr['class'] = req_status + ' all-' + all_status;
// these elements use some specific styling, so we want to minimize their number
if (last && last.attr('class') == attr['class'])
last.css('width', (percent + parseFloat(last.css('width').replace('%', ''))).toFixed(2) + '%');
if (last && last.attr('class').startsWith(attr['class'])) {
w = percent + parseFloat(last.css('width').replace('%', ''));
last.css('width', w.toFixed(2) + '%').attr('class', attr['class'] + ' w' + w.toFixed(0));
}
else {
last = $('<div>').attr(attr);
last = $('<div>').attr(attr).addClass('w' + percent.toFixed(0)).append('<span>');
all_slots.push(last);
}
}
@ -1757,10 +1773,11 @@ function rcube_calendar_ui(settings)
render_freebusy_grid(Math.min(-1, offset));
else
render_freebusy_overlay();
var now = new Date();
$('#schedule-find-prev').button('option', 'disabled', (event.start.getTime() < now.getTime()));
var now = new Date(), disabled = event.start.getTime() < now.getTime();
// FIXME: .button() does nothing in Elastic skin
$('#schedule-find-prev').button('option', 'disabled', disabled).prop('disabled', disabled);
// speak new selection
rcmail.display_message(rcmail.gettext('suggestedslot', 'calendar') + ': ' + me.event_date_text(event, true), 'voice');
}
@ -1989,7 +2006,7 @@ function rcube_calendar_ui(settings)
var buttons = [
{
text: rcmail.gettext('addresource', 'calendar'),
'class': 'mainaction create',
'class': 'mainaction save',
click: function() { rcmail.command('add-resource'); }
},
{
@ -2021,8 +2038,7 @@ function rcube_calendar_ui(settings)
height: 500
}).show();
// define add-button as main action
$('.ui-dialog-buttonset .ui-button', $dialog.parent()).first().addClass('mainaction').attr('id', 'rcmbtncalresadd');
$('.ui-dialog-buttonset .ui-button', $dialog.parent()).first().attr('id', 'rcmbtncalresadd');
me.dialog_resize($dialog.get(0), 540, Math.min(1000, $(window).width() - 50));
@ -4039,7 +4055,7 @@ function rcube_calendar_ui(settings)
});
$('#schedule-freebusy-prev').html('&#9668;').button().click(function(){ render_freebusy_grid(-1); });
$('#schedule-freebusy-next').html('&#9658;').button().click(function(){ render_freebusy_grid(1); }).parent();//FIXME .buttonset();
$('#schedule-freebusy-next').html('&#9658;').button().click(function(){ render_freebusy_grid(1); }); // FIXME .parent().buttonset();
$('#schedule-find-prev').button().click(function(){ freebusy_find_slot(-1); });
$('#schedule-find-next').button().click(function(){ freebusy_find_slot(1); });

View file

@ -211,6 +211,7 @@ $labels['savetocalendar'] = 'Save to calendar';
$labels['openpreview'] = 'Check Calendar';
$labels['noearlierevents'] = 'No earlier events';
$labels['nolaterevents'] = 'No later events';
$labels['legend'] = 'Legend';
// resources
$labels['resource'] = 'Resource';

View file

@ -182,10 +182,11 @@
</div>
<div id="eventresourcesdialog" class="popupmenu">
<div id="resource-dialog-left">
<div id="resource-selection" class="uibox listbox" role="navigation" aria-labelledby="aria-label-resourceselection">
<h2 class="voice" id="aria-label-resourceselection"><roundcube:label name="calendar.arialabelresourceselection" /></h2>
<div id="resourcequicksearch">
<div class="resources-dialog">
<h3 class="voice" id="aria-label-resourceselection"><roundcube:label name="calendar.arialabelresourceselection" /></h2>
<div class="resource-selection uibox listbox" role="navigation" aria-labelledby="aria-label-resourceselection">
<span class="header-title"><roundcube:label name="calendar.resources" /></span>
<div id="resourcequicksearch" class="header">
<div class="searchbox" role="search" aria-labelledby="aria-label-resourcesearchform" aria-controls="resources-list">
<h3 id="aria-label-resourcesearchform" class="voice"><roundcube:label name="calendar.arialabelresourcesearchform" /></h3>
<label for="resourcesearchbox" class="voice"><roundcube:label name="calendar.searchterms" /></label>
@ -198,62 +199,66 @@
<roundcube:object name="plugin.resources_list" id="resources-list" class="listing treelist" />
</div>
</div>
</div>
<div id="resource-dialog-right">
<div id="resource-info" class="uibox contentbox" role="region" aria-labelledby="aria-label-resourcedetails">
<h2 class="boxtitle" id="aria-label-resourcedetails"><roundcube:label name="calendar.resourcedetails" /></h2>
<div class="scroller">
<roundcube:object name="plugin.resource_info" id="resource-details" class="propform" aria-live="polite" aria-relevant="text" aria-atomic="true" />
<div class="resource-content">
<div id="resource-info" class="uibox contentbox" role="region" aria-labelledby="aria-label-resourcedetails">
<h2 class="boxtitle" id="aria-label-resourcedetails"><roundcube:label name="calendar.resourcedetails" /></h2>
<div class="scroller">
<roundcube:object name="plugin.resource_info" id="resource-details" class="propform" aria-live="polite" aria-relevant="text" aria-atomic="true" />
</div>
</div>
</div>
<div id="resource-availability" class="uibox contentbox" role="region" aria-labelledby="aria-label-resourceavailability">
<h2 class="boxtitle" id="aria-label-resourceavailability"><roundcube:label name="calendar.resourceavailability" /></h2>
<roundcube:object name="plugin.resource_calendar" id="resource-freebusy-calendar" />
<div class="boxpagenav">
<roundcube:button name="resource-cal-prev" id="resource-calendar-prev" type="link" class="icon prevpage" title="calendar.prevslot" label="calendar.prevweek" />
<roundcube:button name="resource-cal-next" id="resource-calendar-next" type="link" class="icon nextpage" title="calendar.nextslot" label="calendar.nextweek" />
<div id="resource-availability" class="uibox contentbox" role="region" aria-labelledby="aria-label-resourceavailability">
<h2 class="boxtitle" id="aria-label-resourceavailability"><roundcube:label name="calendar.resourceavailability" /></h2>
<roundcube:object name="plugin.resource_calendar" id="resource-freebusy-calendar" />
<div class="boxpagenav">
<roundcube:button name="resource-cal-prev" id="resource-calendar-prev" type="link" class="icon prevpage" title="calendar.prevslot" label="calendar.prevweek" />
<roundcube:button name="resource-cal-next" id="resource-calendar-next" type="link" class="icon nextpage" title="calendar.nextslot" label="calendar.nextweek" />
</div>
</div>
</div>
</div>
</div>
<div id="eventfreebusy" class="popupmenu calendar-scheduler">
<roundcube:object name="plugin.attendees_freebusy_table" id="attendees-freebusy-table" cellpadding="0" />
<div id="eventfreebusy" class="popupmenu calendar-scheduler formcontent">
<roundcube:object name="plugin.attendees_freebusy_table" id="attendees-freebusy-table"
class="schedule-table" data-h-margin="-1" data-v-margin="1" />
<div class="schedule-nav">
<button id="schedule-freebusy-prev" title="<roundcube:label name='previouspage' />">&#9668;</button>
<button id="schedule-freebusy-next" title="<roundcube:label name='nextpage' />">&#9658;</button>
</div>
<div class="slot-nav">
<div class="schedule-find-buttons">
<button id="schedule-find-prev">&#9668; <roundcube:label name="calendar.prevslot" /></button>
<button id="schedule-find-next"><roundcube:label name="calendar.nextslot" /> &#9658;</button>
<button id="schedule-find-prev" class="btn btn-secondary prev-slot"><roundcube:label name="calendar.prevslot" /></button>
<button id="schedule-find-next" class="btn btn-secondary next-slot"><roundcube:label name="calendar.nextslot" /></button>
</div>
<div class="schedule-options">
<label><input type="checkbox" id="schedule-freebusy-workinghours" value="1" /><roundcube:label name="calendar.onlyworkinghours" /></label>
<label><input type="checkbox" id="schedule-freebusy-workinghours" value="1" class="pretty-checkbox" /><roundcube:label name="calendar.onlyworkinghours" /></label>
</div>
</div>
<div class="schedule-range">
<div class="form-section">
<label for="schedule-startdate"><roundcube:label name="calendar.start" /></label>
<input type="text" name="startdate" size="11" id="schedule-startdate" disabled="true" /> &nbsp;
<input type="text" name="starttime" size="6" id="schedule-starttime" disabled="true" />
<div class="form-group row">
<label for="schedule-startdate" class="col-form-label col-sm-2"><roundcube:label name="calendar.start" /></label>
<span class="col-sm-10 datetime">
<input type="text" name="startdate" size="11" id="schedule-startdate" class="form-control" disabled="true" /> &nbsp;
<input type="text" name="starttime" size="6" id="schedule-starttime" class="form-control" disabled="true" />
</span>
</div>
<div class="form-section">
<label for="schedule-enddate"><roundcube:label name="calendar.end" /></label>
<input type="text" name="enddate" size="11" id="schedule-enddate" disabled="true" /> &nbsp;
<input type="text" name="endtime" size="6" id="schedule-endtime" disabled="true" />
<div class="form-group row">
<label for="schedule-enddate" class="col-form-label col-sm-2"><roundcube:label name="calendar.end" /></label>
<span class="col-sm-10 datetime">
<input type="text" name="enddate" size="11" id="schedule-enddate" class="form-control" disabled="true" /> &nbsp;
<input type="text" name="endtime" size="6" id="schedule-endtime" class="form-control" disabled="true" />
</span>
</div>
</div>
<div class="schedule-legend">
<roundcube:include file="/templates/freebusylegend.html" />
<div class="attendees-list">
<span class="attendee organizer"><roundcube:label name="calendar.roleorganizer" /></span>
<span class="attendee req-participant"><roundcube:label name="calendar.rolerequired" /></span>
<span class="attendee opt-participant"><roundcube:label name="calendar.roleoptional" /></span>
<span class="attendee non-participant"><roundcube:label name="calendar.rolenonparticipant" /></span>
<span class="attendee chair"><roundcube:label name="calendar.rolechair" /></span>
<div class="schedule-legend form-group row">
<label class="col-form-label col-sm-2"><roundcube:label name="calendar.legend" /></label>
<span class="col-sm-10 form-control-plaintext">
<roundcube:include file="/templates/freebusylegend.html" />
<span class="attendee organizer"><roundcube:label name="calendar.roleorganizer" /></span>
<span class="attendee req-participant"><roundcube:label name="calendar.rolerequired" /></span>
<span class="attendee opt-participant"><roundcube:label name="calendar.roleoptional" /></span>
<span class="attendee non-participant"><roundcube:label name="calendar.rolenonparticipant" /></span>
<span class="attendee chair"><roundcube:label name="calendar.rolechair" /></span>
</span>
</div>
</div>
</div>
@ -377,6 +382,12 @@
$(document).ready(function() {
// TODO: $('#timezone-display').appendTo($('.fc-header-title')).removeClass('hidden');
});
// Update layout after initialization
// In devel mode we have to wait until all styles are applied by less
if (rcmail.env.devel_mode && window.less) {
less.pageLoadFinished.then(function() { $(window).resize(); });
}
</script>
<roundcube:include file="includes/footer.html" />

View file

@ -56,6 +56,8 @@
&.calendar.cal---invitation--pending,
&.calendar.cal---invitation--declined,
&.calendar.cal-__bdays__ {
font-style: italic;
a.quickview {
padding-right: .25rem;
}
@ -471,6 +473,11 @@ fieldset.categories .input-group {
padding: 1px;
}
.fc-first .fc-day-header,
.fc-week .fc-first {
border-left: 0;
}
// Agenda Week View, Agenda Day View
.fc-agenda-days th {
@ -489,6 +496,7 @@ fieldset.categories .input-group {
text-align: right;
white-space: nowrap;
font-weight: normal;
border-left: 0;
}
.fc-week-number {
@ -636,7 +644,9 @@ fieldset.categories .input-group {
.fc-view-list,
.fc-view-table {
border: 1px solid #ccc;
border: 1px solid #ccc; // TODO: border
border-left: 0;
border-bottom: 0;
width: auto;
.fc-list-header,
@ -757,12 +767,33 @@ fieldset.categories .input-group {
}
#eventshow {
margin: 0;
@color-availability-unknown: #ddd;
@color-availability-free: #abd640;
@color-availability-busy: #e26569;
@color-availability-tentative: #8383fc;
@color-availability-out-of-office: #fbaa68;
.event-title {
font-size: 1.5rem;
font-weight: bold;
}
.event-location {
.overflow-ellipsis;
white-space: nowrap;
}
.event-description {
margin: 1rem 0;
}
.event-attendees {
margin-bottom: 1rem;
}
}
@color-availability-unknown: #bbb; // Larry: #ddd;
@color-availability-free: @color-success; // Larry: #abd640;
@color-availability-busy: @color-error; // Larry: #e26569;
@color-availability-tentative: #8383fc;
@color-availability-out-of-office: #fbaa68;
.availability {
span {
@ -809,48 +840,6 @@ fieldset.categories .input-group {
}
}
.calendar-scheduler {
.schedule-nav {
}
.schedule-range {
}
.slot-nav {
position: absolute;
top: 1rem;
right: 1rem;
}
.schedule-legend {
margin-top: 1rem;
}
}
#eventshow {
margin: 0;
.event-title {
font-size: 1.5rem;
font-weight: bold;
}
.event-location {
.overflow-ellipsis;
white-space: nowrap;
}
.event-description {
margin: 1rem 0;
}
.event-attendees {
margin-bottom: 1rem;
}
}
// fixes additional checkbox in Elastic's .datetime widget
.datetime {
label {
@ -868,3 +857,317 @@ fieldset.categories .input-group {
width: auto !important;
}
}
.calendar-scheduler {
.schedule-nav {
position: absolute;
right: 1rem;
top: 1rem;
button {
padding: 0 1rem;
}
}
.schedule-range {
width: 50%;
margin-top: 2rem;
}
.schedule-legend {
.attendee {
margin-right: .5rem;
}
}
.schedule-find-buttons {
margin-right: 1rem;
float: left;
button:first-child {
margin-right: .5rem;
}
.prev-slot:before {
content: @fa-var-chevron-left;
}
.next-slot:after {
&:extend(.font-icon-class);
content: @fa-var-chevron-right;
display: inline-block;
float: none;
margin-right: 0;
}
}
.schedule-options {
line-height: 2.8;
label {
vertical-align: middle;
}
}
.attendees-list {
position: relative;
a.attendee-role-toggle {
position: absolute;
left: 0;
display: inline-block;
width: 1em;
cursor: pointer;
}
div.attendee {
border-top: 1px solid @color-list-border;
line-height: 1.7rem;
height: 1.8rem;
}
.attendee {
white-space: nowrap;
&.spacer {
height: 10px;
}
&.loading:before {
.animated-icon-class;
.font-icon-solid(@fa-var-circle-notch);
}
&.total {
font-weight: bold;
}
&.total,
&.spacer {
&:before {
display: none;
}
}
}
}
.schedule-table {
table-layout: fixed;
th {
border-top: 0;
}
td.attendees {
width: 25%;
overflow: hidden;
border-top: 0;
.attendees-list {
border-bottom: 1px solid @color-table-border;
}
}
td.times {
width: auto;
border-top: 0;
table {
margin: 0;
}
td {
height: 1.8rem;
border-top: 1px solid @color-list-border;
}
}
div.scroll {
position: relative;
overflow: auto;
}
.timesheader {
height: 1.4rem;
border-top: 1px solid @color-table-border;
}
.boxtitle {
margin: 0;
padding: 0;
font-size: 1rem;
font-weight: bold;
padding-top: .5rem;
line-height: 2;
}
td {
padding: 4px;
}
tbody td {
padding: 0;
div {
height: 100%;
}
}
tr.spacer td {
padding: 0;
height: 10px;
}
tr.times td {
cursor: pointer;
min-width: 30px;
font-size: .7rem;
text-align: center;
color: @color-link;
height: 1.4rem;
padding: 0 .25rem;
vertical-align: middle;
border-top: 1px solid @color-table-border;
border-left: 1px solid @color-list-border;
}
.fbcontent {
td {
border-left: 1px solid @color-list-border;
}
&:last-child td {
border-bottom: 1px solid @color-table-border;
}
}
div.unknown {
background-color: @color-availability-unknown;
}
div.free {
background-color: @color-availability-free;
}
div.busy {
background-color: @color-availability-busy;
}
div.tentative {
background-color: @color-availability-tentative;
}
div.out-of-office {
background-color: @color-availability-out-of-office;
}
div.all-busy,
div.all-tentative,
div.all-out-of-office {
overflow: hidden;
// This span imitates a slanting line across the parent element
span {
display: block;
width: 200%;
height: 200%;
border: 1px solid #fff;
background: darken(@color-availability-busy, 10%);
transform: rotate(33deg) translate(10%);
}
&.w10 span {
display: none;
}
&.w20 span,
&.w25 span {
transform: rotate(10deg) translate(10%);
}
&.w30 span {
transform: rotate(15deg) translate(10%);
}
&.w40 span {
transform: rotate(20deg) translate(10%);
}
&.w70 span,
&.w75 span {
transform: rotate(42deg) translate(13%);
}
&.w80 span {
transform: rotate(48deg) translate(15%);
}
&.w90 span {
transform: rotate(52deg) translate(18%);
}
&.w100 span {
transform: rotate(55deg) translate(20%, 15%);
}
}
div.all-tentative span {
background: darken(@color-availability-tentative, 10%);
}
div.all-out-of-office span {
background: darken(@color-availability-out-of-office, 10%);
}
}
}
#schedule-event-time {
position: absolute;
opacity: .75;
border-radius: .3rem;
cursor: move;
border: 2px solid @color-black-shade-text;
background-color: @color-black-shade-bg;
}
.resources-dialog {
display: flex;
height: 100%;
.resource-selection {
flex: 4;
border: 1px solid @color-layout-border;
min-width: 300px;
justify-content: center;
.header {
border-bottom: 1px solid @color-layout-border;
display: flex;
background-color: @color-layout-header-background;
font-size: @layout-header-font-size;
font-weight: bold;
line-height: @layout-header-height;
height: @layout-header-height;
min-height: @layout-header-height;
padding: 0 .25em;
position: relative; // for absolute positioning of searchbar
overflow: hidden;
white-space: nowrap;
}
}
.resource-content {
flex: 10;
display: flex;
flex-direction: column;
margin-left: 1em;
}
.listing li.resource > a {
color: @color-font;
&:before {
&:extend(.font-icon-class);
content: @fa-var-cube;
}
}
}

View file

@ -158,59 +158,58 @@
}
.attendee {
&:after {
&:before {
&:extend(.font-icon-class);
content: @fa-var-question-circle;
display: inline;
float: none;
font-size: 1em;
margin-left: .3rem;
color: @color-black-shade-text;
}
&.req-participant:after {
&.req-participant:before {
content: @fa-var-user;
}
&.opt-participant:after {
&.opt-participant:before {
.font-icon-regular(@fa-var-user);
}
&.non-participant:after {
&.non-participant:before {
content: @fa-var-user;
color: #999;
color: #ccc;
}
&.chair:after {
&.chair:before {
content: @fa-var-user; // todo
color: @color-warning;
}
&.completed:after,
&.accepted:after {
&.completed:before,
&.accepted:before {
content: @fa-var-check-circle;
color: @color-success;
}
&.declined:after {
&.declined:before {
content: @fa-var-ban;
color: @color-error;
}
&.tentative:after {
&.tentative:before {
content: @fa-var-check-circle;
color: @color-warning;
}
&.delegated:after {
&.delegated:before {
content: @fa-var-share;
}
&.organizer:after {
&.organizer:before {
content: @fa-var-briefcase; // TODO: better icon
}
&.in-process:after {
&.in-process:before {
content: @fa-var-cog;
}
}
@ -221,10 +220,6 @@
}
}
#edit-attendees-legend {
margin-top: 1rem;
}
.edit-attendees-table {
width: 100%;