<commit_before>loadPhastJS('public/resources-loader.js', function (phast) {

    QUnit.module('ResourcesLoader', function (hooks) {

        var client,
            documentParams = [];

        hooks.before(function () {
            document.querySelectorAll('[data-phast-params]').forEach(function (el) {
                var params = phast.ResourceLoader.RequestParams.fromString(el.dataset.phastParams);
                documentParams.push(params);
            });
        });

        hooks.beforeEach(function () {
            client = new phast.ResourceLoader.BundlerServiceClient('phast.php?service=bundler');
        });

        QUnit.module('Request', function (hooks) {
            var request;

            hooks.beforeEach(function () {
                request = new phast.ResourceLoader.Request();
            });

            QUnit.test('Check setting/getting handlers', function (assert) {

                assert.strictEqual(request.onsuccess, undefined, 'Initially onsuccess is undefined');
                assert.strictEqual(request.onerror, undefined, 'Initially onerror is undefined');

                var onerror = function () {},
                    onsuccess = function () {};

                request.onsuccess = onsuccess;
                request.onerror = onerror;

                assert.strictEqual(request.onsuccess, onsuccess, 'The onsuccess sets correctly');
                assert.strictEqual(request.onerror, onerror, 'The onerror sets correctly');
            });

            QUnit.test('Check resolving/rejecting', function (assert) {
                var called = false;
                request.onsuccess = function (responseText) {
                    assert.equal('my css', responseText, 'Resolves with appropriate argument');
                    called = true;
                };
                request.success('my css');
                assert.ok(called, 'onsuccess callback was called');
                called = false;

                request = new phast.ResourceLoader.Request();
                request.onerror = function () {
                    called = true;
                };
                request.error();
                assert.ok(called, 'onerror callback was called');
            });

            QUnit.test('Check not dying when resolving with no handlers', function (assert) {
                request.error();
                request.success();
                assert.expect(0);
            });

            QUnit.test('Check instant callback if already resolved/rejected', function (assert) {
                var successCalled = false,
                    errorCalled = false;

                request.success('my js');
                request.onsuccess = function (responseText) {
                    assert.equal('my js', responseText, 'Response text is ok');
                    successCalled = true;
                };
                request.onerror = function () {
                    errorCalled = true;
                };

                assert.ok(successCalled, 'Success callback was called on success');
                assert.notOk(errorCalled, 'Error callback was not called on success');

                successCalled = false;
                errorCalled = false;

                request = new phast.ResourceLoader.Request();
                request.error();
                request.onsuccess = function () {
                    successCalled = true;
                };
                request.onerror = function () {
                    errorCalled = true;
                };

                assert.notOk(successCalled, 'Success was not called on error');
                assert.ok(errorCalled, 'Error was not called on error');

            });

            QUnit.test('Check resolving/rejecting only once', function (assert) {
                assert.expect(0);
                var done = assert.async(2);
                var resolved = new phast.ResourceLoader.Request();
                var rejected = new phast.ResourceLoader.Request();
                resolved.onsuccess = function () {
                    done();
                };
                rejected.onerror = function () {
                    done();
                };
                resolved.success('');
                resolved.success('');
                resolved.error();

                rejected.error();
                rejected.error();
                rejected.success('');
            });

            QUnit.test('Check calling onend on resolving', function (assert) {
                assert.expect(0);
                var done = assert.async();
                request.onsuccess = function () {};
                request.onend = function () {
                    done();
                };
                request.success('');
            });

            QUnit.test('Check calling onend on error', function (assert) {
                assert.expect(0);
                var done = assert.async();
                request.onerror = function() {};
                request.onend = function () {
                    done();
                };
                request.error();
            });

        });

        QUnit.module('RequestParams', function () {

            QUnit.test('Check creating from string', function (assert) {
                var string = JSON.stringify({key1: 'value1', key2: 'value2'});
                var params = phast.ResourceLoader.RequestParams.fromString(string);
                assert.equal(params.key1, 'value1', 'key1 is set right');
                assert.equal(params.key2, 'value2', 'key2 is set right');
                assert.notOk(params.isFaulty(), 'It is not faulty');
            });

            QUnit.test('Check bad params', function (assert) {
                var params = phast.ResourceLoader.RequestParams.fromString('invalid json');
                assert.ok(params.isFaulty(), 'It is faulty');
            });
        });

        QUnit.module('BundlerServiceClient', function () {

            QUnit.test('Test fetching files', function (assert) {
                checkFetchingFiles(assert, client);
            });

            QUnit.test('Test fetching from dedicated service file', function (assert) {
                var done = assert.async();
                var client = new phast.ResourceLoader.BundlerServiceClient('bundler.php');
                var request = client.get(documentParams[0]);
                request.then(function (responseText) {
                    assert.equal('text-1-contents', responseText, 'Fetched correctly');
                    done();
                }).catch(function () {
                    assert.ok(false, 'Does not reject');
                    done();
                });
            });

            QUnit.test('Test error on faulty params', function (assert) {
                var done = assert.async();
                var params = phast.ResourceLoader.RequestParams.fromString('invalid json');
                var request = client.get(params);
                request.then(function () {
                    assert.ok(false, 'Does not reject');
                    done();
                }).catch(function () {
                    assert.ok(true, 'Rejects on invalid json');
                    done();
                });
            });

            QUnit.test('Test handling service errors', function (assert) {
                var iterations = 3;
                var done = assert.async(iterations);
                for (var i = 0; i < iterations; i++) {
                    var params = phast.ResourceLoader.RequestParams.fromObject({fail: 'yes'});
                    var request = client.get(params);
                    request.then(function () {
                        assert.ok(false, 'Rejects');
                        done();
                    }).catch(function () {
                        assert.ok(true, 'Rejects');
                        done();
                    });
                }
            });

            QUnit.test('Test handling network errors', function (assert) {
                var iterations = 3;
                var done = assert.async(iterations);
                var client = new phast.ResourceLoader.BundlerServiceClient('does-not-exist');
                for (var i = 0; i < iterations; i++) {
                    var params = phast.ResourceLoader.RequestParams.fromObject({});
                    var request = client.get(params);
                    request.then(function () {
                        assert.ok(false, 'Rejects');
                        done();
                    }).catch(function () {
                        assert.ok(true, 'Rejects');
                        done();
                    });
                }
            });

            QUnit.test('Test ignoring exceptions while dispatching responses', function (assert) {
                var done = assert.async();
                var badParams = {
                    src: 'some-url',
                    token: 'some-token'
                };
                var request1 = client.get(documentParams[0]);
                var request2 = client.get(phast.ResourceLoader.RequestParams.fromObject(badParams));
                var request3 = client.get(documentParams[1]);
                request1.then(function () {
                    throw 'an error';
                });
                request2.catch(function () {
                    throw 'another error';
                });
                request3.then(function () {
                    assert.ok(true, 'Resolves');
                    done();
                });
            });

            QUnit.test('Test ignoring exceptions while dispatching network error', function (assert) {
                var done = assert.async();
                var client = new phast.ResourceLoader.BundlerServiceClient('bad-url');
                var request1 = client.get(documentParams[0]);
                var request2 = client.get(documentParams[1]);
                request1.catch(function () {
                    throw 'an error';
                });
                request2.catch(function () {
                    assert.ok(true, 'Gets executed');
                    done();
                });
            });
        });

        QUnit.module('IndexedDBResourceCache', function (hooks) {

            var Cache = phast.ResourceLoader.IndexedDBResourceCache;
            Cache.cleanupProbability = 0;

            hooks.beforeEach(function () {
                Cache.closeDB();
                Cache.dbName = 'test' + Date.now();
            });

            hooks.afterEach(function () {
                indexedDB.deleteDatabase(Cache.dbName);
            });

            QUnit.test('Check fetching files with client', function (assert) {
                assert.timeout(2000);
                var done = assert.async(documentParams.length);
                var cache = new Cache(client);
                checkFetchingFiles(assert, cache, done);
            });

            QUnit.test('Check retrieval from cache', function (assert) {
                assert.timeout(2000);
                var done = assert.async(documentParams.length);
                var cache = new Cache(client);
                var filesFetched = 0;
                documentParams.map(function (params) {
                    var request = cache.get(params);
                    request.then(function () {
                        filesFetched++;
                    });
                });
                wait(
                    assert,
                    function () {
                        return filesFetched === documentParams.length;
                    },
                    function () {
                        var cache = new Cache({
                            get: function () {
                                throw 'Calling client when should have fetched from cache';
                            }
                        });
                        checkFetchingFiles(assert, cache, done);
                    }
                );
            });

            QUnit.test('Check handling missing store when retrieving from', function (assert) {
                assert.timeout(2000);
                var done = assert.async();
                var dbOpenRequest = Cache.openDB(false);
                dbOpenRequest.then(function (db) {
                    db.close();
                    return new Cache(client).get(documentParams[0]);
                }).then(function (responseText) {
                    assert.equal('text-1-contents', responseText);
                    done();
                });;
            });

            QUnit.test('Check cache cleanup', function (assert) {
                assert.timeout(2000);
                assert.expect(0);
                var done = assert.async(3);
                var slowClient = {
                    get: function (params) {
                        return new Promise(function (resolve) {
                            setTimeout(function () {
                                console.log('Token:', params.token);
                                resolve('Token: ' + params.token);
                            }, 100);
                        })
                    }
                };
                var dummyClient = {
                    get: function () {
                        return Promise.reject();
                    }
                };

                var caching = new Cache(slowClient);
                caching.get({token: 1}).then(function () {
                    return caching.get({token: 2});
                }).then(function () {
                    return caching.get({token: 3});
                }).then(function () {
                    Cache.maybeCleanup(90, 1);

                    setTimeout(function () {
                        var nonCaching = new Cache(dummyClient);
                        nonCaching.get({token: 1}).catch(done);
                        nonCaching.get({token: 2}).catch(done);
                        nonCaching.get({token: 3}).then(done);
                    }, 200);
                });
            });

        });

        function checkFetchingFiles(assert, client, done) {
            done = done || assert.async(documentParams.length);
            assert.expect(documentParams.length);
            documentParams.forEach(function (params, idx) {
                var request = client.get(params);
                request.then(function (responseText) {
                    assert.equal('text-' + (idx + 1) + '-contents', responseText, 'Contents are correct');
                    done();
                });
            });
        }

    });

});
<commit_msg>Cleanup code<commit_after>loadPhastJS('public/resources-loader.js', function (phast) {

    QUnit.module('ResourcesLoader', function (hooks) {

        var client,
            documentParams = [];

        hooks.before(function () {
            document.querySelectorAll('[data-phast-params]').forEach(function (el) {
                var params = phast.ResourceLoader.RequestParams.fromString(el.dataset.phastParams);
                documentParams.push(params);
            });
        });

        hooks.beforeEach(function () {
            client = new phast.ResourceLoader.BundlerServiceClient('phast.php?service=bundler');
        });

        QUnit.module('Request', function (hooks) {
            var request;

            hooks.beforeEach(function () {
                request = new phast.ResourceLoader.Request();
            });

            QUnit.test('Check setting/getting handlers', function (assert) {

                assert.strictEqual(request.onsuccess, undefined, 'Initially onsuccess is undefined');
                assert.strictEqual(request.onerror, undefined, 'Initially onerror is undefined');

                var onerror = function () {},
                    onsuccess = function () {};

                request.onsuccess = onsuccess;
                request.onerror = onerror;

                assert.strictEqual(request.onsuccess, onsuccess, 'The onsuccess sets correctly');
                assert.strictEqual(request.onerror, onerror, 'The onerror sets correctly');
            });

            QUnit.test('Check resolving/rejecting', function (assert) {
                var called = false;
                request.onsuccess = function (responseText) {
                    assert.equal('my css', responseText, 'Resolves with appropriate argument');
                    called = true;
                };
                request.success('my css');
                assert.ok(called, 'onsuccess callback was called');
                called = false;

                request = new phast.ResourceLoader.Request();
                request.onerror = function () {
                    called = true;
                };
                request.error();
                assert.ok(called, 'onerror callback was called');
            });

            QUnit.test('Check not dying when resolving with no handlers', function (assert) {
                request.error();
                request.success();
                assert.expect(0);
            });

            QUnit.test('Check instant callback if already resolved/rejected', function (assert) {
                var successCalled = false,
                    errorCalled = false;

                request.success('my js');
                request.onsuccess = function (responseText) {
                    assert.equal('my js', responseText, 'Response text is ok');
                    successCalled = true;
                };
                request.onerror = function () {
                    errorCalled = true;
                };

                assert.ok(successCalled, 'Success callback was called on success');
                assert.notOk(errorCalled, 'Error callback was not called on success');

                successCalled = false;
                errorCalled = false;

                request = new phast.ResourceLoader.Request();
                request.error();
                request.onsuccess = function () {
                    successCalled = true;
                };
                request.onerror = function () {
                    errorCalled = true;
                };

                assert.notOk(successCalled, 'Success was not called on error');
                assert.ok(errorCalled, 'Error was not called on error');

            });

            QUnit.test('Check resolving/rejecting only once', function (assert) {
                assert.expect(0);
                var done = assert.async(2);
                var resolved = new phast.ResourceLoader.Request();
                var rejected = new phast.ResourceLoader.Request();
                resolved.onsuccess = function () {
                    done();
                };
                rejected.onerror = function () {
                    done();
                };
                resolved.success('');
                resolved.success('');
                resolved.error();

                rejected.error();
                rejected.error();
                rejected.success('');
            });

            QUnit.test('Check calling onend on resolving', function (assert) {
                assert.expect(0);
                var done = assert.async();
                request.onsuccess = function () {};
                request.onend = function () {
                    done();
                };
                request.success('');
            });

            QUnit.test('Check calling onend on error', function (assert) {
                assert.expect(0);
                var done = assert.async();
                request.onerror = function() {};
                request.onend = function () {
                    done();
                };
                request.error();
            });

        });

        QUnit.module('RequestParams', function () {

            QUnit.test('Check creating from string', function (assert) {
                var string = JSON.stringify({key1: 'value1', key2: 'value2'});
                var params = phast.ResourceLoader.RequestParams.fromString(string);
                assert.equal(params.key1, 'value1', 'key1 is set right');
                assert.equal(params.key2, 'value2', 'key2 is set right');
                assert.notOk(params.isFaulty(), 'It is not faulty');
            });

            QUnit.test('Check bad params', function (assert) {
                var params = phast.ResourceLoader.RequestParams.fromString('invalid json');
                assert.ok(params.isFaulty(), 'It is faulty');
            });
        });

        QUnit.module('BundlerServiceClient', function () {

            QUnit.test('Test fetching files', function (assert) {
                checkFetchingFiles(assert, client);
            });

            QUnit.test('Test fetching from dedicated service file', function (assert) {
                var done = assert.async();
                var client = new phast.ResourceLoader.BundlerServiceClient('bundler.php');
                var request = client.get(documentParams[0]);
                request.then(function (responseText) {
                    assert.equal('text-1-contents', responseText, 'Fetched correctly');
                    done();
                }).catch(function () {
                    assert.ok(false, 'Does not reject');
                    done();
                });
            });

            QUnit.test('Test error on faulty params', function (assert) {
                var done = assert.async();
                var params = phast.ResourceLoader.RequestParams.fromString('invalid json');
                var request = client.get(params);
                request.then(function () {
                    assert.ok(false, 'Does not reject');
                    done();
                }).catch(function () {
                    assert.ok(true, 'Rejects on invalid json');
                    done();
                });
            });

            QUnit.test('Test handling service errors', function (assert) {
                var iterations = 3;
                var done = assert.async(iterations);
                for (var i = 0; i < iterations; i++) {
                    var params = phast.ResourceLoader.RequestParams.fromObject({fail: 'yes'});
                    var request = client.get(params);
                    request.then(function () {
                        assert.ok(false, 'Rejects');
                        done();
                    }).catch(function () {
                        assert.ok(true, 'Rejects');
                        done();
                    });
                }
            });

            QUnit.test('Test handling network errors', function (assert) {
                var iterations = 3;
                var done = assert.async(iterations);
                var client = new phast.ResourceLoader.BundlerServiceClient('does-not-exist');
                for (var i = 0; i < iterations; i++) {
                    var params = phast.ResourceLoader.RequestParams.fromObject({});
                    var request = client.get(params);
                    request.then(function () {
                        assert.ok(false, 'Rejects');
                        done();
                    }).catch(function () {
                        assert.ok(true, 'Rejects');
                        done();
                    });
                }
            });

            QUnit.test('Test ignoring exceptions while dispatching responses', function (assert) {
                var done = assert.async();
                var badParams = {
                    src: 'some-url',
                    token: 'some-token'
                };
                var request1 = client.get(documentParams[0]);
                var request2 = client.get(phast.ResourceLoader.RequestParams.fromObject(badParams));
                var request3 = client.get(documentParams[1]);
                request1.then(function () {
                    throw 'an error';
                });
                request2.catch(function () {
                    throw 'another error';
                });
                request3.then(function () {
                    assert.ok(true, 'Resolves');
                    done();
                });
            });

            QUnit.test('Test ignoring exceptions while dispatching network error', function (assert) {
                var done = assert.async();
                var client = new phast.ResourceLoader.BundlerServiceClient('bad-url');
                var request1 = client.get(documentParams[0]);
                var request2 = client.get(documentParams[1]);
                request1.catch(function () {
                    throw 'an error';
                });
                request2.catch(function () {
                    assert.ok(true, 'Gets executed');
                    done();
                });
            });
        });

        QUnit.module('IndexedDBResourceCache', function (hooks) {

            var Cache = phast.ResourceLoader.IndexedDBResourceCache;
            Cache.cleanupProbability = 0;

            hooks.beforeEach(function () {
                Cache.closeDB();
                Cache.dbName = 'test' + Date.now();
            });

            hooks.afterEach(function () {
                indexedDB.deleteDatabase(Cache.dbName);
            });

            QUnit.test('Check fetching files with client', function (assert) {
                assert.timeout(2000);
                var done = assert.async(documentParams.length);
                var cache = new Cache(client);
                checkFetchingFiles(assert, cache, done);
            });

            QUnit.test('Check retrieval from cache', function (assert) {
                assert.timeout(2000);
                var done = assert.async(documentParams.length);
                var cache = new Cache(client);
                Promise.all(documentParams.map(function (params) {
                    return cache.get(params);
                })).then(function () {
                    var cache = new Cache({
                        get: function () {
                            throw 'Calling client when should have fetched from cache';
                        }
                    });
                    checkFetchingFiles(assert, cache, done);
                });
            });

            QUnit.test('Check handling missing store when retrieving from', function (assert) {
                assert.timeout(2000);
                var done = assert.async();
                var dbOpenRequest = Cache.openDB(false);
                dbOpenRequest.then(function (db) {
                    db.close();
                    return new Cache(client).get(documentParams[0]);
                }).then(function (responseText) {
                    assert.equal('text-1-contents', responseText);
                    done();
                });
            });

            QUnit.test('Check cache cleanup', function (assert) {
                assert.timeout(2000);
                assert.expect(0);
                var done = assert.async(3);
                var slowClient = {
                    get: function (params) {
                        return new Promise(function (resolve) {
                            setTimeout(function () {
                                console.log('Token:', params.token);
                                resolve('Token: ' + params.token);
                            }, 100);
                        })
                    }
                };
                var dummyClient = {
                    get: function () {
                        return Promise.reject();
                    }
                };

                var caching = new Cache(slowClient);
                caching.get({token: 1}).then(function () {
                    return caching.get({token: 2});
                }).then(function () {
                    return caching.get({token: 3});
                }).then(function () {
                    Cache.maybeCleanup(90, 1);

                    setTimeout(function () {
                        var nonCaching = new Cache(dummyClient);
                        nonCaching.get({token: 1}).catch(done);
                        nonCaching.get({token: 2}).catch(done);
                        nonCaching.get({token: 3}).then(done);
                    }, 200);
                });
            });

        });

        function checkFetchingFiles(assert, client, done) {
            done = done || assert.async(documentParams.length);
            assert.expect(documentParams.length);
            documentParams.forEach(function (params, idx) {
                var request = client.get(params);
                request.then(function (responseText) {
                    assert.equal('text-' + (idx + 1) + '-contents', responseText, 'Contents are correct');
                    done();
                });
            });
        }

    });

});
<|endoftext|>


<commit_before>/**
 * @providesModule Site
 * @jsx React.DOM
 */

var React = require('React');
var HeaderLinks = require('HeaderLinks');

var Site = React.createClass({
  render: function() {
    return (

          <meta charSet="utf-8" />
          <meta httpEquiv="X-UA-Compatible" content="IE=edge,chrome=1" />
          <title>Flux | Application Architecture for Building User Interfaces</title>
          <meta name="viewport" content="width=device-width" />
          <meta property="og:title" content="Flux | Application Architecture for Building User Interfaces" />
          <meta property="og:type" content="website" />
          <meta property="og:url" content="http://facebook.github.io/flux/index.html" />
          <meta property="og:description" content="Application Architecture for Building User Interfaces" />

          <link rel="stylesheet" href="/flux/css/flux.css" />

                  Flux

                <HeaderLinks section={this.props.section} />

            {this.props.children}

© 2014-2015 Facebook Inc.

                  Flux

                <HeaderLinks section={this.props.section} />

            {this.props.children}

© 2014-2015 Facebook Inc.

Translated by haruair (github)


<commit_before>import React from 'react';
import {without, includes, union, isNull, find} from 'lodash';

function DefaultItemComponent(props) {
	const item = props.item;
	const getItemTitle = props.getItemTitle;
	const selectedItems = props.selectedItems;
	const selected = includes(selectedItems, item);

	return (
		<div title={`${getItemTitle(item)}`}>
			<div
				className={`cpr-multi-selector-item__icon ${selected ? "cps-bg-primary-green +selected" : ""}`}>

{`${getItemTitle(item)}`}

	)
}

DefaultItemComponent.propTypes = {
	item: React.PropTypes.any.isRequired,
	getItemTitle: React.PropTypes.func,
	selectedItems: React.PropTypes.array.isRequired
};

function nearest(element, className) {
	if (!element || !element.className) return false;
	return element.className.indexOf(className) > -1 || nearest(element.parentElement, className);
}

const MultiSelector = React.createClass({
	propTypes: {
		items: React.PropTypes.array.isRequired,
		initialSelectedItems: React.PropTypes.array,
		onInputChange: React.PropTypes.func,
		onChange: React.PropTypes.func,
		getItemTitle: React.PropTypes.func,
		ItemComponent: React.PropTypes.oneOfType(
			[React.PropTypes.element, React.PropTypes.func]
		),
		placeholder: React.PropTypes.string,
		maxLength: React.PropTypes.number,
		noRestrict: React.PropTypes.bool,
		pressEnterToAddPhrase: React.PropTypes.string,
		noResultsPhrase: React.PropTypes.string,
	},

	componentWillMount: function() {
		document.addEventListener('click', this.state.close);
	},

	componentWillUnmount: function() {
		document.removeEventListener('click', this.state.close);
	},

	getInitialState: function() {
		return {
			selectedItems: this.props.initialSelectedItems || [],
			mouseIndex: null,
			mouseActive: true,
			mouseFunc: null,
			dialogDisplayed: false,
			activeIndex: null,
			searchValue: '',
			close: (e) => {
				if (!nearest(e.target, 'cpr-multi-selector')) {
					if (this.state.dialogDisplayed)
						this.props.onBlur && this.props.onBlur();
					this.setState({
						dialogDisplayed: false,
						searchValue: ''
					});
				}
			},
			invalid: false,
		}
	},

	componentWillReceiveProps(nextProps) {
		this.setState({
			selectedItems: nextProps.initialSelectedItems || []
		})
	},

	displayDialog: function(e) {
		if (!this.state.dialogDisplayed)
			this.props.onFocus && this.props.onFocus();
		this.setState({
			dialogDisplayed: true
		})
	},

	removeItem: function(item, e) {
		this.setState({
			selectedItems: without(this.state.selectedItems, item)
		});

		setTimeout(this.triggerItemChange);
	},

	getItemTitle: function(item) {
		return item.label;
	},

	setActiveIndex(index) {
		this.setState({
			activeIndex: index,
			mouseIndex: null,
		}, () => {
			if (!isNull(this.state.activeIndex) && !!this.searchItems[this.state.activeIndex]) {
				this.searchItems[this.state.activeIndex].scrollIntoView();
				if (this.state.mouseActive) {
					this.setState({
						mouseActive: false,
						mouseFunc: () => {
							this.setState({
								mouseActive: true
							})
							document.removeEventListener("mousemove", this.state.mouseFunc);
						}
					}, () => {
						document.addEventListener("mousemove", this.state.mouseFunc);
					})
				}
			}
		})
	},

	handleChange: function(e) {
		e.persist();
		if (this.state.invalid && this.props.validate) {
			this.setState({
				invalid: !this.props.validate(e.target.value),
			});
		}
		this.setState({
			disabled: this.props.disableInput ? this.props.disableInput(e.target.value) : false,
		}, () => {
			this.inputChange(e.target.value);
		});
	},

	inputChange: function(newVal) {
		this.props.onInputChange && this.props.onInputChange(newVal);

		this.setState({
			searchValue: newVal
		});
	},

	keyDown: function(e) {
		const keycode = e.which;
		const activeIndex = this.state.activeIndex;
		const filterItems = this.getFilterItems(this.props.items);

		if (isNull(activeIndex) && filterItems.length !== 0) {
			this.setActiveIndex(0);
		} else if (filterItems.length === 0) {
			this.setActiveIndex(null);
		}

		if (keycode === 13) e.preventDefault();
		if(keycode === 40) { // press down key
			if (isNull(activeIndex) && filterItems.length !== 0) {
				return this.setActiveIndex(0);
			} else {
				if(activeIndex < filterItems.length - 1) {
					return this.setActiveIndex(activeIndex + 1);
				}
			}
		} else if(keycode === 38) { // press up key
			if(!activeIndex) {
				return this.setActiveIndex(0);
			} else {
				if(activeIndex > 0) {
					return this.setActiveIndex(activeIndex - 1);
				}
			}
		} else if(keycode === 13) { // press enter key
			if(!isNull(activeIndex) && filterItems.length !==0) {
				return this.selectItem(filterItems[activeIndex], e);
			} else if(this.props.noRestrict && e.currentTarget.value) {
				// if the noRestrict prop is true it adds the input as a string to the selected items on enter
				e.persist();
				return this.validateInput(e.currentTarget.value, e);
			}
		} else if(keycode === 27) { // press escape key
			return this.setState({
				activeIndex: null,
				dialogDisplayed: false
			});
		}
	},

	triggerItemChange: function() {
		if (this.props.onChange) {
			this.props.onChange.call(null, this.state.selectedItems);
		}
	},

	getSelectedClass: function(item) {
		return includes(this.state.selectedItems, item) ? '+selected' : '';
	},

	getActiveClass: function(index) {
		return this.state.activeIndex === index || this.state.mouseIndex === index
			? '+highlighted'
			: '';
	},

	getFilterItems: function(items = []) {
		let getItemTitle = this.props.getItemTitle || this.getItemTitle;

		return items
			.filter((item) => {
				return getItemTitle(item).toLowerCase().indexOf(this.state.searchValue.toLowerCase()) > -1;
			})
	},

	getSearchItems: function(items = []) {
		let ItemComponent = this.props.ItemComponent || DefaultItemComponent;
		let getItemTitle = this.props.getItemTitle || this.getItemTitle;
		let filterItems = this.getFilterItems(items);

		// Show a message that user can press enter to add new item
		if (filterItems.length === 0) {
			if (this.props.noRestrict && this.state.searchValue) {
				const pressEnterToAddPhrase = this.props.pressEnterToAddPhrase || 'Press Enter to add';
				return (

{pressEnterToAddPhrase} "{this.state.searchValue}"

				)
			} else {
				const noResultsPhrase = this.props.noResultsPhrase || 'No items found.';
				return (

{noResultsPhrase}

				)
			}
		}

		// If noRestrict & the search term doesn't have an exact match, append an additional "result" for the new item
		// This is to allow adding of new items when the search term has matching filtered items but not an exact match
		if (this.props.noRestrict && this.state.searchValue
		&& !find(filterItems, (item) => getItemTitle(item).toLowerCase() === this.state.searchValue.toLowerCase())) {
			filterItems.push(this.state.searchValue);
		}

		return filterItems.map((item, index) => {
			return (

 {
						if (this.searchItems) {
							this.searchItems[index] = ref;
						} else {
							this.searchItems = [];
							this.searchItems[index] = ref;
						}
					}}
					onMouseOver={() => {
						if (this.state.mouseActive) {
							this.setState({
								mouseIndex: index
							})
						}
					}}
					className={`cpr-multi-selector-item ${this.getSelectedClass(item)} ${this.getActiveClass(index)}`}
					onClick={this.selectItem.bind(this, item)}>
					<ItemComponent item={item} selectedItems={this.state.selectedItems} getItemTitle={getItemTitle}/>

			)
		})
	},

	selectItem: function(item, e) {
		let selectedItems = this.state.selectedItems;

		if(includes(selectedItems, item)) {
			this.setState({
				selectedItems: without(selectedItems, item),
				invalid: false,
			}, this.triggerItemChange);
		} else {
			this.setState({
				selectedItems: union(selectedItems, [ item ]),
				invalid: false,
			}, this.triggerItemChange);
		}

		if (e && e.currentTarget) {
			e.currentTarget.value = '';
			this.inputChange('');
		}
	},

	validateInput: function(input, e) {
		if (this.state.disabled) return;
		if (this.props.validate) {
			const valid = this.props.validate(input);
			if (valid) this.selectItem(input, e);
			else this.setState({invalid: true});
		} else {
			this.selectItem(input, e);
		}
	},

	positionDialog: function() {
		setTimeout(() => {
			let el = this.el;
			let height = el.clientHeight;
			let dialog = el.querySelector('.cpr-multi-selector__dialog');

			if (dialog) {
				dialog.style.top = (height + 1) + 'px';
				el.querySelector('.cpr-multi-selector__dialog__input').focus();
			}
		}, 100);
	},

	prevent: function(e){
		if(e.which === 13) e.preventDefault();
	},

	render: function() {
		//Get getItemTitle is the function that should be passed in to decide what `pill` will display on selection.
		let getItemTitle = this.props.getItemTitle || this.getItemTitle;

		let pills = this.state.selectedItems
			.map((item, i) => {
				return (
					<div key={i} className="cpr-multi-selector__pill" title={`${getItemTitle(item)}`}>

							{getItemTitle(item)}

							 {
									e.stopPropagation();
									this.removeItem(item)
								}}
								className="cps-icon cps-icon-sm-neg">

				);
			});

		let dialog;

		if (this.state.dialogDisplayed) {
			let placeholder = this.props.placeholder ? this.props.placeholder : "Type a collaborators name...";
			let maxLength = this.props.maxLength;
			dialog = (

					<div
						className={`${this.state.invalid ? "cps-has-error" : ""}`}
						style={{padding: "16px", borderBottom: "1px solid #E9E9E9"}}>
						<input
							onChange={this.handleChange}
							onKeyDown={this.keyDown}
							className={`cps-form-control cpr-multi-selector__dialog__input`}
							placeholder={placeholder}
							{...(maxLength ? {maxLength} : {})}/>
						{this.state.invalid &&
							{this.props.invalidMsg}}

						{this.getSearchItems(this.props.items)}

						{(this.state.searchValue && this.props.doneButton) &&

								<button
									disabled={this.state.diabled}
									onClick={this.validateInput.bind(this, this.state.searchValue)}
									className={`cps-btn +primary ${this.state.disabled ? "+disabled" : ""}`}>
									DONE
								</button>

}

			)

			this.positionDialog();
		}

		return (

 { if (el) this.el = el }} className='cpr-multi-selector'>
				<input type="input" className="cpr-multi-selector__hidden-input" onFocus={this.displayDialog}/>

					{pills}

				{dialog}

		)
	}
});

if (window && !window.MultiSelector) window.MultiSelector = MultiSelector;

export default MultiSelector;
<commit_msg>Title instead of tooltip on span (#123)<commit_after>import React from 'react';
import {without, includes, union, isNull, find} from 'lodash';

function DefaultItemComponent(props) {
	const item = props.item;
	const getItemTitle = props.getItemTitle;
	const selectedItems = props.selectedItems;
	const selected = includes(selectedItems, item);

	return (
		<div title={`${getItemTitle(item)}`}>
			<div
				className={`cpr-multi-selector-item__icon ${selected ? "cps-bg-primary-green +selected" : ""}`}>

{`${getItemTitle(item)}`}

	)
}

DefaultItemComponent.propTypes = {
	item: React.PropTypes.any.isRequired,
	getItemTitle: React.PropTypes.func,
	selectedItems: React.PropTypes.array.isRequired
};

function nearest(element, className) {
	if (!element || !element.className) return false;
	return element.className.indexOf(className) > -1 || nearest(element.parentElement, className);
}

const MultiSelector = React.createClass({
	propTypes: {
		items: React.PropTypes.array.isRequired,
		initialSelectedItems: React.PropTypes.array,
		onInputChange: React.PropTypes.func,
		onChange: React.PropTypes.func,
		getItemTitle: React.PropTypes.func,
		ItemComponent: React.PropTypes.oneOfType(
			[React.PropTypes.element, React.PropTypes.func]
		),
		placeholder: React.PropTypes.string,
		maxLength: React.PropTypes.number,
		noRestrict: React.PropTypes.bool,
		pressEnterToAddPhrase: React.PropTypes.string,
		noResultsPhrase: React.PropTypes.string,
	},

	componentWillMount: function() {
		document.addEventListener('click', this.state.close);
	},

	componentWillUnmount: function() {
		document.removeEventListener('click', this.state.close);
	},

	getInitialState: function() {
		return {
			selectedItems: this.props.initialSelectedItems || [],
			mouseIndex: null,
			mouseActive: true,
			mouseFunc: null,
			dialogDisplayed: false,
			activeIndex: null,
			searchValue: '',
			close: (e) => {
				if (!nearest(e.target, 'cpr-multi-selector')) {
					if (this.state.dialogDisplayed)
						this.props.onBlur && this.props.onBlur();
					this.setState({
						dialogDisplayed: false,
						searchValue: ''
					});
				}
			},
			invalid: false,
		}
	},

	componentWillReceiveProps(nextProps) {
		this.setState({
			selectedItems: nextProps.initialSelectedItems || []
		})
	},

	displayDialog: function(e) {
		if (!this.state.dialogDisplayed)
			this.props.onFocus && this.props.onFocus();
		this.setState({
			dialogDisplayed: true
		})
	},

	removeItem: function(item, e) {
		this.setState({
			selectedItems: without(this.state.selectedItems, item)
		});

		setTimeout(this.triggerItemChange);
	},

	getItemTitle: function(item) {
		return item.label;
	},

	setActiveIndex(index) {
		this.setState({
			activeIndex: index,
			mouseIndex: null,
		}, () => {
			if (!isNull(this.state.activeIndex) && !!this.searchItems[this.state.activeIndex]) {
				this.searchItems[this.state.activeIndex].scrollIntoView();
				if (this.state.mouseActive) {
					this.setState({
						mouseActive: false,
						mouseFunc: () => {
							this.setState({
								mouseActive: true
							})
							document.removeEventListener("mousemove", this.state.mouseFunc);
						}
					}, () => {
						document.addEventListener("mousemove", this.state.mouseFunc);
					})
				}
			}
		})
	},

	handleChange: function(e) {
		e.persist();
		if (this.state.invalid && this.props.validate) {
			this.setState({
				invalid: !this.props.validate(e.target.value),
			});
		}
		this.setState({
			disabled: this.props.disableInput ? this.props.disableInput(e.target.value) : false,
		}, () => {
			this.inputChange(e.target.value);
		});
	},

	inputChange: function(newVal) {
		this.props.onInputChange && this.props.onInputChange(newVal);

		this.setState({
			searchValue: newVal
		});
	},

	keyDown: function(e) {
		const keycode = e.which;
		const activeIndex = this.state.activeIndex;
		const filterItems = this.getFilterItems(this.props.items);

		if (isNull(activeIndex) && filterItems.length !== 0) {
			this.setActiveIndex(0);
		} else if (filterItems.length === 0) {
			this.setActiveIndex(null);
		}

		if (keycode === 13) e.preventDefault();
		if(keycode === 40) { // press down key
			if (isNull(activeIndex) && filterItems.length !== 0) {
				return this.setActiveIndex(0);
			} else {
				if(activeIndex < filterItems.length - 1) {
					return this.setActiveIndex(activeIndex + 1);
				}
			}
		} else if(keycode === 38) { // press up key
			if(!activeIndex) {
				return this.setActiveIndex(0);
			} else {
				if(activeIndex > 0) {
					return this.setActiveIndex(activeIndex - 1);
				}
			}
		} else if(keycode === 13) { // press enter key
			if(!isNull(activeIndex) && filterItems.length !==0) {
				return this.selectItem(filterItems[activeIndex], e);
			} else if(this.props.noRestrict && e.currentTarget.value) {
				// if the noRestrict prop is true it adds the input as a string to the selected items on enter
				e.persist();
				return this.validateInput(e.currentTarget.value, e);
			}
		} else if(keycode === 27) { // press escape key
			return this.setState({
				activeIndex: null,
				dialogDisplayed: false
			});
		}
	},

	triggerItemChange: function() {
		if (this.props.onChange) {
			this.props.onChange.call(null, this.state.selectedItems);
		}
	},

	getSelectedClass: function(item) {
		return includes(this.state.selectedItems, item) ? '+selected' : '';
	},

	getActiveClass: function(index) {
		return this.state.activeIndex === index || this.state.mouseIndex === index
			? '+highlighted'
			: '';
	},

	getFilterItems: function(items = []) {
		let getItemTitle = this.props.getItemTitle || this.getItemTitle;

		return items
			.filter((item) => {
				return getItemTitle(item).toLowerCase().indexOf(this.state.searchValue.toLowerCase()) > -1;
			})
	},

	getSearchItems: function(items = []) {
		let ItemComponent = this.props.ItemComponent || DefaultItemComponent;
		let getItemTitle = this.props.getItemTitle || this.getItemTitle;
		let filterItems = this.getFilterItems(items);

		// Show a message that user can press enter to add new item
		if (filterItems.length === 0) {
			if (this.props.noRestrict && this.state.searchValue) {
				const pressEnterToAddPhrase = this.props.pressEnterToAddPhrase || 'Press Enter to add';
				return (

{pressEnterToAddPhrase} "{this.state.searchValue}"

				)
			} else {
				const noResultsPhrase = this.props.noResultsPhrase || 'No items found.';
				return (

{noResultsPhrase}

				)
			}
		}

		// If noRestrict & the search term doesn't have an exact match, append an additional "result" for the new item
		// This is to allow adding of new items when the search term has matching filtered items but not an exact match
		if (this.props.noRestrict && this.state.searchValue
		&& !find(filterItems, (item) => getItemTitle(item).toLowerCase() === this.state.searchValue.toLowerCase())) {
			filterItems.push(this.state.searchValue);
		}

		return filterItems.map((item, index) => {
			return (

 {
						if (this.searchItems) {
							this.searchItems[index] = ref;
						} else {
							this.searchItems = [];
							this.searchItems[index] = ref;
						}
					}}
					onMouseOver={() => {
						if (this.state.mouseActive) {
							this.setState({
								mouseIndex: index
							})
						}
					}}
					className={`cpr-multi-selector-item ${this.getSelectedClass(item)} ${this.getActiveClass(index)}`}
					onClick={this.selectItem.bind(this, item)}>
					<ItemComponent item={item} selectedItems={this.state.selectedItems} getItemTitle={getItemTitle}/>

			)
		})
	},

	selectItem: function(item, e) {
		let selectedItems = this.state.selectedItems;

		if(includes(selectedItems, item)) {
			this.setState({
				selectedItems: without(selectedItems, item),
				invalid: false,
			}, this.triggerItemChange);
		} else {
			this.setState({
				selectedItems: union(selectedItems, [ item ]),
				invalid: false,
			}, this.triggerItemChange);
		}

		if (e && e.currentTarget) {
			e.currentTarget.value = '';
			this.inputChange('');
		}
	},

	validateInput: function(input, e) {
		if (this.state.disabled) return;
		if (this.props.validate) {
			const valid = this.props.validate(input);
			if (valid) this.selectItem(input, e);
			else this.setState({invalid: true});
		} else {
			this.selectItem(input, e);
		}
	},

	positionDialog: function() {
		setTimeout(() => {
			let el = this.el;
			let height = el.clientHeight;
			let dialog = el.querySelector('.cpr-multi-selector__dialog');

			if (dialog) {
				dialog.style.top = (height + 1) + 'px';
				el.querySelector('.cpr-multi-selector__dialog__input').focus();
			}
		}, 100);
	},

	prevent: function(e){
		if(e.which === 13) e.preventDefault();
	},

	render: function() {
		//Get getItemTitle is the function that should be passed in to decide what `pill` will display on selection.
		let getItemTitle = this.props.getItemTitle || this.getItemTitle;

		let pills = this.state.selectedItems
			.map((item, i) => {
				return (
					<div key={i} className="cpr-multi-selector__pill" title={`${getItemTitle(item)}`}>

							{getItemTitle(item)}

							 {
									e.stopPropagation();
									this.removeItem(item)
								}}
								className="cps-icon cps-icon-sm-neg">

				);
			});

		let dialog;

		if (this.state.dialogDisplayed) {
			let placeholder = this.props.placeholder ? this.props.placeholder : "Type a collaborators name...";
			let maxLength = this.props.maxLength;
			dialog = (

					<div
						className={`${this.state.invalid ? "cps-has-error" : ""}`}
						style={{padding: "16px", borderBottom: "1px solid #E9E9E9"}}>
						<input
							onChange={this.handleChange}
							onKeyDown={this.keyDown}
							className={`cps-form-control cpr-multi-selector__dialog__input`}
							placeholder={placeholder}
							{...(maxLength ? {maxLength} : {})}/>
						{this.state.invalid &&
							{this.props.invalidMsg}}

						{this.getSearchItems(this.props.items)}

						{(this.state.searchValue && this.props.doneButton) &&

								<button
									disabled={this.state.diabled}
									onClick={this.validateInput.bind(this, this.state.searchValue)}
									className={`cps-btn +primary ${this.state.disabled ? "+disabled" : ""}`}>
									DONE
								</button>

}

			)

			this.positionDialog();
		}

		return (

 { if (el) this.el = el }} className='cpr-multi-selector'>
				<input type="input" className="cpr-multi-selector__hidden-input" onFocus={this.displayDialog}/>

					{pills}

				{dialog}

		)
	}
});

if (window && !window.MultiSelector) window.MultiSelector = MultiSelector;

export default MultiSelector;
<|endoftext|>


<commit_before>(function() {
	'use strict';

	// 请不要问我为什么不用 jQuery 去获取代码，因为，因为，TMD正方的$选择器是自己手写的
	// 所以我也决定手写，用它的不如自己去手写，就这么简单
	var slice = Array.prototype.slice;
	var config = {
		A: 1,
		B: 2,
		C: 3,
		D: 4,
		save: 'Button1',
		submit: 'Button2',
		levels: 'BAAAAAAAAA'
	}

	// 当前科目，-1表示没有开始
	var currentSubject = -1;

	// 提示框，学过VB的一定很熟悉吧
	var msgbox = document.createElement('div');
	msgbox.style.cssText = "position:fixed; top: 0; left: 0;width: 100%;padding: 10px;margin: 0;background: grey;text-align:center;border: 1px solid #ccc;box-shadow: 0 0 10px black;z-index: 10000";
	document.body.appendChild(msgbox);

	var iframe = document.getElementsByTagName('iframe')[0];
	// 注册框架加载完成事件，这样可以保证无论加载多长时间，都可以正确的进行下一次处理
	iframe.addEventListener('load', function listener() {
		setTimeout(function() {
			if (!score()) {
				msg('保存完成，提交打分，2秒后自动消失');
				iframe.removeEventListener('load', listener);
				submit();
				setTimeout(function() {
					document.body.removeChild(msgbox);
					msgbox = null;
				}, 2000);
				return;
			}
		}, 1000)
	})

	/**
	 * 自动打分(score)函数
	 * @return {Boolean} 代表这次操作是否成功，失败的可能就是已经全部处理完成了
	 */
	function score() {
		var fdocument = iframe.contentDocument;
		var subject = fdocument.getElementsByTagName('select')[0];
		if (subject.selectedIndex === currentSubject) return false;
		msg('提交打分：' + subject.selectedOptions[0].innerText);
		currentSubject = subject.selectedIndex;
		var selects = slice.call(fdocument.getElementsByTagName('select'), 1)
		var column = parseInt(selects.length / 10);

		selects.forEach(function(select, index) {
			// 获取出来之后，顺序是以行，从左到右进行排列
			select.selectedIndex = config[config.levels[parseInt(index / column)]]
		})
		fdocument.getElementById(config.save).click();
		return true;
	}

	/**
	 * 获取打分等级分布
	 * 默认情况是第一个选择B，其余的选择A
	 * 用户在弹出的输入框中输入想要的打分顺序，那么将会按照每一个都这样打分
	 * @return {Boolean} false标识取消这次操作
	 */
	function level() {
		var levels;

		do {
			levels = prompt('请输入您想给老师打分的等级，一共10个字符，支持ABCD，其他均为不合法字符\n默认：第一个打B，其余9个打A\n不输入内容是选择【确定】使用默认值，选择【取消】取消此次操作');

			if (levels === null) return false;
		}while(!check(levels))

		config.levels = levels || config.levels;

		/**
		 * 检测这个是否合法
		 * @return {Boolean}
		 */
		function check(string) {
		  if (string === '') return true;
			if (string.length !== 10) return false;
			if (!/^[A-D]{10}$/.test(string)) return false;
			if (/^A{10}$/.test(string)) return false;
			return true;
		}

		return true;
	}

	// 提交函数
	function submit() {
		iframe.contentDocument.getElementById(config.submit).click();
	}

	// 消息现实函数
	function msg(string) {
		msgbox.innerText = string;
	}

	// 获取打分等级(level)分布
	if (!level()) {
		document.body.removeChild(msgbox);
		msgbox = null;
		alert('您取消了此次操作');
		return;
	}
	// 手动启动
	score();
})()
<commit_msg>更改了一下 UI ，变成了大绿色，顺从 zootopia<commit_after>(function() {
	// https://github.com/hdu-coder/evaluate-teacher
	'use strict';

	// 请不要问我为什么不用 jQuery 去获取代码，因为，因为，TMD正方的$选择器是自己手写的
	// 所以我也决定手写，用它的不如自己去手写，就这么简单
	var slice = Array.prototype.slice;
	var config = {
		A: 1,
		B: 2,
		C: 3,
		D: 4,
		save: 'Button1',
		submit: 'Button2',
		levels: 'BAAAAAAAAA'
	}

	// 当前科目，-1表示没有开始
	var currentSubject = -1;

	// 提示框，学过VB的一定很熟悉吧
	var msgbox = document.createElement('div');
	msgbox.style.cssText = "position: fixed;top: 0px;left: 0px;width: 100%;padding: 10px;margin: 0px;text-align: center;border: 1px solid rgb(204, 204, 204);box-shadow: black 0px 0px 10px;z-index: 10000;background: #3e3e3e;background: rgba(00,153,00,0.8);color: white;";
	document.body.appendChild(msgbox);

	var iframe = document.getElementsByTagName('iframe')[0];
	// 注册框架加载完成事件，这样可以保证无论加载多长时间，都可以正确的进行下一次处理
	iframe.addEventListener('load', function listener() {
		setTimeout(function() {
			if (!score()) {
				msg('保存完成，提交打分，2秒后自动消失，Thanks~');
				iframe.removeEventListener('load', listener);
				submit();
				setTimeout(function() {
					document.body.removeChild(msgbox);
					msgbox = null;
				}, 2000);
				return;
			}
		}, 1000)
	})

	/**
	 * 自动打分(score)函数
	 * @return {Boolean} 代表这次操作是否成功，失败的可能就是已经全部处理完成了
	 */
	function score() {
		var fdocument = iframe.contentDocument;
		var subject = fdocument.getElementsByTagName('select')[0];
		if (subject.selectedIndex === currentSubject) return false;
		msg('提交打分：' + subject.selectedOptions[0].innerText);
		currentSubject = subject.selectedIndex;
		var selects = slice.call(fdocument.getElementsByTagName('select'), 1)
		var column = parseInt(selects.length / 10);

		selects.forEach(function(select, index) {
			// 获取出来之后，顺序是以行，从左到右进行排列
			select.selectedIndex = config[config.levels[parseInt(index / column)]]
		})
		fdocument.getElementById(config.save).click();
		return true;
	}

	/**
	 * 获取打分等级分布
	 * 默认情况是第一个选择B，其余的选择A
	 * 用户在弹出的输入框中输入想要的打分顺序，那么将会按照每一个都这样打分
	 * @return {Boolean} false标识取消这次操作
	 */
	function level() {
		var levels;

		do {
			levels = prompt('请输入您想给老师打分的等级，一共10个字符，支持ABCD，其他均为不合法字符\n默认：第一个打B，其余9个打A\n不输入内容是选择【确定】使用默认值，选择【取消】取消此次操作');

			if (levels === null) return false;
		}while(!check(levels))

		config.levels = levels || config.levels;

		/**
		 * 检测这个是否合法
		 * @return {Boolean}
		 */
		function check(string) {
		  if (string === '') return true;
			if (string.length !== 10) return false;
			if (!/^[A-D]{10}$/.test(string)) return false;
			if (/^A{10}$/.test(string)) return false;
			return true;
		}

		return true;
	}

	// 提交函数
	function submit() {
		iframe.contentDocument.getElementById(config.submit).click();
	}

	// 消息现实函数
	function msg(string) {
		msgbox.innerText = string + '  By XGHeaven';
	}

	// 获取打分等级(level)分布
	if (!level()) {
		document.body.removeChild(msgbox);
		msgbox = null;
		alert('您取消了此次操作');
		return;
	}
	// 手动启动
	score();
})()
<|endoftext|>


<commit_before>/*
    ======================================
    Custom dashboard telemetry data filter
    ======================================
*/

// This filter is used to change telemetry data
// before it is displayed on the dashboard.
// For example, you may convert km/h to mph, kilograms to tons, etc.
// "data" object is an instance of the Ets2TelemetryData class
// defined in dashboard-core.ts (or see JSON response in the server's API).

Funbit.Ets.Telemetry.Dashboard.prototype.filter = function (data) {
    // round truck speed
    data.truckSpeedRounded = Math.abs(data.truck.speed > 0
        ? Math.floor(data.truck.speed)
        : Math.round(data.truck.speed));
    // convert kilometers per hour to miles per hour (just an example)
    data.truckSpeedMph = data.truck.speed * 0.621371;
    data.truckSpeedMphRounded = Math.abs(Math.floor(data.truckSpeedMph));
    // convert gear to readable format
    data.gear = data.truck.gear > 0 ? 'D' + data.truck.gear : (data.truck.gear < 0 ? 'R' : 'N');
    data.currentFuelPercentage = (data.truck.fuel / data.truck.fuelCapacity) * 100;

    // scsTruckDamage is the value SCS uses in the route advisor
    data.scsTruckDamage = getDamagePercentage(data);
    data.scsTruckDamageRounded = Math.floor(data.scsTruckDamage);
    data.wearTrailerRounded = Math.floor(data.trailer.wear * 100);

    data.gameTime12h = getTime(data.game.time, 12, true);
    data.jobDeadlineTime12h = getTime(data.job.deadlineTime, 12, false);
    data.trailerMassTons = data.trailer.attached ? ((data.trailer.mass / 1000.0) + ' t') : '';
    data.trailerMassKg = data.trailer.attached ? data.trailer.mass + ' kg' : '';
    data.jobIncome = getJobIncome(data.job.income);
    data.game.nextRestStopTimeArray = getHoursAndMinutes(data.game.nextRestStopTime);
    data.game.nextRestStopTime = processTimeDifferenceArray(data.game.nextRestStopTimeArray);
    data.navigation.speedLimitMph = data.navigation.speedLimit * .621371;
    data.navigation.speedLimitMphRounded = Math.round(data.navigation.speedLimitMph);
    data.navigation.estimatedDistanceKm = data.navigation.estimatedDistance / 1000;
    data.navigation.estimatedDistanceMi = data.navigation.estimatedDistanceKm * .621371;
    data.navigation.estimatedDistanceKmRounded = Math.floor(data.navigation.estimatedDistanceKm);
    data.navigation.estimatedDistanceMiRounded = Math.floor(data.navigation.estimatedDistanceMi);
    var originalEstimatedTime = data.navigation.estimatedTime;
    data.navigation.estimatedTime = getTime(data.navigation.estimatedTime, 24, false);
    data.navigation.estimatedTime12h = getTime(originalEstimatedTime, 12, false);
    var timeToDestinationArray = getTimeDifference(data.game.time, originalEstimatedTime);
    data.navigation.timeToDestination = processTimeDifferenceArray(timeToDestinationArray);

    // return changed data to the core for rendering
    return data;
};

Funbit.Ets.Telemetry.Dashboard.prototype.render = function (data) {
    //
    // data - same data object as in the filter function
    //
    $('#fuelLine').css('width', data.currentFuelPercentage + '%');
    $('#damageLine').css('width', data.scsTruckDamage + '%');
    $('#truckDamageIcon').css('height', getDamageFillForTruck(data.scsTruckDamage) + '%');
    $('#trailerDamageIcon').css('height', getDamageFillForTrailer(data.trailer.wear * 100) + '%');
    $('#restLine').css('width', getFatiguePercentage(data.game.nextRestStopTimeArray[0], data.game.nextRestStopTimeArray[1]) + '%');

    // Process DOM for connection
    if (data.game.connected) {
        $('.has-connection').show();
        $('.no-connection').hide();
    }

    // Process DOM for job
    if (data.trailer.attached) {
        $('.hasJob').show();
        $('.noJob').hide();
    } else {
        $('.hasJob').hide();
        $('.noJob').show();
    }
}

Funbit.Ets.Telemetry.Dashboard.prototype.initialize = function (skinConfig) {
    //
    // skinConfig - a copy of the skin configuration from config.json
    //
    // this function is called before everything else,
    // so you may perform any DOM or resource initializations here

    // Process Speed Units
    var distanceUnits = skinConfig.distanceUnits;
    if (distanceUnits === 'km') {
        $('.speedUnits').text('km/h');
        $('.distanceUnits').text('km');
        $('.truckSpeedRoundedKmhMph').addClass('truckSpeedRounded').removeClass('truckSpeedRoundedKmhMph');
        $('.speedLimitRoundedKmhMph').addClass('navigation-speedLimit').removeClass('speedLimitRoundedKmhMph');
        $('.navigationEstimatedDistanceKmMi').addClass('navigation-estimatedDistanceKmRounded').removeClass('navigationEstimatedDistanceKmMi');
    } else if (distanceUnits === 'mi') {
        $('.speedUnits').text('mph');
        $('.distanceUnits').text('mi');
        $('.truckSpeedRoundedKmhMph').addClass('truckSpeedMphRounded').removeClass('truckSpeedRoundedKmhMph');
        $('.speedLimitRoundedKmhMph').addClass('navigation-speedLimitMphRounded').removeClass('speedLimitRoundedKmhMph');
        $('.navigationEstimatedDistanceKmMi').addClass('navigation-estimatedDistanceMiRounded').removeClass('navigationEstimatedDistanceKmMi');
    }

    // Process kg vs tons
    var weightUnits = skinConfig.weightUnits;
    if (weightUnits === 'kg') {
        $('.trailerMassKgOrT').addClass('trailerMassKg').removeClass('trailerMassKgOrT');
    } else if (weightUnits === 't') {
        $('.trailerMassKgOrT').addClass('trailerMassTons').removeClass('trailerMassKgOrT');
    }

    // Process 12 vs 24 hr time
    var timeFormat = skinConfig.timeFormat;
    if (timeFormat === '12h') {
        $('.game-time').addClass('gameTime12h').removeClass('game-time');
        $('.job-deadlineTime').addClass('jobDeadlineTime12h').removeClass('job-deadlineTime');
        $('.navigation-estimatedTime').addClass('navigation-estimatedTime12h').removeClass('navigation-estimatedTime');
    }

    // Process currency code
    $('.currencyCode').text(skinConfig.currencyCode);

    // Process language JSON
    $.getJSON('skins/mobile-route-advisor/language/'+skinConfig.language, function(json) {
        $.each(json, function(key, value) {
            updateLanguage(key, value);
        });
    });
}

function getHoursAndMinutes(time) {
    var dateTime = new Date(time);
    // ETS2's times are stored in ISO 8601 or YYYY-MM-DDTHH:MM:SSZ. To get hours, grab string positions 11 and 12. For minutes, use 14 and 15.
    var hour = dateTime.getUTCHours();
    var minute = dateTime.getUTCMinutes();
    return [hour, minute];
}

function getFatiguePercentage(hoursUntilRest, minutesUntilRest) {
    var FULLY_RESTED_TIME_REMAINING_IN_MILLISECONDS = 11*60*60*1000; // 11 hours * 60 min * 60 sec * 1000 milliseconds

    if (hoursUntilRest <= 0 && minutesUntilRest <= 0) {
        return 100;
    }

    var hoursInMilliseconds = hoursUntilRest * 60 * 60 * 1000; // # hours * 60 min * 60 sec * 1000 milliseconds
    var minutesInMilliseconds = minutesUntilRest * 60 * 1000; // # minutes * 60 sec * 1000 milliseconds

    return 100 - (((hoursInMilliseconds + minutesInMilliseconds) / FULLY_RESTED_TIME_REMAINING_IN_MILLISECONDS) * 100);
}

function processTimeDifferenceArray(hourMinuteArray) {
    var hours = hourMinuteArray[0];
    var minutes = hourMinuteArray[1];

    if (hours <= 0 && minutes <= 0) {
        minutes = $('.lXMinutes').text().replace('{0}', 0);
        return minutes;
    }

    if (hours == 1) {
        hours = $('.lXHour').text().replace('{0}', hours);
    } else if (hours == 0) {
        hours = '';
    } else {
        hours = $('.lXHours').text().replace('{0}', hours);
    }

    if (minutes == 1) {
        minutes = $('.lXMinute').text().replace('{0}', minutes);
    } else {
        minutes = $('.lXMinutes').text().replace('{0}', minutes);
    }
    return hours + ' ' + minutes;
}

function getTime(gameTime, timeUnits, isHeader) {
    var currentTime = new Date(gameTime);
    var currentPeriod = timeUnits === 12 ? ' AM' : '';
    var currentHours = currentTime.getUTCHours();
    var currentMinutes = currentTime.getUTCMinutes();
    var formattedMinutes = currentMinutes < 10 ? '0'+currentMinutes : currentMinutes;
    var currentDay = '';

    switch (currentTime.getUTCDay()) {
        case 0:
            currentDay = "Sunday";
            break;
        case 1:
            currentDay = "Monday";
            break;
        case 2:
            currentDay = "Tuesday";
            break;
        case 3:
            currentDay = "Wednesday";
            break;
        case 4:
            currentDay = "Thursday";
            break;
        case 5:
            currentDay = "Friday";
            break;
        case 6:
            currentDay = "Saturday";
            break;
    }

    if (currentHours > 12 && timeUnits === 12) {
        currentHours -= 12;
        currentPeriod = ' PM';
    }
    var formattedHours = currentHours < 10 ? '0'+currentHours : currentHours;

    if (currentDay == 'Wednesday' && isHeader) {
        $('#headerTime').css('font-size','.9em');
    } else {
        $('#headerTime').css('font-size','1em');
    }

    return currentDay + ' ' + formattedHours + ':' + formattedMinutes + currentPeriod;
}

function updateLanguage(key, value) {
    $('.l' + key).text(value);
}

function getJobIncome(income) {
    /*
        Looking at an economy_data.sii file found, the conversion rates are:
        EUR: 1
        CHF: 1.2
        CZK: 25
        GBP: 0.8
        PLN: 4.2
        HUF: 293
    */
    var currencyCode = $('.currencyCode').text();
    if (currencyCode == 'EUR') {
        income = '€ ' + income;
    } else if (currencyCode == 'GBP') {
        income *= 0.8;
        income = '£ ' + income;
    } else if (currencyCode == 'CHF') {
        income *= 1.2;
        income += '. - CHF';
    } else if (currencyCode == 'CZK') {
        income *= 25;
        income += '. - Kč';
    } else if (currencyCode == 'PLN') {
        income *= 4.2;
        income += '. - zł';
    } else if (currencyCode == 'HUF') {
        income *= 293;
        income += '. - Ft';
    }
    return income;
}

function getDamagePercentage(data) {
    // Return the max value of all damage percentages.
    return Math.max(data.truck.wearEngine,
                    data.truck.wearTransmission,
                    data.truck.wearCabin,
                    data.truck.wearChassis,
                    data.truck.wearWheels) * 100;
}

function getDamageFillForTruck(damagePercentage) {
    // damagePercentage: The value returned from getDamagePercentage
	damagePercentage = Math.floor(damagePercentage);
    if (damagePercentage < 0.5) {
        return 80;
    }
    if (damagePercentage > 99.4) {
        return 25;
    }

    // This is the closest linear fit found from 3 eyeballed data points.
    return (475/6) - (.55 * damagePercentage);
}

function getDamageFillForTrailer(damagePercentage) {
    // damagePercentage: the same as data.wearTrailer
	damagePercentage = Math.floor(damagePercentage);
    if (damagePercentage < .5) {
        return 65;
    }
    if (damagePercentage > 99.4) {
        return 34;
    }

    // This is the closest linear fit found from 3 eyeballed data points.
    return (389/6) - (0.31 * damagePercentage);
}

function showTab(tabName) {
    // Hide all tabs (map, cargo, damage, about)
    $('#map').hide();
    $('#cargo').hide();
    $('#damage').hide();
    $('#about').hide();

    // Remove the "_footerSelected" class from all items.
    $('#mapFooter').removeClass('_footerSelected');
    $('#cargoFooter').removeClass('_footerSelected');
    $('#damageFooter').removeClass('_footerSelected');
    $('#aboutFooter').removeClass('_footerSelected');

    // Show the ID requested
    $('#' + tabName).show();
    $('#' + tabName + 'Footer').addClass('_footerSelected');
}

/** Returns the difference between two dates in ISO 8601 format in an [hour, minutes] array */
function getTimeDifference(begin, end) {
    var beginDate = new Date(begin);
    var endDate = new Date(end);
    var MILLISECONDS_IN_MINUTE = 60*1000;
    var MILLISECONDS_IN_HOUR = MILLISECONDS_IN_MINUTE*60;
    var MILLISECONDS_IN_DAY = MILLISECONDS_IN_HOUR*24;

    var hours = Math.floor((endDate - beginDate) % MILLISECONDS_IN_DAY / MILLISECONDS_IN_HOUR) // number of hours
    var minutes = Math.floor((endDate - beginDate) % MILLISECONDS_IN_DAY % MILLISECONDS_IN_HOUR / MILLISECONDS_IN_MINUTE) // number of minutes
    return [hours, minutes];
}<commit_msg>Fix ETA<commit_after>/*
    ======================================
    Custom dashboard telemetry data filter
    ======================================
*/

// This filter is used to change telemetry data
// before it is displayed on the dashboard.
// For example, you may convert km/h to mph, kilograms to tons, etc.
// "data" object is an instance of the Ets2TelemetryData class
// defined in dashboard-core.ts (or see JSON response in the server's API).

Funbit.Ets.Telemetry.Dashboard.prototype.filter = function (data) {
    // round truck speed
    data.truckSpeedRounded = Math.abs(data.truck.speed > 0
        ? Math.floor(data.truck.speed)
        : Math.round(data.truck.speed));
    // convert kilometers per hour to miles per hour (just an example)
    data.truckSpeedMph = data.truck.speed * 0.621371;
    data.truckSpeedMphRounded = Math.abs(Math.floor(data.truckSpeedMph));
    // convert gear to readable format
    data.gear = data.truck.gear > 0 ? 'D' + data.truck.gear : (data.truck.gear < 0 ? 'R' : 'N');
    data.currentFuelPercentage = (data.truck.fuel / data.truck.fuelCapacity) * 100;

    // scsTruckDamage is the value SCS uses in the route advisor
    data.scsTruckDamage = getDamagePercentage(data);
    data.scsTruckDamageRounded = Math.floor(data.scsTruckDamage);
    data.wearTrailerRounded = Math.floor(data.trailer.wear * 100);

    data.gameTime12h = getTime(data.game.time, 12, true);
    data.jobDeadlineTime12h = getTime(data.job.deadlineTime, 12, false);
    data.trailerMassTons = data.trailer.attached ? ((data.trailer.mass / 1000.0) + ' t') : '';
    data.trailerMassKg = data.trailer.attached ? data.trailer.mass + ' kg' : '';
    data.jobIncome = getJobIncome(data.job.income);
    data.game.nextRestStopTimeArray = getHoursAndMinutes(data.game.nextRestStopTime);
    data.game.nextRestStopTime = processTimeDifferenceArray(data.game.nextRestStopTimeArray);
    data.navigation.speedLimitMph = data.navigation.speedLimit * .621371;
    data.navigation.speedLimitMphRounded = Math.round(data.navigation.speedLimitMph);
    data.navigation.estimatedDistanceKm = data.navigation.estimatedDistance / 1000;
    data.navigation.estimatedDistanceMi = data.navigation.estimatedDistanceKm * .621371;
    data.navigation.estimatedDistanceKmRounded = Math.floor(data.navigation.estimatedDistanceKm);
    data.navigation.estimatedDistanceMiRounded = Math.floor(data.navigation.estimatedDistanceMi);
    var originalEstimatedTime = data.navigation.estimatedTime;
    var timeToDestinationArray = getHoursAndMinutes(originalEstimatedTime);
    data.navigation.estimatedTime = addHoursAndMinutes(data.game.time, timeToDestinationArray[0], timeToDestinationArray[1]);
    data.navigation.estimatedTime = getTime(data.navigation.estimatedTime, 24, false);
    data.navigation.estimatedTime12h = getTime(originalEstimatedTime, 12, false);
    data.navigation.timeToDestination = processTimeDifferenceArray(timeToDestinationArray);

    // return changed data to the core for rendering
    return data;
};

Funbit.Ets.Telemetry.Dashboard.prototype.render = function (data) {
    //
    // data - same data object as in the filter function
    //
    $('#fuelLine').css('width', data.currentFuelPercentage + '%');
    $('#damageLine').css('width', data.scsTruckDamage + '%');
    $('#truckDamageIcon').css('height', getDamageFillForTruck(data.scsTruckDamage) + '%');
    $('#trailerDamageIcon').css('height', getDamageFillForTrailer(data.trailer.wear * 100) + '%');
    $('#restLine').css('width', getFatiguePercentage(data.game.nextRestStopTimeArray[0], data.game.nextRestStopTimeArray[1]) + '%');

    // Process DOM for connection
    if (data.game.connected) {
        $('.has-connection').show();
        $('.no-connection').hide();
    }

    // Process DOM for job
    if (data.trailer.attached) {
        $('.hasJob').show();
        $('.noJob').hide();
    } else {
        $('.hasJob').hide();
        $('.noJob').show();
    }
}

Funbit.Ets.Telemetry.Dashboard.prototype.initialize = function (skinConfig) {
    //
    // skinConfig - a copy of the skin configuration from config.json
    //
    // this function is called before everything else,
    // so you may perform any DOM or resource initializations here

    // Process Speed Units
    var distanceUnits = skinConfig.distanceUnits;
    if (distanceUnits === 'km') {
        $('.speedUnits').text('km/h');
        $('.distanceUnits').text('km');
        $('.truckSpeedRoundedKmhMph').addClass('truckSpeedRounded').removeClass('truckSpeedRoundedKmhMph');
        $('.speedLimitRoundedKmhMph').addClass('navigation-speedLimit').removeClass('speedLimitRoundedKmhMph');
        $('.navigationEstimatedDistanceKmMi').addClass('navigation-estimatedDistanceKmRounded').removeClass('navigationEstimatedDistanceKmMi');
    } else if (distanceUnits === 'mi') {
        $('.speedUnits').text('mph');
        $('.distanceUnits').text('mi');
        $('.truckSpeedRoundedKmhMph').addClass('truckSpeedMphRounded').removeClass('truckSpeedRoundedKmhMph');
        $('.speedLimitRoundedKmhMph').addClass('navigation-speedLimitMphRounded').removeClass('speedLimitRoundedKmhMph');
        $('.navigationEstimatedDistanceKmMi').addClass('navigation-estimatedDistanceMiRounded').removeClass('navigationEstimatedDistanceKmMi');
    }

    // Process kg vs tons
    var weightUnits = skinConfig.weightUnits;
    if (weightUnits === 'kg') {
        $('.trailerMassKgOrT').addClass('trailerMassKg').removeClass('trailerMassKgOrT');
    } else if (weightUnits === 't') {
        $('.trailerMassKgOrT').addClass('trailerMassTons').removeClass('trailerMassKgOrT');
    }

    // Process 12 vs 24 hr time
    var timeFormat = skinConfig.timeFormat;
    if (timeFormat === '12h') {
        $('.game-time').addClass('gameTime12h').removeClass('game-time');
        $('.job-deadlineTime').addClass('jobDeadlineTime12h').removeClass('job-deadlineTime');
        $('.navigation-estimatedTime').addClass('navigation-estimatedTime12h').removeClass('navigation-estimatedTime');
    }

    // Process currency code
    $('.currencyCode').text(skinConfig.currencyCode);

    // Process language JSON
    $.getJSON('skins/mobile-route-advisor/language/'+skinConfig.language, function(json) {
        $.each(json, function(key, value) {
            updateLanguage(key, value);
        });
    });
}

function getHoursAndMinutes(time) {
    var dateTime = new Date(time);
    // ETS2's times are stored in ISO 8601 or YYYY-MM-DDTHH:MM:SSZ. To get hours, grab string positions 11 and 12. For minutes, use 14 and 15.
    var hour = dateTime.getUTCHours();
    var minute = dateTime.getUTCMinutes();
    return [hour, minute];
}

function addHoursAndMinutes(time, hours, minutes) {
    var dateTime = new Date(time);
    dateTime = dateTime.addHours(hours);
    dateTime = dateTime.addMinutes(minutes);

    return dateTime;
}

function getFatiguePercentage(hoursUntilRest, minutesUntilRest) {
    var FULLY_RESTED_TIME_REMAINING_IN_MILLISECONDS = 11*60*60*1000; // 11 hours * 60 min * 60 sec * 1000 milliseconds

    if (hoursUntilRest <= 0 && minutesUntilRest <= 0) {
        return 100;
    }

    var hoursInMilliseconds = hoursUntilRest * 60 * 60 * 1000; // # hours * 60 min * 60 sec * 1000 milliseconds
    var minutesInMilliseconds = minutesUntilRest * 60 * 1000; // # minutes * 60 sec * 1000 milliseconds

    return 100 - (((hoursInMilliseconds + minutesInMilliseconds) / FULLY_RESTED_TIME_REMAINING_IN_MILLISECONDS) * 100);
}

function processTimeDifferenceArray(hourMinuteArray) {
    var hours = hourMinuteArray[0];
    var minutes = hourMinuteArray[1];

    if (hours <= 0 && minutes <= 0) {
        minutes = $('.lXMinutes').text().replace('{0}', 0);
        return minutes;
    }

    if (hours == 1) {
        hours = $('.lXHour').text().replace('{0}', hours);
    } else if (hours == 0) {
        hours = '';
    } else {
        hours = $('.lXHours').text().replace('{0}', hours);
    }

    if (minutes == 1) {
        minutes = $('.lXMinute').text().replace('{0}', minutes);
    } else {
        minutes = $('.lXMinutes').text().replace('{0}', minutes);
    }
    return hours + ' ' + minutes;
}

function getTime(gameTime, timeUnits, isHeader) {
    var currentTime = new Date(gameTime);
    var currentPeriod = timeUnits === 12 ? ' AM' : '';
    var currentHours = currentTime.getUTCHours();
    var currentMinutes = currentTime.getUTCMinutes();
    var formattedMinutes = currentMinutes < 10 ? '0'+currentMinutes : currentMinutes;
    var currentDay = '';

    switch (currentTime.getUTCDay()) {
        case 0:
            currentDay = "Sunday";
            break;
        case 1:
            currentDay = "Monday";
            break;
        case 2:
            currentDay = "Tuesday";
            break;
        case 3:
            currentDay = "Wednesday";
            break;
        case 4:
            currentDay = "Thursday";
            break;
        case 5:
            currentDay = "Friday";
            break;
        case 6:
            currentDay = "Saturday";
            break;
    }

    if (currentHours > 12 && timeUnits === 12) {
        currentHours -= 12;
        currentPeriod = ' PM';
    }
    var formattedHours = currentHours < 10 ? '0'+currentHours : currentHours;

    if (currentDay == 'Wednesday' && isHeader) {
        $('#headerTime').css('font-size','.9em');
    } else {
        $('#headerTime').css('font-size','1em');
    }

    return currentDay + ' ' + formattedHours + ':' + formattedMinutes + currentPeriod;
}

function updateLanguage(key, value) {
    $('.l' + key).text(value);
}

function getJobIncome(income) {
    /*
        Looking at an economy_data.sii file found, the conversion rates are:
        EUR: 1
        CHF: 1.2
        CZK: 25
        GBP: 0.8
        PLN: 4.2
        HUF: 293
    */
    var currencyCode = $('.currencyCode').text();
    if (currencyCode == 'EUR') {
        income = '€ ' + income;
    } else if (currencyCode == 'GBP') {
        income *= 0.8;
        income = '£ ' + income;
    } else if (currencyCode == 'CHF') {
        income *= 1.2;
        income += '. - CHF';
    } else if (currencyCode == 'CZK') {
        income *= 25;
        income += '. - Kč';
    } else if (currencyCode == 'PLN') {
        income *= 4.2;
        income += '. - zł';
    } else if (currencyCode == 'HUF') {
        income *= 293;
        income += '. - Ft';
    }
    return income;
}

function getDamagePercentage(data) {
    // Return the max value of all damage percentages.
    return Math.max(data.truck.wearEngine,
                    data.truck.wearTransmission,
                    data.truck.wearCabin,
                    data.truck.wearChassis,
                    data.truck.wearWheels) * 100;
}

function getDamageFillForTruck(damagePercentage) {
    // damagePercentage: The value returned from getDamagePercentage
	damagePercentage = Math.floor(damagePercentage);
    if (damagePercentage < 0.5) {
        return 80;
    }
    if (damagePercentage > 99.4) {
        return 25;
    }

    // This is the closest linear fit found from 3 eyeballed data points.
    return (475/6) - (.55 * damagePercentage);
}

function getDamageFillForTrailer(damagePercentage) {
    // damagePercentage: the same as data.wearTrailer
	damagePercentage = Math.floor(damagePercentage);
    if (damagePercentage < .5) {
        return 65;
    }
    if (damagePercentage > 99.4) {
        return 34;
    }

    // This is the closest linear fit found from 3 eyeballed data points.
    return (389/6) - (0.31 * damagePercentage);
}

function showTab(tabName) {
    // Hide all tabs (map, cargo, damage, about)
    $('#map').hide();
    $('#cargo').hide();
    $('#damage').hide();
    $('#about').hide();

    // Remove the "_footerSelected" class from all items.
    $('#mapFooter').removeClass('_footerSelected');
    $('#cargoFooter').removeClass('_footerSelected');
    $('#damageFooter').removeClass('_footerSelected');
    $('#aboutFooter').removeClass('_footerSelected');

    // Show the ID requested
    $('#' + tabName).show();
    $('#' + tabName + 'Footer').addClass('_footerSelected');
}

/** Returns the difference between two dates in ISO 8601 format in an [hour, minutes] array */
function getTimeDifference(begin, end) {
    var beginDate = new Date(begin);
    var endDate = new Date(end);
    var MILLISECONDS_IN_MINUTE = 60*1000;
    var MILLISECONDS_IN_HOUR = MILLISECONDS_IN_MINUTE*60;
    var MILLISECONDS_IN_DAY = MILLISECONDS_IN_HOUR*24;

    var hours = Math.floor((endDate - beginDate) % MILLISECONDS_IN_DAY / MILLISECONDS_IN_HOUR) // number of hours
    var minutes = Math.floor((endDate - beginDate) % MILLISECONDS_IN_DAY % MILLISECONDS_IN_HOUR / MILLISECONDS_IN_MINUTE) // number of minutes
    return [hours, minutes];
}

Date.prototype.addHours = function(h) {
   this.setTime(this.getTime() + (h*60*60*1000));
   return this;
}

Date.prototype.addMinutes = function(m) {
    this.setTime(this.getTime() + (m*60*1000));
    return this;
}<|endoftext|>