- 1 :
export { loader as CalendarLoader };
- 2 :
import { MelEnumerable } from '../classes/enum.js';
- 3 :
import { MainNav } from '../classes/main_nav.js';
- 4 :
import { DATE_TIME_FORMAT } from '../constants/constants.dates.js';
- 5 :
import { MelObject } from '../mel_object.js';
- 6 :
- 7 :
/**
- 8 :
* Sélecteur de la pastille agenda de la navigation principale
- 9 :
* @type {string}
- 10 :
*/
- 11 :
const SELECTOR_CLASS_ROUND_CALENDAR = `${CONST_JQUERY_SELECTOR_CLASS}calendar`;
- 12 :
- 13 :
/**
- 14 :
* @extends MelObject
- 15 :
* Classe qui gère le chargement de l'agenda côté serveur
- 16 :
*
- 17 :
* Donne des fonctions utiles pour charger ces données.
- 18 :
*/
- 19 :
class CalendarLoader extends MelObject {
- 20 :
constructor() {
- 21 :
super();
- 22 :
}
- 23 :
- 24 :
main() {
- 25 :
super.main();
- 26 :
- 27 :
this.timeout = null;
- 28 :
this.on_calendar_updated = new MelEvent();
- 29 :
- 30 :
const now = moment();
- 31 :
- 32 :
if (['bnum', 'webconf', 'chat'].includes(rcmail.env.task)) {
- 33 :
MainNav.try_add_round(
- 34 :
SELECTOR_CLASS_ROUND_CALENDAR,
- 35 :
mel_metapage.Ids.menu.badge.calendar,
- 36 :
).update_badge(
- 37 :
this.get_next_events_day(now, {}).count(),
- 38 :
mel_metapage.Ids.menu.badge.calendar,
- 39 :
);
- 40 :
}
- 41 :
- 42 :
if (
- 43 :
this.load(mel_metapage.Storage.last_calendar_update) !==
- 44 :
moment().format(CONST_DATE_FORMAT_BNUM) ||
- 45 :
this.load(mel_metapage.Storage.calendar_all_events) === null
- 46 :
) {
- 47 :
const force_refresh = true;
- 48 :
this.update_agenda_local_datas(force_refresh);
- 49 :
}
- 50 :
}
- 51 :
- 52 :
/**
- 53 :
* Vérifie si un jour se trouve entre 2 dates
- 54 :
* @param {moment} start Date de début
- 55 :
* @param {moment} end Date de fin
- 56 :
* @param {moment} day Jour de test
- 57 :
* @returns {boolean}
- 58 :
*/
- 59 :
is_date_okay(start, end, day) {
- 60 :
return (
- 61 :
(start <= day && day <= end) ||
- 62 :
(start >= day &&
- 63 :
moment(start).startOf('day').format() ===
- 64 :
moment(day).startOf('day').format() &&
- 65 :
day <= end)
- 66 :
);
- 67 :
}
- 68 :
- 69 :
/**
- 70 :
* Coupe une date de début et une date de fin si celles-ci dépassent le début ou la fin de journée
- 71 :
* @param {moment} start Date de début
- 72 :
* @param {moment} end Date de fin
- 73 :
* @param {moment} day Jour de test
- 74 :
* @returns {SplittedDate} Nouvelles dates de début et de fin si besoin.
- 75 :
*/
- 76 :
get_date_splitted(start, end, day) {
- 77 :
const o_start = start;
- 78 :
const o_end = end;
- 79 :
const start_of_day = moment(day).startOf('day');
- 80 :
const end_of_day = moment(day).endOf('day');
- 81 :
if (start < start_of_day) start = start_of_day;
- 82 :
if (end > end_of_day) end = end_of_day;
- 83 :
- 84 :
return new SplittedDate(start, end, o_start, o_end, day);
- 85 :
}
- 86 :
- 87 :
/**
- 88 :
* Récupère les évènements d'une journée
- 89 :
* @param {moment} day Jour
- 90 :
* @param {Array<Object> | null} loaded_events Si ce paramètre vaut null, les données seront chargés depuis le stockage local
- 91 :
* @returns {Array<Object>} Evènements
- 92 :
*/
- 93 :
get_from_day(day, loaded_events = null) {
- 94 :
let return_datas = [];
- 95 :
if (!day.toDate) day = moment(day).startOf('day');
- 96 :
- 97 :
const events = loaded_events ?? this.load_all_events();
- 98 :
- 99 :
if (events.length !== 0) {
- 100 :
return_datas = MelEnumerable.from(events)
- 101 :
.where((x) => this.is_date_okay(moment(x.start), moment(x.end), day))
- 102 :
.toArray();
- 103 :
}
- 104 :
- 105 :
return return_datas;
- 106 :
}
- 107 :
- 108 :
/**
- 109 :
* Récupère les prochains évènements d'une journée.
- 110 :
*
- 111 :
* La date ne doit pas être une date inférieur à aujourd'hui.
- 112 :
* @param {moment} day Date de traitement
- 113 :
* @param {Object} options Options de cette fonction
- 114 :
* @param {boolean} options.enumerable Si on récupère un énumerable ou non
- 115 :
* @param {Array<Object> | null} options.loaded_events Si ce paramètre vaut null, les données seront chargés depuis le stockage local
- 116 :
* @returns {MelEnumerable | Array<Object>}
- 117 :
*/
- 118 :
get_next_events_day(day, { enumerable = true, loaded_events = null }) {
- 119 :
const now = moment();
- 120 :
let enum_var = MelEnumerable.from(this.get_from_day(day, loaded_events))
- 121 :
.where(
- 122 :
(x) =>
- 123 :
moment(x.end) > now &&
- 124 :
x.free_busy !== CONST_EVENT_DISPO_FREE &&
- 125 :
x.free_busy !== CONST_EVENT_DISPO_TELEWORK,
- 126 :
)
- 127 :
.orderBy((x) => moment(x.start));
- 128 :
- 129 :
return enumerable ? enum_var : enum_var.toArray();
- 130 :
}
- 131 :
- 132 :
/**
- 133 :
* Charge les évènements depuis le stockage local.
- 134 :
* @returns {Array<Object>} Evènements
- 135 :
*/
- 136 :
load_all_events() {
- 137 :
return this.load(mel_metapage.Storage.calendar_all_events, []);
- 138 :
}
- 139 :
- 140 :
/**
- 141 :
* Charge les évènements depuis le stockage local.
- 142 :
*
- 143 :
* Si les évènements depuis le stockages local sont nuls, ils sont chargés depuis la base.
- 144 :
* @async
- 145 :
* @returns {Promise<Array<Object>>} Evènements
- 146 :
*/
- 147 :
async force_load_all_events_from_storage() {
- 148 :
let loaded = this.load(mel_metapage.Storage.calendar_all_events);
- 149 :
- 150 :
if (!loaded) {
- 151 :
const top = true;
- 152 :
await this.rcmail(top).triggerEvent(
- 153 :
mel_metapage.EventListeners.calendar_updated.get,
- 154 :
);
- 155 :
loaded = this.load_all_events();
- 156 :
}
- 157 :
- 158 :
return loaded;
- 159 :
}
- 160 :
- 161 :
/**
- 162 :
* Met à jours les données du stockage local depuis le serveur.
- 163 :
* @async
- 164 :
* @param {boolean} force Force la mise à jours des données depuis le serveur. Par défaut : `'random'`
- 165 :
* @returns {Promise<Array<Objet> | null>} Evènements
- 166 :
*/
- 167 :
async update_agenda_local_datas(force = CONST_CALENDAR_UPDATED_DEFAULT) {
- 168 :
const isTop = window === top;
- 169 :
const url = mel_metapage.Functions.url(
- 170 :
PLUGIN_MEL_METAPAGE,
- 171 :
ACTION_MEL_METAPAGE_CALENDAR_LOAD_EVENT,
- 172 :
{
- 173 :
force,
- 174 :
source: mceToRcId(rcmail.env.username),
- 175 :
start: this._dateNow(new Date()),
- 176 :
end: this._dateNow(
- 177 :
new Date(
- 178 :
new Date().getFullYear(),
- 179 :
new Date().getMonth(),
- 180 :
new Date().getDate() + 7,
- 181 :
),
- 182 :
),
- 183 :
last: moment(
- 184 :
this.load(mel_metapage.Storage.last_calendar_update),
- 185 :
CONST_DATE_FORMAT_BNUM,
- 186 :
).unix(),
- 187 :
},
- 188 :
);
- 189 :
- 190 :
this.rcmail(!isTop).triggerEvent(
- 191 :
mel_metapage.EventListeners.calendar_updated.before,
- 192 :
);
- 193 :
this.trigger_event(mel_metapage.EventListeners.calendar_updated.before);
- 194 :
- 195 :
let events = null;
- 196 :
await this.http_call({
- 197 :
url,
- 198 :
on_success: (datas) => (events = this._on_success(datas, isTop)),
- 199 :
on_error: (...args) => {
- 200 :
console.error(...args);
- 201 :
},
- 202 :
type: 'GET',
- 203 :
});
- 204 :
- 205 :
return events;
- 206 :
}
- 207 :
- 208 :
_on_success(datas, ...args) {
- 209 :
const { forced, events, encoded } = JSON.parse(datas);
- 210 :
const [isTop] = args;
- 211 :
const isForcedRefresh = forced === true;
- 212 :
const haveNewDatas =
- 213 :
(encoded && events !== EMPTY_STRING && events !== EMPTY_ARRAY_STRING) ||
- 214 :
(!encoded && events.length !== 0);
- 215 :
datas = null;
- 216 :
- 217 :
let loadedEvents;
- 218 :
- 219 :
if (isForcedRefresh) loadedEvents = [];
- 220 :
else if (haveNewDatas)
- 221 :
loadedEvents = this.load(mel_metapage.Storage.calendar_all_events, []);
- 222 :
- 223 :
if (haveNewDatas || isForcedRefresh) {
- 224 :
this.save(
- 225 :
CalendarLoader.KEY_LAST_REFRESH,
- 226 :
moment().format(DATE_TIME_FORMAT),
- 227 :
);
- 228 :
- 229 :
if (haveNewDatas) {
- 230 :
let index;
- 231 :
for (const current of this._generator_selected_events(
- 232 :
encoded ? JSON.parse(events) : events,
- 233 :
)) {
- 234 :
index = this._getEventIndex(current.id, loadedEvents);
- 235 :
- 236 :
if (NO_INDEX === index) loadedEvents.push(current);
- 237 :
else loadedEvents[index] = current;
- 238 :
}
- 239 :
}
- 240 :
- 241 :
const now = moment().startOf('day');
- 242 :
let all_events = MelEnumerable.from(loadedEvents)
- 243 :
.where((x) => x !== null)
- 244 :
.orderBy((x) => x.order)
- 245 :
.then((x) => moment(x.start));
- 246 :
all_events = this._events_remove_moment(all_events).toArray();
- 247 :
this.save(mel_metapage.Storage.calendar_all_events, all_events);
- 248 :
this.save(
- 249 :
mel_metapage.Storage.last_calendar_update,
- 250 :
moment().format(CONST_DATE_FORMAT_BNUM),
- 251 :
);
- 252 :
- 253 :
const next_events = this.get_next_events_day(now, {
- 254 :
loaded_events: all_events,
- 255 :
});
- 256 :
MainNav.try_add_round(
- 257 :
SELECTOR_CLASS_ROUND_CALENDAR,
- 258 :
mel_metapage.Ids.menu.badge.calendar,
- 259 :
).update_badge(next_events.count(), mel_metapage.Ids.menu.badge.calendar);
- 260 :
- 261 :
this.rcmail(!isTop).triggerEvent(
- 262 :
mel_metapage.EventListeners.calendar_updated.after,
- 263 :
);
- 264 :
this.trigger_event(mel_metapage.EventListeners.calendar_updated.after);
- 265 :
- 266 :
this._set_timeout(next_events);
- 267 :
- 268 :
this.on_calendar_updated.call();
- 269 :
- 270 :
return all_events;
- 271 :
}
- 272 :
}
- 273 :
- 274 :
_set_timeout(next_events) {
- 275 :
if (
- 276 :
['all', 'page'].includes(
- 277 :
rcmail.env['mel_metapage.tab.notification_style'],
- 278 :
) &&
- 279 :
next_events.any()
- 280 :
) {
- 281 :
if (this.timeout) clearTimeout(this.timeout);
- 282 :
- 283 :
this.timeout = setTimeout(
- 284 :
() => {
- 285 :
const now = moment();
- 286 :
- 287 :
MainNav.try_add_round(
- 288 :
SELECTOR_CLASS_ROUND_CALENDAR,
- 289 :
mel_metapage.Ids.menu.badge.calendar,
- 290 :
).update_badge(
- 291 :
next_events.count(),
- 292 :
mel_metapage.Ids.menu.badge.calendar,
- 293 :
);
- 294 :
- 295 :
window?.update_notification_title?.();
- 296 :
- 297 :
next_events = this.get_next_events_day(now, {
- 298 :
loaded_events: next_events.toArray(),
- 299 :
});
- 300 :
this.timeout = null;
- 301 :
this._set_timeout(next_events);
- 302 :
},
- 303 :
Math.abs(moment() - moment(next_events.first().end)),
- 304 :
);
- 305 :
}
- 306 :
}
- 307 :
- 308 :
_getEventIndex(id, events) {
- 309 :
for (let index = 0, len = events.length; index < len; ++index) {
- 310 :
if (events[index].id === id) return index;
- 311 :
}
- 312 :
- 313 :
return NO_INDEX;
- 314 :
}
- 315 :
- 316 :
_dateNow(date) {
- 317 :
let set = date;
- 318 :
let getDate = set.getDate().toString();
- 319 :
// eslint-disable-next-line eqeqeq
- 320 :
if (getDate.length == 1) {
- 321 :
//example if 1 change to 01
- 322 :
getDate = '0' + getDate;
- 323 :
}
- 324 :
let getMonth = (set.getMonth() + 1).toString();
- 325 :
// eslint-disable-next-line eqeqeq
- 326 :
if (getMonth.length == 1) {
- 327 :
getMonth = '0' + getMonth;
- 328 :
}
- 329 :
let getYear = set.getFullYear().toString();
- 330 :
let dateNow = getYear + '-' + getMonth + '-' + getDate + 'T00:00:00';
- 331 :
return dateNow;
- 332 :
}
- 333 :
- 334 :
*_generator_selected_events(events) {
- 335 :
const now = moment().startOf(CONST_DATE_START_OF_DAY);
- 336 :
const parse = window?.cal?.parseISO8601 ?? ((item) => item);
- 337 :
const user_id = mceToRcId(rcmail.env.username);
- 338 :
- 339 :
for (let index = 0, element, len = events.length; index < len; ++index) {
- 340 :
element = events[index];
- 341 :
- 342 :
if (
- 343 :
user_id !== element.calendar ||
- 344 :
CONST_EVENT_ATTENDEE_STATUS_CANCELLED === element.status
- 345 :
)
- 346 :
continue;
- 347 :
- 348 :
if (
- 349 :
element.allday !== undefined &&
- 350 :
element.allday !== null &&
- 351 :
(element.allDay === undefined || element.allDay === null)
- 352 :
)
- 353 :
element.allDay = element.allday;
- 354 :
element.order = element.allDay ? 0 : 1;
- 355 :
- 356 :
if (STRING === typeof element.end)
- 357 :
element.end = moment(parse(element.end));
- 358 :
else if (!!element.end.date && STRING === typeof element.end.date)
- 359 :
element.end = moment(element.end.date);
- 360 :
- 361 :
if (STRING === typeof element.start)
- 362 :
element.start = moment(parse(element.start));
- 363 :
else if (!!element.start.date && STRING === typeof element.start.date)
- 364 :
element.start = moment(element.start.date);
- 365 :
- 366 :
if (element.end < now) continue;
- 367 :
- 368 :
if (element.allDay) {
- 369 :
element.end = element.end.startOf(CONST_DATE_START_OF_DAY);
- 370 :
- 371 :
if (
- 372 :
element.end.format(CONST_DATE_FORMAT_EN) ===
- 373 :
now.format(CONST_DATE_FORMAT_EN) &&
- 374 :
element.start
- 375 :
.startOf(CONST_DATE_START_OF_DAY)
- 376 :
.format(CONST_DATE_FORMAT_EN) !==
- 377 :
element.end.format(CONST_DATE_FORMAT_EN)
- 378 :
) {
- 379 :
continue;
- 380 :
} else {
- 381 :
element.start = element.start.startOf(CONST_DATE_START_OF_DAY);
- 382 :
//element.end = element.end.startOf(CONST_DATE_START_OF_DAY);
- 383 :
}
- 384 :
}
- 385 :
- 386 :
if (element?.recurrence?.EXCEPTIONS) element.recurrence.EXCEPTIONS = [];
- 387 :
- 388 :
yield element;
- 389 :
}
- 390 :
}
- 391 :
- 392 :
_events_remove_moment(events) {
- 393 :
return events.select((x) => {
- 394 :
if (typeof x.start !== 'string') x.start = x.start.format();
- 395 :
if (typeof x.end !== 'string') x.end = x.end.format();
- 396 :
return x;
- 397 :
});
- 398 :
}
- 399 :
}
- 400 :
- 401 :
/**
- 402 :
* Clé de la dernière mise à jours de l'agenda
- 403 :
* @type {string}
- 404 :
* @static
- 405 :
* @readonly
- 406 :
* @default 'agenda_last_refresh'
- 407 :
*/
- 408 :
CalendarLoader.KEY_LAST_REFRESH = 'agenda_last_refresh';
- 409 :
- 410 :
/**
- 411 :
* Représente une date coupée.
- 412 :
*/
- 413 :
class SplittedDate {
- 414 :
/**
- 415 :
* Constructeur de la classe
- 416 :
* @param {moment} start Nouvelle date coupée
- 417 :
* @param {moment} end Nouvelle date coupée
- 418 :
* @param {moment} original_start Date original
- 419 :
* @param {moment} original_end Date original
- 420 :
* @param {moment} day Jour de traitement
- 421 :
*/
- 422 :
constructor(start, end, original_start, original_end, day) {
- 423 :
this._init()._setup(start, end, original_start, original_end, day);
- 424 :
}
- 425 :
- 426 :
_init() {
- 427 :
const now = moment();
- 428 :
this.start = now;
- 429 :
this.end = now;
- 430 :
this.original = {
- 431 :
start: now,
- 432 :
end: now,
- 433 :
};
- 434 :
this.day = now;
- 435 :
- 436 :
return this;
- 437 :
}
- 438 :
- 439 :
_setup(start, end, original_start, original_end, day) {
- 440 :
Object.defineProperties(this, {
- 441 :
start: {
- 442 :
get: function () {
- 443 :
return start;
- 444 :
},
- 445 :
configurable: true,
- 446 :
},
- 447 :
end: {
- 448 :
get: function () {
- 449 :
return end;
- 450 :
},
- 451 :
configurable: true,
- 452 :
},
- 453 :
original: {
- 454 :
get: function () {
- 455 :
return {
- 456 :
start: original_start,
- 457 :
end: original_end,
- 458 :
};
- 459 :
},
- 460 :
configurable: true,
- 461 :
},
- 462 :
day: {
- 463 :
get: function () {
- 464 :
return day.startOf('day');
- 465 :
},
- 466 :
configurable: true,
- 467 :
},
- 468 :
});
- 469 :
- 470 :
return this;
- 471 :
}
- 472 :
}
- 473 :
- 474 :
/**
- 475 :
* @typedef {Object} CalendarLoaderWrapper
- 476 :
* @property {CalendarLoader} Instance Instance de CalendarLoader
- 477 :
* @property {typeof CalendarLoader} Class Classe de CalendarLoader
- 478 :
*
- 479 :
*/
- 480 :
- 481 :
/**
- 482 :
* Instance de CalendarLoader
- 483 :
* @type {CalendarLoaderWrapper}
- 484 :
*/
- 485 :
let loader = {
- 486 :
_instance: null,
- 487 :
Instance: null,
- 488 :
Class: null,
- 489 :
};
- 490 :
- 491 :
Object.defineProperties(loader, {
- 492 :
Instance: {
- 493 :
get: function () {
- 494 :
if (!loader._instance) loader._instance = new CalendarLoader();
- 495 :
- 496 :
return loader._instance;
- 497 :
},
- 498 :
configurable: true,
- 499 :
},
- 500 :
Class: {
- 501 :
get() {
- 502 :
return CalendarLoader;
- 503 :
},
- 504 :
},
- 505 :
});