VulpesOS/gaia/apps/calendar/js/views/month.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

241 lines
5.9 KiB
JavaScript
Executable file

define(function(require, exports, module) {
'use strict';
var Calc = require('common/calc');
var GestureDetector = require('shared/gesture_detector');
var SingleMonth = require('./single_month');
var View = require('view');
var core = require('core');
var dateFromId = Calc.dateFromId;
var monthStart = Calc.monthStart;
var performance = require('performance');
var router = require('router');
var timeObserver = require('time_observer');
// minimum difference between X and Y axis to be considered an horizontal swipe
var XSWIPE_OFFSET = window.innerWidth / 10;
function Month() {
View.apply(this, arguments);
this.frames = new Map();
window.addEventListener('localized', this);
this._setPresentDate = this._setPresentDate.bind(this);
}
module.exports = Month;
Month.prototype = {
__proto__: View.prototype,
SCALE: 'month',
selectors: {
element: '#month-view',
},
date: null,
/** @type {SingleMonth} */
currentFrame: null,
/**
* used to detect if dbltap happened on same date
* @type {DOMElement}
*/
_lastTarget: null,
/**
* store current, previous and next months
* we load them beforehand and keep on the cache to speed up swipes
* @type {Array<SingleMonth>}
*/
frames: null,
onactive: function() {
View.prototype.onactive.apply(this, arguments);
core.timeController.scale = this.SCALE;
if (this.currentFrame) {
this.currentFrame.activate();
}
},
_onswipe: function(data) {
// only move to a different month if it's an horizontal swipe
if (Math.abs(data.dy) > (Math.abs(data.dx) - XSWIPE_OFFSET)) {
return;
}
var dir = document.documentElement.dir === 'rtl' ? -1 : 1;
this._move(dir * data.dx < 0);
},
_onwheel: function(event) {
// mouse wheel is used for a10y
if (event.deltaMode !== event.DOM_DELTA_PAGE || event.deltaX === 0) {
return;
}
this._move(event.deltaX > 0);
},
_move: function(isNext) {
var controller = core.timeController;
var date = isNext ? this._nextTime() : this._previousTime();
// If we changed months, set the selected day to the 1st
controller.selectedDay = date;
controller.move(date);
},
_nextTime: function() {
return monthStart(this.date, 1);
},
_previousTime: function() {
return monthStart(this.date, -1);
},
_initEvents: function() {
this.controller = core.timeController;
this.element.addEventListener('swipe', this);
this.element.addEventListener('wheel', this);
this.controller.on('monthChange', this);
this.delegate(this.element, 'click', '.month-day', this);
this.delegate(this.element, 'dbltap', '.month-day', this);
timeObserver.on('day', this._setPresentDate);
this.gd = new GestureDetector(this.element);
this.gd.startDetecting();
},
handleEvent: function(e, target) {
switch (e.type) {
case 'swipe':
this._onswipe(e.detail);
break;
case 'wheel':
this._onwheel(e);
break;
case 'click':
var date = dateFromId(target.dataset.date);
this.controller.selectedDay = date;
break;
case 'dbltap':
// make sure we discard double taps that started on a different day
if (this._lastTarget === target) {
this._goToAddEvent();
}
break;
case 'monthChange':
this.changeDate(e.data[0]);
break;
case 'localized':
this.reconstruct();
break;
}
this._lastTarget = target;
},
_goToAddEvent: function(date) {
// slight delay to avoid tapping the elements inside the add event screen
setTimeout(() => {
// don't need to set the date since the first tap triggers a click that
// sets the timeController.selectedDay
router.go('/event/add/');
}, 50);
},
changeDate: function(time) {
this.date = monthStart(time);
if (this.currentFrame) {
this.currentFrame.deactivate();
}
this.currentFrame = this._getFrame(this.date);
this._trimFrames();
this._appendFrames();
this.currentFrame.activate();
},
_getFrame: function(date) {
var id = date.getTime();
var frame = this.frames.get(id);
if (!frame) {
frame = new SingleMonth({
date: date,
container: this.element
});
frame.create();
this.frames.set(id, frame);
}
return frame;
},
_trimFrames: function() {
if (this.frames.size <= 3) {
return;
}
// full month (we always keep previous/next months)
var delta = 31 * 24 * 60 * 60 * 1000;
this.frames.forEach((frame, ts) => {
var base = Number(this.date);
if (Math.abs(base - ts) > delta) {
frame.destroy();
this.frames.delete(ts);
}
});
},
_appendFrames: function() {
// sort elements by timestamp (key = timestamp) so DOM makes more sense
Array.from(this.frames.keys())
.sort((a, b) => a - b)
.forEach(key => this.frames.get(key).append());
},
_setPresentDate: function() {
var id = Calc.getDayId(new Date());
var presentDate = this.element.querySelector('[data-date="' + id + '"]');
var previousDate = this.element.querySelector('.present');
if (previousDate) {
previousDate.classList.remove('present');
previousDate.classList.add('past');
}
if (presentDate) {
presentDate.classList.add('present');
}
},
oninactive: function() {
View.prototype.oninactive.call(this);
if (this.currentFrame) {
this.currentFrame.deactivate();
}
},
onfirstseen: function() {
this._initEvents();
this.changeDate(this.controller.month);
performance.monthReady();
},
destroy: function() {
this.frames.forEach((frame, key) => {
this.frames.delete(key);
frame.destroy();
});
},
reconstruct: function() {
// Watch for changed value from transition of a locale to another
this.destroy();
this.changeDate(this.controller.month);
}
};
});