<commit_before>// Copyright 2019 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 {BackgroundServiceModel, Events} from './BackgroundServiceModel.js';  // eslint-disable-line no-unused-vars

export class BackgroundServiceView extends UI.VBox {
  /**
   * @param {string} serviceName The name of the background service.
   * @return {string} The UI String to display.
   */
  static getUIString(serviceName) {
    switch (serviceName) {
      case Protocol.BackgroundService.ServiceName.BackgroundFetch:
        return ls`Background Fetch`;
      case Protocol.BackgroundService.ServiceName.BackgroundSync:
        return ls`Background Sync`;
      case Protocol.BackgroundService.ServiceName.PushMessaging:
        return ls`Push Messaging`;
      case Protocol.BackgroundService.ServiceName.Notifications:
        return ls`Notifications`;
      case Protocol.BackgroundService.ServiceName.PaymentHandler:
        return ls`Payment Handler`;
      case Protocol.BackgroundService.ServiceName.PeriodicBackgroundSync:
        return ls`Periodic Background Sync`;
      default:
        return '';
    }
  }

  /**
   * @param {!Protocol.BackgroundService.ServiceName} serviceName
   * @param {!BackgroundServiceModel} model
   */
  constructor(serviceName, model) {
    super(true);
    this.registerRequiredCSS('resources/backgroundServiceView.css');
    this.registerRequiredCSS('ui/emptyWidget.css');

    /** @const {!Protocol.BackgroundService.ServiceName} */
    this._serviceName = serviceName;

    /** @const {!BackgroundServiceModel} */
    this._model = model;
    this._model.addEventListener(Events.RecordingStateChanged, this._onRecordingStateChanged, this);
    this._model.addEventListener(Events.BackgroundServiceEventReceived, this._onEventReceived, this);
    this._model.enable(this._serviceName);

    /** @const {?SDK.ServiceWorkerManager} */
    this._serviceWorkerManager = this._model.target().model(SDK.ServiceWorkerManager);

    /** @const {?SDK.SecurityOriginManager} */
    this._securityOriginManager = this._model.target().model(SDK.SecurityOriginManager);
    this._securityOriginManager.addEventListener(
        SDK.SecurityOriginManager.Events.MainSecurityOriginChanged, () => this._onOriginChanged());

    /** @const {!UI.Action} */
    this._recordAction =
        /** @type {!UI.Action} */ (self.UI.actionRegistry.action('background-service.toggle-recording'));
    /** @type {?UI.ToolbarButton} */
    this._recordButton = null;

    /** @type {?UI.ToolbarCheckbox} */
    this._originCheckbox = null;

    /** @type {?UI.ToolbarButton} */
    this._saveButton = null;

    /** @const {!UI.Toolbar} */
    this._toolbar = new UI.Toolbar('background-service-toolbar', this.contentElement);
    this._setupToolbar();

    /**
     * This will contain the DataGrid for displaying events, and a panel at the bottom for showing
     * extra metadata related to the selected event.
     * @const {!UI.SplitWidget}
     */
    this._splitWidget = new UI.SplitWidget(/* isVertical= */ false, /* secondIsSidebar= */ true);
    this._splitWidget.show(this.contentElement);

    /** @const {!DataGrid.DataGrid} */
    this._dataGrid = this._createDataGrid();

    /** @const {!UI.VBox} */
    this._previewPanel = new UI.VBox();

    /** @type {?EventDataNode} */
    this._selectedEventNode = null;

    /** @type {?UI.Widget} */
    this._preview = null;

    this._splitWidget.setMainWidget(this._dataGrid.asWidget());
    this._splitWidget.setSidebarWidget(this._previewPanel);

    this._showPreview(null);
  }

  /**
   * Creates the toolbar UI element.
   */
  async _setupToolbar() {
    this._recordButton = UI.Toolbar.createActionButton(this._recordAction);
    this._toolbar.appendToolbarItem(this._recordButton);

    const clearButton = new UI.ToolbarButton(ls`Clear`, 'largeicon-clear');
    clearButton.addEventListener(UI.ToolbarButton.Events.Click, () => this._clearEvents());
    this._toolbar.appendToolbarItem(clearButton);

    this._toolbar.appendSeparator();

    this._saveButton = new UI.ToolbarButton(ls`Save events`, 'largeicon-download');
    this._saveButton.addEventListener(UI.ToolbarButton.Events.Click, () => this._saveToFile());
    this._saveButton.setEnabled(false);
    this._toolbar.appendToolbarItem(this._saveButton);

    this._toolbar.appendSeparator();

    this._originCheckbox =
        new UI.ToolbarCheckbox(ls`Show events from other domains`, undefined, () => this._refreshView());
    this._toolbar.appendToolbarItem(this._originCheckbox);
  }

  /**
   * Displays all available events in the grid.
   */
  _refreshView() {
    this._clearView();
    const events = this._model.getEvents(this._serviceName).filter(event => this._acceptEvent(event));
    for (const event of events) {
      this._addEvent(event);
    }
  }

  /**
   * Clears the grid and panel.
   */
  _clearView() {
    this._selectedEventNode = null;
    this._dataGrid.rootNode().removeChildren();
    this._saveButton.setEnabled(false);
    this._showPreview(null);
  }

  /**
   * Called when the `Toggle Record` button is clicked.
   */
  _toggleRecording() {
    this._model.setRecording(!this._recordButton.toggled(), this._serviceName);
  }

  /**
   * Called when the `Clear` button is clicked.
   */
  _clearEvents() {
    this._model.clearEvents(this._serviceName);
    this._clearView();
  }

  /**
   * @param {!Common.Event} event
   */
  _onRecordingStateChanged(event) {
    const state = /** @type {!Resources.BackgroundServiceModel.RecordingState} */ (event.data);
    if (state.serviceName !== this._serviceName) {
      return;
    }

    if (state.isRecording === this._recordButton.toggled()) {
      return;
    }

    this._recordButton.setToggled(state.isRecording);
    this._showPreview(this._selectedEventNode);
  }

  /**
   * @param {!Common.Event} event
   */
  _onEventReceived(event) {
    const serviceEvent = /** @type {!Protocol.BackgroundService.BackgroundServiceEvent} */ (event.data);
    if (!this._acceptEvent(serviceEvent)) {
      return;
    }
    this._addEvent(serviceEvent);
  }

  _onOriginChanged() {
    // No need to refresh the view if we are already showing all events.
    if (this._originCheckbox.checked()) {
      return;
    }
    this._refreshView();
  }

  /**
   * @param {!Protocol.BackgroundService.BackgroundServiceEvent} serviceEvent
   */
  _addEvent(serviceEvent) {
    const data = this._createEventData(serviceEvent);
    const dataNode = new EventDataNode(data, serviceEvent.eventMetadata);
    this._dataGrid.rootNode().appendChild(dataNode);

    if (this._dataGrid.rootNode().children.length === 1) {
      this._saveButton.setEnabled(true);
      this._showPreview(this._selectedEventNode);
    }
  }

  /**
   * @return {!DataGrid.DataGrid}
   */
  _createDataGrid() {
    const columns = /** @type {!Array<!DataGrid.ColumnDescriptor>} */ ([
      {id: 'id', title: ls`#`, weight: 1},
      {id: 'timestamp', title: ls`Timestamp`, weight: 8},
      {id: 'eventName', title: ls`Event`, weight: 10},
      {id: 'origin', title: ls`Origin`, weight: 10},
      {id: 'swScope', title: ls`SW Scope`, weight: 2},
      {id: 'instanceId', title: ls`Instance ID`, weight: 10},
    ]);
    const dataGrid = new DataGrid.DataGrid({displayName: ls`Background Services`, columns});
    dataGrid.setStriped(true);

    dataGrid.addEventListener(
        DataGrid.DataGrid.Events.SelectedNode, event => this._showPreview(/** @type {!EventDataNode} */ (event.data)));

    return dataGrid;
  }

  /**
   * Creates the data object to pass to the DataGrid Node.
   * @param {!Protocol.BackgroundService.BackgroundServiceEvent} serviceEvent
   * @return {!Resources.BackgroundServiceView.EventData}
   */
  _createEventData(serviceEvent) {
    let swScope = '';

    // Try to get the scope of the Service Worker registration to be more user-friendly.
    const registration = this._serviceWorkerManager.registrations().get(serviceEvent.serviceWorkerRegistrationId);
    if (registration) {
      swScope = registration.scopeURL.substr(registration.securityOrigin.length);
    }

    return {
      id: this._dataGrid.rootNode().children.length + 1,
      timestamp: UI.formatTimestamp(serviceEvent.timestamp * 1000, /* full= */ true),
      origin: serviceEvent.origin,
      swScope,
      eventName: serviceEvent.eventName,
      instanceId: serviceEvent.instanceId,
    };
  }

  /**
   * Filtration function to know whether event should be shown or not.
   * @param {!Protocol.BackgroundService.BackgroundServiceEvent} event
   * @return {boolean}
   */
  _acceptEvent(event) {
    if (event.service !== this._serviceName) {
      return false;
    }

    if (this._originCheckbox.checked()) {
      return true;
    }

    // Trim the trailing '/'.
    const origin = event.origin.substr(0, event.origin.length - 1);

    return this._securityOriginManager.securityOrigins().includes(origin);
  }

  /**
   * @return {!Element}
   */
  _createLearnMoreLink() {
    let url =
        'https://developers.google.com/web/tools/chrome-devtools/javascript/background-services?utm_source=devtools';

    switch (this._serviceName) {
      case Protocol.BackgroundService.ServiceName.BackgroundFetch:
        url += '#fetch';
        break;
      case Protocol.BackgroundService.ServiceName.BackgroundSync:
        url += '#sync';
        break;
      case Protocol.BackgroundService.ServiceName.PushMessaging:
        url += '#push';
        break;
      case Protocol.BackgroundService.ServiceName.Notifications:
        url += '#notifications';
        break;
      default:
        break;
    }

    return UI.XLink.create(url, ls`Learn more`);
  }

  /**
   * @param {?EventDataNode} dataNode
   */
  _showPreview(dataNode) {
    if (this._selectedEventNode && this._selectedEventNode === dataNode) {
      return;
    }

    this._selectedEventNode = dataNode;

    if (this._preview) {
      this._preview.detach();
    }

    if (this._selectedEventNode) {
      this._preview = this._selectedEventNode.createPreview();
      this._preview.show(this._previewPanel.contentElement);
      return;
    }

    this._preview = new UI.VBox();
    this._preview.contentElement.classList.add('background-service-preview', 'fill');
    const centered = this._preview.contentElement.createChild('div');

    if (this._dataGrid.rootNode().children.length) {
      // Inform users that grid entries are clickable.
      centered.createChild('p').textContent = ls`Select an entry to view metadata`;
    } else if (this._recordButton.toggled()) {
      // Inform users that we are recording/waiting for events.
      const featureName = BackgroundServiceView.getUIString(this._serviceName);
      centered.createChild('p').textContent = ls`Recording ${featureName} activity...`;
      centered.createChild('p').textContent =
          ls`DevTools will record all ${featureName} activity for up to 3 days, even when closed.`;
    } else {
      const landingRecordButton = UI.Toolbar.createActionButton(this._recordAction);

      const recordKey = createElementWithClass('b', 'background-service-shortcut');
      recordKey.textContent =
          self.UI.shortcutRegistry.shortcutDescriptorsForAction('background-service.toggle-recording')[0].name;

      const inlineButton = UI.createInlineButton(landingRecordButton);
      inlineButton.classList.add('background-service-record-inline-button');
      centered.createChild('p').appendChild(
          UI.formatLocalized('Click the record button %s or hit %s to start recording.', [inlineButton, recordKey]));

      centered.appendChild(this._createLearnMoreLink());
    }

    this._preview.show(this._previewPanel.contentElement);
  }

  /**
   * Saves all currently displayed events in a file (JSON format).
   */
  async _saveToFile() {
    const fileName = `${this._serviceName}-${new Date().toISO8601Compact()}.json`;
    const stream = new Bindings.FileOutputStream();

    const accepted = await stream.open(fileName);
    if (!accepted) {
      return;
    }

    const events = this._model.getEvents(this._serviceName).filter(event => this._acceptEvent(event));
    await stream.write(JSON.stringify(events, undefined, 2));
    stream.close();
  }
}

export class EventDataNode extends DataGrid.DataGridNode {
  /**
   * @param {!Object<string, string>} data
   * @param {!Array<!Protocol.BackgroundService.EventMetadata>} eventMetadata
   */
  constructor(data, eventMetadata) {
    super(data);

    /** @const {!Array<!Protocol.BackgroundService.EventMetadata>} */
    this._eventMetadata = eventMetadata.sort((m1, m2) => m1.key.compareTo(m2.key));
  }

  /**
   * @return {!UI.VBox}
   */
  createPreview() {
    const preview = new UI.VBox();
    preview.element.classList.add('background-service-metadata');

    for (const entry of this._eventMetadata) {
      const div = createElementWithClass('div', 'background-service-metadata-entry');
      div.createChild('div', 'background-service-metadata-name').textContent = entry.key + ': ';
      if (entry.value) {
        div.createChild('div', 'background-service-metadata-value source-code').textContent = entry.value;
      } else {
        div.createChild('div', 'background-service-metadata-value background-service-empty-value').textContent =
            ls`empty`;
      }
      preview.element.appendChild(div);
    }

    if (!preview.element.children.length) {
      const div = createElementWithClass('div', 'background-service-metadata-entry');
      div.createChild('div', 'background-service-metadata-name').textContent = ls`No metadata for this event`;
      preview.element.appendChild(div);
    }

    return preview;
  }
}

/**
 * @implements {UI.ActionDelegate}
 * @unrestricted
 */
export class ActionDelegate {
  /**
   * @override
   * @param {!UI.Context} context
   * @param {string} actionId
   * @return {boolean}
   */
  handleAction(context, actionId) {
    const view = context.flavor(BackgroundServiceView);
    switch (actionId) {
      case 'background-service.toggle-recording':
        view._toggleRecording();
        return true;
    }
    return false;
  }
}
<commit_msg>[BackgroundService] Show tooltip over `other domains` checkbox<commit_after>// Copyright 2019 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 {BackgroundServiceModel, Events} from './BackgroundServiceModel.js';  // eslint-disable-line no-unused-vars

export class BackgroundServiceView extends UI.VBox {
  /**
   * @param {string} serviceName The name of the background service.
   * @return {string} The UI String to display.
   */
  static getUIString(serviceName) {
    switch (serviceName) {
      case Protocol.BackgroundService.ServiceName.BackgroundFetch:
        return ls`Background Fetch`;
      case Protocol.BackgroundService.ServiceName.BackgroundSync:
        return ls`Background Sync`;
      case Protocol.BackgroundService.ServiceName.PushMessaging:
        return ls`Push Messaging`;
      case Protocol.BackgroundService.ServiceName.Notifications:
        return ls`Notifications`;
      case Protocol.BackgroundService.ServiceName.PaymentHandler:
        return ls`Payment Handler`;
      case Protocol.BackgroundService.ServiceName.PeriodicBackgroundSync:
        return ls`Periodic Background Sync`;
      default:
        return '';
    }
  }

  /**
   * @param {!Protocol.BackgroundService.ServiceName} serviceName
   * @param {!BackgroundServiceModel} model
   */
  constructor(serviceName, model) {
    super(true);
    this.registerRequiredCSS('resources/backgroundServiceView.css');
    this.registerRequiredCSS('ui/emptyWidget.css');

    /** @const {!Protocol.BackgroundService.ServiceName} */
    this._serviceName = serviceName;

    /** @const {!BackgroundServiceModel} */
    this._model = model;
    this._model.addEventListener(Events.RecordingStateChanged, this._onRecordingStateChanged, this);
    this._model.addEventListener(Events.BackgroundServiceEventReceived, this._onEventReceived, this);
    this._model.enable(this._serviceName);

    /** @const {?SDK.ServiceWorkerManager} */
    this._serviceWorkerManager = this._model.target().model(SDK.ServiceWorkerManager);

    /** @const {?SDK.SecurityOriginManager} */
    this._securityOriginManager = this._model.target().model(SDK.SecurityOriginManager);
    this._securityOriginManager.addEventListener(
        SDK.SecurityOriginManager.Events.MainSecurityOriginChanged, () => this._onOriginChanged());

    /** @const {!UI.Action} */
    this._recordAction =
        /** @type {!UI.Action} */ (self.UI.actionRegistry.action('background-service.toggle-recording'));
    /** @type {?UI.ToolbarButton} */
    this._recordButton = null;

    /** @type {?UI.ToolbarCheckbox} */
    this._originCheckbox = null;

    /** @type {?UI.ToolbarButton} */
    this._saveButton = null;

    /** @const {!UI.Toolbar} */
    this._toolbar = new UI.Toolbar('background-service-toolbar', this.contentElement);
    this._setupToolbar();

    /**
     * This will contain the DataGrid for displaying events, and a panel at the bottom for showing
     * extra metadata related to the selected event.
     * @const {!UI.SplitWidget}
     */
    this._splitWidget = new UI.SplitWidget(/* isVertical= */ false, /* secondIsSidebar= */ true);
    this._splitWidget.show(this.contentElement);

    /** @const {!DataGrid.DataGrid} */
    this._dataGrid = this._createDataGrid();

    /** @const {!UI.VBox} */
    this._previewPanel = new UI.VBox();

    /** @type {?EventDataNode} */
    this._selectedEventNode = null;

    /** @type {?UI.Widget} */
    this._preview = null;

    this._splitWidget.setMainWidget(this._dataGrid.asWidget());
    this._splitWidget.setSidebarWidget(this._previewPanel);

    this._showPreview(null);
  }

  /**
   * Creates the toolbar UI element.
   */
  async _setupToolbar() {
    this._recordButton = UI.Toolbar.createActionButton(this._recordAction);
    this._toolbar.appendToolbarItem(this._recordButton);

    const clearButton = new UI.ToolbarButton(ls`Clear`, 'largeicon-clear');
    clearButton.addEventListener(UI.ToolbarButton.Events.Click, () => this._clearEvents());
    this._toolbar.appendToolbarItem(clearButton);

    this._toolbar.appendSeparator();

    this._saveButton = new UI.ToolbarButton(ls`Save events`, 'largeicon-download');
    this._saveButton.addEventListener(UI.ToolbarButton.Events.Click, () => this._saveToFile());
    this._saveButton.setEnabled(false);
    this._toolbar.appendToolbarItem(this._saveButton);

    this._toolbar.appendSeparator();

    this._originCheckbox = new UI.ToolbarCheckbox(
        ls`Show events from other domains`, ls`Show events from other domains`, () => this._refreshView());
    this._toolbar.appendToolbarItem(this._originCheckbox);
  }

  /**
   * Displays all available events in the grid.
   */
  _refreshView() {
    this._clearView();
    const events = this._model.getEvents(this._serviceName).filter(event => this._acceptEvent(event));
    for (const event of events) {
      this._addEvent(event);
    }
  }

  /**
   * Clears the grid and panel.
   */
  _clearView() {
    this._selectedEventNode = null;
    this._dataGrid.rootNode().removeChildren();
    this._saveButton.setEnabled(false);
    this._showPreview(null);
  }

  /**
   * Called when the `Toggle Record` button is clicked.
   */
  _toggleRecording() {
    this._model.setRecording(!this._recordButton.toggled(), this._serviceName);
  }

  /**
   * Called when the `Clear` button is clicked.
   */
  _clearEvents() {
    this._model.clearEvents(this._serviceName);
    this._clearView();
  }

  /**
   * @param {!Common.Event} event
   */
  _onRecordingStateChanged(event) {
    const state = /** @type {!Resources.BackgroundServiceModel.RecordingState} */ (event.data);
    if (state.serviceName !== this._serviceName) {
      return;
    }

    if (state.isRecording === this._recordButton.toggled()) {
      return;
    }

    this._recordButton.setToggled(state.isRecording);
    this._showPreview(this._selectedEventNode);
  }

  /**
   * @param {!Common.Event} event
   */
  _onEventReceived(event) {
    const serviceEvent = /** @type {!Protocol.BackgroundService.BackgroundServiceEvent} */ (event.data);
    if (!this._acceptEvent(serviceEvent)) {
      return;
    }
    this._addEvent(serviceEvent);
  }

  _onOriginChanged() {
    // No need to refresh the view if we are already showing all events.
    if (this._originCheckbox.checked()) {
      return;
    }
    this._refreshView();
  }

  /**
   * @param {!Protocol.BackgroundService.BackgroundServiceEvent} serviceEvent
   */
  _addEvent(serviceEvent) {
    const data = this._createEventData(serviceEvent);
    const dataNode = new EventDataNode(data, serviceEvent.eventMetadata);
    this._dataGrid.rootNode().appendChild(dataNode);

    if (this._dataGrid.rootNode().children.length === 1) {
      this._saveButton.setEnabled(true);
      this._showPreview(this._selectedEventNode);
    }
  }

  /**
   * @return {!DataGrid.DataGrid}
   */
  _createDataGrid() {
    const columns = /** @type {!Array<!DataGrid.ColumnDescriptor>} */ ([
      {id: 'id', title: ls`#`, weight: 1},
      {id: 'timestamp', title: ls`Timestamp`, weight: 8},
      {id: 'eventName', title: ls`Event`, weight: 10},
      {id: 'origin', title: ls`Origin`, weight: 10},
      {id: 'swScope', title: ls`SW Scope`, weight: 2},
      {id: 'instanceId', title: ls`Instance ID`, weight: 10},
    ]);
    const dataGrid = new DataGrid.DataGrid({displayName: ls`Background Services`, columns});
    dataGrid.setStriped(true);

    dataGrid.addEventListener(
        DataGrid.DataGrid.Events.SelectedNode, event => this._showPreview(/** @type {!EventDataNode} */ (event.data)));

    return dataGrid;
  }

  /**
   * Creates the data object to pass to the DataGrid Node.
   * @param {!Protocol.BackgroundService.BackgroundServiceEvent} serviceEvent
   * @return {!Resources.BackgroundServiceView.EventData}
   */
  _createEventData(serviceEvent) {
    let swScope = '';

    // Try to get the scope of the Service Worker registration to be more user-friendly.
    const registration = this._serviceWorkerManager.registrations().get(serviceEvent.serviceWorkerRegistrationId);
    if (registration) {
      swScope = registration.scopeURL.substr(registration.securityOrigin.length);
    }

    return {
      id: this._dataGrid.rootNode().children.length + 1,
      timestamp: UI.formatTimestamp(serviceEvent.timestamp * 1000, /* full= */ true),
      origin: serviceEvent.origin,
      swScope,
      eventName: serviceEvent.eventName,
      instanceId: serviceEvent.instanceId,
    };
  }

  /**
   * Filtration function to know whether event should be shown or not.
   * @param {!Protocol.BackgroundService.BackgroundServiceEvent} event
   * @return {boolean}
   */
  _acceptEvent(event) {
    if (event.service !== this._serviceName) {
      return false;
    }

    if (this._originCheckbox.checked()) {
      return true;
    }

    // Trim the trailing '/'.
    const origin = event.origin.substr(0, event.origin.length - 1);

    return this._securityOriginManager.securityOrigins().includes(origin);
  }

  /**
   * @return {!Element}
   */
  _createLearnMoreLink() {
    let url =
        'https://developers.google.com/web/tools/chrome-devtools/javascript/background-services?utm_source=devtools';

    switch (this._serviceName) {
      case Protocol.BackgroundService.ServiceName.BackgroundFetch:
        url += '#fetch';
        break;
      case Protocol.BackgroundService.ServiceName.BackgroundSync:
        url += '#sync';
        break;
      case Protocol.BackgroundService.ServiceName.PushMessaging:
        url += '#push';
        break;
      case Protocol.BackgroundService.ServiceName.Notifications:
        url += '#notifications';
        break;
      default:
        break;
    }

    return UI.XLink.create(url, ls`Learn more`);
  }

  /**
   * @param {?EventDataNode} dataNode
   */
  _showPreview(dataNode) {
    if (this._selectedEventNode && this._selectedEventNode === dataNode) {
      return;
    }

    this._selectedEventNode = dataNode;

    if (this._preview) {
      this._preview.detach();
    }

    if (this._selectedEventNode) {
      this._preview = this._selectedEventNode.createPreview();
      this._preview.show(this._previewPanel.contentElement);
      return;
    }

    this._preview = new UI.VBox();
    this._preview.contentElement.classList.add('background-service-preview', 'fill');
    const centered = this._preview.contentElement.createChild('div');

    if (this._dataGrid.rootNode().children.length) {
      // Inform users that grid entries are clickable.
      centered.createChild('p').textContent = ls`Select an entry to view metadata`;
    } else if (this._recordButton.toggled()) {
      // Inform users that we are recording/waiting for events.
      const featureName = BackgroundServiceView.getUIString(this._serviceName);
      centered.createChild('p').textContent = ls`Recording ${featureName} activity...`;
      centered.createChild('p').textContent =
          ls`DevTools will record all ${featureName} activity for up to 3 days, even when closed.`;
    } else {
      const landingRecordButton = UI.Toolbar.createActionButton(this._recordAction);

      const recordKey = createElementWithClass('b', 'background-service-shortcut');
      recordKey.textContent =
          self.UI.shortcutRegistry.shortcutDescriptorsForAction('background-service.toggle-recording')[0].name;

      const inlineButton = UI.createInlineButton(landingRecordButton);
      inlineButton.classList.add('background-service-record-inline-button');
      centered.createChild('p').appendChild(
          UI.formatLocalized('Click the record button %s or hit %s to start recording.', [inlineButton, recordKey]));

      centered.appendChild(this._createLearnMoreLink());
    }

    this._preview.show(this._previewPanel.contentElement);
  }

  /**
   * Saves all currently displayed events in a file (JSON format).
   */
  async _saveToFile() {
    const fileName = `${this._serviceName}-${new Date().toISO8601Compact()}.json`;
    const stream = new Bindings.FileOutputStream();

    const accepted = await stream.open(fileName);
    if (!accepted) {
      return;
    }

    const events = this._model.getEvents(this._serviceName).filter(event => this._acceptEvent(event));
    await stream.write(JSON.stringify(events, undefined, 2));
    stream.close();
  }
}

export class EventDataNode extends DataGrid.DataGridNode {
  /**
   * @param {!Object<string, string>} data
   * @param {!Array<!Protocol.BackgroundService.EventMetadata>} eventMetadata
   */
  constructor(data, eventMetadata) {
    super(data);

    /** @const {!Array<!Protocol.BackgroundService.EventMetadata>} */
    this._eventMetadata = eventMetadata.sort((m1, m2) => m1.key.compareTo(m2.key));
  }

  /**
   * @return {!UI.VBox}
   */
  createPreview() {
    const preview = new UI.VBox();
    preview.element.classList.add('background-service-metadata');

    for (const entry of this._eventMetadata) {
      const div = createElementWithClass('div', 'background-service-metadata-entry');
      div.createChild('div', 'background-service-metadata-name').textContent = entry.key + ': ';
      if (entry.value) {
        div.createChild('div', 'background-service-metadata-value source-code').textContent = entry.value;
      } else {
        div.createChild('div', 'background-service-metadata-value background-service-empty-value').textContent =
            ls`empty`;
      }
      preview.element.appendChild(div);
    }

    if (!preview.element.children.length) {
      const div = createElementWithClass('div', 'background-service-metadata-entry');
      div.createChild('div', 'background-service-metadata-name').textContent = ls`No metadata for this event`;
      preview.element.appendChild(div);
    }

    return preview;
  }
}

/**
 * @implements {UI.ActionDelegate}
 * @unrestricted
 */
export class ActionDelegate {
  /**
   * @override
   * @param {!UI.Context} context
   * @param {string} actionId
   * @return {boolean}
   */
  handleAction(context, actionId) {
    const view = context.flavor(BackgroundServiceView);
    switch (actionId) {
      case 'background-service.toggle-recording':
        view._toggleRecording();
        return true;
    }
    return false;
  }
}
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2017 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.INTERFACE({
  refines: 'foam.blob.Blob',

  methods: [
    {
      name: 'read',
      javaReturns: 'int',
      args: [
        {
          name: 'out',
          javaType: 'java.io.OutputStream'
        },
        {
          name: 'offset',
          javaType: 'int'
        },
        {
          name: 'length',
          javaType: 'int'
        }
      ]
    },
    {
      name: 'getSize',
      javaReturns: 'int'
    }
  ]
});

foam.INTERFACE({
  refines: 'foam.blob.BlobService',

  methods: [
    {
      name: 'put',
      javaReturns: 'foam.blob.Blob',
      args: [
        {
          name: 'blob',
          javaType: 'foam.blob.Blob'
        }
      ]
    },
    {
      name: 'put_',
      javaReturns: 'foam.blob.Blob',
      args: [
        {
          name: 'x',
          javaType: 'foam.core.X'
        },
        {
          name: 'blob',
          javaType: 'foam.blob.Blob'
        }
      ]
    },
    {
      name: 'find',
      javaReturns: 'foam.blob.Blob',
      args: [
        {
          name: 'id',
          javaType: 'Object'
        }
      ]
    },
    {
      name: 'find_',
      javaReturns: 'foam.blob.Blob',
      args: [
        {
          name: 'x',
          javaType: 'foam.core.X'
        },
        {
          name: 'id',
          javaType: 'Object'
        }
      ]
    },
    {
      name: 'urlFor',
      javaReturns: 'String',
      args: [
        {
          name: 'blob',
          javaType: 'foam.blob.Blob'
        }
      ]
    },
    {
      name: 'urlFor_',
      javaReturns: 'String',
      args: [
        {
          name: 'x',
          javaType: 'foam.core.X'
        },
        {
          name: 'blob',
          javaType: 'foam.blob.Blob'
        }
      ]
    }
  ]
});

foam.CLASS({
  refines: 'foam.blob.AbstractBlob',

  methods: [
    {
      name: 'pipe',
      javaCode: 'return;'
    },
    {
      name: 'slice',
      javaReturns: 'foam.blob.Blob',
      args: [
        {
          name: 'offset',
          javaType: 'int'
        },
        {
          name: 'length',
          javaType: 'int'
        }
      ],
      javaCode: 'return new SubBlob(this, offset, length);'
    }
  ]
});

foam.CLASS({
  refines: 'foam.blob.AbstractBlobService',

  methods: [
    {
      name: 'put',
      javaCode: 'return this.put_(getX(), blob);'
    },
    {
      name: 'find',
      javaCode: 'return this.find_(getX(), id);'
    },
    {
      name: 'urlFor',
      javaCode: 'return this.urlFor_(getX(), blob);'
    }
  ]
});

foam.CLASS({
  refines: 'foam.blob.SubBlob',

  methods: [
    {
      name: 'read',
      javaCode:
`length = Math.min(length, getSize() - offset);
return getParent().read(out, offset, length);`
    },
    {
      name: 'slice',
      javaCode: 'return new SubBlob(getParent(), getOffset() + offset, length);'
    }
  ]
});

foam.CLASS({
  refines: 'foam.blob.BlobStore',

  javaImports: [
    'org.apache.commons.io.IOUtils',
    'org.apache.geronimo.mail.util.Hex',
    'java.io.File',
    'java.io.FileOutputStream'
  ],

  constants: [
    {
      name: 'BUFFER_SIZE',
      value: 8192,
      type: 'int'
    }
  ],

  properties: [
    { class: 'String', name: 'tmp', javaFactory: 'return getRoot() + File.separator + "tmp";' },
    { class: 'String', name: 'sha256', javaFactory: 'return getRoot() + File.separator + "sha256";' }
  ],

  methods: [
    {
      name: 'put_',
      javaCode:
`if ( blob instanceof IdentifiedBlob ) {
  return blob;
}

this.setup();

OutputStream os = null;

try {
  int size = blob.getSize();
  File tmp = allocateTmp(1);
  os = new HashingOutputStream(new FileOutputStream(tmp));
  blob.read(os, 0, size);
  os.close();

  String digest = new String(Hex.encode(os.digest()));
  File dest = new File(sha256_ + File.separator + digest);
  tmp.renameTo(dest);

  IdentifiedBlob result = new IdentifiedBlob();
  result.setId(digest);
  result.setX(getX());
  return result;
} catch (Throwable t) {
  return null;
} finally {
  IOUtils.closeQuietly(os);
}`
    },
    {
      name: 'find_',
      javaCode:
`try {
  this.setup();
  if ( ((String) id).indexOf(File.separatorChar) != -1 ) {
    throw new RuntimeException("Invalid file name");
  }

  File file = new File(getSha256() + File.separator + id);
  if ( ! file.exists() ) {
    throw new RuntimeException("File does not exist");
  }

  if ( ! file.canRead() ) {
    throw new RuntimeException("Cannot read file");
  }

  return new FileBlob(file);
} catch (Throwable t) {
  throw new RuntimeException(t);
}`
    },
    {
      name: 'urlFor_',
      javaCode: 'throw new UnsupportedOperationException("Unsupported operation: urlFor_");'
    },
    {
      name: 'setup',
      javaReturns: 'void',
      javaCode:
`if ( this.getIsSet() )
  return;
ensureDir(getRoot());
ensureDir(getTmp());
ensureDir(getSha256());
setIsSet(true);`
    },
    {
      name: 'ensureDir',
      javaReturns: 'void',
      args: [
        {
          name: 'path',
          javaType: 'String'
        }
      ],
      javaCode:
`File parsed = new File(path);
if ( parsed.exists() && parsed.isDirectory() ) {
  return;
}

if ( ! parsed.mkdirs() ) {
  throw new RuntimeException("Failed to create: " + path);
}`
    },
    {
      name: 'allocateTmp',
      javaReturns: 'File',
      args: [
        {
          name: 'name',
          javaType: 'long'
        }
      ],
      javaCode:
`String path = this.tmp_ + File.separator + (name++);
File file = new File(path);
if ( file.exists() ) {
  return allocateTmp(name);
}
return file;`
    }
  ]
});
<commit_msg>Changed os to HashingOutputStream<commit_after>/**
 * @license
 * Copyright 2017 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.INTERFACE({
  refines: 'foam.blob.Blob',

  methods: [
    {
      name: 'read',
      javaReturns: 'int',
      args: [
        {
          name: 'out',
          javaType: 'java.io.OutputStream'
        },
        {
          name: 'offset',
          javaType: 'int'
        },
        {
          name: 'length',
          javaType: 'int'
        }
      ]
    },
    {
      name: 'getSize',
      javaReturns: 'int'
    }
  ]
});

foam.INTERFACE({
  refines: 'foam.blob.BlobService',

  methods: [
    {
      name: 'put',
      javaReturns: 'foam.blob.Blob',
      args: [
        {
          name: 'blob',
          javaType: 'foam.blob.Blob'
        }
      ]
    },
    {
      name: 'put_',
      javaReturns: 'foam.blob.Blob',
      args: [
        {
          name: 'x',
          javaType: 'foam.core.X'
        },
        {
          name: 'blob',
          javaType: 'foam.blob.Blob'
        }
      ]
    },
    {
      name: 'find',
      javaReturns: 'foam.blob.Blob',
      args: [
        {
          name: 'id',
          javaType: 'Object'
        }
      ]
    },
    {
      name: 'find_',
      javaReturns: 'foam.blob.Blob',
      args: [
        {
          name: 'x',
          javaType: 'foam.core.X'
        },
        {
          name: 'id',
          javaType: 'Object'
        }
      ]
    },
    {
      name: 'urlFor',
      javaReturns: 'String',
      args: [
        {
          name: 'blob',
          javaType: 'foam.blob.Blob'
        }
      ]
    },
    {
      name: 'urlFor_',
      javaReturns: 'String',
      args: [
        {
          name: 'x',
          javaType: 'foam.core.X'
        },
        {
          name: 'blob',
          javaType: 'foam.blob.Blob'
        }
      ]
    }
  ]
});

foam.CLASS({
  refines: 'foam.blob.AbstractBlob',

  methods: [
    {
      name: 'pipe',
      javaCode: 'return;'
    },
    {
      name: 'slice',
      javaReturns: 'foam.blob.Blob',
      args: [
        {
          name: 'offset',
          javaType: 'int'
        },
        {
          name: 'length',
          javaType: 'int'
        }
      ],
      javaCode: 'return new SubBlob(this, offset, length);'
    }
  ]
});

foam.CLASS({
  refines: 'foam.blob.AbstractBlobService',

  methods: [
    {
      name: 'put',
      javaCode: 'return this.put_(getX(), blob);'
    },
    {
      name: 'find',
      javaCode: 'return this.find_(getX(), id);'
    },
    {
      name: 'urlFor',
      javaCode: 'return this.urlFor_(getX(), blob);'
    }
  ]
});

foam.CLASS({
  refines: 'foam.blob.SubBlob',

  methods: [
    {
      name: 'read',
      javaCode:
`length = Math.min(length, getSize() - offset);
return getParent().read(out, offset, length);`
    },
    {
      name: 'slice',
      javaCode: 'return new SubBlob(getParent(), getOffset() + offset, length);'
    }
  ]
});

foam.CLASS({
  refines: 'foam.blob.BlobStore',

  javaImports: [
    'org.apache.commons.io.IOUtils',
    'org.apache.geronimo.mail.util.Hex',
    'java.io.File',
    'java.io.FileOutputStream'
  ],

  constants: [
    {
      name: 'BUFFER_SIZE',
      value: 8192,
      type: 'int'
    }
  ],

  properties: [
    { class: 'String', name: 'tmp', javaFactory: 'return getRoot() + File.separator + "tmp";' },
    { class: 'String', name: 'sha256', javaFactory: 'return getRoot() + File.separator + "sha256";' }
  ],

  methods: [
    {
      name: 'put_',
      javaCode:
`if ( blob instanceof IdentifiedBlob ) {
  return blob;
}

this.setup();

HashingOutputStream os = null;

try {
  int size = blob.getSize();
  File tmp = allocateTmp(1);
  os = new HashingOutputStream(new FileOutputStream(tmp));
  blob.read(os, 0, size);
  os.close();

  String digest = new String(Hex.encode(os.digest()));
  File dest = new File(sha256_ + File.separator + digest);
  tmp.renameTo(dest);

  IdentifiedBlob result = new IdentifiedBlob();
  result.setId(digest);
  result.setX(getX());
  return result;
} catch (Throwable t) {
  return null;
} finally {
  IOUtils.closeQuietly(os);
}`
    },
    {
      name: 'find_',
      javaCode:
`try {
  this.setup();
  if ( ((String) id).indexOf(File.separatorChar) != -1 ) {
    throw new RuntimeException("Invalid file name");
  }

  File file = new File(getSha256() + File.separator + id);
  if ( ! file.exists() ) {
    throw new RuntimeException("File does not exist");
  }

  if ( ! file.canRead() ) {
    throw new RuntimeException("Cannot read file");
  }

  return new FileBlob(file);
} catch (Throwable t) {
  throw new RuntimeException(t);
}`
    },
    {
      name: 'urlFor_',
      javaCode: 'throw new UnsupportedOperationException("Unsupported operation: urlFor_");'
    },
    {
      name: 'setup',
      javaReturns: 'void',
      javaCode:
`if ( this.getIsSet() )
  return;
ensureDir(getRoot());
ensureDir(getTmp());
ensureDir(getSha256());
setIsSet(true);`
    },
    {
      name: 'ensureDir',
      javaReturns: 'void',
      args: [
        {
          name: 'path',
          javaType: 'String'
        }
      ],
      javaCode:
`File parsed = new File(path);
if ( parsed.exists() && parsed.isDirectory() ) {
  return;
}

if ( ! parsed.mkdirs() ) {
  throw new RuntimeException("Failed to create: " + path);
}`
    },
    {
      name: 'allocateTmp',
      javaReturns: 'File',
      args: [
        {
          name: 'name',
          javaType: 'long'
        }
      ],
      javaCode:
`String path = this.tmp_ + File.separator + (name++);
File file = new File(path);
if ( file.exists() ) {
  return allocateTmp(name);
}
return file;`
    }
  ]
});
<|endoftext|>


<commit_before>import { Filemanager } from './filemanager.js';
import { Acorn } from './acorn.js';
import { Mod } from './mod.js';
import { UI } from './ui.js';

const CCLOADER_VERSION = '2.4.1';

export class ModLoader {
	constructor() {
		this.filemanager = new Filemanager(this);
		this.ui = new UI(this);
		this.acorn = new Acorn();

		this.frame = document.getElementById('frame');
		this.overlay = document.getElementById('overlay');
		this.status = document.getElementById('status');

		this.modsLoaded = 0;
		/** @type {Mod[]} */
		this.mods = [];

		this._initializeTable();
	}

	/**
	 * Loads and starts the game. It then loads the definitions and mods
	 */
	startGame() {
		this._initializeGame()
			.then(() => {
				this._setStatus('Loading Game');

				this.ui.applyBindings(this._getGameWindow().console);
				this._getGameWindow().reloadTables = () => this.reloadTables();
				this._getGameWindow().document.createEvent('Event').initEvent('modsLoaded', true, true);

				this._waitForGame()
					.then(() => this._executeDb());
			})
			.catch(err => console.error('Something went wrong while loading the game', err));
	}

	/**
	 * Reloads all definitions
	 */
	reloadTables() {
		this.modTables = {};
		this._createTable(this.filemanager.getTableName());
		this.table.execute(this._getGameWindow(), this._getGameWindow());
		for (const mod of this.mods) {
			mod.executeTable(this);
		}
	}

	/**
	 *
	 * @param {string} text
	 */
	_setStatus(text) {
		if (this.status && this.status.isConnected) {
			this.status.innerHTML = text;
		}
	}

	_getGameWindow() {
		return this.frame.contentWindow;
	}

	/**
	 * Loads a cached table if available and creates a new one otherwise
	 */
	_initializeTable() {
		const tableName = this.filemanager.getTableName();
		if (this.filemanager.tableExists(tableName)) {
			this._loadTable(tableName);
		} else {
			this._createTable(tableName);
		}
	}

	/**
	 * Creates the table from the definitions.db. It will also generate a cached table if possible.
	 * @param {string} tableName
	 */
	_createTable(tableName) {
		this._setStatus('Initializing Mapping');
		console.log('Reading files...');
		const jscode = this.filemanager.getResource('assets/js/game.compiled.js');
		const dbtext = this.filemanager.getResource('ccloader/data/definitions.db');

		try {
			const dbdef = JSON.parse(dbtext);
			console.log('Parsing...');
			this.acorn.parse(jscode);
			console.log('Analysing...');
			this.table = this.acorn.analyse(dbdef);
			console.log('Writing...');
			this.filemanager.saveTable(tableName, this.table);
			console.log('Finished!');
		} catch (e) {
			console.error('Could not load definitions.', e);
		}
	}

	/**
	 * Loads the cached table
	 * @param {string} tableName
	 */
	_loadTable(tableName) {
		const hash = this.filemanager.getDefintionHash();
		this.table = this.filemanager.loadTable(tableName, hash);
		if(!this.table) {
			this._createTable(tableName);
		}
	}

	/**
	 * Loads the mods package.json and mod tables
	 */
	_initializeModTables() {
		this._getModList();
		return this._loadMods()
			.then(() => {
				for (const mod of this.mods) {
					if (mod.isEnabled) {
						mod.initializeTable(this);
					}
				}
			});
	}

	/**
	 * Applies all definitions and loads the mods
	 */
	_executeDb() {
		if (!this.table) {
			return this._removeOverlay();
		}

		this.table.execute(this._getGameWindow(), this._getGameWindow());

		const entries = this._setupGamewindow();
		this._setStatus('Initializing Mods');

		this._initializeModTables()
			.then(() => this._initializeMods(entries))
			.then(() => this._waitForMods())
			.then(() => {
				this._getGameWindow().document.body.dispatchEvent(new Event('modsLoaded'));
				this._removeOverlay();
			})
			.catch(err => console.error('An error occured while loading mods', err));
	}

	/**
	 * Sets up all global objects from ccloader in the game window
	 * @returns {{[key: string]: string}} entries
	 */
	_setupGamewindow() {
		const entries = this._getGameWindow().entries = {};
		this._getGameWindow().getEntry = name => entries[name];

		this._buildCrosscodeVersion();
		this.versions = this._getGameWindow().versions = {
			ccloader: CCLOADER_VERSION,
			crosscode: this.ccVersion
		};

		return entries;
	}

	/**
	 * Searches for mods and stores them in this.mods
	 */
	_getModList() {
		const modFiles = this.filemanager.getAllModsFiles();
		this.mods = [];
		for (const modFile of modFiles) {
			this.mods.push(new Mod(this, modFile));
		}
	}

	/**
	 * Loads the package.json of the mods. This makes sure all necessary data needed for loading the mod is available
	 */
	_loadMods() {
		return Promise.all(this.mods.map((mod) => mod.onload()));
	}
	/**
	 * @param {{[key: string]: string}} entries
	 */
	_initializeMods(entries) {
		this._getGameWindow().inactiveMods = [];
		this._getGameWindow().activeMods = [];
		const tempEntries = {};
		Object.assign(tempEntries, this.table.entries);
		for (const mod of this.mods) {
			if (mod.isEnabled && this._canLoad(mod)) {
				this._getGameWindow().activeMods.push(mod);
				this.versions[mod.name] = mod.version;

				mod.executeTable(this);
				if (mod.table) {
					Object.assign(tempEntries, mod.table.entries);
				}

				(mod => {
					mod.load()
						.then(() => {
							this.modsLoaded++;
						})
						.catch(() => {
							console.warn(`Could not load "${mod.name}"`);
							this.modsLoaded++;
						});
				})(mod);
			} else {
				this._getGameWindow().inactiveMods.push(mod);
				this.modsLoaded++;
			}
		}
		Object.defineProperty(this._getGameWindow(), 'entries', {
			value : Object.freeze(tempEntries),
			writable : false
		});
	}

	/**
	 * @returns {Promise<void>}
	 */
	_initializeGame() {
		return new Promise((resolve, reject) => {
			this.frame.onload = () => {
				this.frame.contentWindow.onbeforeunload = () => this.startGame();
				resolve();
			};
			this.frame.onerror = event => reject(event);
			this.frame.src = window.isLocal ? '../assets/node-webkit.html' : '/assets/node-webkit.html';
		});
	}

	/**
	 * Waits for all mods to be completely loaded
	 * @returns {Promise<void>}
	 */
	_waitForMods() {
		return new Promise(resolve => {
			const intervalid = setInterval(() => {
				if(this.modsLoaded >= this._getGameWindow().activeMods.length){
					clearInterval(intervalid);
					resolve();
				}
			}, 1000);
		});
	}

	/**
	 * Waits for the game to be completely loaded
	 * @returns {Promise<void>}
	 */
	_waitForGame() {
		return new Promise(resolve => {
			const intervalid = setInterval(() => {
				if (this._getGameWindow().ig && this._getGameWindow().ig.ready) {
					clearInterval(intervalid);
					resolve();
				}}, 1000);
		});
	}

	_removeOverlay() {
		if (this.status && this.overlay && this.status.isConnected && this.overlay.isConnected) {
			this.status.outerHTML = '';
			this.overlay.outerHTML = '';
		}
	}

	_buildCrosscodeVersion(){
		try {
			const json = JSON.parse(localStorage.getItem('cc.version'));
			this.ccVersion = json.major + '.' + json.minor + '.' + json.patch;
		} catch (e) {
			console.error('Could not find crosscode version. Assuming "0.0.0".', e);
			this.ccVersion = '0.0.0';
		}
	}

	//Requires bind
	_canLoad(mod) {
		const deps = mod.dependencies;
		if(!deps) {
			return true;
		}

		for (const depName in deps){
			if(!deps.hasOwnProperty(depName))
				continue;

			const depRange = semver.validRange(deps[depName]);
			if(!depRange){
				console.warn('Invalid dependency version "' + deps[depName] + '" of "' + depName + '" of "' + mod.name + '"');
			}

			let satisfied = false;

			if(depName == 'ccloader' && semver.satisfies(CCLOADER_VERSION, depRange)) {
				satisfied = true;
			}
			if(depName == 'crosscode' && semver.satisfies(this.ccVersion, depRange)) {
				satisfied = true;
			}

			for(let i = 0; i < this.mods.length && !satisfied; i++){
				if(this.mods[i].name === depName){
					if(semver.satisfies(semver.valid(this.mods[i].version), depRange)){
						satisfied = true;
					}
				}
			}

			if(!satisfied){
				return false;
			}
		}

		return true;
	}
}

<commit_msg>Fixed missing \n<commit_after>import { Filemanager } from './filemanager.js';
import { Acorn } from './acorn.js';
import { Mod } from './mod.js';
import { UI } from './ui.js';

const CCLOADER_VERSION = '2.4.1';

export class ModLoader {
	constructor() {
		this.filemanager = new Filemanager(this);
		this.ui = new UI(this);
		this.acorn = new Acorn();

		this.frame = document.getElementById('frame');
		this.overlay = document.getElementById('overlay');
		this.status = document.getElementById('status');

		this.modsLoaded = 0;
		/** @type {Mod[]} */
		this.mods = [];

		this._initializeTable();
	}

	/**
	 * Loads and starts the game. It then loads the definitions and mods
	 */
	startGame() {
		this._initializeGame()
			.then(() => {
				this._setStatus('Loading Game');

				this.ui.applyBindings(this._getGameWindow().console);
				this._getGameWindow().reloadTables = () => this.reloadTables();
				this._getGameWindow().document.createEvent('Event').initEvent('modsLoaded', true, true);

				this._waitForGame()
					.then(() => this._executeDb());
			})
			.catch(err => console.error('Something went wrong while loading the game', err));
	}

	/**
	 * Reloads all definitions
	 */
	reloadTables() {
		this.modTables = {};
		this._createTable(this.filemanager.getTableName());
		this.table.execute(this._getGameWindow(), this._getGameWindow());
		for (const mod of this.mods) {
			mod.executeTable(this);
		}
	}

	/**
	 *
	 * @param {string} text
	 */
	_setStatus(text) {
		if (this.status && this.status.isConnected) {
			this.status.innerHTML = text;
		}
	}

	_getGameWindow() {
		return this.frame.contentWindow;
	}

	/**
	 * Loads a cached table if available and creates a new one otherwise
	 */
	_initializeTable() {
		const tableName = this.filemanager.getTableName();
		if (this.filemanager.tableExists(tableName)) {
			this._loadTable(tableName);
		} else {
			this._createTable(tableName);
		}
	}

	/**
	 * Creates the table from the definitions.db. It will also generate a cached table if possible.
	 * @param {string} tableName
	 */
	_createTable(tableName) {
		this._setStatus('Initializing Mapping');
		console.log('Reading files...');
		const jscode = this.filemanager.getResource('assets/js/game.compiled.js');
		const dbtext = this.filemanager.getResource('ccloader/data/definitions.db');

		try {
			const dbdef = JSON.parse(dbtext);
			console.log('Parsing...');
			this.acorn.parse(jscode);
			console.log('Analysing...');
			this.table = this.acorn.analyse(dbdef);
			console.log('Writing...');
			this.filemanager.saveTable(tableName, this.table);
			console.log('Finished!');
		} catch (e) {
			console.error('Could not load definitions.', e);
		}
	}

	/**
	 * Loads the cached table
	 * @param {string} tableName
	 */
	_loadTable(tableName) {
		const hash = this.filemanager.getDefintionHash();
		this.table = this.filemanager.loadTable(tableName, hash);
		if(!this.table) {
			this._createTable(tableName);
		}
	}

	/**
	 * Loads the mods package.json and mod tables
	 */
	_initializeModTables() {
		this._getModList();
		return this._loadMods()
			.then(() => {
				for (const mod of this.mods) {
					if (mod.isEnabled) {
						mod.initializeTable(this);
					}
				}
			});
	}

	/**
	 * Applies all definitions and loads the mods
	 */
	_executeDb() {
		if (!this.table) {
			return this._removeOverlay();
		}

		this.table.execute(this._getGameWindow(), this._getGameWindow());

		const entries = this._setupGamewindow();
		this._setStatus('Initializing Mods');

		this._initializeModTables()
			.then(() => this._initializeMods(entries))
			.then(() => this._waitForMods())
			.then(() => {
				this._getGameWindow().document.body.dispatchEvent(new Event('modsLoaded'));
				this._removeOverlay();
			})
			.catch(err => console.error('An error occured while loading mods', err));
	}

	/**
	 * Sets up all global objects from ccloader in the game window
	 * @returns {{[key: string]: string}} entries
	 */
	_setupGamewindow() {
		const entries = this._getGameWindow().entries = {};
		this._getGameWindow().getEntry = name => entries[name];

		this._buildCrosscodeVersion();
		this.versions = this._getGameWindow().versions = {
			ccloader: CCLOADER_VERSION,
			crosscode: this.ccVersion
		};

		return entries;
	}

	/**
	 * Searches for mods and stores them in this.mods
	 */
	_getModList() {
		const modFiles = this.filemanager.getAllModsFiles();
		this.mods = [];
		for (const modFile of modFiles) {
			this.mods.push(new Mod(this, modFile));
		}
	}

	/**
	 * Loads the package.json of the mods. This makes sure all necessary data needed for loading the mod is available
	 */
	_loadMods() {
		return Promise.all(this.mods.map((mod) => mod.onload()));
	}

	/**
	 * @param {{[key: string]: string}} entries
	 */
	_initializeMods(entries) {
		this._getGameWindow().inactiveMods = [];
		this._getGameWindow().activeMods = [];
		const tempEntries = {};
		Object.assign(tempEntries, this.table.entries);
		for (const mod of this.mods) {
			if (mod.isEnabled && this._canLoad(mod)) {
				this._getGameWindow().activeMods.push(mod);
				this.versions[mod.name] = mod.version;

				mod.executeTable(this);
				if (mod.table) {
					Object.assign(tempEntries, mod.table.entries);
				}

				(mod => {
					mod.load()
						.then(() => {
							this.modsLoaded++;
						})
						.catch(() => {
							console.warn(`Could not load "${mod.name}"`);
							this.modsLoaded++;
						});
				})(mod);
			} else {
				this._getGameWindow().inactiveMods.push(mod);
				this.modsLoaded++;
			}
		}
		Object.defineProperty(this._getGameWindow(), 'entries', {
			value : Object.freeze(tempEntries),
			writable : false
		});
	}

	/**
	 * @returns {Promise<void>}
	 */
	_initializeGame() {
		return new Promise((resolve, reject) => {
			this.frame.onload = () => {
				this.frame.contentWindow.onbeforeunload = () => this.startGame();
				resolve();
			};
			this.frame.onerror = event => reject(event);
			this.frame.src = window.isLocal ? '../assets/node-webkit.html' : '/assets/node-webkit.html';
		});
	}

	/**
	 * Waits for all mods to be completely loaded
	 * @returns {Promise<void>}
	 */
	_waitForMods() {
		return new Promise(resolve => {
			const intervalid = setInterval(() => {
				if(this.modsLoaded >= this._getGameWindow().activeMods.length){
					clearInterval(intervalid);
					resolve();
				}
			}, 1000);
		});
	}

	/**
	 * Waits for the game to be completely loaded
	 * @returns {Promise<void>}
	 */
	_waitForGame() {
		return new Promise(resolve => {
			const intervalid = setInterval(() => {
				if (this._getGameWindow().ig && this._getGameWindow().ig.ready) {
					clearInterval(intervalid);
					resolve();
				}}, 1000);
		});
	}

	_removeOverlay() {
		if (this.status && this.overlay && this.status.isConnected && this.overlay.isConnected) {
			this.status.outerHTML = '';
			this.overlay.outerHTML = '';
		}
	}

	_buildCrosscodeVersion(){
		try {
			const json = JSON.parse(localStorage.getItem('cc.version'));
			this.ccVersion = json.major + '.' + json.minor + '.' + json.patch;
		} catch (e) {
			console.error('Could not find crosscode version. Assuming "0.0.0".', e);
			this.ccVersion = '0.0.0';
		}
	}

	//Requires bind
	_canLoad(mod) {
		const deps = mod.dependencies;
		if(!deps) {
			return true;
		}

		for (const depName in deps){
			if(!deps.hasOwnProperty(depName))
				continue;

			const depRange = semver.validRange(deps[depName]);
			if(!depRange){
				console.warn('Invalid dependency version "' + deps[depName] + '" of "' + depName + '" of "' + mod.name + '"');
			}

			let satisfied = false;

			if(depName == 'ccloader' && semver.satisfies(CCLOADER_VERSION, depRange)) {
				satisfied = true;
			}
			if(depName == 'crosscode' && semver.satisfies(this.ccVersion, depRange)) {
				satisfied = true;
			}

			for(let i = 0; i < this.mods.length && !satisfied; i++){
				if(this.mods[i].name === depName){
					if(semver.satisfies(semver.valid(this.mods[i].version), depRange)){
						satisfied = true;
					}
				}
			}

			if(!satisfied){
				return false;
			}
		}

		return true;
	}
}

<|endoftext|>


<commit_before>//Ext.BLANK_IMAGE_URL = 'resources/s.gif';
var startCaseFilter;

Ext.chart.Chart.CHART_URL = '/images/charts.swf';
Ext.FlashComponent.EXPRESS_INSTALL_URL = '/images/expressinstall.swf';
// The Quicktips are used for the toolbar and Tree mouseover tooltips!

var conn = new Ext.data.Connection();

var processNumbers = new Ext.data.ArrayStore({
    fields: [
       {name: 'CASES_COUNT_TO_DO', type: 'integer'},
       {name: 'CASES_COUNT_DRAFT', type: 'integer'},
       {name: 'CASES_COUNT_COMPLETED', type: 'integer'},
       {name: 'CASES_COUNT_CANCELLED', type: 'integer'},
       {name: 'CASES_COUNT', type: 'integer'}
    ]
});

var processNumbersData = [[0,0,0,0,0]];
Docs = {};

Ext.onReady(function() {
  var newCaseTree = new Ext.ux.MaskTree({
    id: 'startCaseTreePanel',
    region: 'center',
    useArrows: true,
    animate: true,
    split : true,
    autoScroll: true,
    dataUrl: 'casesStartPage_Ajax?action=getProcessList',
    rootVisible: false,
    containerScroll: true,
    border: false,
    root: {
      nodeType: 'async',
      expanded : true
    },
    tbar : [
      {
        xtype : 'textfield',
        name : 'processesFilter',
        id : 'processesFilter',
        emptyText : _('ID_FIND_A_PROCESS'),
        enableKeyEvents : true,
        listeners : {
          render : function(f) {
            startCaseFilter = new Ext.ux.tree.TreeFilterX(Ext.getCmp('startCaseTreePanel'));
          },
          specialkey : function(f, e) {
            if (e.getKey() == e.ENTER) {
              txt = Ext.getCmp('processesFilter').getValue();
              startCaseFilter.clear();
              var re = new RegExp('.*' + txt + '.*', 'i');
              startCaseFilter.filter(re, 'text');
            }
          },
          scope : this
        }
      }, {
        text : 'X',
        ctCls : 'pm_search_x_button',
        handler : function() {
          Ext.getCmp('processesFilter').setValue('');
          startCaseFilter.clear();
        }
      }, ' ', ' ', {
        iconCls : 'icon-expand-all',
        tooltip :  _('ID_EXPAND_ALL'),
        handler : function() {
          Ext.getCmp("startCaseTreePanel").root.expand(true);
        },
        scope : this
      }, '-', {
        iconCls : 'icon-collapse-all',
        tooltip : _('ID_COLLAPSE_ALL'),
        handler : function() {
          Ext.getCmp("startCaseTreePanel").root.collapse(true);
        },
        scope : this
      }, ' ', ' ', {
        xtype : 'tbbutton',
        cls : 'x-btn-icon',
        icon : '/images/refresh.gif',

        handler : function() {
          tree = Ext.getCmp('startCaseTreePanel');
          tree.getLoader().load(tree.root);
        }
      }
    ],
    listeners : {
      dblclick : function(n) {
        openCaseA(n);
      },
      click : function(n) {
        showDetailsA(n);
      }
    }
  });

  var details = {
    xtype:'form',
    id : 'process-detail-panel',
    region : 'east',
    split : true,
    width : 450,
    style : {
      width : '450'
    },
    minWidth : 250,
    labelAlign: 'right',
    labelWidth: 85,
    waitMsgTarget: true,
    title: TRANSLATIONS.ID_PROCESS_INFORMATION,
    layout:'form',
    defaults: {width: 350},
    defaultType: 'displayfield',
    items: [{
        fieldLabel: TRANSLATIONS.ID_PROCESS,
        name: 'processName',
        allowBlank:false,
        value: '',
        labelStyle : 'font-size:11px;',
        style : {
          fontSize:'11px'
        },
        id:"processName"
      },
      {
        xtype: 'compositefield',
        fieldLabel: TRANSLATIONS.ID_TASK,
        labelStyle : 'font-size:11px;',
        style : {
          fontSize:'11px'
        },
        items: [
          {
            xtype : 'button',
            id : 'starCaseButton',
            disabled : true,
            iconCls : "ICON_CASES_START_CASE",
            text : TRANSLATIONS.ID_TITLE_START_CASE,
            autoWidth : true,
            handler : function() {
              tree = Ext.getCmp('startCaseTreePanel');
              var selectedNode = tree.getSelectionModel().getSelectedNode();
              if (selectedNode) {
                openCaseA(selectedNode);
              }
            }
          },
          {
            xtype     : 'displayfield',
            name: 'taskName',
            allowBlank:false,
            value: '',
            width:200,
            id:"taskName"
          }
        ]},
        {
          xtype:'textarea',
          fieldLabel: TRANSLATIONS.ID_DESCRIPTION,
          name: 'processDescription',
          value: '',
          readOnly: true,
          labelStyle : 'font-size:11px;',
          style : {
            fontSize:'11px'
          },
          id:"processDescription"
        },{
          fieldLabel: TRANSLATIONS.ID_CATEGORY,
          name: 'processCategory',
          value: '',
          readOnly: true,
          labelStyle : 'font-size:11px;',
          style : {
            fontSize:'11px'
          },
          id:"processCategory"
        },
        {
          xtype: 'grid',
          fieldLabel: ' ',
          labelSeparator : '',
          labelStyle : 'font-size:11px;',
          style : {
            fontSize:'11px'
          },
          ds: processNumbers,
          cm: new Ext.grid.ColumnModel([
            {id:'inbox',header: TRANSLATIONS.ID_INBOX, width:70, sortable: false, locked:true, dataIndex: 'CASES_COUNT_TO_DO'},
            {id:'draft',header: TRANSLATIONS.ID_DRAFT, width:70,  sortable: false, locked:true,  dataIndex: 'CASES_COUNT_DRAFT'},
            {id:'completed',header: TRANSLATIONS.ID_COMPLETED, width:70,  sortable: false, locked:true, dataIndex: 'CASES_COUNT_COMPLETED'},
            {id:'canceled',header: TRANSLATIONS.ID_CANCELLED, width:70,  sortable: false, locked:true,  dataIndex: 'CASES_COUNT_CANCELLED'},
            {id:'totalCases',header: TRANSLATIONS.ID_TOTAL_CASES, width:70,  sortable: false, locked:true , dataIndex: 'CASES_COUNT'}
          ]),
          height: 49,
          width: 355,
          border: true
        },
        {
          fieldLabel: TRANSLATIONS.ID_CALENDAR,
          name: 'calendarName',
          labelStyle : 'font-size:11px;',
          style : {
            fontSize:'11px'
          },
          id:"calendarName"
      },
      {
        xtype:'checkboxgroup',
        fieldLabel: TRANSLATIONS.ID_WORKING_DAYS,
        name: 'calendarWorkDays',
        disabled: true,
        readOnly: true,
        disabledClass:"",
        labelStyle : 'font-size:11px;',
        style : {
          fontSize:'11px'
        },
        id:"calendarWorkDays",
        columns: 7,
        items: [
          {boxLabel: TRANSLATIONS.ID_SUN, name: '0',disabledClass:""},
          {boxLabel: TRANSLATIONS.ID_MON, name: '1',disabledClass:""},
          {boxLabel: TRANSLATIONS.ID_TUE, name: '2',disabledClass:""},
          {boxLabel: TRANSLATIONS.ID_WEN, name: '3',disabledClass:""},
          {boxLabel: TRANSLATIONS.ID_THU, name: '4',disabledClass:""},
          {boxLabel: TRANSLATIONS.ID_FRI, name: '5',disabledClass:""},
          {boxLabel: TRANSLATIONS.ID_SAT, name: '6',disabledClass:""}
        ]
      },
      {
        xtype:'checkbox',
        fieldLabel: TRANSLATIONS.ID_DEBUG_MODE,
        name: 'processDebug',
        labelStyle : 'font-size:11px;',
        style : {
          fontSize:'11px'
        },
        disabled: true,
        readOnly: true,
        id:"processDebug",
        disabledClass:""
      }
    ]
  }

  Ext.QuickTips.init();

  var viewport = new Ext.Viewport({
    layout : 'border',
    items : [newCaseTree, details]
  });

  //routine to hide the debug panel if it is open
  if( typeof parent != 'undefined' ){
    if( parent.PANEL_EAST_OPEN ){
      parent.PANEL_EAST_OPEN = false;
      parent.Ext.getCmp('debugPanel').hide();
      parent.Ext.getCmp('debugPanel').ownerCt.doLayout();
    }
  }

  if (FORMATS.startCaseHideProcessInf) {
    Ext.getCmp('process-detail-panel').hide();
    Ext.getCmp('startCaseTreePanel').ownerCt.doLayout();
  }
});

function openCaseA(n){
  if (n.attributes.optionType == "startProcess") {
    Ext.Msg.show({
      title : '',
      msg : TRANSLATIONS.ID_STARTING_NEW_CASE  + '

' + n.attributes.text + '',
      wait:true,
      waitConfig: {interval:500}
    });
    Ext.Ajax.request({
      url : 'casesStartPage_Ajax.php',
      params : {
        action : 'startCase',
        processId : n.attributes.pro_uid,
        taskId : n.attributes.tas_uid
      },
      success : function(response) {

        try {
          var res = Ext.util.JSON.decode(response.responseText);
          if (res.openCase) {
              window.location = res.openCase.PAGE;
          }
          else {
            Ext.Msg.show({
              title : TRANSLATIONS.ID_ERROR_CREATING_NEW_CASE, // 'Error creating a new Case',
              msg : '<textarea cols="50" rows="10">'
                  + res.message + '</textarea>',
              icon : Ext.MessageBox.ERROR,
              buttons : Ext.Msg.OK
            });
          }
        }
        catch(e) {
          Ext.Msg.show({
            title : TRANSLATIONS.ID_ERROR_CREATING_NEW_CASE, // 'Error creating a new Case',
            msg : 'JSON Decode Error:
<textarea cols="50" rows="2">'
                + e.message + '</textarea>
Server Response
<textarea cols="50" rows="5">'+response.responseText+'</textarea>',
            icon : Ext.MessageBox.ERROR,
            buttons : Ext.Msg.OK
          });
        }
      },
      failure : function() {
        Ext.Msg.alert(TRANSLATIONS.ID_ERROR, TRANSLATIONS.ID_UNABLE_START_CASE);
      }
    });
  }
};

function showDetailsA(selectedNode) {

  // console.log(selectedNode);
  var detailEl = Ext.getCmp('process-detail-panel').body;
  if ((selectedNode)&&(selectedNode.attributes.otherAttributes)) {
    otherAttributes = selectedNode.attributes.otherAttributes;
    calendarDays=(otherAttributes.CALENDAR_WORK_DAYS).split("|");
    calendarObj={};

    for(i=0;i<calendarDays.length;i++){
    calendarObj[calendarDays[i]]=true;
    }
    //console.log(otherAttributes);
    //starCaseButton
    Ext.ComponentMgr.get("starCaseButton").enable();
    Ext.getCmp('process-detail-panel').getForm().setValues({
      processName : otherAttributes.PRO_TITLE,
      taskName : otherAttributes.PRO_TAS_TITLE,
      calendarName : otherAttributes.CALENDAR_NAME,
      calendarDescription : otherAttributes.CALENDAR_DESCRIPTION,
      processCalendar:otherAttributes.CALENDAR_NAME+" "+otherAttributes.CALENDAR_DESCRIPTION,
      calendarWorkDays : calendarObj,/* (otherAttributes.CALENDAR_WORK_DAYS).split("|"), */
      processCategory : otherAttributes.PRO_CATEGORY_LABEL,
      processDebug : otherAttributes.PRO_DEBUG,
      processDescription : otherAttributes.PRO_DESCRIPTION,
      myInbox : otherAttributes.myInbox,
      totalInbox : otherAttributes.totalInbox

    });

    processNumbersData = [[
      otherAttributes.CASES_COUNT_TO_DO,
      otherAttributes.CASES_COUNT_DRAFT,
      otherAttributes.CASES_COUNT_COMPLETED,
      otherAttributes.CASES_COUNT_CANCELLED,
      otherAttributes.CASES_COUNT
    ]];
    processNumbers.loadData(processNumbersData);

  } else {
    //detailEl.update('');
  }

  return;
};

Ext.ux.MaskTree = Ext.extend(Ext.tree.TreePanel, {
    /**
     * @cfg {Boolean} mask Indicates if the tree panel should have a loadmask applied when loading nodes
     */
    mask: true,
    /**
     * @cfg {Object} maskConfig A configuration object that can be applied to the loadmask.
     */
    maskConfig: { msg: _('ID_LOADING') },

    //init
    initComponent:function() {
        // call parent
        Ext.ux.MaskTree.superclass.initComponent.apply(this, arguments);

        if (this.mask) { this.on('render', this.createMask, this); }
    }, //end initComponent

    /**
     * @private
     */
    createMask: function() {
        var mask = new Ext.LoadMask(Ext.getBody(), this.maskConfig);
        this.getLoader().on('beforeload', mask.show, mask);
        this.getLoader().on('load', mask.hide, mask);
    }
}); // end of extend

Ext.reg('masktree', Ext.ux.MaskTree);<commit_msg>BUG 9215 Show scroll in google chrome SOLVED<commit_after>//Ext.BLANK_IMAGE_URL = 'resources/s.gif';
var startCaseFilter;

Ext.chart.Chart.CHART_URL = '/images/charts.swf';
Ext.FlashComponent.EXPRESS_INSTALL_URL = '/images/expressinstall.swf';
// The Quicktips are used for the toolbar and Tree mouseover tooltips!

var conn = new Ext.data.Connection();

var processNumbers = new Ext.data.ArrayStore({
    fields: [
       {name: 'CASES_COUNT_TO_DO', type: 'integer'},
       {name: 'CASES_COUNT_DRAFT', type: 'integer'},
       {name: 'CASES_COUNT_COMPLETED', type: 'integer'},
       {name: 'CASES_COUNT_CANCELLED', type: 'integer'},
       {name: 'CASES_COUNT', type: 'integer'}
    ]
});

var processNumbersData = [[0,0,0,0,0]];
Docs = {};

Ext.onReady(function() {
  var newCaseTree = new Ext.ux.MaskTree({
    id: 'startCaseTreePanel',
    region: 'center',
    useArrows: true,
    animate: true,
    split : true,
    autoScroll: true,
    dataUrl: 'casesStartPage_Ajax?action=getProcessList',
    rootVisible: false,
    containerScroll: true,
    border: false,
    root: {
      nodeType: 'async',
      expanded : true
    },
    tbar : [
      {
        xtype : 'textfield',
        name : 'processesFilter',
        id : 'processesFilter',
        emptyText : _('ID_FIND_A_PROCESS'),
        enableKeyEvents : true,
        listeners : {
          render : function(f) {
            startCaseFilter = new Ext.ux.tree.TreeFilterX(Ext.getCmp('startCaseTreePanel'));
          },
          specialkey : function(f, e) {
            if (e.getKey() == e.ENTER) {
              txt = Ext.getCmp('processesFilter').getValue();
              startCaseFilter.clear();
              var re = new RegExp('.*' + txt + '.*', 'i');
              startCaseFilter.filter(re, 'text');
            }
          },
          scope : this
        }
      }, {
        text : 'X',
        ctCls : 'pm_search_x_button',
        handler : function() {
          Ext.getCmp('processesFilter').setValue('');
          startCaseFilter.clear();
        }
      }, ' ', ' ', {
        iconCls : 'icon-expand-all',
        tooltip :  _('ID_EXPAND_ALL'),
        handler : function() {
          Ext.getCmp("startCaseTreePanel").root.expand(true);
        },
        scope : this
      }, '-', {
        iconCls : 'icon-collapse-all',
        tooltip : _('ID_COLLAPSE_ALL'),
        handler : function() {
          Ext.getCmp("startCaseTreePanel").root.collapse(true);
        },
        scope : this
      }, ' ', ' ', {
        xtype : 'tbbutton',
        cls : 'x-btn-icon',
        icon : '/images/refresh.gif',

        handler : function() {
          tree = Ext.getCmp('startCaseTreePanel');
          tree.getLoader().load(tree.root);
        }
      }
    ],
    listeners : {
      dblclick : function(n) {
        openCaseA(n);
      },
      click : function(n) {
        showDetailsA(n);
      }
    }
  });

  var details = {
    xtype:'form',
    id : 'process-detail-panel',
    region : 'east',
    split : true,
    width : 450,
    style : {
      width : '450'
    },
    minWidth : 250,
    labelAlign: 'right',
    labelWidth: 85,
    waitMsgTarget: true,
    title: TRANSLATIONS.ID_PROCESS_INFORMATION,
    layout:'form',
    defaults: {width: 350},
    defaultType: 'displayfield',
    items: [{
        fieldLabel: TRANSLATIONS.ID_PROCESS,
        name: 'processName',
        allowBlank:false,
        value: '',
        labelStyle : 'font-size:11px;',
        style : {
          fontSize:'11px'
        },
        id:"processName"
      },
      {
        xtype: 'compositefield',
        fieldLabel: TRANSLATIONS.ID_TASK,
        labelStyle : 'font-size:11px;',
        style : {
          fontSize:'11px'
        },
        items: [
          {
            xtype : 'button',
            id : 'starCaseButton',
            disabled : true,
            iconCls : "ICON_CASES_START_CASE",
            text : TRANSLATIONS.ID_TITLE_START_CASE,
            autoWidth : true,
            handler : function() {
              tree = Ext.getCmp('startCaseTreePanel');
              var selectedNode = tree.getSelectionModel().getSelectedNode();
              if (selectedNode) {
                openCaseA(selectedNode);
              }
            }
          },
          {
            xtype     : 'displayfield',
            name: 'taskName',
            allowBlank:false,
            value: '',
            width:200,
            id:"taskName"
          }
        ]},
        {
          xtype:'textarea',
          fieldLabel: TRANSLATIONS.ID_DESCRIPTION,
          name: 'processDescription',
          value: '',
          readOnly: true,
          labelStyle : 'font-size:11px;',
          style : {
            fontSize:'11px'
          },
          id:"processDescription"
        },{
          fieldLabel: TRANSLATIONS.ID_CATEGORY,
          name: 'processCategory',
          value: '',
          readOnly: true,
          labelStyle : 'font-size:11px;',
          style : {
            fontSize:'11px'
          },
          id:"processCategory"
        },
        {
          xtype: 'grid',
          fieldLabel: ' ',
          labelSeparator : '',
          labelStyle : 'font-size:11px;',
          style : {
            fontSize:'11px'
          },
          ds: processNumbers,
          cm: new Ext.grid.ColumnModel([
            {id:'inbox',header: TRANSLATIONS.ID_INBOX, width:70, sortable: false, locked:true, dataIndex: 'CASES_COUNT_TO_DO'},
            {id:'draft',header: TRANSLATIONS.ID_DRAFT, width:70,  sortable: false, locked:true,  dataIndex: 'CASES_COUNT_DRAFT'},
            {id:'completed',header: TRANSLATIONS.ID_COMPLETED, width:70,  sortable: false, locked:true, dataIndex: 'CASES_COUNT_COMPLETED'},
            {id:'canceled',header: TRANSLATIONS.ID_CANCELLED, width:70,  sortable: false, locked:true,  dataIndex: 'CASES_COUNT_CANCELLED'},
            {id:'totalCases',header: TRANSLATIONS.ID_TOTAL_CASES, width:80,  sortable: false, locked:true , dataIndex: 'CASES_COUNT'}
          ]),
          height: 49,
          width: 355,
          border: true,
          stripeRows: false,
          viewConfig: {
            forceFit:true,
            scrollOffset: 0
          }
        },
        {
          fieldLabel: TRANSLATIONS.ID_CALENDAR,
          name: 'calendarName',
          labelStyle : 'font-size:11px;',
          style : {
            fontSize:'11px'
          },
          id:"calendarName"
      },
      {
        xtype:'checkboxgroup',
        fieldLabel: TRANSLATIONS.ID_WORKING_DAYS,
        name: 'calendarWorkDays',
        disabled: true,
        readOnly: true,
        disabledClass:"",
        labelStyle : 'font-size:11px;',
        style : {
          fontSize:'11px'
        },
        id:"calendarWorkDays",
        columns: 7,
        items: [
          {boxLabel: TRANSLATIONS.ID_SUN, name: '0',disabledClass:""},
          {boxLabel: TRANSLATIONS.ID_MON, name: '1',disabledClass:""},
          {boxLabel: TRANSLATIONS.ID_TUE, name: '2',disabledClass:""},
          {boxLabel: TRANSLATIONS.ID_WEN, name: '3',disabledClass:""},
          {boxLabel: TRANSLATIONS.ID_THU, name: '4',disabledClass:""},
          {boxLabel: TRANSLATIONS.ID_FRI, name: '5',disabledClass:""},
          {boxLabel: TRANSLATIONS.ID_SAT, name: '6',disabledClass:""}
        ]
      },
      {
        xtype:'checkbox',
        fieldLabel: TRANSLATIONS.ID_DEBUG_MODE,
        name: 'processDebug',
        labelStyle : 'font-size:11px;',
        style : {
          fontSize:'11px'
        },
        disabled: true,
        readOnly: true,
        id:"processDebug",
        disabledClass:""
      }
    ]
  }

  Ext.QuickTips.init();

  var viewport = new Ext.Viewport({
    layout : 'border',
    items : [newCaseTree, details]
  });

  //routine to hide the debug panel if it is open
  if( typeof parent != 'undefined' ){
    if( parent.PANEL_EAST_OPEN ){
      parent.PANEL_EAST_OPEN = false;
      parent.Ext.getCmp('debugPanel').hide();
      parent.Ext.getCmp('debugPanel').ownerCt.doLayout();
    }
  }

  if (FORMATS.startCaseHideProcessInf) {
    Ext.getCmp('process-detail-panel').hide();
    Ext.getCmp('startCaseTreePanel').ownerCt.doLayout();
  }
});

function openCaseA(n){
  if (n.attributes.optionType == "startProcess") {
    Ext.Msg.show({
      title : '',
      msg : TRANSLATIONS.ID_STARTING_NEW_CASE  + '

' + n.attributes.text + '',
      wait:true,
      waitConfig: {interval:500}
    });
    Ext.Ajax.request({
      url : 'casesStartPage_Ajax.php',
      params : {
        action : 'startCase',
        processId : n.attributes.pro_uid,
        taskId : n.attributes.tas_uid
      },
      success : function(response) {

        try {
          var res = Ext.util.JSON.decode(response.responseText);
          if (res.openCase) {
              window.location = res.openCase.PAGE;
          }
          else {
            Ext.Msg.show({
              title : TRANSLATIONS.ID_ERROR_CREATING_NEW_CASE, // 'Error creating a new Case',
              msg : '<textarea cols="50" rows="10">'
                  + res.message + '</textarea>',
              icon : Ext.MessageBox.ERROR,
              buttons : Ext.Msg.OK
            });
          }
        }
        catch(e) {
          Ext.Msg.show({
            title : TRANSLATIONS.ID_ERROR_CREATING_NEW_CASE, // 'Error creating a new Case',
            msg : 'JSON Decode Error:
<textarea cols="50" rows="2">'
                + e.message + '</textarea>
Server Response
<textarea cols="50" rows="5">'+response.responseText+'</textarea>',
            icon : Ext.MessageBox.ERROR,
            buttons : Ext.Msg.OK
          });
        }
      },
      failure : function() {
        Ext.Msg.alert(TRANSLATIONS.ID_ERROR, TRANSLATIONS.ID_UNABLE_START_CASE);
      }
    });
  }
};

function showDetailsA(selectedNode) {

  // console.log(selectedNode);
  var detailEl = Ext.getCmp('process-detail-panel').body;
  if ((selectedNode)&&(selectedNode.attributes.otherAttributes)) {
    otherAttributes = selectedNode.attributes.otherAttributes;
    calendarDays=(otherAttributes.CALENDAR_WORK_DAYS).split("|");
    calendarObj={};

    for(i=0;i<calendarDays.length;i++){
    calendarObj[calendarDays[i]]=true;
    }
    //console.log(otherAttributes);
    //starCaseButton
    Ext.ComponentMgr.get("starCaseButton").enable();
    Ext.getCmp('process-detail-panel').getForm().setValues({
      processName : otherAttributes.PRO_TITLE,
      taskName : otherAttributes.PRO_TAS_TITLE,
      calendarName : otherAttributes.CALENDAR_NAME,
      calendarDescription : otherAttributes.CALENDAR_DESCRIPTION,
      processCalendar:otherAttributes.CALENDAR_NAME+" "+otherAttributes.CALENDAR_DESCRIPTION,
      calendarWorkDays : calendarObj,/* (otherAttributes.CALENDAR_WORK_DAYS).split("|"), */
      processCategory : otherAttributes.PRO_CATEGORY_LABEL,
      processDebug : otherAttributes.PRO_DEBUG,
      processDescription : otherAttributes.PRO_DESCRIPTION,
      myInbox : otherAttributes.myInbox,
      totalInbox : otherAttributes.totalInbox

    });

    processNumbersData = [[
      otherAttributes.CASES_COUNT_TO_DO,
      otherAttributes.CASES_COUNT_DRAFT,
      otherAttributes.CASES_COUNT_COMPLETED,
      otherAttributes.CASES_COUNT_CANCELLED,
      otherAttributes.CASES_COUNT
    ]];
    processNumbers.loadData(processNumbersData);

  } else {
    //detailEl.update('');
  }

  return;
};

Ext.ux.MaskTree = Ext.extend(Ext.tree.TreePanel, {
    /**
     * @cfg {Boolean} mask Indicates if the tree panel should have a loadmask applied when loading nodes
     */
    mask: true,
    /**
     * @cfg {Object} maskConfig A configuration object that can be applied to the loadmask.
     */
    maskConfig: { msg: _('ID_LOADING') },

    //init
    initComponent:function() {
        // call parent
        Ext.ux.MaskTree.superclass.initComponent.apply(this, arguments);

        if (this.mask) { this.on('render', this.createMask, this); }
    }, //end initComponent

    /**
     * @private
     */
    createMask: function() {
        var mask = new Ext.LoadMask(Ext.getBody(), this.maskConfig);
        this.getLoader().on('beforeload', mask.show, mask);
        this.getLoader().on('load', mask.hide, mask);
    }
}); // end of extend

Ext.reg('masktree', Ext.ux.MaskTree);<|endoftext|>


<commit_before>/* eslint-disable max-statements */

// Form for comment creation and edit
var ui = require('tresdb-ui');
var emitter = require('component-emitter');
var MarkdownView = require('../Markdown');
var AttachmentsForm = require('../AttachmentsForm');
var RemoveView = require('../Remove');
var ErrorView = require('../Error');
var template = require('./template.ejs');
var entryApi = tresdb.stores.entries;

var MIN_LEN = tresdb.config.comments.minMessageLength;
var MAX_LEN = tresdb.config.comments.maxMessageLength;

module.exports = function (entry, comment) {
  // Parameters
  //   entry
  //     entry object
  //   comment
  //     optional comment object with complete attachments.
  //     Leave empty for comment creation.
  //

  // Setup
  var $mount = null;
  var children = {};
  var $elems = {};
  var self = this;
  emitter(self);

  // Comment creation vs comment edit
  var isNew = false;
  if (!comment) {
    isNew = true;
    comment = {
      markdown: '',
      attachments: [],
    };
  }

  var submit = function (markdown, attachments, callback) {
    var locId = entry.locationId;
    var entryId = entry._id;

    if (isNew) {
      entryApi.createComment({
        locationId: locId,
        entryId: entryId,
        markdown: markdown,
        attachments: attachments,
      }, callback);
    } else {
      entryApi.changeComment({
        locationId: locId,
        entryId: entryId,
        commentId: comment.id,
        markdown: markdown,
        attachments: attachments,
      }, callback);
    }
  };

  self.bind = function ($mountEl) {
    $mount = $mountEl;

    $mount.html(template({
      isNew: isNew,
      markdown: comment.markdown,
    }));

    // Cancel button
    $elems.cancel = $mount.find('.comment-form-cancel');
    $elems.cancel.click(function () {
      self.emit('exit');
    });

    // Setup message input
    $elems.markdown = $mount.find('.comment-form-markdown-container');
    children.markdown = new MarkdownView(comment.markdown, {
      label: isNew ? 'Add Comment:' : 'Edit Comment:',
      glyphicon: isNew ? 'glyphicon-comment' : 'glyphicon-pencil flip-x',
      placeholder: 'message...',
      rows: 2,
      minLength: MIN_LEN,
      maxLength: MAX_LEN,
    });
    children.markdown.bind($elems.markdown);

    // Init error view
    $elems.error = $mount.find('.comment-form-error');
    children.error = new ErrorView();
    children.error.bind($elems.error);

    // Delete button
    if (!isNew) {
      children.remove = new RemoveView({
        label: 'Delete comment...',
      });
      children.remove.bind($mount.find('.comment-remove-container'));
      children.remove.on('submit', function () {
        entryApi.removeComment({
          locationId: entry.locationId,
          entryId: entry._id,
          commentId: comment.id,
        }, function (err) {
          if (err) {
            children.remove.reset(); // hide progress
            children.error.update(err.message);
            return;
          }
          // Global location_entry_comment_removed will cause unbind
          // and $mount removal.
        });
      });
    }

    // If comment contains attachments, show them. Otherwise keep
    // attachment form hidden and show only a button to open it.
    $elems.attach = $mount.find('.comment-form-attachments-container');
    $elems.attachBtn = $mount.find('.comment-form-photo-btn');
    var openAttachmentsForm = function () {
      // Hide the form opening button as unnecessary
      ui.hide($elems.attachBtn);
      children.attach = new AttachmentsForm(comment.attachments, {
        label: 'Photo or document:',
        limit: 1,
      });
      children.attach.bind($elems.attach);
    };
    // Form or Button
    if (comment.attachments.length > 0) {
      openAttachmentsForm();
    } else {
      // Show a button to open the attachment form
      $elems.attachBtn.click(openAttachmentsForm);
    }

    // Prepare progress bar for submission
    $elems.progress = $mount.find('.comment-form-progress');

    // Submit
    $elems.form = $mount.find('.comment-form');
    $elems.submit = $mount.find('.comment-form-submit');
    $elems.submit.click(function () {
      // Read message
      var markdown = children.markdown.getMarkdown();
      var len = markdown.length;

      if (len < MIN_LEN || len > MAX_LEN) {
        // Do not submit if too short or long
        return;
      }

      // Read attachments
      var attachments = [];
      if (children.attach) {
        attachments = children.attach.getAttachmentKeys();
      }

      // TODO Purge cache of unfinished comment

      // Hide form and reveal progress. This also prevents double click.
      ui.hide($elems.form);
      ui.show($elems.progress);
      // Hide possible previous messages
      children.error.reset();

      submit(markdown, attachments, function (err) {
        // Show form and hide progress
        ui.show($elems.form);
        ui.hide($elems.progress);

        if (err) {
          // Display error
          children.error.update(err.message);
        } else {
          // Success.
          // Empty the message input for next comment
          children.markdown.clear();
          // Inform parent for example to unbind the form.
          self.emit('success');
        }
      });
    });
  };

  self.unbind = function () {
    if ($mount) {
      $mount = null;
      ui.unbindAll(children);
      children = {};
      ui.offAll($elems);
      $elems = {};
    }
  };
};
<commit_msg>Show error on invalid comment message content<commit_after>/* eslint-disable max-statements */

// Form for comment creation and edit
var ui = require('tresdb-ui');
var emitter = require('component-emitter');
var MarkdownView = require('../Markdown');
var AttachmentsForm = require('../AttachmentsForm');
var RemoveView = require('../Remove');
var ErrorView = require('../Error');
var template = require('./template.ejs');
var entryApi = tresdb.stores.entries;

var MIN_LEN = tresdb.config.comments.minMessageLength;
var MAX_LEN = tresdb.config.comments.maxMessageLength;

module.exports = function (entry, comment) {
  // Parameters
  //   entry
  //     entry object
  //   comment
  //     optional comment object with complete attachments.
  //     Leave empty for comment creation.
  //

  // Setup
  var $mount = null;
  var children = {};
  var $elems = {};
  var self = this;
  emitter(self);

  // Comment creation vs comment edit
  var isNew = false;
  if (!comment) {
    isNew = true;
    comment = {
      markdown: '',
      attachments: [],
    };
  }

  var submit = function (markdown, attachments, callback) {
    var locId = entry.locationId;
    var entryId = entry._id;

    if (isNew) {
      entryApi.createComment({
        locationId: locId,
        entryId: entryId,
        markdown: markdown,
        attachments: attachments,
      }, callback);
    } else {
      entryApi.changeComment({
        locationId: locId,
        entryId: entryId,
        commentId: comment.id,
        markdown: markdown,
        attachments: attachments,
      }, callback);
    }
  };

  self.bind = function ($mountEl) {
    $mount = $mountEl;

    $mount.html(template({
      isNew: isNew,
      markdown: comment.markdown,
    }));

    // Cancel button
    $elems.cancel = $mount.find('.comment-form-cancel');
    $elems.cancel.click(function () {
      self.emit('exit');
    });

    // Setup message input
    $elems.markdown = $mount.find('.comment-form-markdown-container');
    children.markdown = new MarkdownView(comment.markdown, {
      label: isNew ? 'Add Comment:' : 'Edit Comment:',
      glyphicon: isNew ? 'glyphicon-comment' : 'glyphicon-pencil flip-x',
      placeholder: 'message...',
      rows: 2,
      minLength: MIN_LEN,
      maxLength: MAX_LEN,
    });
    children.markdown.bind($elems.markdown);

    // Init error view
    $elems.error = $mount.find('.comment-form-error');
    children.error = new ErrorView();
    children.error.bind($elems.error);

    // Delete button
    if (!isNew) {
      children.remove = new RemoveView({
        label: 'Delete comment...',
      });
      children.remove.bind($mount.find('.comment-remove-container'));
      children.remove.on('submit', function () {
        entryApi.removeComment({
          locationId: entry.locationId,
          entryId: entry._id,
          commentId: comment.id,
        }, function (err) {
          if (err) {
            children.remove.reset(); // hide progress
            children.error.update(err.message);
            return;
          }
          // Global location_entry_comment_removed will cause unbind
          // and $mount removal.
        });
      });
    }

    // If comment contains attachments, show them. Otherwise keep
    // attachment form hidden and show only a button to open it.
    $elems.attach = $mount.find('.comment-form-attachments-container');
    $elems.attachBtn = $mount.find('.comment-form-photo-btn');
    var openAttachmentsForm = function () {
      // Hide the form opening button as unnecessary
      ui.hide($elems.attachBtn);
      children.attach = new AttachmentsForm(comment.attachments, {
        label: 'Photo or document:',
        limit: 1,
      });
      children.attach.bind($elems.attach);
    };
    // Form or Button
    if (comment.attachments.length > 0) {
      openAttachmentsForm();
    } else {
      // Show a button to open the attachment form
      $elems.attachBtn.click(openAttachmentsForm);
    }

    // Prepare progress bar for submission
    $elems.progress = $mount.find('.comment-form-progress');

    // Submit
    $elems.form = $mount.find('.comment-form');
    $elems.submit = $mount.find('.comment-form-submit');
    $elems.submit.click(function () {
      // Read message
      var markdown = children.markdown.getMarkdown();
      var len = markdown.length;

      if (len < MIN_LEN || len > MAX_LEN) {
        // Do not submit if too short or long
        children.error.update('Message is too short or long.');
        return;
      }

      // Read attachments
      var attachments = [];
      if (children.attach) {
        attachments = children.attach.getAttachmentKeys();
      }

      // TODO Purge cache of unfinished comment

      // Hide form and reveal progress. This also prevents double click.
      ui.hide($elems.form);
      ui.show($elems.progress);
      // Hide possible previous messages
      children.error.reset();

      submit(markdown, attachments, function (err) {
        // Show form and hide progress
        ui.show($elems.form);
        ui.hide($elems.progress);

        if (err) {
          // Display error
          children.error.update(err.message);
        } else {
          // Success.
          // Empty the message input for next comment
          children.markdown.clear();
          // Inform parent for example to unbind the form.
          self.emit('success');
        }
      });
    });
  };

  self.unbind = function () {
    if ($mount) {
      $mount = null;
      ui.unbindAll(children);
      children = {};
      ui.offAll($elems);
      $elems = {};
    }
  };
};
<|endoftext|>


<commit_before>Polymer('three-stats', {
		show:false,
		created: function() {
			this.init();
		},
		init:function()
		{
			stats = new Stats();
			//stats.domElement.style.position = 'absolute';
			stats.domElement.style.left = '100px';
			stats.domElement.style.zIndex = 100;
			this.$.stats.appendChild( stats.domElement );
			this.stats = stats;
		},
		update:function()
		{
			this.stats.update();
		}

});
<commit_msg>- modified three-stats to initialize on ready callback instead of 'created'<commit_after>Polymer('three-stats', {
		show:false,
		ready: function() {
			this.init();
		},
		init:function()
		{
			stats = new Stats();
			//stats.domElement.style.position = 'absolute';
			stats.domElement.style.left = '100px';
			stats.domElement.style.zIndex = 100;
			this.$.stats.appendChild( stats.domElement );
			this.stats = stats;
		},
		update:function()
		{
			this.stats.update();
		}

});
<|endoftext|>


<commit_before>'use strict';

function competitions() {
    return {
        championsLeague: {
            code: 'champions-league',
            name: 'Champions League',
            smallName: 'C1'
        },
        europaLeague: {
            code: 'europa-league',
            name: 'Europa League',
            smallName: 'C3'
        },
    };
}

module.exports = competitions();<commit_msg>Remove useless comma<commit_after>'use strict';

function competitions() {
    return {
        championsLeague: {
            code: 'champions-league',
            name: 'Champions League',
            smallName: 'C1'
        },
        europaLeague: {
            code: 'europa-league',
            name: 'Europa League',
            smallName: 'C3'
        }
    };
}

module.exports = competitions();<|endoftext|>