661 строка
17 KiB
JavaScript
661 строка
17 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
/* global cal, calendarNavigationBar, CalendarFilteredViewMixin, calFilterProperties, currentView,
|
|
gCurrentMode, MozElements, MozXULElement, Services, toggleOrientation */
|
|
|
|
/* eslint-enable valid-jsdoc */
|
|
|
|
"use strict";
|
|
|
|
// Wrap in a block to prevent leaking to window scope.
|
|
{
|
|
const { XPCOMUtils } = ChromeUtils.importESModule("resource://gre/modules/XPCOMUtils.sys.mjs");
|
|
|
|
/**
|
|
* Calendar observer for calendar view elements. Used in CalendarBaseView class.
|
|
*
|
|
* @implements {calIObserver}
|
|
* @implements {calICompositeObserver}
|
|
* @implements {calIAlarmServiceObserver}
|
|
*/
|
|
class CalendarViewObserver {
|
|
/**
|
|
* Constructor for CalendarViewObserver.
|
|
*
|
|
* @param {CalendarBaseView} calendarView - A calendar view.
|
|
*/
|
|
constructor(calendarView) {
|
|
this.calView = calendarView.calICalendarView;
|
|
}
|
|
|
|
QueryInterface = ChromeUtils.generateQI(["calIAlarmServiceObserver"]);
|
|
|
|
// calIAlarmServiceObserver
|
|
|
|
onAlarm(alarmItem) {
|
|
this.calView.flashAlarm(alarmItem, false);
|
|
}
|
|
|
|
onNotification() {}
|
|
|
|
onRemoveAlarmsByItem(item) {
|
|
// Stop the flashing for the item.
|
|
this.calView.flashAlarm(item, true);
|
|
}
|
|
|
|
onRemoveAlarmsByCalendar(calendar) {
|
|
// Stop the flashing for all items of this calendar.
|
|
for (const key in this.calView.mFlashingEvents) {
|
|
const item = this.calView.mFlashingEvents[key];
|
|
if (item.calendar.id == calendar.id) {
|
|
this.calView.flashAlarm(item, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
onAlarmsLoaded() {}
|
|
|
|
// End calIAlarmServiceObserver
|
|
}
|
|
|
|
/**
|
|
* Abstract base class for calendar view elements (day, week, multiweek, month).
|
|
*
|
|
* @implements {calICalendarView}
|
|
* @abstract
|
|
*/
|
|
class CalendarBaseView extends CalendarFilteredViewMixin(MozXULElement) {
|
|
/**
|
|
* Whether the view has been initialized.
|
|
*
|
|
* @type {boolean}
|
|
*/
|
|
#isInitialized = false;
|
|
|
|
connectedCallback() {
|
|
if (this.delayConnectedCallback() || this.hasConnected) {
|
|
return;
|
|
}
|
|
this.hasConnected = true;
|
|
|
|
// For some unknown reason, `console.createInstance` isn't available when
|
|
// `ensureInitialized` runs.
|
|
this.mLog = console.createInstance({
|
|
prefix: `calendar.baseview (${this.constructor.name})`,
|
|
maxLogLevel: "Warn",
|
|
maxLogLevelPref: "calendar.baseview.loglevel",
|
|
});
|
|
|
|
this.mSelectedItems = [];
|
|
}
|
|
|
|
ensureInitialized() {
|
|
if (this.#isInitialized) {
|
|
return;
|
|
}
|
|
this.#isInitialized = true;
|
|
|
|
this.calICalendarView = this.getCustomInterfaceCallback(Ci.calICalendarView);
|
|
|
|
this.addEventListener("move", event => {
|
|
this.moveView(event.detail);
|
|
});
|
|
|
|
this.addEventListener("keypress", event => {
|
|
switch (event.key) {
|
|
case "PageUp":
|
|
this.moveView(-1);
|
|
break;
|
|
case "PageDown":
|
|
this.moveView(1);
|
|
break;
|
|
}
|
|
});
|
|
|
|
this.addEventListener("wheel", event => {
|
|
const pixelThreshold = 150;
|
|
|
|
if (event.shiftKey && Services.prefs.getBoolPref("calendar.view.mousescroll", true)) {
|
|
let deltaView = 0;
|
|
if (event.deltaMode == event.DOM_DELTA_LINE) {
|
|
if (event.deltaY != 0) {
|
|
deltaView = event.deltaY < 0 ? -1 : 1;
|
|
}
|
|
} else if (event.deltaMode == event.DOM_DELTA_PIXEL) {
|
|
this.mPixelScrollDelta += event.deltaY;
|
|
if (this.mPixelScrollDelta > pixelThreshold) {
|
|
deltaView = 1;
|
|
this.mPixelScrollDelta = 0;
|
|
} else if (this.mPixelScrollDelta < -pixelThreshold) {
|
|
deltaView = -1;
|
|
this.mPixelScrollDelta = 0;
|
|
}
|
|
}
|
|
|
|
if (deltaView != 0) {
|
|
this.moveView(deltaView);
|
|
}
|
|
event.preventDefault();
|
|
}
|
|
});
|
|
|
|
this.addEventListener("MozRotateGesture", event => {
|
|
// Threshold for the minimum and maximum angle we should accept
|
|
// rotation for. 90 degrees minimum is most logical, but 45 degrees
|
|
// allows you to rotate with one hand.
|
|
const MIN_ROTATE_ANGLE = 45;
|
|
const MAX_ROTATE_ANGLE = 180;
|
|
|
|
const absval = Math.abs(event.delta);
|
|
if (this.supportsRotation && absval >= MIN_ROTATE_ANGLE && absval < MAX_ROTATE_ANGLE) {
|
|
toggleOrientation();
|
|
event.preventDefault();
|
|
}
|
|
});
|
|
|
|
this.addEventListener("MozMagnifyGestureStart", () => {
|
|
this.mMagnifyAmount = 0;
|
|
});
|
|
|
|
this.addEventListener("MozMagnifyGestureUpdate", event => {
|
|
// Threshold as to how much magnification causes the zoom to happen.
|
|
const THRESHOLD = 30;
|
|
|
|
if (this.supportsZoom) {
|
|
this.mMagnifyAmount += event.delta;
|
|
|
|
if (this.mMagnifyAmount > THRESHOLD) {
|
|
this.zoomOut();
|
|
this.mMagnifyAmount = 0;
|
|
} else if (this.mMagnifyAmount < -THRESHOLD) {
|
|
this.zoomIn();
|
|
this.mMagnifyAmount = 0;
|
|
}
|
|
event.preventDefault();
|
|
}
|
|
});
|
|
|
|
this.addEventListener("MozSwipeGesture", event => {
|
|
if (
|
|
(event.direction == SimpleGestureEvent.DIRECTION_UP && !this.rotated) ||
|
|
(event.direction == SimpleGestureEvent.DIRECTION_LEFT && this.rotated)
|
|
) {
|
|
this.moveView(-1);
|
|
} else if (
|
|
(event.direction == SimpleGestureEvent.DIRECTION_DOWN && !this.rotated) ||
|
|
(event.direction == SimpleGestureEvent.DIRECTION_RIGHT && this.rotated)
|
|
) {
|
|
this.moveView(1);
|
|
}
|
|
});
|
|
|
|
this.mRangeStartDate = null;
|
|
this.mRangeEndDate = null;
|
|
|
|
this.mWorkdaysOnly = false;
|
|
|
|
this.mController = null;
|
|
|
|
this.mStartDate = null;
|
|
this.mEndDate = null;
|
|
|
|
this.mTasksInView = false;
|
|
this.mShowCompleted = false;
|
|
|
|
this.mDisplayDaysOff = true;
|
|
this.mDaysOffArray = [0, 6];
|
|
|
|
this.mTimezone = null;
|
|
this.mFlashingEvents = {};
|
|
|
|
this.mDropShadowsLength = null;
|
|
|
|
this.mShadowOffset = null;
|
|
this.mDropShadows = null;
|
|
|
|
this.mMagnifyAmount = 0;
|
|
this.mPixelScrollDelta = 0;
|
|
|
|
this.mViewStart = null;
|
|
this.mViewEnd = null;
|
|
|
|
this.mToggleStatus = 0;
|
|
|
|
this.mToggleStatusFlag = {
|
|
WorkdaysOnly: 1,
|
|
TasksInView: 2,
|
|
ShowCompleted: 4,
|
|
};
|
|
|
|
this.mTimezoneObserver = {
|
|
observe: () => {
|
|
this.timezone = cal.dtz.defaultTimezone;
|
|
this.refreshView();
|
|
|
|
this.updateTimeIndicatorPosition();
|
|
},
|
|
};
|
|
|
|
this.mPrefObserver = {
|
|
calView: this.calICalendarView,
|
|
|
|
observe(subj, topic, pref) {
|
|
this.calView.handlePreference(subj, topic, pref);
|
|
},
|
|
};
|
|
|
|
this.mObserver = new CalendarViewObserver(this);
|
|
|
|
const isChecked = id => document.getElementById(id).getAttribute("checked") == "true";
|
|
|
|
this.workdaysOnly = isChecked("calendar_toggle_workdays_only_command");
|
|
this.tasksInView = isChecked("calendar_toggle_tasks_in_view_command");
|
|
this.rotated = isChecked("calendar_toggle_orientation_command");
|
|
this.showCompleted = isChecked("calendar_toggle_show_completed_in_view_command");
|
|
|
|
this.mTimezone = cal.dtz.defaultTimezone;
|
|
const alarmService = Cc["@mozilla.org/calendar/alarm-service;1"].getService(
|
|
Ci.calIAlarmService
|
|
);
|
|
|
|
alarmService.addObserver(this.mObserver);
|
|
|
|
this.setAttribute("type", this.type);
|
|
|
|
window.addEventListener("viewresize", () => {
|
|
if (gCurrentMode == "calendar" && this.isVisible()) {
|
|
this.onResize();
|
|
}
|
|
});
|
|
window.addEventListener("uifontsizechange", () => {
|
|
this.onFontSizeChange();
|
|
});
|
|
|
|
// Add a preference observer to monitor changes.
|
|
Services.prefs.addObserver("calendar.", this.mPrefObserver);
|
|
Services.obs.addObserver(this.mTimezoneObserver, "defaultTimezoneChanged");
|
|
|
|
this.updateDaysOffPrefs();
|
|
this.updateTimeIndicatorPosition();
|
|
|
|
// Remove observers on window unload.
|
|
window.addEventListener(
|
|
"unload",
|
|
() => {
|
|
alarmService.removeObserver(this.mObserver);
|
|
|
|
Services.prefs.removeObserver("calendar.", this.mPrefObserver);
|
|
Services.obs.removeObserver(this.mTimezoneObserver, "defaultTimezoneChanged");
|
|
},
|
|
{ once: true }
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Handle resizing by adjusting the view to the new size.
|
|
*/
|
|
onResize() {
|
|
// Child classes should provide the implementation.
|
|
throw new Error(this.constructor.name + ".onResize not implemented");
|
|
}
|
|
|
|
/**
|
|
* Called when the font size of the UI changes. Triggers a resize if the
|
|
* view is active.
|
|
*/
|
|
onFontSizeChange() {
|
|
if (gCurrentMode == "calendar" && this.isVisible()) {
|
|
this.onResize();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Whether the view has been initialized.
|
|
*
|
|
* @returns {boolean} - True if the view has been initialized, otherwise
|
|
* false.
|
|
*/
|
|
get isInitialized() {
|
|
return this.#isInitialized;
|
|
}
|
|
|
|
get type() {
|
|
const typelist = this.id.split("-");
|
|
return typelist[0];
|
|
}
|
|
|
|
set rotated(rotated) {
|
|
this.setAttribute("orient", rotated ? "horizontal" : "vertical");
|
|
this.toggleAttribute("rotated", rotated);
|
|
}
|
|
|
|
get rotated() {
|
|
return this.getAttribute("orient") == "horizontal";
|
|
}
|
|
|
|
get supportsRotation() {
|
|
return false;
|
|
}
|
|
|
|
set displayDaysOff(displayDaysOff) {
|
|
this.mDisplayDaysOff = displayDaysOff;
|
|
}
|
|
|
|
get displayDaysOff() {
|
|
return this.mDisplayDaysOff;
|
|
}
|
|
|
|
set controller(controller) {
|
|
this.mController = controller;
|
|
}
|
|
|
|
get controller() {
|
|
return this.mController;
|
|
}
|
|
|
|
set daysOffArray(daysOffArray) {
|
|
this.mDaysOffArray = daysOffArray;
|
|
}
|
|
|
|
get daysOffArray() {
|
|
return this.mDaysOffArray;
|
|
}
|
|
|
|
set tasksInView(tasksInView) {
|
|
this.mTasksInView = tasksInView;
|
|
this.updateItemType();
|
|
}
|
|
|
|
get tasksInView() {
|
|
return this.mTasksInView;
|
|
}
|
|
|
|
set showCompleted(showCompleted) {
|
|
this.mShowCompleted = showCompleted;
|
|
this.updateItemType();
|
|
}
|
|
|
|
get showCompleted() {
|
|
return this.mShowCompleted;
|
|
}
|
|
|
|
set timezone(timezone) {
|
|
this.mTimezone = timezone;
|
|
}
|
|
|
|
get timezone() {
|
|
return this.mTimezone;
|
|
}
|
|
|
|
set workdaysOnly(workdaysOnly) {
|
|
this.mWorkdaysOnly = workdaysOnly;
|
|
}
|
|
|
|
get workdaysOnly() {
|
|
return this.mWorkdaysOnly;
|
|
}
|
|
|
|
get supportsWorkdaysOnly() {
|
|
return true;
|
|
}
|
|
|
|
get supportsZoom() {
|
|
return false;
|
|
}
|
|
|
|
get selectionObserver() {
|
|
return this.mSelectionObserver;
|
|
}
|
|
|
|
get startDay() {
|
|
return this.startDate;
|
|
}
|
|
|
|
get endDay() {
|
|
return this.endDate;
|
|
}
|
|
|
|
get supportDisjointDates() {
|
|
return false;
|
|
}
|
|
|
|
get hasDisjointDates() {
|
|
return false;
|
|
}
|
|
|
|
set rangeStartDate(startDate) {
|
|
this.mRangeStartDate = startDate;
|
|
}
|
|
|
|
get rangeStartDate() {
|
|
return this.mRangeStartDate;
|
|
}
|
|
|
|
set rangeEndDate(endDate) {
|
|
this.mRangeEndDate = endDate;
|
|
}
|
|
|
|
get rangeEndDate() {
|
|
return this.mRangeEndDate;
|
|
}
|
|
|
|
get observerID() {
|
|
return "base-view-observer";
|
|
}
|
|
|
|
// The end date that should be used for getItems and similar queries.
|
|
get queryEndDate() {
|
|
if (!this.endDate) {
|
|
return null;
|
|
}
|
|
const end = this.endDate.clone();
|
|
end.day += 1;
|
|
end.isDate = true;
|
|
return end;
|
|
}
|
|
|
|
/**
|
|
* Return a date object representing the current day.
|
|
*
|
|
* @returns {calIDateTime} A date object.
|
|
*/
|
|
today() {
|
|
const date = cal.dtz.jsDateToDateTime(new Date()).getInTimezone(this.mTimezone);
|
|
date.isDate = true;
|
|
return date;
|
|
}
|
|
|
|
/**
|
|
* Return whether this view is currently active and visible in the UI.
|
|
*
|
|
* @returns {boolean}
|
|
*/
|
|
isVisible() {
|
|
return this == currentView();
|
|
}
|
|
|
|
/**
|
|
* Set the view's item type based on the `tasksInView` and `showCompleted` properties.
|
|
*/
|
|
updateItemType() {
|
|
if (!this.mTasksInView) {
|
|
this.itemType = Ci.calICalendar.ITEM_FILTER_TYPE_EVENT;
|
|
return;
|
|
}
|
|
|
|
let type = Ci.calICalendar.ITEM_FILTER_TYPE_ALL;
|
|
type |= this.mShowCompleted
|
|
? Ci.calICalendar.ITEM_FILTER_COMPLETED_ALL
|
|
: Ci.calICalendar.ITEM_FILTER_COMPLETED_NO;
|
|
this.itemType = type;
|
|
}
|
|
|
|
// CalendarFilteredViewMixin implementation (clearItems and removeItemsFromCalendar
|
|
// are implemented in subclasses).
|
|
|
|
addItems(items) {
|
|
for (const item of items) {
|
|
this.doAddItem(item);
|
|
}
|
|
}
|
|
|
|
removeItems(items) {
|
|
for (const item of items) {
|
|
this.doRemoveItem(item);
|
|
}
|
|
}
|
|
|
|
// End of CalendarFilteredViewMixin implementation.
|
|
|
|
/**
|
|
* Create and fire an event.
|
|
*
|
|
* @param {string} eventName - Name of the event.
|
|
* @param {object} eventDetail - The details to add to the event.
|
|
*/
|
|
fireEvent(eventName, eventDetail) {
|
|
this.dispatchEvent(
|
|
new CustomEvent(eventName, { bubbles: true, cancelable: false, detail: eventDetail })
|
|
);
|
|
}
|
|
|
|
/**
|
|
* A preference handler typically called by a preferences observer when a preference
|
|
* changes. Handles common preferences while other preferences are handled in subclasses.
|
|
*
|
|
* @param {object} subject - A subject, a prefs object.
|
|
* @param {string} topic - A topic.
|
|
* @param {string} preference - A preference that has changed.
|
|
*/
|
|
handleCommonPreference(subject, topic, preference) {
|
|
switch (preference) {
|
|
case "calendar.week.d0sundaysoff":
|
|
case "calendar.week.d1mondaysoff":
|
|
case "calendar.week.d2tuesdaysoff":
|
|
case "calendar.week.d3wednesdaysoff":
|
|
case "calendar.week.d4thursdaysoff":
|
|
case "calendar.week.d5fridaysoff":
|
|
case "calendar.week.d6saturdaysoff":
|
|
this.updateDaysOffPrefs();
|
|
break;
|
|
case "calendar.alarms.indicator.show":
|
|
case "calendar.date.format":
|
|
case "calendar.view.showLocation":
|
|
// Break here to ensure the view is refreshed.
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
this.refreshView();
|
|
}
|
|
|
|
/**
|
|
* Check preferences and update which days are days off.
|
|
*/
|
|
updateDaysOffPrefs() {
|
|
const prefix = "calendar.week.";
|
|
const daysOffPrefs = [
|
|
[0, "d0sundaysoff", "true"],
|
|
[1, "d1mondaysoff", "false"],
|
|
[2, "d2tuesdaysoff", "false"],
|
|
[3, "d3wednesdaysoff", "false"],
|
|
[4, "d4thursdaysoff", "false"],
|
|
[5, "d5fridaysoff", "false"],
|
|
[6, "d6saturdaysoff", "true"],
|
|
];
|
|
const filterDaysOff = ([, name, defaultValue]) =>
|
|
Services.prefs.getBoolPref(prefix + name, defaultValue);
|
|
|
|
this.daysOffArray = daysOffPrefs.filter(filterDaysOff).map(pref => pref[0]);
|
|
}
|
|
|
|
/**
|
|
* Adjust the position of this view's indicator of the current time, if any.
|
|
*/
|
|
updateTimeIndicatorPosition() {}
|
|
|
|
/**
|
|
* Refresh the view.
|
|
*/
|
|
refreshView() {
|
|
if (!this.startDay || !this.endDay) {
|
|
// Don't refresh if we're not initialized.
|
|
return;
|
|
}
|
|
this.goToDay(this.selectedDay);
|
|
}
|
|
|
|
handlePreference() {
|
|
// Do nothing by default.
|
|
}
|
|
|
|
flashAlarm() {
|
|
// Do nothing by default.
|
|
}
|
|
|
|
// calICalendarView Methods
|
|
|
|
/**
|
|
* NOTE: This is overridden in each of the built-in calendar views.
|
|
* It's only left here in case some extension is relying on it.
|
|
*/
|
|
goToDay(date) {
|
|
this.showDate(date);
|
|
}
|
|
|
|
getRangeDescription() {
|
|
return cal.dtz.formatter.formatInterval(this.rangeStartDate, this.rangeEndDate);
|
|
}
|
|
|
|
removeDropShadows() {
|
|
this.querySelectorAll("[dropbox='true']").forEach(dbox => {
|
|
dbox.setAttribute("dropbox", "false");
|
|
});
|
|
}
|
|
|
|
setDateRange(startDate, endDate) {
|
|
calendarNavigationBar.setDateRange(startDate, endDate);
|
|
}
|
|
|
|
getSelectedItems() {
|
|
return this.mSelectedItems;
|
|
}
|
|
|
|
setSelectedItems(items) {
|
|
this.mSelectedItems = items.concat([]);
|
|
return this.mSelectedItems;
|
|
}
|
|
|
|
getDateList() {
|
|
const start = this.startDate.clone();
|
|
const dateList = [];
|
|
while (start.compare(this.endDate) <= 0) {
|
|
dateList.push(start);
|
|
start.day++;
|
|
}
|
|
return dateList;
|
|
}
|
|
|
|
zoomIn() {}
|
|
|
|
zoomOut() {}
|
|
|
|
zoomReset() {}
|
|
|
|
// End calICalendarView Methods
|
|
}
|
|
|
|
XPCOMUtils.defineLazyPreferenceGetter(
|
|
CalendarBaseView.prototype,
|
|
"weekStartOffset",
|
|
"calendar.week.start",
|
|
0
|
|
);
|
|
|
|
MozXULElement.implementCustomInterface(CalendarBaseView, [Ci.calICalendarView]);
|
|
|
|
MozElements.CalendarBaseView = CalendarBaseView;
|
|
}
|