diff options
Diffstat (limited to 'comm/chat/content/chat-account-richlistitem.js')
-rw-r--r-- | comm/chat/content/chat-account-richlistitem.js | 354 |
1 files changed, 354 insertions, 0 deletions
diff --git a/comm/chat/content/chat-account-richlistitem.js b/comm/chat/content/chat-account-richlistitem.js new file mode 100644 index 0000000000..23efcdc596 --- /dev/null +++ b/comm/chat/content/chat-account-richlistitem.js @@ -0,0 +1,354 @@ +/* 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"; + +/* global MozElements, MozXULElement, gAccountManager */ + +// Wrap in a block to prevent leaking to window scope. +{ + const { DownloadUtils } = ChromeUtils.importESModule( + "resource://gre/modules/DownloadUtils.sys.mjs" + ); + const { ChatIcons } = ChromeUtils.importESModule( + "resource:///modules/chatIcons.sys.mjs" + ); + + /** + * The MozChatAccountRichlistitem widget displays the information about the + * configured account: i.e. icon, state, name, error, checkbox for + * auto sign in and buttons for disconnect and properties. + * + * @augments {MozElements.MozRichlistitem} + */ + class MozChatAccountRichlistitem extends MozElements.MozRichlistitem { + static get inheritedAttributes() { + return { + stack: "tooltiptext=protocol", + ".accountName": "value=name", + ".autoSignOn": "checked=autologin", + ".account-buttons": "autologin,name", + }; + } + + connectedCallback() { + if (this.delayConnectedCallback() || this.hasChildNodes()) { + return; + } + + this.setAttribute("is", "chat-account-richlistitem"); + this.addEventListener("dblclick", event => { + if (event.button == 0) { + // If we double clicked on a widget that has already done + // something with the first click, we should ignore the event + let localName = event.target.localName; + if (localName != "button" && localName != "checkbox") { + this.proceedDefaultAction(); + } + } + // Prevent from loading an account wizard + event.stopPropagation(); + }); + + this.appendChild( + MozXULElement.parseXULToFragment( + ` + <vbox flex="1"> + <hbox flex="1" align="start"> + <vbox> + <stack> + <html:img class="accountIcon" alt="" /> + <html:img class="statusTypeIcon" alt="" /> + </stack> + <spacer flex="1"></spacer> + </vbox> + <vbox flex="1" align="start"> + <label crop="end" class="accountName"></label> + <label class="connecting" crop="end" value="&account.connecting;"></label> + <label class="connected" crop="end"></label> + <label class="disconnecting" crop="end" value="&account.disconnecting;"></label> + <label class="disconnected" crop="end" value="&account.disconnected;"></label> + <description class="error error-description"></description> + <description class="error error-reconnect"></description> + <spacer flex="1"></spacer> + </vbox> + <checkbox label="&account.autoSignOn.label;" + class="autoSignOn" + accesskey="&account.autoSignOn.accesskey;" + oncommand="gAccountManager.autologin()"></checkbox> + </hbox> + <hbox flex="1" class="account-buttons"> + <button class="disconnectButton" command="cmd_disconnect"></button> + <button class="connectButton" command="cmd_connect"></button> + <spacer flex="1"></spacer> + <button command="cmd_edit"></button> + </hbox> + </vbox> + `, + ["chrome://chat/locale/accounts.dtd"] + ) + ); + this._buttons = this.querySelector(".account-buttons"); + this._connectedLabel = this.querySelector(".connected"); + this._stateIcon = this.querySelector(".statusTypeIcon"); + this.initializeAttributeInheritance(); + } + + set autoLogin(val) { + if (val) { + this.setAttribute("autologin", "true"); + } else { + this.removeAttribute("autologin"); + } + if (this._account.autoLogin != val) { + this._account.autoLogin = val; + } + } + + get autoLogin() { + return this.hasAttribute("autologin"); + } + + /** + * override the default accessible name + */ + get label() { + return this.getAttribute("name"); + } + + get account() { + return this._account; + } + + get buttons() { + return this._buttons; + } + + build(aAccount) { + this._account = aAccount; + this.setAttribute("name", aAccount.name); + this.setAttribute("id", aAccount.id); + let proto = aAccount.protocol; + this.setAttribute("protocol", proto.name); + this.querySelector(".accountIcon").setAttribute( + "src", + ChatIcons.getProtocolIconURI(proto, 32) + ); + this.refreshState(); + this.autoLogin = aAccount.autoLogin; + } + + /** + * Refresh the shown connection state. + * + * @param {"connected"|"connecting"|"disconnected"|"disconnecting"} + * [forceState] - The connection state to show. Otherwise, determined + * through the account status. + */ + refreshState(forceState) { + let account = this._account; + let state = "unknown"; + if (forceState) { + state = forceState; + } else if (account.connected) { + state = "connected"; + } else if (account.disconnected) { + state = "disconnected"; + } else if (this._account.connecting) { + state = "connecting"; + } else if (this._account.disconnecting) { + state = "disconnecting"; + } + + switch (state) { + case "connected": + this.refreshConnectedLabel(); + break; + case "connecting": + this.updateConnectingProgress(); + break; + } + + /* "state" and "error" attributes are needed for CSS styling of the + * accountIcon and the connection buttons. */ + this.setAttribute("state", state); + + if (account.connectionErrorReason !== Ci.prplIAccount.NO_ERROR) { + /* Icon and error attribute set in other method. */ + this.updateConnectionError(); + return; + } + + this.removeAttribute("error"); + + this._stateIcon.setAttribute("src", ChatIcons.getStatusIconURI(state)); + } + + updateConnectingProgress() { + let bundle = Services.strings.createBundle( + "chrome://messenger/locale/imAccounts.properties" + ); + const key = "account.connection.progress"; + let text = this._account.connectionStateMsg; + text = text + ? bundle.formatStringFromName(key, [text]) + : bundle.GetStringFromName("account.connecting"); + + let progress = this.querySelector(".connecting"); + progress.setAttribute("value", text); + if (this.reconnectUpdateInterval) { + this._cancelReconnectTimer(); + } + } + + updateConnectionError() { + let bundle = Services.strings.createBundle( + "chrome://messenger/locale/imAccounts.properties" + ); + const key = "account.connection.error"; + let account = this._account; + let text; + let errorReason = account.connectionErrorReason; + if (errorReason == Ci.imIAccount.ERROR_UNKNOWN_PRPL) { + text = bundle.formatStringFromName(key + "UnknownPrpl", [ + account.protocol.id, + ]); + } else if (errorReason == Ci.imIAccount.ERROR_MISSING_PASSWORD) { + text = bundle.GetStringFromName(key + "EnteringPasswordRequired"); + } else if (errorReason == Ci.imIAccount.ERROR_CRASHED) { + text = bundle.GetStringFromName(key + "CrashedAccount"); + } else { + text = account.connectionErrorMessage; + } + + if (errorReason != Ci.imIAccount.ERROR_MISSING_PASSWORD) { + text = bundle.formatStringFromName(key, [text]); + } + + /* "error" attribute is needed for CSS styling of the accountIcon and the + * connection buttons. */ + this.setAttribute("error", "true"); + this._stateIcon.setAttribute( + "src", + "chrome://global/skin/icons/warning.svg" + ); + let error = this.querySelector(".error-description"); + error.textContent = text; + + let updateReconnect = () => { + let date = Math.round( + (account.timeOfNextReconnect - Date.now()) / 1000 + ); + let reconnect = ""; + if (date > 0) { + let [val1, unit1, val2, unit2] = DownloadUtils.convertTimeUnits(date); + if (!val2) { + reconnect = bundle.formatStringFromName( + "account.reconnectInSingle", + [val1, unit1] + ); + } else { + reconnect = bundle.formatStringFromName( + "account.reconnectInDouble", + [val1, unit1, val2, unit2] + ); + } + } + this.querySelector(".error-reconnect").textContent = reconnect; + return reconnect; + }; + if (updateReconnect() && !this.reconnectUpdateInterval) { + this.setAttribute("reconnectPending", "true"); + this.reconnectUpdateInterval = setInterval(updateReconnect, 1000); + gAccountManager.disableCommandItems(); + } + } + + refreshConnectedLabel() { + let bundle = Services.strings.createBundle( + "chrome://messenger/locale/imAccounts.properties" + ); + let date = + 60 * Math.floor((Date.now() - this._account.timeOfLastConnect) / 60000); + let value; + if (date > 0) { + let [val1, unit1, val2, unit2] = DownloadUtils.convertTimeUnits(date); + if (!val2) { + value = bundle.formatStringFromName("account.connectedForSingle", [ + val1, + unit1, + ]); + } else { + value = bundle.formatStringFromName("account.connectedForDouble", [ + val1, + unit1, + val2, + unit2, + ]); + } + } else { + value = bundle.GetStringFromName("account.connectedForSeconds"); + } + this._connectedLabel.value = value; + } + + _cancelReconnectTimer() { + this.removeAttribute("reconnectPending"); + clearInterval(this.reconnectUpdateInterval); + delete this.reconnectUpdateInterval; + gAccountManager.disableCommandItems(); + } + + cancelReconnection() { + if (this.reconnectUpdateInterval) { + this._cancelReconnectTimer(); + this._account.cancelReconnection(); + } + } + + destroy() { + // If we have a reconnect timer, stop it: + // it will throw errors otherwise (see bug 480). + if (!this.reconnectUpdateInterval) { + return; + } + clearInterval(this.reconnectUpdateInterval); + delete this.reconnectUpdateInterval; + } + + get activeButton() { + let action = this.account.disconnected + ? ".connectButton" + : ".disconnectButton"; + return this.querySelector(action); + } + + setFocus() { + let focusTarget = this.activeButton; + let accountName = this.getAttribute("name"); + focusTarget.setAttribute( + "aria-label", + focusTarget.label + " " + accountName + ); + if (focusTarget.disabled) { + focusTarget = document.getElementById("accountlist"); + } + focusTarget.focus(); + } + + proceedDefaultAction() { + this.activeButton.click(); + } + } + + MozXULElement.implementCustomInterface(MozChatAccountRichlistitem, [ + Ci.nsIDOMXULSelectControlItemElement, + ]); + + customElements.define( + "chat-account-richlistitem", + MozChatAccountRichlistitem, + { extends: "richlistitem" } + ); +} |