blob: b9533abfcf70d31f95086099f8a37b1b8979d55d [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import './code_input/code_input.js';
import './error_message/error_message.js';
import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
import 'chrome://resources/cr_elements/icons.m.js';
import 'chrome://resources/cr_elements/shared_style_css.m.js';
import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
import {CrButtonElement} from 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
import {I18nMixin} from 'chrome://resources/js/i18n_mixin.js';
import {WebUIListenerMixin} from 'chrome://resources/js/web_ui_listener_mixin.js';
import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {AddSinkResultCode, CastDiscoveryMethod, PageCallbackRouter} from './access_code_cast.mojom-webui.js';
import {BrowserProxy} from './browser_proxy.js';
import {CodeInputElement} from './code_input/code_input.js';
import {ErrorMessageElement} from './error_message/error_message.js';
import {RouteRequestResultCode} from './route_request_result_code.mojom-webui.js';
declare const chrome: any;
enum PageState {
CODE_INPUT,
QR_INPUT,
}
interface AccessCodeCastElement {
$: {
backButton: CrButtonElement;
castButton: CrButtonElement;
codeInputView: HTMLDivElement;
codeInput: CodeInputElement;
dialog: CrDialogElement;
errorMessage: ErrorMessageElement;
qrInputView: HTMLDivElement;
}
}
const AccessCodeCastElementBase = WebUIListenerMixin(I18nMixin(PolymerElement));
class AccessCodeCastElement extends AccessCodeCastElementBase {
static get is() {
return 'access-code-cast-app';
}
static get template() {
return html`{__html_template__}`;
}
private listenerIds: Array<number>;
private router: PageCallbackRouter;
private static readonly ACCESS_CODE_LENGTH = 6;
private accessCode: string;
private canCast: boolean;
private state: PageState;
private qrScannerEnabled: boolean;
constructor() {
super();
this.listenerIds = [];
this.router = BrowserProxy.getInstance().callbackRouter;
this.canCast = true;
this.accessCode = '';
BrowserProxy.getInstance().isQrScanningAvailable().then((available) => {
this.qrScannerEnabled = available;
});
window.onblur = () => {
this.close();
};
document.addEventListener('keydown', (e: KeyboardEvent) => {
if (e.key === 'Enter') {
this.handleEnterPressed();
}
});
}
ready() {
super.ready();
this.setState(PageState.CODE_INPUT);
this.$.errorMessage.setNoError();
this.$.codeInput.addEventListener('access-code-input', (e: any) => {
this.handleCodeInput(e);
});
this.$.dialog.showModal();
}
connectedCallback() {
super.connectedCallback();
}
disconnectedCallback() {
super.disconnectedCallback();
this.listenerIds.forEach(id => this.router.removeListener(id));
}
close() {
BrowserProxy.getInstance().closeDialog();
}
switchToCodeInput() {
this.setState(PageState.CODE_INPUT);
}
switchToQrInput() {
this.setState(PageState.QR_INPUT);
}
async addSinkAndCast() {
if (this.accessCode.length !== AccessCodeCastElement.ACCESS_CODE_LENGTH) {
return;
}
if (!this.canCast) {
return;
}
this.canCast = false;
this.$.errorMessage.setNoError();
const method = this.state === PageState.CODE_INPUT ?
CastDiscoveryMethod.INPUT_ACCESS_CODE : CastDiscoveryMethod.QR_CODE;
const addResult = await this.addSink(method).catch(() => {
return AddSinkResultCode.UNKNOWN_ERROR;
});
if (addResult !== AddSinkResultCode.OK) {
this.$.errorMessage.setAddSinkError(addResult);
this.canCast = true;
return;
}
const castResult = await this.cast().catch(() => {
return RouteRequestResultCode.UNKNOWN_ERROR;
});
if (castResult !== RouteRequestResultCode.OK) {
this.$.errorMessage.setCastError(castResult);
this.canCast = true;
return;
}
this.close();
}
// Even though we can get this.accessCode directly, passing it triggers
// Polymer's data binding whenever this.accessCode updates.
castButtonDisabled(accessCode: string, canCast: boolean) {
if (!canCast) {
return true;
}
return accessCode.length !== AccessCodeCastElement.ACCESS_CODE_LENGTH;
}
setAccessCodeForTest(value: string) {
this.accessCode = value;
}
private setState(state: PageState) {
this.state = state;
this.$.errorMessage.setNoError();
this.$.codeInputView.hidden = state !== PageState.CODE_INPUT;
this.$.castButton.hidden = state !== PageState.CODE_INPUT;
this.$.qrInputView.hidden = state !== PageState.QR_INPUT;
this.$.backButton.hidden = state !== PageState.QR_INPUT;
if (state === PageState.CODE_INPUT) {
this.$.codeInput.clearInput();
this.$.codeInput.focusInput();
}
}
private handleCodeInput(e: any) {
this.accessCode = e.detail.value;
}
private handleEnterPressed() {
if (this.castButtonDisabled(this.accessCode, this.canCast)) {
return;
}
if (this.$.codeInput.getFocusedIndex() === -1) {
return;
}
if (this.state !== PageState.CODE_INPUT) {
return;
}
this.addSinkAndCast();
}
private async addSink(method: CastDiscoveryMethod):
Promise<AddSinkResultCode> {
const addSinkResult = await BrowserProxy.getInstance().handler
.addSink(this.accessCode, method);
return addSinkResult.resultCode as AddSinkResultCode;
}
private async cast(): Promise<RouteRequestResultCode> {
const castResult = await BrowserProxy.getInstance().handler.castToSink();
return castResult.resultCode as RouteRequestResultCode;
}
}
customElements.define(AccessCodeCastElement.is, AccessCodeCastElement);