/**
 * The Searchbox module.
 *
 * @return searchbox class (extends Backbone.View).
 */
define([
  'underscore',
  'backbone',
  'handlebars',
  'map/views/Widget',
  'map/presenters/SearchboxPresenter',
  'text!map/templates/searchbox.handlebars'
], function(_, Backbone, Handlebars, Widget, Presenter, tpl) {

  'use strict';

  var Searchbox = Widget.extend({

    className: 'widget widget-searchbox',

    template: Handlebars.compile(tpl),

    initialize: function() {
      _.bindAll(this, 'setAutocomplete', 'onPlaceSelected');
      this.presenter = new Presenter(this);
      Searchbox.__super__.initialize.apply(this);
      this.setAutocomplete();
    },

    setAutocomplete: function() {
      this.autocomplete = new google.maps.places.Autocomplete(
        this.$el.find('input')[0], {types: ['geocode']});

      google.maps.event.addListener(
        this.autocomplete, 'place_changed', this.onPlaceSelected);
    },

    onPlaceSelected: function() {
      var place = this.autocomplete.getPlace();
      // TODO: When there isn't viewport, and there is location...
      if (place && place.geometry && place.geometry.viewport) {
        this.presenter.fitBounds(place.geometry.viewport);
      }
      ga('send', 'event', 'Map', 'Searchbox', 'Find location');
    }

  });

  return Searchbox;
});

/**
 * The Searchbox module.
 *
 * @return searchbox class (extends Backbone.View).
 */
define([
  'underscore',
  'backbone',
  'handlebars',
  'map/views/Widget',
  'map/presenters/SearchboxPresenter',
  'text!map/templates/searchbox.handlebars'
], function(_, Backbone, Handlebars, Widget, Presenter, tpl) {

  'use strict';

  var Searchbox = Widget.extend({

    className: 'widget widget-searchbox',

    template: Handlebars.compile(tpl),

    initialize: function() {
      _.bindAll(this, 'setAutocomplete', 'onPlaceSelected');
      this.presenter = new Presenter(this);
      Searchbox.__super__.initialize.apply(this);
      this.setAutocomplete();
    },

    setAutocomplete: function() {
      this.autocomplete = new google.maps.places.SearchBox(this.$el.find('input')[0]);
      google.maps.event.addListener(this.autocomplete, 'places_changed', this.onPlaceSelected);
    },

    onPlaceSelected: function() {
      var place = this.autocomplete.getPlaces();
      if (place.length == 1) {
        place = place[0];
        if (place && place.geometry && place.geometry.viewport) {
          this.presenter.fitBounds(place.geometry.viewport);
        }
        // TODO: When there isn't viewport, and there is location...
        if (place && place.geometry && place.geometry.location && !place.geometry.viewport) {
          console.log(place.geometry.location);
          this.presenter.setCenter(place.geometry.location.k,place.geometry.location.B);
        }
      };
      ga('send', 'event', 'Map', 'Searchbox', 'Find location');
    }

  });

  return Searchbox;
});

Change autocomplete plugin to search box plugin, it allows us to use the enter key


'use strict';

module.exports = require('./ServerlessOffline.js');

// Serverless exits with code 1 when a promise rejection is unhandled. Not AWS.
// Users can still use their own unhandledRejection event though.
process.removeAllListeners('unhandledRejection');

Add missing strict mode directive


'use strict';

const url = require('url');

module.exports = function(env, callback) {
  class CNAME extends env.plugins.Page {
    getFilename() {
      return 'CNAME';
    }

    getView() {
      return (env, locals, contents, templates, callback) => {
        if (locals.url) {
          callback(null, new Buffer(url.parse(locals.url).host));
        } else {
          callback();
        }
      };
    }
  }

  env.registerGenerator('cname', (contents, callback) => {
    callback(null, {'CNAME': new CNAME()});
  });

  callback();
};

'use strict';

const url = require('url');

module.exports = function(env, callback) {
  class CNAME extends env.plugins.Page {
    getFilename() {
      return 'CNAME';
    }

    getView() {
      return (env, locals, contents, templates, callback) => {
        if (locals.url) {
          callback(null, new Buffer(url.parse(locals.url).host));
        } else {
          callback(new Error('locals.url must be defined.'));
        }
      };
    }
  }

  env.registerGenerator('cname', (contents, callback) => {
    callback(null, {'CNAME': new CNAME()});
  });

  callback();
};

Raise error when plugin is not configured


export Icon from './components/base/icons/Icon';

export Alert from './components/base/containers/Alert';
export Drawer from './components/base/containers/Drawer';
export Fieldset from './components/base/containers/Fieldset';
export FormGroup from './components/base/containers/FormGroup';
export Tooltip from './components/base/containers/Tooltip';
export PopoverLink from './components/base/containers/PopoverLink';

export Textbox from './components/controls/textbox/Textbox';
export Button from './components/controls/buttons/Button';
export RadioGroup from './components/controls/radio-buttons/RadioGroup';
export Dropdown from './components/controls/dropdown/Dropdown';
export Datepicker from './components/controls/datepicker/DatePicker';

export Icon from './components/base/icons/Icon';

export Alert from './components/base/containers/Alert';
export Drawer from './components/base/containers/Drawer';
export Fieldset from './components/base/containers/Fieldset';
export FormGroup from './components/base/containers/FormGroup';
export Tooltip from './components/base/containers/Tooltip';
export PopoverLink from './components/base/containers/PopoverLink';

export Textbox from './components/controls/textbox/Textbox';
export Button from './components/controls/buttons/Button';
export RadioGroup from './components/controls/radio-buttons/RadioGroup';
export Dropdown from './components/controls/dropdown/Dropdown';
export DatePicker from './components/controls/datepicker/DatePicker';

Change export from Datepicker to DatePicker


import React from 'react';
import ReactDOM from 'react-dom';
import { AutoSizer } from 'react-virtualized';

import Kanban from './Kanban';

import Perf from 'react-addons-perf';

import 'react-virtualized/styles.css';
import './index.css';

window.Perf = Perf;

ReactDOM.render(
  <div className='KanbanWrapper'>
    <AutoSizer>
      {({ width, height }) => <Kanban width={width} height={height} />}
    </AutoSizer>
  </div>,
  document.getElementById('root')
);

import React from 'react';
import ReactDOM from 'react-dom';
import { AutoSizer } from 'react-virtualized';
import Perf from 'react-addons-perf';

import Kanban from './Kanban';

import { generateLists } from './utils/generate_lists';

import 'react-virtualized/styles.css';
import './index.css';

window.Perf = Perf;

const lists = generateLists(5, 20);

ReactDOM.render(
  <div className='KanbanWrapper'>
    <AutoSizer>
      {({ width, height }) => {
        return (
          <Kanban
            lists={lists}
            width={width}
            height={height}
          />
        );
      }}
    </AutoSizer>
  </div>,
  document.getElementById('root')
);

Move lists generation outside Kanban


import { GRAPHQL_URL } from 'constants';

import React from 'react';
import ReactDOM from 'react-dom';

import { Router, browserHistory } from 'react-router';
import { syncHistoryWithStore } from 'react-router-redux';
import withScroll from 'scroll-behavior';

import ApolloClient, { createNetworkInterface } from 'apollo-client';
// import { Provider } from 'react-redux';
import { ApolloProvider } from 'react-apollo';

import createAppStore from './store';
import getRoutes from './routes';
import { checkLogin } from './actions';

const networkInterface = createNetworkInterface(GRAPHQL_URL);

const client = new ApolloClient({
  networkInterface,
});

const scrollBrowserHistory = withScroll(browserHistory);
const store = createAppStore(scrollBrowserHistory, client);
const history = syncHistoryWithStore(scrollBrowserHistory, store);

checkLogin(store.dispatch);

const rootEl = document.getElementById('app');

ReactDOM.render(
  <ApolloProvider store={store} client={client}>
    <Router history={history}>{getRoutes(store)}</Router>
  </ApolloProvider>,
  rootEl
);

import { GRAPHQL_URL } from 'constants';

import React from 'react';
import ReactDOM from 'react-dom';

import { Router, browserHistory } from 'react-router';
import { syncHistoryWithStore } from 'react-router-redux';
import withScroll from 'scroll-behavior';

import ApolloClient, { createNetworkInterface } from 'apollo-client';
// import { Provider } from 'react-redux';
import { ApolloProvider } from 'react-apollo';

import createAppStore from './store';
import getRoutes from './routes';
import { checkLogin } from './actions';

const networkInterface = createNetworkInterface(GRAPHQL_URL);

networkInterface.use([{
  /* eslint-disable no-param-reassign */
  applyMiddleware(req, next) {
    if (!req.options.headers) {
      req.options.headers = {}; // Create the header object if needed.
    }
    // get the authentication token from local storage if it exists
    const idToken = localStorage.getItem('idToken') || null;
    if (idToken) {
      req.options.headers.authorization = `Bearer '${idToken}`;
    }
    next();
  },
  /* eslint-enable no-param-reassign */
}]);

const client = new ApolloClient({
  networkInterface,
});

const scrollBrowserHistory = withScroll(browserHistory);
const store = createAppStore(scrollBrowserHistory, client);
const history = syncHistoryWithStore(scrollBrowserHistory, store);

checkLogin(store.dispatch);

const rootEl = document.getElementById('app');

ReactDOM.render(
  <ApolloProvider store={store} client={client}>
    <Router history={history}>{getRoutes(store)}</Router>
  </ApolloProvider>,
  rootEl
);

Add idToken to graphql requests


import React, { Component, PropTypes } from 'react';
import SVGInjector from 'svg-injector';

export default class ReactSVG extends Component {

  static defaultProps = {
    evalScripts: 'never',
    callback: () => {}
  }

  static propTypes = {
    path: PropTypes.string.isRequired,
    className: PropTypes.string,
    evalScripts: PropTypes.oneOf(['always', 'once', 'never']),
    fallbackPath: PropTypes.string,
    callback: PropTypes.func
  }

  render() {

    const {
      className,
      path,
      fallbackPath
    } = this.props;

    return (
      <img
        className={className}
        data-src={path}
        data-fallback={fallbackPath}
        ref={(img) => this._img = img}
      />
    );

  }

  componentDidMount() {
    this.updateSVG();
  }

  componentDidUpdate() {
    this.updateSVG();
  }

  updateSVG() {

    const {
      evalScripts,
      callback: each
    } = this.props;

    SVGInjector(this._img, {
      evalScripts,
      each
    });

  }

}

import React, { PureComponent, PropTypes } from 'react'
import SVGInjector from 'svg-injector'

export default class ReactSVG extends PureComponent {

  static defaultProps = {
    callback: () => {},
    className: '',
    evalScripts: 'once'
  }

  static propTypes = {
    callback: PropTypes.func,
    className: PropTypes.string,
    evalScripts: PropTypes.oneOf([ 'always', 'once', 'never' ]),
    srcPath: PropTypes.string.isRequired
  }

  injectSVG() {
    const {
      evalScripts,
      callback: each
    } = this.props

    if (this._img) {
      SVGInjector(this._img, {
        evalScripts,
        each
      })
    }
  }

  componentDidMount() {
    this.injectSVG()
  }

  componentDidUpdate() {
    this.injectSVG()
  }

  render() {
    const {
      className,
      srcPath
    } = this.props

    return (
      <img
        ref={img => this._img = img}
        className={className}
        data-src={srcPath}
      />
    )

  }

}

Make pure, ditch fallback png option


import {Video} from '@thiagopnts/kaleidoscope';
import {ContainerPlugin, Mediator, Events} from 'clappr';

export default class Video360 extends ContainerPlugin {
  constructor(container) {
    super(container);
    Mediator.on(`${this.options.playerId}:${Events.PLAYER_RESIZE}`, this.updateSize, this);
    let {height, width, autoplay} = container.options;
    container.playback.el.setAttribute('crossorigin', 'anonymous');
    container.el.style.touchAction = "none";
    container.el.addEventListener("touchmove", function(event) {
      event.preventDefault();
    }, false);
    this.viewer = new Video({height: isNaN(height) ? 300 : height, width: isNaN(width) ? 400 : width, container: this.container.el, source: this.container.playback.el});
    this.viewer.render();
  }

  get name() {
    return 'Video360';
  }

  updateSize() {
    setTimeout(() =>
      this.viewer.setSize({height: this.container.$el.height(), width: this.container.$el.width()})
    , 250)
  }
}

import {Video} from '@thiagopnts/kaleidoscope';
import {ContainerPlugin, Mediator, Events} from 'clappr';

export default class Video360 extends ContainerPlugin {
  constructor(container) {
    super(container);
    Mediator.on(`${this.options.playerId}:${Events.PLAYER_RESIZE}`, this.updateSize, this);
    let {height, width, autoplay} = container.options;
    container.playback.el.setAttribute('crossorigin', 'anonymous');
    container.playback.el.setAttribute('preload', 'metadata');
    container.playback.el.setAttribute('playsinline', 'true');
    container.el.style.touchAction = "none";
    container.el.addEventListener("touchmove", function(event) {
      event.preventDefault();
    }, false);
    this.viewer = new Video({height: isNaN(height) ? 300 : height, width: isNaN(width) ? 400 : width, container: this.container.el, source: this.container.playback.el});
    this.viewer.render();
  }

  get name() {
    return 'Video360';
  }

  updateSize() {
    setTimeout(() =>
      this.viewer.setSize({height: this.container.$el.height(), width: this.container.$el.width()})
    , 250)
  }
}

Fix video jumping to fullscreen on iPhones


import {Video} from 'kaleidoscopejs';
import {ContainerPlugin, Mediator, Events} from 'clappr';

export default class Video360 extends ContainerPlugin {
  constructor(container) {
    super(container);
    Mediator.on(`${this.options.playerId}:${Events.PLAYER_RESIZE}`, this.updateSize, this);
    let {height, width, autoplay} = container.options;
    container.playback.el.setAttribute('crossorigin', 'anonymous');
    this.viewer = new Video({height: isNaN(height) ? 300 : height, width: isNaN(width) ? 400 : width, container: this.container.el, source: this.container.playback.el});
    this.viewer.render();
  }

  get name() {
    return 'Video360';
  }

  updateSize() {
    setTimeout(() =>
      this.viewer.renderer.setSize({height: this.container.$el.height(), width: this.container.$el.width()})
    , 250)
  }
}

import {Video} from 'kaleidoscopejs';
import {ContainerPlugin, Mediator, Events} from 'clappr';

export default class Video360 extends ContainerPlugin {
  constructor(container) {
    super(container);
    Mediator.on(`${this.options.playerId}:${Events.PLAYER_RESIZE}`, this.updateSize, this);
    let {height, width, autoplay} = container.options;
    container.playback.el.setAttribute('crossorigin', 'anonymous');
    this.viewer = new Video({height: isNaN(height) ? 300 : height, width: isNaN(width) ? 400 : width, container: this.container.el, source: this.container.playback.el});
    this.viewer.render();
  }

  get name() {
    return 'Video360';
  }

  updateSize() {
    setTimeout(() =>
      this.viewer.setSize({height: this.container.$el.height(), width: this.container.$el.width()})
    , 250)
  }
}

Change setSize() callee so that camera get new size too


import path from 'path';
import fs from 'fs';
import collect from './collect';

let runOnAllFiles;

function hasFlowPragma(source) {
  return source
    .getAllComments()
    .some(comment => /@flow/.test(comment.value));
}

export default {
  rules: {
    'show-errors': function showErrors(context) {
      return {
        Program() {
          const onTheFly = true;
          let collected;

          if (onTheFly) {
            const source = context.getSourceCode();
            const root = process.cwd();

            // Check to see if we should run on every file
            if (runOnAllFiles === undefined) {
              try {
                runOnAllFiles = fs
                  .readFileSync(path.join(root, '.flowconfig'))
                  .toString()
                  .includes('all=true');
              } catch (err) {
                runOnAllFiles = false;
              }
            }

            if (runOnAllFiles === false && !hasFlowPragma(source)) {
              return true;
            }

            collected = collect(source.getText(), root, context.getFilename());
          } else {
            collected = collect();
          }

          const pluginErrors = Array.isArray(collected)
            ? (onTheFly ? collected : collected.filter(
                each => each.path === context.getFilename()
              ))
            : [];

          pluginErrors.forEach(({ loc, message }) => {
            context.report({
              loc,
              message
            });
          });
        }
      };
    }
  }
};

import path from 'path';
import fs from 'fs';
import collect from './collect';

let runOnAllFiles;

function hasFlowPragma(source) {
  return source
    .getAllComments()
    .some(comment => /@flow/.test(comment.value));
}

export default {
  rules: {
    'show-errors': function showErrors(context) {
      return {
        Program() {
          const onTheFly = true;
          let collected;

          if (onTheFly) {
            const source = context.getSourceCode();
            const root = process.cwd();
            const flowDirSetting = context.settings
              && context.settings['flowtype-errors']
              && context.settings['flowtype-errors']['flowDir'];

            const flowDir = fs.existsSync(path.join(root, flowDirSetting, '.flowconfig'))
              ? path.join(root, flowDirSetting)
              : root

            // Check to see if we should run on every file
            if (runOnAllFiles === undefined) {
              try {
                runOnAllFiles = fs
                  .readFileSync(path.join(flowDir, '.flowconfig'))
                  .toString()
                  .includes('all=true');
              } catch (err) {
                runOnAllFiles = false;
              }
            }

            if (runOnAllFiles === false && !hasFlowPragma(source)) {
              return true;
            }

            collected = collect(source.getText(), flowDir, context.getFilename());
          } else {
            collected = collect();
          }

          const pluginErrors = Array.isArray(collected)
            ? (onTheFly ? collected : collected.filter(
                each => each.path === context.getFilename()
              ))
            : [];

          pluginErrors.forEach(({ loc, message }) => {
            context.report({
              loc,
              message
            });
          });
        }
      };
    }
  }
};

Add eslint setting 'flowDir' to specify location of .flowconfig


import '../vendor/gridforms.css'

import React, {cloneElement, Children, PropTypes} from 'react'

let classNames = (...args) => args.filter(cn => !!cn).join(' ')

export let GridForm = ({children, className, component: Component, ...props}) =>
  <Component {...props} className={classNames('grid-form', className)}>
    {children}
  </Component>

GridForm.propTypes = {
  component: PropTypes.any
}

GridForm.defaultProps = {
  component: 'form'
}

export let Fieldset = ({children, legend, ...props}) =>
  <fieldset {...props}>
    {legend && <legend>{legend}</legend>}
    {children}
  </fieldset>

Fieldset.propTypes = {
  name: PropTypes.string.isRequired
}

export let Row = ({children, className}) => {
  let span = 0
  Children.forEach(children, child => span += Number(child.props.span))
  return

    {children}

}

export let Field = ({children, className, span}) =>

   {children}

Field.propTypes = {
  span: PropTypes.oneOfType([
    PropTypes.number,
    PropTypes.string
  ])
}

Field.defaultProps = {
  span: '1'
}

import '../vendor/gridforms.css'

import React, {cloneElement, Children, PropTypes} from 'react'

let classNames = (...args) => args.filter(cn => !!cn).join(' ')

export let GridForm = React.createClass({
  getDefaultProps() {
    return {
      component: 'form'
    }
  },
  render() {
    let {children, className, component: Component, ...props} = this.props
    return <Component {...props} className={classNames('grid-form', className)}>
      {children}
    </Component>
  }
})

export let Fieldset = React.createClass({
  render() {
    let {children, legend, ...props} = this.props
    return <fieldset {...props}>
      {legend && <legend>{legend}</legend>}
      {children}
    </fieldset>
  }
})

export let Row = React.createClass({
  render() {
    let {children, className} = this.props
    let span = 0
    Children.forEach(children, child => span += Number(child.props.span))
    return

      {children}

  }
})

export let Field = React.createClass({
  propTypes: {
    span: PropTypes.oneOfType([
      PropTypes.number,
      PropTypes.string
    ])
  },
  getDefaultProps() {
    return {
      span: 1
    }
  },
  render() {
    let {children, className, span} = this.props
   return

     {children}

  }
})

Use a regular React component impl until hot reloading issues are resolved


import arrayBufferToImage from './arrayBufferToImage.js';
import createImage from './createImage.js';
import { loadImage, configure } from './loadImage.js';
import { external } from './externalModules.js';

export {
  arrayBufferToImage,
  createImage,
  loadImage,
  configure,
  external
};

import arrayBufferToImage from './arrayBufferToImage.js';
import createImage from './createImage.js';
import { loadImage, configure } from './loadImage.js';
import { external } from './externalModules.js';

const cornerstoneWebImageLoader = {
  arrayBufferToImage,
  createImage,
  loadImage,
  configure,
  external
};

export {
  arrayBufferToImage,
  createImage,
  loadImage,
  configure,
  external
};

export default cornerstoneWebImageLoader;

Add a (default) export named cornerstoneWebImageLoader


// Listing of all the infrastructure unit tests
define([
	"./Destroyable",
	"./register",
	"./Widget-attr",
	"./handlebars",
	"./Widget-lifecycle",
	"./Widget-placeAt",
	"./Widget-utility",
	"./HasDropDown",
	"./CssState",
	"./Container",
	"./a11y",
	"./Widget-on",
	"./Stateful",
	"./Selection"
]);

// Listing of all the infrastructure unit tests
define([
	"./Destroyable",
	"./register",
	"./Widget-attr",
	"./handlebars",
	"./Widget-lifecycle",
	"./Widget-placeAt",
	"./Widget-utility",
	"./HasDropDown",
	"./CssState",
	"./Container",
	"./a11y",
	"./Widget-on",
	"./Stateful",
	"./Selection",
	"./StoreMap",
	"./Store"
]);

Store and StoreMap intern tests added


'use strict';

const _ = require('underscore');
const app = require('express')();
const Analytics = require('analytics-node');
const { secrets }  = require('app/config/config');
const { guid } = require('./users');

const segmentKey = process.env.SEGMENT_WRITE_KEY || secrets.segmentKey;
const env = app.get('env');
let analytics;

// Flush events immediately if on dev environment
if (env !== 'production') {
  analytics = new Analytics(segmentKey, { flushAt: 1 });
} else {
  analytics = new Analytics(segmentKey);
}

function generateOpts() {
  return {
    // Log the environment to differentiate events from production vs dev
    context: { environment: env, platform: 'Web' }
  };
}

module.exports = {
  // https://segment.com/docs/sources/server/node/#identify
  identify(user) {
    let opts = generateOpts();
    if (!user) {
      opts.anonymousId = guid();
    } else {
      const { id: userId, email, name, username } = user;
      opts = {
        userId,
        traits: { name, email, username }
      };
    }

    analytics.identify(opts);
  },

  // https://segment.com/docs/sources/server/node/#track
  track(user, event, properties) {
    const { id: userId } = user;
    let opts = _.extend(generateOpts(), { userId, event, properties });

    analytics.track(opts);
  }
};

'use strict';

const _ = require('underscore');
const app = require('express')();
const Analytics = require('analytics-node');
const { secrets }  = require('app/config/config');
const { guid } = require('./users');

const segmentKey = process.env.SEGMENT_WRITE_KEY || secrets.segmentKey;
const env = app.get('env');
let analytics;

// Flush events immediately if on dev environment
if (env !== 'production') {
  analytics = new Analytics(segmentKey, { flushAt: 1 });
} else {
  analytics = new Analytics(segmentKey);
}

function generateOpts() {
  return {
    // Log the environment to differentiate events from production vs dev
    context: { environment: env, platform: 'Web' }
  };
}

module.exports = {
  // https://segment.com/docs/sources/server/node/#identify
  identify(user) {
    let opts = generateOpts();
    if (!user) {
      opts.anonymousId = guid();
    } else {
      const { id: userId, email, name, username } = user;
      opts = {
        userId,
        traits: { name, email, username }
      };
    }

    analytics.identify(opts);
  },

  // https://segment.com/docs/sources/server/node/#track
  track(user, event, properties) {
    const { id: userId } = user;
    let opts = _.extend(generateOpts(), { userId, event, properties });

    analytics.track(opts);
  },

  // https://segment.com/docs/sources/server/node/#page
  page(user, category, name, properties) {
    const { id: userId } = user;
    let opts = _.extend(generateOpts(), { userId, category, name, properties });

    analytics.page(opts);
  }
};

Implement support for page() event tracking


const { ApolloServer } = require('apollo-server-micro')

const { schema } = require('./schema')
const { createErrorFormatter, sentryIgnoreErrors } = require('./errors')

module.exports = function createHandler(options) {
  let Sentry

  if (options.sentryDsn) {
    Sentry = require('@sentry/node')
    Sentry.init({
      dsn: options.sentryDsn,
      release: options.release,
      environment: process.env.NODE_ENV,
      ignoreErrors: sentryIgnoreErrors,
      onFatalError(error) {
        console.error(error, error.response)
      },
      debug: true,
    })
  }

  const formatError = createErrorFormatter(Sentry)
  const apolloServer = new ApolloServer({ schema, formatError })

  return apolloServer.createHandler({ path: '/' })
}

const { ApolloServer } = require('apollo-server-micro')

const { schema } = require('./schema')
const { createErrorFormatter, sentryIgnoreErrors } = require('./errors')

module.exports = function createHandler(options) {
  let Sentry

  if (options.sentryDsn) {
    Sentry = require('@sentry/node')
    Sentry.init({
      dsn: options.sentryDsn,
      release: options.release,
      environment: process.env.NODE_ENV,
      ignoreErrors: sentryIgnoreErrors,
      onFatalError(error) {
        console.error(error, error.response)
      },
      debug: process.env.DEBUG_SENTRY == 'true',
    })
  }

  const formatError = createErrorFormatter(Sentry)
  const apolloServer = new ApolloServer({ schema, formatError })

  return apolloServer.createHandler({ path: '/' })
}

Make sentry debug controllable with env variable


define([
    "less!src/stylesheets/main.less"
], function() {
    var manager = new codebox.tabs.Manager({
        draggable: false
    });
    manager.$el.addClass("component-panels");

    // Add tabs to grid
    codebox.app.grid.addView(manager, {
        width: 20,
        at: 0
    });

    // Render the tabs manager
    manager.render();

    // Make the tab manager global
    codebox.panels = manager;
});

define([
    "less!src/stylesheets/main.less"
], function() {
    var manager = new codebox.tabs.Manager({
        tabMenu: false
    });
    manager.$el.addClass("component-panels");

    // Add tabs to grid
    codebox.app.grid.addView(manager, {
        width: 20,
        at: 0
    });

    // Render the tabs manager
    manager.render();

    // Make the tab manager global
    codebox.panels = manager;
});

Disable menu on tabs, accept drag and drop


function AutoFillTodaysDate(day, month, year) {

	var dod_elm = document.getElementById("todaysDateOfDisposalLabel");
	var dod_day = document.getElementById("dateOfDisposal_day");
	var dod_month = document.getElementById("dateOfDisposal_month");
	var dod_year = document.getElementById("dateOfDisposal_year");

  if (dod_elm.checked === false) {
    dod_day.value = '';
    dod_month.value = '';
    dod_year.value = '';
  } else {
    dod_day.value = day;
    dod_month.value = month;
    dod_year.value = year;
  }
}

function AutoFillTodaysDate(day, month, year) {

	var dod_elm = document.getElementById("todaysDateOfDisposal");
	var dod_day = document.getElementById("dateOfDisposal_day");
	var dod_month = document.getElementById("dateOfDisposal_month");
	var dod_year = document.getElementById("dateOfDisposal_year");

  if (dod_elm.checked === false) {
    dod_day.value = '';
    dod_month.value = '';
    dod_year.value = '';
  } else {
    dod_day.value = day;
    dod_month.value = month;
    dod_year.value = year;
  }
}

Fix unticking the box should reset to default


import React, { Component } from 'react';
import getComponentName from '../getComponentName';

/* eslint-disable */
class Foo extends Component {
    render() {
        return <div />;
    }
}

function Bar() {
    return <div />;
}

const OldComp = React.createClass({
    render: function() {
        return <div />;
    }
});

const OldCompRev = React.createClass({
    displayName: 'Rev(OldComp)',
    render: function() {
        return <div />;
    }
});

class FooRev extends Component {
    static displayName = 'Rev(Foo)';

    render() {
        return <span />;
    }
}
/* eslint-enable */

it('reads name from React components', () => {
    expect(getComponentName(Foo)).toBe('Foo');
    expect(getComponentName(Bar)).toBe('Bar');
    expect(getComponentName(OldComp)).toBe('OldComp');
});

it('reads name from components with custom name', () => {
    expect(getComponentName(OldCompRev)).toBe('Rev(OldComp)');
    expect(getComponentName(FooRev)).toBe('Rev(Foo)');
});

it('throws if no Component passed in', () => {
    expect(() => getComponentName()).toThrow();
});

Test getComponentName() should throw when nothing passed in


import { compose, createStore } from 'redux';
import middleware from './middleware';
import reducers from './reducers';

function configureStore() {
  return compose(middleware)(createStore)(reducers);
}

export var store = configureStore();

import { compose, createStore } from 'redux';
import middleware from './middleware';
import reducers from './reducers';

// Poor man's hot reloader.

var lastState = JSON.parse(localStorage["farmbot"] || "{auth: {}}");

export var store = compose(middleware)(createStore)(reducers, lastState);

if (lastState.auth.authenticated) {
  store.dispatch({ type: "LOGIN_OK", payload: lastState.auth });
};

function saveState(){
  localStorage["farmbot"] = JSON.stringify(store.getState());
}

store.subscribe(saveState);

Add improvised localstorage saving mechanism


define(function () {
    "use strict";

    var noNew = function (Constructor) {
        return function () {
            var instance = Object.create(Constructor.prototype);
            Constructor.apply(instance, arguments);
            return instance;
        };
    };

    return {
        noNew: noNew,
    };
});

define(function () {
    "use strict";

    // Allows constructors to be called without using `new`
    var noNew = function (Constructor) {
        return function () {
            var instance = Object.create(Constructor.prototype);
            Constructor.apply(instance, arguments);
            return instance;
        };
    };

    return {
        noNew: noNew,
    };
});

Add comment for noNew utility


var mongoose = require('mongoose');
var Assignment = new mongoose.Schema({
  due: {
    type: Date,
    required: true
  },
  description: {
    type: String
  },
  posted: {
    type: Number,
    required: true
  }
});
mongoose.model('Assignment', Assignment);

module.exports = Assignment;

var mongoose = require('mongoose');
var Assignment = new mongoose.Schema({
  due: {
    type: Number,
    required: true
  },
  description: {
    type: String
  },
  posted: {
    type: Number,
    required: true
  }
});
mongoose.model('Assignment', Assignment);

module.exports = Assignment;

Move from Date to Number


var Promise = require('bluebird');
var request = require('superagent');

/**
 * @module utils
 */
var utils = module.exports = {};

/**
 * Given a JSON string, convert it to its base64 representation.
 *
 * @method
 * @memberOf utils
 */
utils.jsonToBase64 = function (json) {
  var bytes = new Buffer(JSON.stringify(json));

  return bytes
    .toString('base64')
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=+$/, '');
};

/**
 * Simple wrapper that, given a class, a property name and a method name,
 * creates a new method in the class that is a wrapper for the given
 * property method.
 *
 * @method
 * @memberOf utils
 */
utils.wrapPropertyMethod = function (Parent, name, propertyMethod) {
  var path = propertyMethod.split('.');
  var property = path.shift();
  var method = path.pop();

  Object.defineProperty(Parent.prototype, name, {
    enumerable: false,
    get: function () {
      return this[property][method].bind(this[property]);
    }
  });
}

/**
 * Perform a request with the given settings and return a promise that resolves
 * when the request is successfull and rejects when there's an error.
 *
 * @method
 * @memberOf utils
 */
utils.getRequestPromise = function (settings) {
  return new Promise(function (resolve, reject) {
    var method = settings.method.toLowerCase();
    var req = request[method](settings.url);

    for (var name in settings.headers) {
      req = req.set(name, settings.headers[name]);
    }

    if (typeof settings.data === 'object') {
      req = req.send(settings.data);
    }

    req.end(function (err, res) {
      if (err) {
        reject(err);
        return;
      }

      resolve(res.body);
    });
  });
};

Add utility function that abstracts the request process


angular.module('igTruncate', []).filter('truncate', function (){
  return function (text, length, end){
    if (text !== undefined){
      if (isNaN(length)){
        length = 10;
      }

      if (end === undefined){
        end = "...";
      }

      if (text.length <= length || text.length - end.length <= length){
        return text;
      }else{
        return String(text).substring(0, length - end.length) + end;
      }
    }
  };
});

angular.module('igTruncate', []).filter('truncate', function (){
  return function (text, length, end){
    if (text !== undefined){
      if (isNaN(length)){
        length = 10;
      }

      end = end || "...";

      if (text.length <= length || text.length - end.length <= length){
        return text;
      }else{
        return String(text).substring(0, length - end.length) + end;
      }
    }
  };
});

Simplify default for end variable.


var parser = require('./parser');
var Interpreter = require('./interpreter');

function TileAction(tile, tileX, tileY, key, data, renderer, callback) {
  this.tile = tile;
  this.key = key;
  this.data = data;
  this.tileX = tileX;
  this.tileY = tileY;
  this.renderer = renderer;
  this.callback = callback;
}

TileAction.prototype.exec = function() {
  this.before = this.tile[this.key];
  this.tile[this.key] = this.data;
  this.update();
}

TileAction.prototype.undo = function() {
  this.tile[this.key] = this.before;
  this.update();
}

TileAction.prototype.update = function() {
  // Fill the data with 0 if it's required and null.
  var command = Interpreter.CommandMap[this.tile.command];
  if(command != null && command.argument && this.tile.data == null) {
    this.tile.data = 0;
  }
  this.tile.original = parser.encodeSyllable(this.tile);
  this.renderer.map.set(this.tileX, this.tileY, this.tile);
  this.renderer.updateTile(this.tileX, this.tileY);
  if(this.callback) this.callback();
}

module.exports = TileAction;

var parser = require('./parser');
var Interpreter = require('./interpreter');

function TileAction(tile, tileX, tileY, key, data, renderer, callback) {
  this.tile = tile;
  this.key = key;
  this.data = data;
  this.tileX = tileX;
  this.tileY = tileY;
  this.renderer = renderer;
  this.callback = callback;
}

TileAction.prototype.exec = function() {
  this.before = this.tile[this.key];
  this.tile[this.key] = this.data;
  this.update();
}

TileAction.prototype.undo = function() {
  this.tile[this.key] = this.before;
  this.update();
}

TileAction.prototype.update = function() {
  // Fill the data with 0 if it's required and null.
  var command = Interpreter.CommandMap[this.tile.command];
  if(command != null && command.argument && this.tile.data == null) {
    this.tile.data = 0;
  }
  if(this.tile.command == 'push' && this.tile.data > 9) {
    // Force set data to 0 as it can't handle larger than 9
    this.tile.data = 0;
  }
  this.tile.original = parser.encodeSyllable(this.tile);
  this.renderer.map.set(this.tileX, this.tileY, this.tile);
  this.renderer.updateTile(this.tileX, this.tileY);
  if(this.callback) this.callback();
}

module.exports = TileAction;

Fix tile data for push command bug


'use strict';

var assign = require('lodash').assign;
var utils = require('gulp-util');
var webpack = require('webpack');
var PluginError = utils.PluginError;

var defaults = {
  optimize: false,
  plugins: {
    webpack: {
      entry: './src/main.js',
      output: {
        path: './dist'
      },
      plugins: []
    }
  }
};

module.exports = function (gulp, options) {
  var opts = assign(defaults, options);
  var plugins = opts.plugins;
  var optimize = opts.optimize;

  gulp.task('js', function (done) {
    var settings = plugins.webpack;

    if (optimize) {
      settings.plugins.push(
        new webpack.optimize.UglifyJsPlugin()
      );
    }
    webpack(settings, function (err, stats) {
      if (err) throw new PluginError('webpack', err);
      done();
    });
  });
};

'use strict';

var assign = require('lodash').assign;
var utils = require('gulp-util');
var webpack = require('webpack');
var PluginError = utils.PluginError;

var defaults = {
  optimize: false,
  plugins: {
    webpack: {
      entry: './src/main.js',
      output: {
        path: './dist',
        filename: 'main.js'
      },
      plugins: []
    }
  }
};

module.exports = function (gulp, options) {
  var opts = assign(defaults, options);
  var plugins = opts.plugins;
  var optimize = opts.optimize;

  gulp.task('js', function (done) {
    var settings = plugins.webpack;

    if (optimize) {
      settings.plugins.push(
        new webpack.optimize.UglifyJsPlugin()
      );
    }
    webpack(settings, function (err, stats) {
      if (err) throw new PluginError('webpack', err);
      done();
    });
  });
};

Add default setting for Webpack output.filename


import { call, takeLatest, select } from 'redux-saga/effects';
import { TOGGLE_NOTIFICATION } from 'common/actionTypes/userSettings';
import { ENABLE_VOTING } from 'common/actionTypes/voting';
import { notify, notifyPermission } from '../../utils/notification';
import { getIssueText } from '../issue/selectors';
import { notificationIsEnabled } from '../userSettings/selectors';

function* openIssue() {
  const notificationEnabled = yield select(notificationIsEnabled);
  const { description } = yield select(getIssueText);
  if (notificationEnabled) {
    yield call(notify, description);
  }
}

function* toggleNotification() {
  const notificationEnabled = yield select(notificationIsEnabled);
  if (notificationEnabled) {
    yield call(notifyPermission);
  }
}

export default function* issueSaga() {
  yield takeLatest(ENABLE_VOTING, openIssue);
  yield takeLatest(TOGGLE_NOTIFICATION, toggleNotification);
}

import { call, takeLatest, select } from 'redux-saga/effects';
import { TOGGLE_NOTIFICATION } from 'common/actionTypes/userSettings';
import { ENABLE_VOTING } from 'common/actionTypes/voting';
import { notify, notifyPermission } from '../../utils/notification';
import { getIssueText } from '../issue/selectors';
import { notificationIsEnabled } from '../userSettings/selectors';

function* openIssue() {
  const notificationEnabled = yield select(notificationIsEnabled);
  const description = yield select(getIssueText);
  if (notificationEnabled) {
    yield call(notify, description);
  }
}

function* toggleNotification() {
  const notificationEnabled = yield select(notificationIsEnabled);
  if (notificationEnabled) {
    yield call(notifyPermission);
  }
}

export default function* issueSaga() {
  yield takeLatest(ENABLE_VOTING, openIssue);
  yield takeLatest(TOGGLE_NOTIFICATION, toggleNotification);
}

Fix voting notification returning undefined


#!/usr/bin/env node

/* Gear task runner. Executes Gearfile using a tasks workflow.
 * See http://gearjs.org/#tasks.tasks
 *
 * Usage:
 * > gear
 */
var gear = require('../index'),
    vm = require('vm'),
    path = require('path'),
    fs = require('fs'),
    filename = 'Gearfile',
    existsSync = fs.existsSync || path.existsSync; // 0.6 compat

if (!existsSync(filename)) {
    notify(filename + ' not found');
    return 1;
}

try {
    var tasks = vm.runInNewContext('var tasks = ' + fs.readFileSync(filename) + '; tasks;', {
        require: require,
        process: process,
        console: console,
        gear: gear
    }, filename, true);
} catch(e) {
    notify(e);
    return 1;
}

if (tasks) {
    new gear.Queue({registry: new gear.Registry({module: 'gear-lib'})})
        .tasks(tasks)
        .run(function(err, res) {
            if (err) {
                notify(err);
            }
        });
}

function notify(msg) {
    console.error('gear: ', msg);
}

#!/usr/bin/env node
'use strict';

/* Gear task runner. Executes Gearfile using a tasks workflow.
 * See http://gearjs.org/#tasks.tasks
 *
 * Usage:
 * > gear [options]
 *
 * Available options:
 * --cwd <dir>        change working directory
 * --Gearfile <path>  execute a specific gearfile
 */
var Liftoff = require('liftoff'),
    vm = require('vm'),
    path = require('path'),
    fs = require('fs'),
    filename = 'Gearfile',
    existsSync = fs.existsSync || path.existsSync; // 0.6 compat

var GearCLI = new Liftoff({
    name: 'Gear',
    configName: filename,
    extensions: {
        '': null,
        '.js': null
    }
});

GearCLI.launch(function(env) {
    // Loads a local install of gear. Falls back to the global install.
    var gear = require(env.modulePath || '../index');
    if(process.cwd !== env.cwd) {
        process.chdir(env.cwd);
    }

    if (!env.configPath) {
        notify(filename + ' not found');
        process.exit(1);
    }

    var tasks;
    try {
        tasks = vm.runInNewContext('var tasks = ' + fs.readFileSync(filename) + '; tasks;', {
            require: require,
            process: process,
            console: console,
            gear: gear
        }, env.configPath, true);
    } catch(e) {
        notify(e);
        process.exit(1);
    }

    if (tasks) {
        new gear.Queue({registry: new gear.Registry({module: 'gear-lib'})})
            .tasks(tasks)
            .run(function(err, res) {
                if (err) {
                    notify(err);
                }
            });
    }
});

function notify(msg) {
    console.error('gear: ', msg);
}

Use node-liftoff to improve CLI mode.


var childProcess = require('child_process');
var fs = require('fs');
var path = require('path');

function isMangarack(key) {
    return /^mangarack/.test(key);
}

fs.readdirSync(__dirname).forEach(function(folderName) {
    if (!isMangarack(folderName)) return;
    var fullPath = path.join(__dirname, folderName);
    childProcess.execSync('npm publish', {cwd: fullPath, stdio: [0, 1, 2]}});
});

var childProcess = require('child_process');
var fs = require('fs');
var path = require('path');

function isMangarack(key) {
    return /^mangarack/.test(key);
}

fs.readdirSync(__dirname).forEach(function(folderName) {
    if (!isMangarack(folderName)) return;
    var fullPath = path.join(__dirname, folderName);
    childProcess.execSync('npm publish', {cwd: fullPath, stdio: [0, 1, 2]});
});

Fix the npm update utility.


// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import "@testing-library/jest-dom/extend-expect";

// need this for async/await in tests
import "core-js/stable";
import "regenerator-runtime/runtime";

import jquery from "jquery";
window.$ = window.jQuery = jquery;
jquery.expr.pseudos.visible = function () {
    // Fix jQuery ":visible" selector always returns false in JSDOM.
    // https://github.com/jsdom/jsdom/issues/1048#issuecomment-401599392
    return true;
};

// pat-subform
// See https://github.com/jsdom/jsdom/issues/1937#issuecomment-461810980
window.HTMLFormElement.prototype.submit = () => {};

// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import "@testing-library/jest-dom/extend-expect";

// need this for async/await in tests
import "core-js/stable";
import "regenerator-runtime/runtime";

import jquery from "jquery";
window.$ = window.jQuery = jquery;
jquery.expr.pseudos.visible = function () {
    // Fix jQuery ":visible" selector always returns false in JSDOM.
    // https://github.com/jsdom/jsdom/issues/1048#issuecomment-401599392
    return true;
};

class URL {
    constructor(url) {
        this.url = url;
        this.protocol = url.split("//")[0];
    }
}
window.URL = URL;

// pat-subform
// See https://github.com/jsdom/jsdom/issues/1937#issuecomment-461810980
window.HTMLFormElement.prototype.submit = () => {};

Add URL mock for Jest tests. Only supporting protocol attribute yet.


// Karma configuration
//
// For all available config options and default values, see:
// http://karma-runner.github.io/0.10/config/configuration-file.html

module.exports = function (config) {
  'use strict';

  config.set({
    autoWatch: true,
    basePath: '',
    browsers: [process.env.TRAVIS ? 'Firefox' : 'Chrome'],
    captureTimeout: 5000,
    exclude: [
      'app/js/main.js'
    ],
    files: [
      // loaded without require
      'app/bower_components/jquery/dist/jquery.js',
      'app/bower_components/jasmine-jquery/lib/jasmine-jquery.js',
      'app/bower_components/jasmine-flight/lib/jasmine-flight.js',
      // loaded with require
      {pattern: 'app/bower_components/flight/**/*.js', included: false},
      {pattern: 'app/js/**/*.js', included: false},
      {pattern: 'test/spec/**/*.spec.js', included: false},
      // test config
      'test/test-main.js'
    ],
    frameworks: [
      'jasmine',
      'requirejs'
    ],
    reporters: [process.env.TRAVIS ? 'dots' : 'progress'],
    reportSlowerThan: 500,
    singleRun: false
  });
};

// Karma configuration
//
// For all available config options and default values, see:
// http://karma-runner.github.io/0.10/config/configuration-file.html

module.exports = function (config) {
  'use strict';

  config.set({
    autoWatch: true,
    basePath: '',
    browsers: [process.env.TRAVIS ? 'Firefox' : 'Chrome'],
    captureTimeout: 5000,
    exclude: [
      'app/js/main.js'
    ],
    files: [
      // loaded without require
      'app/bower_components/jquery/dist/jquery.js',
      'app/bower_components/underscore/underscore.js',
      'app/bower_components/jasmine-jquery/lib/jasmine-jquery.js',
      'app/bower_components/jasmine-flight/lib/jasmine-flight.js',
      // loaded with require
      {pattern: 'app/bower_components/flight/**/*.js', included: false},
      {pattern: 'app/js/**/*.js', included: false},
      {pattern: 'test/spec/**/*.spec.js', included: false},
      // test config
      'test/test-main.js'
    ],
    frameworks: [
      'jasmine',
      'requirejs'
    ],
    reporters: [process.env.TRAVIS ? 'dots' : 'progress'],
    reportSlowerThan: 500,
    singleRun: false
  });
};

Add underscore.js to test require


const path = require('path');

module.exports = function(config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine'],
    preprocessors: { 'src/testception-spec.js': ['webpack'] },
    files: ['src/testception-spec.js'],
    webpack: {
      mode: 'none',
      module: {
        rules: [
          {
            test: /\.js$/,
            exclude: /node_modules/,
            loader: 'babel-loader'
          },
          {
            test: /\.js$/,
            use: {
              loader: 'istanbul-instrumenter-loader',
              options: { esModules: true }
            },
            include: path.resolve('src/')
          }
        ]
      }
    },
    reporters: ['progress', 'coverage-istanbul'],
    coverageIstanbulReporter: {
      dir: 'coverage',
      subdir: '.',
      reports: ['html', 'text-summary'],
      thresholds: {
        each: {
          statements: 100,
          branches: 100,
          functions: 100,
          lines: 100
        }
      }
    },
    port: 8080,
    runnerPort: 9100,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: false,
    browsers: ['jsdom'],
    captureTimeout: 5000,
    singleRun: true
  });
};

const path = require('path');

module.exports = function(config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine'],
    preprocessors: { 'src/testception-spec.js': ['webpack'] },
    files: ['src/testception-spec.js'],
    webpack: {
      mode: 'none',
      module: {
        rules: [
          {
            test: /\.js$/,
            exclude: /node_modules/,
            loader: 'babel-loader'
          },
          {
            test: /\.js$/,
            use: {
              loader: 'istanbul-instrumenter-loader',
              options: { esModules: true }
            },
            include: path.resolve('src/')
          }
        ]
      }
    },
    reporters: ['progress', 'coverage-istanbul'],
    coverageIstanbulReporter: {
      dir: 'coverage',
      subdir: '.',
      reports: ['html', 'text-summary', 'lcov'],
      thresholds: {
        each: {
          statements: 100,
          branches: 100,
          functions: 100,
          lines: 100
        }
      }
    },
    port: 8080,
    runnerPort: 9100,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: false,
    browsers: ['jsdom'],
    captureTimeout: 5000,
    singleRun: true
  });
};

Create lcov report so that it can be uploaded to Codeclimate


var webpackConfig = require("./webpack.config");
Object.assign(webpackConfig, {
    debug: true,
    devtool: "inline-source-map"
});

webpackConfig.externals.push("react/lib/ExecutionEnvironment");
webpackConfig.externals.push("react/lib/ReactContext");
webpackConfig.externals.push("react/addons");
webpackConfig.externals.push("jsdom");

module.exports = function(config) {
    config.set({
        basePath: "",
        frameworks: [ "jasmine" ],

        files: [
            { pattern: "src/**/*.ts", watched: true, included: false, served: false },
            { pattern: "tests/**/*.ts", watched: true, included: false, served: false },

            "tests/test-index.js"
        ],
        exclude: [],
        preprocessors: { "tests/test-index.js": [ "webpack", "sourcemap" ] },
        webpack: webpackConfig,
        webpackServer: { noInfo: true },
        reporters: [ "progress", "kjhtml", "coverage" ],
        port: 9876,
        colors: true,
        logLevel: config.LOG_INFO,
        autoWatch: true,
        browsers: [ "Chrome" ],
        singleRun: true,
        concurrency: Infinity,
        coverageReporter: {
            dir: "./dist/testresults",
            reporters: [
                { type: "json", subdir: ".", file: "coverage.json" },
                { type: "text" }
            ]
        }
    });
};

var webpackConfig = require("./webpack.config");
Object.assign(webpackConfig, {
    debug: true,
    devtool: "inline-source-map"
});

webpackConfig.externals.push("react/lib/ExecutionEnvironment");
webpackConfig.externals.push("react/lib/ReactContext");
webpackConfig.externals.push("react/addons");
webpackConfig.externals.push("jsdom");

module.exports = function(config) {
    config.set({
        basePath: "",
        frameworks: [ "jasmine" ],

        files: [
            { pattern: "src/**/*.ts", watched: true, included: false, served: false },
            { pattern: "tests/**/*.ts", watched: true, included: false, served: false },

            "tests/test-index.js"
        ],
        exclude: [],
        preprocessors: { "tests/test-index.js": [ "webpack", "sourcemap" ] },
        webpack: webpackConfig,
        webpackServer: { noInfo: true },
        reporters: [ "progress", "kjhtml", "coverage" ],
        port: 9876,
        colors: true,
        logLevel: config.LOG_INFO,
        autoWatch: true,
        browsers: [ "Chrome" ],
        singleRun: false,
        concurrency: Infinity,
        coverageReporter: {
            dir: "./dist/testresults",
            reporters: [
                { type: "json", subdir: ".", file: "coverage.json" },
                { type: "text" }
            ]
        }
    });
};

Set singleRun setting to false


var browserRequire = require('../require')

console.log(browserRequire.toString())

var compiler = require('../compiler')

compiler.compileJS('require("socket.io/support/socket.io-client/socket.io")')

Add test case for new npm/module-aware compiler


describe('attributeName filter', function() {
  var filter;
  beforeEach(angular.mock.module('PraxisDocBrowser'));

  beforeEach(inject(function(attributeNameFilter, $sce) {
    filter = function(input) {
      return $sce.getTrusted('html', attributeNameFilter(input));
    };
  }));

  it('only modifies input with one dot', function() {
    expect(function(str) {
      var dotsCount = str.split('.').length - 1;
      if (dotsCount !== 1) {
        return str === filter(str);
      }
    }).forAll(qc.string);
  });

  it('adds a tag to the prefix', function() {
    expect(filter('ns.test')).toEqual('ns.test');
  });
});

describe('attributeName filter', function() {
  var filter;
  beforeEach(angular.mock.module('PraxisDocBrowser'));

  beforeEach(inject(function(attributeNameFilter, $sce) {
    filter = function(input) {
      return $sce.getTrusted('html', attributeNameFilter(input));
    };
  }));

  it('only modifies input with one or more dots', function() {
    expect(function(str) {
      var dotsCount = str.split('.').length - 1;
      if (dotsCount === 0) {
        return str === filter(str);
      }
    }).forAll(qc.string);
  });

  it('adds a tag to the prefix', function() {
    expect(filter('ns.test')).toEqual('ns.test');
  });
});

Fix the broken js spec that started failing when sub-attributes were properly grayed out.


export function extend(obj) {
    var arg, i, k;

    for (i = 1; i < arguments.length; i++) {
        arg = arguments[i];

        for (k in arg) {
            if (arg.hasOwnProperty(k)) {
                obj[k] = arg[k];
            }
        }
    }

    return obj;
};

export function extend(obj) {
    var arg, i, k;

    for (i = 1; i < arguments.length; i++) {
        arg = arguments[i];

        for (k in arg) {
            if (arg.hasOwnProperty(k) && arg[k] !== undefined) {
                obj[k] = arg[k];
            }
        }
    }

    return obj;
}

Update extend to not carry over undefined values.


;(function ($, window, document, undefined) {
  'use strict';

  Foundation.libs.offcanvas = {
    name : 'offcanvas',

    version : '5.0.3',

    settings : {},

    init : function (scope, method, options) {
      this.events();
    },

    events : function () {
      $(this.scope).off('.offcanvas')
        .on('click.fndtn.offcanvas', '.left-off-canvas-toggle', function (e) {
          e.preventDefault();
          $(this).closest('.off-canvas-wrap').toggleClass('move-right');
        })
        .on('click.fndtn.offcanvas', '.exit-off-canvas', function (e) {
          e.preventDefault();
          $(".off-canvas-wrap").removeClass("move-right");
        })
        .on('click.fndtn.offcanvas', '.right-off-canvas-toggle', function (e) {
          e.preventDefault();
          $(this).closest(".off-canvas-wrap").toggleClass("move-left");
        })
        .on('click.fndtn.offcanvas', '.exit-off-canvas', function (e) {
          e.preventDefault();
          $(".off-canvas-wrap").removeClass("move-left");
        });
    },

    reflow : function () {}
  };
}(jQuery, this, this.document));

;(function ($, window, document, undefined) {
  'use strict';

  Foundation.libs.offcanvas = {
    name : 'offcanvas',

    version : '5.1.0',

    settings : {},

    init : function (scope, method, options) {
      this.events();
    },

    events : function () {
      var S = this.S;

      S(this.scope).off('.offcanvas')
        .on('click.fndtn.offcanvas', '.left-off-canvas-toggle', function (e) {
          e.preventDefault();
          S(this).closest('.off-canvas-wrap').toggleClass('move-right');
        })
        .on('click.fndtn.offcanvas', '.exit-off-canvas', function (e) {
          e.preventDefault();
          S(".off-canvas-wrap").removeClass("move-right");
        })
        .on('click.fndtn.offcanvas', '.right-off-canvas-toggle', function (e) {
          e.preventDefault();
          S(this).closest(".off-canvas-wrap").toggleClass("move-left");
        })
        .on('click.fndtn.offcanvas', '.exit-off-canvas', function (e) {
          e.preventDefault();
          S(".off-canvas-wrap").removeClass("move-left");
        });
    },

    reflow : function () {}
  };
}(jQuery, this, this.document));

Update Offcanvas to use S selector


/**
 * Bulgarian translation for bootstrap-datepicker
 * Apostol Apostolov <apostol.s.apostolov@gmail.com>
 */
;(function($){
	$.fn.datepicker.dates['bg'] = {
		days: ["Неделя", "Понеделник", "Вторник", "Сряда", "Четвъртък", "Петък", "Събота", "Неделя"],
		daysShort: ["Нед", "Пон", "Вто", "Сря", "Чет", "Пет", "Съб", "Нед"],
		daysMin: ["Н", "П", "В", "С", "Ч", "П", "С", "Н"],
		months: ["Януари", "Февруари", "Март", "Април", "Май", "Юни", "Юли", "Август", "Септември", "Октомври", "Ноември", "Декември"],
		monthsShort: ["Ян", "Фев", "Мар", "Апр", "Май", "Юни", "Юли", "Авг", "Сеп", "Окт", "Ное", "Дек"]
	};
}(jQuery));

/**
 * Bulgarian translation for bootstrap-datepicker
 * Apostol Apostolov <apostol.s.apostolov@gmail.com>
 */
;(function($){
	$.fn.datepicker.dates['bg'] = {
		days: ["Неделя", "Понеделник", "Вторник", "Сряда", "Четвъртък", "Петък", "Събота", "Неделя"],
		daysShort: ["Нед", "Пон", "Вто", "Сря", "Чет", "Пет", "Съб", "Нед"],
		daysMin: ["Н", "П", "В", "С", "Ч", "П", "С", "Н"],
		months: ["Януари", "Февруари", "Март", "Април", "Май", "Юни", "Юли", "Август", "Септември", "Октомври", "Ноември", "Декември"],
		monthsShort: ["Ян", "Фев", "Мар", "Апр", "Май", "Юни", "Юли", "Авг", "Сеп", "Окт", "Ное", "Дек"],
		today: "днес"
	};
}(jQuery));

Add 'today' to bulgarian locale


import './index.css';
import React, { PropTypes } from 'react';

import HashLink from './../HashLink';
import ConceptTile from './../ConceptTile';
import ConceptDragSource from '../ConceptDragSource';
import Padding from '../Padding';

const ConceptList = ({ concepts, concept, dispatch }) =>
  <ul className="List">
    {concepts.map((item) =>
      <li
        className={['List-item', item.id === (concept && concept.id) ? 'List-item--active' : ''].join(' ')}
        key={item.id}
      >
        <HashLink hash={item.id}>
          <ConceptDragSource concept={item} dispatch={dispatch}>
            <Padding>
              <ConceptTile concept={item} />
            </Padding>
          </ConceptDragSource>
        </HashLink>
      </li>
    )}
  </ul>;

ConceptList.propTypes = {
  concepts: PropTypes.array.isRequired,
  concept: PropTypes.object,
  dispatch: PropTypes.func.isRequired,
};

export default ConceptList;

import './index.css';
import React, { PropTypes } from 'react';

import HashLink from './../HashLink';
import ConceptTile from './../ConceptTile';
import ConceptDragSource from '../ConceptDragSource';
import Padding from '../Padding';

const ConceptList = ({ concepts, concept, dispatch }) =>
  <ul className="List">
    {concepts.map((item) =>
      <li
        className={['List-item', item.id === (concept && concept.id) ? 'List-item--active' : ''].join(' ')}
        key={item.id}
      >
        <ConceptDragSource concept={item} dispatch={dispatch}>
          <HashLink hash={item.id}>
            <Padding>
              <ConceptTile concept={item} />
            </Padding>
          </HashLink>
        </ConceptDragSource>
      </li>
    )}
  </ul>;

ConceptList.propTypes = {
  concepts: PropTypes.array.isRequired,
  concept: PropTypes.object,
  dispatch: PropTypes.func.isRequired,
};

export default ConceptList;

Remove link wrapper around drag source - Fixes drag-drop bug in FF


'use strict';

(function() {

  angular.module('ncsaas')
    .directive('loginbox', function($window) {
      return {
        restrict: 'A',
        link: function(scope, element, attrs) {
          var winHeight = $window.innerHeight;
          element.css('height', winHeight - 100 + 'px');
        }
      };
    })

    .directive('loginboxcontext', function($window) {
      return {
        restrict: 'A',
        link: function(scope, element, attrs) {
          var winHeight = $window.innerHeight;
          element.css('top', winHeight / 5 + 'px');
        }
      };
    });

})();

'use strict';

(function() {

  angular.module('ncsaas')
    .directive('loginboxcontext', function($window) {
      return {
        restrict: 'A',
        link: function(scope, element, attrs) {
          var elHeight = element[0].offsetHeight;
          element.css('margin-top', -elHeight / 2 + 'px');
        }
      };
    });

})();

Change directive loginboxcontext for vertical aligne login blovk. Remove directive loginbox.


'use strict';

/**
 * @ngdoc service
 * @name ausEnvApp.environment
 * @description
 * # environment
 * Service in the ausEnvApp.
 */
angular.module('ausEnvApp')
  .service('environment', function () {
    var service = this;

    service.STATIC_CSV_SOURCE='/aucsv/accounts2/';
    service.REGION_AREAS='/aucsv/region_areas/';
    service.LANDCOVER_CODES='static/config/DLCD_codes.csv';
    service.LANDUSE_CODES='static/config/landuse_codes.csv';
    service.BY_LAND_TYPE_SUMMARY='clum0917';
    service.THEMES='static/config/themes.json';
    service.LAYER_DETAILS='static/config/additional_metadata.json';
  });

'use strict';

/**
 * @ngdoc service
 * @name ausEnvApp.environment
 * @description
 * # environment
 * Service in the ausEnvApp.
 */
angular.module('ausEnvApp')
  .service('environment', function () {
    var service = this;

    service.STATIC_CSV_SOURCE='/aucsv/accounts2/';
    service.REGION_AREAS='/aucsv/region_areas/';
    service.LANDCOVER_CODES='static/config/DLCD_codes.csv';
    service.LANDUSE_CODES='static/config/landuse_codes.csv';
    service.BY_LAND_TYPE_SUMMARY='clum1219';
    service.THEMES='static/config/themes.json';
    service.LAYER_DETAILS='static/config/additional_metadata.json';
  });

Update paths of land-use summaries


// MRのassignee_nameが既知の場合には、チャットワークの通知先IDを設定します。
export default function(req, res, next) {
  if (req.objectKind === 'merge_request') {
    if (req.body.object_attributes.assignee_name === 'shigeru.nakajima') {
      req.body.object_attributes.assignee_chatwark_id = '903097'
    }
  }

  next()
}

const map = new Map([
  ['ai.yuichi', '903099'],
  ['kurubushionline', '933045'],
  ['masaomi.tsuge', '903098'],
  ['shigeru.nakajima', '903097'],
  ['yusuke.matsuda', '903099']
])

// MRのassignee_nameが既知の場合には、チャットワークの通知先IDを設定します。
export default function(req, res, next) {
  if (req.objectKind === 'merge_request') {
    if (map.has(req.body.object_attributes.assignee_name)) {
      req.body.object_attributes.assignee_chatwark_id = map.get(req.body.object_attributes.assignee_name)
    }
  }

  next()
}

Add target members to ask review


var Chrome = require('../');
var assert = require('assert');

describe('registering event', function () {
    describe('"event"', function () {
        it('should give the raw message', function (done) {
            Chrome(function (chrome) {
                chrome.once('event', function (message) {
                    assert(message.method);
                    chrome.close(done);
                });
                chrome.send('Network.enable');
                chrome.send('Page.navigate', {'url': 'chrome://newtab/'});
            });
        });
    });
    it('should give the payload only', function (done) {
        Chrome(function (chrome) {
            chrome.once('Network.requestWillBeSent', function (message) {
                assert(!message.method);
                chrome.close(done);
            });
            chrome.send('Network.enable');
            chrome.send('Page.navigate', {'url': 'chrome://newtab/'});
        });
    });
    it('should give the payload only (alternate syntax)', function (done) {
        Chrome(function (chrome) {
            chrome.Network.requestWillBeSent(function (message) {
                assert(!message.method);
                chrome.close(done);
            });
            chrome.send('Network.enable');
            chrome.send('Page.navigate', {'url': 'chrome://newtab/'});
        });
    });
});

var Chrome = require('../');
var assert = require('assert');

describe('registering event', function () {
    describe('"event"', function () {
        it('should give the raw message', function (done) {
            Chrome(function (chrome) {
                chrome.once('event', function (message) {
                    assert(message.method);
                    chrome.close(done);
                });
                chrome.send('Network.enable');
                chrome.send('Page.navigate', {'url': 'chrome://newtab/'});
            });
        });
    });
    it('should give the payload only', function (done) {
        Chrome(function (chrome) {
            chrome.once('Network.requestWillBeSent', function (message) {
                assert(!message.method);
                chrome.close(done);
            });
            chrome.send('Network.enable');
            chrome.send('Page.navigate', {'url': 'chrome://newtab/'});
        });
    });
    it('should give the payload only (alternate syntax)', function (done) {
        Chrome(function (chrome) {
            chrome.Network.requestWillBeSent(function (message) {
                assert(!message.method);
                chrome.close(done);
                // avoid to call `done()` more than once
                chrome.removeAllListeners();
            });
            chrome.send('Network.enable');
            chrome.send('Page.navigate', {'url': 'chrome://newtab/'});
        });
    });
});

Fix race condition in test


'use strict';

var method = require('../../waterlock-ldap-auth');
var ldap = method.ldap;
var connection = method.connection;

/**
 * Login action
 */
module.exports = function(req, res) {

  var params = req.params.all();

  if (typeof params.username === 'undefined' || typeof params.password === 'undefined') {
    waterlock.cycle.loginFailure(req, res, null, {error: 'Invalid username or password'});
  } else {
    var auth = new ldap(connection);
    console.log("User attributes: %j", waterlock.User.attributes);
    auth.authenticate(params.username, params.password, function(err, user) {
      if (err) {
        waterlock.cycle.loginFailure(req, res, user, {error: 'Invalid username or password'});
      } else {
        var criteria = {
          username: params.username
        };
        var attr = {
          username: params.username,
          entryUUID: user
        };
        waterlock.engine.findOrCreateAuth(criteria, attr).exec(function(err, user) {
          if (err) {
            waterlock.cycle.loginFailure(req, res, null, {error: 'user not found'});
          } else {
            waterlock.cycle.loginSuccess(req, res, user);
          }
        });
      }
    });
  }
};

'use strict';

var method = require('../../waterlock-ldap-auth');
var ldap = method.ldap;
var connection = method.connection;

/**
 * Login action
 */
module.exports = function(req, res) {

  var params = req.params.all();

  if (typeof params.username === 'undefined' || typeof params.password === 'undefined') {
    waterlock.cycle.loginFailure(req, res, null, {error: 'Invalid username or password'});
  } else {
    var auth = new ldap(connection);
    console.log("User attributes: %j", waterlock.User.attributes);
    auth.authenticate(params.username, params.password, function(err, user) {
      if (err) {
        waterlock.cycle.loginFailure(req, res, user, {error: 'Invalid username or password'});
      } else {
        var criteria = {
          username: params.username
        };
        var attr = {
          username: params.username,
          entryUUID: user.entryUUID
        };
        waterlock.engine.findOrCreateAuth(criteria, attr, function(err, user) {
          if (err) {
            waterlock.cycle.loginFailure(req, res, null, {error: 'user not found'});
          } else {
            waterlock.cycle.loginSuccess(req, res, user);
          }
        });
      }
    });
  }
};

Fix creation of Auth instance and pass entryUUID to it.


//I hear you like to run node in your node, so I'm testing node using node
var spawn = require('spawn-cmd').spawn,
    testProcess = spawn('node ./src/index.js < ./tests/eval.js'),
    assert = require('assert');

testProcess.stdout.on('data', function (data) {
  assert.equal(data, 'This is only a test');
});

testProcess.stderr.on('data', function (data) {
  assert.fail(data, "nothing", "Did not expect any stderr", ":")
});

testProcess.on('close', function (code) {
  assert.equal(code, 0, "The spawned process did not exit cleanly");
});

//I hear you like to run node in your node, so I'm testing node using node
var spawn = require('spawn-cmd').spawn,
    testProcess = spawn('node ./src/index.js < ./tests/eval.js'),
    assert = require('assert');
console.log("Modules required and process spawned");

testProcess.stdout.on('data', function (data) {
  assert.equal(data, 'This is only a test');
  console.log("Received expected output on stdout");
});

testProcess.stderr.on('data', function (data) {
  assert.fail(data, "nothing", "Did not expect any stderr", ":")
});

testProcess.on('close', function (code) {
  assert.equal(code, 0, "The spawned process did not exit cleanly");

});

Test still failing on TravisCI


QUnit.module("png");

QUnit.test("png-image-element format returns an image", function(assert) {
  var done = assert.async();

  var image = Viz("digraph { a -> b; }", { format: "png-image-element" });

  assert.ok(image instanceof Image, "image should be an Image");

  image.onload = function() {
    done();
  }

  image.onerror = function(e) {
    throw e;
  }
});

QUnit.test("png-image-element format with a worker", function(assert) {
  var done = assert.async();

  var worker = new Worker("./worker.js");

  worker.onmessage = function(e) {
    var image = Viz.svgXmlToPngImageElement(e.data);
    assert.ok(image instanceof Image, "image should be an Image");
    done();
  }

  worker.onerror = function(e) {
    throw e;
  }

  worker.postMessage({ src: "digraph { a -> b; }", options: { format: "svg" } });
});

QUnit.test("asking for plain png format should throw an exception", function(assert) {
  assert.throws(function() {
    Viz("digraph { a -> b; }", { format: "png" });
  }, /renderer for png is unavailable/);
});

QUnit.module("png");

QUnit.test("png-image-element format returns an image", function(assert) {
  var done = assert.async();

  var image = Viz("digraph { a -> b; }", { format: "png-image-element" });

  assert.ok(image instanceof Image, "image should be an Image");

  image.onload = function() {
    done();
  }

  image.onerror = function(e) {
    throw e;
  }
});

QUnit.test("specifying the scale option should change resulting image's natural size", function(assert) {
  var done = assert.async();

  var image = Viz("digraph { size=\"1,1!\"; a -> b; }", { format: "png-image-element", scale: 3 });

  image.onload = function() {
    assert.equal(image.height, 96);
    assert.equal(image.naturalHeight, 288);
    done();
  }

  image.onerror = function(e) {
    throw e;
  }
});

QUnit.test("png-image-element format with a worker", function(assert) {
  var done = assert.async();

  var worker = new Worker("./worker.js");

  worker.onmessage = function(e) {
    var image = Viz.svgXmlToPngImageElement(e.data);
    assert.ok(image instanceof Image, "image should be an Image");
    done();
  }

  worker.onerror = function(e) {
    throw e;
  }

  worker.postMessage({ src: "digraph { a -> b; }", options: { format: "svg" } });
});

QUnit.test("asking for plain png format should throw an exception", function(assert) {
  assert.throws(function() {
    Viz("digraph { a -> b; }", { format: "png" });
  }, /renderer for png is unavailable/);
});

Add test for scale option.


/*!
 * Start Bootstrap - Agency Bootstrap Theme (http://startbootstrap.com)
 * Code licensed under the Apache License v2.0.
 * For details, see http://www.apache.org/licenses/LICENSE-2.0.
 */

// jQuery for page scrolling feature - requires jQuery Easing plugin
$(function() {
    $('a.page-scroll').bind('click', function(event) {
        var $anchor = $(this);
        $('html, body').stop().animate({
            scrollTop: $($anchor.attr('href')).offset().top
        }, 1500, 'easeInOutExpo');
        event.preventDefault();
    });
});

// Highlight the top nav as scrolling occurs
$('body').scrollspy({
    target: '.navbar-fixed-top'
})

// Closes the Responsive Menu on Menu Item Click
$('.navbar-collapse ul li a').click(function() {
    $('.navbar-toggle:visible').click();
});

/*!
 * Start Bootstrap - Agency Bootstrap Theme (http://startbootstrap.com)
 * Code licensed under the Apache License v2.0.
 * For details, see http://www.apache.org/licenses/LICENSE-2.0.
 */

function hideOnRefresh() {
    console.log($(".navbar").offset().top);
    if ($(".navbar").offset().top > 50) {
        $(".navbar-fixed-top").addClass("navbar-collapse navbar-shrink");
    } else {
        $(".navbar-fixed-top").removeClass("navbar-collapse navbar-shrink");
    }
}
// jQuery for page scrolling feature - requires jQuery Easing plugin
$(function() {
    $('a.page-scroll').bind('click', function(event) {
        var $anchor = $(this);
        $('html, body').stop().animate({
            scrollTop: $($anchor.attr('href')).offset().top
        }, 1500, 'easeInOutExpo');
        event.preventDefault();
    });
});

// Highlight the top nav as scrolling occurs
$('body').scrollspy({
    target: '.navbar-fixed-top'
})

// Closes the Responsive Menu on Menu Item Click
$('.navbar-collapse ul li a').click(function() {
    $('.navbar-toggle:visible').click();
});

$(document).ready(function() {
    hideOnRefresh();
});

$(window).scroll(function() {
    hideOnRefresh();
});

Fix to collapse navbar on page reloads


const { ipcMain } = require('electron');

function isPromise(object) {
    return object.then && typeof(object.then) === 'function';
}

function isGenerator(object) {
    return object.next && typeof(object.next) === 'function';
}

class IPCHandler {
    handle(event, funct, binding = this) {
        const localFunc = funct.bind(binding);
        ipcMain.on(event, (ev, ...args) => {
            const localFuncResult = localFunc(...args);
            if(isPromise(localFuncResult)) {
                localFuncResult.then((...result) => {
                    ev.sender.send(event, ...result);
                });
            } else if(isGenerator(localFuncResult)) {
                for(let result of localFuncResult) {
                    ev.sender.send(event, result);
                }
            } else {
                ev.sender.send(event, localFuncResult);
            }
        });
    }

    handleSync(event, funct, binding = this) {
        const localFunc = funct.bind(binding);
        ipcMain.on(event, (ev, ...args) => {
            const localFuncResult = localFunc(...args);
            if(isPromise(localFuncResult)) {
                localFuncResult.then((result) => {
                    ev.returnValue = result;
                });
            } else {
                ev.returnValue = localFuncResult;
            }
        });
    }

    provide(event, func) {
        const localFunc = func.bind(this);
        ipcMain.on(event, localFunc);
    }
}

module.exports = IPCHandler;

const { ipcMain } = require('electron');

function isPromise(object) {
    return object.then && typeof(object.then) === 'function';
}

function isGenerator(object) {
    return object[Symbol.iterator] && typeof(object[Symbol.iterator]) === 'function';
}

class IPCHandler {
    handle(event, funct, binding = this) {
        const localFunc = funct.bind(binding);
        ipcMain.on(event, (ev, ...args) => {
            const localFuncResult = localFunc(...args);
            if(isPromise(localFuncResult)) {
                localFuncResult.then((...result) => {
                    ev.sender.send(event, ...result);
                });
            } else if(isGenerator(localFuncResult)) {
                for(let result of localFuncResult) {
                    ev.sender.send(event, result);
                }
            } else {
                ev.sender.send(event, localFuncResult);
            }
        });
    }

    handleSync(event, funct, binding = this) {
        const localFunc = funct.bind(binding);
        ipcMain.on(event, (ev, ...args) => {
            const localFuncResult = localFunc(...args);
            if(isPromise(localFuncResult)) {
                localFuncResult.then((result) => {
                    ev.returnValue = result;
                });
            } else {
                ev.returnValue = localFuncResult;
            }
        });
    }

    provide(event, func) {
        const localFunc = func.bind(this);
        ipcMain.on(event, localFunc);
    }
}

module.exports = IPCHandler;

Fix check for generator ipc response


/**
 * Isomorphic CSS style loader for Webpack
 *
 * Copyright © 2015 Kriasoft, LLC. All rights reserved.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE.txt file in the root directory of this source tree.
 */

import React, { Component, PropTypes } from 'react';

function withStyles(ComposedComponent, ...styles) {
  return class Styles extends Component {
    static contextTypes = {
      insertCss: PropTypes.func.isRequired,
    };

    componentWillMount() {
      this.removeCss = this.context.insertCss.apply(undefined, styles);
    }

    componentWillUnmount() {
      setTimeout(this.removeCss, 0);
    }

    render() {
      return <ComposedComponent {...this.props} />;
    }
  };
}

export default withStyles;

/**
 * Isomorphic CSS style loader for Webpack
 *
 * Copyright © 2015 Kriasoft, LLC. All rights reserved.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE.txt file in the root directory of this source tree.
 */

import React, { Component, PropTypes } from 'react';

function withStyles(ComposedComponent, ...styles) {
  return class Styles extends Component {
    static contextTypes = {
      insertCss: PropTypes.func.isRequired,
    };

    static displayName = ComposedComponent.displayName || ComposedComponent.name;

    componentWillMount() {
      this.removeCss = this.context.insertCss.apply(undefined, styles);
    }

    componentWillUnmount() {
      setTimeout(this.removeCss, 0);
    }

    render() {
      return <ComposedComponent {...this.props} />;
    }
  };
}

export default withStyles;

Fix warning in console, to display ComposedComponent name, rather `Styles` name.


$(function() {

	/* Vars */

	var TIME = 500;

	var pics = $('.ref-pic');
	var refs = $('.ref-block');

	/* Handlers */

	var refHandler = function(event) {
		var $this = $(this);
		var ACT = 'active';
		var i = $this.index();

		if (! $this.hasClass( ACT )) {
			pics.removeClass( ACT );
			$this.addClass( ACT );

			refs.hide( TIME );
			refs.eq(i).show( TIME );
		}
	}

	/*$('body').on('click', pics, refHandler);*/
	pics.on('click', refHandler);

});

$(function() {

	/* Vars */

	var TIME = 500;

	var pics = $('.ref-pic');
	var refs = $('.ref-block');

	var navItem = $('.nav-item');

	/* Handlers */
	var menuItemHandler = function(event) {
		event.preventDefault();
		hash = $(this).children('a')[0].hash;

		$('html').animate(function() {
			offsetY = $(hash).position.top;
			console.log(offsetY);
			window.scrollY = offsetY
		}, TIME);
	}

	var refHandler = function(event) {
		var $this = $(this);
		var ACT = 'active';
		var i = $this.index();

		if (! $this.hasClass( ACT )) {
			pics.removeClass( ACT );
			$this.addClass( ACT );

			refs.hide( TIME );
			refs.eq(i).show( TIME );
		}
	}

	/* Event Handlers */

	pics.on('click', refHandler);
	navItem.on('click', menuItemHandler);

});

Add temporary scroll animation function


'use strict'

// Create a pushover instance
const pushover = new (require('pushover-notifications'))({
  user: process.env.PUSHOVER_USER,
  token: process.env.PUSHOVER_TOKEN
})

// Load utilities
const i18n = require('../../locale')
const logger = require('../../log')

// Get package data
const pkg = require('../../../../package')

// Send notification to mobile device
module.exports = function (msg) {
  // DEBUG
  logger.verbose(i18n.t('utils.methods.notify.send', msg))

  // Send the message
  return pushover.send({
    message: msg,
    title: pkg.name,
    sound: 'magic',
    priority: 1
  }, (err, result) => {
    if (err !== undefined && result && !result.status) {
      return logger.error(i18n.t('utils.methods.notify.error', msg))
    }
  })
}

'use strict'

// Load requirements
const emoji = require('node-emoji')

// Create a pushover instance
const pushover = new (require('pushover-notifications'))({
  user: process.env.PUSHOVER_USER,
  token: process.env.PUSHOVER_TOKEN
})

// Load utilities
const i18n = require('../../locale')
const logger = require('../../log')

// Get package data
const pkg = require('../../../../package')

// Send notification to mobile device
module.exports = function (msg) {
  // DEBUG
  logger.verbose(i18n.t('utils.methods.notify.send', msg))

  // Send the message
  return pushover.send({
    message: emoji.emojify(msg),
    title: pkg.name,
    sound: 'magic',
    priority: 1
  }, (err, result) => {
    if (err !== undefined && result && !result.status) {
      return logger.error(i18n.t('utils.methods.notify.error', msg))
    }
  })
}

Add emoji handling to push notifications


//@flow

/*******************************************************************************
 * Imports
 *******************************************************************************/

import React from 'react'

import { Markdown, Cr } from 'tldr/components/Markdown'
import Link from 'tldr/components/Link'
import Tldr from 'tldr/components/Tldr'

/*******************************************************************************
 * Public API
 *******************************************************************************/

export default () => (
  <Markdown className="content">
    # Oops! Command not found!
{Cr}
    We looked and looked for it, but it's nowhere. Maybe you can help us find it?
{Cr}
    <Tldr size="small"/> is a community effort, we need people like you to raise the bar and
      find missing commands, suggest editions, and propose new pages.
{Cr}
    ### How can I help?
{Cr}
    Take a look at the open <Link href="https://github.com/tldr-pages/tldr/issues?q=is%3Aopen+is%3Aissue+label%3Acommand" text="Command Requests" /> to throw a jab at things people need, or maybe join in on any of the open <Link href="https://github.com/tldr-pages/tldr/pulls" text="command proposals" />.
{Cr}
    If the command you want hasn't been proposed yet, feel encouraged to submit a proposal yourself! 😉 — <Link href="https://github.com/tldr-pages/tldr/blob/master/CONTRIBUTING.md" text="Start here" />
{Cr}
  </Markdown>
)

//@flow

/*******************************************************************************
 * Imports
 *******************************************************************************/

import React from 'react'

import { Markdown, Cr } from 'tldr/components/Markdown'
import Link from 'tldr/components/Link'
import Tldr from 'tldr/components/Tldr'

/*******************************************************************************
 * Public API
 *******************************************************************************/

export default () => (
  <Markdown className="content">
    # Oops! Command not found!
{Cr}
    We looked and looked for it, but it's nowhere. Maybe you can help us find it?
{Cr}
    <Tldr size="small"/> is a community effort, we need people like you to raise the bar and
      find missing commands, suggest editions, and propose new pages.
{Cr}
    ### How can I help?
{Cr}
    Take a look at the open <Link href="https://github.com/tldr-pages/tldr/issues?q=is%3Aissue+is%3Aopen+label%3A%22new+command%22" text="Command Requests" /> to throw a jab at things people need, or maybe join in on any of the open <Link href="https://github.com/tldr-pages/tldr/pulls" text="command proposals" />.
{Cr}
    If the command you want hasn't been proposed yet, feel encouraged to submit a proposal yourself! 😉 — <Link href="https://github.com/tldr-pages/tldr/blob/master/CONTRIBUTING.md" text="Start here" />
{Cr}
  </Markdown>
)

Update not found page to fix command request link.


var audio = new AudioContext || new webkitAudioContext;

function playRadarPing() {
  var osc = audio.createOscillator();
  var gain = audio.createGain();
  var time = audio.currentTime;

  gain.connect(audio.destination);
  osc.connect(gain);

  osc.type = 'triangle';
  gain.gain.setValueAtTime(0.2, time);
  gain.gain.linearRampToValueAtTime(0, time + 0.2);
  osc.start(time);
  osc.stop(time + 0.2);
}

Add function to play radar ping sound


Module.register("recognizer",{

  start() {
    this.display = false;
    this.image = "";
    console.log("Recognizer started");
    this.sendSocketNotification("RECOGNIZER_STARTUP");
    return;
  },

  socketNotificationReceived: function(notification) {
    console.log("Recognizer recieved a notification: " + notification)

    if (notification === "RECOGNIZER_CONNECTED") {
      console.log("Recognizer initialized, awaiting Activation");
    }

    else if (notification === "SELFIE_IS_GO") {
      console.log("Begin Display Selfie");
      this.display = true;
      this.updateDom();
    }

  },

  notificationReceived: function(notification) {
    if(notification === "picture") {
      console.log("========== pic request ===================");
      this.sendSocketNotification("TAKE_SELFIE");
    }
	},

  getDom: function() {
    var wrapper = document.createElement("div");
    wrapper.className = "selfie-display";

    if (this.display) {
      var imgElem = document.createElement("img");
      imgElem.src = "./public/webcam_pic.jpg";
      wrapper.innerHTML = '<img id="selfie" src="./public/webcam_pic.jpg" />';
      document.getElementById("selfie-display").appendChild(imgElem);
      return wrapper;
    }
  },

});

//this should cause a change

Module.register("recognizer",{

  start() {
    this.display = false;
    this.image = "";
    console.log("Recognizer started");
    this.sendSocketNotification("RECOGNIZER_STARTUP");
    return;
  },

  socketNotificationReceived: function(notification) {
    console.log("Recognizer recieved a notification: " + notification)

    if (notification === "RECOGNIZER_CONNECTED") {
      console.log("Recognizer initialized, awaiting Activation");
    }

    else if (notification === "SELFIE_IS_GO") {
      console.log("Begin Display Selfie");
      this.display = true;
      this.updateDom();
    }

  },

  notificationReceived: function(notification) {
    if(notification === "picture") {
      console.log("========== pic request ===================");
      this.sendSocketNotification("TAKE_SELFIE");
    }
	},

  getDom: function() {
    var wrapper = document.createElement("div");
    wrapper.className = "selfie-display";

    if (this.display) {
      var imgElem = document.createElement("img");
      imgElem.src = "./public/webcam_pic.jpg";
      wrapper.appendChild(imgElem);
      return wrapper;
    }
    return wrapper;
  },

});

//this should cause a change

Add picture display after taken on mirror, need to set a timer for that


$(document).ready(() => {

  const restaurants = ['taste', 'variantti', 'kanttiini', 'blancco', 'factory']

  const days = [
  	{name: "Maanantai"},
  	{name: "Tiistai"},
  	{name: "Keskiviikko"},
  	{name: "Torstai"},
  	{name: "Perjantai"},
  ]

  restaurants.forEach((r) => {
    $.ajax({
      url: `/crawled/${r}.html`,
      type: 'get',
    }).done((res) => {
      $(`#${r}`).html(res);

      var number = Math.floor(Math.random()*(10-1+1)+1);
      $(`#${r}`).prepend(`<img style="float:right;" src="http://lorempixel.com/200/200/food/${number}/">`)

      days.forEach((o) => {
  		  var re = new RegExp(o.name, "gi");
	  	  $('.tab-content').html($('.tab-content').html().replace(re, `<div class="day">${o.name}</div>`));
      });
    });
  })

});

$(document).ready(() => {

  const restaurants = ['taste', 'variantti', 'kanttiini', 'blancco', 'factory']

  const days = [
  	{name: "Maanantai"},
  	{name: "Tiistai"},
  	{name: "Keskiviikko"},
  	{name: "Torstai"},
  	{name: "Perjantai"},
  ]

  restaurants.forEach((r) => {
    $.ajax({
      url: `/crawled/${r}.html`,
      type: 'get',
    }).done((res) => {
      $(`#${r}`).html(`<div class="float-sm-left">${res}</div`);

      var number = Math.floor(Math.random()*(10-1+1)+1);
      $(`#${r}`).append(`<img class="float-sm-right" src="http://lorempixel.com/200/200/food/${number}/">`)

      days.forEach((o) => {
  		  var re = new RegExp(o.name, "gi");
	  	  $('.tab-content').html($('.tab-content').html().replace(re, `<div class="day">${o.name}</div>`));
      });
    });
  })

});

Put inspirational images on the bottom on xs devices


var assert = require('assert');

describe("Basic Test", function(){
    it("Loads the basic demo page and sends values to server", function(){
        browser.url('http://localhost:7888/src/e2e-tests/basic-demo-page?auto-activate-glasswing')
        browser.waitUntil(function () {
            var a = browser.execute(function(){
                return window.a
            }).value
            return a == 25;
        }, 30000, 'expected a to be set to 25 by the page')
        browser.waitUntil(function () {
            var a = browser.execute(function(){
                return window.__jscb.recordedValueBuffer.length
            }).value
            console.log("length", a)
            return a == 0;
        }, 30000, 'expected values to be sent to server and value buffer to be reset');
    })
})

var assert = require('assert');

describe("Basic Test", function(){
    it("Loads the basic demo page and sends values to server", function(){
        browser.url('http://localhost:7888/src/e2e-tests/basic-demo-page?auto-activate-glasswing')
        browser.waitUntil(function () {
            var a = browser.execute(function(){
                return window.a
            }).value
            return a == 25;
        }, 20000, 'expected a to be set to 25 by the page')
        browser.waitUntil(function () {
            var a = browser.execute(function(){
                return window.__jscb.recordedValueBuffer.length
            }).value
            console.log("length", a)
            return a == 0;
        }, 20000, 'expected values to be sent to server and value buffer to be reset');
    })
    it("Shows simple.js in the Glasswing file listing", function(){
        browser.url('http://localhost:9500')
        var fileLinksTable = $(".file-links")
        fileLinksTable.waitForExist(5000)
        var jsFilePath = "http://localhost:7888/src/e2e-tests/basic-demo-page/basic-demo.js"
        assert(fileLinksTable.getText().indexOf(jsFilePath) !== -1)
    })
    })
})

Check file listing loads in E2E test.


/**
 * Policies are simply Express middleware functions which run before your controllers.
 * You can apply one or more policies for a given controller or action.
 *
 * Any policy file (e.g. `authenticated.js`) can be dropped into the `/policies` folder,
 * at which point it can be accessed below by its filename, minus the extension, (e.g. `authenticated`)
 *
 * For more information on policies, check out:
 * http://sailsjs.org/#documentation
 */

module.exports.policies = {

    // Default policy for all controllers and actions
    // (`true` allows public access)
    '*': true,

    UserController: {
        '*': 'authenticated'
    },

    RoomController: {
        '*': 'authenticated'
    }
};

/**
 * Policies are simply Express middleware functions which run before your controllers.
 * You can apply one or more policies for a given controller or action.
 *
 * Any policy file (e.g. `authenticated.js`) can be dropped into the `/policies` folder,
 * at which point it can be accessed below by its filename, minus the extension, (e.g. `authenticated`)
 *
 * For more information on policies, check out:
 * http://sailsjs.org/#documentation
 */

module.exports.policies = {

    // Default policy for all controllers and actions
    // (`true` allows public access)
    '*': true,

    MainController: {
        'dashboard': 'authenticated'
    },

    RoomController: {
        '*': 'authenticated'
    },

    UserController: {
        '*': 'authenticated'
    }
};

Add 'authenticated' policy to dashboard action.


import Promise from 'bluebird'
import childProcess from 'child_process'

import logger from '../logger'

Promise.promisifyAll(childProcess)

const log = logger({
  component: 'FS'
})

// Hides a directory on Windows.
// Errors are logged, not thrown.
export async function hideOnWindows (path: string): Promise<void> {
  try {
    await childProcess.execAsync(`attrib +h "${path}"`)
  } catch (err) {
    log.error(err)
  }
}

const ILLEGAL_CHARACTERS = '/?<>\\:*|"'
const ILLEGAL_CHARACTERS_REGEXP = new RegExp(`[${ILLEGAL_CHARACTERS}]`, 'g')
const REPLACEMENT_CHARACTER = '_'

// Return a new name compatible with target filesystems by replacing invalid
// characters from the given file/dir name.
export function validName (name: string) {
  return name.replace(ILLEGAL_CHARACTERS_REGEXP, REPLACEMENT_CHARACTER)
}

import Promise from 'bluebird'
import childProcess from 'child_process'

import logger from '../logger'

Promise.promisifyAll(childProcess)

const log = logger({
  component: 'FS'
})

// Hides a directory on Windows.
// Errors are logged, not thrown.
export async function hideOnWindows (path: string): Promise<void> {
  if (process.platform !== 'win32') return
  try {
    await childProcess.execAsync(`attrib +h "${path}"`)
  } catch (err) {
    log.error(err)
  }
}

const ILLEGAL_CHARACTERS = '/?<>\\:*|"'
const ILLEGAL_CHARACTERS_REGEXP = new RegExp(`[${ILLEGAL_CHARACTERS}]`, 'g')
const REPLACEMENT_CHARACTER = '_'

// Return a new name compatible with target filesystems by replacing invalid
// characters from the given file/dir name.
export function validName (name: string) {
  return name.replace(ILLEGAL_CHARACTERS_REGEXP, REPLACEMENT_CHARACTER)
}

Hide dirs on Windows only


import {List} from 'immutable';
import {expect} from 'chai';

import {createPlayer} from '../src/core';

describe('createPlayer', () => {
        it('sets name properly', () => {
            const name = 'someName';
            const player = createPlayer(name);

            expect(player.name).to.equal(name);
        });

        it('sets a non-empty ID', () => {
            const player = createPlayer('someName');

            expect(player.id).to.not.be.empty;
        });

        it('initializes an empty hand', () => {
            const player = createPlayer('someName');

            expect(player.hand).to.equal(List.of());
        });
    }
);

import {List} from 'immutable';
import {expect} from 'chai';

import {createPlayer} from '../src/core';

describe('createPlayer', () => {
        it('sets name properly', () => {
            const name = 'someName';
            const player = createPlayer(name);

            expect(player.name).to.equal(name);
        });

        it('sets a non-empty ID', () => {
            const player = createPlayer('someName');

            expect(player.id).to.exist;
        });

        it('initializes an empty hand', () => {
            const player = createPlayer('someName');

            expect(player.hand).to.equal(List.of());
        });
    }
);

Simplify assertion from to.not.be.empty to to.exist


export default function(a, b) {
  const alast = a.lastOpened;
  const blast = b.lastOpened;
  if (alast < blast) {
    return 1;
  } else if (alast > blast) {
    return -1;
  }
  return 0;
}

export default function(a, b) {
  const alast = a.lastOpened || a.id;
  const blast = b.lastOpened || b.id;
  if (alast < blast) {
    return 1;
  } else if (alast > blast) {
    return -1;
  }
  return 0;
}

Fix bug with sort albums


'use strict';

import config      from '../config';
import url         from 'url';
import browserSync from 'browser-sync';
import gulp        from 'gulp';

gulp.task('browserSync', function() {

  const DEFAULT_FILE = 'index.html';
  const ASSET_EXTENSIONS = ['js', 'css', 'png', 'jpg', 'jpeg', 'gif'];

  browserSync.init({
    server: {
      baseDir: config.buildDir,
      middleware: function(req, res, next) {
        let fileHrefArray = url.parse(req.url).href.split('.');
        let fileExtension = fileHrefArray[fileHrefArray.length - 1];

        if ( ASSET_EXTENSIONS.indexOf(fileExtension) === -1 ) {
          req.url = '/' + DEFAULT_FILE;
        }

        return next();
      }
    },
  	port: config.browserPort,
  	ui: {
    	port: config.UIPort
    },
    ghostMode: {
      links: false
    }
  });

});

'use strict';

import config      from '../config';
import url         from 'url';
import browserSync from 'browser-sync';
import gulp        from 'gulp';

gulp.task('browserSync', function() {

  const DEFAULT_FILE = 'index.html';
  const ASSET_EXTENSION_REGEX = /\b(?!\?)\.(js|css|png|jpe?g|gif|svg|eot|otf|ttc|ttf|woff2?)(?!\.)/i;

  browserSync.init({
    server: {
      baseDir: config.buildDir,
      middleware: function(req, res, next) {
        let fileHref = url.parse(req.url).href;

        if ( !ASSET_EXTENSION_REGEX.test(fileHref) ) {
          req.url = '/' + DEFAULT_FILE;
        }

        return next();
      }
    },
  	port: config.browserPort,
  	ui: {
    	port: config.UIPort
    },
    ghostMode: {
      links: false
    }
  });

});

Fix not being able to serve fonts and other assets using query string cache busters.