<commit_before>import 'source-map-support/register';

import path from 'path';
import chalk from 'chalk';
import { isPlainObject, isBoolean, isString, set, difference } from 'lodash';
import resolve from 'resolve';
import leven from 'leven';
import trimNewlines from 'trim-newlines';
import redent from 'redent';

import { merge } from '../configuration';
import buildDocumentationObject from '../documentation/build-documentation-object';
import generateTable from '../documentation/generate-table';
import { getDefaultValue } from '../documentation/helpers';
import { fileExists } from '../helpers';
import { throwError } from '../validation';

/**
 * Builds a configuration object.
 *
 * @param {boolean} debug - If debug mode should be enabled, logs some extra information.
 * @param {object} config - The base configuration.
 * @param {object} meta - The base meta configuration.
 * @param {object} newConfig - The new configuration to base the merge on.
 * @param {object} newMeta - The new meta configuration to base the merge on.
 * @param {string} [directory=process.cwd()] - The directory to resolve relative paths from.
 * @param {boolean} [validate=true] - If the newConfig and the newMeta structure should be validated.
 * @returns {object} The result of with the built configurations.
 * @property {extensionConfig} The extensions merged configurations
 * @property {config} The final configuration, with application configuration.
 * @property {meta} The merged meta configuration.
 */
export function buildCompleteConfig(
    debug, config = {}, meta = {}, newConfig = {}, newMeta = {}, directory = process.cwd(), validate = true
) {
    let finalConfig = { ...config };
    let finalMeta = { ...meta };

    let usedExtensions = [];
    const mergeExtension = (extensionName) => {
        const { baseConfig, metaConfig } = getExtension(extensionName, directory);

        if (baseConfig && metaConfig) {
            usedExtensions.push(extensionName);
            finalConfig = merge(finalConfig, baseConfig);
            finalMeta = merge(finalMeta, metaConfig);
        }
    };

    if (fileExists('package.json')) {
        // If extensions are defined we will use them to merge the configurations
        if (newConfig.extensions && newConfig.extensions.length) {
            newConfig.extensions.forEach(mergeExtension);
        } else {
            const projectPackageJson = require(path.join(directory, 'package.json'));
            [
                ...Object.keys(projectPackageJson.dependencies || {}),
                ...Object.keys(projectPackageJson.devDependencies || {})
            ]
            .filter(dependecy => /^roc(-.+)/.test(dependecy))
            .forEach(mergeExtension);
        }

        if (usedExtensions.length && debug) {
            console.log('The following Roc extensions will be used:', usedExtensions, '\n');
        }

        // Check for a mismatch between application configuration and extensions.
        if (validate) {
            if (Object.keys(newConfig).length) {
                console.log(validateConfigurationStructure(finalConfig, newConfig));
            }
            if (Object.keys(newMeta).length) {
                console.log(validateConfigurationStructure(finalMeta, newMeta));
            }
        }
    }

    return {
        extensionConfig: finalConfig,
        config: merge(finalConfig, newConfig),
        meta: merge(finalMeta, newMeta)
    };
}

function getExtension(extensionName, directory) {
    try {
        const { baseConfig, metaConfig } = require(resolve.sync(extensionName, { basedir: directory }));
        return { baseConfig, metaConfig };
    } catch (err) {
        console.log(
            chalk.bgRed(
                'Failed to load Roc extension ' + chalk.bold(extensionName) + '. ' +
                'Make sure you have it installed. Try running:'
            ) + ' ' +
            chalk.underline('npm install --save ' + extensionName)
        , '\n');
        return {};
    }
}

function validateConfigurationStructure(config, applicationConfig) {
    const getKeys = (obj, oldPath = '', allKeys = []) => {
        Object.keys(obj).forEach((key) => {
            const value = obj[key];
            const newPath = oldPath + key;

            if (isPlainObject(value)) {
                getKeys(value, newPath + '.', allKeys);
            } else {
                allKeys.push(newPath);
            }
        });

        return allKeys;
    };
    const info = [];
    const keys = getKeys(config);
    const diff = difference(getKeys(applicationConfig), keys);
    if (diff.length > 0) {
        info.push(chalk.bgRed('Configuration problem') +
            ' There was a mismatch in the application configuration structure, make sure this is correct.\n');
        info.push(getSuggestions(diff, keys));
        info.push('');
    }
    // }
    return info.join('\n');
}

export function getSuggestions(current, possible, command = false) {
    const info = [];

    current.forEach((currentKey) => {
        let shortest = 0;
        let closest;

        for (let key of possible) {
            let distance = leven(currentKey, key);

            if (distance <= 0 || distance > 4) {
                continue;
            }

            if (shortest && distance >= shortest) {
                continue;
            }

            closest = key;
            shortest = distance;
        }

        const extra = command ? '--' : '';
        if (closest) {
            info.push('Did not understand ' + chalk.underline(extra + currentKey) +
                ' - Did you mean ' + chalk.underline(extra + closest));
        } else {
            info.push('Did not understand ' + chalk.underline(extra + currentKey));
        }
    });

    return info.join('\n');
}

export function generateCommandsDocumentation({ commands }, { commands: commandsMeta }) {
    const header = {
        name: true,
        description: true
    };

    const noCommands = {'No commands available.': ''};
    commandsMeta = commandsMeta || {};

    let table = [{
        name: 'Commands',
        objects: Object.keys(commands || noCommands).map((command) => {
            const options = commandsMeta[command] ?
                ' ' + getCommandOptionsAsString(commandsMeta[command]) :
                '';
            const description = commandsMeta[command] && commandsMeta[command].description ?
                commandsMeta[command].description :
                '';

            return {
                name: (command + options),
                description
            };
        })
    }, {
        name: 'Options',
        objects: [{
            name: '-h, --help',
            description: 'Output usage information.'
        }, {
            name: '-v, --version',
            description: 'Output version number.'
        }, {
            name: '-d, --debug',
            description: 'Enable debug mode.'
        }, {
            name: '-c, --config',
            description: `Path to configuration file, will default to ${chalk.bold('roc.config.js')} in current ` +
                `working directory.`
        }, {
            name: '-D, --directory',
            description: 'Path to working directory, will default to the current working directory. Can be either ' +
                'absolute or relative.'
        }]
    }];

    return generateTable(table, header, {
        compact: true,
        titleWrapper: (name) => name + ':',
        cellDivider: '',
        rowWrapper: (input) => `${input}`,
        header: false,
        groupTitleWrapper: (input) => input + ':'
    });
}

function getCommandOptionsAsString(command = {}) {
    let options = '';
    (command.options || []).forEach((option) => {
        options += option.required ? `<${option.name}> ` : `[${option.name}] `;
    });

    return options;
}

/**
 * Generates plain text documentation for the provided configuration object
 *
 * Prints the documentation directly to the console.log
 *
 * @param {object} config - the configuration object to generate documentation for
 * @param {object} metaConfig - the meta configuration object that has information about the configuration object
 * @param {string} command - the current command to show information about
 * @param {string} name - the current command line program
 * @returns {string} The documentation as a string.
 */
export function generateCommandDocumentation({ settings }, { commands, settings: meta }, command, name) {
    const rows = [];
    rows.push('Usage: ' + name + ' ' + command + ' ' + getCommandOptionsAsString(commands[command]));
    rows.push('');

    if (commands[command] && commands[command].help) {
        rows.push(redent(trimNewlines(commands[command].help)));
        rows.push('');
    }

    let documentationObject = [];

    // Generate the options table
    if (commands[command] && commands[command].settings) {
        rows.push('Options:');
        rows.push('');

        const filter = commands[command].settings === true ? [] : commands[command].settings;

        documentationObject = buildDocumentationObject(settings, meta, filter);
    }

    const header = {
        cli: true,
        description: {
            name: 'Description',
            padding: false
        },
        defaultValue: {
            name: 'Default',
            renderer: (input) => {
                input = getDefaultValue(input);

                if (input === undefined) {
                    return '';
                }

                if (!input) {
                    return chalk.yellow('No default value');
                }

                return chalk.cyan(input);
            }
        }
    };

    documentationObject.push({
        name: 'CLI options',
        objects: [{
            cli: '-h, --help',
            description: 'Output usage information.'
        }, {
            cli: '-v, --version',
            description: 'Output version number.'
        }, {
            cli: '-d, --debug',
            description: 'Enable debug mode.'
        }, {
            cli: '-c, --config',
            description: `Path to configuration file, will default to ${chalk.bold('roc.config.js')} in current ` +
                `working directory.`
        }, {
            cli: '-D, --directory',
            description: 'Path to working directory, will default to the current working directory. Can be either ' +
                'absolute or relative.'
        }]
    });

    rows.push(generateTable(documentationObject, header, {
        compact: true,
        titleWrapper: (input) => input + ':',
        cellDivider: '',
        rowWrapper: (input) => `${input}`,
        header: false,
        groupTitleWrapper: (input) => input + ':'
    }));

    return rows.join('\n');
}

export function parseOptions(command, meta, options) {
    // If the command supports options
    if (meta[command] && meta[command].options) {
        let parsedArguments = {};
        meta[command].options.forEach((option, index) => {
            const value = options[index];

            if (option.required && !value) {
                throw new Error(`Required option "${option.name}" was not provided.`);
            }

            if (value && option.validation) {
                const validationResult = option.validation(value);
                if (validationResult !== true) {
                    try {
                        throwError(option.name, validationResult, value, 'option');
                    } catch (err) {
                        /* eslint-disable no-process-exit, no-console */
                        console.log(chalk.bgRed('Arguments problem') + ' An option was not valid.\n');
                        console.log(err.message);
                        process.exit(1);
                        /* eslint-enable */
                    }
                }
            }

            parsedArguments[option.name] = value;
        });

        return {
            arguments: parsedArguments,
            rest: options.splice(Object.keys(parsedArguments).length)
        };
    }

    return {
        arguments: undefined,
        rest: options
    };
}

export function getMappings(documentationObject) {
    const recursiveHelper = (groups) => {
        let mappings = {};

        groups.forEach((group) => {
            group.objects.forEach((element) => {
                // Remove the two dashes in the beginning to match correctly
                mappings[element.cli.substr(2)] = {
                    path: element.path,
                    convertor: getConvertor(element.defaultValue, element.cli),
                    validator: element.validator
                };
            });

            mappings = Object.assign({}, mappings, recursiveHelper(group.children));
        });

        return mappings;
    };

    return recursiveHelper(documentationObject);
}

// Convert values based on their default value
function getConvertor(value, name) {
    if (isBoolean(value)) {
        return (input) => {
            if (isBoolean(input)) {
                return input;
            }
            if (input === 'true' || input === 'false') {
                return input === 'true';
            }

            // TODO Do not have console.log here!
            console.log(`Invalid value given for ${chalk.bold(name)}. Will use the default ` +
                `${chalk.bold(value)}.`);

            return value;
        };
    } else if (Array.isArray(value)) {
        return (input) => {
            let parsed;
            try {
                parsed = JSON.parse(input);
            } catch (err) {
                // Ignore this case
            }

            if (Array.isArray(parsed)) {
                return parsed;
            }

            return input.toString().split(',');
        };
    } else if (Number.isInteger(value)) {
        return (input) => parseInt(input, 10);
    } else if (!isString(value) && (!value || Object.keys(value).length === 0)) {
        return (input) => JSON.parse(input);
    }

    return (input) => input;
}

export function parseArguments(args, mappings) {
    const config = {};
    const info = [];

    Object.keys(args).forEach((key) => {
        if (mappings[key]) {
            const value = convert(args[key], mappings[key]);
            set(config, mappings[key].path, value);
        } else {
            // We did not find a match
            info.push(getSuggestions([key], Object.keys(mappings), true));
        }
    });

    if (info.length > 0) {
        console.log(chalk.bgRed('CLI problem') + ' Some commands was not understood.\n');
        console.log(info.join('\n') + '\n');
    }

    return config;
}

function convert(value, mapping) {
    // Maybe we can let the validation happen later?
    // Should in reallity be managed when we do validation on everything
    const val = mapping.convertor(value);
    if (mapping.validator(val)) {
        return val;
    }
}
<commit_msg>Made it possible to use RegExp for option validation<commit_after>import 'source-map-support/register';

import path from 'path';
import chalk from 'chalk';
import { isPlainObject, isBoolean, isString, set, difference } from 'lodash';
import resolve from 'resolve';
import leven from 'leven';
import trimNewlines from 'trim-newlines';
import redent from 'redent';

import { merge } from '../configuration';
import buildDocumentationObject from '../documentation/build-documentation-object';
import generateTable from '../documentation/generate-table';
import { getDefaultValue } from '../documentation/helpers';
import { fileExists } from '../helpers';
import { throwError } from '../validation';
import { isValid } from '../validation/helpers';

/**
 * Builds a configuration object.
 *
 * @param {boolean} debug - If debug mode should be enabled, logs some extra information.
 * @param {object} config - The base configuration.
 * @param {object} meta - The base meta configuration.
 * @param {object} newConfig - The new configuration to base the merge on.
 * @param {object} newMeta - The new meta configuration to base the merge on.
 * @param {string} [directory=process.cwd()] - The directory to resolve relative paths from.
 * @param {boolean} [validate=true] - If the newConfig and the newMeta structure should be validated.
 * @returns {object} The result of with the built configurations.
 * @property {extensionConfig} The extensions merged configurations
 * @property {config} The final configuration, with application configuration.
 * @property {meta} The merged meta configuration.
 */
export function buildCompleteConfig(
    debug, config = {}, meta = {}, newConfig = {}, newMeta = {}, directory = process.cwd(), validate = true
) {
    let finalConfig = { ...config };
    let finalMeta = { ...meta };

    let usedExtensions = [];
    const mergeExtension = (extensionName) => {
        const { baseConfig, metaConfig } = getExtension(extensionName, directory);

        if (baseConfig && metaConfig) {
            usedExtensions.push(extensionName);
            finalConfig = merge(finalConfig, baseConfig);
            finalMeta = merge(finalMeta, metaConfig);
        }
    };

    if (fileExists('package.json')) {
        // If extensions are defined we will use them to merge the configurations
        if (newConfig.extensions && newConfig.extensions.length) {
            newConfig.extensions.forEach(mergeExtension);
        } else {
            const projectPackageJson = require(path.join(directory, 'package.json'));
            [
                ...Object.keys(projectPackageJson.dependencies || {}),
                ...Object.keys(projectPackageJson.devDependencies || {})
            ]
            .filter(dependecy => /^roc(-.+)/.test(dependecy))
            .forEach(mergeExtension);
        }

        if (usedExtensions.length && debug) {
            console.log('The following Roc extensions will be used:', usedExtensions, '\n');
        }

        // Check for a mismatch between application configuration and extensions.
        if (validate) {
            if (Object.keys(newConfig).length) {
                console.log(validateConfigurationStructure(finalConfig, newConfig));
            }
            if (Object.keys(newMeta).length) {
                console.log(validateConfigurationStructure(finalMeta, newMeta));
            }
        }
    }

    return {
        extensionConfig: finalConfig,
        config: merge(finalConfig, newConfig),
        meta: merge(finalMeta, newMeta)
    };
}

function getExtension(extensionName, directory) {
    try {
        const { baseConfig, metaConfig } = require(resolve.sync(extensionName, { basedir: directory }));
        return { baseConfig, metaConfig };
    } catch (err) {
        console.log(
            chalk.bgRed(
                'Failed to load Roc extension ' + chalk.bold(extensionName) + '. ' +
                'Make sure you have it installed. Try running:'
            ) + ' ' +
            chalk.underline('npm install --save ' + extensionName)
        , '\n');
        return {};
    }
}

function validateConfigurationStructure(config, applicationConfig) {
    const getKeys = (obj, oldPath = '', allKeys = []) => {
        Object.keys(obj).forEach((key) => {
            const value = obj[key];
            const newPath = oldPath + key;

            if (isPlainObject(value)) {
                getKeys(value, newPath + '.', allKeys);
            } else {
                allKeys.push(newPath);
            }
        });

        return allKeys;
    };
    const info = [];
    const keys = getKeys(config);
    const diff = difference(getKeys(applicationConfig), keys);
    if (diff.length > 0) {
        info.push(chalk.bgRed('Configuration problem') +
            ' There was a mismatch in the application configuration structure, make sure this is correct.\n');
        info.push(getSuggestions(diff, keys));
        info.push('');
    }
    // }
    return info.join('\n');
}

export function getSuggestions(current, possible, command = false) {
    const info = [];

    current.forEach((currentKey) => {
        let shortest = 0;
        let closest;

        for (let key of possible) {
            let distance = leven(currentKey, key);

            if (distance <= 0 || distance > 4) {
                continue;
            }

            if (shortest && distance >= shortest) {
                continue;
            }

            closest = key;
            shortest = distance;
        }

        const extra = command ? '--' : '';
        if (closest) {
            info.push('Did not understand ' + chalk.underline(extra + currentKey) +
                ' - Did you mean ' + chalk.underline(extra + closest));
        } else {
            info.push('Did not understand ' + chalk.underline(extra + currentKey));
        }
    });

    return info.join('\n');
}

export function generateCommandsDocumentation({ commands }, { commands: commandsMeta }) {
    const header = {
        name: true,
        description: true
    };

    const noCommands = {'No commands available.': ''};
    commandsMeta = commandsMeta || {};

    let table = [{
        name: 'Commands',
        objects: Object.keys(commands || noCommands).map((command) => {
            const options = commandsMeta[command] ?
                ' ' + getCommandOptionsAsString(commandsMeta[command]) :
                '';
            const description = commandsMeta[command] && commandsMeta[command].description ?
                commandsMeta[command].description :
                '';

            return {
                name: (command + options),
                description
            };
        })
    }, {
        name: 'Options',
        objects: [{
            name: '-h, --help',
            description: 'Output usage information.'
        }, {
            name: '-v, --version',
            description: 'Output version number.'
        }, {
            name: '-d, --debug',
            description: 'Enable debug mode.'
        }, {
            name: '-c, --config',
            description: `Path to configuration file, will default to ${chalk.bold('roc.config.js')} in current ` +
                `working directory.`
        }, {
            name: '-D, --directory',
            description: 'Path to working directory, will default to the current working directory. Can be either ' +
                'absolute or relative.'
        }]
    }];

    return generateTable(table, header, {
        compact: true,
        titleWrapper: (name) => name + ':',
        cellDivider: '',
        rowWrapper: (input) => `${input}`,
        header: false,
        groupTitleWrapper: (input) => input + ':'
    });
}

function getCommandOptionsAsString(command = {}) {
    let options = '';
    (command.options || []).forEach((option) => {
        options += option.required ? `<${option.name}> ` : `[${option.name}] `;
    });

    return options;
}

/**
 * Generates plain text documentation for the provided configuration object
 *
 * Prints the documentation directly to the console.log
 *
 * @param {object} config - the configuration object to generate documentation for
 * @param {object} metaConfig - the meta configuration object that has information about the configuration object
 * @param {string} command - the current command to show information about
 * @param {string} name - the current command line program
 * @returns {string} The documentation as a string.
 */
export function generateCommandDocumentation({ settings }, { commands, settings: meta }, command, name) {
    const rows = [];
    rows.push('Usage: ' + name + ' ' + command + ' ' + getCommandOptionsAsString(commands[command]));
    rows.push('');

    if (commands[command] && commands[command].help) {
        rows.push(redent(trimNewlines(commands[command].help)));
        rows.push('');
    }

    let documentationObject = [];

    // Generate the options table
    if (commands[command] && commands[command].settings) {
        rows.push('Options:');
        rows.push('');

        const filter = commands[command].settings === true ? [] : commands[command].settings;

        documentationObject = buildDocumentationObject(settings, meta, filter);
    }

    const header = {
        cli: true,
        description: {
            name: 'Description',
            padding: false
        },
        defaultValue: {
            name: 'Default',
            renderer: (input) => {
                input = getDefaultValue(input);

                if (input === undefined) {
                    return '';
                }

                if (!input) {
                    return chalk.yellow('No default value');
                }

                return chalk.cyan(input);
            }
        }
    };

    documentationObject.push({
        name: 'CLI options',
        objects: [{
            cli: '-h, --help',
            description: 'Output usage information.'
        }, {
            cli: '-v, --version',
            description: 'Output version number.'
        }, {
            cli: '-d, --debug',
            description: 'Enable debug mode.'
        }, {
            cli: '-c, --config',
            description: `Path to configuration file, will default to ${chalk.bold('roc.config.js')} in current ` +
                `working directory.`
        }, {
            cli: '-D, --directory',
            description: 'Path to working directory, will default to the current working directory. Can be either ' +
                'absolute or relative.'
        }]
    });

    rows.push(generateTable(documentationObject, header, {
        compact: true,
        titleWrapper: (input) => input + ':',
        cellDivider: '',
        rowWrapper: (input) => `${input}`,
        header: false,
        groupTitleWrapper: (input) => input + ':'
    }));

    return rows.join('\n');
}

export function parseOptions(command, meta, options) {
    // If the command supports options
    if (meta[command] && meta[command].options) {
        let parsedArguments = {};
        meta[command].options.forEach((option, index) => {
            const value = options[index];

            if (option.required && !value) {
                throw new Error(`Required option "${option.name}" was not provided.`);
            }

            if (value && option.validation) {
                const validationResult = isValid(value, option.validation);
                if (validationResult !== true) {
                    try {
                        throwError(option.name, validationResult, value, 'option');
                    } catch (err) {
                        /* eslint-disable no-process-exit, no-console */
                        console.log(chalk.bgRed('Arguments problem') + ' An option was not valid.\n');
                        console.log(err.message);
                        process.exit(1);
                        /* eslint-enable */
                    }
                }
            }

            parsedArguments[option.name] = value;
        });

        return {
            arguments: parsedArguments,
            rest: options.splice(Object.keys(parsedArguments).length)
        };
    }

    return {
        arguments: undefined,
        rest: options
    };
}

export function getMappings(documentationObject) {
    const recursiveHelper = (groups) => {
        let mappings = {};

        groups.forEach((group) => {
            group.objects.forEach((element) => {
                // Remove the two dashes in the beginning to match correctly
                mappings[element.cli.substr(2)] = {
                    path: element.path,
                    convertor: getConvertor(element.defaultValue, element.cli),
                    validator: element.validator
                };
            });

            mappings = Object.assign({}, mappings, recursiveHelper(group.children));
        });

        return mappings;
    };

    return recursiveHelper(documentationObject);
}

// Convert values based on their default value
function getConvertor(value, name) {
    if (isBoolean(value)) {
        return (input) => {
            if (isBoolean(input)) {
                return input;
            }
            if (input === 'true' || input === 'false') {
                return input === 'true';
            }

            // TODO Do not have console.log here!
            console.log(`Invalid value given for ${chalk.bold(name)}. Will use the default ` +
                `${chalk.bold(value)}.`);

            return value;
        };
    } else if (Array.isArray(value)) {
        return (input) => {
            let parsed;
            try {
                parsed = JSON.parse(input);
            } catch (err) {
                // Ignore this case
            }

            if (Array.isArray(parsed)) {
                return parsed;
            }

            return input.toString().split(',');
        };
    } else if (Number.isInteger(value)) {
        return (input) => parseInt(input, 10);
    } else if (!isString(value) && (!value || Object.keys(value).length === 0)) {
        return (input) => JSON.parse(input);
    }

    return (input) => input;
}

export function parseArguments(args, mappings) {
    const config = {};
    const info = [];

    Object.keys(args).forEach((key) => {
        if (mappings[key]) {
            const value = convert(args[key], mappings[key]);
            set(config, mappings[key].path, value);
        } else {
            // We did not find a match
            info.push(getSuggestions([key], Object.keys(mappings), true));
        }
    });

    if (info.length > 0) {
        console.log(chalk.bgRed('CLI problem') + ' Some commands was not understood.\n');
        console.log(info.join('\n') + '\n');
    }

    return config;
}

function convert(value, mapping) {
    // Maybe we can let the validation happen later?
    // Should in reallity be managed when we do validation on everything
    const val = mapping.convertor(value);
    if (mapping.validator(val)) {
        return val;
    }
}
<|endoftext|>


<commit_before>import React from 'react'
import Component from 'react-pure-render/component'
import Auth0Badge from 'home/Auth0Badge.jsx'
import HeapAnalyticsBadge from 'home/HeapAnalyticsBadge.jsx'
import SheetsuBadge from 'home/SheetsuBadge.jsx'
import { Link } from 'react-router'
import 'home/Home.scss'
import Badge from 'calendar/badges/Badge.jsx'
import Colors from 'styles/colors'
import moment from 'moment'

const WhatIsNextItem = ({date, children, done}) => (

      {done
        ?
        (<Badge square heightRem={3} borderColor={Colors.body} color={Colors.body} bgColor='white'
          style={{marginRight: '1rem'}}>
          DONE
        </Badge>)
        : <Badge square heightRem={3} bgColor={Colors.primary} style={{marginRight: '1rem'}}>PLANNED</Badge>
      }
      {done
        ? planned {date}
        : ({date})
      }

    {done
      ?
{children}

      :
{children}

    }

)

export default class Home extends Component {
  render() {
    return (

RCN.io

            We build Calendars for Bike Racers. Check out official
            <Link to={'/calendars/ncnca-2017'}>2017 NCNCA Calendar</Link>.

            We need your help with finding unofficial NorCal MTB Events. Drop us a link (right bottom corner)
            if you know any events that were not covered
            in <Link to={'/calendars/norcal-mtb'}>2016 MTB Calendar</Link>.

What is coming next?

          <WhatIsNextItem date={moment('20 Sep 2016').fromNow()}>Filters! Allowing to filter by Discipline, Event Type
            and more, all at the same time. Mix and match!
          </WhatIsNextItem>
          <WhatIsNextItem done date={moment('1 Jul 2016').fromNow()}>
            We are working on full NCNCA Calendar that would include official USAC events,
            not only MTB, but also Road, Track and CX. Next we will focus on event details page that would include Map,
            Flyer, Start Times and other useful info.
          </WhatIsNextItem>

          <Auth0Badge className="badge" />
          <HeapAnalyticsBadge className="badge"/>
          <SheetsuBadge className="badge" />

    )
  }
}
<commit_msg>fixing a bug on home screen with momentjs<commit_after>import React from 'react'
import Component from 'react-pure-render/component'
import Auth0Badge from 'home/Auth0Badge.jsx'
import HeapAnalyticsBadge from 'home/HeapAnalyticsBadge.jsx'
import SheetsuBadge from 'home/SheetsuBadge.jsx'
import { Link } from 'react-router'
import 'home/Home.scss'
import Badge from 'calendar/badges/Badge.jsx'
import Colors from 'styles/colors'
import moment from 'moment'

const WhatIsNextItem = ({date, children, done}) => (

      {done
        ?
        (<Badge square heightRem={3} borderColor={Colors.body} color={Colors.body} bgColor='white'
          style={{marginRight: '1rem'}}>
          DONE
        </Badge>)
        : <Badge square heightRem={3} bgColor={Colors.primary} style={{marginRight: '1rem'}}>PLANNED</Badge>
      }
      {done
        ? planned {date}
        : ({date})
      }

    {done
      ?
{children}

      :
{children}

    }

)

export default class Home extends Component {
  render() {
    return (

RCN.io

            We build Calendars for Bike Racers. Check out official
            <Link to={'/calendars/ncnca-2017'}>2017 NCNCA Calendar</Link>.

            We need your help with finding unofficial NorCal MTB Events. Drop us a link (right bottom corner)
            if you know any events that were not covered
            in <Link to={'/calendars/norcal-mtb'}>2016 MTB Calendar</Link>.

What is coming next?

          <WhatIsNextItem date={moment('20 Sep 2016', 'DD MMM YYY').fromNow()}>Filters! Allowing to filter by Discipline, Event Type
            and more, all at the same time. Mix and match!
          </WhatIsNextItem>
          <WhatIsNextItem done date={moment('1 Jul 2016', 'DD MMM YYY').fromNow()}>
            We are working on full NCNCA Calendar that would include official USAC events,
            not only MTB, but also Road, Track and CX. Next we will focus on event details page that would include Map,
            Flyer, Start Times and other useful info.
          </WhatIsNextItem>

          <Auth0Badge className="badge" />
          <HeapAnalyticsBadge className="badge"/>
          <SheetsuBadge className="badge" />

    )
  }
}
<|endoftext|>


<commit_before>/**
 * boot.js -- loads lively in any page that inserts through a script tag
 *
 **/

/* eslint no-console: off */

/*
 * HELPER
 */

async function loadJavaScript(name, src, force) {
  var code = await fetch(src).then(r => r.text())
  eval(code)
}

self.lively4currentbootid = "" + new Date()
self.lively4bootlogData = []
self.lively4bootlog = function add(url, date=Date.now(), mode="load", time=0, parentURL) {
  self.lively4bootlogData.push({
    url, date, mode, time, parentURL, bootid: self.lively4currentbootid
  })
}

if (!(localStorage["logLivelyBoot"] == "true")) {
  self.lively4bootlog = function() {
    // do nothing
  }
}

/*
 * CACHES
 */
self.lively4transpilationCache = {
  update(cacheKey, cache) {
    this.cache.set(cacheKey, cache)
    var cacheJSO = {
      url: cacheKey,
      input: cache.input,
      output: cache.output,
      map: JSON.stringify(cache.map),
    }

    if (!cacheKey.match(/^workspace/) && !self.__karma__) {
      console.log("[babel] update transpilation cache " + cacheKey) // from client to server :-) #Security anybody?
      var transpileCacheURL = lively4url + "/.transpiled/" + cacheKey.replace(lively4url + "/","").replace(/\//g,"_") // flatten path
      fetch(transpileCacheURL, {
        method: "PUT",
        headers: {
          nocommit: true, // .transpiled is in gitignore... and the server cannot handle it automaitcally
        },
        body: cacheJSO.output
      })

      fetch(transpileCacheURL + ".map.json", {
        method: "PUT",
        headers: {
          nocommit: true
        },
        body: cacheJSO.map
      })
    }
  },
  cache: new Map()
}

self.lively4syncCache = new Map()
self.lively4optionsCache = new Map()
self.lively4fetchLog = []

async function invalidateFileCaches()  {
  var url = lively4url + "/"

  if (self.lively && lively.fileIndexWorker) {
    lively.fileIndexWorker.postMessage({message: "updateDirectory", url})
  }

  const FilesCaches = await System.import("src/client/files-caches.js")
  console.log("[boot] invalidateFileCaches:\n" + FilesCaches.invalidateTranspiledFiles())

}
self.lively4invalidateFileCaches = invalidateFileCaches

async function preloadFileCaches() {
  await loadJavaScript("JSZip", lively4url + "/src/external/jszip.js" )

  var start = performance.now()
  var preloadurl = lively4url + "/.lively4bundle.zip" + "?" + Date.now()
  var resp = await fetch(preloadurl)
  if (resp.status != "200") {
    console.warn("NO preload cache found in", preloadurl)
    return
  }
  var contents = await resp.blob()

  var archive = await self.JSZip.loadAsync(contents)
  console.log("[boot] preloadFileCache fetched contents in  " + Math.round(performance.now() - start) + "ms")

  start = performance.now()
  for(let ea of Object.keys(archive.files)) {
    if (ea.match(/\.transpiled\//) || ea.match(/\.options\//)) continue;

    let file = archive.file(ea);
    if (file) {
      let modified = file.date.toISOString().replace(/T/, " ").replace(/\..*/, ""),
        url = lively4url + "/" + ea,
        content = await file.async("string"),
        mimeType = " text/plain"
      if (url.match(/\.js$/)) mimeType = "application/javascript"
      if (url.match(/\.css$/)) mimeType = "text/css"
      var response = new Response(content, {
        headers: {
          "content-type": mimeType,
          modified: modified
        }
      })
      self.lively4syncCache.set(url, response)

      let optionsPath = ".options/" + ea.replace(/\//g,"_"),
        optionsFile = archive.file(optionsPath)
      if (optionsFile) {
        let optionsContent = await optionsFile.async("string")
        self.lively4optionsCache.set(url, new Response(optionsContent, {
          headers: {
            "content-type": "application/json"
          }
        }))
      }

      if (ea.match(/.js$/)) {
        let transpiledPath = ".transpiled/" + ea.replace(/\//g,"_"),
            transpiledFile = archive.file(transpiledPath),
            mapFile = archive.file(transpiledPath + ".map.json");

        if (transpiledFile) {
          // console.log("[boot] preloadFileCache initialize transpiled javascript: " + ea)
          try {
            var transpiledCode = await transpiledFile.async("string")
            if (mapFile) {
              var map = JSON.parse(await mapFile.async("string"))
            }
            self.lively4transpilationCache.cache.set(url, {
              input: content,
              output: transpiledCode,
              map: map,
              modified: modified
            })
          } catch(e) {
            console.error("[boot] error in loading transpiled code: " + ea, e)
          }
        }
      }
    }
  }
  console.log("[boot] preloadFileCache updated caches in  " + Math.round(performance.now() - start) + "ms")
}

self.lively4fetchHandlers = []
function instrumentFetch() {
  if (!self.originalFetch) self.originalFetch = self.fetch
  self.fetch = async function(request, options, ...rest) {
    var result = await new Promise(resolve => {
      try {

        if (self.lively4fetchHandlers) {
          // go through our list of handlers... the first one who handles it wins
          for(var handler of self.lively4fetchHandlers) {
            var handled = handler.handle && handler.handle(request, options)
            if (handled) return resolve(handled.result);
          }
        }
      } catch(e) {
        console.error(`FETCH ERROR during rques: ${request} falling back because of`, e)
      }
      return resolve(self.originalFetch.apply(self, [request, options, ...rest]))
    })
    if (self.lively4fetchHandlers) {
      // anybody insterested when it finished
      for(var handler of self.lively4fetchHandlers) {
        handler.finsihed && await handler.finsihed(request, options)
      }
    }
    return result
  }
}

function installCachingFetch() {
  self.lively4fetchHandlers.push({
    handle(request, options) {
      var url = (request.url || request).toString()
      var method = "GET"
      if (options && options.method) method = options.method;

      if (url.match(lively4url)) {
        self.lively4fetchLog.push({
          time: performance.now(),
          method: method,
          url: url
        })
        if (!self.lively4syncCache) return
        if (method == "GET") {
          if (options && options.headers && (options.headers["fileversion"] || options.headers["forediting"])) {
            return // don't cache versions request...
          }

          let match = self.lively4syncCache.get(url)
          if (match) {
            // console.log("[boot] SYNC CACHED " + url)
            return {
              result: Promise.resolve(match.clone())
            }
          } else {
            // console.log("[boot] SYNC MISSED " + url)
          }
        } else if (method == "PUT") {
          // clear cache for PUT
          // so next GET will get the new content
          self.lively4syncCache.set(url, null)

          // #TODO we could further store the PUT already locally?
          // PRO: offline support
          // CONTRA: not sure if the file reached the server....

          // and don't further handle it... so that it will be saved on the server
        } else if (method == "OPTIONS") {
           if (options && options.headers && options.headers["showversions"]) {
            return // don't cache versions request...
          }

          let match = self.lively4optionsCache.get(url)
          if (match) {
            // console.log("[boot] SYNC OPTIONS CACHED " + url)
            return {
              result: Promise.resolve(match.clone())
            }
          } else {
            // console.log("[boot] SYNC OPTIONS MISSED " + url)
          }
        } else {
          // do nothing
        }
      }
    },
    finished(request, options) {
      console.log("[boot] FINISHED fetch " + request.toString())
    }
  })
}

/*
 * MAIN BOOT FUNCTION: load Lively4 and get it going....
 */
async function intializeLively() {
  if(self.lively && self.lively4url) {
    return console.log("CANCEL BOOT Lively4, because it is already loaded")
  }
  // for finding the baseURL...
  var script = document.currentScript;
  var scriptURL = script.src;
  self.lively4url = scriptURL.replace("/src/client/boot.js","");

  // early feedback due to long loading time
  let livelyBooting = document.createElement('div');
  Object.assign(livelyBooting.style, {
    position: 'fixed',
    top: '50%',
    left: '50%',
    transform: 'translate(-50%, -50%)',

    zIndex: '10000',

    backgroundColor: 'white',
    border: 'black 1px solid',
    padding: '5px',
    boxShadow: '0px 0px 3px 0px rgba(40, 40, 40,0.66)'
  });
  livelyBooting.innerHTML = `
Booting:

`;
  document.body.appendChild(livelyBooting);

  self.lively4bootGroupedMessages = []
  var lastMessage

  var estimatedSteps = 10;
  var stepCounter = 1;

  function groupedMessage( message) {
    var part = stepCounter++
    var numberOfSteps = estimatedSteps
    lastMessage =  {part, message, begin: performance.now()}

    console.group(`${part}/${numberOfSteps}: ${message}.`);

    let messageDiv = document.body.querySelector('#lively-booting-message');
    if(messageDiv) {
      messageDiv.innerHTML = `<span>${part}</span>/<span>${numberOfSteps}</span>: <span>${message}.</span>`;
    }
  }

  function groupedMessageEnd() {
    console.groupEnd();
    if (lastMessage) {
      lastMessage.end = performance.now()
      self.lively4bootGroupedMessages.push(lastMessage)
    }
  }

  console.group("BOOT");

  // some performance logging
  self.lively4performance = {start: performance.now()}
  try {
    Object.defineProperty(window, 'lively4stamp', {
      get: function() {
        if (!self.lively4performance) return;
        var newLast = performance.now()
        var t = (newLast - (self.lively4performance.last || self.lively4performance.start)) / 1000
        self.lively4performance.last = newLast
        return (t.toFixed(3) + "s ")
      }
    })
  } catch(e) {
    console.error(e)
  }

  var loadContainer = script.getAttribute("data-container"); // some simple configuration

  console.log("lively4url: " + lively4url);

  // first things first
  instrumentFetch()
  installCachingFetch()

  groupedMessage('Preload Files');
    await preloadFileCaches()
    // we could wait, or not... if we load transpiled things... waiting is better
  groupedMessageEnd();

  groupedMessage('Setup SystemJS');
    await loadJavaScript("systemjs", lively4url + "/src/external/systemjs/system.src.js");
    await loadJavaScript("systemjs-config", lively4url + "/src/systemjs-config.js");
  groupedMessageEnd();

  try {
    groupedMessage('Initialize SystemJS');
      await System.import(lively4url + "/src/client/preload.js");
    groupedMessageEnd();

    groupedMessage('Setup fetch proxy');
      await System.import(lively4url + "/src/client/fetch.js").then(mod => {
        return mod.installProxyFetch()
      })
    groupedMessageEnd();

    groupedMessage('Wait on service worker (in load.js)');
      await (await System.import(lively4url + "/src/client/load-swx.js")).whenLoaded; // wait on service worker
    groupedMessageEnd();

    groupedMessage('Load Base System (lively.js)');
      await System.import("src/client/lively.js")

      // from load.js
      // lively.components.loadUnresolved(document.body, true, "load.js", true)

      // Customize.... #TODO where should it go?
      if (!self.__karma__ && navigator.userAgent.toLowerCase().indexOf('electron/') == -1) {
        self.onbeforeunload = function() {
          return 'Do you really want to leave this page?'; // gets overriden by Chrome native
        };
        self.onunload = function() {
          lively.onUnload && lively.onUnload()
        };
      }
    groupedMessageEnd();

    groupedMessage('Load Standard Library');
      await System.import("lang");
      await System.import("lang-ext");
    groupedMessageEnd();

    groupedMessage('Initialize Document (in lively.js)' );
      await lively.initializeDocument(document, self.lively4chrome, loadContainer);
    groupedMessageEnd();

    groupedMessage('Look for uninitialized instances of Web Compoments');
      await lively.components.loadUnresolved(document.body, true, "boot.js", true)
    groupedMessageEnd();

    // wait on all components to intialize their content.... e.g. the container loading a file
    var componentWithContent = Array.from(lively.allElements(document.body))
        .filter(ea => ea.livelyContentLoaded && ea.livelyContentLoaded.then)
    groupedMessage(`Wait on ${componentWithContent.length} components with content`);
      await Promise.all(componentWithContent.map(ea => ea.livelyContentLoaded))
    groupedMessageEnd();

    console.log("Finally loaded!");
    if (self.lively4bootGroupedMessages) {
      var str =  self.lively4bootGroupedMessages.map(ea => {
        return ea.part + " "  + Math.round(ea.end - ea.begin) + "ms "+ ea.message
      }).join("\n")
      console.log("BOOT", str)
    }

    if (self.lively4bootlogData) {
      System.import("src/client/bootlog.js").then(m => {
        m.default.current().addLogs(self.lively4bootlogData)
      }).then(() => console.log("saved bootlog"))
    }
    document.dispatchEvent(new Event("livelyloaded"));
  } finally {
    console.groupEnd(); // BOOT
    livelyBooting.remove();
  }
}

intializeLively()
<commit_msg>misc docu<commit_after>/*MD
# boot
loads lively in any page that inserts through a script tag
MD*/

/* eslint no-console: off */

/*
 * HELPER
 */

async function loadJavaScript(name, src, force) {
  var code = await fetch(src).then(r => r.text())
  eval(code)
}

self.lively4currentbootid = "" + new Date()
self.lively4bootlogData = []
self.lively4bootlog = function add(url, date=Date.now(), mode="load", time=0, parentURL) {
  self.lively4bootlogData.push({
    url, date, mode, time, parentURL, bootid: self.lively4currentbootid
  })
}

if (!(localStorage["logLivelyBoot"] == "true")) {
  self.lively4bootlog = function() {
    // do nothing
  }
}

/*
 * CACHES
 */
self.lively4transpilationCache = {
  update(cacheKey, cache) {
    this.cache.set(cacheKey, cache)
    var cacheJSO = {
      url: cacheKey,
      input: cache.input,
      output: cache.output,
      map: JSON.stringify(cache.map),
    }

    if (!cacheKey.match(/^workspace/) && !self.__karma__) {
      console.log("[babel] update transpilation cache " + cacheKey) // from client to server :-) #Security anybody?
      var transpileCacheURL = lively4url + "/.transpiled/" + cacheKey.replace(lively4url + "/","").replace(/\//g,"_") // flatten path
      fetch(transpileCacheURL, {
        method: "PUT",
        headers: {
          nocommit: true, // .transpiled is in gitignore... and the server cannot handle it automaitcally
        },
        body: cacheJSO.output
      })

      fetch(transpileCacheURL + ".map.json", {
        method: "PUT",
        headers: {
          nocommit: true
        },
        body: cacheJSO.map
      })
    }
  },
  cache: new Map()
}

self.lively4syncCache = new Map()
self.lively4optionsCache = new Map()
self.lively4fetchLog = []

async function invalidateFileCaches()  {
  var url = lively4url + "/"

  if (self.lively && lively.fileIndexWorker) {
    lively.fileIndexWorker.postMessage({message: "updateDirectory", url})
  }

  const FilesCaches = await System.import("src/client/files-caches.js")
  console.log("[boot] invalidateFileCaches:\n" + FilesCaches.invalidateTranspiledFiles())

}
self.lively4invalidateFileCaches = invalidateFileCaches

async function preloadFileCaches() {
  await loadJavaScript("JSZip", lively4url + "/src/external/jszip.js" )

  var start = performance.now()
  var preloadurl = lively4url + "/.lively4bundle.zip" + "?" + Date.now()
  var resp = await fetch(preloadurl)
  if (resp.status != "200") {
    console.warn("NO preload cache found in", preloadurl)
    return
  }
  var contents = await resp.blob()

  var archive = await self.JSZip.loadAsync(contents)
  console.log("[boot] preloadFileCache fetched contents in  " + Math.round(performance.now() - start) + "ms")

  start = performance.now()
  for(let ea of Object.keys(archive.files)) {
    if (ea.match(/\.transpiled\//) || ea.match(/\.options\//)) continue;

    let file = archive.file(ea);
    if (file) {
      let modified = file.date.toISOString().replace(/T/, " ").replace(/\..*/, ""),
        url = lively4url + "/" + ea,
        content = await file.async("string"),
        mimeType = " text/plain"
      if (url.match(/\.js$/)) mimeType = "application/javascript"
      if (url.match(/\.css$/)) mimeType = "text/css"
      var response = new Response(content, {
        headers: {
          "content-type": mimeType,
          modified: modified
        }
      })
      self.lively4syncCache.set(url, response)

      let optionsPath = ".options/" + ea.replace(/\//g,"_"),
        optionsFile = archive.file(optionsPath)
      if (optionsFile) {
        let optionsContent = await optionsFile.async("string")
        self.lively4optionsCache.set(url, new Response(optionsContent, {
          headers: {
            "content-type": "application/json"
          }
        }))
      }

      if (ea.match(/.js$/)) {
        let transpiledPath = ".transpiled/" + ea.replace(/\//g,"_"),
            transpiledFile = archive.file(transpiledPath),
            mapFile = archive.file(transpiledPath + ".map.json");

        if (transpiledFile) {
          // console.log("[boot] preloadFileCache initialize transpiled javascript: " + ea)
          try {
            var transpiledCode = await transpiledFile.async("string")
            if (mapFile) {
              var map = JSON.parse(await mapFile.async("string"))
            }
            self.lively4transpilationCache.cache.set(url, {
              input: content,
              output: transpiledCode,
              map: map,
              modified: modified
            })
          } catch(e) {
            console.error("[boot] error in loading transpiled code: " + ea, e)
          }
        }
      }
    }
  }
  console.log("[boot] preloadFileCache updated caches in  " + Math.round(performance.now() - start) + "ms")
}

self.lively4fetchHandlers = []
function instrumentFetch() {
  if (!self.originalFetch) self.originalFetch = self.fetch
  self.fetch = async function(request, options, ...rest) {
    var result = await new Promise(resolve => {
      try {

        if (self.lively4fetchHandlers) {
          // go through our list of handlers... the first one who handles it wins
          for(var handler of self.lively4fetchHandlers) {
            var handled = handler.handle && handler.handle(request, options)
            if (handled) return resolve(handled.result);
          }
        }
      } catch(e) {
        console.error(`FETCH ERROR during rques: ${request} falling back because of`, e)
      }
      return resolve(self.originalFetch.apply(self, [request, options, ...rest]))
    })
    if (self.lively4fetchHandlers) {
      // anybody insterested when it finished
      for(var handler of self.lively4fetchHandlers) {
        handler.finsihed && await handler.finsihed(request, options)
      }
    }
    return result
  }
}

function installCachingFetch() {
  self.lively4fetchHandlers.push({
    handle(request, options) {
      var url = (request.url || request).toString()
      var method = "GET"
      if (options && options.method) method = options.method;

      if (url.match(lively4url)) {
        self.lively4fetchLog.push({
          time: performance.now(),
          method: method,
          url: url
        })
        if (!self.lively4syncCache) return
        if (method == "GET") {
          if (options && options.headers && (options.headers["fileversion"] || options.headers["forediting"])) {
            return // don't cache versions request...
          }

          let match = self.lively4syncCache.get(url)
          if (match) {
            // console.log("[boot] SYNC CACHED " + url)
            return {
              result: Promise.resolve(match.clone())
            }
          } else {
            // console.log("[boot] SYNC MISSED " + url)
          }
        } else if (method == "PUT") {
          // clear cache for PUT
          // so next GET will get the new content
          self.lively4syncCache.set(url, null)

          // #TODO we could further store the PUT already locally?
          // PRO: offline support
          // CONTRA: not sure if the file reached the server....

          // and don't further handle it... so that it will be saved on the server
        } else if (method == "OPTIONS") {
           if (options && options.headers && options.headers["showversions"]) {
            return // don't cache versions request...
          }

          let match = self.lively4optionsCache.get(url)
          if (match) {
            // console.log("[boot] SYNC OPTIONS CACHED " + url)
            return {
              result: Promise.resolve(match.clone())
            }
          } else {
            // console.log("[boot] SYNC OPTIONS MISSED " + url)
          }
        } else {
          // do nothing
        }
      }
    },
    finished(request, options) {
      console.log("[boot] FINISHED fetch " + request.toString())
    }
  })
}

/*
 * MAIN BOOT FUNCTION: load Lively4 and get it going....
 */
async function intializeLively() {
  if(self.lively && self.lively4url) {
    return console.log("CANCEL BOOT Lively4, because it is already loaded")
  }
  // for finding the baseURL...
  var script = document.currentScript;
  var scriptURL = script.src;
  self.lively4url = scriptURL.replace("/src/client/boot.js","");

  // early feedback due to long loading time
  let livelyBooting = document.createElement('div');
  Object.assign(livelyBooting.style, {
    position: 'fixed',
    top: '50%',
    left: '50%',
    transform: 'translate(-50%, -50%)',

    zIndex: '10000',

    backgroundColor: 'white',
    border: 'black 1px solid',
    padding: '5px',
    boxShadow: '0px 0px 3px 0px rgba(40, 40, 40,0.66)'
  });
  livelyBooting.innerHTML = `
Booting:

`;
  document.body.appendChild(livelyBooting);

  self.lively4bootGroupedMessages = []
  var lastMessage

  var estimatedSteps = 10;
  var stepCounter = 1;

  function groupedMessage( message) {
    var part = stepCounter++
    var numberOfSteps = estimatedSteps
    lastMessage =  {part, message, begin: performance.now()}

    console.group(`${part}/${numberOfSteps}: ${message}.`);

    let messageDiv = document.body.querySelector('#lively-booting-message');
    if(messageDiv) {
      messageDiv.innerHTML = `<span>${part}</span>/<span>${numberOfSteps}</span>: <span>${message}.</span>`;
    }
  }

  function groupedMessageEnd() {
    console.groupEnd();
    if (lastMessage) {
      lastMessage.end = performance.now()
      self.lively4bootGroupedMessages.push(lastMessage)
    }
  }

  console.group("BOOT");

  // some performance logging
  self.lively4performance = {start: performance.now()}
  try {
    Object.defineProperty(window, 'lively4stamp', {
      get: function() {
        if (!self.lively4performance) return;
        var newLast = performance.now()
        var t = (newLast - (self.lively4performance.last || self.lively4performance.start)) / 1000
        self.lively4performance.last = newLast
        return (t.toFixed(3) + "s ")
      }
    })
  } catch(e) {
    console.error(e)
  }

  var loadContainer = script.getAttribute("data-container"); // some simple configuration

  console.log("lively4url: " + lively4url);

  // first things first
  instrumentFetch()
  installCachingFetch()

  groupedMessage('Preload Files');
    await preloadFileCaches()
    // we could wait, or not... if we load transpiled things... waiting is better
  groupedMessageEnd();

  groupedMessage('Setup SystemJS');
    await loadJavaScript("systemjs", lively4url + "/src/external/systemjs/system.src.js");
    await loadJavaScript("systemjs-config", lively4url + "/src/systemjs-config.js");
  groupedMessageEnd();

  try {
    groupedMessage('Initialize SystemJS');
      await System.import(lively4url + "/src/client/preload.js");
    groupedMessageEnd();

    groupedMessage('Setup fetch proxy');
      await System.import(lively4url + "/src/client/fetch.js").then(mod => {
        return mod.installProxyFetch()
      })
    groupedMessageEnd();

    groupedMessage('Wait on service worker (in load.js)');
      await (await System.import(lively4url + "/src/client/load-swx.js")).whenLoaded; // wait on service worker
    groupedMessageEnd();

    groupedMessage('Load Base System (lively.js)');
      await System.import("src/client/lively.js")

      // from load.js
      // lively.components.loadUnresolved(document.body, true, "load.js", true)

      // Customize.... #TODO where should it go?
      if (!self.__karma__ && navigator.userAgent.toLowerCase().indexOf('electron/') == -1) {
        self.onbeforeunload = function() {
          return 'Do you really want to leave this page?'; // gets overriden by Chrome native
        };
        self.onunload = function() {
          lively.onUnload && lively.onUnload()
        };
      }
    groupedMessageEnd();

    groupedMessage('Load Standard Library');
      await System.import("lang");
      await System.import("lang-ext");
    groupedMessageEnd();

    groupedMessage('Initialize Document (in lively.js)' );
      await lively.initializeDocument(document, self.lively4chrome, loadContainer);
    groupedMessageEnd();

    groupedMessage('Look for uninitialized instances of Web Compoments');
      await lively.components.loadUnresolved(document.body, true, "boot.js", true)
    groupedMessageEnd();

    // wait on all components to intialize their content.... e.g. the container loading a file
    var componentWithContent = Array.from(lively.allElements(document.body))
        .filter(ea => ea.livelyContentLoaded && ea.livelyContentLoaded.then)
    groupedMessage(`Wait on ${componentWithContent.length} components with content`);
      await Promise.all(componentWithContent.map(ea => ea.livelyContentLoaded))
    groupedMessageEnd();

    console.log("Finally loaded!");
    if (self.lively4bootGroupedMessages) {
      var str =  self.lively4bootGroupedMessages.map(ea => {
        return ea.part + " "  + Math.round(ea.end - ea.begin) + "ms "+ ea.message
      }).join("\n")
      console.log("BOOT", str)
    }

    if (self.lively4bootlogData) {
      System.import("src/client/bootlog.js").then(m => {
        m.default.current().addLogs(self.lively4bootlogData)
      }).then(() => console.log("saved bootlog"))
    }
    document.dispatchEvent(new Event("livelyloaded"));
  } finally {
    console.groupEnd(); // BOOT
    livelyBooting.remove();
  }
}

intializeLively()
<|endoftext|>


<commit_before>"use strict";
/**
 * pjax.js
 * Copyright(c) 2018 Aaron Hedges <aaron@dashron.com>
 * MIT Licensed
 *
 * This file exposes a PJAX class to help with client side rendering
 */

 /**
  * This is a helper class to make PJAX easier. PJAX is a clean way of improving the performance of webpages
  * by progressively turning standard HTML links into AJAX requests for portions of a web page.
  *
  * @todo Form support
  * @todo tests
  */
module.exports = class RoadsPjax {
	/**
	 * Creates a new RoadsPjax instance. The road provided to this constructor will be the backbone of your PJAX requests.
	 *
	 * @param {Road} road - The road that will turn your pjax requests into HTML
	 * @param {DomElement} container_element - The element that will be filled with your roads output
	 * @param {Window} window - The pages window object to help set page title and other items
	 */
	constructor(road, container_element, window) {
		this._road = road;
		this._page_title = null;
		this._window = window;
		this._container_element = container_element;
	}

	/**
	 * Adds middleware to the assigned road whcih will adds setTitle to the request context. This allows you to easily update the page title.
	 *
	 * @returns {RoadsPjax} this, useful for chaining
	 */
	addTitleMiddleware () {
		var _self = this;

		this._road.use(function (method, url, body, headers, next) {
			this.setTitle = function (title) {
				_self._page_title = title;
			};

			return next();
		});

		return this;
	}

	/**
	 * Assigns the cookie middlware to the road to properly handle cookies
	 *
	 * @param {Document} document - The pages document object to properly parse and set cookies
	 */
	addCookieMiddleware (document) {
		var cookie = require('cookie');

		this._road.use(function (method, url, body, headers, next) {
			if (document.cookie) {
				this.cookies = cookie.parse(document.cookie);
			} else {
				this.cookies = {};
			}

			return next();
		});
	}

	/**
	 * Hooks up the PJAX functionality to the information provided via the constructor.
	 */
	register () {
		// Handle navigation changes besides pushState. TODO: don' blow out existing onpopstate's
		// TODO: If a request is in process during the popstate, we should kill it and use the new url
		this._window.onpopstate = (event) => {
			if (event.state.pjax) {
				// if the popped state was generated  via pjax, execute the appropriate route
				this._road.request('GET', this._window.location.pathname)
				.then((response) => {
					this.render(response);
					this._window.document.title = this._page_title;
				})
				.catch((err) => {
					console.log('road err');
					console.log(err);
				});
			} else {
				// reload the page if the popped state wasn't generated via an pjax call
				this._window.location.pathname = this._window.location.pathname;
			}
		};

		// Trigger the pjax on any click event for roads links
		this._container_element.addEventListener('click', this._roadsLinkEvent.bind(this));

		// initial state
		this._window.history.replaceState({
			page_title: this._window.document.title,
			pjax: false
		}, this._page_title);
	}

	/**
	 * The response from the roads request
	 *
	 * @param {Response} response_object
	 */
	render (response_object) {
		this._container_element.innerHTML = response_object.body;
	}

	/**
	 * Handles the link click event. If the link has the right data attribute we will execute and render the appropriate controller
	 *
	 * @param  {Object} event             [description]
	 */
	_roadsLinkEvent (event) {
		if (event.target.tagName === 'A' && event.target.dataset['roads-pjax'] === "link" && !event.ctrlKey) {
			event.preventDefault();

			this._road.request('GET', event.target.href)
			.then((response) => {
				this._window.history.pushState({
					page_title: this._page_title,
					pjax: true
				}, this._page_title, event.target.href);
				this.render(response);
				this._window.document.title = this._page_title;
			})
			.catch((err) => {
				console.log('road err');
				console.log(err);
				return;
			});
		}
	}
};<commit_msg>fix dataset for pjax<commit_after>"use strict";
/**
 * pjax.js
 * Copyright(c) 2018 Aaron Hedges <aaron@dashron.com>
 * MIT Licensed
 *
 * This file exposes a PJAX class to help with client side rendering
 */

 /**
  * This is a helper class to make PJAX easier. PJAX is a clean way of improving the performance of webpages
  * by progressively turning standard HTML links into AJAX requests for portions of a web page.
  *
  * @todo Form support
  * @todo tests
  */
module.exports = class RoadsPjax {
	/**
	 * Creates a new RoadsPjax instance. The road provided to this constructor will be the backbone of your PJAX requests.
	 *
	 * @param {Road} road - The road that will turn your pjax requests into HTML
	 * @param {DomElement} container_element - The element that will be filled with your roads output
	 * @param {Window} window - The pages window object to help set page title and other items
	 */
	constructor(road, container_element, window) {
		this._road = road;
		this._page_title = null;
		this._window = window;
		this._container_element = container_element;
	}

	/**
	 * Adds middleware to the assigned road whcih will adds setTitle to the request context. This allows you to easily update the page title.
	 *
	 * @returns {RoadsPjax} this, useful for chaining
	 */
	addTitleMiddleware () {
		var _self = this;

		this._road.use(function (method, url, body, headers, next) {
			this.setTitle = function (title) {
				_self._page_title = title;
			};

			return next();
		});

		return this;
	}

	/**
	 * Assigns the cookie middlware to the road to properly handle cookies
	 *
	 * @param {Document} document - The pages document object to properly parse and set cookies
	 */
	addCookieMiddleware (document) {
		var cookie = require('cookie');

		this._road.use(function (method, url, body, headers, next) {
			if (document.cookie) {
				this.cookies = cookie.parse(document.cookie);
			} else {
				this.cookies = {};
			}

			return next();
		});
	}

	/**
	 * Hooks up the PJAX functionality to the information provided via the constructor.
	 */
	register () {
		// Handle navigation changes besides pushState. TODO: don' blow out existing onpopstate's
		// TODO: If a request is in process during the popstate, we should kill it and use the new url
		this._window.onpopstate = (event) => {
			if (event.state.pjax) {
				// if the popped state was generated  via pjax, execute the appropriate route
				this._road.request('GET', this._window.location.pathname)
				.then((response) => {
					this.render(response);
					this._window.document.title = this._page_title;
				})
				.catch((err) => {
					console.log('road err');
					console.log(err);
				});
			} else {
				// reload the page if the popped state wasn't generated via an pjax call
				this._window.location.pathname = this._window.location.pathname;
			}
		};

		// Trigger the pjax on any click event for roads links
		this._container_element.addEventListener('click', this._roadsLinkEvent.bind(this));

		// initial state
		this._window.history.replaceState({
			page_title: this._window.document.title,
			pjax: false
		}, this._page_title);
	}

	/**
	 * The response from the roads request
	 *
	 * @param {Response} response_object
	 */
	render (response_object) {
		this._container_element.innerHTML = response_object.body;
	}

	/**
	 * Handles the link click event. If the link has the right data attribute we will execute and render the appropriate controller
	 *
	 * @param  {Object} event             [description]
	 */
	_roadsLinkEvent (event) {
		if (event.target.tagName === 'A' && event.target.dataset['roadsPjax'] === "link" && !event.ctrlKey) {
			event.preventDefault();

			this._road.request('GET', event.target.href)
			.then((response) => {
				this._window.history.pushState({
					page_title: this._page_title,
					pjax: true
				}, this._page_title, event.target.href);
				this.render(response);
				this._window.document.title = this._page_title;
			})
			.catch((err) => {
				console.log('road err');
				console.log(err);
				return;
			});
		}
	}
};<|endoftext|>


<commit_before>/**
 * Copyright (c) 2014, Arthur Schiwon <blizzz@owncloud.com>
 * This file is licensed under the Affero General Public License version 3 or later.
 * See the COPYING-README file.
 */

/**
 * @brief takes care of deleting things represented by an ID
 * @param String endpoint: the corresponding ajax PHP script. Currently limited
 * to settings - ajax path.
 * @param String paramID: the by the script expected parameter name holding the
 * ID of the object to delete
 * @param Function markCallback: the function to be called after successfully
 * marking the object for deletion.
 * @param Function removeCallback: the function to be called after successful
 * delete. The id of the object will be passed as argument. Insuccessful
 * operations will display an error using OC.dialogs, no callback is fired.
 */
function DeleteHandler(endpoint, paramID, markCallback, removeCallback) {
	this.oidToDelete = false;
	this.canceled = false;

	this.ajaxEndpoint = endpoint;
	this.ajaxParamID = paramID;

	this.markCallback = markCallback;
	this.removeCallback = removeCallback;
	this.undoCallback = false;

	this.notifier = false;
	this.notificationDataID = false;
	this.notificationMessage = false;
	this.notificationPlaceholder = '%oid';
}

/**
 * @brief enabled the notification system. Required for undo UI.
 * @param Object notifier: Usually OC.Notification
 * @param String dataID: an identifier for the notificatior, e.g. 'deleteuser'
 * @param String message: the message that should be shown upon delete. %oid
 * will be replaced with the affected id of the item to be deleted
 * @param Function undoCb: called after "undo" was clicked so consument can
 * update the web interface
 */
DeleteHandler.prototype.setNotification = function(notifier, dataID, message, undoCb) {
	this.notifier = notifier;
	this.notificationDataID = dataID;
	this.notificationMessage = message;
	this.undoCallback = undoCb;

	dh = this;

	$('#notification').on('click', '.undo', function () {
		if ($('#notification').data(dh.notificationDataID)) {
			oid = dh.oidToDelete;
			UserDeleteHandler.cancel();
			if(typeof dh.undoCallback !== 'undefined') {
				dh.undoCallback(oid);
			}
		}
		dh.notifier.hide();
	});
}

/**
 * @brief shows the Undo Notification (if configured)
 */
DeleteHandler.prototype.showNotification = function() {
	if(this.notifier !== false) {
		if(!this.notifier.isHidden()) {
			this.hideNotification();
		}
		$('#notification').data(this.notificationDataID, true);
		msg = this.notificationMessage.replace(this.notificationPlaceholder,
											this.oidToDelete);
		console.log('NOTISHOW ' + msg);
		this.notifier.showHtml(msg);
	}
}

/**
 * @brief hides the Undo Notification
 */
DeleteHandler.prototype.hideNotification = function() {
	if(this.notifier !== false) {
		$('#notification').removeData(this.notificationDataID);
		this.notifier.hide();
	}
}

/**
 * @brief initilizes the delete operation for a given object id
 * @param String oid: the object id
 */
DeleteHandler.prototype.mark = function(oid) {
	if(this.oidToDelete !== false) {
		this.delete();
	}
	this.oidToDelete = oid;
	this.canceled = false;
	this.markCallback(oid);
	this.showNotification();
}

/**
 * @brief cancels a delete operation
 */
DeleteHandler.prototype.cancel = function(oid) {
	this.canceled = true;
	this.oidToDelete = false;
}

/**
 * @brief executes a delete operation. Requires that the operation has been
 * initilized by mark(). On error, it will show a message via
 * OC.dialogs.alert. On success, a callback is fired so that the client can
 * update the web interface accordingly.
 */
DeleteHandler.prototype.delete = function() {
	if(this.canceled || this.oidToDelete === false) {
		return false;
	}

	dh = this;
	console.log($('#notification').data(this.notificationDataID));
	if($('#notification').data(this.notificationDataID) === true) {
		dh.hideNotification();
		console.log('HIDDEN NOTI');
	}

	payload = {};
	payload[dh['ajaxParamID']] = dh.oidToDelete;
	$.ajax({
		type: 'POST',
		url: OC.filePath('settings', 'ajax', dh.ajaxEndpoint),
		async: false,
		data: payload,
		success: function (result) {
			if (result.status === 'success') {
				// Remove undo option, & remove user from table

				//TODO: following line
				dh.removeCallback(dh.oidToDelete);
				dh.canceled = true;
				console.log(dh.ajaxEndpoint);
			} else {
				OC.dialogs.alert(result.data.message, t('settings', 'Unable to delete ' + escapeHTML(dh.oidToDelete)));
				dh.undoCallback(dh.oidToDelete);
			}
		}
	});
}<commit_msg>js cleanup<commit_after>/**
 * Copyright (c) 2014, Arthur Schiwon <blizzz@owncloud.com>
 * This file is licensed under the Affero General Public License version 3 or later.
 * See the COPYING-README file.
 */

/**
 * @brief takes care of deleting things represented by an ID
 * @param String endpoint: the corresponding ajax PHP script. Currently limited
 * to settings - ajax path.
 * @param String paramID: the by the script expected parameter name holding the
 * ID of the object to delete
 * @param Function markCallback: the function to be called after successfully
 * marking the object for deletion.
 * @param Function removeCallback: the function to be called after successful
 * delete. The id of the object will be passed as argument. Unsuccessful
 * operations will display an error using OC.dialogs, no callback is fired.
 */
function DeleteHandler(endpoint, paramID, markCallback, removeCallback) {
	this.oidToDelete = false;
	this.canceled = false;

	this.ajaxEndpoint = endpoint;
	this.ajaxParamID = paramID;

	this.markCallback = markCallback;
	this.removeCallback = removeCallback;
	this.undoCallback = false;

	this.notifier = false;
	this.notificationDataID = false;
	this.notificationMessage = false;
	this.notificationPlaceholder = '%oid';
}

/**
 * @brief enabled the notification system. Required for undo UI.
 * @param Object notifier: Usually OC.Notification
 * @param String dataID: an identifier for the notifier, e.g. 'deleteuser'
 * @param String message: the message that should be shown upon delete. %oid
 * will be replaced with the affected id of the item to be deleted
 * @param Function undoCb: called after "undo" was clicked so the consumer can
 * update the web interface
 */
DeleteHandler.prototype.setNotification = function(notifier, dataID, message, undoCallback) {
	this.notifier = notifier;
	this.notificationDataID = dataID;
	this.notificationMessage = message;
	this.undoCallback = undoCallback;

	var dh = this;

	$('#notification').on('click', '.undo', function () {
		if ($('#notification').data(dh.notificationDataID)) {
			var oid = dh.oidToDelete;
			UserDeleteHandler.cancel();
			if(typeof dh.undoCallback !== 'undefined') {
				dh.undoCallback(oid);
			}
		}
		dh.notifier.hide();
	});
};

/**
 * @brief shows the Undo Notification (if configured)
 */
DeleteHandler.prototype.showNotification = function() {
	if(this.notifier !== false) {
		if(!this.notifier.isHidden()) {
			this.hideNotification();
		}
		$('#notification').data(this.notificationDataID, true);
		var msg = this.notificationMessage.replace(this.notificationPlaceholder,
											this.oidToDelete);
		console.log('NOTISHOW ' + msg);
		this.notifier.showHtml(msg);
	}
};

/**
 * @brief hides the Undo Notification
 */
DeleteHandler.prototype.hideNotification = function() {
	if(this.notifier !== false) {
		$('#notification').removeData(this.notificationDataID);
		this.notifier.hide();
	}
};

/**
 * @brief initializes the delete operation for a given object id
 * @param String oid: the object id
 */
DeleteHandler.prototype.mark = function(oid) {
	if(this.oidToDelete !== false) {
		this.delete();
	}
	this.oidToDelete = oid;
	this.canceled = false;
	this.markCallback(oid);
	this.showNotification();
};

/**
 * @brief cancels a delete operation
 */
DeleteHandler.prototype.cancel = function() {
	this.canceled = true;
	this.oidToDelete = false;
};

/**
 * @brief executes a delete operation. Requires that the operation has been
 * initialized by mark(). On error, it will show a message via
 * OC.dialogs.alert. On success, a callback is fired so that the client can
 * update the web interface accordingly.
 */
DeleteHandler.prototype.delete = function() {
	if(this.canceled || this.oidToDelete === false) {
		return false;
	}

	var dh = this;
	console.log($('#notification').data(this.notificationDataID));
	if($('#notification').data(this.notificationDataID) === true) {
		dh.hideNotification();
		console.log('HIDDEN NOTI');
	}

	var payload = {};
	payload[dh.ajaxParamID] = dh.oidToDelete;
	$.ajax({
		type: 'POST',
		url: OC.filePath('settings', 'ajax', dh.ajaxEndpoint),
		async: false,
		data: payload,
		success: function (result) {
			if (result.status === 'success') {
				// Remove undo option, & remove user from table

				//TODO: following line
				dh.removeCallback(dh.oidToDelete);
				dh.canceled = true;
				console.log(dh.ajaxEndpoint);
			} else {
				OC.dialogs.alert(result.data.message, t('settings', 'Unable to delete ' + escapeHTML(dh.oidToDelete)));
				dh.undoCallback(dh.oidToDelete);
			}
		}
	});
};
<|endoftext|>


<commit_before>(function (env) {
    "use strict";
    env.ddg_spice_vrc_teams = function(team_data) {
        if (!team_data || team_data.status !== 1 || team_data.size === 0 || team_data.result[0].is_registered === "0") {
            return Spice.failed('vrc_teams');
        }

        //infobox data for statistics
        function infobox(ranking, skills){
            var l = []
            if (ranking.status === 1 && ranking.size > 0){
                l.push({heading: 'Qualifying Matches'});
                var total_wins = 0,
                    total_losses = 0,
                    total_ties = 0,
                    total_ccwm = 0.0,
                    max_score = 0,
                    latest = ranking.result[0];

                $.each(ranking.result, function(ind, i){
                    total_wins += parseInt(i.wins);
                    total_losses += parseInt(i.losses);
                    total_ties += parseInt(i.ties);
                    total_ccwm += parseFloat(i.ccwm);
                    max_score = Math.max(max_score, parseInt(i.max_score));
                });

                var total_wlt = total_wins + '-' + total_losses + '-' + total_ties;
                var latest_wlt = latest.wins + '-' + latest.losses + '-' + latest.ties;
                l.push({label: 'Total W-L-T', value: total_wlt});
                l.push({label: 'Latest W-L-T', value: latest_wlt});
                l.push({label: 'Greatest Match Score', value: max_score});

                var average_ccwm = total_ccwm / ranking.size;
                l.push({label: 'Average CCWM', value: average_ccwm.toFixed(2)});
                l.push({label: 'Latest CCWM', value: parseFloat(latest.ccwm).toFixed(2)});
            }
            if (skills.status === 1 && skills.size > 0){
                l.push({heading: 'Best Skills'});
                var best_robot = 0,
                    best_programming = 0;

                $.each(skills.result, function(ind, i){
                    if (i.type === "0" && parseInt(i.score) > best_robot){
                        best_robot = parseInt(i.score);
                    }
                    if (i.type === "1" && parseInt(i.score) > best_programming){
                        best_programming = parseInt(i.score);
                    }
                });

                l.push({label: 'Robot', value: best_robot});
                l.push({label: 'Programming', value: best_programming});
            }

            return l.length ? l : undefined;
        }

        //description data for team location and grade
        function description(team){
            return team.grade + ' team from ' + team.city + ', ' + team.region + ', ' + team.country;
        }

        var team = team_data.result[0], ranking_data, skills_data;

        Spice.add({
            id: "vrc_teams",
            name: "VRC Team",
            data: team,
            meta: {
                sourceName: "VexDB.io",
                sourceUrl: 'http://vexdb.io/teams/view/' + team.number
            },
            normalize: function(item) {
                return {
                    title: item.team_name,
                    subtitle: item.program + ' Team ' + item.number + ' — ' + item.organisation,
                    description: description(item)
                    //infoboxData: infobox(ranking_data, skills_data)
                };
            },
            templates: {
                group: 'info',
                options: {
                    moreAt: true
                }
            },
            onItemShown: function(item){
                //the api splits the data up into multiple endpoints, fire them simultaneously
                $.when(
                    $.getJSON('/js/spice/vrc/rankings/' + team.number, function(res){
                        ranking_data = res;
                    }),
                    $.getJSON('/js/spice/vrc/skills/' + team.number, function(res){
                        skills_data = res;
                    })
                ).then(function(){
                    item.set({infoboxData: infobox(ranking_data, skills_data)});
                });
            }
        });
    };
}(this));
<commit_msg>VRCTeams: [WIP] forgot meta.rerender, still doesn't work :/<commit_after>(function (env) {
    "use strict";
    env.ddg_spice_vrc_teams = function(team_data) {
        if (!team_data || team_data.status !== 1 || team_data.size === 0 || team_data.result[0].is_registered === "0") {
            return Spice.failed('vrc_teams');
        }

        //infobox data for statistics
        function infobox(ranking, skills){
            var l = []
            if (ranking.status === 1 && ranking.size > 0){
                l.push({heading: 'Qualifying Matches'});
                var total_wins = 0,
                    total_losses = 0,
                    total_ties = 0,
                    total_ccwm = 0.0,
                    max_score = 0,
                    latest = ranking.result[0];

                $.each(ranking.result, function(ind, i){
                    total_wins += parseInt(i.wins);
                    total_losses += parseInt(i.losses);
                    total_ties += parseInt(i.ties);
                    total_ccwm += parseFloat(i.ccwm);
                    max_score = Math.max(max_score, parseInt(i.max_score));
                });

                var total_wlt = total_wins + '-' + total_losses + '-' + total_ties;
                var latest_wlt = latest.wins + '-' + latest.losses + '-' + latest.ties;
                l.push({label: 'Total W-L-T', value: total_wlt});
                l.push({label: 'Latest W-L-T', value: latest_wlt});
                l.push({label: 'Greatest Match Score', value: max_score});

                var average_ccwm = total_ccwm / ranking.size;
                l.push({label: 'Average CCWM', value: average_ccwm.toFixed(2)});
                l.push({label: 'Latest CCWM', value: parseFloat(latest.ccwm).toFixed(2)});
            }
            if (skills.status === 1 && skills.size > 0){
                l.push({heading: 'Best Skills'});
                var best_robot = 0,
                    best_programming = 0;

                $.each(skills.result, function(ind, i){
                    if (i.type === "0" && parseInt(i.score) > best_robot){
                        best_robot = parseInt(i.score);
                    }
                    if (i.type === "1" && parseInt(i.score) > best_programming){
                        best_programming = parseInt(i.score);
                    }
                });

                l.push({label: 'Robot', value: best_robot});
                l.push({label: 'Programming', value: best_programming});
            }

            console.log(l);
            return l.length ? l : undefined;
        }

        //description data for team location and grade
        function description(team){
            return team.grade + ' team from ' + team.city + ', ' + team.region + ', ' + team.country;
        }

        var team = team_data.result[0], ranking_data, skills_data;

        Spice.add({
            id: "vrc_teams",
            name: "VRC Team",
            data: team,
            meta: {
                sourceName: "VexDB.io",
                sourceUrl: 'http://vexdb.io/teams/view/' + team.number,
                rerender: ['infoboxData']
            },
            normalize: function(item) {
                return {
                    title: item.team_name,
                    subtitle: item.program + ' Team ' + item.number + ' — ' + item.organisation,
                    description: description(item)
                    //infoboxData: infobox(ranking_data, skills_data)
                };
            },
            templates: {
                group: 'info',
                options: {
                    moreAt: true
                }
            },
            onItemShown: function(item){
                //the api splits the data up into multiple endpoints, fire them simultaneously
                $.when(
                    $.getJSON('/js/spice/vrc/rankings/' + team.number, function(res){
                        ranking_data = res;
                    }),
                    $.getJSON('/js/spice/vrc/skills/' + team.number, function(res){
                        skills_data = res;
                    })
                ).then(function(){
                    item.set({infoboxData: infobox(ranking_data, skills_data)});
                });
            }
        });
    };
}(this));
<|endoftext|>