<commit_before>beforeEach(() => {
  // ensure freshly mocked clients in each test
  jest.resetModules()

  // mock globals set by RN at runtime
  global.__DEV__ = true
  global.ErrorUtils = {
    setGlobalHandler: jest.fn(),
    getGlobalHandler: jest.fn()
  }
})

test('constructor(): throws if called with bad input', () => {
  jest.mock('react-native', () => ({
    NativeModules: { BugsnagReactNative: null }
  }), { virtual: true })

  const { Client } = require('../Bugsnag')

  // non-string and non-undefined value as config/api key
  expect(() => new Client(1)).toThrowError(/API key/)

  // missing BugsnagReactNative bindings
  expect(() => new Client('API_KEY')).toThrowError(/BugsnagReactNative/)
})

test('constructor(): works if everything is ok', () => {
  const mockStart = jest.fn()
  jest.mock('react-native', () => ({
    NativeModules: { BugsnagReactNative: { startWithOptions: mockStart } }
  }), { virtual: true })

  const { Client } = require('../Bugsnag')
  const c = new Client('API_KEY')
  expect(mockStart).toHaveBeenCalledWith(expect.objectContaining({ apiKey: 'API_KEY' }))
  expect(global.ErrorUtils.getGlobalHandler).toHaveBeenCalled()
  expect(global.ErrorUtils.setGlobalHandler).toHaveBeenCalled()
})

test('handleUncaughtErrors(): error handler calls notify(…) correctly', () => {
  jest.mock('react-native', () => ({
    NativeModules: { BugsnagReactNative: { startWithOptions: jest.fn() } }
  }), { virtual: true })

  const { Client } = require('../Bugsnag')
  const c = new Client('API_KEY')

  // get the function that the client passed to the global error handler
  const handler = global.ErrorUtils.setGlobalHandler.mock.calls[0][0]

  c.notify = jest.fn()
  handler(new Error('boom!'), false)

  expect(c.notify).toHaveBeenCalledWith(expect.any(Error), null, false, expect.any(Function), {
    originalSeverity: "error",
    severityReason: "unhandledException",
    unhandled: true
  })
})

test('handlePromiseRejections(): error handler calls notify(…) correctly', () => {
  jest.mock('react-native', () => ({
    NativeModules: { BugsnagReactNative: { startWithOptions: jest.fn() } },
  }), { virtual: true })

  const Promise = require('promise/setimmediate')
  const { Client, Configuration } = require('../Bugsnag')
  const config = new Configuration('API_KEY')
  config.handlePromiseRejections = true
  const c = new Client(config)

  c.notify = jest.fn()
  Promise.reject(new Error('boom!'))

  // promise rejection handler has a 2 second delay before running, so check after minimum viable timeout
  // https://github.com/then/promise/blob/7f33b7bccf07a2da37569095c53d91bea7dbd226/src/rejection-tracking.js#L54
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      try {

        expect(c.notify).toHaveBeenCalledWith(expect.any(Error), null, false, null, {
          originalSeverity: "error",
          severityReason: "unhandledPromiseRejection",
          unhandled: true
        })
      } catch (e) {
        reject(e)
      }
      resolve()
    }, 2001)
  })
})

test('shouldNotify(): returns true/false in the correct situations', () => {
  jest.mock('react-native', () => ({ NativeModules: {} }), { virtual: true })
  const { Configuration } = require('../Bugsnag')
  const c = new Configuration('API_KEY')
  expect(c.shouldNotify()).toEqual(true)
  c.notifyReleaseStages = [ 'production', 'staging' ]
  expect(c.shouldNotify()).toEqual(true)
  c.releaseStage = [ 'testing' ]
  expect(c.shouldNotify()).toEqual(false)
})

test('notify(): calls the correct native notify/notifyBlocking method', () => {
  const mockNotify = jest.fn()
  const mockNotifyBlocking = jest.fn()
  jest.mock('react-native', () => ({
    NativeModules: {
      BugsnagReactNative: {
        startWithOptions: jest.fn(),
        notify: mockNotify,
        notifyBlocking: mockNotifyBlocking
      }
    }
  }), { virtual: true })

  const { Client } = require('../Bugsnag')
  const c = new Client('API_KEY')

  // called with non-error
  c.notify(1)
  c.notify(1, null, true)
  expect(mockNotify).toHaveBeenCalledTimes(0)
  expect(mockNotifyBlocking).toHaveBeenCalledTimes(0)

  // non-blocking
  c.notify(new Error('boom!'))
  expect(mockNotify).toHaveBeenCalledWith(
    expect.objectContaining({
      apiKey: 'API_KEY',
      errorClass: 'Error',
      errorMessage: 'boom!',
      severity: 'warning',
      unhandled: false,
      severityReason: 'handledException'
    })
  )

  // blocking
  c.notify(new Error('nb boom!'), null, true)
  expect(mockNotifyBlocking).toHaveBeenCalledWith(
    expect.objectContaining({
      apiKey: 'API_KEY',
      errorClass: 'Error',
      errorMessage: 'nb boom!',
      severity: 'warning',
      unhandled: false,
      severityReason: 'handledException'
    }),
    true,
    undefined
  )
})

test('notify(): supplying unhandled state as param changes payload', () => {
  const mockNotify = jest.fn()
  const mockNotifyBlocking = jest.fn()
  jest.mock('react-native', () => ({
    NativeModules: {
      BugsnagReactNative: {
        startWithOptions: jest.fn(),
        notify: mockNotify,
        notifyBlocking: mockNotifyBlocking
      }
    }
  }), { virtual: true })

  const { Client } = require('../Bugsnag')
  const c = new Client('API_KEY')

  // send unhandled state
  c.notify(new Error('nb boom!'), null, true, null, {
    originalSeverity: 'warning',
    unhandled: false,
    severityReason: "handledException",
  })
  expect(mockNotifyBlocking).toHaveBeenCalledWith(
    expect.objectContaining({
      severity: 'warning',
      unhandled: false,
      severityReason: "handledException"
    }),
    true,
    null
  )

  // mutate severity
  c.notify(new Error('Mutate Severity'), report => {report.severity = 'info'}, true, null, {
    originalSeverity: 'warning',
    unhandled: false,
    severityReason: "handledException",
  })
  expect(mockNotifyBlocking).toHaveBeenCalledWith(
    expect.objectContaining({
      severity: 'info',
      severityReason: "userCallbackSetSeverity"
    }),
    true,
    null
  )
})

test('notify(): doesn’t call native notify/notifyBlocking when shouldNotify() returns false', () => {
  const mockNotify = jest.fn()
  const mockNotifyBlocking = jest.fn()
  jest.mock('react-native', () => ({
    NativeModules: {
      BugsnagReactNative: {
        startWithOptions: jest.fn(),
        notify: mockNotify,
        notifyBlocking: mockNotifyBlocking
      }
    }
  }), { virtual: true })

  const { Client, Configuration } = require('../Bugsnag')
  const config = new Configuration('API_KEY')
  const c = new Client(config)

  // replace shouldNotify with a function that always returns false
  config.shouldNotify = () => false

  // non-blocking
  c.notify(new Error('boom!'))
  expect(mockNotify).toHaveBeenCalledTimes(0)

  // blocking
  c.notify(new Error('nb boom!'), null, true)
  expect(mockNotifyBlocking).toHaveBeenCalledTimes(0)
})

test('leaveBreadcrumb(): calls the native leaveBreadcrumb method correctly', () => {
  const mockLeaveBreadcrumb = jest.fn()
  jest.mock('react-native', () => ({
    NativeModules: { BugsnagReactNative: { startWithOptions: jest.fn(), leaveBreadcrumb: mockLeaveBreadcrumb } }
  }), { virtual: true })

  const { Client, Configuration } = require('../Bugsnag')
  const config = new Configuration('API_KEY')
  const c = new Client(config)

  // reject name=String, metadata=undefined
  c.leaveBreadcrumb(1)
  expect(mockLeaveBreadcrumb).toHaveBeenCalledTimes(0)
  mockLeaveBreadcrumb.mockClear()

  // accept name=String, metadata=undefined
  c.leaveBreadcrumb('menu_expand')
  expect(mockLeaveBreadcrumb).toHaveBeenCalledWith({
    name: 'menu_expand',
    type: 'manual',
    metadata: {}
  })
  mockLeaveBreadcrumb.mockClear()

  // accept name=String, metadata=String
  c.leaveBreadcrumb('menu_expand', 'via longpress')
  expect(mockLeaveBreadcrumb).toHaveBeenCalledWith({
    name: 'menu_expand',
    type: 'manual',
    metadata: { message: { type: 'string', value: 'via longpress' } }
  })
  mockLeaveBreadcrumb.mockClear()

  // discard name=String, metadata=Number
  c.leaveBreadcrumb('menu_expand', 1)
  expect(mockLeaveBreadcrumb).toHaveBeenCalledWith({
    name: 'menu_expand',
    type: 'manual',
    metadata: {}
  })
  mockLeaveBreadcrumb.mockClear()
})

test('{enable|disable}ConsoleBreadCrumbs(): wraps/unwraps console methods and calls leaveBreadcrumb appropriately', () => {
  const mockLeaveBreadcrumb = jest.fn()
  jest.mock('react-native', () => ({
    NativeModules: { BugsnagReactNative: { startWithOptions: jest.fn(), leaveBreadcrumb: mockLeaveBreadcrumb } }
  }), { virtual: true })

  const { Client, Configuration } = require('../Bugsnag')
  const config = new Configuration('API_KEY')
  const c = new Client(config)

  console.log('don’t mind me cluttering up the test output!')
  expect(mockLeaveBreadcrumb).toHaveBeenCalledTimes(0)

  c.enableConsoleBreadcrumbs()
  console.log('and me ;)')
  expect(mockLeaveBreadcrumb).toHaveBeenCalledTimes(1)

  c.disableConsoleBreadCrumbs()
  console.log('me also')
  expect(mockLeaveBreadcrumb).toHaveBeenCalledTimes(1)
})

test('config.consoleBreadcrumbsEnabled=true: causes console breadcrumbs to be enabled', () => {
  const mockLeaveBreadcrumb = jest.fn()
  jest.mock('react-native', () => ({
    NativeModules: { BugsnagReactNative: { startWithOptions: jest.fn(), leaveBreadcrumb: mockLeaveBreadcrumb } }
  }), { virtual: true })

  const { Client, Configuration } = require('../Bugsnag')
  const config = new Configuration('API_KEY')
  config.consoleBreadcrumbsEnabled = true
  const c = new Client(config)

  console.warn('check 1, 2')
  expect(mockLeaveBreadcrumb).toHaveBeenCalledTimes(1)

  // because global side effects, ensure console wrapping is
  // switched off for the next test ¯\_(ツ)_/¯
  c.disableConsoleBreadCrumbs()
})

test('{enable|disable}ConsoleBreadCrumbs(): gracefully handles serialization edge-cases', () => {
  const mockLeaveBreadcrumb = jest.fn()
  jest.mock('react-native', () => ({
    NativeModules: { BugsnagReactNative: { startWithOptions: jest.fn(), leaveBreadcrumb: mockLeaveBreadcrumb } }
  }), { virtual: true })

  const { Client, Configuration } = require('../Bugsnag')
  const config = new Configuration('API_KEY')
  config.consoleBreadcrumbsEnabled = true
  const c = new Client(config)

  const circular = {};
  circular.ref = circular;

  console.log('undefined cannot have toString() called on it', undefined);
  console.warn('neither can null', null);
  console.error('and objects with circular refs cannot be passed to JSON.stringify()', circular);
  expect(mockLeaveBreadcrumb).toHaveBeenCalledTimes(3)
})
<commit_msg>Clean up global state after tests<commit_after>beforeEach(() => {
  // ensure freshly mocked clients in each test
  jest.resetModules()

  // mock globals set by RN at runtime
  global.__DEV__ = true
  global.ErrorUtils = {
    setGlobalHandler: jest.fn(),
    getGlobalHandler: jest.fn()
  }
})

test('constructor(): throws if called with bad input', () => {
  jest.mock('react-native', () => ({
    NativeModules: { BugsnagReactNative: null }
  }), { virtual: true })

  const { Client } = require('../Bugsnag')

  // non-string and non-undefined value as config/api key
  expect(() => new Client(1)).toThrowError(/API key/)

  // missing BugsnagReactNative bindings
  expect(() => new Client('API_KEY')).toThrowError(/BugsnagReactNative/)
})

test('constructor(): works if everything is ok', () => {
  const mockStart = jest.fn()
  jest.mock('react-native', () => ({
    NativeModules: { BugsnagReactNative: { startWithOptions: mockStart } }
  }), { virtual: true })

  const { Client } = require('../Bugsnag')
  const c = new Client('API_KEY')
  expect(mockStart).toHaveBeenCalledWith(expect.objectContaining({ apiKey: 'API_KEY' }))
  expect(global.ErrorUtils.getGlobalHandler).toHaveBeenCalled()
  expect(global.ErrorUtils.setGlobalHandler).toHaveBeenCalled()
})

test('handleUncaughtErrors(): error handler calls notify(…) correctly', () => {
  jest.mock('react-native', () => ({
    NativeModules: { BugsnagReactNative: { startWithOptions: jest.fn() } }
  }), { virtual: true })

  const { Client } = require('../Bugsnag')
  const c = new Client('API_KEY')

  // get the function that the client passed to the global error handler
  const handler = global.ErrorUtils.setGlobalHandler.mock.calls[0][0]

  c.notify = jest.fn()
  handler(new Error('boom!'), false)

  expect(c.notify).toHaveBeenCalledWith(expect.any(Error), null, false, expect.any(Function), {
    originalSeverity: "error",
    severityReason: "unhandledException",
    unhandled: true
  })
})

test('handlePromiseRejections(): error handler calls notify(…) correctly', () => {
  jest.mock('react-native', () => ({
    NativeModules: { BugsnagReactNative: { startWithOptions: jest.fn() } },
  }), { virtual: true })

  const Promise = require('promise/setimmediate')
  const { Client, Configuration } = require('../Bugsnag')
  const config = new Configuration('API_KEY')
  config.handlePromiseRejections = true
  const c = new Client(config)

  c.notify = jest.fn()
  Promise.reject(new Error('boom!'))

  // promise rejection handler has a 2 second delay before running, so check after minimum viable timeout
  // https://github.com/then/promise/blob/7f33b7bccf07a2da37569095c53d91bea7dbd226/src/rejection-tracking.js#L54
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      try {

        expect(c.notify).toHaveBeenCalledWith(expect.any(Error), null, false, null, {
          originalSeverity: "error",
          severityReason: "unhandledPromiseRejection",
          unhandled: true
        })
      } catch (e) {
        reject(e)
      }
      resolve()
    }, 2001)
  })
})

test('shouldNotify(): returns true/false in the correct situations', () => {
  jest.mock('react-native', () => ({ NativeModules: {} }), { virtual: true })
  const { Configuration } = require('../Bugsnag')
  const c = new Configuration('API_KEY')
  expect(c.shouldNotify()).toEqual(true)
  c.notifyReleaseStages = [ 'production', 'staging' ]
  expect(c.shouldNotify()).toEqual(true)
  c.releaseStage = [ 'testing' ]
  expect(c.shouldNotify()).toEqual(false)
})

test('notify(): calls the correct native notify/notifyBlocking method', () => {
  const mockNotify = jest.fn()
  const mockNotifyBlocking = jest.fn()
  jest.mock('react-native', () => ({
    NativeModules: {
      BugsnagReactNative: {
        startWithOptions: jest.fn(),
        notify: mockNotify,
        notifyBlocking: mockNotifyBlocking
      }
    }
  }), { virtual: true })

  const { Client } = require('../Bugsnag')
  const c = new Client('API_KEY')

  // called with non-error
  c.notify(1)
  c.notify(1, null, true)
  expect(mockNotify).toHaveBeenCalledTimes(0)
  expect(mockNotifyBlocking).toHaveBeenCalledTimes(0)

  // non-blocking
  c.notify(new Error('boom!'))
  expect(mockNotify).toHaveBeenCalledWith(
    expect.objectContaining({
      apiKey: 'API_KEY',
      errorClass: 'Error',
      errorMessage: 'boom!',
      severity: 'warning',
      unhandled: false,
      severityReason: 'handledException'
    })
  )

  // blocking
  c.notify(new Error('nb boom!'), null, true)
  expect(mockNotifyBlocking).toHaveBeenCalledWith(
    expect.objectContaining({
      apiKey: 'API_KEY',
      errorClass: 'Error',
      errorMessage: 'nb boom!',
      severity: 'warning',
      unhandled: false,
      severityReason: 'handledException'
    }),
    true,
    undefined
  )
})

test('notify(): supplying unhandled state as param changes payload', () => {
  const mockNotify = jest.fn()
  const mockNotifyBlocking = jest.fn()
  jest.mock('react-native', () => ({
    NativeModules: {
      BugsnagReactNative: {
        startWithOptions: jest.fn(),
        notify: mockNotify,
        notifyBlocking: mockNotifyBlocking
      }
    }
  }), { virtual: true })

  const { Client } = require('../Bugsnag')
  const c = new Client('API_KEY')

  // send unhandled state
  c.notify(new Error('nb boom!'), null, true, null, {
    originalSeverity: 'warning',
    unhandled: false,
    severityReason: "handledException",
  })
  expect(mockNotifyBlocking).toHaveBeenCalledWith(
    expect.objectContaining({
      severity: 'warning',
      unhandled: false,
      severityReason: "handledException"
    }),
    true,
    null
  )

  // mutate severity
  c.notify(new Error('Mutate Severity'), report => {report.severity = 'info'}, true, null, {
    originalSeverity: 'warning',
    unhandled: false,
    severityReason: "handledException",
  })
  expect(mockNotifyBlocking).toHaveBeenCalledWith(
    expect.objectContaining({
      severity: 'info',
      severityReason: "userCallbackSetSeverity"
    }),
    true,
    null
  )
})

test('notify(): doesn’t call native notify/notifyBlocking when shouldNotify() returns false', () => {
  const mockNotify = jest.fn()
  const mockNotifyBlocking = jest.fn()
  jest.mock('react-native', () => ({
    NativeModules: {
      BugsnagReactNative: {
        startWithOptions: jest.fn(),
        notify: mockNotify,
        notifyBlocking: mockNotifyBlocking
      }
    }
  }), { virtual: true })

  const { Client, Configuration } = require('../Bugsnag')
  const config = new Configuration('API_KEY')
  const c = new Client(config)

  // replace shouldNotify with a function that always returns false
  config.shouldNotify = () => false

  // non-blocking
  c.notify(new Error('boom!'))
  expect(mockNotify).toHaveBeenCalledTimes(0)

  // blocking
  c.notify(new Error('nb boom!'), null, true)
  expect(mockNotifyBlocking).toHaveBeenCalledTimes(0)
})

test('leaveBreadcrumb(): calls the native leaveBreadcrumb method correctly', () => {
  const mockLeaveBreadcrumb = jest.fn()
  jest.mock('react-native', () => ({
    NativeModules: { BugsnagReactNative: { startWithOptions: jest.fn(), leaveBreadcrumb: mockLeaveBreadcrumb } }
  }), { virtual: true })

  const { Client, Configuration } = require('../Bugsnag')
  const config = new Configuration('API_KEY')
  const c = new Client(config)

  // reject name=String, metadata=undefined
  c.leaveBreadcrumb(1)
  expect(mockLeaveBreadcrumb).toHaveBeenCalledTimes(0)
  mockLeaveBreadcrumb.mockClear()

  // accept name=String, metadata=undefined
  c.leaveBreadcrumb('menu_expand')
  expect(mockLeaveBreadcrumb).toHaveBeenCalledWith({
    name: 'menu_expand',
    type: 'manual',
    metadata: {}
  })
  mockLeaveBreadcrumb.mockClear()

  // accept name=String, metadata=String
  c.leaveBreadcrumb('menu_expand', 'via longpress')
  expect(mockLeaveBreadcrumb).toHaveBeenCalledWith({
    name: 'menu_expand',
    type: 'manual',
    metadata: { message: { type: 'string', value: 'via longpress' } }
  })
  mockLeaveBreadcrumb.mockClear()

  // discard name=String, metadata=Number
  c.leaveBreadcrumb('menu_expand', 1)
  expect(mockLeaveBreadcrumb).toHaveBeenCalledWith({
    name: 'menu_expand',
    type: 'manual',
    metadata: {}
  })
  mockLeaveBreadcrumb.mockClear()
})

test('{enable|disable}ConsoleBreadCrumbs(): wraps/unwraps console methods and calls leaveBreadcrumb appropriately', () => {
  const mockLeaveBreadcrumb = jest.fn()
  jest.mock('react-native', () => ({
    NativeModules: { BugsnagReactNative: { startWithOptions: jest.fn(), leaveBreadcrumb: mockLeaveBreadcrumb } }
  }), { virtual: true })

  const { Client, Configuration } = require('../Bugsnag')
  const config = new Configuration('API_KEY')
  const c = new Client(config)

  console.log('don’t mind me cluttering up the test output!')
  expect(mockLeaveBreadcrumb).toHaveBeenCalledTimes(0)

  c.enableConsoleBreadcrumbs()
  console.log('and me ;)')
  expect(mockLeaveBreadcrumb).toHaveBeenCalledTimes(1)

  c.disableConsoleBreadCrumbs()
  console.log('me also')
  expect(mockLeaveBreadcrumb).toHaveBeenCalledTimes(1)
})

test('config.consoleBreadcrumbsEnabled=true: causes console breadcrumbs to be enabled', () => {
  const mockLeaveBreadcrumb = jest.fn()
  jest.mock('react-native', () => ({
    NativeModules: { BugsnagReactNative: { startWithOptions: jest.fn(), leaveBreadcrumb: mockLeaveBreadcrumb } }
  }), { virtual: true })

  const { Client, Configuration } = require('../Bugsnag')
  const config = new Configuration('API_KEY')
  config.consoleBreadcrumbsEnabled = true
  const c = new Client(config)

  console.warn('check 1, 2')
  expect(mockLeaveBreadcrumb).toHaveBeenCalledTimes(1)

  // because global side effects, ensure console wrapping is
  // switched off for the next test ¯\_(ツ)_/¯
  c.disableConsoleBreadCrumbs()
})

test('{enable|disable}ConsoleBreadCrumbs(): gracefully handles serialization edge-cases', () => {
  const mockLeaveBreadcrumb = jest.fn()
  jest.mock('react-native', () => ({
    NativeModules: { BugsnagReactNative: { startWithOptions: jest.fn(), leaveBreadcrumb: mockLeaveBreadcrumb } }
  }), { virtual: true })

  const { Client, Configuration } = require('../Bugsnag')
  const config = new Configuration('API_KEY')
  config.consoleBreadcrumbsEnabled = true
  const c = new Client(config)

  const circular = {};
  circular.ref = circular;

  console.log('undefined cannot have toString() called on it', undefined);
  console.warn('neither can null', null);
  console.error('and objects with circular refs cannot be passed to JSON.stringify()', circular);
  expect(mockLeaveBreadcrumb).toHaveBeenCalledTimes(3)

  // because global side effects, ensure console wrapping is
  // switched off for the next test ¯\_(ツ)_/¯
  c.disableConsoleBreadCrumbs()
})
<|endoftext|>


<commit_before>/*globals define*/
/*jshint node:true, browser:true*/

/**
 * Generated by PluginGenerator 1.7.0 from webgme on Thu May 05 2016 13:51:44 GMT-0700 (PDT).
 */

define([
    'plugin/PluginConfig',
    'text!./metadata.json',
    'plugin/PluginBase',
    'gridlabd/meta',
    'q'
], function (
    PluginConfig,
    pluginMetadata,
    PluginBase,
    MetaTypes,
    Q) {
    'use strict';

    pluginMetadata = JSON.parse(pluginMetadata);

    /**
     * Initializes a new instance of UpdateGLDMeta.
     * @class
     * @augments {PluginBase}
     * @classdesc This class represents the plugin UpdateGLDMeta.
     * @constructor
     */
    var UpdateGLDMeta = function () {
        // Call base class' constructor.
        PluginBase.call(this);
        this.pluginMetadata = pluginMetadata;
        this.metaTypes = MetaTypes;
    };

    /**
     * Metadata associated with the plugin. Contains id, name, version, description, icon, configStructue etc.
     * This is also available at the instance at this.pluginMetadata.
     * @type {object}
     */
    UpdateGLDMeta.metadata = pluginMetadata;

    // Prototypical inheritance from PluginBase.
    UpdateGLDMeta.prototype = Object.create(PluginBase.prototype);
    UpdateGLDMeta.prototype.constructor = UpdateGLDMeta;

    /**
     * Main function for the plugin to execute. This will perform the execution.
     * Notes:
     * - Always log with the provided logger.[error,warning,info,debug].
     * - Do NOT put any user interaction logic UI, etc. inside this method.
     * - callback always has to be called even if error happened.
     *
     * @param {function(string, plugin.PluginResult)} callback - the result callback
     */
    UpdateGLDMeta.prototype.main = function (callback) {
        // Use self to access core, project, result, logger etc from PluginBase.
        // These are all instantiated at this point.
        var self = this;

        self.updateMETA(self.metaTypes);

	var currentConfig = self.getCurrentConfig(),
	    metaFileHash = currentConfig.gldModHelp;

	self.blobClient.getMetadata(metaFileHash)
	    .then(function(glmMetaData) {
		var splitName = glmMetaData.name.split(".");
		var newName = "";
		for (var i=0;i<splitName.length-1;i++) {
		    newName += splitName[i];
		}
		self.logger.info('loaded: ' + newName);
		return self.blobClient.getObjectAsString(metaFileHash);
	    })
	    .then(function(metaFile) {
		return self.parseObjectsFromFile(metaFile);
	    })
	    .then(function() {
		return self.createModelArtifacts();
	    })
	    .then(function() {
		//return self.save('UpdateGLDMeta updated model meta.');
	    })
	    .then(function() {
		self.result.setSuccess(true);
		callback(null, self.result);
	    })
	    .catch(function(err) {
		self.logger.error('ERROR:: '+err);
		self.result.setSuccess(false);
		callback(null, self.result);
	    });
    };

    UpdateGLDMeta.prototype.parseObjectsFromFile = function(fileStr) {
	var self = this,
	    class_regex = /class (\w+) {/g,
	    class_end_regex = /}$/gm,
	    lines = fileStr.split('\n'),
	    results,
	    objects = {},
	    currentObj,
	    objStrings = [],
	    depth = 0;
	lines.map((line) => {
	    if (results = class_regex.exec(line)) {
		depth++;
		var name = results[1];
		if (objects[name] === undefined) {
		    self.logger.info('got class: ' + name);
		    objects[name] = {name: name};
		    currentObj = objects[name];
		    objStrings[depth] = '';
		}
	    }
	    else if (class_end_regex.test(line)) {
		if (typeof objStrings[depth] === 'string' &&
		    objStrings[depth].length > 0) {
		    currentObj = self.parseObjectString(currentObj, objStrings[depth]);
		    objStrings.splice(depth, 1);
		}
		depth--;
	    }
	    else if (typeof objStrings[depth] === 'string') {
		objStrings[depth] += line + '\n';
	    }
	});
    };

    UpdateGLDMeta.prototype.parseObjectString = function (obj, str) {
	var attr_regex = /(\w+)\s([\w]+)(\[\S+\])?;/g;
	var enum_set_regex = /(enumeration|set)\s\{([\S ]+)\}\s(\w+);/g;
	var value_regex = /(\w+)=(\d+)/gi;

	var convertAttrToType = function(attr) {
	    if (attr.type === 'complex' ||
		attr.type === 'set' ||
		attr.type === 'enumeration' ||
		attr.type === 'loadshape' ||
		attr.type === 'enduse' ||
		attr.type === 'timestamp' ||
		attr.type.indexOf('char') > -1)
		return 'string';
	    else if (attr.type.indexOf('int') > -1)
		return 'integer';
	    else if (attr.type === 'double')
		return 'float';
	    else if (attr.type === 'bool')
		return 'bool';
	    else
		return undefined;
	};

	var isPointer = function(attr) {
	    return attr.type === 'object';
	};

	var isParent = function(attr) {
	    return attr.type === 'parent';
	};

    };

    UpdateGLDMeta.prototype.createModelArtifacts = function() {
    };

    UpdateGLDMeta.prototype.createMetaNode = function(name, base, attrs, ptrs) {
	if (this.META[name]) {
	    this.logger.warn('"' + name + '" already exists!');
	    return this.META[name];
	}

	// set METAAspectSet of the ROOT node (means it is META)
	//  adds to the meta sheet
	//   : core.addMember(self.rootNode, 'MetaAspectSet', node)
	//  adds to a tab of the meta sheet
	//   : var set = self.core.getSetNames(self.rootNode).find(name => name !== 'MetaAspectSet');
	//   : core.addMember(this.rootNode, set, node);
	// need to position the nodes in the meta sheet!
	// means we can create meta-sheets for each of the loaded files! :D

	// USEFUL FUNCTIONS:
	//   core.setAspectMetaTarget(node, name, target)
	//   core.getFCO(node)
	//   core.getAllMetaNodes(node)
	//   core.getChildrenMeta(node)
	//   core.getAttributeNames(node)
	//   core.getAttributeMeta(node, name)
	//   core.getBase(node)
	//   core.getBaseType(node)
	//   core.getPointerNames(node)
	//   core.getPointerMeta(node, name)
	//   core.getRegistryNames(node)
	//   core.getRegistry(node, name)
	//   core.getSetNames(node)

	// require:
	//   META.Language
	//   META.Object
	//   META.Module
	//   META.Class
	//   META.Parent

	var node = this.core.createNode({
	    parent: this.META.Language,
	    base: base
	});
	this.core.setAttribute(node, 'name', name);

	// add to the META sheet
	this.core.addMember(this.rootNode, 'MetaAspectSet', node);

	// add to the specific sheet
	var set = this.core.getSetNames(this.rootNode)
	    .find(name => name !== 'MetaAspectSet');
	this.core.addMember(this.rootNode, set, node);

	// position the node based on the position of the most recently created node on that sheet
	// core.setRegistry(node, 'position', {x: , y:})

	// set the attributes
	if (attrs) {
	    attrs.forEach((attr, index) => {
		var name = attr.name;
		var desc = attr;
		desc.argindex = index;
		this.addAttribute(name, node, desc);
	    });
	}

	// set the pointers
	if (ptrs) {
	    ptrs.map((ptr) => {
		var name = ptr.name;
		var desc = ptr;
		this.addPointer(name, node, desc);
	    });
	}
    };

    UpdateGLDMeta.prototype.addAttribute = function(name, node, desc) {
	var initial,
	    schema = {};

	schema.type = desc.type || 'integer';

	if (desc.min !== undefined) {
	    schema.min = +desc.min;
	}
	if (desc.max !== undefined) {
	    schema.max = +desc.max;
	}
	if (desc.infer) {
	    schema.infer = def.infer;
	}
	if (desc.enum) {
	    schema.enum = desc.enum;
	}
	schema.argindex = def.argindex;
	this.core.setAttributeMeta(node, name, schema);

	// determine and set the initial value for the attribute
	initial = desc.hasOwnProperty('default') ? desc.default : def.min || null;
	if (schema.type === 'boolean') {
	    initial = initial !== null ? initial : false;
	}
	if (initial !== null) {
	    this.core.setAttribute(node, name, initial);
	}
    };

    UpdateGLDMeta.prototype.addPointer = function(name, node, desc) {
	this.core.setPointerMetaTarget(node, name, desc.target, desc.min || -1, desc.max || -1);
    };

    return UpdateGLDMeta;
});
<commit_msg>fleshing out parser for schema.<commit_after>/*globals define*/
/*jshint node:true, browser:true*/

/**
 * Generated by PluginGenerator 1.7.0 from webgme on Thu May 05 2016 13:51:44 GMT-0700 (PDT).
 */

define([
    'plugin/PluginConfig',
    'text!./metadata.json',
    'plugin/PluginBase',
    'gridlabd/meta',
    'q'
], function (
    PluginConfig,
    pluginMetadata,
    PluginBase,
    MetaTypes,
    Q) {
    'use strict';

    pluginMetadata = JSON.parse(pluginMetadata);

    /**
     * Initializes a new instance of UpdateGLDMeta.
     * @class
     * @augments {PluginBase}
     * @classdesc This class represents the plugin UpdateGLDMeta.
     * @constructor
     */
    var UpdateGLDMeta = function () {
        // Call base class' constructor.
        PluginBase.call(this);
        this.pluginMetadata = pluginMetadata;
        this.metaTypes = MetaTypes;
    };

    /**
     * Metadata associated with the plugin. Contains id, name, version, description, icon, configStructue etc.
     * This is also available at the instance at this.pluginMetadata.
     * @type {object}
     */
    UpdateGLDMeta.metadata = pluginMetadata;

    // Prototypical inheritance from PluginBase.
    UpdateGLDMeta.prototype = Object.create(PluginBase.prototype);
    UpdateGLDMeta.prototype.constructor = UpdateGLDMeta;

    /**
     * Main function for the plugin to execute. This will perform the execution.
     * Notes:
     * - Always log with the provided logger.[error,warning,info,debug].
     * - Do NOT put any user interaction logic UI, etc. inside this method.
     * - callback always has to be called even if error happened.
     *
     * @param {function(string, plugin.PluginResult)} callback - the result callback
     */
    UpdateGLDMeta.prototype.main = function (callback) {
        // Use self to access core, project, result, logger etc from PluginBase.
        // These are all instantiated at this point.
        var self = this;

        self.updateMETA(self.metaTypes);

	var currentConfig = self.getCurrentConfig(),
	    metaFileHash = currentConfig.gldModHelp;

	self.blobClient.getMetadata(metaFileHash)
	    .then(function(glmMetaData) {
		var splitName = glmMetaData.name.split(".");
		var newName = "";
		for (var i=0;i<splitName.length-1;i++) {
		    newName += splitName[i];
		}
		self.logger.info('loaded: ' + newName);
		return self.blobClient.getObjectAsString(metaFileHash);
	    })
	    .then(function(metaFile) {
		return self.parseObjectsFromFile(metaFile);
	    })
	    .then(function() {
		return self.createModelArtifacts();
	    })
	    .then(function() {
		//return self.save('UpdateGLDMeta updated model meta.');
	    })
	    .then(function() {
		self.result.setSuccess(true);
		callback(null, self.result);
	    })
	    .catch(function(err) {
		self.logger.error('ERROR:: '+err);
		self.result.setSuccess(false);
		callback(null, self.result);
	    });
    };

    UpdateGLDMeta.prototype.parseObjectsFromFile = function(fileStr) {
	var self = this,
	    class_regex = /class (\w+) {/g,
	    class_end_regex = /}$/gm,
	    lines = fileStr.split('\n'),
	    results,
	    objects = {},
	    currentObj,
	    objStrings = [],
	    depth = 0;
	lines.map((line) => {
	    if (results = class_regex.exec(line)) {
		depth++;
		var name = results[1];
		if (objects[name] === undefined) {
		    self.logger.info('got class: ' + name);
		    objects[name] = {name: name};
		    currentObj = objects[name];
		    objStrings[depth] = [];
		}
	    }
	    else if (class_end_regex.test(line)) {
		if (objStrings[depth] instanceof Array &&
		    objStrings[depth].length > 0) {
		    currentObj = self.parseObjectString(currentObj, objStrings[depth]);
		    objStrings.pop();
		}
		depth--;
	    }
	    else if (objStrings[depth] instanceof Array) {
		objStrings[depth].push(line);
	    }
	});
    };

    UpdateGLDMeta.prototype.parseObjectString = function (obj, lines) {
	var self = this,
	    attr_regex = /(\w+)\s([\w]+)(\[\S+\])?;/g,
	    enum_set_regex = /(enumeration|set)\s\{([\S ]+)\}\s(\w+);/g,
	    value_regex = /(\w+)=(\d+)/gi,
	    results;

	var convertAttrToType = function(attr) {
	    if (attr === 'complex' ||
		attr === 'set' ||
		attr === 'enumeration' ||
		attr === 'loadshape' ||
		attr === 'enduse' ||
		attr === 'timestamp' ||
		attr.indexOf('char') > -1)
		return 'string';
	    else if (attr.indexOf('int') > -1)
		return 'integer';
	    else if (attr === 'double')
		return 'float';
	    else if (attr === 'bool')
		return 'bool';
	    else
		return undefined;
	};

	var isPointer = function(attr) {
	    return attr === 'object';
	};

	var isParent = function(attr) {
	    return attr === 'parent';
	};

	lines.map((line) => {
	    if (results = attr_regex.exec(line)) {
		var kind = convertAttrToType(results[1]);
		if (kind === undefined) { // must be object or parent
		    if (results[1] === 'parent') {
			self.logger.info('got parent!');
		    }
		    else if (results[1] === 'object') {
			self.logger.info('got pointer!');
		    }
		}
		else {
		    self.logger.info('got kind: '+results[1] + ': ' +kind);
		}
	    }
	    else if (results = enum_set_regex.exec(line)) {
		var kind = convertAttrToType(results[1]);
		self.logger.info('got enum/set: '+results[1] + ': ' +kind);
	    }
	});

    };

    UpdateGLDMeta.prototype.createModelArtifacts = function() {
    };

    UpdateGLDMeta.prototype.createMetaNode = function(name, base, attrs, ptrs) {
	if (this.META[name]) {
	    this.logger.warn('"' + name + '" already exists!');
	    return this.META[name];
	}

	// set METAAspectSet of the ROOT node (means it is META)
	//  adds to the meta sheet
	//   : core.addMember(self.rootNode, 'MetaAspectSet', node)
	//  adds to a tab of the meta sheet
	//   : var set = self.core.getSetNames(self.rootNode).find(name => name !== 'MetaAspectSet');
	//   : core.addMember(this.rootNode, set, node);
	// need to position the nodes in the meta sheet!
	// means we can create meta-sheets for each of the loaded files! :D

	// USEFUL FUNCTIONS:
	//   core.setAspectMetaTarget(node, name, target)
	//   core.getFCO(node)
	//   core.getAllMetaNodes(node)
	//   core.getChildrenMeta(node)
	//   core.getAttributeNames(node)
	//   core.getAttributeMeta(node, name)
	//   core.getBase(node)
	//   core.getBaseType(node)
	//   core.getPointerNames(node)
	//   core.getPointerMeta(node, name)
	//   core.getRegistryNames(node)
	//   core.getRegistry(node, name)
	//   core.getSetNames(node)

	// require:
	//   META.Language
	//   META.Object
	//   META.Module
	//   META.Class
	//   META.Parent

	var node = this.core.createNode({
	    parent: this.META.Language,
	    base: base
	});
	this.core.setAttribute(node, 'name', name);

	// add to the META sheet
	this.core.addMember(this.rootNode, 'MetaAspectSet', node);

	// add to the specific sheet
	var set = this.core.getSetNames(this.rootNode)
	    .find(name => name !== 'MetaAspectSet');
	this.core.addMember(this.rootNode, set, node);

	// position the node based on the position of the most recently created node on that sheet
	// core.setRegistry(node, 'position', {x: , y:})

	// set the attributes
	if (attrs) {
	    attrs.forEach((attr, index) => {
		var name = attr.name;
		var desc = attr;
		desc.argindex = index;
		this.addAttribute(name, node, desc);
	    });
	}

	// set the pointers
	if (ptrs) {
	    ptrs.map((ptr) => {
		var name = ptr.name;
		var desc = ptr;
		this.addPointer(name, node, desc);
	    });
	}
    };

    UpdateGLDMeta.prototype.addAttribute = function(name, node, desc) {
	var initial,
	    schema = {};

	schema.type = desc.type || 'integer';

	if (desc.min !== undefined) {
	    schema.min = +desc.min;
	}
	if (desc.max !== undefined) {
	    schema.max = +desc.max;
	}
	if (desc.infer) {
	    schema.infer = def.infer;
	}
	if (desc.enum) {
	    schema.enum = desc.enum;
	}
	schema.argindex = def.argindex;
	this.core.setAttributeMeta(node, name, schema);

	// determine and set the initial value for the attribute
	initial = desc.hasOwnProperty('default') ? desc.default : def.min || null;
	if (schema.type === 'boolean') {
	    initial = initial !== null ? initial : false;
	}
	if (initial !== null) {
	    this.core.setAttribute(node, name, initial);
	}
    };

    UpdateGLDMeta.prototype.addPointer = function(name, node, desc) {
	this.core.setPointerMetaTarget(node, name, desc.target, desc.min || -1, desc.max || -1);
    };

    return UpdateGLDMeta;
});
<|endoftext|>


<commit_before>/**
 * Reordering utilities for the items drag and drop ordering page -AZ
 * Does not put stuff into the global namespace and uses only methods that are compatible with most browsers
 */
$(document).ready(
	function () {

		var baseId = "blockPage:::";

		function $it(id) {
        	return document.getElementById(id);
        }

		function enableOrderButtons() {
			$it(baseId + "revertOrderButton").disabled = false;
			$it(baseId + "saveReorderButton").disabled = false;
		}

		function disableOrderButtons() {
			$it(baseId + "revertOrderButton").disabled = true;
			$it(baseId + "saveReorderButton").disabled = true;
		}

        var sortableIds;
        function buildSortableIds() {
        	sortableIds = new Array();
			var domList = $("div.itemList > div").get();
			for (var i = 0; i < domList.length; i++) {
				sortableIds.push(domList[i].id);
				//var itemNum = $it(domList[i].id + "item-num");
				//$(itemNum).removeClass("orderChanged");
				//var itemSelect = $it(domList[i].id + "item-select-selection");
				//$(itemSelect).removeClass("orderChanged");
			}
		}
		buildSortableIds();

		var reorderButtonsExist = false;
		var saveReorderButton = $it(baseId + "saveReorderButton");

		if (saveReorderButton != null) {
			reorderButtonsExist = true;
			saveReorderButton.onclick = function() {
				disableOrderButtons();
				buildSortableIds();
				$(saveReorderButton.form).ajaxSubmit();
				return false;
			};
		}

		function setIndex(itemId, newindex) {
			$it(itemId + "item-num").innerHTML = (parseInt(newindex) + 1);
		}

		function setRadioActions() {

			var radioList = $("div.blockText > input").get();
			var textAreaDiv = $it("item-text-div");

			if (radioList.length != 0) {

				for (i in radioList) {
					if (radioList[i].id) {
						radioList[i].onclick = function() {
							textAreaDiv.style.display = "none";
						};
					}
				}

				radioList[radioList.length - 1].onclick = function() {
					textAreaDiv.style.display = "block";
				};

				radioList[radioList.length - 1].checked = true;

			}

		}

		setRadioActions();

		// put original order into the revertOrder trigger
		var revertOrderButton = $it(baseId + "revertOrderButton");

		if (revertOrderButton != null) {
			revertOrderButton.onclick = function() {
				disableOrderButtons();
				for(var i = sortableIds.length - 1; i > 0; --i) {
					var thisitem = $it(sortableIds[i]);
					var previtem = $it(sortableIds[i - 1]);
					previtem.parentNode.removeChild(previtem);
					thisitem.parentNode.insertBefore(previtem, thisitem);
					setIndex(thisitem.id, i);
				}
				setIndex(sortableIds[0], 0);
			};
		}

		var saveBlockButton = $it(baseId + "saveBlockAction");
		saveBlockButton.onclick = function() {
			flag = "save";
			var list = $("div.itemTable > div").get();
			var childIdList = new Array();
			for (i in list) {
				if (list[i].id) {
					childIdList.push($it(list[i].id + "hidden-item-id").value);
				}
			}
			$it(baseId + "ordered-child-ids").value = childIdList.toString();
		}

		$("div.itemTable").Sortable(
			{
				accept: 		"itemRow",
				activeclass: 	"sortableactive",
				axis:			"vertically",
				hoverclass: 	"sortablehover",
				helperclass: 	"sorthelper",
				onchange:
					function(data) {
						var list = $("div.itemTable > div").get();
						for (i in list) {
							if (list[i].id) {
								itemId = list[i].id;
								setIndex(itemId, i);
							}
						}
						if (reorderButtonsExist)
							enableOrderButtons();
					},
				opacity: 		0.5,
				tolerance:		"intersect"
			}
		);
	}
);
<commit_msg>fix to commit 11203 <commit_after>/**
 * Reordering utilities for the items drag and drop ordering page -AZ
 * Does not put stuff into the global namespace and uses only methods that are compatible with most browsers
 */
$(document).ready(
	function () {

		var baseId = "blockPage::";

		function $it(id) {
        	return document.getElementById(id);
        }

		function enableOrderButtons() {
			$it(baseId + "revertOrderButton").disabled = false;
			$it(baseId + "saveReorderButton").disabled = false;
		}

		function disableOrderButtons() {
			$it(baseId + "revertOrderButton").disabled = true;
			$it(baseId + "saveReorderButton").disabled = true;
		}

        var sortableIds;
        function buildSortableIds() {
        	sortableIds = new Array();
			var domList = $("div.itemList > div").get();
			for (var i = 0; i < domList.length; i++) {
				sortableIds.push(domList[i].id);
				//var itemNum = $it(domList[i].id + "item-num");
				//$(itemNum).removeClass("orderChanged");
				//var itemSelect = $it(domList[i].id + "item-select-selection");
				//$(itemSelect).removeClass("orderChanged");
			}
		}
		buildSortableIds();

		var reorderButtonsExist = false;
		var saveReorderButton = $it(baseId + "saveReorderButton");

		if (saveReorderButton != null) {
			reorderButtonsExist = true;
			saveReorderButton.onclick = function() {
				disableOrderButtons();
				buildSortableIds();
				$(saveReorderButton.form).ajaxSubmit();
				return false;
			};
		}

		function setIndex(itemId, newindex) {
			$it(itemId + "item-num").innerHTML = (parseInt(newindex) + 1);
		}

		function setRadioActions() {

			var radioList = $("div.blockText > input").get();
			var textAreaDiv = $it("item-text-div");

			if (radioList.length != 0) {

				for (i in radioList) {
					if (radioList[i].id) {
						radioList[i].onclick = function() {
							textAreaDiv.style.display = "none";
						};
					}
				}

				radioList[radioList.length - 1].onclick = function() {
					textAreaDiv.style.display = "block";
				};

				radioList[radioList.length - 1].checked = true;

			}

		}

		setRadioActions();

		// put original order into the revertOrder trigger
		var revertOrderButton = $it(baseId + "revertOrderButton");

		if (revertOrderButton != null) {
			revertOrderButton.onclick = function() {
				disableOrderButtons();
				for(var i = sortableIds.length - 1; i > 0; --i) {
					var thisitem = $it(sortableIds[i]);
					var previtem = $it(sortableIds[i - 1]);
					previtem.parentNode.removeChild(previtem);
					thisitem.parentNode.insertBefore(previtem, thisitem);
					setIndex(thisitem.id, i);
				}
				setIndex(sortableIds[0], 0);
			};
		}

		var saveBlockButton = $it(baseId + "saveBlockAction");
		saveBlockButton.onclick = function() {
			flag = "save";
			var list = $("div.itemTable > div").get();
			var childIdList = new Array();
			for (i in list) {
				if (list[i].id) {
					childIdList.push($it(list[i].id + "hidden-item-id").value);
				}
			}
			$it(baseId + "ordered-child-ids").value = childIdList.toString();
		}

		$("div.itemTable").Sortable(
			{
				accept: 		"itemRow",
				activeclass: 	"sortableactive",
				axis:			"vertically",
				hoverclass: 	"sortablehover",
				helperclass: 	"sorthelper",
				onchange:
					function(data) {
						var list = $("div.itemTable > div").get();
						for (i in list) {
							if (list[i].id) {
								itemId = list[i].id;
								setIndex(itemId, i);
							}
						}
						if (reorderButtonsExist)
							enableOrderButtons();
					},
				opacity: 		0.5,
				tolerance:		"intersect"
			}
		);
	}
);
<|endoftext|>


<commit_before>/* ***** BEGIN LICENSE BLOCK *****
 *
 * Copyright (c) 2009 Aptana, Inc.
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * ***** END LICENSE BLOCK ***** */

/**
 * Will match() the given path and call the dispatcher if one is found.
 * @alias ActiveRoutes.prototype.dispatch
 * @param {String} path
 * @exception {ActiveRoutes.Errors.UnresolvableUrl}
 * @example
 * var routes = new ActiveRoutes([['post','/blog/post/:id',{object:'blog',method: 'post'}]]);

 * routes.dispatch('/blog/post/5');

 * //by default calls Blog.post({object:'blog',method: 'post',id: 5});
 */
ActiveRoutes.prototype.dispatch = function dispatch(path)
{
    var route;
    if(typeof(path) == 'string')
    {
        route = this.match(path);
        if(!route)
        {
            throw Errors.UnresolvableUrl + path;
        }
    }
    else
    {
        route = {
            params: path
        };
    }
    this.history.push(route);
    this.index = this.history.length - 1;
    this.dispatcher(route);
};

/**
 * If no "dispatcher" key is passed into the options to contstruct a route set
 *  this is used. It will call scope.object_name.method_name(route.params)
 * @property {Function}
 * @alias ActiveRoutes.prototype.defaultDispatcher
 */
ActiveRoutes.prototype.defaultDispatcher = function defaultDispatcher(route)
{
    this.scope[route.params.object][route.params.method](route.params);
};<commit_msg>error information now thrown instead of generic error<commit_after>/* ***** BEGIN LICENSE BLOCK *****
 *
 * Copyright (c) 2009 Aptana, Inc.
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * ***** END LICENSE BLOCK ***** */

/**
 * Will match() the given path and call the dispatcher if one is found.
 * @alias ActiveRoutes.prototype.dispatch
 * @param {String} path
 * @exception {ActiveRoutes.Errors.UnresolvableUrl}
 * @example
 * var routes = new ActiveRoutes([['post','/blog/post/:id',{object:'blog',method: 'post'}]]);

 * routes.dispatch('/blog/post/5');

 * //by default calls Blog.post({object:'blog',method: 'post',id: 5});
 */
ActiveRoutes.prototype.dispatch = function dispatch(path)
{
    var route;
    if(typeof(path) == 'string')
    {
        route = this.match(path);
        if(!route)
        {
            if(this.error)
            {
                throw this.error;
            }
            else
            {
                throw Errors.UnresolvableUrl + path;
            }
        }
    }
    else
    {
        route = {
            params: path
        };
    }
    this.history.push(route);
    this.index = this.history.length - 1;
    this.dispatcher(route);
};

/**
 * If no "dispatcher" key is passed into the options to contstruct a route set
 *  this is used. It will call scope.object_name.method_name(route.params)
 * @property {Function}
 * @alias ActiveRoutes.prototype.defaultDispatcher
 */
ActiveRoutes.prototype.defaultDispatcher = function defaultDispatcher(route)
{
    this.scope[route.params.object][route.params.method](route.params);
};<|endoftext|>


<commit_before>(function() {
	var Pocket,
		POSTrequest;

	var createParams = function(consumer_key, access_token, obj) {
		obj = obj || {};
		obj.consumer_key = consumer_key;
		obj.access_token = access_token;
		return obj;
	};

	var URI = {
			ADD: "https://getpocket.com/v3/add",
			MODIFY: "https://getpocket.com/v3/modify",
			RETRIEVE: "https://getpocket.com/v3/get",
			REQUEST: "https://getpocket.com/v3/oauth/request",
			AUTHORIZE: "https://getpocket.com/v3/oauth/authorize",
			G_AUTHORIZE: "https://getpocket.com/auth/authorize"
		};

	Pocket = function(consumer_key, access_token) {
		this.consumer_key = consumer_key;
		this.access_token = access_token;
	};
	Pocket.prototype = {
		constructor: Pocket,
		/**
		 * Adds an item to pocket.
		 * {@see http://getpocket.com/developer/docs/v3/add}
		 *
		 * @param {!Object} params Object with the details of the item
		 *   to add. And url key-value must exists.
		 * @param {!function(?Error, ?number, ?Object)} callback
		 *   Callback function. It has three arguments. An error, if
		 *   there was one, the status and the item added.
		 */
		add: function(params, callback) {
			POSTrequest(
				URI.ADD,
				createParams(this.consumer_key, this.access_token, params),
				function(err, data) {
					if(err) {
						callback(err);
					} else {
						callback(null, data.status, data.item);
					}
				});
		},
		modify: function(params, callback) {
			POSTrequest(
				URI.MODIFY,
				createParams(this.consumer_key, this.access_token, params),
				function(err, data) {
					if(err) {
						callback(err);
					} else {
						callback(null, data.status, data.item);
					}
				});
		},
		retrieve: function(params, callback) {
			POSTrequest(
				URI.RETRIEVE,
				createParams(this.consumer_key, this.access_token, params),
				function(err, data) {
					if(err) {
						callback(err);
					} else {
						callback(null, data.status, data.list);
					}
				});
		}
	};
	Pocket.auth = {
		request: function(consumer_key, redirect_uri, callback) {
			POSTrequest(
				URI.REQUEST,
				{
					"consumer_key": consumer_key,
					"redirect_uri": redirect_uri
				},
				function(err, data, url) {
					if(err) {
						callback(err);
					}else{
						callback(null, data.code);
					}
				});
		},
		authorize: function(consumer_key, request_token, callback) {
			POSTrequest(
				URI.AUTHORIZE,
				{
					"consumer_key": consumer_key,
					"code": request_token
				},
				function(err, data, url) {
					if(err) {
						callback(err);
					}else{
						callback(null, data.access_token, data.username);
					}
				});
		},
		getAuthorizeURL: function(token, redirectUrl) {
			return URI.G_AUTHORIZE+"?request_token="+token+"&redirect_uri="+redirectUrl;
		}
	};
	/**
	 * State of an item.
	 *
	 * @type {!Object}
	 * @enum
	 */
	Pocket.STATE = {
		ALL: "all",
		UNREAD: "unread",
		ARCHIVE: "archive"
	};
	/**
	 * Detail of item(s).
	 *
	 * @type {!Object}
	 * @enum
	 */
	Pocket.DETAIL_TYPE = {
		SIMPLE: "simple",
		COMPLETE: "complete"
	};
	/**
	 * Sorting of a list.
	 *
	 * @type {!Object}
	 * @enum
	 */
	Pocket.SORT = {
		NEWEST: "newest",
		OLDEST: "oldest",
		TITLE: "title",
		SITE: "site"
	};
	/**
	 * Type of item.
	 *
	 * @type {!Object}
	 * @enum
	 */
	Pocket.CONTENT_TYPE = {
		ARTICLE: "article",
		VIDEO: "video",
		IMAGE: "image"
	};
	/**
	 * Tag type.
	 *
	 * @type {!Object}
	 * @enum
	 */
	Pocket.TAG = {
		UNTAGGED: "_untagged_"
	};
	/**
	 * Favorite.
	 *
	 * @type {!Object}
	 * @enum
	 */
	Pocket.FAVORITE = {
		YES: 1,
		NO: 0
	};

	if (typeof module !== 'undefined' && typeof module.exports !== 'undefined'){
		POSTrequest = (function() {
			var request = require("http").request,
				urlUtils = require("url");
			return function(url, data, callback) {
				var options = urlUtils.parse(url);
				options.method = "POST";
				options.headers = {
					"Content-Type", "application/json; charset=UTF-8",
					"X-Accept", "application/json"
				}
				var req = request(options, function(res) {
					var data = "";
					res.on("data", function(chunk) {
						data += chunk;
						callback(undefined, chunk);
					});
					res.on("end", function() {
						callback(undefined, JSON.parse(data));
					});
				});
				req.on("error", function(e) {
					callback(err);
				});
				req.write(JSON.stringify(data));
				req.end();
			};
		})();
		module.exports = Pocket;
	} else {
		POSTrequest = function(url, data, callback) {
			var xhr = new XMLHttpRequest();

			xhr.open("POST", url, true);

			xhr.setRequestHeader("Content-Type", "application/json; charset=UTF-8");
			xhr.setRequestHeader("X-Accept", "application/json");

			xhr.send(JSON.stringify(data));

			xhr.onreadystatechange = function() {
				if( xhr.readyState === xhr.DONE ) {
					var response, err;
					try {
						response = JSON.parse(xhr.responseText);
					} catch (e) {
						err = e;
					}
					callback(err, response);
				}
			}
		};
		window.Pocket = Pocket;
	}

})();<commit_msg>Fixed object syntax.<commit_after>(function() {
	var Pocket,
		POSTrequest;

	var createParams = function(consumer_key, access_token, obj) {
		obj = obj || {};
		obj.consumer_key = consumer_key;
		obj.access_token = access_token;
		return obj;
	};

	var URI = {
			ADD: "https://getpocket.com/v3/add",
			MODIFY: "https://getpocket.com/v3/modify",
			RETRIEVE: "https://getpocket.com/v3/get",
			REQUEST: "https://getpocket.com/v3/oauth/request",
			AUTHORIZE: "https://getpocket.com/v3/oauth/authorize",
			G_AUTHORIZE: "https://getpocket.com/auth/authorize"
		};

	Pocket = function(consumer_key, access_token) {
		this.consumer_key = consumer_key;
		this.access_token = access_token;
	};
	Pocket.prototype = {
		constructor: Pocket,
		/**
		 * Adds an item to pocket.
		 * {@see http://getpocket.com/developer/docs/v3/add}
		 *
		 * @param {!Object} params Object with the details of the item
		 *   to add. And url key-value must exists.
		 * @param {!function(?Error, ?number, ?Object)} callback
		 *   Callback function. It has three arguments. An error, if
		 *   there was one, the status and the item added.
		 */
		add: function(params, callback) {
			POSTrequest(
				URI.ADD,
				createParams(this.consumer_key, this.access_token, params),
				function(err, data) {
					if(err) {
						callback(err);
					} else {
						callback(null, data.status, data.item);
					}
				});
		},
		modify: function(params, callback) {
			POSTrequest(
				URI.MODIFY,
				createParams(this.consumer_key, this.access_token, params),
				function(err, data) {
					if(err) {
						callback(err);
					} else {
						callback(null, data.status, data.item);
					}
				});
		},
		retrieve: function(params, callback) {
			POSTrequest(
				URI.RETRIEVE,
				createParams(this.consumer_key, this.access_token, params),
				function(err, data) {
					if(err) {
						callback(err);
					} else {
						callback(null, data.status, data.list);
					}
				});
		}
	};
	Pocket.auth = {
		request: function(consumer_key, redirect_uri, callback) {
			POSTrequest(
				URI.REQUEST,
				{
					"consumer_key": consumer_key,
					"redirect_uri": redirect_uri
				},
				function(err, data, url) {
					if(err) {
						callback(err);
					}else{
						callback(null, data.code);
					}
				});
		},
		authorize: function(consumer_key, request_token, callback) {
			POSTrequest(
				URI.AUTHORIZE,
				{
					"consumer_key": consumer_key,
					"code": request_token
				},
				function(err, data, url) {
					if(err) {
						callback(err);
					}else{
						callback(null, data.access_token, data.username);
					}
				});
		},
		getAuthorizeURL: function(token, redirectUrl) {
			return URI.G_AUTHORIZE+"?request_token="+token+"&redirect_uri="+redirectUrl;
		}
	};
	/**
	 * State of an item.
	 *
	 * @type {!Object}
	 * @enum
	 */
	Pocket.STATE = {
		ALL: "all",
		UNREAD: "unread",
		ARCHIVE: "archive"
	};
	/**
	 * Detail of item(s).
	 *
	 * @type {!Object}
	 * @enum
	 */
	Pocket.DETAIL_TYPE = {
		SIMPLE: "simple",
		COMPLETE: "complete"
	};
	/**
	 * Sorting of a list.
	 *
	 * @type {!Object}
	 * @enum
	 */
	Pocket.SORT = {
		NEWEST: "newest",
		OLDEST: "oldest",
		TITLE: "title",
		SITE: "site"
	};
	/**
	 * Type of item.
	 *
	 * @type {!Object}
	 * @enum
	 */
	Pocket.CONTENT_TYPE = {
		ARTICLE: "article",
		VIDEO: "video",
		IMAGE: "image"
	};
	/**
	 * Tag type.
	 *
	 * @type {!Object}
	 * @enum
	 */
	Pocket.TAG = {
		UNTAGGED: "_untagged_"
	};
	/**
	 * Favorite.
	 *
	 * @type {!Object}
	 * @enum
	 */
	Pocket.FAVORITE = {
		YES: 1,
		NO: 0
	};

	if (typeof module !== 'undefined' && typeof module.exports !== 'undefined'){
		POSTrequest = (function() {
			var request = require("http").request,
				urlUtils = require("url");
			return function(url, data, callback) {
				var options = urlUtils.parse(url);
				options.method = "POST";
				options.headers = {
					"Content-Type": "application/json; charset=UTF-8",
					"X-Accept": "application/json"
				};
				var req = request(options, function(res) {
					var data = "";
					res.on("data", function(chunk) {
						data += chunk;
						callback(undefined, chunk);
					});
					res.on("end", function() {
						callback(undefined, JSON.parse(data));
					});
				});
				req.on("error", function(e) {
					callback(err);
				});
				req.write(JSON.stringify(data));
				req.end();
			};
		})();
		module.exports = Pocket;
	} else {
		POSTrequest = function(url, data, callback) {
			var xhr = new XMLHttpRequest();

			xhr.open("POST", url, true);

			xhr.setRequestHeader("Content-Type", "application/json; charset=UTF-8");
			xhr.setRequestHeader("X-Accept", "application/json");

			xhr.send(JSON.stringify(data));

			xhr.onreadystatechange = function() {
				if( xhr.readyState === xhr.DONE ) {
					var response, err;
					try {
						response = JSON.parse(xhr.responseText);
					} catch (e) {
						err = e;
					}
					callback(err, response);
				}
			}
		};
		window.Pocket = Pocket;
	}

})();<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { View, Image, TouchableHighlight } from 'react-native';

export default function Brick (props) {
  // Avoid margins for first element
  const image = (props.onPress) ? _getTouchableUnit(props, props.gutter) : _getImageTag(props, props.gutter);
  const footer = (props.renderFooter) ? props.renderFooter(props.data) : null;
  const header = (props.renderHeader) ? props.renderHeader(props.data) : null;

  return (
    <View key={props.brickKey}>
      {header}
      {image}
      {footer}
    </View>
  );
}

// _getImageTag :: Image, Gutter -> ImageTag
export function _getImageTag (image, gutter = 0) {
  return (
      <Image
        key={image.uri}
        source={{ uri: image.uri }}
        resizeMethod='auto'
        style={{ width: image.width, height: image.height, marginTop: gutter }} />
  );
}

// _getTouchableUnit :: Image, Number -> TouchableTag
export function _getTouchableUnit (image, gutter = 0) {
  return (
      <TouchableHighlight
         key={image.uri}
         onPress={() => image.onPress(image.data)}>
            { _getImageTag(image, gutter) }
      </TouchableHighlight>
  );
}
<commit_msg>Update Brick.js<commit_after>import React, { Component } from 'react';
import { View, Image, TouchableHighlight } from 'react-native';

export default function Brick (props) {
  // Avoid margins for first element
  const image = (props.onPress) ? _getTouchableUnit(props, props.gutter) : _getImageTag(props, props.gutter);
  const footer = (props.renderFooter) ? props.renderFooter(props.data) : null;
  const header = (props.renderHeader) ? props.renderHeader(props.data) : null;

  return (
    <View key={props.brickKey}>
      {header}
      {image}
      {footer}
    </View>
  );
}

// _getImageTag :: Image, Gutter -> ImageTag
export function _getImageTag (image, gutter = 0) {
  return (
      <Image
        key={image.uri}
        source={{ uri: image.uri }}
        resizeMethod='auto'
        style={{ width: image.width, height: image.height, marginTop: gutter, ...image.imageContainerStyle }} />
  );
}

// _getTouchableUnit :: Image, Number -> TouchableTag
export function _getTouchableUnit (image, gutter = 0) {
  return (
      <TouchableHighlight
         key={image.uri}
         onPress={() => image.onPress(image.data)}>
            { _getImageTag(image, gutter) }
      </TouchableHighlight>
  );
}
<|endoftext|>


<commit_before>/* global snoowrap, Highcharts */
var REDDIT_APP_ID = 'R7NdC-SvRV45Uw';
var REDIRECT_URI = 'https://timendum.github.io/reddit-commented-thread/';
var REQUIRED_SCOPES = ['read', 'identity'];
var USER_AGENT = 'reddit most commented thread by /u/timendum';
var FORM_CONFIGS = [
    'reddit-url', 'pieComments', 'scatterSubmissions',
    'scatterMaxX', 'scatterMaxY', 'filter-multireddit', 'logaritmic'
];
let COLORS = [
    [51, 102, 204],
    [220, 57, 18],
    [255, 153, 0],
    [16, 150, 24],
    [153, 0, 153],
    [59, 62, 172],
    [0, 153, 198],
    [221, 68, 119],
    [102, 170, 0],
    [184, 46, 46],
];
let SUBREDDITS_COLOR = {};

function arrayToColor(color, fading) {
    return '#' + color.map(function (obj) {
        return Math.round(obj + (255 - obj) * fading).toString(16).toUpperCase().padStart(2, '0');
    }).join('');
}

var cachedReddit = null;

(function (H) {
    // Pass error messages
    H.Axis.prototype.allowNegativeLog = true;
    H.Axis.prototype.log2lin = function (num) {
        var isNegative = num < 0,
            adjustedNum = Math.abs(num),
            result;
        if (adjustedNum < 2) {
            adjustedNum += (2 - adjustedNum) / 2;
        }
        result = Math.log(adjustedNum) / Math.LN10;
        return isNegative ? -result : result;
    };
    H.Axis.prototype.lin2log = function (num) {
        var isNegative = num < 0,
            absNum = Math.abs(num),
            result = Math.pow(10, absNum);
        if (result < 2) {
            result = (2 * (result - 1)) / (2 - 1);
        }
        return isNegative ? -result : result;
    };
}(Highcharts));

function setError(message) {
    var div = document.getElementById('url-error-message');
    if (message) {
        div.innerHTML = message;
        div.classList.remove('hidden');
    } else {
        div.classList.add('hidden');
    }
}

function getAuthRedirect() {
    return snoowrap.getAuthUrl({
        clientId: REDDIT_APP_ID,
        scope: REQUIRED_SCOPES,
        redirectUri: REDIRECT_URI,
        state: saveForm(),
        permanent: false
    });
}

function parseUrl(url) {
    var matches = url.match(/(?:^:https?:\/\/)?(?:\w*\.)?reddit\.com\/((?:r\/\w{1,21})|(?:me\/m\/\w{1,21})|(?:u(?:ser)?\/\w{1,21}\/m\/\w{1,21}))\//);
    if (matches) {
        return matches[1];
    }
    throw new TypeError('Please enter the URL of a subreddit or a multireddit, ending with /.');
}

function selectPieHandler(e) {
    if (e.point.subreddit && e.point.linkId) {
        var subreddit = e.point.subreddit;
        var id = e.point.linkId.replace(/^t3_/, '');
        var url = `https://www.reddit.com/r/${subreddit}/comments/${id}/_/`;
        window.open(url, id);
    } else {
        var url = document.getElementById('reddit-url').value;
        window.open(url, url);
    }
}

function createPieDataTimeDependant(comments) {
    var chartData = [];
    var now = (new Date()).getTime() / 1000;
    for (var comment of comments) {
        var updated = false;
        var deltaTime = (now - comment.created_utc) / 60 / 60;
        var value = 1 / (Math.pow(deltaTime, 2) / 5 + 1);
        for (var cd of chartData) {
            if (cd.name === comment.link_title) {
                cd.y += value;
                cd.numComments += 1;
                updated = true;
                break;
            }
        }
        if (!updated) {
            chartData.push({
                name: comment.link_title,
                y: value,
                numComments: 1,
                subreddit: comment.subreddit.display_name,
                linkId: comment.link_id
            });
        }
    }
    chartData.sort(function (a, b) {
        return b.y - a.y;
    });
    window.pieData = chartData;
    return addCustomLabelPieData(chartData);
}

function addCustomLabelPieData(chartData) {
    function sumReduce(accumulator, currentValue) {
        return accumulator + currentValue;
    }
    const total = chartData.map(el => el.y).reduce(sumReduce);
    const tooSmallIndex = chartData.findIndex(function (element) {
        return element.y < total * 0.02;
    });
    let smallElems = chartData.splice(tooSmallIndex);
    chartData.push({
        name: 'Others',
        color: '#B5B5B5',
        y: smallElems.map(el => el.y).reduce(sumReduce),
        numComments: smallElems.map(el => el.numComments).reduce(sumReduce),
        subreddit: null,
        linkId: null
    });
    chartData.forEach(function (elem, index) {
        if (elem.y < total * 0.03) {
            elem.dataLabels = {enabled: false};
        }
    });
    return chartData;
}

function plotPie(comments) {
    try {
        Highcharts.chart('pie_div', {
            chart: {
                plotBackgroundColor: null,
                plotBorderWidth: null,
                plotShadow: false,
                type: 'pie'
            },
            title: {
                text: 'Topic hotness by comment number'
            },
            plotOptions: {
                pie: {
                    events: {
                        click: selectPieHandler
                    },
                    cursor: 'pointer',
                    dataLabels: {
                        enabled: true,
                        distance: -50,
                        format: '{percentage:.1f}%',
                        style: {
                            fontWeight: 'normal'
                        }
                    }
                }
            },
            tooltip: {
                pointFormat: 'Comments: {point.numComments} ({point.percentage:.1f}%)'
            },
            series: [{data: createPieDataTimeDependant(comments)}],
            credits: false
        });
    } catch (e) {
        console.log(e);
        setError('Error during the creation of the pie chart');
    }
}

function selectHandler(e) {
    console.log(e);
    if (e.point.threadId && e.point.permalink) {
        const id = e.point.threadId;
        const permalink = e.point.permalink;
        const url = `https://www.reddit.com${permalink}`;
        window.open(url, id);
    }
}

function filterThreadsData(threads) {
    let distance = {};
    // init subreddits
    for (let thread of threads) {
        distance[thread.subreddit_id] = [];
    }
    if (Object.keys(distance).length === 1) {
        return threads;
    }
    // calculate real distance
    for (let thread of threads) {
        distance[thread.subreddit_id].push(Math.pow(thread.score, 2) +
                                           Math.pow(thread.num_comments, 2));
    }
    let dlimit = {};
    for (let subredditId of Object.keys(distance)) {
        let values = distance[subredditId];
        dlimit[subredditId] = values.sort((a, b) => a - b)[Math.floor(values.length / 2)] * 0.75;
    }
    let fthreads = [];
    for (let thread of threads) {
        if (dlimit[thread.subreddit_id] < Math.pow(thread.score, 2) +
                                          Math.pow(thread.num_comments, 2)) {
            fthreads.push(thread);
        }
    }
    return fthreads;
}

function createPointsData(threads, maxValues, minValues) {
    let chartData = {};
    let now = (new Date()).getTime() / 1000;
    for (let thread of threads) {
        const subreddit = thread.subreddit.display_name;
        let deltaTime = (now - thread.created_utc) / 60 / 60;
        // a value beween 1 and 0.2, to fade the point color
        let fading = Math.max(1 - 1 / (Math.pow(deltaTime, 2) / 100 + 1), 0.3);
        let subredditColor = SUBREDDITS_COLOR[subreddit];
        if (!subredditColor) {
            subredditColor = COLORS.shift();
            SUBREDDITS_COLOR[subreddit] = subredditColor;
            COLORS.push(subredditColor);
        }
        let color = arrayToColor(subredditColor, fading);
        chartData[subreddit] = chartData[subreddit] || [];
        chartData[subreddit] .push({
            x: Math.max(Math.min(thread.score, maxValues[0]), minValues[0]),
            y: Math.max(Math.min(thread.num_comments, maxValues[1]), minValues[1]),
            name: thread.title,
            score: thread.score,
            threadId: thread.id,
            permalink: thread.permalink,
            numComments: thread.num_comments,
            color: color
        });
    }
    return Object.keys(chartData).map(function (key) {
        return {name: key, data: chartData[key], color: arrayToColor(SUBREDDITS_COLOR[key], 0)};
    });
}

function plotPoints(threads) {
    threads.reverse();
    var maxX = document.getElementById('scatterMaxX').value;
    maxX = parseInt(maxX, 10);
    var maxY = document.getElementById('scatterMaxY').value;
    maxY = parseInt(maxY, 10);
    if (document.getElementById('filter-multireddit').checked) {
        threads = filterThreadsData(threads);
    }
    let axisType = "linear";
	let minValues = [-Infinity, -Infinity];
    if (document.getElementById('logaritmic').checked) {
        axisType = "logarithmic";
		minValues = [1, 1];
    }
    var chartData = createPointsData(threads, [maxX || Infinity, maxY || Infinity], minValues);
    Highcharts.chart('points_div', {
        chart: {
            type: 'scatter',
            zoomType: 'xy'
        },
        title: {
            text: ''
        },
        xAxis: {
            title: {
                enabled: true,
                text: 'Score'
            },
            type: axisType,
            minRange: 0,
            endOnTick: true,
            showLastLabel: true
        },
        yAxis: {
            title: {
                enabled: true,
                text: 'Comments'
            },
            type: axisType,
            minRange: 0,
            startOnTick: true,
            endOnTick: true,
            showLastLabel: true
        },
        plotOptions: {
            scatter: {
                events: {
                    click: selectHandler
                },
                cursor: 'pointer',
                tooltip: {
                    pointFormat: '{point.name}
' +
                                 'Score {point.x} - Comments: {point.y}'
                }
            }
        },
        series: chartData,
        credits: false
    });
}

function createChart(url) {
    var accessToken = getAccessToken();
    var button = document.getElementById('submit');
    var pieLimit = parseInt(document.getElementById('pieComments').value, 10);
    var scatterLimit = parseInt(document.getElementById('scatterSubmissions').value, 10);
    button.textContent = button.dataset.loadingText;
    if (accessToken) {
        button.setAttribute('disabled', 'disabled');
        accessToken.then(function (token) {
            return getReddit(token);
        }).then(function (reddit) {
            return reddit._getListing({uri: url + '/comments', qs: {limit: pieLimit}});
        }).then(
            plotPie
        ).catch(function (e) {
            console.log(e);
            setError('Error reading data from Reddit.');
            sessionStorage.removeItem('accessToken');
            sessionStorage.removeItem('accessTokenDate');
            /* eslint-disable no-native-reassign */
            location = getAuthRedirect();
            return null;
        }).then(function () {
            // get cached istance
            return getReddit(null);
        })
        .then(function (reddit) {
            return reddit._getListing({uri: url + '/new', qs: {limit: scatterLimit}});
        }).catch(function (e) {
            console.log(e);
            setError('Error reading data from Reddit.');
            sessionStorage.removeItem('accessToken');
            sessionStorage.removeItem('accessTokenDate');
            /* eslint-disable no-native-reassign */
            location = getAuthRedirect();
            return null;
        }).then(
            plotPoints
        ).catch(console.log)
        .then(function () {
            button.removeAttribute('disabled');
            button.textContent = button.dataset.originalText;
        });
    } else {
        /* eslint-disable no-native-reassign */
        location = getAuthRedirect();
    }
    return false;
}

function getReddit(accessToken) {
    if (cachedReddit) {
        return cachedReddit;
    }
    /* eslint-disable new-cap */
    cachedReddit = new snoowrap({userAgent: USER_AGENT, accessToken: accessToken});
    return cachedReddit;
}

function onSubmit(evt) {
    if (evt) {
        evt.preventDefault();
    }
    var url = document.getElementById('reddit-url').value;
    var parsedUrl;
    try {
        parsedUrl = parseUrl(url);
    } catch (err) {
        setError(err.message);
        throw err;
    }
    setError(null);
    sessionStorage.formConfig = saveForm();
    createChart(parsedUrl);
}

function getAccessToken() {
    if (sessionStorage.accessToken) {
        // no authCode but accessToken
        try {
            var accessTokenDate = sessionStorage.accessTokenDate;
            if (accessTokenDate) {
                if (new Date() - Date.parse(accessTokenDate) < 1000 * 60 * 60) {
                    return Promise.resolve(sessionStorage.accessToken);
                }
                sessionStorage.removeItem('accessToken');
                sessionStorage.removeItem('accessTokenDate');
            }
        } catch (e) {
            // ok
        }
    }
    var authCode = new URL(window.location.href).searchParams.get('code');
    if (authCode) {
        // remove old accessToken
        sessionStorage.removeItem('accessToken');
        sessionStorage.removeItem('accessTokenDate');
        return snoowrap.fromAuthCode({
            code: authCode,
            userAgent: USER_AGENT,
            clientId: REDDIT_APP_ID,
            redirectUri: REDIRECT_URI
        }).then(function (reddit) {
            cachedReddit = reddit;
            sessionStorage.accessToken = reddit.accessToken;
            sessionStorage.accessTokenDate = new Date();
            return reddit.accessToken;
        }, function () {
            /* eslint-disable no-native-reassign */
            location = getAuthRedirect();
        });
    }

    return null;
}

function onAdvanced() {
    var hidden = !this.checked;
    for (let elem of document.getElementsByClassName('form-advanced')) {
        elem.hidden = hidden;
    }
}

function saveForm() {
    var config = {};
    for (let id of FORM_CONFIGS) {
        if (document.getElementById(id).type === "checkbox") {
            config[id] = document.getElementById(id).checked;
        } else {
            config[id] = document.getElementById(id).value;
        }
    }
    return JSON.stringify(config);
}

function restoreForm(formConfig) {
    if (!formConfig) {
        return;
    }
    var config = JSON.parse(formConfig);
    for (let id of FORM_CONFIGS) {
        let value = config[id];
        if (value !== undefined) {
            if (document.getElementById(id).type === "checkbox") {
                document.getElementById(id).checked = value;
            } else {
                document.getElementById(id).value = value;
            }
        }
    }
}

document.addEventListener('DOMContentLoaded', function () {
    restoreForm(sessionStorage.formConfig);
    var authState = new URL(window.location.href).searchParams.get('state');
    if (authState) {
        restoreForm(authState);
        var authCode = new URL(window.location.href).searchParams.get('code');
        if (authCode) {
            onSubmit();
        }
    }
    document.getElementById('main-form').addEventListener('submit', onSubmit);
    document.getElementById('advanced-form').addEventListener('change', onAdvanced);
    document.getElementById('advanced-form').checked = false;
});
<commit_msg>Tweak fading<commit_after>/* global snoowrap, Highcharts */
var REDDIT_APP_ID = 'R7NdC-SvRV45Uw';
var REDIRECT_URI = 'https://timendum.github.io/reddit-commented-thread/';
var REQUIRED_SCOPES = ['read', 'identity'];
var USER_AGENT = 'reddit most commented thread by /u/timendum';
var FORM_CONFIGS = [
    'reddit-url', 'pieComments', 'scatterSubmissions',
    'scatterMaxX', 'scatterMaxY', 'filter-multireddit', 'logaritmic'
];
let COLORS = [
    [51, 102, 204],
    [220, 57, 18],
    [255, 153, 0],
    [16, 150, 24],
    [153, 0, 153],
    [59, 62, 172],
    [0, 153, 198],
    [221, 68, 119],
    [102, 170, 0],
    [184, 46, 46],
];
let SUBREDDITS_COLOR = {};

function arrayToColor(color, fading) {
    return '#' + color.map(function (obj) {
        return Math.round(obj + (255 - obj) * fading).toString(16).toUpperCase().padStart(2, '0');
    }).join('');
}

var cachedReddit = null;

(function (H) {
    // Pass error messages
    H.Axis.prototype.allowNegativeLog = true;
    H.Axis.prototype.log2lin = function (num) {
        var isNegative = num < 0,
            adjustedNum = Math.abs(num),
            result;
        if (adjustedNum < 2) {
            adjustedNum += (2 - adjustedNum) / 2;
        }
        result = Math.log(adjustedNum) / Math.LN10;
        return isNegative ? -result : result;
    };
    H.Axis.prototype.lin2log = function (num) {
        var isNegative = num < 0,
            absNum = Math.abs(num),
            result = Math.pow(10, absNum);
        if (result < 2) {
            result = (2 * (result - 1)) / (2 - 1);
        }
        return isNegative ? -result : result;
    };
}(Highcharts));

function setError(message) {
    var div = document.getElementById('url-error-message');
    if (message) {
        div.innerHTML = message;
        div.classList.remove('hidden');
    } else {
        div.classList.add('hidden');
    }
}

function getAuthRedirect() {
    return snoowrap.getAuthUrl({
        clientId: REDDIT_APP_ID,
        scope: REQUIRED_SCOPES,
        redirectUri: REDIRECT_URI,
        state: saveForm(),
        permanent: false
    });
}

function parseUrl(url) {
    var matches = url.match(/(?:^:https?:\/\/)?(?:\w*\.)?reddit\.com\/((?:r\/\w{1,21})|(?:me\/m\/\w{1,21})|(?:u(?:ser)?\/\w{1,21}\/m\/\w{1,21}))\//);
    if (matches) {
        return matches[1];
    }
    throw new TypeError('Please enter the URL of a subreddit or a multireddit, ending with /.');
}

function selectPieHandler(e) {
    if (e.point.subreddit && e.point.linkId) {
        var subreddit = e.point.subreddit;
        var id = e.point.linkId.replace(/^t3_/, '');
        var url = `https://www.reddit.com/r/${subreddit}/comments/${id}/_/`;
        window.open(url, id);
    } else {
        var url = document.getElementById('reddit-url').value;
        window.open(url, url);
    }
}

function createPieDataTimeDependant(comments) {
    var chartData = [];
    var now = (new Date()).getTime() / 1000;
    for (var comment of comments) {
        var updated = false;
        var deltaTime = (now - comment.created_utc) / 60 / 60;
        var value = 1 / (Math.pow(deltaTime, 2) / 5 + 1);
        for (var cd of chartData) {
            if (cd.name === comment.link_title) {
                cd.y += value;
                cd.numComments += 1;
                updated = true;
                break;
            }
        }
        if (!updated) {
            chartData.push({
                name: comment.link_title,
                y: value,
                numComments: 1,
                subreddit: comment.subreddit.display_name,
                linkId: comment.link_id
            });
        }
    }
    chartData.sort(function (a, b) {
        return b.y - a.y;
    });
    window.pieData = chartData;
    return addCustomLabelPieData(chartData);
}

function addCustomLabelPieData(chartData) {
    function sumReduce(accumulator, currentValue) {
        return accumulator + currentValue;
    }
    const total = chartData.map(el => el.y).reduce(sumReduce);
    const tooSmallIndex = chartData.findIndex(function (element) {
        return element.y < total * 0.02;
    });
    let smallElems = chartData.splice(tooSmallIndex);
    chartData.push({
        name: 'Others',
        color: '#B5B5B5',
        y: smallElems.map(el => el.y).reduce(sumReduce),
        numComments: smallElems.map(el => el.numComments).reduce(sumReduce),
        subreddit: null,
        linkId: null
    });
    chartData.forEach(function (elem, index) {
        if (elem.y < total * 0.03) {
            elem.dataLabels = {enabled: false};
        }
    });
    return chartData;
}

function plotPie(comments) {
    try {
        Highcharts.chart('pie_div', {
            chart: {
                plotBackgroundColor: null,
                plotBorderWidth: null,
                plotShadow: false,
                type: 'pie'
            },
            title: {
                text: 'Topic hotness by comment number'
            },
            plotOptions: {
                pie: {
                    events: {
                        click: selectPieHandler
                    },
                    cursor: 'pointer',
                    dataLabels: {
                        enabled: true,
                        distance: -50,
                        format: '{percentage:.1f}%',
                        style: {
                            fontWeight: 'normal'
                        }
                    }
                }
            },
            tooltip: {
                pointFormat: 'Comments: {point.numComments} ({point.percentage:.1f}%)'
            },
            series: [{data: createPieDataTimeDependant(comments)}],
            credits: false
        });
    } catch (e) {
        console.log(e);
        setError('Error during the creation of the pie chart');
    }
}

function selectHandler(e) {
    console.log(e);
    if (e.point.threadId && e.point.permalink) {
        const id = e.point.threadId;
        const permalink = e.point.permalink;
        const url = `https://www.reddit.com${permalink}`;
        window.open(url, id);
    }
}

function filterThreadsData(threads) {
    let distance = {};
    // init subreddits
    for (let thread of threads) {
        distance[thread.subreddit_id] = [];
    }
    if (Object.keys(distance).length === 1) {
        return threads;
    }
    // calculate real distance
    for (let thread of threads) {
        distance[thread.subreddit_id].push(Math.pow(thread.score, 2) +
                                           Math.pow(thread.num_comments, 2));
    }
    let dlimit = {};
    for (let subredditId of Object.keys(distance)) {
        let values = distance[subredditId];
        dlimit[subredditId] = values.sort((a, b) => a - b)[Math.floor(values.length / 2)] * 0.75;
    }
    let fthreads = [];
    for (let thread of threads) {
        if (dlimit[thread.subreddit_id] < Math.pow(thread.score, 2) +
                                          Math.pow(thread.num_comments, 2)) {
            fthreads.push(thread);
        }
    }
    return fthreads;
}

function createPointsData(threads, maxValues, minValues) {
    let chartData = {};
    let now = (new Date()).getTime() / 1000;
    for (let thread of threads) {
        const subreddit = thread.subreddit.display_name;
        let deltaTime = (now - thread.created_utc) / 60 / 60;
        // a value beween 1 and 0.3, to fade the point color
	if (document.getElementById('logaritmic').checked) {
		let fading = Math.max(1 - 1 / (Math.pow(deltaTime, 2) / 100 + 1), 0.3);
	} else {
		let fading = Math.max(1 - 1 / (deltaTime / 10 + 1), 0.3);
	}
        let subredditColor = SUBREDDITS_COLOR[subreddit];
        if (!subredditColor) {
            subredditColor = COLORS.shift();
            SUBREDDITS_COLOR[subreddit] = subredditColor;
            COLORS.push(subredditColor);
        }
        let color = arrayToColor(subredditColor, fading);
        chartData[subreddit] = chartData[subreddit] || [];
        chartData[subreddit] .push({
            x: Math.max(Math.min(thread.score, maxValues[0]), minValues[0]),
            y: Math.max(Math.min(thread.num_comments, maxValues[1]), minValues[1]),
            name: thread.title,
            score: thread.score,
            threadId: thread.id,
            permalink: thread.permalink,
            numComments: thread.num_comments,
            color: color
        });
    }
    return Object.keys(chartData).map(function (key) {
        return {name: key, data: chartData[key], color: arrayToColor(SUBREDDITS_COLOR[key], 0)};
    });
}

function plotPoints(threads) {
    threads.reverse();
    var maxX = document.getElementById('scatterMaxX').value;
    maxX = parseInt(maxX, 10);
    var maxY = document.getElementById('scatterMaxY').value;
    maxY = parseInt(maxY, 10);
    if (document.getElementById('filter-multireddit').checked) {
        threads = filterThreadsData(threads);
    }
    let axisType = "linear";
	let minValues = [-Infinity, -Infinity];
    if (document.getElementById('logaritmic').checked) {
        axisType = "logarithmic";
		minValues = [1, 1];
    }
    var chartData = createPointsData(threads, [maxX || Infinity, maxY || Infinity], minValues);
    Highcharts.chart('points_div', {
        chart: {
            type: 'scatter',
            zoomType: 'xy'
        },
        title: {
            text: ''
        },
        xAxis: {
            title: {
                enabled: true,
                text: 'Score'
            },
            type: axisType,
            minRange: 0,
            endOnTick: true,
            showLastLabel: true
        },
        yAxis: {
            title: {
                enabled: true,
                text: 'Comments'
            },
            type: axisType,
            minRange: 0,
            startOnTick: true,
            endOnTick: true,
            showLastLabel: true
        },
        plotOptions: {
            scatter: {
                events: {
                    click: selectHandler
                },
                cursor: 'pointer',
                tooltip: {
                    pointFormat: '{point.name}
' +
                                 'Score {point.x} - Comments: {point.y}'
                }
            }
        },
        series: chartData,
        credits: false
    });
}

function createChart(url) {
    var accessToken = getAccessToken();
    var button = document.getElementById('submit');
    var pieLimit = parseInt(document.getElementById('pieComments').value, 10);
    var scatterLimit = parseInt(document.getElementById('scatterSubmissions').value, 10);
    button.textContent = button.dataset.loadingText;
    if (accessToken) {
        button.setAttribute('disabled', 'disabled');
        accessToken.then(function (token) {
            return getReddit(token);
        }).then(function (reddit) {
            return reddit._getListing({uri: url + '/comments', qs: {limit: pieLimit}});
        }).then(
            plotPie
        ).catch(function (e) {
            console.log(e);
            setError('Error reading data from Reddit.');
            sessionStorage.removeItem('accessToken');
            sessionStorage.removeItem('accessTokenDate');
            /* eslint-disable no-native-reassign */
            location = getAuthRedirect();
            return null;
        }).then(function () {
            // get cached istance
            return getReddit(null);
        })
        .then(function (reddit) {
            return reddit._getListing({uri: url + '/new', qs: {limit: scatterLimit}});
        }).catch(function (e) {
            console.log(e);
            setError('Error reading data from Reddit.');
            sessionStorage.removeItem('accessToken');
            sessionStorage.removeItem('accessTokenDate');
            /* eslint-disable no-native-reassign */
            location = getAuthRedirect();
            return null;
        }).then(
            plotPoints
        ).catch(console.log)
        .then(function () {
            button.removeAttribute('disabled');
            button.textContent = button.dataset.originalText;
        });
    } else {
        /* eslint-disable no-native-reassign */
        location = getAuthRedirect();
    }
    return false;
}

function getReddit(accessToken) {
    if (cachedReddit) {
        return cachedReddit;
    }
    /* eslint-disable new-cap */
    cachedReddit = new snoowrap({userAgent: USER_AGENT, accessToken: accessToken});
    return cachedReddit;
}

function onSubmit(evt) {
    if (evt) {
        evt.preventDefault();
    }
    var url = document.getElementById('reddit-url').value;
    var parsedUrl;
    try {
        parsedUrl = parseUrl(url);
    } catch (err) {
        setError(err.message);
        throw err;
    }
    setError(null);
    sessionStorage.formConfig = saveForm();
    createChart(parsedUrl);
}

function getAccessToken() {
    if (sessionStorage.accessToken) {
        // no authCode but accessToken
        try {
            var accessTokenDate = sessionStorage.accessTokenDate;
            if (accessTokenDate) {
                if (new Date() - Date.parse(accessTokenDate) < 1000 * 60 * 60) {
                    return Promise.resolve(sessionStorage.accessToken);
                }
                sessionStorage.removeItem('accessToken');
                sessionStorage.removeItem('accessTokenDate');
            }
        } catch (e) {
            // ok
        }
    }
    var authCode = new URL(window.location.href).searchParams.get('code');
    if (authCode) {
        // remove old accessToken
        sessionStorage.removeItem('accessToken');
        sessionStorage.removeItem('accessTokenDate');
        return snoowrap.fromAuthCode({
            code: authCode,
            userAgent: USER_AGENT,
            clientId: REDDIT_APP_ID,
            redirectUri: REDIRECT_URI
        }).then(function (reddit) {
            cachedReddit = reddit;
            sessionStorage.accessToken = reddit.accessToken;
            sessionStorage.accessTokenDate = new Date();
            return reddit.accessToken;
        }, function () {
            /* eslint-disable no-native-reassign */
            location = getAuthRedirect();
        });
    }

    return null;
}

function onAdvanced() {
    var hidden = !this.checked;
    for (let elem of document.getElementsByClassName('form-advanced')) {
        elem.hidden = hidden;
    }
}

function saveForm() {
    var config = {};
    for (let id of FORM_CONFIGS) {
        if (document.getElementById(id).type === "checkbox") {
            config[id] = document.getElementById(id).checked;
        } else {
            config[id] = document.getElementById(id).value;
        }
    }
    return JSON.stringify(config);
}

function restoreForm(formConfig) {
    if (!formConfig) {
        return;
    }
    var config = JSON.parse(formConfig);
    for (let id of FORM_CONFIGS) {
        let value = config[id];
        if (value !== undefined) {
            if (document.getElementById(id).type === "checkbox") {
                document.getElementById(id).checked = value;
            } else {
                document.getElementById(id).value = value;
            }
        }
    }
}

document.addEventListener('DOMContentLoaded', function () {
    restoreForm(sessionStorage.formConfig);
    var authState = new URL(window.location.href).searchParams.get('state');
    if (authState) {
        restoreForm(authState);
        var authCode = new URL(window.location.href).searchParams.get('code');
        if (authCode) {
            onSubmit();
        }
    }
    document.getElementById('main-form').addEventListener('submit', onSubmit);
    document.getElementById('advanced-form').addEventListener('change', onAdvanced);
    document.getElementById('advanced-form').checked = false;
});
<|endoftext|>