<commit_before>// the eslint "no-unused-expressions" rule is triggered by the `expect`
// assertions in the tests. So, in order to avoid problems with eslint, I'm
// disabling that rule in this file
//
/* eslint-disable no-unused-expressions */

import React from 'react';
import { shallow, render, mount } from 'enzyme';
import chai from 'chai';
import sinon from 'sinon';
import TextInput from './TextInput';

const expect = chai.expect;

/* *****************************************************************************
on editing, the TextInput component
    should call onChange with the new value
    should have the new value in the component state

    should call onValidation with the correct arguments when required=true and the new value is valid
    should have updated validation state when required=true and the new value is valid

    should call onValidation with the correct arguments when required=true and the new value is blank
    should have updated validation state when required=true and the new value is blank

    should call onValidation with the correct arguments when required=false and the new value is blank
    should have updated validation state when required=false and the new value is blank
*/

/* *****************************************************************************
on initialization, the TextInput component
    should not call onChange
    should call onValidation with the correct arguments when required=true and value is valid
    should call onValidation with the correct arguments when required=true and value is blank
    should call onValidation with the correct arguments when required=false and value is blank
*/
describe('on initialization, the TextInput component', () => {

    const description = 'description';

    it('should not call onChange', () => {

        const onChange = sinon.spy();

        const value = 'anchovies';

        mount(<TextInput value={value} onChange={onChange} />);

        expect(onChange.callCount).to.equal(0);
    });

    it('should call onValidation with the correct arguments when ' +
        'required=true and value is valid', () => {

        const onValidation = sinon.spy();

        const required = true;
        const value = 'anchovies';

        mount(<TextInput required={required} value={value} onValidation={onValidation} />);

        expect(onValidation.callCount).to.equal(1);
        expect(onValidation.calledWith(false, true, null)).to.equal(true);
    });

    it('should call onValidation with the correct arguments when ' +
        'required=true and value is blank', () => {

        const onValidation = sinon.spy();

        const required = true;
        const value = '';

        const expectedMessage = `${description} is required`;

        mount(<TextInput
            required={required}
            description={description}
            value={value}
            onValidation={onValidation}
        />);

        expect(onValidation.callCount).to.equal(1);
        expect(onValidation.calledWith(false, false, expectedMessage))
            .to.equal(true, 'calledWith');
    });

    it('should call onValidation with the correct arguments when ' +
        'required=false and value is blank', () => {

        const onValidation = sinon.spy();

        const required = false;
        const value = '';

        mount(<TextInput
            required={required}
            description={description}
            value={value}
            onValidation={onValidation}
        />);

        expect(onValidation.callCount).to.equal(1);
        expect(onValidation.calledWith(false, true, null)).to.equal(true, 'calledWith');
    });

});

/* *****************************************************************************
the state of the TextInput component
    should be initialized with the correct value
    should have the correct hasValidated state
    should have the correct isValid state when the value is required and it's blank
    should have the correct isValid state when the value is required and it's not blank
    should have the correct isValid state when the value is not required and it is blank
    should have the correct validationMessage state when the value is required and it's blank
    should have the correct validationMessage state when the value is required
        and it's not blank
    should have the correct validationMessage state when the value is not
        required and it is blank
*/
describe('the state of the TextInput component', () => {

    const description = 'Some value';

    it('should be initialized with the correct value', () => {
        const values = [
            { value: '', expected: '' },
            { value: null, expected: '' },
            { value: undefined, expected: '' },
            { value: 'aaa', expected: 'aaa' },
            { value: 42, expected: '42' },
        ];

        let component;

        values.forEach((val) => {
            component = shallow(<TextInput value={val.value} />);

            expect(component.state().value).to.equal(val.expected);
        });
    });

    it('should have the correct hasValidated state', () => {
        const value = 'anchovies';

        const component = shallow(<TextInput value={value} />);

        expect(component.state().hasValidated).to.equal(false);
    });

    it('should have the correct isValid state when the value is required and ' +
        'it is blank', () => {

        const required = true;
        const value = '';

        const component = shallow(<TextInput required={required} value={value} />);

        expect(component.state().isValid).to.equal(false);
    });

    it('should have the correct isValid state when the value is required and ' +
        'it is not blank', () => {

        const required = true;
        const value = 'pizza';

        const component = shallow(<TextInput required={required} value={value} />);

        expect(component.state().isValid).to.equal(true);
    });

    it('should have the correct isValid state when the value is not required ' +
        'and it is blank', () => {

        const required = false;
        const value = '';

        const component = shallow(<TextInput required={required} value={value} />);

        expect(component.state().isValid).to.equal(true);
    });

    it('should have the correct validationMessage state when the value is ' +
        'required and it is blank', () => {

        const required = true;
        const value = '';
        const expectedMessage = `${description} is required`;

        const component = shallow(<TextInput
            required={required}
            description={description}
            value={value}
        />);

        expect(component.state().validationMessage).to.equal(expectedMessage);
    });

    it('should have the correct validationMessage state when the value is ' +
        'required and it is not blank', () => {

        const required = true;
        const value = 'this is valid';

        const component = shallow(<TextInput
            required={required}
            description={description}
            value={value}
        />);

        expect(component.state().validationMessage).to.equal(null);
    });

    it('should have the correct validationMessage state when the value is ' +
        'not required and it is blank', () => {

        const required = false;
        const value = '';

        const component = shallow(<TextInput
            required={required}
            description={description}
            value={value}
        />);

        expect(component.state().validationMessage).to.equal(null);
    });
});

/* *****************************************************************************
in terms of basic markup, the TextInput component
    should be a div.form-group
    should include an input[type="text"]
    should include an input.form-control
    should include a label with the appropriate text if a label is specified
    should include a placeholder if a placeholder is specified
*/
describe('in terms of basic markup, the TextInput component', () => {

    const label = 'something something';
    const placeholder = 'blah-de-blah';

    it('should be a div.form-group', () => {
        const component = shallow(<TextInput />);

        expect(component.is('div.form-group')).to.equal(true);
    });

    it('should include an input[type="text"]', () => {
        const component = render(<TextInput />);

        expect(component.find('div.form-group input').length).to.equal(1);
        expect(component.find('div.form-group input[type="text"]').length).to.equal(1);
    });

    it('should include an input.form-control', () => {
        const component = render(<TextInput />);

        expect(component.find('div.form-group input').length).to.equal(1);
        expect(component.find('div.form-group input.form-control').length).to.equal(1);
    });

    it('should include a label with the appropriate text if a label is specified', () => {
        const component = render(<TextInput label={label} />);

        expect(component.find('div.form-group label').length).to.equal(1);
        expect(component.find('div.form-group label').text()).to.equal(label);
    });

    it('should include a placeholder if a placeholder is specified', () => {
        const component = render(<TextInput placeholder={placeholder} />);

        expect(component.find('div.form-group input').prop('placeholder')).to.equal(placeholder);
    });

});
<commit_msg>updated specs for editing<commit_after>// the eslint "no-unused-expressions" rule is triggered by the `expect`
// assertions in the tests. So, in order to avoid problems with eslint, I'm
// disabling that rule in this file
//
/* eslint-disable no-unused-expressions */

import React from 'react';
import { shallow, render, mount } from 'enzyme';
import chai from 'chai';
import sinon from 'sinon';
import TextInput from './TextInput';

const expect = chai.expect;

/* *****************************************************************************
on editing, the TextInput component
    should call onChange with the new value
    should have the new value in the component state
    should call onValidation with the correct arguments when required=true and
        the new value is valid
    should have updated validation state when required=true and the new value is valid
    should call onValidation with the correct arguments when required=true and
        the new value is blank
    should have updated validation state when required=true and the new value is blank
    should call onValidation with the correct arguments when required=false and
        the new value is blank
    should have updated validation state when required=false and the new value is blank
*/
describe('on editing, the TextInput component', () => {

    const description = 'wtf?';

    const expectedMessage = `${description} is required`;

    it('should call onChange with the new value', () => {

        const onChange = sinon.spy();

        const initialValue = 'anchovies';
        const newValue = 'pineapple';

        const component = mount(<TextInput value={initialValue} onChange={onChange} />);

        expect(onChange.callCount).to.equal(0, 'initial call count');

        component.find('input').simulate('change', {
            target: { value: newValue }
        });

        expect(onChange.callCount).to.equal(1, ' after call count');
        expect(onChange.calledWith(newValue)).to.equal(true, 'called with');
    });

    it('should have the new value in the component state', () => {
        const initialValue = 'anchovies';
        const newValue = 'pineapple';

        const component = mount(<TextInput value={initialValue} />);

        component.find('input').simulate('change', {
            target: { value: newValue }
        });

        expect(component.state().value).to.equal(newValue);
    });

    it('should call onValidation with the correct arguments when ' +
        'required=true and the new value is valid', () => {

        const onValidation = sinon.spy();

        const required = true;
        const initialValue = 'anchovies';
        const newValue = 'pineapple';

        const component = mount(<TextInput
            required={required}
            value={initialValue}
            onValidation={onValidation}
        />);

        expect(onValidation.callCount).to.equal(1);

        component.find('input').simulate('change', {
            target: { value: newValue }
        });

        expect(onValidation.callCount).to.equal(2, 'callcount after');

        expect(onValidation.secondCall.args[0]).to.equal(true, 'hasValidated');
        expect(onValidation.secondCall.args[1]).to.equal(true, 'isValid');
        expect(onValidation.secondCall.args[2]).to.equal(null, 'validationMessage');
    });

    it('should have updated validation state when required=true and the new ' +
        'value is valid', () => {

        const required = true;
        const initialValue = 'anchovies';
        const newValue = 'pineapple';

        const component = mount(<TextInput required={required} value={initialValue} />);

        component.find('input').simulate('change', {
            target: { value: newValue }
        });

        expect(component.state().hasValidated).to.equal(true, 'hasValidated');
        expect(component.state().isValid).to.equal(true, 'isValid');
        expect(component.state().validationMessage).to.equal(null, 'validationMessage');
    });

    it('should call onValidation with the correct arguments when ' +
        'required=true and the new value is blank', () => {

        const onValidation = sinon.spy();

        const required = true;
        const initialValue = 'anchovies';
        const newValue = '';

        const component = mount(<TextInput
            description={description}
            required={required}
            value={initialValue}
            onValidation={onValidation}
        />);

        expect(onValidation.callCount).to.equal(1);

        component.find('input').simulate('change', {
            target: { value: newValue }
        });

        expect(onValidation.callCount).to.equal(2, 'callcount after');

        expect(onValidation.secondCall.args[0]).to.equal(true, 'hasValidated');
        expect(onValidation.secondCall.args[1]).to.equal(false, 'isValid');
        expect(onValidation.secondCall.args[2]).to.equal(expectedMessage, 'validationMessage');
    });

    it('should have updated validation state when required=true and the new ' +
        'value is blank', () => {

        const required = true;
        const initialValue = 'anchovies';
        const newValue = '';

        const component = mount(<TextInput
            description={description}
            required={required}
            value={initialValue}
        />);

        component.find('input').simulate('change', {
            target: { value: newValue }
        });

        expect(component.state().hasValidated).to.equal(true, 'hasValidated');
        expect(component.state().isValid).to.equal(false, 'isValid');
        expect(component.state().validationMessage).to.equal(expectedMessage, 'validationMessage');
    });

    it('should call onValidation with the correct arguments when ' +
        'required=false and the new value is blank', () => {

        const onValidation = sinon.spy();

        const required = false;
        const initialValue = 'anchovies';
        const newValue = '';

        const component = mount(<TextInput
            description={description}
            required={required}
            value={initialValue}
            onValidation={onValidation}
        />);

        expect(onValidation.callCount).to.equal(1);

        component.find('input').simulate('change', {
            target: { value: newValue }
        });

        expect(onValidation.callCount).to.equal(2, 'callcount after');

        expect(onValidation.secondCall.args[0]).to.equal(true, 'hasValidated');
        expect(onValidation.secondCall.args[1]).to.equal(true, 'isValid');
        expect(onValidation.secondCall.args[2]).to.equal(null, 'validationMessage');
    });

    it('should have updated validation state when required=false and the new ' +
        'value is blank', () => {
        const required = false;
        const initialValue = 'anchovies';
        const newValue = '';

        const component = mount(<TextInput
            description={description}
            required={required}
            value={initialValue}
        />);

        component.find('input').simulate('change', {
            target: { value: newValue }
        });

        expect(component.state().hasValidated).to.equal(true, 'hasValidated');
        expect(component.state().isValid).to.equal(true, 'isValid');
        expect(component.state().validationMessage).to.equal(null, 'validationMessage');
    });

});

/* *****************************************************************************
on initialization, the TextInput component
    should not call onChange
    should call onValidation with the correct arguments when required=true and value is valid
    should call onValidation with the correct arguments when required=true and value is blank
    should call onValidation with the correct arguments when required=false and value is blank
*/
describe('on initialization, the TextInput component', () => {

    const description = 'description';

    it('should not call onChange', () => {

        const onChange = sinon.spy();

        const value = 'anchovies';

        mount(<TextInput value={value} onChange={onChange} />);

        expect(onChange.callCount).to.equal(0);
    });

    it('should call onValidation with the correct arguments when ' +
        'required=true and value is valid', () => {

        const onValidation = sinon.spy();

        const required = true;
        const value = 'anchovies';

        mount(<TextInput required={required} value={value} onValidation={onValidation} />);

        expect(onValidation.callCount).to.equal(1);
        expect(onValidation.calledWith(false, true, null)).to.equal(true);
    });

    it('should call onValidation with the correct arguments when ' +
        'required=true and value is blank', () => {

        const onValidation = sinon.spy();

        const required = true;
        const value = '';

        const expectedMessage = `${description} is required`;

        mount(<TextInput
            required={required}
            description={description}
            value={value}
            onValidation={onValidation}
        />);

        expect(onValidation.callCount).to.equal(1);
        expect(onValidation.calledWith(false, false, expectedMessage))
            .to.equal(true, 'calledWith');
    });

    it('should call onValidation with the correct arguments when ' +
        'required=false and value is blank', () => {

        const onValidation = sinon.spy();

        const required = false;
        const value = '';

        mount(<TextInput
            required={required}
            description={description}
            value={value}
            onValidation={onValidation}
        />);

        expect(onValidation.callCount).to.equal(1);
        expect(onValidation.calledWith(false, true, null)).to.equal(true, 'calledWith');
    });

});

/* *****************************************************************************
the state of the TextInput component
    should be initialized with the correct value
    should have the correct hasValidated state
    should have the correct isValid state when the value is required and it's blank
    should have the correct isValid state when the value is required and it's not blank
    should have the correct isValid state when the value is not required and it is blank
    should have the correct validationMessage state when the value is required and it's blank
    should have the correct validationMessage state when the value is required
        and it's not blank
    should have the correct validationMessage state when the value is not
        required and it is blank
*/
describe('the state of the TextInput component', () => {

    const description = 'Some value';

    it('should be initialized with the correct value', () => {
        const values = [
            { value: '', expected: '' },
            { value: null, expected: '' },
            { value: undefined, expected: '' },
            { value: 'aaa', expected: 'aaa' },
            { value: 42, expected: '42' },
        ];

        let component;

        values.forEach((val) => {
            component = shallow(<TextInput value={val.value} />);

            expect(component.state().value).to.equal(val.expected);
        });
    });

    it('should have the correct hasValidated state', () => {
        const value = 'anchovies';

        const component = shallow(<TextInput value={value} />);

        expect(component.state().hasValidated).to.equal(false);
    });

    it('should have the correct isValid state when the value is required and ' +
        'it is blank', () => {

        const required = true;
        const value = '';

        const component = shallow(<TextInput required={required} value={value} />);

        expect(component.state().isValid).to.equal(false);
    });

    it('should have the correct isValid state when the value is required and ' +
        'it is not blank', () => {

        const required = true;
        const value = 'pizza';

        const component = shallow(<TextInput required={required} value={value} />);

        expect(component.state().isValid).to.equal(true);
    });

    it('should have the correct isValid state when the value is not required ' +
        'and it is blank', () => {

        const required = false;
        const value = '';

        const component = shallow(<TextInput required={required} value={value} />);

        expect(component.state().isValid).to.equal(true);
    });

    it('should have the correct validationMessage state when the value is ' +
        'required and it is blank', () => {

        const required = true;
        const value = '';
        const expectedMessage = `${description} is required`;

        const component = shallow(<TextInput
            required={required}
            description={description}
            value={value}
        />);

        expect(component.state().validationMessage).to.equal(expectedMessage);
    });

    it('should have the correct validationMessage state when the value is ' +
        'required and it is not blank', () => {

        const required = true;
        const value = 'this is valid';

        const component = shallow(<TextInput
            required={required}
            description={description}
            value={value}
        />);

        expect(component.state().validationMessage).to.equal(null);
    });

    it('should have the correct validationMessage state when the value is ' +
        'not required and it is blank', () => {

        const required = false;
        const value = '';

        const component = shallow(<TextInput
            required={required}
            description={description}
            value={value}
        />);

        expect(component.state().validationMessage).to.equal(null);
    });
});

/* *****************************************************************************
in terms of basic markup, the TextInput component
    should be a div.form-group
    should include an input[type="text"]
    should include an input.form-control
    should include a label with the appropriate text if a label is specified
    should include a placeholder if a placeholder is specified
*/
describe('in terms of basic markup, the TextInput component', () => {

    const label = 'something something';
    const placeholder = 'blah-de-blah';

    it('should be a div.form-group', () => {
        const component = shallow(<TextInput />);

        expect(component.is('div.form-group')).to.equal(true);
    });

    it('should include an input[type="text"]', () => {
        const component = render(<TextInput />);

        expect(component.find('div.form-group input').length).to.equal(1);
        expect(component.find('div.form-group input[type="text"]').length).to.equal(1);
    });

    it('should include an input.form-control', () => {
        const component = render(<TextInput />);

        expect(component.find('div.form-group input').length).to.equal(1);
        expect(component.find('div.form-group input.form-control').length).to.equal(1);
    });

    it('should include a label with the appropriate text if a label is specified', () => {
        const component = render(<TextInput label={label} />);

        expect(component.find('div.form-group label').length).to.equal(1);
        expect(component.find('div.form-group label').text()).to.equal(label);
    });

    it('should include a placeholder if a placeholder is specified', () => {
        const component = render(<TextInput placeholder={placeholder} />);

        expect(component.find('div.form-group input').prop('placeholder')).to.equal(placeholder);
    });

});
<|endoftext|>


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

'use strict';

var fs             = require('fs');
var url            = require('url');
var extend         = require('extend');
var requestFactory = require('../../lib/requestwrapper');
var solr           = require('solr-client');
var pick           = require('object.pick');
var omit           = require('object.omit');
var isStream       = require('isstream');

function RetrieveAndRank(options) {
  var serviceDefaults = {
    version: 'v1',
    url: 'https://gateway.watsonplatform.net/retrieve-and-rank/api'
  };

  // Extend default options with user provided options
  this._options = extend(serviceDefaults, options);
}

/**
 * Creates a ranker
 */
RetrieveAndRank.prototype.createRanker = function(params, callback) {
  params = params || {};

  if (!params || !params.training_data) {
    callback(new Error('Missing required parameters: training_data'));
    return;
  }
  if ((typeof params.training_data !== 'string') && (!isStream(params.training_data))) {
    callback(new Error('training_data needs to be a String or Stream'));
    return;
  }

  var parameters = {
    options: {
      url: '/v1/rankers',
      method: 'POST',
      json: true,
      formData: {
        training_data: params.training_data,
        training_metadata: JSON.stringify(omit(params, ['training_data']))
      }
    },
    defaultOptions: this._options
  };
  return requestFactory(parameters, callback);
};

/**
 * Returns the ranked candidates
 */
RetrieveAndRank.prototype.rank = function(params, callback) {
  params = params || {};

  if (!params || !params.answer_data) {
    callback(new Error('Missing required parameters: answer_data'));
    return;
  }
  if ((typeof params.answer_data !== 'string') && (!isStream(params.answer_data))) {
    callback(new Error('answer_data needs to be a String or Stream'));
    return;
  }

  var parameters = {
    options: {
      url: '/v1/rankers/{ranker_id}/rank',
      method: 'POST',
      json: true,
      formData: {
        answer_data: params.answer_data,
        answer_metadata: JSON.stringify(omit(params, ['answer_data']))
      },
      path: pick(params, ['ranker_id'])
    },
    requiredParams: ['ranker_id'],
    defaultOptions: this._options
  };
  return requestFactory(parameters, callback);
};

/**
 * Returns the training status of the ranker
 */
RetrieveAndRank.prototype.rankerStatus = function(params, callback) {
  params = params || {};

  var parameters = {
    options: {
      url: '/v1/rankers/{ranker_id}',
      method: 'GET',
      json: true,
      path: params
    },
    requiredParams: ['ranker_id'],
    defaultOptions: this._options
  };

  return requestFactory(parameters, callback);
};

/**
 * Retrieves the list of rankers for the user
 */
RetrieveAndRank.prototype.listRankers = function(params, callback) {
  var parameters = {
    options: {
      url: '/v1/rankers',
      method: 'GET',
      json: true
    },
    defaultOptions: this._options
  };

  return requestFactory(parameters, callback);
};

/**
 * Deletes a ranker
 */
RetrieveAndRank.prototype.deleteRanker = function(params, callback) {
  params = params || {};

  var parameters = {
    options: {
      url: '/v1/rankers/{ranker_id}',
      method: 'DELETE',
      path: params,
      json: true
    },
    requiredParams: ['ranker_id'],
    defaultOptions: this._options
  };

  return requestFactory(parameters, callback);
};

// Solr cluster lifecycle operations

/**
 * Lists all Solr clusters associated with the service instance.
 *
 * @param params An Object representing the parameters for this service call.
 *   This request currently does not require any parameters.
 *
 * @param callback The callback.
 */
RetrieveAndRank.prototype.listClusters = function(params, callback) {
  return sendRequest('GET', solrClustersPath(), this._options, callback);
};

/**
 * Creates a Solr cluster.
 *
 * @param params An Object representing the parameters for this service call.
 *   Optional params
 *     - cluster_name: name to use for identifying the cluster in responses
 *     - cluster_size: size of the cluster to create
 *
 * @param callback The callback.
 */
RetrieveAndRank.prototype.createCluster = function(params, callback) {
  // Avoid modifying the global options by making a deep copy
  var createOptions = JSON.parse(JSON.stringify(this._options));
  createOptions.body = JSON.stringify(params || {});
  if (createOptions.headers === undefined) {
    createOptions.headers = {};
  }
  createOptions.headers['Content-Type'] = 'application/json';

  return sendRequest('POST', solrClustersPath(), createOptions, callback);
};

/**
 * Checks whether the specified Solr cluster is ready for use.
 *
 * @param params An Object representing the parameters for this service call.
 *   Required params:
 *     - cluster_id: the ID of the Solr cluster to poll
 *
 * @param callback The callback.
 */
RetrieveAndRank.prototype.pollCluster = function(params, callback) {
  if (!params || !params.cluster_id) {
    return callback(new Error('Missing required parameter: cluster_id'));
  }
  return sendRequest('GET', solrClusterPath(params.cluster_id), this._options, callback);
};

/**
 * Deletes a Solr cluster.
 *
 * @param params An Object representing the parameters for this service call.
 *   Required params:
 *     - cluster_id: the ID of the Solr cluster to delete
 *
 * @param callback The callback.
 */
RetrieveAndRank.prototype.deleteCluster = function(params, callback) {
  if (!params || !params.cluster_id) {
    return callback(new Error('Missing required parameter: cluster_id'));
  }
  return sendRequest('DELETE', solrClusterPath(params.cluster_id), this._options, callback);
};

// Solr config operations

/**
 * Lists the configuration sets in ZooKeeper.
 *
 * @param params An Object representing the parameters for this service call.
 *   Required params:
 *     - cluster_id: the ID of the Solr cluster to get configuration from
 *
 * @param callback The callback.
 */
RetrieveAndRank.prototype.listConfigs = function(params, callback) {
  if (!params || !params.cluster_id) {
    return callback(new Error('Missing required parameter: cluster_id'));
  }
  return sendRequest('GET', solrConfigsPath(params.cluster_id), this._options, callback);
};

/**
 * Upload a configuration set to ZooKeeper.
 *
 * @param params An Object representing the parameters for this service call.
 *   Required params:
 *     - cluster_id: the ID of the Solr cluster to upload configuration to
 *     - config_name: the name of the config in ZooKeeper referenced when creating a collection
 *     - config_zip_path: the ZIP file to upload
 *
 * @param callback The callback.
 */
RetrieveAndRank.prototype.uploadConfig = function(params, callback) {
  if (!params || !params.cluster_id) {
    return callback(new Error('Missing required parameter: cluster_id'));
  } else if (!params.config_name) {
    return callback(new Error('Missing required parameter: config_name'));
  } else if (!params.config_zip_path) {
    return callback(new Error('Missing required parameter: config_zip_path'));
  }

  // Avoid modifying the global options by making a deep copy
  var uploadOptions = JSON.parse(JSON.stringify(this._options));
  uploadOptions.body = fs.readFileSync(params.config_zip_path);
  if (uploadOptions.headers === undefined) {
    uploadOptions.headers = {};
  }
  uploadOptions.headers['Content-Type'] = 'application/zip';

  return sendRequest('POST', solrConfigPath(params.cluster_id, params.config_name), uploadOptions, callback);
};

/**
 * Get a configuration set from ZooKeeper as a .zip file.
 *
 * @param params An Object representing the parameters for this service call.
 *   Required params:
 *     - cluster_id: the ID of the Solr cluster to get configuration from
 *     - config_name: the name of the config in ZooKeeper
 *
 * @param callback The callback.
 */
RetrieveAndRank.prototype.getConfig = function(params, callback) {
  if (!params || !params.cluster_id) {
    return callback(new Error('Missing required parameter: cluster_id'));
  } else if (!params.config_name) {
    return callback(new Error('Missing required parameter: config_name'));
  }
  return sendRequest('GET', solrConfigPath(params.cluster_id, params.config_name), this._options, callback);
};

/**
 * Delete a configuration set in ZooKeeper.
 *
 * @param params An Object representing the parameters for this service call.
 *   Required params:
 *     - cluster_id: the ID of the Solr cluster to delete configuration from
 *     - config_name: the name of the config in ZooKeeper
 *
 * @param callback The callback.
 */
RetrieveAndRank.prototype.deleteConfig = function(params, callback) {
  if (!params || !params.cluster_id) {
    return callback(new Error('Missing required parameter: cluster_id'));
  } else if (!params.config_name) {
    return callback(new Error('Missing required parameter: config_name'));
  }
  return sendRequest('DELETE', solrConfigPath(params.cluster_id, params.config_name), this._options, callback);
};

// Solr collection operations

/**
 * List all collections for a Solr cluster.
 *
 * @param params An Object representing the parameters for this service call.
 *   Required params:
 *     - cluster_id: the ID of the Solr cluster to list collections from
 *
 * @param callback The callback.
 */
RetrieveAndRank.prototype.listCollections = function(params, callback) {
  if (!params || !params.cluster_id) {
    return callback(new Error('Missing required parameter: cluster_id'));
  }
  return sendRequest('GET', adminCollectionsPath(params.cluster_id, 'LIST'), this._options, callback);
};

/**
 * Create a Solr collection.
 *
 * @param params An Object representing the parameters for this service call.
 *   Required params:
 *     - cluster_id: the ID of the Solr cluster to create the collection on
 *     - collection_name: the name of the collection to create
 *     - config_name: the name of the config in ZooKeeper
 *
 * @param callback The callback.
 */
RetrieveAndRank.prototype.createCollection = function(params, callback) {
  if (!params || !params.cluster_id) {
    return callback(new Error('Missing required parameter: cluster_id'));
  } else if (!params.collection_name) {
    return callback(new Error('Missing required parameter: collection_name'));
  } else if (!params.config_name) {
    return callback(new Error('Missing required parameter: config_name'));
  }
  var createPath = adminCollectionsPath(params.cluster_id, 'CREATE');
  createPath += '&name=' + params.collection_name + '&collection.configName=' + params.config_name;
  return sendRequest('GET', createPath, this._options, callback);
};

/**
 * Delete a Solr collection.
 *
 * @param params An Object representing the parameters for this service call.
 *   Required params:
 *     - cluster_id: the ID of the Solr cluster to delete the collection on
 *     - collection_name: the name of the collection to delete
 *
 * @param callback The callback.
 */
RetrieveAndRank.prototype.deleteCollection = function(params, callback) {
  if (!params || !params.cluster_id) {
    return callback(new Error('Missing required parameter: cluster_id'));
  } else if (!params.collection_name) {
    return callback(new Error('Missing required parameter: collection_name'));
  }
  var deletePath = adminCollectionsPath(params.cluster_id, 'DELETE') + '&name=' + params.collection_name;
  return sendRequest('GET', deletePath, this._options, callback);
};

/**
 * Get a Solr client for indexing and searching documents.
 * See https://github.com/lbdremy/solr-node-client for documentation and examples.
 *
 * @param params An Object representing the parameters for this service call.
 *   Required params:
 *     - cluster_id: the ID of the Solr cluster to delete the collection on
 *     - collection_name: the name of the collection for indexing/searching
 */
RetrieveAndRank.prototype.createSolrClient = function(params) {
  if (!params || !params.cluster_id) {
    throw new Error('Missing required parameter: cluster_id');
  } else if (!params.collection_name) {
    throw new Error('Missing required parameter: collection_name');
  }
  var serviceUrl = url.parse(this._options.url);
  var apiPath = serviceUrl.path === '/' ? '' : serviceUrl.path || '';

  var solrClient = solr.createClient({
    host: serviceUrl.hostname,
    path: apiPath + solrClusterPath(params.cluster_id) + '/solr',
    port: serviceUrl.port || '443',
    secure: true,
    core: params.collection_name
  });

  if (this._options.api_key) {
    var auth = new Buffer(this._options.api_key, 'base64').toString('ascii').split(':');
    solrClient.basicAuth(auth[0], auth[1]);
  }
  return solrClient;
};

// Helper methods

function solrClustersPath() {
  return '/v1/solr_clusters';
}

function solrClusterPath(clusterId) {
  return solrClustersPath() +'/' + clusterId;
}

function solrConfigsPath(clusterId) {
  return solrClusterPath(clusterId) + '/config';
}

function solrConfigPath(clusterId, configName) {
  return solrConfigsPath(clusterId) + '/' + configName;
}

function adminCollectionsPath(clusterId, action) {
  return solrClusterPath(clusterId) + '/solr/admin/collections?action=' + action;
}

function sendRequest(method, url, options, callback) {
  var parameters = {
    options: {
      method: method,
      url: url
    },
    defaultOptions: options
  };
  return requestFactory(parameters, callback);
}

module.exports = RetrieveAndRank;
<commit_msg>Update v1.js<commit_after>/**
 * Copyright 2015 IBM Corp. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';

var fs             = require('fs');
var url            = require('url');
var extend         = require('extend');
var requestFactory = require('../../lib/requestwrapper');
var solr           = require('solr-client');
var pick           = require('object.pick');
var omit           = require('object.omit');
var isStream       = require('isstream');

function RetrieveAndRank(options) {
  var serviceDefaults = {
    version: 'v1',
    url: 'https://gateway.watsonplatform.net/retrieve-and-rank/api'
  };

  // Extend default options with user provided options
  this._options = extend(serviceDefaults, options);
}

/**
 * Creates a ranker
 */
RetrieveAndRank.prototype.createRanker = function(params, callback) {
  params = params || {};

  if (!params || !params.training_data) {
    callback(new Error('Missing required parameters: training_data'));
    return;
  }
  if ((typeof params.training_data !== 'string') && (!isStream(params.training_data))) {
    callback(new Error('training_data needs to be a String or Stream'));
    return;
  }

  var parameters = {
    options: {
      url: '/v1/rankers',
      method: 'POST',
      json: true,
      formData: {
        training_data: params.training_data,
        training_metadata: params.training_metadata || JSON.stringify(omit(params, ['training_data']))
      }
    },
    defaultOptions: this._options
  };
  return requestFactory(parameters, callback);
};

/**
 * Returns the ranked candidates
 */
RetrieveAndRank.prototype.rank = function(params, callback) {
  params = params || {};

  if (!params || !params.answer_data) {
    callback(new Error('Missing required parameters: answer_data'));
    return;
  }
  if ((typeof params.answer_data !== 'string') && (!isStream(params.answer_data))) {
    callback(new Error('answer_data needs to be a String or Stream'));
    return;
  }

  var parameters = {
    options: {
      url: '/v1/rankers/{ranker_id}/rank',
      method: 'POST',
      json: true,
      formData: {
        answer_data: params.answer_data,
        answer_metadata: JSON.stringify(omit(params, ['answer_data']))
      },
      path: pick(params, ['ranker_id'])
    },
    requiredParams: ['ranker_id'],
    defaultOptions: this._options
  };
  return requestFactory(parameters, callback);
};

/**
 * Returns the training status of the ranker
 */
RetrieveAndRank.prototype.rankerStatus = function(params, callback) {
  params = params || {};

  var parameters = {
    options: {
      url: '/v1/rankers/{ranker_id}',
      method: 'GET',
      json: true,
      path: params
    },
    requiredParams: ['ranker_id'],
    defaultOptions: this._options
  };

  return requestFactory(parameters, callback);
};

/**
 * Retrieves the list of rankers for the user
 */
RetrieveAndRank.prototype.listRankers = function(params, callback) {
  var parameters = {
    options: {
      url: '/v1/rankers',
      method: 'GET',
      json: true
    },
    defaultOptions: this._options
  };

  return requestFactory(parameters, callback);
};

/**
 * Deletes a ranker
 */
RetrieveAndRank.prototype.deleteRanker = function(params, callback) {
  params = params || {};

  var parameters = {
    options: {
      url: '/v1/rankers/{ranker_id}',
      method: 'DELETE',
      path: params,
      json: true
    },
    requiredParams: ['ranker_id'],
    defaultOptions: this._options
  };

  return requestFactory(parameters, callback);
};

// Solr cluster lifecycle operations

/**
 * Lists all Solr clusters associated with the service instance.
 *
 * @param params An Object representing the parameters for this service call.
 *   This request currently does not require any parameters.
 *
 * @param callback The callback.
 */
RetrieveAndRank.prototype.listClusters = function(params, callback) {
  return sendRequest('GET', solrClustersPath(), this._options, callback);
};

/**
 * Creates a Solr cluster.
 *
 * @param params An Object representing the parameters for this service call.
 *   Optional params
 *     - cluster_name: name to use for identifying the cluster in responses
 *     - cluster_size: size of the cluster to create
 *
 * @param callback The callback.
 */
RetrieveAndRank.prototype.createCluster = function(params, callback) {
  // Avoid modifying the global options by making a deep copy
  var createOptions = JSON.parse(JSON.stringify(this._options));
  createOptions.body = JSON.stringify(params || {});
  if (createOptions.headers === undefined) {
    createOptions.headers = {};
  }
  createOptions.headers['Content-Type'] = 'application/json';

  return sendRequest('POST', solrClustersPath(), createOptions, callback);
};

/**
 * Checks whether the specified Solr cluster is ready for use.
 *
 * @param params An Object representing the parameters for this service call.
 *   Required params:
 *     - cluster_id: the ID of the Solr cluster to poll
 *
 * @param callback The callback.
 */
RetrieveAndRank.prototype.pollCluster = function(params, callback) {
  if (!params || !params.cluster_id) {
    return callback(new Error('Missing required parameter: cluster_id'));
  }
  return sendRequest('GET', solrClusterPath(params.cluster_id), this._options, callback);
};

/**
 * Deletes a Solr cluster.
 *
 * @param params An Object representing the parameters for this service call.
 *   Required params:
 *     - cluster_id: the ID of the Solr cluster to delete
 *
 * @param callback The callback.
 */
RetrieveAndRank.prototype.deleteCluster = function(params, callback) {
  if (!params || !params.cluster_id) {
    return callback(new Error('Missing required parameter: cluster_id'));
  }
  return sendRequest('DELETE', solrClusterPath(params.cluster_id), this._options, callback);
};

// Solr config operations

/**
 * Lists the configuration sets in ZooKeeper.
 *
 * @param params An Object representing the parameters for this service call.
 *   Required params:
 *     - cluster_id: the ID of the Solr cluster to get configuration from
 *
 * @param callback The callback.
 */
RetrieveAndRank.prototype.listConfigs = function(params, callback) {
  if (!params || !params.cluster_id) {
    return callback(new Error('Missing required parameter: cluster_id'));
  }
  return sendRequest('GET', solrConfigsPath(params.cluster_id), this._options, callback);
};

/**
 * Upload a configuration set to ZooKeeper.
 *
 * @param params An Object representing the parameters for this service call.
 *   Required params:
 *     - cluster_id: the ID of the Solr cluster to upload configuration to
 *     - config_name: the name of the config in ZooKeeper referenced when creating a collection
 *     - config_zip_path: the ZIP file to upload
 *
 * @param callback The callback.
 */
RetrieveAndRank.prototype.uploadConfig = function(params, callback) {
  if (!params || !params.cluster_id) {
    return callback(new Error('Missing required parameter: cluster_id'));
  } else if (!params.config_name) {
    return callback(new Error('Missing required parameter: config_name'));
  } else if (!params.config_zip_path) {
    return callback(new Error('Missing required parameter: config_zip_path'));
  }

  // Avoid modifying the global options by making a deep copy
  var uploadOptions = JSON.parse(JSON.stringify(this._options));
  uploadOptions.body = fs.readFileSync(params.config_zip_path);
  if (uploadOptions.headers === undefined) {
    uploadOptions.headers = {};
  }
  uploadOptions.headers['Content-Type'] = 'application/zip';

  return sendRequest('POST', solrConfigPath(params.cluster_id, params.config_name), uploadOptions, callback);
};

/**
 * Get a configuration set from ZooKeeper as a .zip file.
 *
 * @param params An Object representing the parameters for this service call.
 *   Required params:
 *     - cluster_id: the ID of the Solr cluster to get configuration from
 *     - config_name: the name of the config in ZooKeeper
 *
 * @param callback The callback.
 */
RetrieveAndRank.prototype.getConfig = function(params, callback) {
  if (!params || !params.cluster_id) {
    return callback(new Error('Missing required parameter: cluster_id'));
  } else if (!params.config_name) {
    return callback(new Error('Missing required parameter: config_name'));
  }
  return sendRequest('GET', solrConfigPath(params.cluster_id, params.config_name), this._options, callback);
};

/**
 * Delete a configuration set in ZooKeeper.
 *
 * @param params An Object representing the parameters for this service call.
 *   Required params:
 *     - cluster_id: the ID of the Solr cluster to delete configuration from
 *     - config_name: the name of the config in ZooKeeper
 *
 * @param callback The callback.
 */
RetrieveAndRank.prototype.deleteConfig = function(params, callback) {
  if (!params || !params.cluster_id) {
    return callback(new Error('Missing required parameter: cluster_id'));
  } else if (!params.config_name) {
    return callback(new Error('Missing required parameter: config_name'));
  }
  return sendRequest('DELETE', solrConfigPath(params.cluster_id, params.config_name), this._options, callback);
};

// Solr collection operations

/**
 * List all collections for a Solr cluster.
 *
 * @param params An Object representing the parameters for this service call.
 *   Required params:
 *     - cluster_id: the ID of the Solr cluster to list collections from
 *
 * @param callback The callback.
 */
RetrieveAndRank.prototype.listCollections = function(params, callback) {
  if (!params || !params.cluster_id) {
    return callback(new Error('Missing required parameter: cluster_id'));
  }
  return sendRequest('GET', adminCollectionsPath(params.cluster_id, 'LIST'), this._options, callback);
};

/**
 * Create a Solr collection.
 *
 * @param params An Object representing the parameters for this service call.
 *   Required params:
 *     - cluster_id: the ID of the Solr cluster to create the collection on
 *     - collection_name: the name of the collection to create
 *     - config_name: the name of the config in ZooKeeper
 *
 * @param callback The callback.
 */
RetrieveAndRank.prototype.createCollection = function(params, callback) {
  if (!params || !params.cluster_id) {
    return callback(new Error('Missing required parameter: cluster_id'));
  } else if (!params.collection_name) {
    return callback(new Error('Missing required parameter: collection_name'));
  } else if (!params.config_name) {
    return callback(new Error('Missing required parameter: config_name'));
  }
  var createPath = adminCollectionsPath(params.cluster_id, 'CREATE');
  createPath += '&name=' + params.collection_name + '&collection.configName=' + params.config_name;
  return sendRequest('GET', createPath, this._options, callback);
};

/**
 * Delete a Solr collection.
 *
 * @param params An Object representing the parameters for this service call.
 *   Required params:
 *     - cluster_id: the ID of the Solr cluster to delete the collection on
 *     - collection_name: the name of the collection to delete
 *
 * @param callback The callback.
 */
RetrieveAndRank.prototype.deleteCollection = function(params, callback) {
  if (!params || !params.cluster_id) {
    return callback(new Error('Missing required parameter: cluster_id'));
  } else if (!params.collection_name) {
    return callback(new Error('Missing required parameter: collection_name'));
  }
  var deletePath = adminCollectionsPath(params.cluster_id, 'DELETE') + '&name=' + params.collection_name;
  return sendRequest('GET', deletePath, this._options, callback);
};

/**
 * Get a Solr client for indexing and searching documents.
 * See https://github.com/lbdremy/solr-node-client for documentation and examples.
 *
 * @param params An Object representing the parameters for this service call.
 *   Required params:
 *     - cluster_id: the ID of the Solr cluster to delete the collection on
 *     - collection_name: the name of the collection for indexing/searching
 */
RetrieveAndRank.prototype.createSolrClient = function(params) {
  if (!params || !params.cluster_id) {
    throw new Error('Missing required parameter: cluster_id');
  } else if (!params.collection_name) {
    throw new Error('Missing required parameter: collection_name');
  }
  var serviceUrl = url.parse(this._options.url);
  var apiPath = serviceUrl.path === '/' ? '' : serviceUrl.path || '';

  var solrClient = solr.createClient({
    host: serviceUrl.hostname,
    path: apiPath + solrClusterPath(params.cluster_id) + '/solr',
    port: serviceUrl.port || '443',
    secure: true,
    core: params.collection_name
  });

  if (this._options.api_key) {
    var auth = new Buffer(this._options.api_key, 'base64').toString('ascii').split(':');
    solrClient.basicAuth(auth[0], auth[1]);
  }
  return solrClient;
};

// Helper methods

function solrClustersPath() {
  return '/v1/solr_clusters';
}

function solrClusterPath(clusterId) {
  return solrClustersPath() +'/' + clusterId;
}

function solrConfigsPath(clusterId) {
  return solrClusterPath(clusterId) + '/config';
}

function solrConfigPath(clusterId, configName) {
  return solrConfigsPath(clusterId) + '/' + configName;
}

function adminCollectionsPath(clusterId, action) {
  return solrClusterPath(clusterId) + '/solr/admin/collections?action=' + action;
}

function sendRequest(method, url, options, callback) {
  var parameters = {
    options: {
      method: method,
      url: url
    },
    defaultOptions: options
  };
  return requestFactory(parameters, callback);
}

module.exports = RetrieveAndRank;
<|endoftext|>


<commit_before>// If something's wrong with the options (generally, if they aren't set yet),
// clear all options and reset to defaults.
// TODO: This is duplicated in options.js. Combine these somehow.
// TODO: Updating isn't exactly graceful. Find a better way to do this.
if (!localStorage.clockMode) localStorage.clockMode = false;
if (!localStorage.teams) localStorage.teams = undefined;
if (!localStorage.name) localStorage.name = true;
if (!localStorage.location) localStorage.location = true;
if (!localStorage.vetting) localStorage.vetting = true;
if (!localStorage.optionsButton) localStorage.optionsButton = true;

// Alias major page elements so we don't have to keep getting them by ID
var el = {
	number: document.getElementById('number'),
	name: document.getElementById('name'),
	location: document.getElementById('location'),
	bg: document.getElementById('background'),
	options: document.getElementById('options')
};

function importData() {
	// If custom teams value is truthy, valid, and not an empty array,
	// replace the data.js teams array with the custom one from localStorage.
	if (localStorage.teams !== '[]' && localStorage.teams !== '' && localStorage.teams !== 'undefined') {
		// TEMPORARY: If the 'teams' data has brackets around it, remove the brackets.
		if (localStorage.teams[0] === '[' || localStorage.teams[localStorage.teams.length - 1] === ']') {
			localStorage.teams = localStorage.teams.substring(1, localStorage.teams.length - 1);
		}
		teams = JSON.parse('[' + localStorage.teams + ']');
	}
}

function loadTeam() {
	// Initialize int-type team number variable
	var teamNum;
	// and string-type team number var (used more for clock mode)
	var teamNumStr;

	// If clock mode is turned on, get the team that corresponds to the current time.
	if (JSON.parse(localStorage.clockMode)) {
		// Get date, current hour, and current minute.
		var d = new Date();
		var hours = d.getHours();
		var minutes = d.getMinutes();
		// Construct team number
		teamNum = hours * 100 + minutes;
		if (minutes < 10) {
			minutes = '0' + minutes;
		}
		teamNumStr = hours + ':' + minutes;
	} else {
		// If clock mode is off, choose a random team.
		teamNum = teams[parseInt(Math.random() * teams.length)];
		teamNumStr = teamNum.toString();
	}

	var teamInfo = {
		num: teamNum,
		numStr: teamNumStr
	}

	return teamInfo;
}

function showOptions() {
	// If the option to show the options button is on, show it.
	// (If it's not, it will remain hidden as normal.)
	if (JSON.parse(localStorage.optionsButton)) el.options.style.display = 'block';
}

// Import data.js
importData();

// Get and load team number
// Converts to clock mode if appropriate
teamInfo = loadTeam();

// Show options icon if appropriate
showOptions();

// Get data from TBA
getTBAData(teamInfo);

function getTBAData(teamInfo) {
    try {
        // Create request to get data from TBA
        var req = new XMLHttpRequest();
        // Get data for team
        req.open('GET', 'https://www.thebluealliance.com/api/v2/team/frc' + teamInfo.num + '?X-TBA-App-Id=erikboesen:frcnewtab:v1.0');
        // Send empty data for conclusion
        req.send();
        // When the data is ready, figure out where an image is and get ready to set it as the background.
        req.onreadystatechange = function() {
            if (req.readyState == 4 && req.status == 200) {
                // Parse the newly-fetched team data into JSON to get it ready to be used
                team = JSON.parse(req.responseText);

                // Put team number on page if the team exists
                el.number.innerHTML = '' + teamInfo.numStr + '';

                // If name showing is enabled,
                if (JSON.parse(localStorage.name)) {
                    // If team has a website that isn't "Coming Soon"
                    if (team.website && team.website !== 'Coming Soon') {
                        // Make the name a link to the website
                        el.name.innerHTML = '' + team.nickname + '';
                    } else {
                        // Insert name without a link
                        el.name.innerHTML = team.nickname;
                    }
                } else {
                    // Otherwise, delete the name element from the DOM.
                    el.name.parentNode.removeChild(el.name);
                }

                // If location showing is on,
                if (JSON.parse(localStorage.location)) {
                    // Then set the location onscreen.
                    el.location.innerHTML = team.location;
                } else {
                    // Otherwise, remove the location element from the DOM.
                    el.location.parentNode.removeChild(el.location);
                }

                if (JSON.parse(localStorage.dynamicTitle)) {
                    // Display the team name and number in titlebar.
                    el.number.insertAdjacentHTML('beforeend', '<title>' + teamInfo.num + ' - ' + team.nickname + '</title>');
                }
            } else if (req.readyState == 4 && req.status != 200) {
                // If the team doesn't exist (for clock mode)
                el.number.innerHTML = teamInfo.numStr;
            }
        };
    } catch (e) {}
}

// Initialize background image source as a randomly-chosen fallback image.
var src = '../res/bg/' + (Math.floor(Math.random() * 10) + 1) + '.jpg';
try {
	// Make a new request to get list of team media from TBA
	var mediaReq = new XMLHttpRequest();

	// When the data is ready,
	mediaReq.onreadystatechange = function() {
		if (mediaReq.readyState == 4 && mediaReq.status == 200) {
			// Parse data for processing
			var media = [];
			try {
				media = JSON.parse(mediaReq.responseText);
			} catch (e) {}
			console.log(media.length);
			if (media.length > 0) {
				var images = [];
				// Go through every piece of media and add high quality images to an array
				for (i = 0; i < media.length; i++) {
					// Find media that's an image

					if ((media[i].type === 'imgur' || media[i].type === 'cdphotothread')) {
						// If vetting is on and the image is high quality add it to the array
						if (JSON.parse(localStorage.vetting)) {
							if (media[i].preferred) images.push(i);
						} else {
							images.push(i); // Otherwise add every image
						}
					}
				}
				// Grab a random image if vetting is enabled; otherwise grab the first image
				var target = JSON.parse(localStorage.vetting) ? images[Math.floor(Math.random()*images.length)] : 0;
				// Check where the media is sourced from. Use this to build a link to the image.
				if (target !== null) {
					switch (media[target].type) {
						case 'imgur':
							src = 'http://i.imgur.com/' + media[target].foreign_key + '.png';
							break;
						case 'cdphotothread':
							src = 'https://www.chiefdelphi.com/media/img/' + media[target].details.image_partial;
							break;
					}
				}
			}
			// Put the image into the background (see below).
			renderImage();
		} else if (mediaReq.readyState == 4 && mediaReq.status != 200) {
			// Media GET request failed - render a stock image
			renderImage();
		}
	};
	// Actually open the request.
	mediaReq.open('GET', 'https://www.thebluealliance.com/api/v2/team/frc' + teamNum + '/media?X-TBA-App-Id=erikboesen:frcnewtab:v1.0');
	mediaReq.send();
} catch (e) {
	// If there's a problem, just render the fallback image we created a link to earlier.
	renderImage();
}

function renderImage() {
	console.log('Rendering', src);
	// Create image. This will be used to check if the image is smaller than the window.
	var img = new Image();
	// Give it the proper source.
	img.src = src;
	// When the source is done loading,
	img.onload = function() {
		// Check if the image is smaller than the window.
		if (img.naturalWidth >= window.innerWidth) {
			// If it is, blur the background image.
			el.bg.style['-webkit-filter'] = 'none';
		}
	};

	// Set the src of the real background image.
	el.bg.style.backgroundImage = 'url(' + src + ')';
}

// TODO: Make this whole process more efficient.
<commit_msg>Fix indents<commit_after>// If something's wrong with the options (generally, if they aren't set yet),
// clear all options and reset to defaults.
// TODO: This is duplicated in options.js. Combine these somehow.
// TODO: Updating isn't exactly graceful. Find a better way to do this.
if (!localStorage.clockMode) localStorage.clockMode = false;
if (!localStorage.teams) localStorage.teams = undefined;
if (!localStorage.name) localStorage.name = true;
if (!localStorage.location) localStorage.location = true;
if (!localStorage.vetting) localStorage.vetting = true;
if (!localStorage.optionsButton) localStorage.optionsButton = true;

// Alias major page elements so we don't have to keep getting them by ID
var el = {
	number: document.getElementById('number'),
	name: document.getElementById('name'),
	location: document.getElementById('location'),
	bg: document.getElementById('background'),
	options: document.getElementById('options')
};

function importData() {
	// If custom teams value is truthy, valid, and not an empty array,
	// replace the data.js teams array with the custom one from localStorage.
	if (localStorage.teams !== '[]' && localStorage.teams !== '' && localStorage.teams !== 'undefined') {
		// TEMPORARY: If the 'teams' data has brackets around it, remove the brackets.
		if (localStorage.teams[0] === '[' || localStorage.teams[localStorage.teams.length - 1] === ']') {
			localStorage.teams = localStorage.teams.substring(1, localStorage.teams.length - 1);
		}
		teams = JSON.parse('[' + localStorage.teams + ']');
	}
}

function loadTeam() {
	// Initialize int-type team number variable
	var teamNum;
	// and string-type team number var (used more for clock mode)
	var teamNumStr;

	// If clock mode is turned on, get the team that corresponds to the current time.
	if (JSON.parse(localStorage.clockMode)) {
		// Get date, current hour, and current minute.
		var d = new Date();
		var hours = d.getHours();
		var minutes = d.getMinutes();
		// Construct team number
		teamNum = hours * 100 + minutes;
		if (minutes < 10) {
			minutes = '0' + minutes;
		}
		teamNumStr = hours + ':' + minutes;
	} else {
		// If clock mode is off, choose a random team.
		teamNum = teams[parseInt(Math.random() * teams.length)];
		teamNumStr = teamNum.toString();
	}

	var teamInfo = {
		num: teamNum,
		numStr: teamNumStr
	}

	return teamInfo;
}

function showOptions() {
	// If the option to show the options button is on, show it.
	// (If it's not, it will remain hidden as normal.)
	if (JSON.parse(localStorage.optionsButton)) el.options.style.display = 'block';
}

function getTBAData(teamInfo) {
	try {
		// Create request to get data from TBA
		var req = new XMLHttpRequest();
		// Get data for team
		req.open('GET', 'https://www.thebluealliance.com/api/v2/team/frc' + teamInfo.num + '?X-TBA-App-Id=erikboesen:frcnewtab:v1.0');
		// Send empty data for conclusion
		req.send();
		// When the data is ready, figure out where an image is and get ready to set it as the background.
		req.onreadystatechange = function() {
			if (req.readyState == 4 && req.status == 200) {
				// Parse the newly-fetched team data into JSON to get it ready to be used
				team = JSON.parse(req.responseText);

				// Put team number on page if the team exists
				el.number.innerHTML = '' + teamInfo.numStr + '';

				// If name showing is enabled,
				if (JSON.parse(localStorage.name)) {
					// If team has a website that isn't "Coming Soon"
					if (team.website && team.website !== 'Coming Soon') {
						// Make the name a link to the website
						el.name.innerHTML = '' + team.nickname + '';
					} else {
						// Insert name without a link
						el.name.innerHTML = team.nickname;
					}
				} else {
					// Otherwise, delete the name element from the DOM.
					el.name.parentNode.removeChild(el.name);
				}

				// If location showing is on,
				if (JSON.parse(localStorage.location)) {
					// Then set the location onscreen.
					el.location.innerHTML = team.location;
				} else {
					// Otherwise, remove the location element from the DOM.
					el.location.parentNode.removeChild(el.location);
				}

				if (JSON.parse(localStorage.dynamicTitle)) {
					// Display the team name and number in titlebar.
					el.number.insertAdjacentHTML('beforeend', '<title>' + teamInfo.num + ' - ' + team.nickname + '</title>');
				}
			} else if (req.readyState == 4 && req.status != 200) {
				// If the team doesn't exist (for clock mode)
				el.number.innerHTML = teamInfo.numStr;
			}
		};
	} catch (e) {}
}

// Import data.js
importData();

// Get and load team number
// Converts to clock mode if appropriate
teamInfo = loadTeam();

// Show options icon if appropriate
showOptions();

// Get data from TBA
getTBAData(teamInfo);

// Initialize background image source as a randomly-chosen fallback image.
var src = '../res/bg/' + (Math.floor(Math.random() * 10) + 1) + '.jpg';
try {
	// Make a new request to get list of team media from TBA
	var mediaReq = new XMLHttpRequest();

	// When the data is ready,
	mediaReq.onreadystatechange = function() {
		if (mediaReq.readyState == 4 && mediaReq.status == 200) {
			// Parse data for processing
			var media = [];
			try {
				media = JSON.parse(mediaReq.responseText);
			} catch (e) {}
			console.log(media.length);
			if (media.length > 0) {
				var images = [];
				// Go through every piece of media and add high quality images to an array
				for (i = 0; i < media.length; i++) {
					// Find media that's an image

					if ((media[i].type === 'imgur' || media[i].type === 'cdphotothread')) {
						// If vetting is on and the image is high quality add it to the array
						if (JSON.parse(localStorage.vetting)) {
							if (media[i].preferred) images.push(i);
						} else {
							images.push(i); // Otherwise add every image
						}
					}
				}
				// Grab a random image if vetting is enabled; otherwise grab the first image
				var target = JSON.parse(localStorage.vetting) ? images[Math.floor(Math.random()*images.length)] : 0;
				// Check where the media is sourced from. Use this to build a link to the image.
				if (target !== null) {
					switch (media[target].type) {
						case 'imgur':
							src = 'http://i.imgur.com/' + media[target].foreign_key + '.png';
							break;
						case 'cdphotothread':
							src = 'https://www.chiefdelphi.com/media/img/' + media[target].details.image_partial;
							break;
					}
				}
			}
			// Put the image into the background (see below).
			renderImage();
		} else if (mediaReq.readyState == 4 && mediaReq.status != 200) {
			// Media GET request failed - render a stock image
			renderImage();
		}
	};
	// Actually open the request.
	mediaReq.open('GET', 'https://www.thebluealliance.com/api/v2/team/frc' + teamNum + '/media?X-TBA-App-Id=erikboesen:frcnewtab:v1.0');
	mediaReq.send();
} catch (e) {
	// If there's a problem, just render the fallback image we created a link to earlier.
	renderImage();
}

function renderImage() {
	console.log('Rendering', src);
	// Create image. This will be used to check if the image is smaller than the window.
	var img = new Image();
	// Give it the proper source.
	img.src = src;
	// When the source is done loading,
	img.onload = function() {
		// Check if the image is smaller than the window.
		if (img.naturalWidth >= window.innerWidth) {
			// If it is, blur the background image.
			el.bg.style['-webkit-filter'] = 'none';
		}
	};

	// Set the src of the real background image.
	el.bg.style.backgroundImage = 'url(' + src + ')';
}

// TODO: Make this whole process more efficient.
<|endoftext|>


<commit_before>import BABYLON from 'babylonjs';
import React from 'react';
import Relay from 'react-relay';

import TileHoverInAnimationFactory from '../babylonjs/animations/tiles/TileHoverInAnimationFactory';
import TileHoverOutAnimationFactory from '../babylonjs/animations/tiles/TileHoverOutAnimationFactory';

import ChangeTileTypeMutation from '../mutations/ChangeTileTypeMutation';

const VIEWPORT_SIZE = 5;
const TILE_TYPES = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; //TODO get it from backend

const SIZE_X = 20;
const SIZE_Y = 20;

class Map extends React.Component {
    render() {
        const component = this;

        const tileset = this.props.tileset;

        return (
            <canvas className="map" ref="mapRef"></canvas>
        );
    }
    _onTileTypeChanged(tile, newTileType){
        this.props.relay.commitUpdate(
            new ChangeTileTypeMutation({
                tile: tile,
                tileType: newTileType
            })
        );
    }
    componentDidMount(){
        const mapRef = this.refs.mapRef;

        this._initBabylon(mapRef);
    }
    _initBabylon(canvas){
        const component = this;

        // Load the BABYLON 3D engine
        const engine = new BABYLON.Engine(canvas, true);
        this._engine = engine;

        const scene = this._createScene(canvas);
        scene.ambientColor = new BABYLON.Color3(1, 1, 1);
        scene.lightsEnabled = false;
        scene.debugLayer.show();
        scene.createOrUpdateSelectionOctree();

        this._updateAspectRatio(scene);

        // Register a render loop to repeatedly render the scene
        engine.runRenderLoop(function () {
            scene.render();
        });

        // Watch for browser/canvas resize events
        window.addEventListener('resize', function () {
            component._updateAspectRatio.apply(component, [scene]);

            component._engine.resize();
        });
    }
    _updateAspectRatio(scene){
        const camera = this._mainCamera;

        let height = this.refs.mapRef.height;
        const width = this.refs.mapRef.width;

        if (height == 0) height = 1;

        /* Calculate aspect ratio --------------------------------------------------------------------*/
        const aspectRatio = width / height;

        if (width >= height)
        {
            camera.orthoLeft = -VIEWPORT_SIZE*aspectRatio;
            camera.orthoRight = VIEWPORT_SIZE*aspectRatio;
            camera.orthoBottom = -VIEWPORT_SIZE;
            camera.orthoTop = VIEWPORT_SIZE;
        }
        else
        {
            camera.orthoLeft = -VIEWPORT_SIZE;
            camera.orthoRight = VIEWPORT_SIZE;
            camera.orthoBottom = -VIEWPORT_SIZE*aspectRatio;
            camera.orthoTop = VIEWPORT_SIZE*aspectRatio;
        }
    }
    componentWillUnmount(){
        const engine = this._engine;
        engine.dispose();
    }
    _createScene(canvas){
        const engine = this._engine;
        const scene =  new BABYLON.Scene(engine);
        scene.shadowsEnabled = false;

        const camera = new BABYLON.TargetCamera('mainCamera', new BABYLON.Vector3(0, 0, -10), scene);
       camera.mode = BABYLON.Camera.ORTHOGRAPHIC_CAMERA;

        // attach the camera to the canvas
        camera.attachControl(canvas, false);

        this._mainCamera = camera;

        const materials =this._createMaterials(scene);
        this._createTiles(scene, materials);

        return scene;
    }
    _createTiles(scene, materials){
        var xSize = 20;
        var ySize = 20;

        this._firstTileMesh = null;
        this._firstTileBorderMesh = null;

        for(var tileY = 0; tileY < SIZE_Y; tileY++){
            this._createTilesRow(scene, materials, tileY);
        }
    }
    _createTilesRow(scene, materials, tileY){
        for (var tileX = 0; tileX < SIZE_X; tileX++){
            const tileTag = this._createTileTag(tileX, tileY);

            const tileMesh = this._createTileMesh(scene, tileTag);
            const tileBorderMesh = this._createTileBorderMesh(scene, tileTag);
            tileBorderMesh.parent = tileMesh;

            const materialIdx = Math.floor(Math.random() * TILE_TYPES.length);

            const origMaterial = materials[materialIdx];
            tileMesh.material = origMaterial;
            tileMesh.position.x = tileX;
            tileMesh.position.y = tileY;

            this._setUpTileActions(scene, tileMesh, origMaterial);
        }
    }
    _createTileTag(tileX, tileY){
        return 'tile-' + tileX + '-' + tileY;
    }
    _createTileMesh(scene, tileTag){
        let tileMesh = null;

        if(this._firstTileMesh === null){
            this._firstTileMesh = BABYLON.Mesh.CreatePlane(tileTag, 1.0, scene);
            tileMesh = this._firstTileMesh;
        }else{
            tileMesh = this._firstTileMesh.clone(tileTag);
        }
        return tileMesh;
    }
    _createTileBorderMesh(scene, tileTag){
        let tileBorderMesh = null;

        const borderMeshTag = 'border-' + tileTag;

        if(this._firstTileBorderMesh === null){
            this._firstTileBorderMesh = BABYLON.Mesh.CreateLines(borderMeshTag, [
                new BABYLON.Vector3(-0.5, -0.5, 0),
                new BABYLON.Vector3(0.5, -0.5, 0),
                new BABYLON.Vector3(0.5, 0.5, 0),
                new BABYLON.Vector3(-0.5, 0.5, 0),
                new BABYLON.Vector3(-0.5, -0.5, 0)
            ], scene);

            tileBorderMesh = this._firstTileBorderMesh;
        }else{
            tileBorderMesh = this._firstTileBorderMesh.clone(borderMeshTag);
        }
        return tileBorderMesh;
    }
    _setUpTileActions(scene, tileMesh, origMaterial){
        tileMesh.actionManager = new BABYLON.ActionManager(scene);

        this._setUpTileHoverInAction(scene, tileMesh, origMaterial);
        this._setUpTileHoverOutAction(scene, tileMesh, origMaterial);
    }
    _setUpTileHoverInAction(scene, tileMesh,origMaterial){
        const hoverInAnimation = TileHoverInAnimationFactory.create('anim-hover-in-' + tileMesh.tag);
        tileMesh.animations.push(hoverInAnimation);

        const hoverInAction = new BABYLON.ExecuteCodeAction(
            BABYLON.ActionManager.OnPointerOverTrigger,
            function(){
                if(tileMesh.material === origMaterial){
                    //Clone the material
                    tileMesh.material = origMaterial.clone();
                }

                scene.stopAnimation(tileMesh);
                scene.beginDirectAnimation(tileMesh, [hoverInAnimation], 0, 100, false, 1);
            }
        );

        tileMesh.actionManager.registerAction(hoverInAction);
    }
    _setUpTileHoverOutAction(scene, tileMesh, origMaterial){
        const hoverOutAnimation = TileHoverOutAnimationFactory.create('anim-hover-out-' + tileMesh.tag);
        tileMesh.animations.push(hoverOutAnimation);

        const hoverOutAction = new BABYLON.ExecuteCodeAction(
            BABYLON.ActionManager.OnPointerOutTrigger,
            function(){
                if(tileMesh.material === origMaterial){
                    //Clone the material
                    tileMesh.material = origMaterial.clone();
                }

                scene.stopAnimation(tileMesh);
                scene.beginDirectAnimation(tileMesh, [hoverOutAnimation], 0, 100, false);
            }
        );

        tileMesh.actionManager.registerAction(hoverOutAction);
    }
    _createMaterials(scene){
        return TILE_TYPES.map(function(tileType, index){
            const material = new BABYLON.StandardMaterial('material-tile-type-' + index, scene);
            material.ambientColor = new BABYLON.Color3(1 / (index + 1), 1 / (index + 1), 1 / (index + 1));
            material.freeze();

            return material;
        });
    }
}

export default Relay.createContainer(Map, {
    fragments: {
        tileset: () => Relay.QL`
            fragment on Tileset {
                rows{
                    id,
                    tiles {
                        id,
                        type,
                        ${ChangeTileTypeMutation.getFragment('tile')}
                    },
                },
                availableTileTypes
            }
        `
    }
});
<commit_msg>Create dummy update method<commit_after>import BABYLON from 'babylonjs';
import React from 'react';
import Relay from 'react-relay';

import TileHoverInAnimationFactory from '../babylonjs/animations/tiles/TileHoverInAnimationFactory';
import TileHoverOutAnimationFactory from '../babylonjs/animations/tiles/TileHoverOutAnimationFactory';

import ChangeTileTypeMutation from '../mutations/ChangeTileTypeMutation';

const VIEWPORT_SIZE = 5;
const TILE_TYPES = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; //TODO get it from backend

const SIZE_X = 20;
const SIZE_Y = 20;

class Map extends React.Component {
    constructor(){
        super();

        this._oldProps = {};
    }
    render() {
        const component = this;

        this._update();

        return (
            <canvas className="map" ref="mapRef"></canvas>
        );
    }
    _update(){
        //TODO
    }
    _onTileTypeChanged(tile, newTileType){
        this.props.relay.commitUpdate(
            new ChangeTileTypeMutation({
                tile: tile,
                tileType: newTileType
            })
        );
    }
    componentDidMount(){
        const mapRef = this.refs.mapRef;

        this._initBabylon(mapRef);
    }
    _initBabylon(canvas){
        const component = this;

        // Load the BABYLON 3D engine
        const engine = new BABYLON.Engine(canvas, true);
        this._engine = engine;

        const scene = this._createScene(canvas);
        scene.ambientColor = new BABYLON.Color3(1, 1, 1);
        scene.lightsEnabled = false;
        scene.debugLayer.show();
        scene.createOrUpdateSelectionOctree();

        this._updateAspectRatio(scene);

        // Register a render loop to repeatedly render the scene
        engine.runRenderLoop(function () {
            scene.render();
        });

        // Watch for browser/canvas resize events
        window.addEventListener('resize', function () {
            component._updateAspectRatio.apply(component, [scene]);

            component._engine.resize();
        });
    }
    _updateAspectRatio(scene){
        const camera = this._mainCamera;

        let height = this.refs.mapRef.height;
        const width = this.refs.mapRef.width;

        if (height == 0) height = 1;

        /* Calculate aspect ratio --------------------------------------------------------------------*/
        const aspectRatio = width / height;

        if (width >= height)
        {
            camera.orthoLeft = -VIEWPORT_SIZE*aspectRatio;
            camera.orthoRight = VIEWPORT_SIZE*aspectRatio;
            camera.orthoBottom = -VIEWPORT_SIZE;
            camera.orthoTop = VIEWPORT_SIZE;
        }
        else
        {
            camera.orthoLeft = -VIEWPORT_SIZE;
            camera.orthoRight = VIEWPORT_SIZE;
            camera.orthoBottom = -VIEWPORT_SIZE*aspectRatio;
            camera.orthoTop = VIEWPORT_SIZE*aspectRatio;
        }
    }
    componentWillUnmount(){
        const engine = this._engine;
        engine.dispose();
    }
    _createScene(canvas){
        const engine = this._engine;
        const scene =  new BABYLON.Scene(engine);
        scene.shadowsEnabled = false;

        const camera = new BABYLON.TargetCamera('mainCamera', new BABYLON.Vector3(0, 0, -10), scene);
       camera.mode = BABYLON.Camera.ORTHOGRAPHIC_CAMERA;

        // attach the camera to the canvas
        camera.attachControl(canvas, false);

        this._mainCamera = camera;

        const materials =this._createMaterials(scene);
        this._createTiles(scene, materials);

        return scene;
    }
    _createTiles(scene, materials){
        var xSize = 20;
        var ySize = 20;

        this._firstTileMesh = null;
        this._firstTileBorderMesh = null;

        for(var tileY = 0; tileY < SIZE_Y; tileY++){
            this._createTilesRow(scene, materials, tileY);
        }
    }
    _createTilesRow(scene, materials, tileY){
        for (var tileX = 0; tileX < SIZE_X; tileX++){
            const tileTag = this._createTileTag(tileX, tileY);

            const tileMesh = this._createTileMesh(scene, tileTag);
            const tileBorderMesh = this._createTileBorderMesh(scene, tileTag);
            tileBorderMesh.parent = tileMesh;

            const materialIdx = Math.floor(Math.random() * TILE_TYPES.length);

            const origMaterial = materials[materialIdx];
            tileMesh.material = origMaterial;
            tileMesh.position.x = tileX;
            tileMesh.position.y = tileY;

            this._setUpTileActions(scene, tileMesh, origMaterial);
        }
    }
    _createTileTag(tileX, tileY){
        return 'tile-' + tileX + '-' + tileY;
    }
    _createTileMesh(scene, tileTag){
        let tileMesh = null;

        if(this._firstTileMesh === null){
            this._firstTileMesh = BABYLON.Mesh.CreatePlane(tileTag, 1.0, scene);
            tileMesh = this._firstTileMesh;
        }else{
            tileMesh = this._firstTileMesh.clone(tileTag);
        }
        return tileMesh;
    }
    _createTileBorderMesh(scene, tileTag){
        let tileBorderMesh = null;

        const borderMeshTag = 'border-' + tileTag;

        if(this._firstTileBorderMesh === null){
            this._firstTileBorderMesh = BABYLON.Mesh.CreateLines(borderMeshTag, [
                new BABYLON.Vector3(-0.5, -0.5, 0),
                new BABYLON.Vector3(0.5, -0.5, 0),
                new BABYLON.Vector3(0.5, 0.5, 0),
                new BABYLON.Vector3(-0.5, 0.5, 0),
                new BABYLON.Vector3(-0.5, -0.5, 0)
            ], scene);

            tileBorderMesh = this._firstTileBorderMesh;
        }else{
            tileBorderMesh = this._firstTileBorderMesh.clone(borderMeshTag);
        }
        return tileBorderMesh;
    }
    _setUpTileActions(scene, tileMesh, origMaterial){
        tileMesh.actionManager = new BABYLON.ActionManager(scene);

        this._setUpTileHoverInAction(scene, tileMesh, origMaterial);
        this._setUpTileHoverOutAction(scene, tileMesh, origMaterial);
    }
    _setUpTileHoverInAction(scene, tileMesh,origMaterial){
        const hoverInAnimation = TileHoverInAnimationFactory.create('anim-hover-in-' + tileMesh.tag);
        tileMesh.animations.push(hoverInAnimation);

        const hoverInAction = new BABYLON.ExecuteCodeAction(
            BABYLON.ActionManager.OnPointerOverTrigger,
            function(){
                if(tileMesh.material === origMaterial){
                    //Clone the material
                    tileMesh.material = origMaterial.clone();
                }

                scene.stopAnimation(tileMesh);
                scene.beginDirectAnimation(tileMesh, [hoverInAnimation], 0, 100, false, 1);
            }
        );

        tileMesh.actionManager.registerAction(hoverInAction);
    }
    _setUpTileHoverOutAction(scene, tileMesh, origMaterial){
        const hoverOutAnimation = TileHoverOutAnimationFactory.create('anim-hover-out-' + tileMesh.tag);
        tileMesh.animations.push(hoverOutAnimation);

        const hoverOutAction = new BABYLON.ExecuteCodeAction(
            BABYLON.ActionManager.OnPointerOutTrigger,
            function(){
                if(tileMesh.material === origMaterial){
                    //Clone the material
                    tileMesh.material = origMaterial.clone();
                }

                scene.stopAnimation(tileMesh);
                scene.beginDirectAnimation(tileMesh, [hoverOutAnimation], 0, 100, false);
            }
        );

        tileMesh.actionManager.registerAction(hoverOutAction);
    }
    _createMaterials(scene){
        return TILE_TYPES.map(function(tileType, index){
            const material = new BABYLON.StandardMaterial('material-tile-type-' + index, scene);
            material.ambientColor = new BABYLON.Color3(1 / (index + 1), 1 / (index + 1), 1 / (index + 1));
            material.freeze();

            return material;
        });
    }
    shouldComponentUpdate(nextProps, nextState) {
        this._oldProps = nextProps;

        return true;
    }
}

export default Relay.createContainer(Map, {
    fragments: {
        tileset: () => Relay.QL`
            fragment on Tileset {
                rows{
                    id,
                    tiles {
                        id,
                        type,
                        ${ChangeTileTypeMutation.getFragment('tile')}
                    },
                },
                availableTileTypes
            }
        `
    }
});
<|endoftext|>