| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import './critical_error_page.js'; |
| import './hardware_error_page.js'; |
| import './onboarding_choose_destination_page.js'; |
| import './onboarding_choose_wipe_device_page.js'; |
| import './onboarding_choose_wp_disable_method_page.js'; |
| import './onboarding_enter_rsu_wp_disable_code_page.js'; |
| import './onboarding_landing_page.js'; |
| import './onboarding_network_page.js'; |
| import './onboarding_select_components_page.js'; |
| import './onboarding_update_page.js'; |
| import './onboarding_wait_for_manual_wp_disable_page.js'; |
| import './onboarding_wp_disable_complete_page.js'; |
| import './reboot_page.js'; |
| import './reimaging_calibration_failed_page.js'; |
| import './reimaging_calibration_run_page.js'; |
| import './reimaging_calibration_setup_page.js'; |
| import './reimaging_device_information_page.js'; |
| import './reimaging_firmware_update_page.js'; |
| import './reimaging_provisioning_page.js'; |
| import './shimless_3p_diagnostics.js'; |
| import './shimless_rma_shared.css.js'; |
| import './splash_screen.js'; |
| import './wrapup_finalize_page.js'; |
| import './wrapup_repair_complete_page.js'; |
| import './wrapup_restock_page.js'; |
| import './wrapup_wait_for_manual_wp_enable_page.js'; |
| import 'chrome://resources/ash/common/cr_elements/cr_button/cr_button.js'; |
| |
| import {CrDialogElement} from 'chrome://resources/ash/common/cr_elements/cr_dialog/cr_dialog.js'; |
| import {I18nMixin} from 'chrome://resources/ash/common/cr_elements/i18n_mixin.js'; |
| import {assert} from 'chrome://resources/js/assert.js'; |
| import {FilePath} from 'chrome://resources/mojo/mojo/public/mojom/base/file_path.mojom-webui.js'; |
| import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; |
| |
| import {CriticalErrorPage} from './critical_error_page.js'; |
| import {CLICK_EXIT_BUTTON, CLICK_NEXT_BUTTON, DISABLE_ALL_BUTTONS, DISABLE_NEXT_BUTTON, DisableAllButtonsEvent, DisableNextButtonEvent, ENABLE_ALL_BUTTONS, FATAL_HARDWARE_ERROR, FatalHardwareEvent, OPEN_LOGS_DIALOG, SET_NEXT_BUTTON_LABEL, SetNextButtonLabelEvent, TRANSITION_STATE, TransitionStateEvent} from './events.js'; |
| import {HardwareErrorPage} from './hardware_error_page.js'; |
| import {getShimlessRmaService} from './mojo_interface_provider.js'; |
| import {OnboardingChooseDestinationPageElement} from './onboarding_choose_destination_page.js'; |
| import {OnboardingChooseWipeDevicePage} from './onboarding_choose_wipe_device_page.js'; |
| import {OnboardingChooseWpDisableMethodPage} from './onboarding_choose_wp_disable_method_page.js'; |
| import {OnboardingEnterRsuWpDisableCodePage} from './onboarding_enter_rsu_wp_disable_code_page.js'; |
| import {OnboardingLandingPage} from './onboarding_landing_page.js'; |
| import {OnboardingNetworkPage} from './onboarding_network_page.js'; |
| import {OnboardingSelectComponentsPageElement} from './onboarding_select_components_page.js'; |
| import {OnboardingUpdatePageElement} from './onboarding_update_page.js'; |
| import {OnboardingWaitForManualWpDisablePage} from './onboarding_wait_for_manual_wp_disable_page.js'; |
| import {OnboardingWpDisableCompletePage} from './onboarding_wp_disable_complete_page.js'; |
| import {RebootPage} from './reboot_page.js'; |
| import {ReimagingCalibrationFailedPage} from './reimaging_calibration_failed_page.js'; |
| import {ReimagingCalibrationRunPage} from './reimaging_calibration_run_page.js'; |
| import {ReimagingCalibrationSetupPage} from './reimaging_calibration_setup_page.js'; |
| import {ReimagingDeviceInformationPage} from './reimaging_device_information_page.js'; |
| import {UpdateRoFirmwarePage} from './reimaging_firmware_update_page.js'; |
| import {ReimagingProvisioningPage} from './reimaging_provisioning_page.js'; |
| import {Shimless3pDiagnostics} from './shimless_3p_diagnostics.js'; |
| import {getTemplate} from './shimless_rma.html.js'; |
| import {ErrorObserverReceiver, ExternalDiskStateObserverReceiver, RmadErrorCode, ShimlessRmaServiceInterface, State, StateResult} from './shimless_rma.mojom-webui.js'; |
| import {SplashScreen} from './splash_screen.js'; |
| import {WrapupFinalizePage} from './wrapup_finalize_page.js'; |
| import {WrapupRepairCompletePage} from './wrapup_repair_complete_page.js'; |
| import {WrapupRestockPage} from './wrapup_restock_page.js'; |
| import {WrapupWaitForManualWpEnablePage} from './wrapup_wait_for_manual_wp_enable_page.js'; |
| |
| |
| declare global { |
| interface WindowEventMap { |
| [TRANSITION_STATE]: TransitionStateEvent; |
| [DISABLE_NEXT_BUTTON]: DisableNextButtonEvent; |
| [FATAL_HARDWARE_ERROR]: FatalHardwareEvent; |
| [DISABLE_ALL_BUTTONS]: DisableAllButtonsEvent; |
| [DISABLE_NEXT_BUTTON]: DisableNextButtonEvent; |
| [SET_NEXT_BUTTON_LABEL]: SetNextButtonLabelEvent; |
| } |
| } |
| |
| export interface SaveLogResponse { |
| savePath: FilePath; |
| error: RmadErrorCode; |
| } |
| |
| /** |
| * Enum for the state of USB used for saving logs. The states are transitioned |
| * through as the user plugs in a USB then attempts to save the log. |
| */ |
| enum UsbLogState { |
| USB_UNPLUGGED = 0, |
| USB_READY = 1, |
| SAVING_LOGS = 2, |
| LOG_SAVE_SUCCESS = 3, |
| LOG_SAVE_FAIL = 4, |
| } |
| |
| // TODO(b/315002705): Replace this type with a mapped type that can infer |
| // which shimless custom element is returned by `loadComponent`. |
| type ShimlessCustomElementType = HTMLElement&{ |
| confirmExitButtonClicked?: boolean, |
| hidden?: boolean, |
| errorCode?: RmadErrorCode, |
| getStartedButtonClicked?: boolean, |
| allButtonsDisabled?: boolean, |
| onNextButtonClick?: () => Promise<{stateResult: StateResult}>, |
| onExitButtonClick?: () => Promise<{stateResult: StateResult}>, |
| }; |
| |
| /** |
| * The starting USB state for the logs dialog. |
| */ |
| const DEFAULT_USB_LOG_STATE: UsbLogState = UsbLogState.USB_READY; |
| |
| /** |
| * Enum for button states. |
| */ |
| export enum ButtonState { |
| VISIBLE = 'visible', |
| DISABLED = 'disabled', |
| HIDDEN = 'hidden', |
| } |
| |
| const HEADER_FOOTER_HEIGHT_PX = 80; |
| |
| const OOBE_LARGE_SCREEN_WIDTH_PX = 80; |
| |
| interface PageInfo { |
| componentIs: string; |
| requiresReloadWhenShown?: boolean; |
| buttonNext: ButtonState; |
| buttonNextLabelKey?: string|null; |
| buttonExitLabelKey?: string|null; |
| buttonExit: ButtonState; |
| buttonBack: ButtonState; |
| } |
| |
| export const StateComponentMapping: {[key in State]: PageInfo} = { |
| // It is assumed that if state is kUnknown the error is kRmaNotRequired. |
| [State.kUnknown]: { |
| componentIs: CriticalErrorPage.is, |
| requiresReloadWhenShown: false, |
| buttonNext: ButtonState.HIDDEN, |
| buttonExit: ButtonState.HIDDEN, |
| buttonBack: ButtonState.HIDDEN, |
| }, |
| [State.kWelcomeScreen]: { |
| componentIs: OnboardingLandingPage.is, |
| requiresReloadWhenShown: false, |
| buttonNext: ButtonState.HIDDEN, |
| buttonNextLabelKey: 'getStartedButtonLabel', |
| buttonExit: ButtonState.HIDDEN, |
| buttonBack: ButtonState.HIDDEN, |
| }, |
| [State.kConfigureNetwork]: { |
| componentIs: OnboardingNetworkPage.is, |
| requiresReloadWhenShown: false, |
| buttonNext: ButtonState.DISABLED, |
| buttonNextLabelKey: 'skipButtonLabel', |
| buttonExit: ButtonState.VISIBLE, |
| buttonBack: ButtonState.VISIBLE, |
| }, |
| [State.kUpdateOs]: { |
| componentIs: OnboardingUpdatePageElement.is, |
| requiresReloadWhenShown: false, |
| buttonNext: ButtonState.DISABLED, |
| buttonNextLabelKey: 'skipButtonLabel', |
| buttonExit: ButtonState.VISIBLE, |
| buttonBack: ButtonState.VISIBLE, |
| }, |
| [State.kSelectComponents]: { |
| componentIs: OnboardingSelectComponentsPageElement.is, |
| requiresReloadWhenShown: true, |
| buttonNext: ButtonState.DISABLED, |
| buttonExit: ButtonState.VISIBLE, |
| buttonBack: ButtonState.VISIBLE, |
| }, |
| [State.kChooseDestination]: { |
| componentIs: OnboardingChooseDestinationPageElement.is, |
| requiresReloadWhenShown: false, |
| buttonNext: ButtonState.DISABLED, |
| buttonExit: ButtonState.VISIBLE, |
| buttonBack: ButtonState.VISIBLE, |
| }, |
| [State.kChooseWipeDevice]: { |
| componentIs: OnboardingChooseWipeDevicePage.is, |
| requiresReloadWhenShown: false, |
| buttonNext: ButtonState.DISABLED, |
| buttonExit: ButtonState.VISIBLE, |
| buttonBack: ButtonState.VISIBLE, |
| }, |
| [State.kChooseWriteProtectDisableMethod]: { |
| componentIs: |
| OnboardingChooseWpDisableMethodPage.is, |
| requiresReloadWhenShown: false, |
| buttonNext: ButtonState.DISABLED, |
| buttonExit: ButtonState.VISIBLE, |
| buttonBack: ButtonState.VISIBLE, |
| }, |
| [State.kEnterRSUWPDisableCode]: { |
| componentIs: |
| OnboardingEnterRsuWpDisableCodePage.is, |
| requiresReloadWhenShown: true, |
| buttonNext: ButtonState.DISABLED, |
| buttonExit: ButtonState.VISIBLE, |
| buttonBack: ButtonState.VISIBLE, |
| }, |
| [State.kWaitForManualWPDisable]: { |
| componentIs: |
| OnboardingWaitForManualWpDisablePage.is, |
| requiresReloadWhenShown: true, |
| buttonNext: ButtonState.HIDDEN, |
| buttonExit: ButtonState.VISIBLE, |
| buttonBack: ButtonState.VISIBLE, |
| }, |
| [State.kWPDisableComplete]: { |
| componentIs: OnboardingWpDisableCompletePage.is, |
| requiresReloadWhenShown: false, |
| buttonNext: ButtonState.DISABLED, |
| buttonExit: ButtonState.VISIBLE, |
| buttonBack: ButtonState.HIDDEN, |
| }, |
| [State.kUpdateRoFirmware]: { |
| componentIs: UpdateRoFirmwarePage.is, |
| requiresReloadWhenShown: false, |
| buttonNext: ButtonState.HIDDEN, |
| buttonExit: ButtonState.HIDDEN, |
| buttonBack: ButtonState.HIDDEN, |
| }, |
| [State.kUpdateDeviceInformation]: { |
| componentIs: ReimagingDeviceInformationPage.is, |
| requiresReloadWhenShown: false, |
| buttonNext: ButtonState.DISABLED, |
| buttonExit: ButtonState.HIDDEN, |
| buttonBack: ButtonState.HIDDEN, |
| }, |
| [State.kCheckCalibration]: { |
| componentIs: ReimagingCalibrationFailedPage.is, |
| requiresReloadWhenShown: true, |
| buttonNext: ButtonState.DISABLED, |
| buttonExitLabelKey: 'calibrationFailedSkipCalibrationButtonLabel', |
| buttonExit: ButtonState.VISIBLE, |
| buttonBack: ButtonState.VISIBLE, |
| }, |
| [State.kRunCalibration]: { |
| componentIs: ReimagingCalibrationRunPage.is, |
| requiresReloadWhenShown: true, |
| buttonNext: ButtonState.DISABLED, |
| buttonExit: ButtonState.HIDDEN, |
| buttonBack: ButtonState.HIDDEN, |
| }, |
| [State.kSetupCalibration]: { |
| componentIs: ReimagingCalibrationSetupPage.is, |
| requiresReloadWhenShown: true, |
| buttonNext: ButtonState.DISABLED, |
| buttonExit: ButtonState.HIDDEN, |
| buttonBack: ButtonState.VISIBLE, |
| }, |
| [State.kProvisionDevice]: { |
| componentIs: ReimagingProvisioningPage.is, |
| requiresReloadWhenShown: true, |
| buttonNext: ButtonState.HIDDEN, |
| buttonExit: ButtonState.HIDDEN, |
| buttonBack: ButtonState.HIDDEN, |
| }, |
| [State.kWaitForManualWPEnable]: { |
| componentIs: |
| WrapupWaitForManualWpEnablePage.is, |
| requiresReloadWhenShown: true, |
| buttonNext: ButtonState.HIDDEN, |
| buttonExit: ButtonState.HIDDEN, |
| buttonBack: ButtonState.HIDDEN, |
| }, |
| [State.kRestock]: { |
| componentIs: WrapupRestockPage.is, |
| requiresReloadWhenShown: false, |
| buttonNext: ButtonState.HIDDEN, |
| buttonExit: ButtonState.HIDDEN, |
| buttonBack: ButtonState.VISIBLE, |
| }, |
| [State.kFinalize]: { |
| componentIs: WrapupFinalizePage.is, |
| buttonNext: ButtonState.HIDDEN, |
| buttonExit: ButtonState.HIDDEN, |
| buttonBack: ButtonState.HIDDEN, |
| }, |
| [State.kRepairComplete]: { |
| componentIs: WrapupRepairCompletePage.is, |
| requiresReloadWhenShown: false, |
| buttonNext: ButtonState.HIDDEN, |
| buttonExit: ButtonState.HIDDEN, |
| buttonBack: ButtonState.HIDDEN, |
| }, |
| [State.kHardwareError]: { |
| componentIs: HardwareErrorPage.is, |
| requiresReloadWhenShown: false, |
| buttonNext: ButtonState.HIDDEN, |
| buttonExit: ButtonState.HIDDEN, |
| buttonBack: ButtonState.HIDDEN, |
| }, |
| [State.kReboot]: { |
| componentIs: RebootPage.is, |
| requiresReloadWhenShown: false, |
| buttonNext: ButtonState.HIDDEN, |
| buttonExit: ButtonState.HIDDEN, |
| buttonBack: ButtonState.HIDDEN, |
| }, |
| }; |
| |
| /** |
| * @fileoverview |
| * 'shimless-rma' is the main page for the shimless rma process modal dialog. |
| */ |
| |
| const ShimlessRmaBase = I18nMixin(PolymerElement); |
| |
| export class ShimlessRma extends ShimlessRmaBase { |
| static get is() { |
| return 'shimless-rma' as const; |
| } |
| |
| static get template() { |
| return getTemplate(); |
| } |
| |
| static get properties() { |
| return { |
| /** |
| * Current PageInfo based on current state |
| */ |
| currentPage: { |
| reflectToAttribute: true, |
| type: Object, |
| value: { |
| componentIs: SplashScreen.is, |
| requiresReloadWhenShown: false, |
| buttonNext: ButtonState.HIDDEN, |
| buttonExit: ButtonState.HIDDEN, |
| buttonBack: ButtonState.HIDDEN, |
| }, |
| }, |
| |
| shimlessRmaService: { |
| type: Object, |
| value: {}, |
| }, |
| |
| /** |
| * Used to disable all buttons while waiting for long running mojo API |
| * calls to complete. Also controls the busy state overlay. |
| */ |
| allButtonsDisabled: { |
| type: Boolean, |
| value: true, |
| reflectToAttribute: true, |
| }, |
| |
| /** |
| * Show busy state overlay while waiting for the service response. |
| */ |
| showBusyStateOverlay: { |
| type: Boolean, |
| value: false, |
| reflectToAttribute: true, |
| }, |
| |
| /** |
| * After the next button is clicked, true until the next state is |
| * processed. |
| */ |
| nextButtonClicked: { |
| type: Boolean, |
| value: false, |
| }, |
| |
| /** |
| * After the back button is clicked, true until the next state is |
| * processed. |
| */ |
| backButtonClicked: { |
| type: Boolean, |
| value: false, |
| }, |
| |
| /** |
| * After the exit button is clicked, true until the next state is |
| * processed. |
| */ |
| confirmExitButtonClicked: { |
| type: Boolean, |
| value: false, |
| }, |
| |
| log: { |
| type: String, |
| value: '', |
| }, |
| |
| /** |
| * Tracks the current status of the USB and log saving. |
| */ |
| usbLogState: { |
| type: Number, |
| value: DEFAULT_USB_LOG_STATE, |
| }, |
| |
| logSavedStatusText: { |
| type: String, |
| value: '', |
| }, |
| }; |
| } |
| |
| protected currentPage: PageInfo; |
| protected allButtonsDisabled: boolean; |
| protected showBusyStateOverlay: boolean; |
| protected nextButtonClicked: boolean; |
| protected backButtonClicked: boolean; |
| protected confirmExitButtonClicked: boolean; |
| protected log: string; |
| protected usbLogState: UsbLogState; |
| protected logSavedStatusText: string; |
| shimlessRmaService: ShimlessRmaServiceInterface = getShimlessRmaService(); |
| errorObserverReceiver: ErrorObserverReceiver; |
| externalDiskStateReceiver: ExternalDiskStateObserverReceiver; |
| transitionState: (e: TransitionStateEvent) => void; |
| disableNextButtonCallback: (e: DisableNextButtonEvent) => void; |
| enableAllButtonsCallback: () => void; |
| disableAllButtonsCallback: (e: DisableAllButtonsEvent) => void; |
| exitButtonCallback: () => void; |
| nextButtonCallback: () => void; |
| setNextButtonLabelCallback: (e: SetNextButtonLabelEvent) => void; |
| fatalHardwareErrorCallback: (e: FatalHardwareEvent) => void; |
| openLogsDialogCallback: () => void; |
| onKeyDownCallback: (e: KeyboardEvent) => void; |
| |
| constructor() { |
| super(); |
| |
| this.errorObserverReceiver = new ErrorObserverReceiver(this); |
| |
| this.shimlessRmaService.observeError( |
| this.errorObserverReceiver.$.bindNewPipeAndPassRemote()); |
| |
| this.externalDiskStateReceiver = |
| new ExternalDiskStateObserverReceiver(this); |
| |
| this.shimlessRmaService.observeExternalDiskState( |
| this.externalDiskStateReceiver.$.bindNewPipeAndPassRemote()); |
| |
| /** |
| * transitionState is used by page elements to trigger state transition |
| * functions and switching to the next page without using the 'Next' button. |
| */ |
| this.transitionState = (e: TransitionStateEvent): void => { |
| this.setAllButtonsState( |
| /* shouldDisableButtons= */ true, /* showBusyStateOverlay= */ true); |
| e.detail().then((stateResult) => this.processStateResult(stateResult)); |
| }; |
| |
| /** |
| * The disableNextButton callback is used by page elements to control the |
| * disabled state of the 'Next' button. |
| */ |
| this.disableNextButtonCallback = (e: DisableNextButtonEvent): void => { |
| this.currentPage.buttonNext = |
| e.detail ? ButtonState.DISABLED : ButtonState.VISIBLE; |
| // Allow polymer to observe the changed state. |
| this.notifyPath('currentPage.buttonNext'); |
| }; |
| |
| /** |
| * The enableAllButtons callback is used by page elements to enable all |
| * buttons. |
| */ |
| this.enableAllButtonsCallback = (): void => { |
| this.setAllButtonsState( |
| /* shouldDisableButtons= */ false, /* showBusyStateOverlay= */ false); |
| }; |
| |
| /** |
| * The disableAllButtons callback is used by page elements to disable all |
| * buttons and optionally show a busy overlay. |
| */ |
| this.disableAllButtonsCallback = (e: DisableAllButtonsEvent): void => { |
| this.setAllButtonsState( |
| /* shouldDisableButtons= */ true, e.detail.showBusyStateOverlay); |
| }; |
| |
| /** |
| * The exitButtonCallback callback is used by the landing page to create |
| * its own Exit button in the left pane. |
| */ |
| this.exitButtonCallback = (): void => { |
| this.onExitButtonClicked(); |
| }; |
| |
| /** |
| * The nextButtonCallback callback is used by the landing page to simulate |
| * the next button being clicked. |
| */ |
| this.nextButtonCallback = (): void => { |
| this.onNextButtonClicked(); |
| }; |
| |
| /** |
| * The setNextButtonLabelCallback callback is used by page elements to set |
| * the text label for the 'Next' button. |
| */ |
| this.setNextButtonLabelCallback = (e: SetNextButtonLabelEvent): void => { |
| this.currentPage.buttonNextLabelKey = e.detail; |
| this.notifyPath('currentPage.buttonNextLabelKey'); |
| }; |
| |
| /** |
| * The fatalHardwareErrorCallback callback is used by the finalization |
| * page and the provisioning page to tell the app that there is a fatal |
| * hardware error. |
| */ |
| this.fatalHardwareErrorCallback = (event: FatalHardwareEvent): void => { |
| const errorState = { |
| stateResult: { |
| state: State.kHardwareError, |
| canExit: false, |
| canGoBack: false, |
| error: event.detail.fatalErrorCode, |
| }, |
| }; |
| this.showState(errorState); |
| }; |
| |
| /** |
| * Opens the logs dialog. |
| */ |
| this.openLogsDialogCallback = (): void => { |
| this.openLogsDialog(); |
| }; |
| |
| this.onKeyDownCallback = (event: KeyboardEvent): void => { |
| this.handleKeyboardShortcut(event); |
| }; |
| } |
| |
| override connectedCallback() { |
| super.connectedCallback(); |
| window.addEventListener(TRANSITION_STATE, this.transitionState); |
| window.addEventListener( |
| DISABLE_NEXT_BUTTON, this.disableNextButtonCallback); |
| window.addEventListener( |
| SET_NEXT_BUTTON_LABEL, this.setNextButtonLabelCallback); |
| window.addEventListener( |
| DISABLE_ALL_BUTTONS, this.disableAllButtonsCallback); |
| window.addEventListener(ENABLE_ALL_BUTTONS, this.enableAllButtonsCallback); |
| window.addEventListener(CLICK_EXIT_BUTTON, this.exitButtonCallback); |
| window.addEventListener(CLICK_NEXT_BUTTON, this.nextButtonCallback); |
| window.addEventListener( |
| FATAL_HARDWARE_ERROR, this.fatalHardwareErrorCallback); |
| window.addEventListener(OPEN_LOGS_DIALOG, this.openLogsDialogCallback); |
| |
| window.addEventListener('keydown', this.onKeyDownCallback); |
| } |
| |
| override disconnectedCallback() { |
| super.disconnectedCallback(); |
| window.removeEventListener(TRANSITION_STATE, this.transitionState); |
| window.removeEventListener( |
| DISABLE_NEXT_BUTTON, this.disableNextButtonCallback); |
| window.removeEventListener( |
| SET_NEXT_BUTTON_LABEL, this.setNextButtonLabelCallback); |
| window.removeEventListener( |
| DISABLE_ALL_BUTTONS, this.disableAllButtonsCallback); |
| window.removeEventListener( |
| ENABLE_ALL_BUTTONS, this.enableAllButtonsCallback); |
| window.removeEventListener(CLICK_EXIT_BUTTON, this.exitButtonCallback); |
| window.removeEventListener(CLICK_NEXT_BUTTON, this.nextButtonCallback); |
| window.removeEventListener( |
| FATAL_HARDWARE_ERROR, this.fatalHardwareErrorCallback); |
| window.removeEventListener(OPEN_LOGS_DIALOG, this.openLogsDialogCallback); |
| |
| window.removeEventListener('keydown', this.onKeyDownCallback); |
| } |
| |
| override ready() { |
| super.ready(); |
| |
| this.style.setProperty( |
| '--header-footer-height', `${HEADER_FOOTER_HEIGHT_PX}px`); |
| |
| const screenWidth = window.innerWidth; |
| // TODO(b/315002705): Replace integers with variables for |
| // `containerHorizontalPadding` calculation. |
| const containerHorizontalPadding = |
| screenWidth > OOBE_LARGE_SCREEN_WIDTH_PX ? ((screenWidth - 1040) / 2) : |
| (screenWidth * .08); |
| this.style.setProperty( |
| '--container-horizontal-padding', `${containerHorizontalPadding}px`); |
| |
| const contentContainerWidth = |
| screenWidth - (containerHorizontalPadding * 2); |
| this.style.setProperty( |
| '--content-container-width', `${contentContainerWidth}px`); |
| |
| const screenHeight = window.innerHeight; |
| const containerVerticalPadding = screenHeight * .06; |
| this.style.setProperty( |
| '--container-vertical-padding', `${containerVerticalPadding}px`); |
| |
| const contentContainerHeight = screenHeight - |
| (containerVerticalPadding * 2) - (HEADER_FOOTER_HEIGHT_PX * 2); |
| this.style.setProperty( |
| '--content-container-height', `${contentContainerHeight}px`); |
| |
| const splashComponent = this.loadComponent(this.currentPage.componentIs); |
| splashComponent.hidden = false; |
| |
| // Get the initial state. |
| this.shimlessRmaService.getCurrentState().then( |
| (stateResult: {stateResult: StateResult}) => { |
| this.processStateResult(stateResult); |
| }); |
| } |
| |
| private processStateResult(stateResult: {stateResult: StateResult}): void { |
| // Do not show the state screen if the critical error screen was shown. |
| if (this.handleStandardAndCriticalError(stateResult.stateResult.error)) { |
| return; |
| } |
| |
| // This is a special case for showing the reboot page when the platform |
| // sends the error code for expecting a reboot or a shut down. |
| if (stateResult.stateResult.error === RmadErrorCode.kExpectReboot || |
| stateResult.stateResult.error === RmadErrorCode.kExpectShutdown) { |
| const rebootState = { |
| stateResult: { |
| state: State.kReboot, |
| canExit: false, |
| canGoBack: false, |
| error: stateResult.stateResult.error, |
| }, |
| }; |
| this.showState(rebootState); |
| return; |
| } |
| |
| this.showState(stateResult); |
| } |
| |
| onError(error: RmadErrorCode): void { |
| this.handleStandardAndCriticalError(error); |
| } |
| |
| /** |
| * Returns true if the critical error screen was displayed. |
| */ |
| private handleStandardAndCriticalError(error: RmadErrorCode): boolean { |
| // Critical error - expected to be in RMA. |
| if (error === RmadErrorCode.kRmaNotRequired) { |
| const errorState = { |
| stateResult: { |
| state: State.kUnknown, |
| canExit: false, |
| canGoBack: false, |
| error: RmadErrorCode.kRmaNotRequired, |
| }, |
| }; |
| this.showState(errorState); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| private showState({stateResult}: {stateResult: StateResult}): void { |
| // Reset clicked variables to hide the spinners. |
| this.nextButtonClicked = false; |
| this.backButtonClicked = false; |
| this.confirmExitButtonClicked = false; |
| |
| const nextStatePageInfo: PageInfo = |
| StateComponentMapping[stateResult.state]; |
| assert(nextStatePageInfo); |
| |
| if (this.currentPage.requiresReloadWhenShown) { |
| this.removeComponent(this.currentPage.componentIs); |
| } |
| |
| // Only perform the below actions if the page needs to change or reload. |
| const shouldLoadNextPage = this.currentPage !== nextStatePageInfo || |
| this.currentPage.requiresReloadWhenShown; |
| if (shouldLoadNextPage) { |
| this.hideAllComponents(); |
| |
| // Set the next page as the current page. |
| this.currentPage = nextStatePageInfo; |
| if (!stateResult.canExit) { |
| // The calibration failed page is a special case because the Exit button |
| // is used as the Skip Calibration button. So we don't want to |
| // acknowledge `canExit` here. |
| if (this.currentPage.componentIs !== ReimagingCalibrationFailedPage.is) { |
| this.currentPage.buttonExit = ButtonState.HIDDEN; |
| } |
| } |
| if (!stateResult.canGoBack) { |
| this.currentPage.buttonBack = ButtonState.HIDDEN; |
| } |
| |
| // Load the next page so it's visible. |
| const currentPageComponent = |
| this.loadComponent(this.currentPage.componentIs); |
| currentPageComponent.hidden = false; |
| currentPageComponent.errorCode = stateResult.error; |
| this.notifyPath('currentPage.buttonNext'); |
| this.notifyPath('currentPage.buttonExit'); |
| this.notifyPath('currentPage.buttonBack'); |
| |
| // A special case for the landing page, which has its own navigation |
| // buttons. |
| currentPageComponent.getStartedButtonClicked = false; |
| currentPageComponent.confirmExitButtonClicked = false; |
| } |
| |
| this.setAllButtonsState( |
| /* shouldDisableButtons= */ false, /* showBusyStateOverlay= */ false); |
| } |
| |
| /** |
| * Utility method to bulk hide all contents. |
| */ |
| hideAllComponents(): void { |
| const components = this.shadowRoot!.querySelectorAll('.shimless-content'); |
| Array.from(components).map((c) => (c as HTMLElement).hidden = true); |
| } |
| |
| private removeComponent(componentIs: string): void { |
| const currentPageComponent = |
| this.shadowRoot!.querySelector(`#${componentIs}`); |
| assert(!!currentPageComponent); |
| currentPageComponent.remove(); |
| } |
| |
| private loadComponent(componentIs: string): ShimlessCustomElementType { |
| const alreadyLoadedComponent = |
| this.shadowRoot!.querySelector<ShimlessCustomElementType>( |
| `#${componentIs}`); |
| if (alreadyLoadedComponent) { |
| return alreadyLoadedComponent; |
| } |
| |
| const shimlessBody = this.shadowRoot!.querySelector('#contentContainer'); |
| assert(shimlessBody); |
| |
| const component = |
| document.createElement(componentIs) as ShimlessCustomElementType; |
| component.setAttribute('id', componentIs); |
| component.setAttribute('class', 'shimless-content'); |
| component.hidden = true; |
| |
| shimlessBody.appendChild(component); |
| return component; |
| } |
| |
| protected isButtonHidden(button: ButtonState): boolean { |
| return button === ButtonState.HIDDEN; |
| } |
| |
| protected isButtonDisabled(button: ButtonState): boolean { |
| return (button === ButtonState.DISABLED) || this.allButtonsDisabled; |
| } |
| |
| protected setAllButtonsState( |
| shouldDisableButtons: boolean, showBusyStateOverlay: boolean): void { |
| // `showBusyStateOverlay` should only be true when disabling all buttons. |
| assert(!showBusyStateOverlay || shouldDisableButtons); |
| |
| this.allButtonsDisabled = shouldDisableButtons; |
| this.showBusyStateOverlay = showBusyStateOverlay; |
| const component = this.loadComponent(this.currentPage.componentIs); |
| if (!component) { |
| return; |
| } |
| |
| component!.allButtonsDisabled = this.allButtonsDisabled; |
| } |
| |
| updateButtonState(buttonName: string, buttonState: ButtonState): void { |
| assert(this.currentPage.hasOwnProperty(buttonName)); |
| this.set(`currentPage.${buttonName}`, buttonState); |
| } |
| |
| protected onBackButtonClicked(): void { |
| this.backButtonClicked = true; |
| this.setAllButtonsState( |
| /* shouldDisableButtons= */ true, /* showBusyStateOverlay= */ true); |
| this.shimlessRmaService.transitionPreviousState().then( |
| (stateResult: {stateResult: StateResult}) => |
| this.processStateResult(stateResult)); |
| } |
| |
| protected onNextButtonClicked(): void { |
| const page = this.loadComponent(this.currentPage.componentIs); |
| assert(page, 'Could not find page ' + this.currentPage.componentIs); |
| assert( |
| page!.onNextButtonClick, |
| 'No onNextButtonClick for ' + this.currentPage.componentIs); |
| assert( |
| typeof page!.onNextButtonClick === 'function', |
| 'onNextButtonClick not a function for ' + this.currentPage.componentIs); |
| this.nextButtonClicked = true; |
| this.setAllButtonsState( |
| /* shouldDisableButtons= */ true, /* showBusyStateOverlay= */ true); |
| page!.onNextButtonClick() |
| .then((stateResult) => { |
| this.processStateResult(stateResult); |
| }) |
| .catch((_err: Error) => { |
| this.nextButtonClicked = false; |
| this.setAllButtonsState( |
| /* shouldDisableButtons= */ false, |
| /* showBusyStateOverlay= */ false); |
| }); |
| } |
| |
| protected onExitButtonClicked(): void { |
| const page = this.loadComponent(this.currentPage.componentIs); |
| assert(page); |
| |
| // Don't show the exit dialog if it's on calibration failed page. |
| if (page.onExitButtonClick) { |
| // A special case for the calibration failed page, where the skip button |
| // replaces the exit button. |
| page.onExitButtonClick() |
| .then((stateResult) => { |
| this.processStateResult(stateResult); |
| }) |
| .catch((_err: Error) => { |
| this.confirmExitButtonClicked = false; |
| this.setAllButtonsState( |
| /* shouldDisableButtons= */ false, |
| /* showBusyStateOverlay= */ false); |
| }); |
| } else { |
| const dialog: CrDialogElement|null = |
| this.shadowRoot!.querySelector('#exitDialog'); |
| assert(dialog); |
| dialog.showModal(); |
| } |
| } |
| |
| protected onConfirmExitButtonClicked(): void { |
| this.confirmExitButtonClicked = true; |
| this.closeDialog(); |
| |
| // Show exit button spinner on the landing page |
| const currentPageComponent = |
| this.loadComponent(this.currentPage.componentIs); |
| currentPageComponent!.confirmExitButtonClicked = true; |
| |
| this.setAllButtonsState( |
| /* shouldDisableButtons= */ true, /* showBusyStateOverlay= */ true); |
| |
| this.shimlessRmaService.abortRma().then((result) => { |
| this.confirmExitButtonClicked = false; |
| this.handleStandardAndCriticalError(result.error); |
| }); |
| } |
| |
| protected closeDialog(): void { |
| const dialog: CrDialogElement|null = |
| this.shadowRoot!.querySelector('#exitDialog'); |
| assert(dialog); |
| dialog.close(); |
| } |
| |
| private getNextButtonLabel(): string { |
| return this.i18n( |
| this.currentPage.buttonNextLabelKey ? |
| this.currentPage.buttonNextLabelKey : |
| 'nextButtonLabel'); |
| } |
| |
| protected getExitButtonLabel(): string { |
| return this.i18n( |
| this.currentPage.buttonExitLabelKey ? |
| this.currentPage.buttonExitLabelKey : |
| 'exitButtonLabel'); |
| } |
| |
| protected openLogsDialog(): void { |
| this.shimlessRmaService.getLog().then( |
| (res: {log: string, error: RmadErrorCode}) => this.log = res.log); |
| const dialog: CrDialogElement|null = |
| this.shadowRoot!.querySelector('#logsDialog'); |
| assert(dialog); |
| if (!dialog.open) { |
| dialog.showModal(); |
| } |
| } |
| |
| protected launch3pDiagnostics(): void { |
| if (this.allButtonsDisabled) { |
| return; |
| } |
| |
| const diagnostics: Shimless3pDiagnostics|null = |
| this.shadowRoot!.querySelector('#shimless3pDiagnostics'); |
| assert(diagnostics); |
| diagnostics.launch3pDiagnostics(); |
| } |
| |
| private saveLog(): void { |
| this.shimlessRmaService.saveLog().then((result: SaveLogResponse) => { |
| if (result.error === RmadErrorCode.kOk) { |
| this.logSavedStatusText = |
| this.i18n('rmaLogsSaveSuccessText', result.savePath.path); |
| this.usbLogState = UsbLogState.LOG_SAVE_SUCCESS; |
| } else if (result.error === RmadErrorCode.kUsbNotFound) { |
| this.logSavedStatusText = this.i18n('rmaLogsSaveUsbNotFound'); |
| this.usbLogState = UsbLogState.LOG_SAVE_FAIL; |
| } else { |
| this.logSavedStatusText = this.i18n('rmaLogsSaveFailText'); |
| this.usbLogState = UsbLogState.LOG_SAVE_FAIL; |
| } |
| }); |
| } |
| |
| protected onSaveLogClick(): void { |
| this.saveLog(); |
| } |
| |
| protected retrySaveLogs(): void { |
| this.saveLog(); |
| } |
| |
| protected closeLogsDialog(): void { |
| const dialog: CrDialogElement|null = |
| this.shadowRoot!.querySelector('#logsDialog'); |
| assert(dialog); |
| dialog.close(); |
| |
| // Reset the USB state back to the default. |
| this.usbLogState = DEFAULT_USB_LOG_STATE; |
| } |
| |
| /** |
| * Implements ExternalDiskStateObserver.onExternalDiskStateChanged() |
| */ |
| onExternalDiskStateChanged(detected: boolean): void { |
| if (!detected) { |
| this.usbLogState = UsbLogState.USB_UNPLUGGED; |
| return; |
| } |
| |
| if (this.usbLogState === UsbLogState.USB_UNPLUGGED) { |
| this.usbLogState = UsbLogState.USB_READY; |
| } |
| } |
| |
| protected shouldShowSaveToUsbButton(): boolean { |
| return this.usbLogState === UsbLogState.USB_READY; |
| } |
| |
| protected shouldShowLogSaveAttemptContainer(): boolean { |
| return this.usbLogState === UsbLogState.LOG_SAVE_SUCCESS || |
| this.usbLogState === UsbLogState.LOG_SAVE_FAIL; |
| } |
| |
| protected shouldShowRetryButton(): boolean { |
| return this.usbLogState === UsbLogState.LOG_SAVE_FAIL; |
| } |
| |
| protected shouldShowLogUsbMessageContainer(): boolean { |
| return this.usbLogState === UsbLogState.USB_UNPLUGGED; |
| } |
| |
| protected getSaveLogResultIcon(): string { |
| switch (this.usbLogState) { |
| case UsbLogState.LOG_SAVE_SUCCESS: |
| return 'shimless-icon:check'; |
| case UsbLogState.LOG_SAVE_FAIL: |
| return 'shimless-icon:warning'; |
| default: |
| return ''; |
| } |
| } |
| |
| private handleKeyboardShortcut(event: KeyboardEvent): void { |
| // Handle `Alt + Shift + {key}` shortcuts. |
| if (event.altKey && event.shiftKey) { |
| switch (event.key.toLowerCase()) { |
| case 'l': |
| this.openLogsDialog(); |
| break; |
| case 'd': |
| this.launch3pDiagnostics(); |
| break; |
| } |
| } |
| } |
| } |
| |
| declare global { |
| interface HTMLElementTagNameMap { |
| [ShimlessRma.is]: ShimlessRma; |
| } |
| } |
| |
| customElements.define(ShimlessRma.is, ShimlessRma); |