Javascript  |  451行  |  13.88 KB

// Copyright (c) 2012 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.

var localStrings;
var browserBridge;

/**
 * Class that keeps track of current burn process state.
 * @param {Object} strings Localized state strings.
 * @constructor
 */
function State(strings) {
  this.setStrings(strings);
  this.changeState(State.StatesEnum.DEVICE_NONE);
}

/**
 * State Enum object.
 */
State.StatesEnum = {
  DEVICE_NONE: {
    cssState: 'device-detected-none',
  },
  DEVICE_USB: {
    cssState: 'device-detected-usb warning',
  },
  DEVICE_SD: {
    cssState: 'device-detected-sd warning',
  },
  DEVICE_MUL: {
    cssState: 'device-detected-mul warning',
  },
  ERROR_NO_NETWORK: {
    cssState: 'warning-no-conf',
  },
  ERROR_DEVICE_TOO_SMALL: {
    cssState: 'warning-no-conf',
  },
  PROGRESS_DOWNLOAD: {
    cssState: 'progress progress-canceble',
  },
  PROGRESS_UNZIP: {
    cssState: 'progress progress-canceble',
  },
  PROGRESS_BURN: {
    cssState: 'progress',
  },
  FAIL: {
    cssState: 'error',
  },
  SUCCESS: {
    cssState: 'success',
  },
};

State.prototype = {
  /**
   * Sets the state strings.
   * @param {Object} strings Localized state strings.
   */
  setStrings: function(strings) {
    State.StatesEnum.DEVICE_NONE.statusText =
        strings.getString('statusDevicesNone');
    State.StatesEnum.DEVICE_NONE.warningText =
        strings.getString('warningDevicesNone');
    State.StatesEnum.DEVICE_USB.statusText =
      strings.getString('statusDeviceUSB');
    State.StatesEnum.DEVICE_SD.statusText = strings.getString('statusDeviceSD');
    State.StatesEnum.DEVICE_MUL.statusText =
        strings.getString('statusDevicesMultiple');
    State.StatesEnum.ERROR_NO_NETWORK.statusText =
        strings.getString('statusNoConnection');
    State.StatesEnum.ERROR_NO_NETWORK.warningText =
        strings.getString('warningNoConnection');
    State.StatesEnum.ERROR_DEVICE_TOO_SMALL.statusText =
        strings.getString('statusNoSpace');
    State.StatesEnum.PROGRESS_DOWNLOAD.statusText =
        strings.getString('statusDownloading');
    State.StatesEnum.PROGRESS_UNZIP.statusText =
        strings.getString('statusUnzip');
    State.StatesEnum.PROGRESS_BURN.statusText = strings.getString('statusBurn');
    State.StatesEnum.FAIL.statusText = strings.getString('statusError');
    State.StatesEnum.SUCCESS.statusText = strings.getString('statusSuccess');
    State.StatesEnum.SUCCESS.warningText = strings.getString('warningSuccess');
  },

  /**
   * Changes the current state to new state.
   * @param {Object} newState Specifies the new state object.
   */
  changeState: function(newState) {
    if (newState == this.state)
      return;
    this.state = newState;

    $('main-content').className = this.state.cssState;

    $('status-text').textContent = this.state.statusText;

    if (newState.warningText)
      $('warning-text').textContent = this.state.warningText;

    if (this.isInitialState() && this.state != State.StatesEnum.DEVICE_NONE) {
      $('warning-button').textContent = localStrings.getString('confirmButton');
    } else if (this.state == State.StatesEnum.FAIL) {
      $('warning-button').textContent =
          localStrings.getString('retryButton');
    }
  },

  /**
   * Reset to initial state.
   * @param {Array} devices Array of device information.
   */
  gotoInitialState: function(devices) {
    if (devices.length == 0) {
      this.changeState(State.StatesEnum.DEVICE_NONE);
    } else if (devices.length == 1) {
      // If a device type is not specified for some reason, we should
      // default to display a USB device.
      var initialState = State.StatesEnum.DEVICE_USB;
      if (devices[0].type == 'sd')
        initialState = State.StatesEnum.DEVICE_SD;
      this.changeState(initialState);
    } else {
      this.changeState(State.StatesEnum.DEVICE_MUL);
    }
  },

  /**
   * Returns true if the device is in initial state.
   * @return {boolean} True if the device is in initial state else false.
   */
  isInitialState: function() {
    return this.state == State.StatesEnum.DEVICE_NONE ||
           this.state == State.StatesEnum.DEVICE_USB ||
           this.state == State.StatesEnum.DEVICE_SD ||
           this.state == State.StatesEnum.DEVICE_MUL;
  },

  /**
   * Returns true if device state matches the given state name.
   * @param {string} stateName Given state name.
   * @return {boolean} True if the device state matches the given state name.
   */
  equals: function(stateName) {
    return this.state == stateName;
  }
};

/**
 * Class that keeps track of available devices.
 * @constructor
 */
function DeviceSelection() {
  this.selectedDevice = undefined;
  this.devices = [];
}

DeviceSelection.prototype = {
  /**
   * Shows the currently selected device.
   */
  showDeviceSelection: function() {
    if (this.devices.length == 0) {
      this.selectedDevice = undefined;
    } else {
      this.selectDevice(this.devices[0].devicePath);
    }
  },

  /**
   * Handles device selected event.
   * @param {string} label Device label.
   * @param {string} filePath File path.
   * @param {string} devicePath Selected device path.
   */
  onDeviceSelected: function(label, filePath, devicePath) {
    $('warning-button').onclick =
        browserBridge.sendBurnImageMessage.bind(browserBridge, filePath,
            devicePath);

    this.selectedDevice = devicePath;

    $('warning-text').textContent =
        localStrings.getStringF('warningDevices', label);
  },

  /**
   * Selects the specified device based on the specified path.
   * @param {string} path Device path.
   */
  selectDevice: function(path) {
    var element = $('radio-' + path);
    element.checked = true;
    element.onclick.apply(element);
  },

  /**
   * Creates a new device element.
   * @param {Object} device Specifies new device information.
   * @return {HTMLLIElement} New device element.
   */
  createNewDeviceElement: function(device) {
    var element = document.createElement('li');
    var radioButton = document.createElement('input');
    radioButton.type = 'radio';
    radioButton.name = 'device';
    radioButton.value = device.label;
    radioButton.id = 'radio-' + device.devicePath;
    radioButton.className = 'float-start';
    var deviceLabelText = document.createElement('p');
    deviceLabelText.textContent = device.label;
    deviceLabelText.className = 'select-option float-start';
    var newLine = document.createElement('div');
    newLine.className = 'new-line';
    element.appendChild(radioButton);
    element.appendChild(deviceLabelText);
    element.appendChild(newLine);
    element.id = 'select-' + device.devicePath;
    element.className = 'selection-element';
    radioButton.onclick = this.onDeviceSelected.bind(this,
        device.label, device.filePath, device.devicePath);
    return element;
  },

  /**
   * Updates the list of selected devices.
   * @param {Array} devices List of devices.
   */
  devicesUpdated: function(newDevices) {
    this.devices = newDevices;
    var selectListDOM = $('device-selection');
    selectListDOM.innerHTML = '';
    if (this.devices.length > 0) {
      for (var i = 0; i < this.devices.length; i++) {
        var element = this.createNewDeviceElement(this.devices[i]);
        selectListDOM.appendChild(element);
      }
      this.selectDevice(this.devices[0].devicePath);
    } else {
      this.selectedDevice = undefined;
    }
  },

  /**
   * Handles device added event.
   * @param {Object} device Device information.
   * @param {boolean} allowSelect True to update the selected device info.
   */
  deviceAdded: function(device, allowSelect) {
    this.devices.push(device);
    var selectListDOM = $('device-selection');
    selectListDOM.appendChild(this.createNewDeviceElement(device));
    if (allowSelect && this.devices.length == 1)
      this.selectDevice(device.devicePath);
  },

  /**
   * Handles device removed event.
   * @param {string} devicePath Device path to be removed.
   * @param {boolean} allowSelect True to update the selected device info.
   */
  deviceRemoved: function(devicePath, allowSelect) {
    device = this.findDevice(devicePath);
    if (!device)
      return;
    this.devices.splice(this.devices.indexOf(device), 1);

    // Remove device selection element from DOM.
    var deviceSelectElement = $('select-' + devicePath);
    deviceSelectElement.parentNode.removeChild(deviceSelectElement);

    // Update selected device element.
    if (allowSelect) {
      if (this.devices.length > 0) {
        if (this.selectedDevice == devicePath)
          this.selectDevice(this.devices[0].devicePath);
      } else {
        this.selectedDevice = undefined;
      }
    }
  },

  /**
   * Finds device with given device path property.
   * @param {string} devicePath Device path of device to find.
   * @return {Object} Matching device information or undefined if not found.
   */
  findDevice: function(devicePath) {
    for (var i = 0; i < this.devices.length; ++i) {
      if (this.devices[i].devicePath == devicePath) {
        return this.devices[i];
      }
    }
    return undefined;
  }
};

/**
 * Class that handles communication with chrome.
 * @constructor
 */
function BrowserBridge() {
  this.currentState = new State(localStrings);
  this.deviceSelection = new DeviceSelection();
  // We will use these often so it makes sence making them class members to
  // avoid frequent document.getElementById calls.
  this.progressElement = $('progress-div');
  this.progressText = $('progress-text');
  this.progressTimeLeftText = $('pending-time');
}

BrowserBridge.prototype = {
  sendCancelMessage: function() {
    chrome.send('cancelBurnImage');
  },

  sendGetDevicesMessage: function() {
    chrome.send('getDevices');
  },

  sendWebuiInitializedMessage: function() {
    chrome.send('webuiInitialized');
  },

  /**
   * Sends the burn image message to c++ code.
   * @param {string} filePath Specifies the file path.
   * @param {string} devicePath Specifies the device path.
   */
  sendBurnImageMessage: function(filePath, devicePath) {
    chrome.send('burnImage', [devicePath, filePath]);
  },

  reportSuccess: function() {
    this.currentState.changeState(State.StatesEnum.SUCCESS);
  },

  /**
   * Update the device state to report a failure and display an error message to
   * the user.
   * @param {string} errorMessage Specifies the warning text message.
   */
  reportFail: function(errorMessage) {
    this.currentState.changeState(State.StatesEnum.FAIL);
    $('warning-text').textContent = errorMessage;
    $('warning-button').onclick = this.onBurnRetry.bind(this);
  },

  /**
   * Handles device added event.
   * @param {Object} device Device information.
   */
  deviceAdded: function(device) {
    var inInitialState = this.currentState.isInitialState();
    this.deviceSelection.deviceAdded(device, inInitialState);
    if (inInitialState)
      this.currentState.gotoInitialState(this.deviceSelection.devices);
  },

  /**
   * Handles device removed event.
   * @param {string} devicePath Device path to be removed.
   */
  deviceRemoved: function(devicePath) {
    var inInitialState = this.currentState.isInitialState();
    this.deviceSelection.deviceRemoved(devicePath, inInitialState);
    if (inInitialState)
      this.currentState.gotoInitialState(this.deviceSelection.devices);
  },

  /**
   * Gets device callbacks and update the current state.
   * @param {Array} devices List of devices.
   */
  getDevicesCallback: function(devices) {
    this.deviceSelection.devicesUpdated(devices);
    this.currentState.gotoInitialState(this.deviceSelection.devices);
    this.sendWebuiInitializedMessage();
  },

  /**
   *  Updates the progress information based on the signal received.
   *  @param {Object} updateSignal Specifies the signal information.
   */
  updateProgress: function(updateSignal) {
    if (updateSignal.progressType == 'download' &&
        !this.currentState.equals(State.StatesEnum.PROGRESS_DOWNLOAD)) {
      this.currentState.changeState(State.StatesEnum.PROGRESS_DOWNLOAD);
    } else if (updateSignal.progressType == 'unzip' &&
        !this.currentState.equals(State.StatesEnum.PROGRESS_UNZIP)) {
      this.currentState.changeState(State.StatesEnum.PROGRESS_UNZIP);
    } else if (updateSignal.progressType == 'burn' &&
        !this.currentState.equals(State.StatesEnum.PROGRESS_BURN)) {
      this.currentState.changeState(State.StatesEnum.PROGRESS_BURN);
    }

    if (!(updateSignal.amountTotal > 0)) {
      this.progressElement.removeAttribute('value');
    } else {
      this.progressElement.value = updateSignal.amountFinished;
      this.progressElement.max = updateSignal.amountTotal;
    }

    this.progressText.textContent = updateSignal.progressText;
    this.progressTimeLeftText.textContent = updateSignal.timeLeftText;
  },

  reportNoNetwork: function() {
    this.currentState.changeState(State.StatesEnum.ERROR_NO_NETWORK);
  },

  reportNetworkDetected: function() {
    if (this.currentState.equals(State.StatesEnum.ERROR_NO_NETWORK)) {
      this.deviceSelection.showDeviceSelection();
      this.currentState.gotoInitialState(this.deviceSelection.devices);
    }
  },

  /**
   *  Updates the current state to report device too small error.
   *  @param {number} deviceSize Received device size.
   */
  reportDeviceTooSmall: function(deviceSize) {
    this.currentState.changeState(State.StatesEnum.ERROR_DEVICE_TOO_SMALL);
    $('warning-text').textContent =
        localStrings.getStringF('warningNoSpace', deviceSize);
  },

  /**
   * Processes click on 'Retry' button in FAIL state.
   */
  onBurnRetry: function() {
    this.deviceSelection.showDeviceSelection();
    this.currentState.gotoInitialState(this.deviceSelection.devices);
  }
};

document.addEventListener('DOMContentLoaded', function() {
  localStrings = new LocalStrings();
  browserBridge = new BrowserBridge();

  jstProcess(new JsEvalContext(templateData), $('more-info-link'));

  $('cancel-button').onclick =
      browserBridge.sendCancelMessage.bind(browserBridge);
  browserBridge.sendGetDevicesMessage();
});