summaryrefslogtreecommitdiffstats
path: root/comm/calendar/base/content/widgets/calendar-notifications-setting.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/calendar/base/content/widgets/calendar-notifications-setting.js')
-rw-r--r--comm/calendar/base/content/widgets/calendar-notifications-setting.js259
1 files changed, 259 insertions, 0 deletions
diff --git a/comm/calendar/base/content/widgets/calendar-notifications-setting.js b/comm/calendar/base/content/widgets/calendar-notifications-setting.js
new file mode 100644
index 0000000000..1f772992c7
--- /dev/null
+++ b/comm/calendar/base/content/widgets/calendar-notifications-setting.js
@@ -0,0 +1,259 @@
+/* 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/. */
+
+"use strict";
+
+/* globals MozXULElement */
+
+// Wrap in a block to prevent leaking to window scope.
+{
+ var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm");
+ var { PluralForm } = ChromeUtils.importESModule("resource://gre/modules/PluralForm.sys.mjs");
+
+ /**
+ * A calendar-notifications-setting provides controls to config notifications
+ * times of a calendar.
+ *
+ * @augments {MozXULElement}
+ */
+ class CalendarNotificationsSetting extends MozXULElement {
+ connectedCallback() {
+ MozXULElement.insertFTLIfNeeded("calendar/calendar-widgets.ftl");
+ }
+
+ /**
+ * @type {string} A string in the form of "PT5M PT0M" to represent the notifications times.
+ */
+ get value() {
+ return [...this._elList.children]
+ .map(row => {
+ let count = row.querySelector("input").value;
+ let unit = row.querySelector(".unit-menu").value;
+ let [relation, tag] = row.querySelector(".relation-menu").value.split("-");
+
+ tag = tag == "END" ? "END:" : "";
+ relation = relation == "before" ? "-" : "";
+ let durTag = unit == "D" ? "P" : "PT";
+ return `${tag}${relation}${durTag}${count}${unit}`;
+ })
+ .join(",");
+ }
+
+ set value(value) {
+ // An array of notifications times, each item is in the form of [5, "M",
+ // "before-start"], i.e. a triple of time, unit and relation.
+ let items = [];
+ let durations = value?.split(",") || [];
+ for (let dur of durations) {
+ dur = dur.trim();
+ if (!dur) {
+ continue;
+ }
+ let [relation, value] = dur.split(":");
+ if (!value) {
+ value = relation;
+ relation = "START";
+ }
+ if (value.startsWith("-")) {
+ relation = `before-${relation}`;
+ value = value.slice(1);
+ } else {
+ relation = `after-${relation}`;
+ }
+ let prefix = value.slice(0, 2);
+ if (prefix != "PT") {
+ prefix = value[0];
+ }
+ let unit = value.slice(-1);
+ if ((prefix == "P" && unit != "D") || (prefix == "PT" && !["M", "H"].includes(unit))) {
+ continue;
+ }
+ value = value.slice(prefix.length, -1);
+ items.push([value, unit, relation]);
+ }
+ this._render(items);
+ }
+
+ /**
+ * @type {boolean} If true, all form controls should be disabled.
+ */
+ set disabled(disabled) {
+ this._disabled = disabled;
+ this._updateDisabled();
+ }
+
+ /**
+ * Update the disabled attributes of all form controls to this._disabled.
+ */
+ _updateDisabled() {
+ for (let el of this.querySelectorAll("label, input, button, menulist")) {
+ el.disabled = this._disabled;
+ }
+ }
+
+ /**
+ * Because form controls can be dynamically added/removed, we bind events to
+ * _elButtonAdd and _elList.
+ */
+ _bindEvents() {
+ this._elButtonAdd.addEventListener("click", e => {
+ // Add a notification time row.
+ this._addNewRow(0, "M", "before-START");
+ this._emit();
+ });
+
+ this._elList.addEventListener("change", e => {
+ if (!HTMLInputElement.isInstance(e.target)) {
+ // We only care about change event of input elements.
+ return;
+ }
+ // We don't want this to interfere with the 'change' event emitted by
+ // calendar-notifications-setting itself.
+ e.stopPropagation();
+ this._updateMenuLists();
+ this._emit();
+ });
+
+ this._elList.addEventListener("command", e => {
+ let el = e.target;
+ if (el.tagName == "menuitem") {
+ this._emit();
+ } else if (el.tagName == "button") {
+ // Remove a notification time row.
+ el.closest("hbox").remove();
+ this._updateAddButton();
+ this._emit();
+ }
+ });
+ }
+
+ /**
+ * Render the layout and the add button, then bind events. This is delayed
+ * until the first `set value` call, so that l10n works correctly.
+ */
+ _renderLayout() {
+ this.appendChild(
+ MozXULElement.parseXULToFragment(`
+ <hbox align="center">
+ <label data-l10n-id="calendar-notifications-label"></label>
+ <spacer flex="1"></spacer>
+ <button class="add-button"
+ data-l10n-id="calendar-add-notification-button"/>
+ </hbox>
+ <separator class="thin"/>
+ <vbox class="calendar-notifications-list indent"></vbox>
+ `)
+ );
+ this._elList = this.querySelector(".calendar-notifications-list");
+ this._elButtonAdd = this.querySelector("button");
+ this._bindEvents();
+ }
+
+ /**
+ * Render this_items to a list of rows.
+ *
+ * @param {Array<[number, string, string]>} items - An array of count, unit and relation.
+ */
+ _render(items) {
+ this._renderLayout();
+
+ // Render a row for each item in this._items.
+ items.forEach(([value, unit, relation]) => {
+ this._addNewRow(value, unit, relation);
+ });
+ if (items.length) {
+ this._updateMenuLists();
+ this._updateDisabled();
+ }
+ }
+
+ /**
+ * Render a notification entry to a row. Each row contains a time input, a
+ * unit menulist, a relation menulist and a remove button.
+ */
+ _addNewRow(value, unit, relation) {
+ let fragment = MozXULElement.parseXULToFragment(`
+ <hbox class="calendar-notifications-row" align="center">
+ <html:input class="size3" value="${value}" type="number" min="0"/>
+ <menulist class="unit-menu" crop="none" value="${unit}">
+ <menupopup>
+ <menuitem value="M"/>
+ <menuitem value="H"/>
+ <menuitem value="D"/>
+ </menupopup>
+ </menulist>
+ <menulist class="relation-menu" crop="none" value="${relation}">
+ <menupopup class="reminder-relation-origin-menupopup">
+ <menuitem data-id="reminderCustomOriginBeginBeforeEvent"
+ value="before-START"/>
+ <menuitem data-id="reminderCustomOriginBeginAfterEvent"
+ value="after-START"/>
+ <menuitem data-id="reminderCustomOriginEndBeforeEvent"
+ value="before-END"/>
+ <menuitem data-id="reminderCustomOriginEndAfterEvent"
+ value="after-END"/>
+ </menupopup>
+ </menulist>
+ <button class="remove-button"></button>
+ </hbox>
+ `);
+ this._elList.appendChild(fragment);
+ this._updateMenuLists();
+ this._updateAddButton();
+ }
+
+ /**
+ * To prevent a too crowded UI, hide the add button if already have 5 rows.
+ */
+ _updateAddButton() {
+ if (this._elList.childElementCount >= 5) {
+ this._elButtonAdd.hidden = true;
+ } else {
+ this._elButtonAdd.hidden = false;
+ }
+ }
+
+ /**
+ * Iterate all rows, update the plurality of menulist (unit) to the input
+ * value (time).
+ */
+ _updateMenuLists() {
+ for (let row of this._elList.children) {
+ let input = row.querySelector("input");
+ let menulist = row.querySelector(".unit-menu");
+ this._updateMenuList(input.value, menulist);
+ for (let menuItem of row.querySelectorAll(".relation-menu menuitem")) {
+ menuItem.label = cal.l10n.getString("calendar-alarms", menuItem.dataset.id);
+ }
+ }
+ }
+
+ /**
+ * Update the plurality of a menulist (unit) options to the input value (time).
+ */
+ _updateMenuList(length, menu) {
+ let getUnitEntry = unit =>
+ ({
+ M: "unitMinutes",
+ H: "unitHours",
+ D: "unitDays",
+ }[unit] || "unitMinutes");
+
+ for (let menuItem of menu.getElementsByTagName("menuitem")) {
+ menuItem.label = PluralForm.get(length, cal.l10n.getCalString(getUnitEntry(menuItem.value)))
+ .replace("#1", "")
+ .trim();
+ }
+ }
+
+ /**
+ * Emit a change event.
+ */
+ _emit() {
+ this.dispatchEvent(new CustomEvent("change", { detail: this.value }));
+ }
+ }
+
+ customElements.define("calendar-notifications-setting", CalendarNotificationsSetting);
+}