VulpesOS/gaia/apps/calendar/js/views/single_day.js
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

219 lines
6.3 KiB
JavaScript
Executable file

/* global IntlHelper */
define(function(require, exports, module) {
'use strict';
var Overlap = require('utils/overlap');
var buildElement = require('utils/dom').buildElement;
var core = require('core');
var isSameDate = require('common/calc').isSameDate;
var relativeDuration = require('common/calc').relativeDuration;
var relativeOffset = require('common/calc').relativeOffset;
var spanOfDay = require('common/calc').spanOfDay;
var template = require('templates/multi_day');
var _id = 0;
IntlHelper.define('longDateFormat', 'datetime', {
weekday: 'long',
day: 'numeric',
month: 'long',
year: 'numeric'
});
function SingleDay(config) {
this.date = config.date;
this._hourHeight = config.hourHeight;
this._daysHolder = config.daysHolder;
this._allDayIcon = config.allDayIcon;
this._alldaysHolder = config.alldaysHolder;
this._oneDayLabelFormat = config.oneDayLabelFormat;
this._render = this._render.bind(this);
this._instanceID = _id++;
this.overlaps = new Overlap();
}
module.exports = SingleDay;
SingleDay.prototype = {
_isActive: false,
_borderWidth: 0.1,
_attached: false,
_observer: null,
setup: function() {
this.day = buildElement(template.day.render({
date: this.date
}));
this.allday = buildElement(template.allday.render({
date: this.date,
id: this._instanceID
}));
this._dayName = this.allday.querySelector('.md__day-name');
this._alldayEvents = this.allday.querySelector('.md__allday-events');
this._updateDayName();
this.onactive();
},
_updateDayName: function() {
// we can't use [data-l10n-date-format] because format might change based
// on locale
this._dayName.textContent = this.date.toLocaleString(navigator.languages, {
weekday: 'short',
day: 'numeric'
});
},
handleEvent: function(evt) {
switch(evt.type) {
case 'localized':
this._updateDayName();
break;
}
},
append: function() {
this._daysHolder.appendChild(this.day);
this._alldaysHolder.appendChild(this.allday);
this._attached = true;
},
onactive: function() {
if (this._isActive) {
return;
}
this._observer = core.bridge.observeDay(this.date);
this._observer.listen(this._render);
window.addEventListener('localized', this);
this._isActive = true;
},
_render: function(records) {
this._alldayEvents.innerHTML = '';
records.allday.forEach(this._renderAlldayEvent, this);
this.overlaps.reset();
this.day.innerHTML = '';
records.basic.forEach(this._renderEvent, this);
if (this._alldayEvents.children.length > 0) {
// If there are all day events, the section acts as a listbox.
this._alldayEvents.setAttribute('role', 'listbox');
this._alldayEvents.setAttribute('aria-labelledby', this._allDayIcon.id +
' ' + this._dayName.id);
} else {
// If there are no all day events, the section acts as a create new all
// day event button.
this._alldayEvents.setAttribute('role', 'button');
this._alldayEvents.setAttribute('data-l10n-id', 'create-all-day-event');
this._alldayEvents.setAttribute('aria-describedby', this._dayName.id);
}
},
_renderEvent: function(record) {
var el = this._buildEventElement(record);
var {busytime} = record;
var {startDate, endDate} = busytime;
// we don't set the height on the template because we use the same markup
// for all day events as well
var duration = relativeDuration(this.date, startDate, endDate);
// we subtract border to keep a margin between consecutive events
var hei = duration * this._hourHeight - this._borderWidth;
// It doesn't make sense to set height less than 1 px, so we set it to 1 px
// in order to show events correctly.
hei = Math.max(1, hei);
el.style.height = hei + 'px';
if (duration < 1) {
el.classList.add('is-partial');
var size = '';
// we need to toggle layout if event lasts less than 20, 30 and 45min
if (duration < 0.3) {
size = 'micro';
} else if (duration < 0.5) {
size = 'tiny';
} else if (duration < 0.75) {
size = 'small';
}
if (size) {
el.classList.add('is-partial-' + size);
}
}
var offset = relativeOffset(this.date, startDate);
el.style.top = (offset * this._hourHeight) + 'px';
this.overlaps.add(busytime, el);
this.day.appendChild(el);
},
_buildEventElement: function(record) {
var {title, location, alarms} = record.event.remote;
var {startDate, endDate, _id} = record.busytime;
// screen reader should be aware if the event spans multiple dates and also
// know the event duration without having the open it
var labelFormat = isSameDate(startDate, endDate) ? this._oneDayLabelFormat :
'event-multiple-day-duration';
var longDateFormat = IntlHelper.get('longDateFormat');
var shortTimeFormat = IntlHelper.get('shortTimeFormat');
var labelFormatArgs = JSON.stringify({
startDate: longDateFormat.format(startDate),
startTime: shortTimeFormat.format(startDate),
endDate: longDateFormat.format(endDate),
endTime: shortTimeFormat.format(endDate)
});
return buildElement(template.event.render({
id: _id,
instance: this._instanceID,
title: title,
location: location,
hasAlarms: alarms && alarms.length,
color: record.color,
labelFormat: labelFormat,
labelFormatArgs: labelFormatArgs
}));
},
_renderAlldayEvent: function(record) {
var el = this._buildEventElement(record);
el.classList.add('is-allday');
el.setAttribute('role', 'option');
this._alldayEvents.appendChild(el);
},
destroy: function() {
this.oninactive();
this._detach();
},
_detach: function() {
if (this._attached) {
this._daysHolder.removeChild(this.day);
this._alldaysHolder.removeChild(this.allday);
this._attached = false;
}
},
oninactive: function() {
if (!this._isActive) {
return;
}
this._observer.cancel();
window.removeEventListener('localized', this);
this._isActive = false;
},
setVisibleForScreenReader: function(visibleRange) {
var visible = visibleRange.contains(spanOfDay(this.date));
this.day.setAttribute('aria-hidden', !visible);
this.allday.setAttribute('aria-hidden', !visible);
}
};
});