<commit_before>/**
 * The DatePicker Component
 *
 * @module aui-datepicker
 * @submodule aui-datepicker-popover
 */

var Lang = A.Lang,

    _DOCUMENT = A.one(A.config.doc);

/**
 * A base class for `DatePickerPopover`.
 *
 * @class A.DatePickerPopover
 * @param {Object} config Object literal specifying widget configuration
 *     properties.
 * @constructor
 */

function DatePickerPopover() {}

/**
 * Static property used to define the default attribute configuration for the
 * `DatePickerPopover`.
 *
 * @property ATTRS
 * @type {Object}
 * @static
 */
DatePickerPopover.ATTRS = {

    /**
     * Sets the initial visibility.
     *
     * @attribute autoHide
     * @default true
     * @type {Boolean}
     */
    autoHide: {
        validator: Lang.isBoolean,
        value: true
    },

    /**
     * Stores the configuration of the `Popover` instance.
     *
     * @attribute popover
     * @default {}
     * @writeOnce
     */
    popover: {
        setter: '_setPopover',
        value: {},
        writeOnce: true
    },

    /**
     * Defines the CSS classname of the `Popover`.
     *
     * @attribute popoverCssClass
     * @default A.getClassName('datepicker-popover')
     * @type {String}
     */
    popoverCssClass: {
        validator: Lang.isString,
        value: A.getClassName('datepicker-popover')
    }
};

A.mix(DatePickerPopover.prototype, {
    popover: null,

    /**
     * Sets the `Popover` alignment.
     *
     * @method alignTo
     * @param node
     */
    alignTo: function(node) {
        var instance = this,
            popover = instance.getPopover();

        popover.set('align.node', node);
    },

    /**
     * Returns an existent `Popover` instance or creates a new one if it
     * doesn't exists.
     *
     * @method getPopover
     * @return {Popover}
     */
    getPopover: function() {
        var instance = this,
            popover = instance.popover;

        if (!popover) {
            popover = new A.Popover(instance.get('popover'));
            popover.get('boundingBox').on(
                'clickoutside', instance._onPopoverClickOutside, instance);

            instance.popover = popover;
        }

        return popover;
    },

    /**
     * Hides the `Popover`.
     *
     * @method hide
     */
    hide: function() {
        var instance = this;

        instance.getPopover().hide();
    },

    /**
     * Shows the `Popover`.
     *
     * @method show
     */
    show: function() {
        var instance = this;

        instance.getPopover().show();
    },

    /**
     * Checks if the active input is focused.
     *
     * @method _isActiveInputFocused
     * @protected
     * @return {Boolean}
     */
    _isActiveInputFocused: function() {
        var instance = this,
            activeInput = instance.get('activeInput');

        return (activeInput === _DOCUMENT.get('activeElement'));
    },

    /**
     * Fires when there's a click outside of the `Popover`.
     *
     * @method _onPopoverClickOutside
     * @param event
     * @protected
     */
    _onPopoverClickOutside: function(event) {
        var instance = this,
            target = event.target,
            activeInput = instance.get('activeInput');

        if (activeInput && (!instance._isActiveInputFocused() && !activeInput.contains(target))) {
            instance.hide();

            activeInput.focus();
        }
    },

    /**
     * Sets the `popover` value by merging its object with another properties.
     *
     * @method _setPopover
     * @param val
     * @protected
     */
    _setPopover: function(val) {
        var instance = this;

        return A.merge({
            bodyContent: '',
            cssClass: instance.get('popoverCssClass'),
            constrain: true,
            hideOn: [
                {
                    node: _DOCUMENT,
                    eventName: 'key',
                    keyCode: 'esc'
                }
            ],
            position: 'bottom',
            render: true,
            triggerShowEvent: 'click',
            triggerToggleEvent: null,
            visible: false
        }, val);
    }
}, true);

A.DatePickerPopover = DatePickerPopover;<commit_msg>Revert "AUI-1353 Focus on activeInput when clicking outside of popover calendar"<commit_after>/**
 * The DatePicker Component
 *
 * @module aui-datepicker
 * @submodule aui-datepicker-popover
 */

var Lang = A.Lang,

    _DOCUMENT = A.one(A.config.doc);

/**
 * A base class for `DatePickerPopover`.
 *
 * @class A.DatePickerPopover
 * @param {Object} config Object literal specifying widget configuration
 *     properties.
 * @constructor
 */

function DatePickerPopover() {}

/**
 * Static property used to define the default attribute configuration for the
 * `DatePickerPopover`.
 *
 * @property ATTRS
 * @type {Object}
 * @static
 */
DatePickerPopover.ATTRS = {

    /**
     * Sets the initial visibility.
     *
     * @attribute autoHide
     * @default true
     * @type {Boolean}
     */
    autoHide: {
        validator: Lang.isBoolean,
        value: true
    },

    /**
     * Stores the configuration of the `Popover` instance.
     *
     * @attribute popover
     * @default {}
     * @writeOnce
     */
    popover: {
        setter: '_setPopover',
        value: {},
        writeOnce: true
    },

    /**
     * Defines the CSS classname of the `Popover`.
     *
     * @attribute popoverCssClass
     * @default A.getClassName('datepicker-popover')
     * @type {String}
     */
    popoverCssClass: {
        validator: Lang.isString,
        value: A.getClassName('datepicker-popover')
    }
};

A.mix(DatePickerPopover.prototype, {
    popover: null,

    /**
     * Sets the `Popover` alignment.
     *
     * @method alignTo
     * @param node
     */
    alignTo: function(node) {
        var instance = this,
            popover = instance.getPopover();

        popover.set('align.node', node);
    },

    /**
     * Returns an existent `Popover` instance or creates a new one if it
     * doesn't exists.
     *
     * @method getPopover
     * @return {Popover}
     */
    getPopover: function() {
        var instance = this,
            popover = instance.popover;

        if (!popover) {
            popover = new A.Popover(instance.get('popover'));
            popover.get('boundingBox').on(
                'clickoutside', instance._onPopoverClickOutside, instance);

            instance.popover = popover;
        }

        return popover;
    },

    /**
     * Hides the `Popover`.
     *
     * @method hide
     */
    hide: function() {
        var instance = this;

        instance.getPopover().hide();
    },

    /**
     * Shows the `Popover`.
     *
     * @method show
     */
    show: function() {
        var instance = this;

        instance.getPopover().show();
    },

    /**
     * Checks if the active input is focused.
     *
     * @method _isActiveInputFocused
     * @protected
     * @return {Boolean}
     */
    _isActiveInputFocused: function() {
        var instance = this,
            activeInput = instance.get('activeInput');

        return (activeInput === _DOCUMENT.get('activeElement'));
    },

    /**
     * Fires when there's a click outside of the `Popover`.
     *
     * @method _onPopoverClickOutside
     * @param event
     * @protected
     */
    _onPopoverClickOutside: function(event) {
        var instance = this,
            target = event.target,
            activeInput = instance.get('activeInput');

        if (activeInput && (!instance._isActiveInputFocused() && !activeInput.contains(target))) {

            instance.hide();
        }
    },

    /**
     * Sets the `popover` value by merging its object with another properties.
     *
     * @method _setPopover
     * @param val
     * @protected
     */
    _setPopover: function(val) {
        var instance = this;

        return A.merge({
            bodyContent: '',
            cssClass: instance.get('popoverCssClass'),
            constrain: true,
            hideOn: [
                {
                    node: _DOCUMENT,
                    eventName: 'key',
                    keyCode: 'esc'
                }
            ],
            position: 'bottom',
            render: true,
            triggerShowEvent: 'click',
            triggerToggleEvent: null,
            visible: false
        }, val);
    }
}, true);

A.DatePickerPopover = DatePickerPopover;
<|endoftext|>


<commit_before>
describe('Account tests', function() {

  it('Change password', function() {
    browser.get('#/account/change_password');

    element(by.model('account.oldPassword')).sendKeys('3632465b3b5d1b8b4b8e56f74600da00cea92baf');
    element(by.model('account.newPassword')).sendKeys('3632465b3b5d1b8b4b8e56f74600da00cea92baf');
    element(by.model('account.verifyPassword')).sendKeys('3632465b3b5d1b8b4b8e56f74600da00cea92baf');

    $('button.btn-primary').click();

    browser.wait(EC.visibilityOf($('div.alert-success')), 5000);

    expect($('div.alert-success > div').getText()).toEqual('Password successful changed');
  });

});
<commit_msg>fix account test<commit_after>
describe('Account tests', function() {

  it('Change password', function() {
    browser.get('#/account/change_password');

    var EC = protractor.ExpectedConditions;

    element(by.model('account.oldPassword')).sendKeys('3632465b3b5d1b8b4b8e56f74600da00cea92baf');
    element(by.model('account.newPassword')).sendKeys('3632465b3b5d1b8b4b8e56f74600da00cea92baf');
    element(by.model('account.verifyPassword')).sendKeys('3632465b3b5d1b8b4b8e56f74600da00cea92baf');

    $('button.btn-primary').click();

    browser.wait(EC.visibilityOf($('div.alert-success')), 5000);

    expect($('div.alert-success > div').getText()).toEqual('Password successful changed');
  });

});
<|endoftext|>


<commit_before>/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 ~ Copyright 2020 Adobe Systems Incorporated
 ~
 ~ Licensed under the Apache License, Version 2.0 (the "License");
 ~ you may not use this file except in compliance with the License.
 ~ You may obtain a copy of the License at
 ~
 ~     http://www.apache.org/licenses/LICENSE-2.0
 ~
 ~ Unless required by applicable law or agreed to in writing, software
 ~ distributed under the License is distributed on an "AS IS" BASIS,
 ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 ~ See the License for the specific language governing permissions and
 ~ limitations under the License.
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html

// To be able to run tests in Docker
process.env.CHROME_BIN = require('puppeteer').executablePath();

module.exports = function(config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine', '@angular-devkit/build-angular'],
    plugins: [
      require('karma-jasmine'),
      require('karma-chrome-launcher'),
      require('karma-jasmine-html-reporter'),
      require('karma-coverage-istanbul-reporter'),
      require('@angular-devkit/build-angular/plugins/karma')
    ],
    client: {
      clearContext: false // leave Jasmine Spec Runner output visible in browser
    },
    coverageIstanbulReporter: {
      dir: require('path').join(__dirname, './coverage/angular-app'),
      reports: ['html', 'lcovonly', 'text-summary'],
      fixWebpackSourcePaths: true
    },
    reporters: ['progress', 'kjhtml'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['ChromeHeadless'], // To be able to run tests in Docker
    browserNoActivityTimeout: 60000,
    singleRun: true, // To be able to run tests in Docker
    restartOnFileChange: true
  });
};
<commit_msg>@releng increase disconnect timeout for angular frontend ITs<commit_after>/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 ~ Copyright 2020 Adobe Systems Incorporated
 ~
 ~ Licensed under the Apache License, Version 2.0 (the "License");
 ~ you may not use this file except in compliance with the License.
 ~ You may obtain a copy of the License at
 ~
 ~     http://www.apache.org/licenses/LICENSE-2.0
 ~
 ~ Unless required by applicable law or agreed to in writing, software
 ~ distributed under the License is distributed on an "AS IS" BASIS,
 ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 ~ See the License for the specific language governing permissions and
 ~ limitations under the License.
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html

// To be able to run tests in Docker
process.env.CHROME_BIN = require('puppeteer').executablePath();

module.exports = function(config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine', '@angular-devkit/build-angular'],
    plugins: [
      require('karma-jasmine'),
      require('karma-chrome-launcher'),
      require('karma-jasmine-html-reporter'),
      require('karma-coverage-istanbul-reporter'),
      require('@angular-devkit/build-angular/plugins/karma')
    ],
    client: {
      clearContext: false // leave Jasmine Spec Runner output visible in browser
    },
    coverageIstanbulReporter: {
      dir: require('path').join(__dirname, './coverage/angular-app'),
      reports: ['html', 'lcovonly', 'text-summary'],
      fixWebpackSourcePaths: true
    },
    reporters: ['progress', 'kjhtml'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['ChromeHeadless'], // To be able to run tests in Docker
    // to avoid DISCONNECTED messages
    browserDisconnectTimeout : 10000, // default 2000
    browserDisconnectTolerance : 1, // default 0
    browserNoActivityTimeout : 4*60*1000, //default 10000
    captureTimeout : 4*60*1000, //default 60000
    singleRun: true, // To be able to run tests in Docker
    restartOnFileChange: true
  });
};
<|endoftext|>


<commit_before><commit_msg>Minor edit.<commit_after><|endoftext|>


<commit_before>/**
 *
 *    Copyright (c) 2020 Silicon Labs
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

const { dialog, Menu, shell } = require('electron')
const env = require('../util/env.js')
const queryGeneric = require('../db/query-generic.js')
const querySession = require('../db/query-session.js')
const uiJs = require('./ui-util.js')
const preference = require('../main-process/preference.js')
const about = require('../main-process/about.js')
const generationEngine = require('../generator/generation-engine.js')
const queryPackage = require('../db/query-package.js')
const dbEnum = require('../../src-shared/db-enum.js')
const commonUrl = require('../../src-shared/common-url.js')
const browserApi = require('./browser-api.js')

const newConfiguration = 'New Configuration'

const template = (db, httpPort) => [
  {
    role: 'fileMenu',
    submenu: [
      {
        label: newConfiguration + '...',
        accelerator: 'CmdOrCtrl+N',
        httpPort: httpPort,
        click(menuItem, browserWindow, event) {
          uiJs.openNewConfiguration(menuItem.httpPort)
        },
      },
      {
        label: 'Open File...',
        accelerator: 'CmdOrCtrl+O',
        db: db,
        click(menuItem, browserWindow, event) {
          doOpen(menuItem.db)
        },
      },
      {
        label: 'Save',
        accelerator: 'CmdOrCtrl+S',
        db: db,
        click(menuItem, browserWindow, event) {
          doSave(menuItem.db, browserWindow)
        },
      },
      {
        label: 'Save As...',
        db: db,
        click(menuItem, browserWindow, event) {
          doSaveAs(menuItem.db, browserWindow)
        },
      },
      {
        type: 'separator',
      },
      {
        label: 'Generate Code',
        db: db,
        click(menuItem, browserWindow, event) {
          generateInDir(menuItem.db, browserWindow)
        },
      },
      {
        label: 'Preferences...',
        httpPort: httpPort,
        click(menuItem, browserWindow, event) {
          preference.createOrShowPreferencesWindow(
            browserWindow,
            menuItem.httpPort
          )
        },
      },
      {
        type: 'separator',
      },
      {
        role: 'close',
      },
      {
        type: 'separator',
      },
      {
        role: 'quit',
      },
    ],
  },
  {
    role: 'editMenu',
  },
  {
    role: 'viewMenu',
  },
  {
    role: 'windowMenu',
  },
  {
    label: 'Help',
    submenu: [
      {
        label: 'Documentation',
        click(menuItem, browserWindow, event) {
          shell.openExternal(commonUrl.documentationUrl)
        },
      },
      {
        label: 'User and session information',
        db: db,
        click(menuItem, browserWindow, event) {
          getUserSessionInfoMessage(menuItem.db, browserWindow)
            .then((msg) => {
              dialog.showMessageBox(browserWindow, {
                title: 'User and session information',
                message: msg,
                buttons: ['Dismiss'],
              })
            })
            .catch((err) => uiJs.showErrorMessage('Session info', err))
        },
      },
      {
        label: 'Renderer API information',
        click(menuItem, browserWindow, event) {
          browserApi.getRendererApiInformation(browserWindow).then((msg) => {
            dialog.showMessageBox(browserWindow, {
              title: 'Renderer API information',
              message: msg,
              buttons: ['Dismiss'],
            })
          })
        },
      },
      {
        type: 'separator',
      },
      {
        label: 'Start progress',
        click(menuItem, browserWindow) {
          browserApi.progressStart(browserWindow, 'Test progress indication.')
        },
      },
      {
        label: 'End progress',
        click(menuItem, browserWindow) {
          browserApi.progressEnd(browserWindow)
        },
      },
      {
        label: 'About',
        httpPort: httpPort,
        click(menuItem, browserWindow, event) {
          about.createOrShowAboutWindow(browserWindow, menuItem.httpPort)
        },
      },
    ],
  },
]

async function getUserSessionInfoMessage(db, browserWindow) {
  let userKey = await browserApi.getUserKeyFromBrowserWindow(browserWindow)
  let session = await querySession.getSessionInfoFromSessionKey(db, userKey)
  let sessionUuid = await browserApi.getSessionUuidFromBrowserWindow(
    browserWindow
  )
  return `
  Browser session UUID: ${sessionUuid}
  Browser user key: ${userKey}

  Session id: ${session.sessionId}
  Session creationTime: ${new Date(session.creationTime)}
  Session session key:  ${session.sessionKey}
  `
}

/**
 * Perform a file->open operation.
 *
 * @param {*} menuItem
 * @param {*} browserWindow
 * @param {*} event
 */
function doOpen(db) {
  queryGeneric
    .selectFileLocation(db, dbEnum.fileLocationCategory.save)
    .then((filePath) => {
      let opts = {
        properties: ['openFile', 'multiSelections'],
      }
      if (filePath != null) {
        opts.defaultPath = filePath
      }
      return dialog.showOpenDialog(opts)
    })
    .then((result) => {
      if (!result.canceled) {
        fileOpen(result.filePaths)
      }
    })
    .catch((err) => uiJs.showErrorMessage('Open file', err))
}

/**
 * Perform a save, defering to save as if file is not yet selected.
 *
 * @param {*} menuItem
 * @param {*} browserWindow
 * @param {*} event
 */
function doSave(db, browserWindow) {
  if (browserWindow.getTitle().includes(newConfiguration)) {
    doSaveAs(db, browserWindow)
  } else {
    fileSave(browserWindow, null)
  }
}

/**
 * Perform save as.
 *
 * @param {*} menuItem
 * @param {*} browserWindow
 * @param {*} event
 */
function doSaveAs(db, browserWindow) {
  queryGeneric
    .selectFileLocation(db, dbEnum.fileLocationCategory.save)
    .then((filePath) => {
      let opts = {
        filters: [
          { name: 'ZAP Config', extensions: ['zap'] },
          { name: 'All Files', extensions: ['*'] },
        ],
      }
      if (filePath != null) {
        opts.defaultPath = filePath
      }
      return dialog.showSaveDialog(opts)
    })
    .then((result) => {
      if (!result.canceled) {
        fileSave(browserWindow, result.filePath)
        return result.filePath
      } else {
        return null
      }
    })
    .then((filePath) => {
      if (filePath != null) {
        queryGeneric.insertFileLocation(
          db,
          filePath,
          dbEnum.fileLocationCategory.save
        )
        browserWindow.setTitle(filePath)
      }
    })
    .catch((err) => uiJs.showErrorMessage('Save file', err))
}

/**
 * This function gets the directory where user wants the output and
 * calls generateCode function which generates the code in the user selected
 * output.
 *
 * @param {*} browserWindow
 */
function generateInDir(db, browserWindow) {
  dialog
    .showOpenDialog({
      buttonLabel: 'Save',
      properties: ['openDirectory', 'createDirectory'],
    })
    .then((result) => {
      if (!result.canceled) {
        return Promise.resolve({ path: result.filePaths[0] })
      } else {
        return Promise.resolve({})
      }
    })
    .then((context) => {
      if (!('path' in context)) return context

      return browserApi
        .getUserKeyFromBrowserWindow(browserWindow)
        .then((sessionKey) =>
          querySession.getSessionInfoFromSessionKey(db, sessionKey)
        )
        .then((session) => {
          env.logInfo(`Generating for session ${session.sessionId}`)
          context.sessionId = session.sessionId
          return context
        })
    })
    .then((context) => {
      context.packageIds = []
      if (!('sessionId' in context)) return context

      env.logInfo(
        `Collecting session packages for session ${context.sessionId}`
      )
      return queryPackage
        .getSessionPackagesByType(
          db,
          context.sessionId,
          dbEnum.packageType.genTemplatesJson
        )
        .then((pkgs) => {
          pkgs.forEach((pkg) => {
            env.logInfo(`Package ${pkg.id}, type: ${pkg.type}`)
            context.packageIds.push(pkg.id)
          })
          return context
        })
    })
    .then((context) => {
      let promises = []
      context.packageIds.forEach((pkgId) => {
        env.logInfo(
          `Setting up generation for session ${context.sessionId} and package ${pkgId}`
        )
        promises.push(
          generationEngine.generateAndWriteFiles(
            db,
            context.sessionId,
            pkgId,
            context.path
          )
        )
      })
      return Promise.all(promises).then(() => context)
    })
    .then((context) => {
      dialog.showMessageBox(browserWindow, {
        title: 'Generation',
        message: `Generation Output: ${context.path}`,
        buttons: ['Ok'],
      })
    })
    .catch((err) => uiJs.showErrorMessage('Save file', err))
}

/**
 * perform the save.
 *
 * @param {*} db
 * @param {*} browserWindow
 * @param {*} filePath
 * @returns Promise of saving.
 */
function fileSave(browserWindow, filePath) {
  browserApi.executeSave(browserWindow, filePath)
}

/**
 * Perform the do open action, possibly reading in multiple files.
 *
 * @param {*} db
 * @param {*} filePaths
 */
function fileOpen(filePaths) {
  filePaths.forEach((filePath, index) => {
    uiJs.openFileConfiguration(filePath, httpPort)
  })
}

/**
 * Initialize a menu.
 *
 * @export
 * @param {*} port
 */
function initMenu(db, httpPort) {
  const menu = Menu.buildFromTemplate(template(db, httpPort))
  Menu.setApplicationMenu(menu)
}

exports.initMenu = initMenu
exports.newConfiguration = newConfiguration
<commit_msg>Fix generation from the File menu.<commit_after>/**
 *
 *    Copyright (c) 2020 Silicon Labs
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

const { dialog, Menu, shell } = require('electron')
const env = require('../util/env.js')
const queryGeneric = require('../db/query-generic.js')
const querySession = require('../db/query-session.js')
const uiJs = require('./ui-util.js')
const preference = require('../main-process/preference.js')
const about = require('../main-process/about.js')
const generationEngine = require('../generator/generation-engine.js')
const queryPackage = require('../db/query-package.js')
const dbEnum = require('../../src-shared/db-enum.js')
const commonUrl = require('../../src-shared/common-url.js')
const browserApi = require('./browser-api.js')

const newConfiguration = 'New Configuration'

const template = (db, httpPort) => [
  {
    role: 'fileMenu',
    submenu: [
      {
        label: newConfiguration + '...',
        accelerator: 'CmdOrCtrl+N',
        httpPort: httpPort,
        click(menuItem, browserWindow, event) {
          uiJs.openNewConfiguration(menuItem.httpPort)
        },
      },
      {
        label: 'Open File...',
        accelerator: 'CmdOrCtrl+O',
        db: db,
        click(menuItem, browserWindow, event) {
          doOpen(menuItem.db)
        },
      },
      {
        label: 'Save',
        accelerator: 'CmdOrCtrl+S',
        db: db,
        click(menuItem, browserWindow, event) {
          doSave(menuItem.db, browserWindow)
        },
      },
      {
        label: 'Save As...',
        db: db,
        click(menuItem, browserWindow, event) {
          doSaveAs(menuItem.db, browserWindow)
        },
      },
      {
        type: 'separator',
      },
      {
        label: 'Generate Code',
        db: db,
        click(menuItem, browserWindow, event) {
          generateInDir(menuItem.db, browserWindow)
        },
      },
      {
        label: 'Preferences...',
        httpPort: httpPort,
        click(menuItem, browserWindow, event) {
          preference.createOrShowPreferencesWindow(
            browserWindow,
            menuItem.httpPort
          )
        },
      },
      {
        type: 'separator',
      },
      {
        role: 'close',
      },
      {
        type: 'separator',
      },
      {
        role: 'quit',
      },
    ],
  },
  {
    role: 'editMenu',
  },
  {
    role: 'viewMenu',
  },
  {
    role: 'windowMenu',
  },
  {
    label: 'Help',
    submenu: [
      {
        label: 'Documentation',
        click(menuItem, browserWindow, event) {
          shell.openExternal(commonUrl.documentationUrl)
        },
      },
      {
        label: 'User and session information',
        db: db,
        click(menuItem, browserWindow, event) {
          getUserSessionInfoMessage(menuItem.db, browserWindow)
            .then((msg) => {
              dialog.showMessageBox(browserWindow, {
                title: 'User and session information',
                message: msg,
                buttons: ['Dismiss'],
              })
            })
            .catch((err) => uiJs.showErrorMessage('Session info', err))
        },
      },
      {
        label: 'Renderer API information',
        click(menuItem, browserWindow, event) {
          browserApi.getRendererApiInformation(browserWindow).then((msg) => {
            dialog.showMessageBox(browserWindow, {
              title: 'Renderer API information',
              message: msg,
              buttons: ['Dismiss'],
            })
          })
        },
      },
      {
        type: 'separator',
      },
      {
        label: 'Start progress',
        click(menuItem, browserWindow) {
          browserApi.progressStart(browserWindow, 'Test progress indication.')
        },
      },
      {
        label: 'End progress',
        click(menuItem, browserWindow) {
          browserApi.progressEnd(browserWindow)
        },
      },
      {
        label: 'About',
        httpPort: httpPort,
        click(menuItem, browserWindow, event) {
          about.createOrShowAboutWindow(browserWindow, menuItem.httpPort)
        },
      },
    ],
  },
]

async function getUserSessionInfoMessage(db, browserWindow) {
  let userKey = await browserApi.getUserKeyFromBrowserWindow(browserWindow)
  let session = await querySession.getSessionInfoFromSessionKey(db, userKey)
  let sessionUuid = await browserApi.getSessionUuidFromBrowserWindow(
    browserWindow
  )
  return `
  Browser session UUID: ${sessionUuid}
  Browser user key: ${userKey}

  Session id: ${session.sessionId}
  Session creationTime: ${new Date(session.creationTime)}
  Session session key:  ${session.sessionKey}
  `
}

/**
 * Perform a file->open operation.
 *
 * @param {*} menuItem
 * @param {*} browserWindow
 * @param {*} event
 */
function doOpen(db) {
  queryGeneric
    .selectFileLocation(db, dbEnum.fileLocationCategory.save)
    .then((filePath) => {
      let opts = {
        properties: ['openFile', 'multiSelections'],
      }
      if (filePath != null) {
        opts.defaultPath = filePath
      }
      return dialog.showOpenDialog(opts)
    })
    .then((result) => {
      if (!result.canceled) {
        fileOpen(result.filePaths)
      }
    })
    .catch((err) => uiJs.showErrorMessage('Open file', err))
}

/**
 * Perform a save, defering to save as if file is not yet selected.
 *
 * @param {*} menuItem
 * @param {*} browserWindow
 * @param {*} event
 */
function doSave(db, browserWindow) {
  if (browserWindow.getTitle().includes(newConfiguration)) {
    doSaveAs(db, browserWindow)
  } else {
    fileSave(browserWindow, null)
  }
}

/**
 * Perform save as.
 *
 * @param {*} menuItem
 * @param {*} browserWindow
 * @param {*} event
 */
function doSaveAs(db, browserWindow) {
  queryGeneric
    .selectFileLocation(db, dbEnum.fileLocationCategory.save)
    .then((filePath) => {
      let opts = {
        filters: [
          { name: 'ZAP Config', extensions: ['zap'] },
          { name: 'All Files', extensions: ['*'] },
        ],
      }
      if (filePath != null) {
        opts.defaultPath = filePath
      }
      return dialog.showSaveDialog(opts)
    })
    .then((result) => {
      if (!result.canceled) {
        fileSave(browserWindow, result.filePath)
        return result.filePath
      } else {
        return null
      }
    })
    .then((filePath) => {
      if (filePath != null) {
        queryGeneric.insertFileLocation(
          db,
          filePath,
          dbEnum.fileLocationCategory.save
        )
        browserWindow.setTitle(filePath)
      }
    })
    .catch((err) => uiJs.showErrorMessage('Save file', err))
}

/**
 * This function gets the directory where user wants the output and
 * calls generateCode function which generates the code in the user selected
 * output.
 *
 * @param {*} browserWindow
 */
function generateInDir(db, browserWindow) {
  dialog
    .showOpenDialog({
      buttonLabel: 'Save',
      properties: ['openDirectory', 'createDirectory'],
    })
    .then((result) => {
      if (!result.canceled) {
        return Promise.resolve({ path: result.filePaths[0] })
      } else {
        return Promise.resolve({})
      }
    })
    .then((context) => {
      if (!('path' in context)) return context

      return browserApi
        .getSessionUuidFromBrowserWindow(browserWindow)
        .then((sessionKey) =>
          querySession.getSessionInfoFromSessionKey(db, sessionKey)
        )
        .then((session) => {
          env.logInfo(`Generating for session ${session.sessionId}`)
          context.sessionId = session.sessionId
          return context
        })
    })
    .then((context) => {
      context.packageIds = []
      if (!('sessionId' in context)) return context

      env.logInfo(
        `Collecting session packages for session ${context.sessionId}`
      )
      return queryPackage
        .getSessionPackagesByType(
          db,
          context.sessionId,
          dbEnum.packageType.genTemplatesJson
        )
        .then((pkgs) => {
          pkgs.forEach((pkg) => {
            env.logInfo(`Package ${pkg.id}, type: ${pkg.type}`)
            context.packageIds.push(pkg.id)
          })
          return context
        })
    })
    .then((context) => {
      let promises = []
      context.packageIds.forEach((pkgId) => {
        env.logInfo(
          `Setting up generation for session ${context.sessionId} and package ${pkgId}`
        )
        promises.push(
          generationEngine.generateAndWriteFiles(
            db,
            context.sessionId,
            pkgId,
            context.path
          )
        )
      })
      return Promise.all(promises).then(() => context)
    })
    .then((context) => {
      dialog.showMessageBox(browserWindow, {
        title: 'Generation',
        message: `Generation Output: ${context.path}`,
        buttons: ['Ok'],
      })
    })
    .catch((err) => uiJs.showErrorMessage('Save file', err))
}

/**
 * perform the save.
 *
 * @param {*} db
 * @param {*} browserWindow
 * @param {*} filePath
 * @returns Promise of saving.
 */
function fileSave(browserWindow, filePath) {
  browserApi.executeSave(browserWindow, filePath)
}

/**
 * Perform the do open action, possibly reading in multiple files.
 *
 * @param {*} db
 * @param {*} filePaths
 */
function fileOpen(filePaths) {
  filePaths.forEach((filePath, index) => {
    uiJs.openFileConfiguration(filePath, httpPort)
  })
}

/**
 * Initialize a menu.
 *
 * @export
 * @param {*} port
 */
function initMenu(db, httpPort) {
  const menu = Menu.buildFromTemplate(template(db, httpPort))
  Menu.setApplicationMenu(menu)
}

exports.initMenu = initMenu
exports.newConfiguration = newConfiguration
<|endoftext|>


<commit_before>var app = angular.module("quiz", ['nvd3ChartDirectives']);

function Quiz($scope,$http){

    function refresh() {
        var request = $http({
            method: "get",
            url: "./quiz_list"
        });
        request.success(function(data) {
            s.quizes = data.quizAry;
            s.isLoading = false;
        });

        request.error(function(data) {
            s.isLoading = false;
        });
    }

    function refreshSubmissionCount() {
        var request = $http({
            method: "get",
            url: "./sub_count?" + "qid",
        });
        request.success(function(data) {
            s.quizes = data.quizAry;
            s.isLoading = false;
        });
        request.error(function(data) {
            s.isLoading = false;
        });
    }

    function loadSubmissions() {
        var request = $http({
            method: "get",
            url: "./submissions?quiz_id=" + s.quiz._id
        });
        request.success(function(data) {
            console.log(data);
            s.submissions = data.submissionsAry;
            s.loadingResults = false;
        });
    }

    var s = $scope;
    s.isLoading = true;
    s.takingQuiz = false;
    s.quizes = [];
    s.responses = [];
    s.viewingResults = false;
    s.averageScore = '';
    s.quiz = {};
    //s.

    refresh();

    s.take = function(q) {
        console.log(q);
        s.quiz = q;
        s.takingQuiz = true;
    }

    s.submit = function() {
        s.takingQuiz = false;

        var request = $http({
            method: "post",
            url: "./",
            data: {quiz:s.quiz,
                name:s.name,
                responses:s.responses
            }
        });

        request.success(function(data) {
            //refreshSubmissionCount()
        });

        request.error(function(data) {
            //s.isLoading = false;
        });
    }

    s.viewResults = function(q) {
        s.viewingResults = true;
        s.loadingResults = true;
        s.quiz = q;
        loadSubmissions();
    }

    s.removeBlank = function() {
        var request = $http({
            method: "post",
            url: "./removeBlank"
        });

        request.success(function(data) {
            refresh();
        });
    }

    s.response = function(q,r) {
        s.responses[q] = r;
    };

    s.subTotals = function() {
        var subs = s.submissions;
        var quiz = s.quiz;
        if (!s.viewingResults || s.loadingResults) return;
        var questions = quiz.questions;
        var totalPoints = 0;
        var data = [];
        var questions = s.quiz.questions;
        for (var i in questions) {
            var q = questions[i];
            data[i] = [{
                key: "chart" + i,
                values: []
            }];
            var values = data[i][0].values;
            var responses = q.responses;
            for (var j in responses) {
                values.push([j,0]);
            }
        }
        for (var i in subs) {
            var q = questions[i];
            var answer = q.answer;
            var submission = subs[i];
            var responses = submission.responses;
            for (var j in responses) {
                data[j][0].values[responses[j]][1] += 1;
                if (responses[i] == answer) {
                    totalPoints += 1;
                }
            }
        }
        console.log(data);
        s.data = data;

        var totalQuestions = questions.length;
        var totalSubmissions = subs.length;
        if (totalSubmissions > 0 && totalQuestions > 0) {
            s.averageScore = totalPoints / (totalSubmissions * totalQuestions);
        }
        return totalSubmissions;
    };

    s.return = function() {
        s.viewingResults = false;
    }
        /*
        for (var i in subs) {
            var sub = subs[i];
            var responses = sub.responses;
            for
                s.totals[j] += responses[]j]
        }

        for (var i in questions) {
            var q = questions[i];
            var answer = q.opts.answer;
            var unanswered = (responses[i] === undefined || (!responses[i] && (responses[i] !== 0)));
            if (responses[i] == answer) {
                totalPoints += 1;
            }
            responses[i];
        }

        if (totalSubmissions > 0 && totalQuestions > 0) {
            s.averageScore = totalPoints / (totalSubmissions * totalQuestions)
        }
        */
}
<commit_msg>fix average score calc<commit_after>var app = angular.module("quiz", ['nvd3ChartDirectives']);

function Quiz($scope,$http){

    function refresh() {
        var request = $http({
            method: "get",
            url: "./quiz_list"
        });
        request.success(function(data) {
            s.quizes = data.quizAry;
            s.isLoading = false;
        });

        request.error(function(data) {
            s.isLoading = false;
        });
    }

    function refreshSubmissionCount() {
        var request = $http({
            method: "get",
            url: "./sub_count?" + "qid",
        });
        request.success(function(data) {
            s.quizes = data.quizAry;
            s.isLoading = false;
        });
        request.error(function(data) {
            s.isLoading = false;
        });
    }

    function loadSubmissions() {
        var request = $http({
            method: "get",
            url: "./submissions?quiz_id=" + s.quiz._id
        });
        request.success(function(data) {
            console.log(data);
            s.submissions = data.submissionsAry;
            s.loadingResults = false;
        });
    }

    var s = $scope;
    s.isLoading = true;
    s.takingQuiz = false;
    s.quizes = [];
    s.responses = [];
    s.viewingResults = false;
    s.averageScore = '';
    s.quiz = {};
    //s.

    refresh();

    s.take = function(q) {
        console.log(q);
        s.quiz = q;
        s.takingQuiz = true;
    }

    s.submit = function() {
        s.takingQuiz = false;

        var request = $http({
            method: "post",
            url: "./",
            data: {quiz:s.quiz,
                name:s.name,
                responses:s.responses
            }
        });

        request.success(function(data) {
            //refreshSubmissionCount()
        });

        request.error(function(data) {
            //s.isLoading = false;
        });
    }

    s.viewResults = function(q) {
        s.viewingResults = true;
        s.loadingResults = true;
        s.quiz = q;
        loadSubmissions();
    }

    s.removeBlank = function() {
        var request = $http({
            method: "post",
            url: "./removeBlank"
        });

        request.success(function(data) {
            refresh();
        });
    }

    s.response = function(q,r) {
        s.responses[q] = r;
    };

    s.subTotals = function() {
        var subs = s.submissions;
        var quiz = s.quiz;
        if (!s.viewingResults || s.loadingResults) return;
        var questions = quiz.questions;
        var totalPoints = 0;
        var data = [];
        var questions = s.quiz.questions;
        for (var i in questions) {
            var q = questions[i];
            data[i] = [{
                key: "chart" + i,
                values: []
            }];
            var values = data[i][0].values;
            var responses = q.responses;
            for (var j in responses) {
                values.push([j,0]);
            }
        }
        for (var i in subs) {
            var submission = subs[i];
            var responses = submission.responses;
            for (var j in responses) {
                data[j][0].values[responses[j]][1] += 1;
                var q = questions[j];
                var answer = q.answer;
                if (responses[i] == answer) {
                    totalPoints += 1;
                }
            }
        }
        console.log(data);
        s.data = data;

        var totalQuestions = questions.length;
        var totalSubmissions = subs.length;
        if (totalSubmissions > 0 && totalQuestions > 0) {
            s.averageScore = totalPoints / (totalSubmissions * totalQuestions);
        }
        return totalSubmissions;
    };

    s.return = function() {
        s.viewingResults = false;
    }
        /*
        for (var i in subs) {
            var sub = subs[i];
            var responses = sub.responses;
            for
                s.totals[j] += responses[]j]
        }

        for (var i in questions) {
            var q = questions[i];
            var answer = q.opts.answer;
            var unanswered = (responses[i] === undefined || (!responses[i] && (responses[i] !== 0)));
            if (responses[i] == answer) {
                totalPoints += 1;
            }
            responses[i];
        }

        if (totalSubmissions > 0 && totalQuestions > 0) {
            s.averageScore = totalPoints / (totalSubmissions * totalQuestions)
        }
        */
}
<|endoftext|>


<commit_before>import React from 'react';
import { translate } from 'react-i18next';
import SearchIcon from '@material-ui/icons/Search';
import CloseRoundedIcon from '@material-ui/icons/CloseRounded';
import DeleteForeverRoundedIcon from '@material-ui/icons/DeleteForeverRounded';
import EditRoundedIcon from '@material-ui/icons/EditRounded';
import KeyboardArrowDownRoundedIcon from '@material-ui/icons/KeyboardArrowDownRounded';
import OpenInNewRoundedIcon from '@material-ui/icons/OpenInNewRounded';
import VisibilityRoundedIcon from '@material-ui/icons/VisibilityRounded';
import { Box, Flex } from 'rebass/styled-components';
import map from 'lodash/map';
import filter from 'lodash/filter';
import get from 'lodash/get';
import includes from 'lodash/includes';
import keyBy from 'lodash/keyBy';
import keys from 'lodash/keys';
import noop from 'lodash/noop';
import reduce from 'lodash/reduce';
import transform from 'lodash/transform';
import values from 'lodash/values';
import without from 'lodash/without';

import {
  bindPopover,
  bindTrigger,
  usePopupState,
} from 'material-ui-popup-state/hooks';

import {
  Dialog,
  DialogTitle,
  DialogContent,
  DialogActions,
} from '../../components/elements/Dialog';

import Table from '../../components/elements/Table';
import Button from '../../components/elements/Button';
import Checkbox from '../../components/elements/Checkbox';
import Popover from '../../components/elements/Popover';
import PopoverMenu from '../../components/elements/PopoverMenu';
import TextInput from '../../components/elements/TextInput';
import { Body1, Headline, MediumTitle } from '../../components/elements/FontStyles';
import withPrescriptions from './withPrescriptions';
import { dateRegex, prescriptionStateOptions } from './prescriptionFormConstants';
import { useToasts } from '../../providers/ToastProvider';
import { useIsFirstRender } from '../../core/hooks';

const Prescriptions = props => {
  const {
    t,
    deletePrescription,
    deletingPrescription,
    history,
    prescriptions = [],
  } = props;

  const isFirstRender = useIsFirstRender();
  const { set: setToast } = useToasts();

  const prescriptionStates = keyBy(prescriptionStateOptions, 'value');

  const popupFilterState = usePopupState({
    variant: 'popover',
    popupId: 'filterState',
  });

  const initialDeleteDialogState = {
    closeParentPopover: noop,
    open: false,
    prescription: {},
  };

  const [deleteDialog, setDeleteDialog] = React.useState(initialDeleteDialogState);

  function closeDeleteDialog() {
    setDeleteDialog({
      ...deleteDialog,
      open: false,
    });

    // We fully reset the dialog after it's had time to close, so that we don't see the patient name
    // in the delete dialog disappear before it closes.
    setTimeout(() => setDeleteDialog(initialDeleteDialogState), 100);
  }

  const [searchText, setSearchText] = React.useState('');
  const [filterStateActive, setFilterStateActive] = React.useState(false);

  const [activeStates, setActiveStates] = React.useState(reduce(prescriptionStateOptions, (result, { value }) => {
    result[value] = true;
    return result;
  }, {}));

  const activeStatesCount = without(values(activeStates), false).length;

  const [pendingActiveStates, setPendingActiveStates] = React.useState(activeStates);
  const togglePendingActiveState = (value) => setPendingActiveStates({ ...pendingActiveStates, [value]: !pendingActiveStates[value] })

  const data = filter(
    map(prescriptions, prescription => ({
      birthday: get(prescription, 'latestRevision.attributes.birthday').replace(dateRegex, '$2/$3/$1'),
      createdTime: get(prescription, 'latestRevision.attributes.createdTime'),
      firstName: get(prescription, 'latestRevision.attributes.firstName'),
      id: get(prescription, 'id'),
      lastName: get(prescription, 'latestRevision.attributes.lastName'),
      mrn: get(prescription, 'latestRevision.attributes.mrn'),
      patientUserId: get(prescription, 'patientUserId'),
      state: get(prescription, 'state'),
    })),
    prescription => activeStates[prescription.state]
  );

  function handleSearchChange(event) {
    setSearchText(event.target.value);
  }

  function clearSearchText() {
    setSearchText('');
  }

  const handleAddNew = () => props.history.push('/prescriptions/new');

  const handleOpenPrescription = prescription => popupState => {
    props.history.push(`/prescriptions/${prescription.id}`);
  };

  const handleDeletePrescription = prescription => popupState => {
    setDeleteDialog({
      closeParentPopover: popupState.close,
      open: true,
      prescription,
    });
  }

  function handleRowClick(prescription) {
    return handleOpenPrescription(prescription)();
  }

  const actionMenuItems = prescription => {
    const isEditable = includes(['draft', 'pending'], prescription.state);

    const items = [
      {
        icon: isEditable ? EditRoundedIcon : VisibilityRoundedIcon,
        iconLabel: isEditable ? t('Update') : t('View'),
        iconPosition: 'left',
        id: isEditable ? 'update' : 'view',
        onClick: handleOpenPrescription(prescription),
        text: isEditable ? t('Update prescription') : t('View Prescription'),
        variant: 'actionListItem',
      },
    ];

    if (isEditable) items.push({
      icon: DeleteForeverRoundedIcon,
      iconLabel: 'Delete',
      iconPosition: 'left',
      id: 'delete',
      onClick: handleDeletePrescription(prescription),
      text: 'Delete prescription',
      variant: 'actionListItemDanger',
      disabled: !isEditable,
    });

    return items;
  };

  const renderMore = (prescription) => (
    <Box onClick={e => {
      // Prevent clicks from propogating up to the table row click handlers
      e.stopPropagation();
    }}>
      <PopoverMenu
        id="more-prescription-actions"
        label="More actions"
        items={actionMenuItems(prescription)}
      />
    </Box>
  );

  const openPatientData = (patientUserId) => {
    const href = history.createHref({
      pathname: `/patients/${patientUserId}/data`,
    });

    window.open(href, '_blank');
  }

  const patientNameFromPrescription = ({ firstName = '', lastName = '' }) => [firstName, lastName].join(' ');

  const renderName = prescription => (
    <>
     {prescription.patientUserId ? <Button
        p={0}
        m={0}
        color="text.link"
        fontSize={1}
        icon={OpenInNewRoundedIcon}
        iconLabel="Open patient data in new tab"
        variant="textPrimary"
        onClick={(e) => {
          // Prevent clicks from propogating up to the table row click handlers
          e.stopPropagation();
          openPatientData(prescription.patientUserId);
        }}
      >
        {patientNameFromPrescription(prescription)}
      </Button> : patientNameFromPrescription(prescription)
     }
    </>
  );
  const renderState = ({ state }) => get(prescriptionStates, [state, 'label'], '');

  const columns = [
    { title: t('Name'), field: 'patient', align: 'left', sortable: true, sortBy: 'firstName', render: renderName, searchable: true, searchBy: ['firstName', 'lastName'] },
    { title: t('MRN'), field: 'mrn', align: 'left', sortable: true, searchable: true },
    { title: t('Date of birth'), field: 'birthday', align: 'left', sortable: true, searchable: true },
    { title: t('Status'), field: 'state', render: renderState, align: 'left', sortable: true },
    { title: '', field: 'more', render: renderMore, align: 'left' },
  ];

  // Handle successful or failed deletion attempts
  React.useEffect(() => {
    const { inProgress, completed, notification } = deletingPrescription;

    if (!isFirstRender && !inProgress) {
      if (completed) {
        if (deleteDialog.open) {
          deleteDialog.closeParentPopover();
          closeDeleteDialog();
        }

        setToast({
          message: t('You have successfully {{messageAction}} a Tidepool Loop prescription.', { messageAction: 'deleted' }),
          variant: 'success',
        });
      }

      if (completed === false) {
        setToast({
          message: get(notification, 'message'),
          variant: 'danger',
        });
      }
    }
  }, [deletingPrescription]);

  // Render
  return (
    <Box mx={3} mb={5} px={4} py={4} bg='white'>
      <Flex my={3} justifyContent="space-between">
        <Box alignSelf="flex-end">
          <Flex>
            <Headline mr={3}>{t('Prescriptions')}</Headline>
            <Button variant="primary" onClick={handleAddNew}>{t('Add New')}</Button>
          </Flex>
        </Box>
        <Box>
          <Flex>
            <Button
              variant="filter"
              active={filterStateActive}
              {...bindTrigger(popupFilterState)}
              icon={KeyboardArrowDownRoundedIcon}
              iconLabel="Filter by status"
              mr={2}
              fontSize={1}
            >
              {t('Status{{count}}', {
                count: activeStatesCount < keys(prescriptionStates).length ? ` (${activeStatesCount})` : '',
              })}
            </Button>

            <Popover width="15em" {...bindPopover(popupFilterState)}>
              <DialogContent px={2} py={3} dividers>
                {map(prescriptionStateOptions, ({label, value}) => (
                  <Checkbox
                    checked={pendingActiveStates[value]}
                    key={`filter-${value}`}
                    name={`filter-${value}`}
                    label={label}
                    onChange={() => togglePendingActiveState(value)}
                  />
                ))}
              </DialogContent>
              <DialogActions justifyContent="space-between" p={1}>
                <Button
                  fontSize={1}
                  variant="textSecondary"
                  onClick={() => {
                    const active = without(values(pendingActiveStates), false).length < keys(prescriptionStates).length;

                    setPendingActiveStates(transform(pendingActiveStates, function(result, value, key) {
                      result[key] = active;
                    }, {}));
                  }}
                >
                  {(without(values(pendingActiveStates), false).length < keys(prescriptionStates).length) ? t('Select All') : t('Deselect All')}
                </Button>
                <Button fontSize={0} variant="textPrimary" onClick={() => {
                  setActiveStates(pendingActiveStates);
                  setFilterStateActive(includes(values(pendingActiveStates), false));
                  popupFilterState.close();
                }}>
                  {t('Apply')}
                </Button>
              </DialogActions>
            </Popover>

            <TextInput
              themeProps={{
                width: 'auto',
                minWidth: '250px',
              }}
              placeholder={t('Search Entries')}
              icon={searchText ? CloseRoundedIcon : SearchIcon}
              iconLabel="search"
              onClickIcon={searchText ? clearSearchText : null}
              name="search-prescriptions"
              onChange={handleSearchChange}
              value={searchText}
              variant="condensed"
            />
          </Flex>
        </Box>
      </Flex>
      <Table
        fontSize={1}
        label="Prescription List"
        id="prescriptions-table"
        data={data}
        columns={columns}
        rowsPerPage={3}
        searchText={searchText}
        onClickRow={handleRowClick}
        orderBy="createdTime"
        order="desc"
        pagination
      />
      <Dialog
        id={'prescription-delete'}
        aria-labelledby="dialog-title"
        open={deleteDialog.open}
        onClose={closeDeleteDialog}
      >
        <DialogTitle onClose={closeDeleteDialog}>
          <MediumTitle mr={2} id="dialog-title">Delete Prescription for {patientNameFromPrescription(deleteDialog.prescription)}</MediumTitle>
        </DialogTitle>
        <DialogContent>
          <Body1>
            Are you sure you want to delete this prescription?
          </Body1>
        </DialogContent>
        <DialogActions>
          <Button variant="secondary" onClick={closeDeleteDialog}>
            Cancel
          </Button>
          <Button
            variant="danger"
            processing={deletingPrescription.inProgress}
            onClick={() => deletePrescription(deleteDialog.prescription.id)}
          >
            Delete Prescription
          </Button>
        </DialogActions>
      </Dialog>
    </Box>
  );
};

export default withPrescriptions(translate()(Prescriptions));
<commit_msg>[WEB-1073] Only render patient view link in prescriptions if clinician has view permission<commit_after>import React from 'react';
import { translate } from 'react-i18next';
import SearchIcon from '@material-ui/icons/Search';
import CloseRoundedIcon from '@material-ui/icons/CloseRounded';
import DeleteForeverRoundedIcon from '@material-ui/icons/DeleteForeverRounded';
import EditRoundedIcon from '@material-ui/icons/EditRounded';
import KeyboardArrowDownRoundedIcon from '@material-ui/icons/KeyboardArrowDownRounded';
import OpenInNewRoundedIcon from '@material-ui/icons/OpenInNewRounded';
import VisibilityRoundedIcon from '@material-ui/icons/VisibilityRounded';
import { Box, Flex } from 'rebass/styled-components';
import map from 'lodash/map';
import filter from 'lodash/filter';
import get from 'lodash/get';
import includes from 'lodash/includes';
import keyBy from 'lodash/keyBy';
import keys from 'lodash/keys';
import noop from 'lodash/noop';
import reduce from 'lodash/reduce';
import transform from 'lodash/transform';
import values from 'lodash/values';
import without from 'lodash/without';

import {
  bindPopover,
  bindTrigger,
  usePopupState,
} from 'material-ui-popup-state/hooks';

import {
  Dialog,
  DialogTitle,
  DialogContent,
  DialogActions,
} from '../../components/elements/Dialog';

import Table from '../../components/elements/Table';
import Button from '../../components/elements/Button';
import Checkbox from '../../components/elements/Checkbox';
import Popover from '../../components/elements/Popover';
import PopoverMenu from '../../components/elements/PopoverMenu';
import TextInput from '../../components/elements/TextInput';
import { Body1, Headline, MediumTitle } from '../../components/elements/FontStyles';
import withPrescriptions from './withPrescriptions';
import withAssociatedAccounts from './withAssociatedAccounts';
import { dateRegex, prescriptionStateOptions } from './prescriptionFormConstants';
import { useToasts } from '../../providers/ToastProvider';
import { useIsFirstRender } from '../../core/hooks';

const Prescriptions = props => {
  const {
    t,
    deletePrescription,
    deletingPrescription,
    history,
    prescriptions = [],
    membershipPermissionsInOtherCareTeams = {},
  } = props;

  const isFirstRender = useIsFirstRender();
  const { set: setToast } = useToasts();

  const prescriptionStates = keyBy(prescriptionStateOptions, 'value');

  const popupFilterState = usePopupState({
    variant: 'popover',
    popupId: 'filterState',
  });

  const initialDeleteDialogState = {
    closeParentPopover: noop,
    open: false,
    prescription: {},
  };

  const [deleteDialog, setDeleteDialog] = React.useState(initialDeleteDialogState);

  function closeDeleteDialog() {
    setDeleteDialog({
      ...deleteDialog,
      open: false,
    });

    // We fully reset the dialog after it's had time to close, so that we don't see the patient name
    // in the delete dialog disappear before it closes.
    setTimeout(() => setDeleteDialog(initialDeleteDialogState), 100);
  }

  const [searchText, setSearchText] = React.useState('');
  const [filterStateActive, setFilterStateActive] = React.useState(false);

  const [activeStates, setActiveStates] = React.useState(reduce(prescriptionStateOptions, (result, { value }) => {
    result[value] = true;
    return result;
  }, {}));

  const activeStatesCount = without(values(activeStates), false).length;

  const [pendingActiveStates, setPendingActiveStates] = React.useState(activeStates);
  const togglePendingActiveState = (value) => setPendingActiveStates({ ...pendingActiveStates, [value]: !pendingActiveStates[value] })

  const data = filter(
    map(prescriptions, prescription => ({
      birthday: get(prescription, 'latestRevision.attributes.birthday').replace(dateRegex, '$2/$3/$1'),
      createdTime: get(prescription, 'latestRevision.attributes.createdTime'),
      firstName: get(prescription, 'latestRevision.attributes.firstName'),
      id: get(prescription, 'id'),
      lastName: get(prescription, 'latestRevision.attributes.lastName'),
      mrn: get(prescription, 'latestRevision.attributes.mrn'),
      patientUserId: get(prescription, 'patientUserId'),
      state: get(prescription, 'state'),
    })),
    prescription => activeStates[prescription.state]
  );

  function handleSearchChange(event) {
    setSearchText(event.target.value);
  }

  function clearSearchText() {
    setSearchText('');
  }

  const handleAddNew = () => props.history.push('/prescriptions/new');

  const handleOpenPrescription = prescription => popupState => {
    props.history.push(`/prescriptions/${prescription.id}`);
  };

  const handleDeletePrescription = prescription => popupState => {
    setDeleteDialog({
      closeParentPopover: popupState.close,
      open: true,
      prescription,
    });
  }

  function handleRowClick(prescription) {
    return handleOpenPrescription(prescription)();
  }

  const actionMenuItems = prescription => {
    const isEditable = includes(['draft', 'pending'], prescription.state);

    const items = [
      {
        icon: isEditable ? EditRoundedIcon : VisibilityRoundedIcon,
        iconLabel: isEditable ? t('Update') : t('View'),
        iconPosition: 'left',
        id: isEditable ? 'update' : 'view',
        onClick: handleOpenPrescription(prescription),
        text: isEditable ? t('Update prescription') : t('View Prescription'),
        variant: 'actionListItem',
      },
    ];

    if (isEditable) items.push({
      icon: DeleteForeverRoundedIcon,
      iconLabel: 'Delete',
      iconPosition: 'left',
      id: 'delete',
      onClick: handleDeletePrescription(prescription),
      text: 'Delete prescription',
      variant: 'actionListItemDanger',
      disabled: !isEditable,
    });

    return items;
  };

  const renderMore = (prescription) => (
    <Box onClick={e => {
      // Prevent clicks from propogating up to the table row click handlers
      e.stopPropagation();
    }}>
      <PopoverMenu
        id="more-prescription-actions"
        label="More actions"
        items={actionMenuItems(prescription)}
      />
    </Box>
  );

  const openPatientData = (patientUserId) => {
    const href = history.createHref({
      pathname: `/patients/${patientUserId}/data`,
    });

    window.open(href, '_blank');
  }

  const patientNameFromPrescription = ({ firstName = '', lastName = '' }) => [firstName, lastName].join(' ');

  const renderName = prescription => (
    <>
     {prescription.patientUserId && get(membershipPermissionsInOtherCareTeams, [prescription.patientUserId, 'view']) ? <Button
        p={0}
        m={0}
        color="text.link"
        fontSize={1}
        icon={OpenInNewRoundedIcon}
        iconLabel="Open patient data in new tab"
        variant="textPrimary"
        onClick={(e) => {
          // Prevent clicks from propogating up to the table row click handlers
          e.stopPropagation();
          openPatientData(prescription.patientUserId);
        }}
      >
        {patientNameFromPrescription(prescription)}
      </Button> : patientNameFromPrescription(prescription)
     }
    </>
  );
  const renderState = ({ state }) => get(prescriptionStates, [state, 'label'], '');

  const columns = [
    { title: t('Name'), field: 'patient', align: 'left', sortable: true, sortBy: 'firstName', render: renderName, searchable: true, searchBy: ['firstName', 'lastName'] },
    { title: t('MRN'), field: 'mrn', align: 'left', sortable: true, searchable: true },
    { title: t('Date of birth'), field: 'birthday', align: 'left', sortable: true, searchable: true },
    { title: t('Status'), field: 'state', render: renderState, align: 'left', sortable: true },
    { title: '', field: 'more', render: renderMore, align: 'left' },
  ];

  // Handle successful or failed deletion attempts
  React.useEffect(() => {
    const { inProgress, completed, notification } = deletingPrescription;

    if (!isFirstRender && !inProgress) {
      if (completed) {
        if (deleteDialog.open) {
          deleteDialog.closeParentPopover();
          closeDeleteDialog();
        }

        setToast({
          message: t('You have successfully {{messageAction}} a Tidepool Loop prescription.', { messageAction: 'deleted' }),
          variant: 'success',
        });
      }

      if (completed === false) {
        setToast({
          message: get(notification, 'message'),
          variant: 'danger',
        });
      }
    }
  }, [deletingPrescription]);

  // Render
  return (
    <Box mx={3} mb={5} px={4} py={4} bg='white'>
      <Flex my={3} justifyContent="space-between">
        <Box alignSelf="flex-end">
          <Flex>
            <Headline mr={3}>{t('Prescriptions')}</Headline>
            <Button variant="primary" onClick={handleAddNew}>{t('Add New')}</Button>
          </Flex>
        </Box>
        <Box>
          <Flex>
            <Button
              variant="filter"
              active={filterStateActive}
              {...bindTrigger(popupFilterState)}
              icon={KeyboardArrowDownRoundedIcon}
              iconLabel="Filter by status"
              mr={2}
              fontSize={1}
            >
              {t('Status{{count}}', {
                count: activeStatesCount < keys(prescriptionStates).length ? ` (${activeStatesCount})` : '',
              })}
            </Button>

            <Popover width="15em" {...bindPopover(popupFilterState)}>
              <DialogContent px={2} py={3} dividers>
                {map(prescriptionStateOptions, ({label, value}) => (
                  <Checkbox
                    checked={pendingActiveStates[value]}
                    key={`filter-${value}`}
                    name={`filter-${value}`}
                    label={label}
                    onChange={() => togglePendingActiveState(value)}
                  />
                ))}
              </DialogContent>
              <DialogActions justifyContent="space-between" p={1}>
                <Button
                  fontSize={1}
                  variant="textSecondary"
                  onClick={() => {
                    const active = without(values(pendingActiveStates), false).length < keys(prescriptionStates).length;

                    setPendingActiveStates(transform(pendingActiveStates, function(result, value, key) {
                      result[key] = active;
                    }, {}));
                  }}
                >
                  {(without(values(pendingActiveStates), false).length < keys(prescriptionStates).length) ? t('Select All') : t('Deselect All')}
                </Button>
                <Button fontSize={0} variant="textPrimary" onClick={() => {
                  setActiveStates(pendingActiveStates);
                  setFilterStateActive(includes(values(pendingActiveStates), false));
                  popupFilterState.close();
                }}>
                  {t('Apply')}
                </Button>
              </DialogActions>
            </Popover>

            <TextInput
              themeProps={{
                width: 'auto',
                minWidth: '250px',
              }}
              placeholder={t('Search Entries')}
              icon={searchText ? CloseRoundedIcon : SearchIcon}
              iconLabel="search"
              onClickIcon={searchText ? clearSearchText : null}
              name="search-prescriptions"
              onChange={handleSearchChange}
              value={searchText}
              variant="condensed"
            />
          </Flex>
        </Box>
      </Flex>
      <Table
        fontSize={1}
        label="Prescription List"
        id="prescriptions-table"
        data={data}
        columns={columns}
        rowsPerPage={3}
        searchText={searchText}
        onClickRow={handleRowClick}
        orderBy="createdTime"
        order="desc"
        pagination
      />
      <Dialog
        id={'prescription-delete'}
        aria-labelledby="dialog-title"
        open={deleteDialog.open}
        onClose={closeDeleteDialog}
      >
        <DialogTitle onClose={closeDeleteDialog}>
          <MediumTitle mr={2} id="dialog-title">Delete Prescription for {patientNameFromPrescription(deleteDialog.prescription)}</MediumTitle>
        </DialogTitle>
        <DialogContent>
          <Body1>
            Are you sure you want to delete this prescription?
          </Body1>
        </DialogContent>
        <DialogActions>
          <Button variant="secondary" onClick={closeDeleteDialog}>
            Cancel
          </Button>
          <Button
            variant="danger"
            processing={deletingPrescription.inProgress}
            onClick={() => deletePrescription(deleteDialog.prescription.id)}
          >
            Delete Prescription
          </Button>
        </DialogActions>
      </Dialog>
    </Box>
  );
};

export default withPrescriptions(withAssociatedAccounts(translate()(Prescriptions)));
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { connect } from 'react-redux';
import DashboardComponent from '../components/DashboardComponent';

/**
 * Connects and passes down the Redux dispatch function to ExportComponent
 */
export default connect()(DashboardComponent);
<commit_msg>fixed a comment I accidentally changed in dashboardContainer<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { connect } from 'react-redux';
import DashboardComponent from '../components/DashboardComponent';

/**
 * Connects and passes down the Redux dispatch function to DashboardComponent
 */
export default connect()(DashboardComponent);
<|endoftext|>


<commit_before>define(["backbone", "underscore", "places/models/POIModel", "moxie.conf", 'moxie.position'], function(Backbone, _, POI, conf, userPosition) {

    var POIs = Backbone.Collection.extend({

        model: POI,

        initialize: function(query) {
            this.query = query || {};
            this.facets = new Backbone.Collection();
        },

        followUser: function() {
            userPosition.follow(_.bind(this.handle_geolocation_query, this));
        },

        unfollowUser: function() {
            userPosition.unfollow(_.bind(this.handle_geolocation_query, this));
        },

        geoFetch: function() { return this.fetch(arguments); },
        handle_geolocation_query: function(position) {
            var geoFetch = function(options) {
                options = options || {};
                options.headers = options.headers || {};
                options.headers['Geo-Position'] = [position.coords.latitude, position.coords.longitude].join(';');
                this.fetch(options);
            };
            this.geoFetch = _.bind(geoFetch, this);
        },

        fetchNextPage: function() {
            if (this.next_results) {
                var urlFunc = this.url;
                this.url = conf.endpoint + this.next_results.href;
                this.geoFetch({update: true, remove: false});
                this.url = urlFunc;
            } else {
                return false;
            }
        },

        parse: function(data) {
            // Called when we want to empty the existing collection
            // For example when a search is issued and we clear the existing results.
            this.next_results = data._links['hl:next'];
            this.facets.reset(data._links['hl:types']);
            return data._embedded;
        },

        url: function() {
            var qstring = $.param(this.query);
            var searchPath = conf.pathFor('places_search');
            if (qstring) {
                searchPath += ('?' + qstring);
            }
            return conf.endpoint + searchPath.replace(/\+/g, "%20");
        }

    });

    // Returns the Model class
    return POIs;

});
<commit_msg>Facets is no longer a Backbone.Collection<commit_after>define(["backbone", "underscore", "places/models/POIModel", "moxie.conf", 'moxie.position'], function(Backbone, _, POI, conf, userPosition) {

    var POIs = Backbone.Collection.extend({

        model: POI,

        initialize: function(query) {
            this.query = query || {};
        },

        followUser: function() {
            userPosition.follow(_.bind(this.handle_geolocation_query, this));
        },

        unfollowUser: function() {
            userPosition.unfollow(_.bind(this.handle_geolocation_query, this));
        },

        geoFetch: function() { return this.fetch(arguments); },
        handle_geolocation_query: function(position) {
            var geoFetch = function(options) {
                options = options || {};
                options.headers = options.headers || {};
                options.headers['Geo-Position'] = [position.coords.latitude, position.coords.longitude].join(';');
                this.fetch(options);
            };
            this.geoFetch = _.bind(geoFetch, this);
        },

        fetchNextPage: function() {
            if (this.next_results) {
                var urlFunc = this.url;
                this.url = conf.endpoint + this.next_results.href;
                this.geoFetch({update: true, remove: false});
                this.url = urlFunc;
            } else {
                return false;
            }
        },

        parse: function(data) {
            // Called when we want to empty the existing collection
            // For example when a search is issued and we clear the existing results.
            this.next_results = data._links['hl:next'];
            this.facets = data._links['hl:types'];
            return data._embedded;
        },

        url: function() {
            var qstring = $.param(this.query);
            var searchPath = conf.pathFor('places_search');
            if (qstring) {
                searchPath += ('?' + qstring);
            }
            return conf.endpoint + searchPath.replace(/\+/g, "%20");
        }

    });

    // Returns the Model class
    return POIs;

});
<|endoftext|>


<commit_before>const EventEmitter = require('events').EventEmitter;
const NaCl = require('tweetnacl');
const VoiceBroadcast = require('../VoiceBroadcast');

const nonce = new Buffer(24);
nonce.fill(0);

/**
 * The class that sends voice packet data to the voice connection.
 * ```js
 * // obtained using:
 * voiceChannel.join().then(connection => {
 *   // you can play a file or a stream here:
 *   const dispatcher = connection.playFile('./file.mp3');
 * });
 * ```
 * @extends {EventEmitter}
 */
class StreamDispatcher extends EventEmitter {
  constructor(player, stream, streamOptions) {
    super();
    this.player = player;
    this.stream = stream;
    if (!(this.stream instanceof VoiceBroadcast)) this.startStreaming();
    this.streamOptions = streamOptions;
    this.streamOptions.volume = this.streamOptions.volume || 0;

    const data = this.streamingData;
    data.length = 20;
    data.missed = 0;

    /**
     * Whether playing is paused
     * @type {boolean}
     */
    this.paused = false;

    this.destroyed = false;

    this.setVolume(streamOptions.volume || 1);
  }

  get passes() {
    return this.streamOptions.passes || 1;
  }

  get streamingData() {
    return this.player.streamingData;
  }

  /**
   * How long the stream dispatcher has been "speaking" for
   * @type {number}
   * @readonly
   */
  get time() {
    return this.streamingData.count * (this.streamingData.length || 0);
  }

  /**
   * The total time, taking into account pauses and skips, that the dispatcher has been streaming for
   * @type {number}
   * @readonly
   */
  get totalStreamTime() {
    return this.time + this.streamingData.pausedTime;
  }

  /**
   * The volume of the stream, relative to the stream's input volume
   * @type {number}
   * @readonly
   */
  get volume() {
    return this.streamOptions.volume;
  }

  /**
   * Sets the volume relative to the input stream - i.e. 1 is normal, 0.5 is half, 2 is double.
   * @param {number} volume The volume that you want to set
   */
  setVolume(volume) {
    this.emit('volumeChange', this.streamOptions.volume, volume);
    this.streamOptions.volume = volume;
  }

  /**
   * Set the volume in decibels
   * @param {number} db The decibels
   */
  setVolumeDecibels(db) {
    this.setVolume(Math.pow(10, db / 20));
  }

  /**
   * Set the volume so that a perceived value of 0.5 is half the perceived volume etc.
   * @param {number} value The value for the volume
   */
  setVolumeLogarithmic(value) {
    this.setVolume(Math.pow(value, 1.660964));
  }

  /**
   * Stops sending voice packets to the voice connection (stream may still progress however)
   */
  pause() { this.setPaused(true); }

  /**
   * Resumes sending voice packets to the voice connection (may be further on in the stream than when paused)
   */
  resume() { this.setPaused(false); }

  /**
   * Stops the current stream permanently and emits an `end` event.
   * @param {string} [reason='user'] An optional reason for stopping the dispatcher.
   */
  end(reason = 'user') {
    this.destroy('end', reason);
  }

  setSpeaking(value) {
    if (this.speaking === value) return;
    this.speaking = value;
    /**
     * Emitted when the dispatcher starts/stops speaking
     * @event StreamDispatcher#speaking
     * @param {boolean} value Whether or not the dispatcher is speaking
     */
    this.emit('speaking', value);
  }

  sendBuffer(buffer, sequence, timestamp, opusPacket) {
    opusPacket = opusPacket || this.player.opusEncoder.encode(buffer);
    let repeats = this.passes;
    const packet = this.createPacket(sequence, timestamp, opusPacket);
    while (repeats--) {
      this.player.voiceConnection.sockets.udp.send(packet)
        .catch(e => this.emit('debug', `Failed to send a packet ${e}`));
    }
  }

  createPacket(sequence, timestamp, buffer) {
    const packetBuffer = new Buffer(buffer.length + 28);
    packetBuffer.fill(0);
    packetBuffer[0] = 0x80;
    packetBuffer[1] = 0x78;

    packetBuffer.writeUIntBE(sequence, 2, 2);
    packetBuffer.writeUIntBE(timestamp, 4, 4);
    packetBuffer.writeUIntBE(this.player.voiceConnection.authentication.ssrc, 8, 4);

    packetBuffer.copy(nonce, 0, 0, 12);
    buffer = NaCl.secretbox(buffer, nonce, this.player.voiceConnection.authentication.secretKey.key);

    for (let i = 0; i < buffer.length; i++) packetBuffer[i + 12] = buffer[i];

    return packetBuffer;
  }

  applyVolume(buffer) {
    if (this.volume === 1) return buffer;

    const out = new Buffer(buffer.length);
    for (let i = 0; i < buffer.length; i += 2) {
      if (i >= buffer.length - 1) break;
      const uint = Math.min(32767, Math.max(-32767, Math.floor(this.volume * buffer.readInt16LE(i))));
      out.writeInt16LE(uint, i);
    }

    return out;
  }

  process(buffer, controlled, packet) {
    try {
      if (this.destroyed) {
        this.setSpeaking(false);
        return;
      }

      const data = this.streamingData;

      if (data.missed >= 5 && !controlled) {
        this.destroy('end', 'Stream is not generating quickly enough.');
        return;
      }

      if (this.paused) {
        this.setSpeaking(false);
        // data.timestamp = data.timestamp + 4294967295 ? data.timestamp + 960 : 0;
        data.pausedTime += data.length * 10;
        // if buffer is provided we are assuming a master process is controlling the dispatcher
        if (!buffer) this.player.voiceConnection.voiceManager.client.setTimeout(() => this.process(), data.length * 10);
        return;
      }

      if (!buffer && controlled) {
        data.missed++;
        data.pausedTime += data.length * 10;
        return;
      }

      this.setSpeaking(true);

      if (!data.startTime) {
        /**
         * Emitted once the dispatcher starts streaming
         * @event StreamDispatcher#start
         */
        this.emit('start');
        data.startTime = Date.now();
      }

      if (packet) {
        data.count++;
        data.sequence = data.sequence < 65535 ? data.sequence + 1 : 0;
        data.timestamp = data.timestamp + 4294967295 ? data.timestamp + 960 : 0;
        this.sendBuffer(null, data.sequence, data.timestamp, packet);
        return;
      }

      const bufferLength = 1920 * data.channels;
      if (!controlled) {
        buffer = this.stream.read(bufferLength);
        if (!buffer) {
          data.missed++;
          data.pausedTime += data.length * 10;
          this.player.voiceConnection.voiceManager.client.setTimeout(() => this.process(), data.length * 10);
          return;
        }
      }

      data.missed = 0;

      if (buffer.length !== bufferLength) {
        const newBuffer = new Buffer(bufferLength).fill(0);
        buffer.copy(newBuffer);
        buffer = newBuffer;
      }

      buffer = this.applyVolume(buffer);

      data.count++;
      data.sequence = data.sequence < 65535 ? data.sequence + 1 : 0;
      data.timestamp = data.timestamp + 4294967295 ? data.timestamp + 960 : 0;
      this.sendBuffer(buffer, data.sequence, data.timestamp);

      if (controlled) return;
      const nextTime = data.length + (data.startTime + data.pausedTime + (data.count * data.length) - Date.now());
      this.player.voiceConnection.voiceManager.client.setTimeout(() => this.process(), nextTime);
    } catch (e) {
      this.destroy('error', e);
    }
  }

  destroy(type, reason) {
    if (this.destroyed) return;
    this.destroyed = true;
    this.setSpeaking(false);
    this.emit(type, reason);
    if (type !== 'end') this.emit('end', `destroyed due to ${type} - ${reason}`);
  }

  startStreaming() {
    if (!this.stream) {
      this.emit('error', 'No stream');
      return;
    }

    this.stream.on('end', err => this.destroy('end', err || 'stream'));
    this.stream.on('error', err => this.destroy('error', err));

    const data = this.streamingData;
    data.length = 20;
    data.missed = 0;

    this.stream.once('readable', () => {
      data.startTime = null;
      data.count = 0;
      this.process();
    });
  }

  setPaused(paused) { this.setSpeaking(!(this.paused = paused)); }
}

module.exports = StreamDispatcher;
<commit_msg>StreamDispatcher documentation<commit_after>const EventEmitter = require('events').EventEmitter;
const NaCl = require('tweetnacl');
const VoiceBroadcast = require('../VoiceBroadcast');

const nonce = new Buffer(24);
nonce.fill(0);

/**
 * The class that sends voice packet data to the voice connection.
 * ```js
 * // obtained using:
 * voiceChannel.join().then(connection => {
 *   // you can play a file or a stream here:
 *   const dispatcher = connection.playFile('./file.mp3');
 * });
 * ```
 * @extends {EventEmitter}
 */
class StreamDispatcher extends EventEmitter {
  constructor(player, stream, streamOptions) {
    super();
    /**
     * The Audio Player that controls this dispatcher
     * @type {AudioPlayer}
     */
    this.player = player;
    /**
     * The stream that the dispatcher plays
     * @type {ReadableStream|VoiceBroadcast}
     */
    this.stream = stream;
    if (!(this.stream instanceof VoiceBroadcast)) this.startStreaming();
    this.streamOptions = streamOptions;
    this.streamOptions.volume = this.streamOptions.volume || 0;

    const data = this.streamingData;
    data.length = 20;
    data.missed = 0;

    /**
     * Whether playing is paused
     * @type {boolean}
     */
    this.paused = false;
    /**
     * Whether this dispatcher has been destroyed
     * @type {boolean}
     */
    this.destroyed = false;

    this.setVolume(streamOptions.volume || 1);
  }

  /**
   * How many passes the dispatcher should take when sending packets to reduce packet loss. Values over 5
   * aren't recommended, as it means you are using 5x more bandwidth. You _can_ edit this at runtime.
   * @type {number}
   */
  get passes() {
    return this.streamOptions.passes || 1;
  }

  set passes(n) {
    this.streamOptions.passes = n;
  }

  get streamingData() {
    return this.player.streamingData;
  }

  /**
   * How long the stream dispatcher has been "speaking" for
   * @type {number}
   * @readonly
   */
  get time() {
    return this.streamingData.count * (this.streamingData.length || 0);
  }

  /**
   * The total time, taking into account pauses and skips, that the dispatcher has been streaming for
   * @type {number}
   * @readonly
   */
  get totalStreamTime() {
    return this.time + this.streamingData.pausedTime;
  }

  /**
   * The volume of the stream, relative to the stream's input volume
   * @type {number}
   * @readonly
   */
  get volume() {
    return this.streamOptions.volume;
  }

  /**
   * Sets the volume relative to the input stream - i.e. 1 is normal, 0.5 is half, 2 is double.
   * @param {number} volume The volume that you want to set
   */
  setVolume(volume) {
    /**
     * Emitted when the volume of this dispatcher changes
     * @param {number} oldVolume the old volume
     * @param {number} newVolume the new volume
     * @event StreamDispatcher#volumeChange
     */
    this.emit('volumeChange', this.streamOptions.volume, volume);
    this.streamOptions.volume = volume;
  }

  /**
   * Set the volume in decibels
   * @param {number} db The decibels
   */
  setVolumeDecibels(db) {
    this.setVolume(Math.pow(10, db / 20));
  }

  /**
   * Set the volume so that a perceived value of 0.5 is half the perceived volume etc.
   * @param {number} value The value for the volume
   */
  setVolumeLogarithmic(value) {
    this.setVolume(Math.pow(value, 1.660964));
  }

  /**
   * Stops sending voice packets to the voice connection (stream may still progress however)
   */
  pause() { this.setPaused(true); }

  /**
   * Resumes sending voice packets to the voice connection (may be further on in the stream than when paused)
   */
  resume() { this.setPaused(false); }

  /**
   * Stops the current stream permanently and emits an `end` event.
   * @param {string} [reason='user'] An optional reason for stopping the dispatcher.
   */
  end(reason = 'user') {
    this.destroy('end', reason);
  }

  setSpeaking(value) {
    if (this.speaking === value) return;
    this.speaking = value;
    /**
     * Emitted when the dispatcher starts/stops speaking
     * @event StreamDispatcher#speaking
     * @param {boolean} value Whether or not the dispatcher is speaking
     */
    this.emit('speaking', value);
  }

  sendBuffer(buffer, sequence, timestamp, opusPacket) {
    opusPacket = opusPacket || this.player.opusEncoder.encode(buffer);
    let repeats = this.passes;
    const packet = this.createPacket(sequence, timestamp, opusPacket);
    /**
     * Emitted whenever the dispatcher has debug information
     * @event StreamDispatcher#debug
     * @param {string} info the debug info
     */
    while (repeats--) {
      this.player.voiceConnection.sockets.udp.send(packet)
        .catch(e => this.emit('debug', `Failed to send a packet ${e}`));
    }
  }

  createPacket(sequence, timestamp, buffer) {
    const packetBuffer = new Buffer(buffer.length + 28);
    packetBuffer.fill(0);
    packetBuffer[0] = 0x80;
    packetBuffer[1] = 0x78;

    packetBuffer.writeUIntBE(sequence, 2, 2);
    packetBuffer.writeUIntBE(timestamp, 4, 4);
    packetBuffer.writeUIntBE(this.player.voiceConnection.authentication.ssrc, 8, 4);

    packetBuffer.copy(nonce, 0, 0, 12);
    buffer = NaCl.secretbox(buffer, nonce, this.player.voiceConnection.authentication.secretKey.key);

    for (let i = 0; i < buffer.length; i++) packetBuffer[i + 12] = buffer[i];

    return packetBuffer;
  }

  applyVolume(buffer) {
    if (this.volume === 1) return buffer;

    const out = new Buffer(buffer.length);
    for (let i = 0; i < buffer.length; i += 2) {
      if (i >= buffer.length - 1) break;
      const uint = Math.min(32767, Math.max(-32767, Math.floor(this.volume * buffer.readInt16LE(i))));
      out.writeInt16LE(uint, i);
    }

    return out;
  }

  process(buffer, controlled, packet) {
    try {
      if (this.destroyed) {
        this.setSpeaking(false);
        return;
      }

      const data = this.streamingData;

      if (data.missed >= 5 && !controlled) {
        this.destroy('end', 'Stream is not generating quickly enough.');
        return;
      }

      if (this.paused) {
        this.setSpeaking(false);
        // data.timestamp = data.timestamp + 4294967295 ? data.timestamp + 960 : 0;
        data.pausedTime += data.length * 10;
        // if buffer is provided we are assuming a master process is controlling the dispatcher
        if (!buffer) this.player.voiceConnection.voiceManager.client.setTimeout(() => this.process(), data.length * 10);
        return;
      }

      if (!buffer && controlled) {
        data.missed++;
        data.pausedTime += data.length * 10;
        return;
      }

      this.setSpeaking(true);

      if (!data.startTime) {
        /**
         * Emitted once the dispatcher starts streaming
         * @event StreamDispatcher#start
         */
        this.emit('start');
        data.startTime = Date.now();
      }

      if (packet) {
        data.count++;
        data.sequence = data.sequence < 65535 ? data.sequence + 1 : 0;
        data.timestamp = data.timestamp + 4294967295 ? data.timestamp + 960 : 0;
        this.sendBuffer(null, data.sequence, data.timestamp, packet);
        return;
      }

      const bufferLength = 1920 * data.channels;
      if (!controlled) {
        buffer = this.stream.read(bufferLength);
        if (!buffer) {
          data.missed++;
          data.pausedTime += data.length * 10;
          this.player.voiceConnection.voiceManager.client.setTimeout(() => this.process(), data.length * 10);
          return;
        }
      }

      data.missed = 0;

      if (buffer.length !== bufferLength) {
        const newBuffer = new Buffer(bufferLength).fill(0);
        buffer.copy(newBuffer);
        buffer = newBuffer;
      }

      buffer = this.applyVolume(buffer);

      data.count++;
      data.sequence = data.sequence < 65535 ? data.sequence + 1 : 0;
      data.timestamp = data.timestamp + 4294967295 ? data.timestamp + 960 : 0;
      this.sendBuffer(buffer, data.sequence, data.timestamp);

      if (controlled) return;
      const nextTime = data.length + (data.startTime + data.pausedTime + (data.count * data.length) - Date.now());
      this.player.voiceConnection.voiceManager.client.setTimeout(() => this.process(), nextTime);
    } catch (e) {
      this.destroy('error', e);
    }
  }

  destroy(type, reason) {
    if (this.destroyed) return;
    this.destroyed = true;
    this.setSpeaking(false);
    this.emit(type, reason);
    /**
     * Emitted once the dispatcher ends
     * @param {string} [reason] the reason the dispatcher ended
     * @event StreamDispatcher#end
     */
    if (type !== 'end') this.emit('end', `destroyed due to ${type} - ${reason}`);
  }

  startStreaming() {
    if (!this.stream) {
      /**
       * Emitted if the dispatcher encounters an error
       * @param {string} error the error message
       */
      this.emit('error', 'No stream');
      return;
    }

    this.stream.on('end', err => this.destroy('end', err || 'stream'));
    this.stream.on('error', err => this.destroy('error', err));

    const data = this.streamingData;
    data.length = 20;
    data.missed = 0;

    this.stream.once('readable', () => {
      data.startTime = null;
      data.count = 0;
      this.process();
    });
  }

  setPaused(paused) { this.setSpeaking(!(this.paused = paused)); }
}

module.exports = StreamDispatcher;
<|endoftext|>