<commit_before>import { remove as removeDiacritics } from 'diacritics';
import { fixRTLTextForSvg, rtlRegex } from './svg_paths_rtl_fix';

import { t, textDirection } from './locale';
import { utilArrayUnion } from './array';
import { utilDetect } from './detect';

export function utilTagText(entity) {
    var obj = (entity && entity.tags) || {};
    return Object.keys(obj)
        .map(function(k) { return k + '=' + obj[k]; })
        .join(', ');
}

export function utilTagDiff(oldTags, newTags) {
    var tagDiff = [];
    var keys = utilArrayUnion(Object.keys(oldTags), Object.keys(newTags)).sort();
    keys.forEach(function(k) {
        var oldVal = oldTags[k];
        var newVal = newTags[k];

        if ((oldVal || oldVal === '') && (newVal === undefined || newVal !== oldVal)) {
            tagDiff.push({
                type: '-',
                key: k,
                oldVal: oldVal,
                newVal: newVal,
                display: '- ' + k + '=' + oldVal
            });
        }
        if ((newVal || newVal === '') && (oldVal === undefined || newVal !== oldVal)) {
            tagDiff.push({
                type: '+',
                key: k,
                oldVal: oldVal,
                newVal: newVal,
                display: '+ ' + k + '=' + newVal
            });
        }
    });
    return tagDiff;
}

export function utilEntitySelector(ids) {
    return ids.length ? '.' + ids.join(',.') : 'nothing';
}

// returns an selector to select entity ids for:
//  - entityIDs passed in
//  - shallow descendant entityIDs for any of those entities that are relations
export function utilEntityOrMemberSelector(ids, graph) {
    var seen = new Set(ids);
    ids.forEach(collectShallowDescendants);
    return utilEntitySelector(Array.from(seen));

    function collectShallowDescendants(id) {
        var entity = graph.hasEntity(id);
        if (!entity || entity.type !== 'relation') return;

        entity.members
            .map(function(member) { return member.id; })
            .forEach(function(id) { seen.add(id); });
    }
}

// returns an selector to select entity ids for:
//  - entityIDs passed in
//  - deep descendant entityIDs for any of those entities that are relations
export function utilEntityOrDeepMemberSelector(ids, graph) {
    return utilEntitySelector(utilEntityAndDeepMemberIDs(ids, graph));
}

// returns an selector to select entity ids for:
//  - entityIDs passed in
//  - deep descendant entityIDs for any of those entities that are relations
export function utilEntityAndDeepMemberIDs(ids, graph) {
    var seen = new Set();
    ids.forEach(collectDeepDescendants);
    return Array.from(seen);

    function collectDeepDescendants(id) {
        if (seen.has(id)) return;
        seen.add(id);

        var entity = graph.hasEntity(id);
        if (!entity || entity.type !== 'relation') return;

        entity.members
            .map(function(member) { return member.id; })
            .forEach(collectDeepDescendants);   // recurse
    }
}

// returns an selector to select entity ids for:
//  - deep descendant entityIDs for any of those entities that are relations
export function utilDeepMemberSelector(ids, graph, skipMultipolgonMembers) {
    var idsSet = new Set(ids);
    var seen = new Set();
    var returners = new Set();
    ids.forEach(collectDeepDescendants);
    return utilEntitySelector(Array.from(returners));

    function collectDeepDescendants(id) {
        if (seen.has(id)) return;
        seen.add(id);

        if (!idsSet.has(id)) {
            returners.add(id);
        }

        var entity = graph.hasEntity(id);
        if (!entity || entity.type !== 'relation') return;
        if (skipMultipolgonMembers && entity.isMultipolygon()) return;
        entity.members
            .map(function(member) { return member.id; })
            .forEach(collectDeepDescendants);   // recurse
    }
}

// Adds or removes highlight styling for the specified entities
export function utilHighlightEntities(ids, highlighted, context) {
    context.surface()
        .selectAll(utilEntityOrDeepMemberSelector(ids, context.graph()))
        .classed('highlighted', highlighted);
}

// returns an Array that is the union of:
//  - nodes for any nodeIDs passed in
//  - child nodes of any wayIDs passed in
//  - descendant member and child nodes of relationIDs passed in
export function utilGetAllNodes(ids, graph) {
    var seen = new Set();
    var nodes = new Set();

    ids.forEach(collectNodes);
    return Array.from(nodes);

    function collectNodes(id) {
        if (seen.has(id)) return;
        seen.add(id);

        var entity = graph.hasEntity(id);
        if (!entity) return;

        if (entity.type === 'node') {
            nodes.add(entity);
        } else if (entity.type === 'way') {
            entity.nodes.forEach(collectNodes);
        } else {
            entity.members
                .map(function(member) { return member.id; })
                .forEach(collectNodes);   // recurse
        }
    }
}

export function utilDisplayName(entity) {
    var localizedNameKey = 'name:' + utilDetect().locale.toLowerCase().split('-')[0];
    var name = entity.tags[localizedNameKey] || entity.tags.name || '';
    var network = entity.tags.cycle_network || entity.tags.network;

    if (!name && entity.tags.ref) {
        name = entity.tags.ref;
        if (network) {
            name = network + ' ' + name;
        }
    }

    return name;
}

export function utilDisplayNameForPath(entity) {
    var name = utilDisplayName(entity);
    var isFirefox = utilDetect().browser.toLowerCase().indexOf('firefox') > -1;

    if (!isFirefox && name && rtlRegex.test(name)) {
        name = fixRTLTextForSvg(name);
    }

    return name;
}

export function utilDisplayType(id) {
    return {
        n: t('inspector.node'),
        w: t('inspector.way'),
        r: t('inspector.relation')
    }[id.charAt(0)];
}

export function utilDisplayLabel(entity, context) {
    var displayName = utilDisplayName(entity);
    if (displayName) {
        // use the display name if there is one
        return displayName;
    }
    var preset = utilPreset(entity, context);
    if (preset && preset.name()) {
        // use the preset name if there is a match
        return preset.name();
    }
    // fallback to the display type (node/way/relation)
    return utilDisplayType(entity.id);
}

export function utilPreset(entity, context) {
    return context.presets().match(entity, context.graph());
}

export function utilEntityRoot(entityType) {
    return {
        node: 'n',
        way: 'w',
        relation: 'r'
    }[entityType];
}

// Returns a single object containing the tags of all the given entities.
// Example:
// {
//   highway: 'service',
//   service: 'parking_aisle'
// }
//           +
// {
//   highway: 'service',
//   service: 'driveway',
//   width: '3'
// }
//           =
// {
//   highway: 'service',
//   service: [ 'driveway', 'parking_aisle' ],
//   width: [ '3', undefined ]
// }
export function utilCombinedTags(entityIDs, graph) {

    var tags = {};
    var tagCounts = {};
    var allKeys = new Set();

    var entities = entityIDs.map(function(entityID) {
        return graph.hasEntity(entityID);
    }).filter(Boolean);

    // gather the aggregate keys
    entities.forEach(function(entity) {
        var keys = Object.keys(entity.tags).filter(Boolean);
        keys.forEach(function(key) {
            allKeys.add(key);
        });
    });

    entities.forEach(function(entity) {

        allKeys.forEach(function(key) {

            var value = entity.tags[key]; // purposely allow `undefined`

            if (!tags.hasOwnProperty(key)) {
                // first value, set as raw
                tags[key] = value;
            } else {
                if (!Array.isArray(tags[key])) {
                    if (tags[key] !== value) {
                        // first alternate value, replace single value with array
                        tags[key] = [tags[key], value];
                    }
                } else { // type is array
                    if (tags[key].indexOf(value) === -1) {
                        // subsequent alternate value, add to array
                        tags[key].push(value);
                    }
                }
            }

            var tagHash = key + '=' + value;
            if (!tagCounts[tagHash]) tagCounts[tagHash] = 0;
            tagCounts[tagHash] += 1;
        });
    });

    for (var key in tags) {
        if (!Array.isArray(tags[key])) continue;

        // sort values by frequency then alphabetically
        tags[key] = tags[key].sort(function(val1, val2) {
            var key = key; // capture
            var count2 = tagCounts[key + '=' + val2];
            var count1 = tagCounts[key + '=' + val1];
            if (count2 !== count1) {
                return count2 - count1;
            }
            if (val2 && val1) {
                return val1.localeCompare(val2);
            }
            return val1 ? 1 : -1;
        });
    }

    return tags;
}

export function utilStringQs(str) {
    var i = 0;  // advance past any leading '?' or '#' characters
    while (i < str.length && (str[i] === '?' || str[i] === '#')) i++;
    str = str.slice(i);

    return str.split('&').reduce(function(obj, pair){
        var parts = pair.split('=');
        if (parts.length === 2) {
            obj[parts[0]] = (null === parts[1]) ? '' : decodeURIComponent(parts[1]);
        }
        return obj;
    }, {});
}

export function utilQsString(obj, noencode) {
    // encode everything except special characters used in certain hash parameters:
    // "/" in map states, ":", ",", {" and "}" in background
    function softEncode(s) {
        return encodeURIComponent(s).replace(/(%2F|%3A|%2C|%7B|%7D)/g, decodeURIComponent);
    }

    return Object.keys(obj).sort().map(function(key) {
        return encodeURIComponent(key) + '=' + (
            noencode ? softEncode(obj[key]) : encodeURIComponent(obj[key]));
    }).join('&');
}

export function utilPrefixDOMProperty(property) {
    var prefixes = ['webkit', 'ms', 'moz', 'o'];
    var i = -1;
    var n = prefixes.length;
    var s = document.body;

    if (property in s)
        return property;

    property = property.substr(0, 1).toUpperCase() + property.substr(1);

    while (++i < n) {
        if (prefixes[i] + property in s) {
            return prefixes[i] + property;
        }
    }

    return false;
}

export function utilPrefixCSSProperty(property) {
    var prefixes = ['webkit', 'ms', 'Moz', 'O'];
    var i = -1;
    var n = prefixes.length;
    var s = document.body.style;

    if (property.toLowerCase() in s) {
        return property.toLowerCase();
    }

    while (++i < n) {
        if (prefixes[i] + property in s) {
            return '-' + prefixes[i].toLowerCase() + property.replace(/([A-Z])/g, '-$1').toLowerCase();
        }
    }

    return false;
}

var transformProperty;
export function utilSetTransform(el, x, y, scale) {
    var prop = transformProperty = transformProperty || utilPrefixCSSProperty('Transform');
    var translate = utilDetect().opera ? 'translate('   + x + 'px,' + y + 'px)'
        : 'translate3d(' + x + 'px,' + y + 'px,0)';
    return el.style(prop, translate + (scale ? ' scale(' + scale + ')' : ''));
}

// Calculates Levenshtein distance between two strings
// see:  https://en.wikipedia.org/wiki/Levenshtein_distance
// first converts the strings to lowercase and replaces diacritic marks with ascii equivalents.
export function utilEditDistance(a, b) {
    a = removeDiacritics(a.toLowerCase());
    b = removeDiacritics(b.toLowerCase());
    if (a.length === 0) return b.length;
    if (b.length === 0) return a.length;
    var matrix = [];
    for (var i = 0; i <= b.length; i++) { matrix[i] = [i]; }
    for (var j = 0; j <= a.length; j++) { matrix[0][j] = j; }
    for (i = 1; i <= b.length; i++) {
        for (j = 1; j <= a.length; j++) {
            if (b.charAt(i-1) === a.charAt(j-1)) {
                matrix[i][j] = matrix[i-1][j-1];
            } else {
                matrix[i][j] = Math.min(matrix[i-1][j-1] + 1, // substitution
                    Math.min(matrix[i][j-1] + 1, // insertion
                    matrix[i-1][j] + 1)); // deletion
            }
        }
    }
    return matrix[b.length][a.length];
}

// a d3.mouse-alike which
// 1. Only works on HTML elements, not SVG
// 2. Does not cause style recalculation
export function utilFastMouse(container) {
    var rect = container.getBoundingClientRect();
    var rectLeft = rect.left;
    var rectTop = rect.top;
    var clientLeft = +container.clientLeft;
    var clientTop = +container.clientTop;
    return function(e) {
        return [
            e.clientX - rectLeft - clientLeft,
            e.clientY - rectTop - clientTop];
    };
}

export function utilAsyncMap(inputs, func, callback) {
    var remaining = inputs.length;
    var results = [];
    var errors = [];

    inputs.forEach(function(d, i) {
        func(d, function done(err, data) {
            errors[i] = err;
            results[i] = data;
            remaining--;
            if (!remaining) callback(errors, results);
        });
    });
}

// wraps an index to an interval [0..length-1]
export function utilWrap(index, length) {
    if (index < 0) {
        index += Math.ceil(-index/length)*length;
    }
    return index % length;
}

/**
 * a replacement for functor
 *
 * @param {*} value any value
 * @returns {Function} a function that returns that value or the value if it's a function
 */
export function utilFunctor(value) {
    if (typeof value === 'function') return value;
    return function() {
        return value;
    };
}

export function utilNoAuto(selection) {
    var isText = (selection.size() && selection.node().tagName.toLowerCase() === 'textarea');

    return selection
        // assign 'new-password' even for non-password fields to prevent browsers (Chrome) ignoring 'off'
        .attr('autocomplete', 'new-password')
        .attr('autocorrect', 'off')
        .attr('autocapitalize', 'off')
        .attr('spellcheck', isText ? 'true' : 'false');
}

// https://stackoverflow.com/questions/194846/is-there-any-kind-of-hash-code-function-in-javascript
// https://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
export function utilHashcode(str) {
    var hash = 0;
    if (str.length === 0) {
        return hash;
    }
    for (var i = 0; i < str.length; i++) {
        var char = str.charCodeAt(i);
        hash = ((hash << 5) - hash) + char;
        hash = hash & hash; // Convert to 32bit integer
    }
    return hash;
}

// returns version of `str` with all runs of special characters replaced by `_`;
// suitable for HTML ids, classes, selectors, etc.
export function utilSafeClassName(str) {
    return str.toLowerCase().replace(/[^a-z0-9]+/g, '_');
}

// Returns string based on `str` that is highly unlikely to collide with an id
// used previously or that's present elsewhere in the document. Useful for preventing
// browser-provided autofills or when embedding iD on pages with unknown elements.
export function utilUniqueDomId(str) {
    return 'ideditor-' + utilSafeClassName(str) + '-' + new Date().getTime().toString();
}
<commit_msg>Fix lint warning<commit_after>import { remove as removeDiacritics } from 'diacritics';
import { fixRTLTextForSvg, rtlRegex } from './svg_paths_rtl_fix';

import { t } from './locale';
import { utilArrayUnion } from './array';
import { utilDetect } from './detect';

export function utilTagText(entity) {
    var obj = (entity && entity.tags) || {};
    return Object.keys(obj)
        .map(function(k) { return k + '=' + obj[k]; })
        .join(', ');
}

export function utilTagDiff(oldTags, newTags) {
    var tagDiff = [];
    var keys = utilArrayUnion(Object.keys(oldTags), Object.keys(newTags)).sort();
    keys.forEach(function(k) {
        var oldVal = oldTags[k];
        var newVal = newTags[k];

        if ((oldVal || oldVal === '') && (newVal === undefined || newVal !== oldVal)) {
            tagDiff.push({
                type: '-',
                key: k,
                oldVal: oldVal,
                newVal: newVal,
                display: '- ' + k + '=' + oldVal
            });
        }
        if ((newVal || newVal === '') && (oldVal === undefined || newVal !== oldVal)) {
            tagDiff.push({
                type: '+',
                key: k,
                oldVal: oldVal,
                newVal: newVal,
                display: '+ ' + k + '=' + newVal
            });
        }
    });
    return tagDiff;
}

export function utilEntitySelector(ids) {
    return ids.length ? '.' + ids.join(',.') : 'nothing';
}

// returns an selector to select entity ids for:
//  - entityIDs passed in
//  - shallow descendant entityIDs for any of those entities that are relations
export function utilEntityOrMemberSelector(ids, graph) {
    var seen = new Set(ids);
    ids.forEach(collectShallowDescendants);
    return utilEntitySelector(Array.from(seen));

    function collectShallowDescendants(id) {
        var entity = graph.hasEntity(id);
        if (!entity || entity.type !== 'relation') return;

        entity.members
            .map(function(member) { return member.id; })
            .forEach(function(id) { seen.add(id); });
    }
}

// returns an selector to select entity ids for:
//  - entityIDs passed in
//  - deep descendant entityIDs for any of those entities that are relations
export function utilEntityOrDeepMemberSelector(ids, graph) {
    return utilEntitySelector(utilEntityAndDeepMemberIDs(ids, graph));
}

// returns an selector to select entity ids for:
//  - entityIDs passed in
//  - deep descendant entityIDs for any of those entities that are relations
export function utilEntityAndDeepMemberIDs(ids, graph) {
    var seen = new Set();
    ids.forEach(collectDeepDescendants);
    return Array.from(seen);

    function collectDeepDescendants(id) {
        if (seen.has(id)) return;
        seen.add(id);

        var entity = graph.hasEntity(id);
        if (!entity || entity.type !== 'relation') return;

        entity.members
            .map(function(member) { return member.id; })
            .forEach(collectDeepDescendants);   // recurse
    }
}

// returns an selector to select entity ids for:
//  - deep descendant entityIDs for any of those entities that are relations
export function utilDeepMemberSelector(ids, graph, skipMultipolgonMembers) {
    var idsSet = new Set(ids);
    var seen = new Set();
    var returners = new Set();
    ids.forEach(collectDeepDescendants);
    return utilEntitySelector(Array.from(returners));

    function collectDeepDescendants(id) {
        if (seen.has(id)) return;
        seen.add(id);

        if (!idsSet.has(id)) {
            returners.add(id);
        }

        var entity = graph.hasEntity(id);
        if (!entity || entity.type !== 'relation') return;
        if (skipMultipolgonMembers && entity.isMultipolygon()) return;
        entity.members
            .map(function(member) { return member.id; })
            .forEach(collectDeepDescendants);   // recurse
    }
}

// Adds or removes highlight styling for the specified entities
export function utilHighlightEntities(ids, highlighted, context) {
    context.surface()
        .selectAll(utilEntityOrDeepMemberSelector(ids, context.graph()))
        .classed('highlighted', highlighted);
}

// returns an Array that is the union of:
//  - nodes for any nodeIDs passed in
//  - child nodes of any wayIDs passed in
//  - descendant member and child nodes of relationIDs passed in
export function utilGetAllNodes(ids, graph) {
    var seen = new Set();
    var nodes = new Set();

    ids.forEach(collectNodes);
    return Array.from(nodes);

    function collectNodes(id) {
        if (seen.has(id)) return;
        seen.add(id);

        var entity = graph.hasEntity(id);
        if (!entity) return;

        if (entity.type === 'node') {
            nodes.add(entity);
        } else if (entity.type === 'way') {
            entity.nodes.forEach(collectNodes);
        } else {
            entity.members
                .map(function(member) { return member.id; })
                .forEach(collectNodes);   // recurse
        }
    }
}

export function utilDisplayName(entity) {
    var localizedNameKey = 'name:' + utilDetect().locale.toLowerCase().split('-')[0];
    var name = entity.tags[localizedNameKey] || entity.tags.name || '';
    var network = entity.tags.cycle_network || entity.tags.network;

    if (!name && entity.tags.ref) {
        name = entity.tags.ref;
        if (network) {
            name = network + ' ' + name;
        }
    }

    return name;
}

export function utilDisplayNameForPath(entity) {
    var name = utilDisplayName(entity);
    var isFirefox = utilDetect().browser.toLowerCase().indexOf('firefox') > -1;

    if (!isFirefox && name && rtlRegex.test(name)) {
        name = fixRTLTextForSvg(name);
    }

    return name;
}

export function utilDisplayType(id) {
    return {
        n: t('inspector.node'),
        w: t('inspector.way'),
        r: t('inspector.relation')
    }[id.charAt(0)];
}

export function utilDisplayLabel(entity, context) {
    var displayName = utilDisplayName(entity);
    if (displayName) {
        // use the display name if there is one
        return displayName;
    }
    var preset = utilPreset(entity, context);
    if (preset && preset.name()) {
        // use the preset name if there is a match
        return preset.name();
    }
    // fallback to the display type (node/way/relation)
    return utilDisplayType(entity.id);
}

export function utilPreset(entity, context) {
    return context.presets().match(entity, context.graph());
}

export function utilEntityRoot(entityType) {
    return {
        node: 'n',
        way: 'w',
        relation: 'r'
    }[entityType];
}

// Returns a single object containing the tags of all the given entities.
// Example:
// {
//   highway: 'service',
//   service: 'parking_aisle'
// }
//           +
// {
//   highway: 'service',
//   service: 'driveway',
//   width: '3'
// }
//           =
// {
//   highway: 'service',
//   service: [ 'driveway', 'parking_aisle' ],
//   width: [ '3', undefined ]
// }
export function utilCombinedTags(entityIDs, graph) {

    var tags = {};
    var tagCounts = {};
    var allKeys = new Set();

    var entities = entityIDs.map(function(entityID) {
        return graph.hasEntity(entityID);
    }).filter(Boolean);

    // gather the aggregate keys
    entities.forEach(function(entity) {
        var keys = Object.keys(entity.tags).filter(Boolean);
        keys.forEach(function(key) {
            allKeys.add(key);
        });
    });

    entities.forEach(function(entity) {

        allKeys.forEach(function(key) {

            var value = entity.tags[key]; // purposely allow `undefined`

            if (!tags.hasOwnProperty(key)) {
                // first value, set as raw
                tags[key] = value;
            } else {
                if (!Array.isArray(tags[key])) {
                    if (tags[key] !== value) {
                        // first alternate value, replace single value with array
                        tags[key] = [tags[key], value];
                    }
                } else { // type is array
                    if (tags[key].indexOf(value) === -1) {
                        // subsequent alternate value, add to array
                        tags[key].push(value);
                    }
                }
            }

            var tagHash = key + '=' + value;
            if (!tagCounts[tagHash]) tagCounts[tagHash] = 0;
            tagCounts[tagHash] += 1;
        });
    });

    for (var key in tags) {
        if (!Array.isArray(tags[key])) continue;

        // sort values by frequency then alphabetically
        tags[key] = tags[key].sort(function(val1, val2) {
            var key = key; // capture
            var count2 = tagCounts[key + '=' + val2];
            var count1 = tagCounts[key + '=' + val1];
            if (count2 !== count1) {
                return count2 - count1;
            }
            if (val2 && val1) {
                return val1.localeCompare(val2);
            }
            return val1 ? 1 : -1;
        });
    }

    return tags;
}

export function utilStringQs(str) {
    var i = 0;  // advance past any leading '?' or '#' characters
    while (i < str.length && (str[i] === '?' || str[i] === '#')) i++;
    str = str.slice(i);

    return str.split('&').reduce(function(obj, pair){
        var parts = pair.split('=');
        if (parts.length === 2) {
            obj[parts[0]] = (null === parts[1]) ? '' : decodeURIComponent(parts[1]);
        }
        return obj;
    }, {});
}

export function utilQsString(obj, noencode) {
    // encode everything except special characters used in certain hash parameters:
    // "/" in map states, ":", ",", {" and "}" in background
    function softEncode(s) {
        return encodeURIComponent(s).replace(/(%2F|%3A|%2C|%7B|%7D)/g, decodeURIComponent);
    }

    return Object.keys(obj).sort().map(function(key) {
        return encodeURIComponent(key) + '=' + (
            noencode ? softEncode(obj[key]) : encodeURIComponent(obj[key]));
    }).join('&');
}

export function utilPrefixDOMProperty(property) {
    var prefixes = ['webkit', 'ms', 'moz', 'o'];
    var i = -1;
    var n = prefixes.length;
    var s = document.body;

    if (property in s)
        return property;

    property = property.substr(0, 1).toUpperCase() + property.substr(1);

    while (++i < n) {
        if (prefixes[i] + property in s) {
            return prefixes[i] + property;
        }
    }

    return false;
}

export function utilPrefixCSSProperty(property) {
    var prefixes = ['webkit', 'ms', 'Moz', 'O'];
    var i = -1;
    var n = prefixes.length;
    var s = document.body.style;

    if (property.toLowerCase() in s) {
        return property.toLowerCase();
    }

    while (++i < n) {
        if (prefixes[i] + property in s) {
            return '-' + prefixes[i].toLowerCase() + property.replace(/([A-Z])/g, '-$1').toLowerCase();
        }
    }

    return false;
}

var transformProperty;
export function utilSetTransform(el, x, y, scale) {
    var prop = transformProperty = transformProperty || utilPrefixCSSProperty('Transform');
    var translate = utilDetect().opera ? 'translate('   + x + 'px,' + y + 'px)'
        : 'translate3d(' + x + 'px,' + y + 'px,0)';
    return el.style(prop, translate + (scale ? ' scale(' + scale + ')' : ''));
}

// Calculates Levenshtein distance between two strings
// see:  https://en.wikipedia.org/wiki/Levenshtein_distance
// first converts the strings to lowercase and replaces diacritic marks with ascii equivalents.
export function utilEditDistance(a, b) {
    a = removeDiacritics(a.toLowerCase());
    b = removeDiacritics(b.toLowerCase());
    if (a.length === 0) return b.length;
    if (b.length === 0) return a.length;
    var matrix = [];
    for (var i = 0; i <= b.length; i++) { matrix[i] = [i]; }
    for (var j = 0; j <= a.length; j++) { matrix[0][j] = j; }
    for (i = 1; i <= b.length; i++) {
        for (j = 1; j <= a.length; j++) {
            if (b.charAt(i-1) === a.charAt(j-1)) {
                matrix[i][j] = matrix[i-1][j-1];
            } else {
                matrix[i][j] = Math.min(matrix[i-1][j-1] + 1, // substitution
                    Math.min(matrix[i][j-1] + 1, // insertion
                    matrix[i-1][j] + 1)); // deletion
            }
        }
    }
    return matrix[b.length][a.length];
}

// a d3.mouse-alike which
// 1. Only works on HTML elements, not SVG
// 2. Does not cause style recalculation
export function utilFastMouse(container) {
    var rect = container.getBoundingClientRect();
    var rectLeft = rect.left;
    var rectTop = rect.top;
    var clientLeft = +container.clientLeft;
    var clientTop = +container.clientTop;
    return function(e) {
        return [
            e.clientX - rectLeft - clientLeft,
            e.clientY - rectTop - clientTop];
    };
}

export function utilAsyncMap(inputs, func, callback) {
    var remaining = inputs.length;
    var results = [];
    var errors = [];

    inputs.forEach(function(d, i) {
        func(d, function done(err, data) {
            errors[i] = err;
            results[i] = data;
            remaining--;
            if (!remaining) callback(errors, results);
        });
    });
}

// wraps an index to an interval [0..length-1]
export function utilWrap(index, length) {
    if (index < 0) {
        index += Math.ceil(-index/length)*length;
    }
    return index % length;
}

/**
 * a replacement for functor
 *
 * @param {*} value any value
 * @returns {Function} a function that returns that value or the value if it's a function
 */
export function utilFunctor(value) {
    if (typeof value === 'function') return value;
    return function() {
        return value;
    };
}

export function utilNoAuto(selection) {
    var isText = (selection.size() && selection.node().tagName.toLowerCase() === 'textarea');

    return selection
        // assign 'new-password' even for non-password fields to prevent browsers (Chrome) ignoring 'off'
        .attr('autocomplete', 'new-password')
        .attr('autocorrect', 'off')
        .attr('autocapitalize', 'off')
        .attr('spellcheck', isText ? 'true' : 'false');
}

// https://stackoverflow.com/questions/194846/is-there-any-kind-of-hash-code-function-in-javascript
// https://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
export function utilHashcode(str) {
    var hash = 0;
    if (str.length === 0) {
        return hash;
    }
    for (var i = 0; i < str.length; i++) {
        var char = str.charCodeAt(i);
        hash = ((hash << 5) - hash) + char;
        hash = hash & hash; // Convert to 32bit integer
    }
    return hash;
}

// returns version of `str` with all runs of special characters replaced by `_`;
// suitable for HTML ids, classes, selectors, etc.
export function utilSafeClassName(str) {
    return str.toLowerCase().replace(/[^a-z0-9]+/g, '_');
}

// Returns string based on `str` that is highly unlikely to collide with an id
// used previously or that's present elsewhere in the document. Useful for preventing
// browser-provided autofills or when embedding iD on pages with unknown elements.
export function utilUniqueDomId(str) {
    return 'ideditor-' + utilSafeClassName(str) + '-' + new Date().getTime().toString();
}
<|endoftext|>


<commit_before>window.onload=function() {
  var start = 0;
  var stop = 0;
  var interval = 0;
  var fcInterval = 0;
  var timerRunning = 0;
  var firstCrack = 0;

  $("#start-button").click(function() {
    if (interval == 0) {
      start = new Date().getTime();
    }

    if (timerRunning == 1) {
      timerRunning = 2;
      stop = new Date().getTime();
      $(this).text("Reset");
      $(this).removeClass("btn-danger");
      $(this).addClass("btn-warning");

      clearInterval(interval);
      clearInterval(fcInterval);
    } else if (timerRunning == 2) {
      start = 0;
      stop = 0;
      interval = 0;
      fcInterval = 0;
      timerRunning = 0;
      firstCrack = 0;
      $(this).text("Start");
      $(this).removeClass("btn-warning");
      $(this).addClass("btn-primary");
      $(".jumbotron h1#timer").text("00:00");
      $("#first-crack").text("First Crack: ");
      $("#development-time").text("Development %: ");
      $("#development-time").removeClass("label-warning");
      $("#development-time").removeClass("label-danger");
      $("#lowest").text("15.0%: ");
      $("#lower").text("17.5%: ");
      $("#low").text("20.0%: ");
      $("#mid").text("22.5%: ");
      $("#high").text("25.0%: ");
    } else {
      timerRunning = 1;
      $(this).text("Stop");
      $(this).removeClass("btn-primary");
      $(this).addClass("btn-danger");

      interval = setInterval(function() {
        $(".jumbotron h1#timer").text(timeToString(new Date().getTime() - start));
      }, 100)
    }
  });

  $("#first-crack-button").click(function() {
    if (start != 0 && timerRunning == 1) {
      firstCrack = new Date().getTime();
      $("#first-crack").addClass("label-success");
      $("#first-crack").text("First Crack: " + timeToString(firstCrack - start));
      $("#firstCrackButtons").toggle();
      $("#development-time").removeClass("label-warning");
      $("#development-time").removeClass("label-danger");

      if (fcInterval != 0) {
        clearInterval(fcInterval);
      }
      fcInterval = setInterval(function() {
        var firstCrackMillis = firstCrack - start;
        var firstCrackSeconds = Math.floor(firstCrackMillis / 1000);
        var currentTimeMillis = new Date().getTime() - start;
        var currentTimeSeconds = Math.floor(currentTimeMillis / 1000);
        var developmentTime = ((1 - (firstCrackSeconds / currentTimeSeconds)) * 100).toFixed(2);

        $("#development-time").text("Development %: " + developmentTime + "%");
        if (developmentTime > 15) {
          $("#development-time").addClass("label-warning");
        }
        if (developmentTime > 20) {
          $("#development-time").addClass("label-danger");
        }
      }, 1000)

      var developmentTimeMillis = firstCrack - start;
      $("#lowest").text("15.0%: " + timeToString(developmentTimeMillis / .85));
      $("#lower").text("17.5%: " + timeToString(developmentTimeMillis / .825));
      $("#low").text("20.0%: " + timeToString(developmentTimeMillis / .8));
      $("#mid").text("22.5%: " + timeToString(developmentTimeMillis / .775));
      $("#high").text("25.0%: " + timeToString(developmentTimeMillis / .75));
    }
  });

  $("#calc-button").click(function() {
    var greenWeight = $("#green-weight").val() || 0;
    var roastWeight = $("#roast-weight").val() || 0;
    $("#weight-loss").text("Weight Loss: " + calcWeightLoss(greenWeight, roastWeight) + "%");
    $("#date-timestamp").text("Date: " + new moment().format('MMM Do YYYY, h:mm:ss a'));
  });

  function calcWeightLoss(greenWeight, roastWeight) {
    var weightLoss = 0;

    if (greenWeight != 0 && roastWeight !=0) {
      weightLoss = 100*((greenWeight - roastWeight)/greenWeight);
    }

    return weightLoss.toFixed(2);
  }

  function timeToString(millis) {
    millis = Math.round(millis);
    var timeSeconds = Math.floor(millis / 1000);
    var timeMinutes = Math.floor(timeSeconds / 60);

    if (timeSeconds > 59) {
      timeSeconds = timeSeconds - (timeMinutes * 60);
    }
    if (timeSeconds < 10) {
      timeSeconds = "0" + timeSeconds;
    }
    if (timeMinutes < 10) {
      timeMinutes = "0" + timeMinutes;
    }

    return timeMinutes + ":" + timeSeconds;
  }
}
<commit_msg>HTML tweaks.<commit_after>window.onload=function() {
  var start = 0;
  var stop = 0;
  var interval = 0;
  var fcInterval = 0;
  var timerRunning = 0;
  var firstCrack = 0;

  $("#start-button").click(function() {
    if (interval == 0) {
      start = new Date().getTime();
    }

    if (timerRunning == 1) {
      timerRunning = 2;
      stop = new Date().getTime();
      $(this).text("Reset");
      $(this).removeClass("btn-danger");
      $(this).addClass("btn-warning");

      clearInterval(interval);
      clearInterval(fcInterval);
    } else if (timerRunning == 2) {
      start = 0;
      stop = 0;
      interval = 0;
      fcInterval = 0;
      timerRunning = 0;
      firstCrack = 0;
      $(this).text("Start");
      $(this).removeClass("btn-warning");
      $(this).addClass("btn-primary");
      $(".jumbotron h1#timer").text("00:00");
      $("#first-crack").text("First Crack: ");
      $("#development-time").text("Development: ");
      $("#development-time").removeClass("label-warning");
      $("#development-time").removeClass("label-danger");
      $("#lowest").text("15.0%: ");
      $("#lower").text("17.5%: ");
      $("#low").text("20.0%: ");
      $("#mid").text("22.5%: ");
      $("#high").text("25.0%: ");
    } else {
      timerRunning = 1;
      $(this).text("Stop");
      $(this).removeClass("btn-primary");
      $(this).addClass("btn-danger");

      interval = setInterval(function() {
        $(".jumbotron h1#timer").text(timeToString(new Date().getTime() - start));
      }, 100)
    }
  });

  $("#first-crack-button").click(function() {
    if (start != 0 && timerRunning == 1) {
      firstCrack = new Date().getTime();
      $("#first-crack").addClass("label-success");
      $("#first-crack").text("First Crack: " + timeToString(firstCrack - start));
      $("#firstCrackButtons").toggle();
      $("#development-time").removeClass("label-warning");
      $("#development-time").removeClass("label-danger");

      if (fcInterval != 0) {
        clearInterval(fcInterval);
      }
      fcInterval = setInterval(function() {
        var firstCrackMillis = firstCrack - start;
        var firstCrackSeconds = Math.floor(firstCrackMillis / 1000);
        var currentTimeMillis = new Date().getTime() - start;
        var currentTimeSeconds = Math.floor(currentTimeMillis / 1000);
        var developmentTime = ((1 - (firstCrackSeconds / currentTimeSeconds)) * 100).toFixed(2);

        $("#development-time").text("Development %: " + developmentTime + "%");
        if (developmentTime > 15) {
          $("#development-time").addClass("label-warning");
        }
        if (developmentTime > 20) {
          $("#development-time").addClass("label-danger");
        }
      }, 1000)

      var developmentTimeMillis = firstCrack - start;
      $("#lowest").text("15.0%: " + timeToString(developmentTimeMillis / .85));
      $("#lower").text("17.5%: " + timeToString(developmentTimeMillis / .825));
      $("#low").text("20.0%: " + timeToString(developmentTimeMillis / .8));
      $("#mid").text("22.5%: " + timeToString(developmentTimeMillis / .775));
      $("#high").text("25.0%: " + timeToString(developmentTimeMillis / .75));
    }
  });

  $("#calc-button").click(function() {
    var greenWeight = $("#green-weight").val() || 0;
    var roastWeight = $("#roast-weight").val() || 0;
    $("#weight-loss").text("Weight Loss: " + calcWeightLoss(greenWeight, roastWeight) + "%");
    $("#date-timestamp").text("Date: " + new moment().format('MMM Do YYYY, h:mm:ss a'));
  });

  function calcWeightLoss(greenWeight, roastWeight) {
    var weightLoss = 0;

    if (greenWeight != 0 && roastWeight !=0) {
      weightLoss = 100*((greenWeight - roastWeight)/greenWeight);
    }

    return weightLoss.toFixed(2);
  }

  function timeToString(millis) {
    millis = Math.round(millis);
    var timeSeconds = Math.floor(millis / 1000);
    var timeMinutes = Math.floor(timeSeconds / 60);

    if (timeSeconds > 59) {
      timeSeconds = timeSeconds - (timeMinutes * 60);
    }
    if (timeSeconds < 10) {
      timeSeconds = "0" + timeSeconds;
    }
    if (timeMinutes < 10) {
      timeMinutes = "0" + timeMinutes;
    }

    return timeMinutes + ":" + timeSeconds;
  }
}
<|endoftext|>


<commit_before>#!/usr/bin/env node
//
// Test the conflict detector

var lib = require('./lib')
var test = lib.test
var util = require('util')
var request = require('request')

test('Conflicts', function(t, type) {
  if(type == 'ecouchdb')
    return t.end() // XXX

  t.plan(38)

  var conflicts = lib.db + '/_design/'+type + '/_view/conflicts'
  var rnd = Math.random().toString().replace(/^0\./, '')
  var docs = [1, 2, 3].map(function(a) {
    return {'_id':type+a+'_'+rnd, 'value':a}
  })

  var bulk = lib.db + '/_bulk_docs'
  request.post({'url':bulk, 'json':{'docs':docs}}, function(er, res) {
    if(er) throw er
    t.equal(res.statusCode, 201, 'Store the first doc batch')

    var dupes = JSON.parse(JSON.stringify(docs))
    dupes.forEach(function(doc) { doc.value *= 2 })
    request.post({'url':bulk, 'json':{'all_or_nothing':true, 'docs':dupes}}, function(er, res) {
      if(er) throw er
      t.equal(res.statusCode, 201, 'Store the second doc batch')

      request({'url':conflicts+'?reduce=false', 'json':true}, function(er, res) {
        if(er) throw er
        test_doc()
        function test_doc(i) {
          i = i || 0
          var doc = docs[i]
          if(!doc)
            return test_reduce()

          var hits = res.body.rows.filter(function(row) { return row.id == doc._id })

          t.equal(hits.length, 2, 'Two conflict hits for document '+doc._id)
          t.equal(hits[0].key.length, 2, 'First conflict key is a 2-array: '+doc._id)
          t.equal(hits[1].key.length, 2, 'Second conflict key is a 2-array: '+doc._id)

          t.equal(hits[0].key[0], doc._id, 'Conflict first key is the doc id: '+doc._id)
          t.equal(hits[1].key[0], doc._id, 'Conflict second key is the doc id: '+doc._id)
          t.notEqual(hits[0].key[1], hits[1].key[1], 'Conflict revs are dissimilar: '+doc._id)

          var doc1, doc2
          request({'url':lib.db+'/'+doc._id+'?rev='+hits[0].key[1], 'json':true}, function(er, res) {
            if(er) throw er

            t.equal(res.statusCode, 200, 'Got first doc revision: '+doc._id)
            doc1 = res.body

            request({'url':lib.db+'/'+doc._id+'?rev='+hits[1].key[1], 'json':true}, function(er, res) {
              if(er) throw er

              t.equal(res.statusCode, 200, 'Got second doc revision: '+doc._id)
              doc2 = res.body

              t.ok(doc1.value == doc2.value*2 || doc2.value == doc1.value*2,
                   'One doc value is twice the other: '+doc._id)

              test_doc(i+1)
            })
          })
        }
      })
    })
  })

  function test_reduce() {
    request({'url':conflicts+'?group_level=1', 'json':true}, function(er, res) {
      if(er) throw er
      docs.forEach(function(doc) {
        var matched_rows = res.body.rows.filter(function(row) { return row.key[0] == doc._id })
        t.equal(matched_rows.length, 1, 'One unique row for doc: '+doc._id)

        var row = matched_rows[0]
        t.equal(row.key.length, 1, 'Row key length is 1 (group level 1): '+doc._id)
        t.equal(row.value, 2, 'Row indicates two conflicting revisions: '+doc._id)
      })

      t.end()
    })
  }
})
<commit_msg>Tests confirming that ?include_docs pulls the correct revisions<commit_after>#!/usr/bin/env node
//
// Test the conflict detector

var lib = require('./lib')
var test = lib.test
var util = require('util')
var request = require('request')

test('Conflicts', function(t, type) {
  if(type == 'ecouchdb')
    return t.end() // XXX

  t.plan(50)

  var conflicts = lib.db + '/_design/'+type + '/_view/conflicts'
  var rnd = Math.random().toString().replace(/^0\./, '')
  var docs = [1, 2, 3].map(function(a) {
    return {'_id':type+a+'_'+rnd, 'value':a}
  })

  var bulk = lib.db + '/_bulk_docs'
  request.post({'url':bulk, 'json':{'docs':docs}}, function(er, res) {
    if(er) throw er
    t.equal(res.statusCode, 201, 'Store the first doc batch')

    var dupes = JSON.parse(JSON.stringify(docs))
    dupes.forEach(function(doc) { doc.value *= 2 })
    request.post({'url':bulk, 'json':{'all_or_nothing':true, 'docs':dupes}}, function(er, res) {
      if(er) throw er
      t.equal(res.statusCode, 201, 'Store the second doc batch')

      request({'url':conflicts+'?reduce=false', 'json':true}, function(er, res) {
        if(er) throw er
        test_doc()
        function test_doc(i) {
          i = i || 0
          var doc = docs[i]
          if(!doc)
            return test_reduce()

          var hits = res.body.rows.filter(function(row) { return row.id == doc._id })

          t.equal(hits.length, 2, 'Two conflict hits for document '+doc._id)
          t.equal(hits[0].key.length, 2, 'First conflict key is a 2-array: '+doc._id)
          t.equal(hits[1].key.length, 2, 'Second conflict key is a 2-array: '+doc._id)

          t.equal(hits[0].key[0], doc._id, 'Conflict first key is the doc id: '+doc._id)
          t.equal(hits[1].key[0], doc._id, 'Conflict second key is the doc id: '+doc._id)
          t.notEqual(hits[0].key[1], hits[1].key[1], 'Conflict revs are dissimilar: '+doc._id)

          var doc1, doc2
          request({'url':lib.db+'/'+doc._id+'?rev='+hits[0].key[1], 'json':true}, function(er, res) {
            if(er) throw er

            t.equal(res.statusCode, 200, 'Got first doc revision: '+doc._id)
            doc1 = res.body

            request({'url':lib.db+'/'+doc._id+'?rev='+hits[1].key[1], 'json':true}, function(er, res) {
              if(er) throw er

              t.equal(res.statusCode, 200, 'Got second doc revision: '+doc._id)
              doc2 = res.body

              t.ok(doc1.value == doc2.value*2 || doc2.value == doc1.value*2,
                   'One doc value is twice the other: '+doc._id)

              test_doc(i+1)
            })
          })
        }
      })
    })
  })

  function test_reduce() {
    request({'url':conflicts+'?group_level=1', 'json':true}, function(er, res) {
      if(er) throw er
      docs.forEach(function(doc) {
        var matched_rows = res.body.rows.filter(function(row) { return row.key[0] == doc._id })
        t.equal(matched_rows.length, 1, 'One unique row for doc: '+doc._id)

        var row = matched_rows[0]
        t.equal(row.key.length, 1, 'Row key length is 1 (group level 1): '+doc._id)
        t.equal(row.value, 2, 'Row indicates two conflicting revisions: '+doc._id)
      })

      return test_include_docs()
    })
  }

  function test_include_docs() {
    request({'url':conflicts+'?reduce=false&include_docs=true', 'json':true}, function(er, res) {
      if(er) throw er

      docs.forEach(function(doc) {
        var rows = res.body.rows.filter(function(row) { return row.key[0] == doc._id })

        t.equal(rows.length, 2, 'Two rows for doc with ?include_docs: '+doc._id)
        var doc1 = rows[0].doc
          , doc2 = rows[1].doc

        t.type(doc1, 'object', 'Got first revision with include_docs: '+doc._id)
        t.type(doc2, 'object', 'Got second revision with include_docs: '+doc._id)

        t.ok(doc1.value == doc2.value*2 || doc2.value == doc1.value*2,
             'One doc value is twice the other with include_docs: '+doc._id)
      })

      t.end()
    })
  }
})
<|endoftext|>


<commit_before>import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import theme from './theme.css';

const spacings = [0, 1, 2, 3, 4, 5, 6, 7, 8];

class Box extends PureComponent {
  static propTypes = {
    alignContent: PropTypes.oneOf([
      'center',
      'flex-start',
      'flex-end',
      'space-around',
      'space-between',
      'space-evenly',
    ]),
    alignItems: PropTypes.oneOf(['center', 'flex-start', 'flex-end']),
    alignSelf: PropTypes.oneOf(['center', 'flex-start', 'flex-end', 'stretch']),
    children: PropTypes.any,
    className: PropTypes.string,
    display: PropTypes.oneOf(['inline', 'inline-block', 'block', 'flex', 'inline-flex']),
    element: PropTypes.node,
    flex: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
    flexDirection: PropTypes.oneOf(['row', 'row-reverse', 'column', 'column-reverse']),
    flexWrap: PropTypes.oneOf(['nowrap', 'wrap', 'wrap-reverse']),
    justifyContent: PropTypes.oneOf([
      'center',
      'flex-start',
      'flex-end',
      'space-around',
      'space-between',
      'space-evenly',
    ]),
    margin: PropTypes.oneOf(spacings),
    marginHorizontal: PropTypes.oneOf(spacings),
    marginVertical: PropTypes.oneOf(spacings),
    marginBottom: PropTypes.oneOf(spacings),
    marginLeft: PropTypes.oneOf(spacings),
    marginRight: PropTypes.oneOf(spacings),
    marginTop: PropTypes.oneOf(spacings),
    padding: PropTypes.oneOf(spacings),
    paddingHorizontal: PropTypes.oneOf(spacings),
    paddingVertical: PropTypes.oneOf(spacings),
    paddingBottom: PropTypes.oneOf(spacings),
    paddingLeft: PropTypes.oneOf(spacings),
    paddingRight: PropTypes.oneOf(spacings),
    paddingTop: PropTypes.oneOf(spacings),
  };

  static defaultProps = {
    element: 'div',
    margin: 0,
    padding: 0,
  };

  render() {
    const {
      alignContent,
      alignItems,
      alignSelf,
      children,
      className,
      display,
      element,
      flex,
      flexDirection,
      flexWrap,
      justifyContent,
      margin,
      marginHorizontal = margin,
      marginVertical = margin,
      marginBottom = marginVertical,
      marginLeft = marginHorizontal,
      marginRight = marginHorizontal,
      marginTop = marginVertical,
      padding,
      paddingHorizontal = padding,
      paddingVertical = padding,
      paddingBottom = paddingVertical,
      paddingLeft = paddingHorizontal,
      paddingRight = paddingHorizontal,
      paddingTop = paddingVertical,
      ...others
    } = this.props;

    const classNames = cx(
      theme['box'],
      {
        [theme[`align-content-${alignContent}`]]: alignContent,
        [theme[`align-items-${alignItems}`]]: alignItems,
        [theme[`align-self-${alignSelf}`]]: alignSelf,
        [theme[`display-${display}`]]: display,
        [theme[`flex-direction-${flexDirection}`]]: flexDirection,
        [theme[`flex-wrap-${flexWrap}`]]: flexWrap,
        [theme[`justify-content-${justifyContent}`]]: justifyContent,
        [theme[`margin-bottom-${marginBottom}`]]: marginBottom > 0,
        [theme[`margin-left-${marginLeft}`]]: marginLeft > 0,
        [theme[`margin-right-${marginRight}`]]: marginRight > 0,
        [theme[`margin-top-${marginTop}`]]: marginTop > 0,
        [theme[`padding-bottom-${paddingBottom}`]]: paddingBottom > 0,
        [theme[`padding-left-${paddingLeft}`]]: paddingLeft > 0,
        [theme[`padding-right-${paddingRight}`]]: paddingRight > 0,
        [theme[`padding-top-${paddingTop}`]]: paddingTop > 0,
      },
      className,
    );

    const styles = {
      ...flex && {flex},
    };

    const Element = element;

    return (
      <Element className={classNames} style={styles} {...others}>
        {children}
      </Element>
    );
  }
}

export default Box;
<commit_msg>Implemented flexBasis prop<commit_after>import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import theme from './theme.css';

const spacings = [0, 1, 2, 3, 4, 5, 6, 7, 8];

class Box extends PureComponent {
  static propTypes = {
    alignContent: PropTypes.oneOf([
      'center',
      'flex-start',
      'flex-end',
      'space-around',
      'space-between',
      'space-evenly',
    ]),
    alignItems: PropTypes.oneOf(['center', 'flex-start', 'flex-end']),
    alignSelf: PropTypes.oneOf(['center', 'flex-start', 'flex-end', 'stretch']),
    children: PropTypes.any,
    className: PropTypes.string,
    display: PropTypes.oneOf(['inline', 'inline-block', 'block', 'flex', 'inline-flex']),
    element: PropTypes.node,
    flex: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
    flexBasis: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
    flexDirection: PropTypes.oneOf(['row', 'row-reverse', 'column', 'column-reverse']),
    flexWrap: PropTypes.oneOf(['nowrap', 'wrap', 'wrap-reverse']),
    justifyContent: PropTypes.oneOf([
      'center',
      'flex-start',
      'flex-end',
      'space-around',
      'space-between',
      'space-evenly',
    ]),
    margin: PropTypes.oneOf(spacings),
    marginHorizontal: PropTypes.oneOf(spacings),
    marginVertical: PropTypes.oneOf(spacings),
    marginBottom: PropTypes.oneOf(spacings),
    marginLeft: PropTypes.oneOf(spacings),
    marginRight: PropTypes.oneOf(spacings),
    marginTop: PropTypes.oneOf(spacings),
    padding: PropTypes.oneOf(spacings),
    paddingHorizontal: PropTypes.oneOf(spacings),
    paddingVertical: PropTypes.oneOf(spacings),
    paddingBottom: PropTypes.oneOf(spacings),
    paddingLeft: PropTypes.oneOf(spacings),
    paddingRight: PropTypes.oneOf(spacings),
    paddingTop: PropTypes.oneOf(spacings),
  };

  static defaultProps = {
    element: 'div',
    margin: 0,
    padding: 0,
  };

  render() {
    const {
      alignContent,
      alignItems,
      alignSelf,
      children,
      className,
      display,
      element,
      flex,
      flexBasis,
      flexDirection,
      flexWrap,
      justifyContent,
      margin,
      marginHorizontal = margin,
      marginVertical = margin,
      marginBottom = marginVertical,
      marginLeft = marginHorizontal,
      marginRight = marginHorizontal,
      marginTop = marginVertical,
      padding,
      paddingHorizontal = padding,
      paddingVertical = padding,
      paddingBottom = paddingVertical,
      paddingLeft = paddingHorizontal,
      paddingRight = paddingHorizontal,
      paddingTop = paddingVertical,
      ...others
    } = this.props;

    const classNames = cx(
      theme['box'],
      {
        [theme[`align-content-${alignContent}`]]: alignContent,
        [theme[`align-items-${alignItems}`]]: alignItems,
        [theme[`align-self-${alignSelf}`]]: alignSelf,
        [theme[`display-${display}`]]: display,
        [theme[`flex-direction-${flexDirection}`]]: flexDirection,
        [theme[`flex-wrap-${flexWrap}`]]: flexWrap,
        [theme[`justify-content-${justifyContent}`]]: justifyContent,
        [theme[`margin-bottom-${marginBottom}`]]: marginBottom > 0,
        [theme[`margin-left-${marginLeft}`]]: marginLeft > 0,
        [theme[`margin-right-${marginRight}`]]: marginRight > 0,
        [theme[`margin-top-${marginTop}`]]: marginTop > 0,
        [theme[`padding-bottom-${paddingBottom}`]]: paddingBottom > 0,
        [theme[`padding-left-${paddingLeft}`]]: paddingLeft > 0,
        [theme[`padding-right-${paddingRight}`]]: paddingRight > 0,
        [theme[`padding-top-${paddingTop}`]]: paddingTop > 0,
      },
      className,
    );

    const styles = {
      ...(flex && { flex }),
      ...(flexBasis && { flexBasis }),
    };

    const Element = element;

    return (
      <Element className={classNames} style={styles} {...others}>
        {children}
      </Element>
    );
  }
}

export default Box;
<|endoftext|>


<commit_before>var SteamUser = require('../index.js');
var Steam = require('steam-client');
var SteamID = require('steamid');
var ByteBuffer = require('bytebuffer');

/**
 * Set your persona online state and optionally name.
 * @param {EPersonaState} state - Your new online state
 * @param {string} [name] - Optional. Set a new profile name.
 */
SteamUser.prototype.setPersona = function(state, name) {
	this._send(Steam.EMsg.ClientChangeStatus, {
		"persona_state": state,
		"player_name": name
	});
};

/**
 * Send (or accept) a friend invitiation.
 * @param {(SteamID|string)} steamID - Either a SteamID object of the user to add, or a string which can parse into one.
 */
SteamUser.prototype.addFriend = function(steamID) {
	if(typeof steamID === 'string') {
		steamID = new SteamID(steamID);
	}

	this._send(Steam.EMsg.ClientAddFriend, {"steamid_to_add": steamID.getSteamID64()});
};

/**
 * Remove a friend from your friends list (or decline an invitiation)
 * @param {(SteamID|string)} steamID - Either a SteamID object of the user to remove, or a string which can parse into one.
 */
SteamUser.prototype.removeFriend = function(steamID) {
	if(typeof steamID === 'string') {
		steamID = new SteamID(steamID);
	}

	this._send(Steam.EMsg.ClientRemoveFriend, {"friendid": steamID.getSteamID64()});
};

/**
 * Block all communication with a user.
 * @param {(SteamID|string)} steamID - Either a SteamID object of the user to block, or a string which can parse into one.
 * @param {function} [callback] - Optional. Called with an `eresult` parameter on completion.
 */
SteamUser.prototype.blockUser = function(steamID, callback) {
	if(typeof steamID === 'string') {
		steamID = new SteamID(steamID);
	}

	var buffer = new ByteBuffer(17, ByteBuffer.LITTLE_ENDIAN);
	buffer.writeUint64(this.steamID.getSteamID64());
	buffer.writeUint64(steamID.getSteamID64());
	buffer.writeUint8(1);

	this._send(Steam.EMsg.ClientSetIgnoreFriend, buffer.flip(), function(body) {
		if(!callback) {
			return; // ignore
		}

		body.readUint64(); // unknown
		callback(body.readUint32());
	});
};

/**
 * Unblock all communication with a user.
 * @param {(SteamID|string)} steamID - Either a SteamID object of the user to unblock, or a string which can parse into one.
 * @param {function} [callback] - Optional. Called with an `eresult` parameter on completion.
 */
SteamUser.prototype.unblockUser = function(steamID, callback) {
	if(typeof steamID === 'string') {
		steamID = new SteamID(steamID);
	}

	var buffer = new ByteBuffer(17, ByteBuffer.LITTLE_ENDIAN);
	buffer.writeUint64(this.steamID.getSteamID64());
	buffer.writeUint64(steamID.getSteamID64());
	buffer.writeUint8(0);

	this._send(Steam.EMsg.ClientSetIgnoreFriend, buffer.flip(), function(body) {
		if(!callback) {
			return; // ignore
		}

		body.readUint64(); // unknown
		callback(body.readUint32());
	});
};

/**
 * Requests information about one or more user profiles.
 * @param {(SteamID[]|string[])} steamids - An array of SteamID objects or strings which can parse into them.
 * @param {function} [callback] - Optional. Called with an object whose keys are 64-bit SteamIDs as strings, and whose values are persona objects.
 */
SteamUser.prototype.getPersonas = function(steamids, callback) {
	var Flags = Steam.EClientPersonaStateFlag;
	var flags = Flags.Status|Flags.PlayerName|Flags.QueryPort|Flags.SourceID|Flags.Presence|
		Flags.Metadata|Flags.LastSeen|Flags.ClanInfo|Flags.GameExtraInfo|Flags.GameDataBlob|
		Flags.ClanTag|Flags.Facebook;

	var ids = steamids.map(function(id) {
		if(typeof id === 'string') {
			return (new SteamID(id)).getSteamID64();
		}

		return id.toString();
	});

	this._send(Steam.EMsg.ClientRequestFriendData, {
		"friends": ids,
		"persona_state_requested": flags
	});

	if(callback) {
		var output = {};

		var self = this;
		ids.forEach(function(id) {
			self.once('user#' + id, receive);
		});

		function receive(sid, user) {
			var sid64 = sid.getSteamID64();
			output[sid64] = user;

			var index = ids.indexOf(sid64);
			if(index != -1) {
				ids.splice(index, 1);
			}

			if(ids.length === 0) {
				callback(output);
			}
		}
	}
};

/**
 * Gets the Steam Level of one or more Steam users.
 * @param {(SteamID[]|string[])} steamids - An array of SteamID objects, or strings which can parse into one.
 * @param {function} callback - Called on completion with an object whose keys are 64-bit SteamIDs as strings, and whose values are Steam Level numbers.
 */
SteamUser.prototype.getSteamLevels = function(steamids, callback) {
	var accountids = steamids.map(function(steamID) {
		if(typeof steamID === 'string') {
			return (new SteamID(steamID)).accountid;
		} else {
			return steamID.accountid;
		}
	});

	this._send(Steam.EMsg.ClientFSGetFriendsSteamLevels, {"accountids": accountids}, function(body) {
		var output = {};

		var sid = new SteamID();
		sid.universe = SteamID.Universe.PUBLIC;
		sid.type = SteamID.Type.INDIVIDUAL;
		sid.instance = SteamID.Instance.DESKTOP;

		(body.friends || []).forEach(function(user) {
			sid.accountid = user.accountid;
			output[sid.getSteamID64()] = user.level;
		});

		callback(output);
	});
};

// Handlers

SteamUser.prototype._handlers[Steam.EMsg.ClientPersonaState] = function(body) {
	var self = this;
	body.friends.forEach(function(user) {
		var sid = new SteamID(user.friendid.toString());
		var sid64 = sid.getSteamID64();
		delete user.friendid;

		var i;
		if(!self.users[sid64]) {
			self.users[sid64] = user;
			processUser(self.users[sid64]);
		} else {
			// Replace unknown data in the received object with already-known data
			for(i in self.users[sid64]) {
				if(self.users[sid64].hasOwnProperty(i) && user.hasOwnProperty(i) && user[i] === null) {
					user[i] = self.users[sid64][i];
				}
			}
		}

		processUser(user);

		/**
		 * Emitted when we receive persona info about a user.
		 * You can also listen for user#steamid64 to get info only for a specific user.
		 *
		 * @event SteamUser#user
		 * @param {SteamID} steamID - The SteamID of the user
		 * @param {Object} user - An object containing the user's persona info
		 */

		self._emitIdEvent('user', sid, user);

		if(user.gameid) {
			self._addAppToCache(user.gameid);
		}

		for(i in user) {
			if(user.hasOwnProperty(i) && user[i] !== null) {
				self.users[sid][i] = user[i];
			}
		}
	});
};

SteamUser.prototype._handlers[Steam.EMsg.ClientClanState] = function(body) {
	var sid = new SteamID(body.steamid_clan.toString());
	var sid64 = sid.getSteamID64();
	delete body.steamid_clan;

	var i;
	if(!this.groups[sid64]) {
		this.groups[sid64] = body;
	} else {
		// Replace unknown data in the received object with already-known data
		for(i in this.groups[sid64]) {
			if(this.groups[sid64].hasOwnProperty(i) && body.hasOwnProperty(i) && body[i] === null) {
				body[i] = this.groups[sid64][i];
			}
		}
	}

	/**
	 * Emitted when we receive info about a Steam group.
	 * You can also listen for group#steamid64 to get info only for a specific group.
	 *
	 * @event SteamUser#group
	 * @param {SteamID} steamID - The SteamID of the group
	 * @param {Object} user - An object containing the group's info
	 */

	this._emitIdEvent('group', sid, body);

	for(i in body) {
		if(body.hasOwnProperty(i) && body[i] !== null) {
			this.groups[sid64][i] = body[i];
		}
	}

	var self = this;
	(body.events || []).forEach(function(event) {
		if(!event.just_posted) {
			return;
		}

		/**
		 * Emitted when a new event is posted to a Steam group.
		 * You can also listen for groupEvent#steamid64 to get events only for a specific group.
		 *
		 * @event SteamUser#groupEvent
		 * @param {SteamID} steamID - The SteamID of the group
		 * @param {string} headline - The title of the event
		 * @param {Date} timestamp - The time when the event will start
		 * @param {string} gid - The event's GID
		 * @param {number} gameID - If this is an event for a game, this is the game's appid
		 */

		self._emitIdEvent('groupEvent', sid, event.headline, new Date(event.event_time * 1000), event.gid.toString(), event.game_id.toNumber());
	});

	(body.announcements || []).forEach(function(announcement) {
		if(!announcement.just_posted) {
			return;
		}

		/**
		 * Emitted when a new announcement is posted to a Steam group.
		 * You can also listen for groupAnnouncement#steamid64 to get announcements only for a specific group.
		 *
		 * @event SteamUser#groupAnnouncement
		 * @param {SteamID} steamID - The SteamID of the group
		 * @param {string} headline - The title of the announcement
		 * @param {string} gid - The announcement's GID
		 */

		self._emitIdEvent('groupAnnouncement', sid, announcement.headline, announcement.gid.toString());
	});
};

SteamUser.prototype._handlers[Steam.EMsg.ClientFriendsList] = function(body) {
	var self = this;
	(body.friends || []).forEach(function(relationship) {
		var sid = new SteamID(relationship.ulfriendid.toString());
		var key = sid.type == SteamID.Type.CLAN ? 'myGroups' : 'myFriends';

		if(body.bincremental) {
			/**
			 * Emitted when a relationship with a Steam group changes. The relationship in myGroups is updated after this is emitted.
			 *
			 * @event SteamUser#groupRelationship
			 * @param {SteamID} steamID - The SteamID of the group
			 * @param {EFriendRelationship} relationship - Your new relationship with the group
			 */

			/**
			 * Emitted when a relationship with a Steam user changes. The relationship in myFriendsis updated after this is emitted.
			 *
			 * @event SteamUser#friendRelationship
			 * @param {SteamID} steamID - The SteamID of the group
			 * @param {EFriendRelationship} relationship - Your new relationship with the user
			 */

			// This isn't an initial download of the friends list, something changed
			self._emitIdEvent(key == 'myGroups' ? 'groupRelationship' : 'friendRelationship', sid, relationship.efriendrelationship);
		}

		if(relationship.efriendrelationship == Steam.EFriendRelationship.None) {
			delete self[key][sid.getSteamID64()];
		} else {
			self[key][sid.getSteamID64()] = relationship.efriendrelationship;
		}
	});

	if(!body.bincremental) {
		/**
		 * Emitted when our entire friends list is loaded.
		 *
		 * @event SteamUser#friendsList
		 */

		/**
		 * Emitted when our entire group list is loaded.
		 *
		 * @event SteamUser#groupList
		 */

		this.emit('friendsList');
		this.emit('groupList');
	}
};

SteamUser.prototype._handlers[Steam.EMsg.ClientFriendsGroupsList] = function(body) {
	var groupList = {};

	body.friendGroups.forEach(function (group) {
		groupList[group.nGroupID] = {
			"name": group.strGroupName,
			"members": []
		};
	});

	body.memberships.forEach(function(friend) {
		var sid = new SteamID(friend.ulSteamID.toString());

		groupList[friend.nGroupID].members.push(sid);

		if (body.bincremental) {
			// For now it doesn't really fire, so can't really check on how to do remove / add stuff with an emit.
		}
	});

	if (!body.bincremental) {
		/**
		 * Emitted when our entire friends group list is loaded.
		 *
		 * @event SteamUser#friendsGroupList
		 */

		this.emit('friendsGroupList', groupList);
	}

	this.myFriendGroups = groupList;
};

SteamUser.prototype._handlers[Steam.EMsg.ClientPlayerNicknameList] = function(body) {
	// TODO
	//console.log(body);
};

function processUser(user) {
	if(typeof user.gameid === 'object' && user.gameid !== null) {
		user.gameid = user.gameid.toNumber();
	}

	if(typeof user.last_logoff === 'number') {
		user.last_logoff = new Date(user.last_logoff * 1000);
	}

	if(typeof user.last_logon === 'number') {
		user.last_logon = new Date(user.last_logon * 1000);
	}

	if (typeof user.avatar_hash !== 'undefined') {
		var hash = user.avatar_hash.toString('hex');
		user.avatar_url_icon = "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/" + hash.substring(0, 2) + "/" + hash;
		user.avatar_url_medium = user.avatar_url_icon + "_medium.jpg";
		user.avatar_url_full = user.avatar_url_icon + "_full.jpg";
		user.avatar_url_icon += ".jpg";
	}
}
<commit_msg>Fixed potential crash if a user's data doesn't include an avatar hash<commit_after>var SteamUser = require('../index.js');
var Steam = require('steam-client');
var SteamID = require('steamid');
var ByteBuffer = require('bytebuffer');

/**
 * Set your persona online state and optionally name.
 * @param {EPersonaState} state - Your new online state
 * @param {string} [name] - Optional. Set a new profile name.
 */
SteamUser.prototype.setPersona = function(state, name) {
	this._send(Steam.EMsg.ClientChangeStatus, {
		"persona_state": state,
		"player_name": name
	});
};

/**
 * Send (or accept) a friend invitiation.
 * @param {(SteamID|string)} steamID - Either a SteamID object of the user to add, or a string which can parse into one.
 */
SteamUser.prototype.addFriend = function(steamID) {
	if(typeof steamID === 'string') {
		steamID = new SteamID(steamID);
	}

	this._send(Steam.EMsg.ClientAddFriend, {"steamid_to_add": steamID.getSteamID64()});
};

/**
 * Remove a friend from your friends list (or decline an invitiation)
 * @param {(SteamID|string)} steamID - Either a SteamID object of the user to remove, or a string which can parse into one.
 */
SteamUser.prototype.removeFriend = function(steamID) {
	if(typeof steamID === 'string') {
		steamID = new SteamID(steamID);
	}

	this._send(Steam.EMsg.ClientRemoveFriend, {"friendid": steamID.getSteamID64()});
};

/**
 * Block all communication with a user.
 * @param {(SteamID|string)} steamID - Either a SteamID object of the user to block, or a string which can parse into one.
 * @param {function} [callback] - Optional. Called with an `eresult` parameter on completion.
 */
SteamUser.prototype.blockUser = function(steamID, callback) {
	if(typeof steamID === 'string') {
		steamID = new SteamID(steamID);
	}

	var buffer = new ByteBuffer(17, ByteBuffer.LITTLE_ENDIAN);
	buffer.writeUint64(this.steamID.getSteamID64());
	buffer.writeUint64(steamID.getSteamID64());
	buffer.writeUint8(1);

	this._send(Steam.EMsg.ClientSetIgnoreFriend, buffer.flip(), function(body) {
		if(!callback) {
			return; // ignore
		}

		body.readUint64(); // unknown
		callback(body.readUint32());
	});
};

/**
 * Unblock all communication with a user.
 * @param {(SteamID|string)} steamID - Either a SteamID object of the user to unblock, or a string which can parse into one.
 * @param {function} [callback] - Optional. Called with an `eresult` parameter on completion.
 */
SteamUser.prototype.unblockUser = function(steamID, callback) {
	if(typeof steamID === 'string') {
		steamID = new SteamID(steamID);
	}

	var buffer = new ByteBuffer(17, ByteBuffer.LITTLE_ENDIAN);
	buffer.writeUint64(this.steamID.getSteamID64());
	buffer.writeUint64(steamID.getSteamID64());
	buffer.writeUint8(0);

	this._send(Steam.EMsg.ClientSetIgnoreFriend, buffer.flip(), function(body) {
		if(!callback) {
			return; // ignore
		}

		body.readUint64(); // unknown
		callback(body.readUint32());
	});
};

/**
 * Requests information about one or more user profiles.
 * @param {(SteamID[]|string[])} steamids - An array of SteamID objects or strings which can parse into them.
 * @param {function} [callback] - Optional. Called with an object whose keys are 64-bit SteamIDs as strings, and whose values are persona objects.
 */
SteamUser.prototype.getPersonas = function(steamids, callback) {
	var Flags = Steam.EClientPersonaStateFlag;
	var flags = Flags.Status|Flags.PlayerName|Flags.QueryPort|Flags.SourceID|Flags.Presence|
		Flags.Metadata|Flags.LastSeen|Flags.ClanInfo|Flags.GameExtraInfo|Flags.GameDataBlob|
		Flags.ClanTag|Flags.Facebook;

	var ids = steamids.map(function(id) {
		if(typeof id === 'string') {
			return (new SteamID(id)).getSteamID64();
		}

		return id.toString();
	});

	this._send(Steam.EMsg.ClientRequestFriendData, {
		"friends": ids,
		"persona_state_requested": flags
	});

	if(callback) {
		var output = {};

		var self = this;
		ids.forEach(function(id) {
			self.once('user#' + id, receive);
		});

		function receive(sid, user) {
			var sid64 = sid.getSteamID64();
			output[sid64] = user;

			var index = ids.indexOf(sid64);
			if(index != -1) {
				ids.splice(index, 1);
			}

			if(ids.length === 0) {
				callback(output);
			}
		}
	}
};

/**
 * Gets the Steam Level of one or more Steam users.
 * @param {(SteamID[]|string[])} steamids - An array of SteamID objects, or strings which can parse into one.
 * @param {function} callback - Called on completion with an object whose keys are 64-bit SteamIDs as strings, and whose values are Steam Level numbers.
 */
SteamUser.prototype.getSteamLevels = function(steamids, callback) {
	var accountids = steamids.map(function(steamID) {
		if(typeof steamID === 'string') {
			return (new SteamID(steamID)).accountid;
		} else {
			return steamID.accountid;
		}
	});

	this._send(Steam.EMsg.ClientFSGetFriendsSteamLevels, {"accountids": accountids}, function(body) {
		var output = {};

		var sid = new SteamID();
		sid.universe = SteamID.Universe.PUBLIC;
		sid.type = SteamID.Type.INDIVIDUAL;
		sid.instance = SteamID.Instance.DESKTOP;

		(body.friends || []).forEach(function(user) {
			sid.accountid = user.accountid;
			output[sid.getSteamID64()] = user.level;
		});

		callback(output);
	});
};

// Handlers

SteamUser.prototype._handlers[Steam.EMsg.ClientPersonaState] = function(body) {
	var self = this;
	body.friends.forEach(function(user) {
		var sid = new SteamID(user.friendid.toString());
		var sid64 = sid.getSteamID64();
		delete user.friendid;

		var i;
		if(!self.users[sid64]) {
			self.users[sid64] = user;
			processUser(self.users[sid64]);
		} else {
			// Replace unknown data in the received object with already-known data
			for(i in self.users[sid64]) {
				if(self.users[sid64].hasOwnProperty(i) && user.hasOwnProperty(i) && user[i] === null) {
					user[i] = self.users[sid64][i];
				}
			}
		}

		processUser(user);

		/**
		 * Emitted when we receive persona info about a user.
		 * You can also listen for user#steamid64 to get info only for a specific user.
		 *
		 * @event SteamUser#user
		 * @param {SteamID} steamID - The SteamID of the user
		 * @param {Object} user - An object containing the user's persona info
		 */

		self._emitIdEvent('user', sid, user);

		if(user.gameid) {
			self._addAppToCache(user.gameid);
		}

		for(i in user) {
			if(user.hasOwnProperty(i) && user[i] !== null) {
				self.users[sid][i] = user[i];
			}
		}
	});
};

SteamUser.prototype._handlers[Steam.EMsg.ClientClanState] = function(body) {
	var sid = new SteamID(body.steamid_clan.toString());
	var sid64 = sid.getSteamID64();
	delete body.steamid_clan;

	var i;
	if(!this.groups[sid64]) {
		this.groups[sid64] = body;
	} else {
		// Replace unknown data in the received object with already-known data
		for(i in this.groups[sid64]) {
			if(this.groups[sid64].hasOwnProperty(i) && body.hasOwnProperty(i) && body[i] === null) {
				body[i] = this.groups[sid64][i];
			}
		}
	}

	/**
	 * Emitted when we receive info about a Steam group.
	 * You can also listen for group#steamid64 to get info only for a specific group.
	 *
	 * @event SteamUser#group
	 * @param {SteamID} steamID - The SteamID of the group
	 * @param {Object} user - An object containing the group's info
	 */

	this._emitIdEvent('group', sid, body);

	for(i in body) {
		if(body.hasOwnProperty(i) && body[i] !== null) {
			this.groups[sid64][i] = body[i];
		}
	}

	var self = this;
	(body.events || []).forEach(function(event) {
		if(!event.just_posted) {
			return;
		}

		/**
		 * Emitted when a new event is posted to a Steam group.
		 * You can also listen for groupEvent#steamid64 to get events only for a specific group.
		 *
		 * @event SteamUser#groupEvent
		 * @param {SteamID} steamID - The SteamID of the group
		 * @param {string} headline - The title of the event
		 * @param {Date} timestamp - The time when the event will start
		 * @param {string} gid - The event's GID
		 * @param {number} gameID - If this is an event for a game, this is the game's appid
		 */

		self._emitIdEvent('groupEvent', sid, event.headline, new Date(event.event_time * 1000), event.gid.toString(), event.game_id.toNumber());
	});

	(body.announcements || []).forEach(function(announcement) {
		if(!announcement.just_posted) {
			return;
		}

		/**
		 * Emitted when a new announcement is posted to a Steam group.
		 * You can also listen for groupAnnouncement#steamid64 to get announcements only for a specific group.
		 *
		 * @event SteamUser#groupAnnouncement
		 * @param {SteamID} steamID - The SteamID of the group
		 * @param {string} headline - The title of the announcement
		 * @param {string} gid - The announcement's GID
		 */

		self._emitIdEvent('groupAnnouncement', sid, announcement.headline, announcement.gid.toString());
	});
};

SteamUser.prototype._handlers[Steam.EMsg.ClientFriendsList] = function(body) {
	var self = this;
	(body.friends || []).forEach(function(relationship) {
		var sid = new SteamID(relationship.ulfriendid.toString());
		var key = sid.type == SteamID.Type.CLAN ? 'myGroups' : 'myFriends';

		if(body.bincremental) {
			/**
			 * Emitted when a relationship with a Steam group changes. The relationship in myGroups is updated after this is emitted.
			 *
			 * @event SteamUser#groupRelationship
			 * @param {SteamID} steamID - The SteamID of the group
			 * @param {EFriendRelationship} relationship - Your new relationship with the group
			 */

			/**
			 * Emitted when a relationship with a Steam user changes. The relationship in myFriendsis updated after this is emitted.
			 *
			 * @event SteamUser#friendRelationship
			 * @param {SteamID} steamID - The SteamID of the group
			 * @param {EFriendRelationship} relationship - Your new relationship with the user
			 */

			// This isn't an initial download of the friends list, something changed
			self._emitIdEvent(key == 'myGroups' ? 'groupRelationship' : 'friendRelationship', sid, relationship.efriendrelationship);
		}

		if(relationship.efriendrelationship == Steam.EFriendRelationship.None) {
			delete self[key][sid.getSteamID64()];
		} else {
			self[key][sid.getSteamID64()] = relationship.efriendrelationship;
		}
	});

	if(!body.bincremental) {
		/**
		 * Emitted when our entire friends list is loaded.
		 *
		 * @event SteamUser#friendsList
		 */

		/**
		 * Emitted when our entire group list is loaded.
		 *
		 * @event SteamUser#groupList
		 */

		this.emit('friendsList');
		this.emit('groupList');
	}
};

SteamUser.prototype._handlers[Steam.EMsg.ClientFriendsGroupsList] = function(body) {
	var groupList = {};

	body.friendGroups.forEach(function (group) {
		groupList[group.nGroupID] = {
			"name": group.strGroupName,
			"members": []
		};
	});

	body.memberships.forEach(function(friend) {
		var sid = new SteamID(friend.ulSteamID.toString());

		groupList[friend.nGroupID].members.push(sid);

		if (body.bincremental) {
			// For now it doesn't really fire, so can't really check on how to do remove / add stuff with an emit.
		}
	});

	if (!body.bincremental) {
		/**
		 * Emitted when our entire friends group list is loaded.
		 *
		 * @event SteamUser#friendsGroupList
		 */

		this.emit('friendsGroupList', groupList);
	}

	this.myFriendGroups = groupList;
};

SteamUser.prototype._handlers[Steam.EMsg.ClientPlayerNicknameList] = function(body) {
	// TODO
	//console.log(body);
};

function processUser(user) {
	if(typeof user.gameid === 'object' && user.gameid !== null) {
		user.gameid = user.gameid.toNumber();
	}

	if(typeof user.last_logoff === 'number') {
		user.last_logoff = new Date(user.last_logoff * 1000);
	}

	if(typeof user.last_logon === 'number') {
		user.last_logon = new Date(user.last_logon * 1000);
	}

	if (typeof user.avatar_hash === 'object' && Buffer.isBuffer(user.avatar_hash)) {
		var hash = user.avatar_hash.toString('hex');
		user.avatar_url_icon = "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/" + hash.substring(0, 2) + "/" + hash;
		user.avatar_url_medium = user.avatar_url_icon + "_medium.jpg";
		user.avatar_url_full = user.avatar_url_icon + "_full.jpg";
		user.avatar_url_icon += ".jpg";
	}
}
<|endoftext|>


<commit_before>/** @jsxImportSource theme-ui */

// gallery

import Image from 'next/image'
import { useQuery, gql } from '@apollo/client'
import { Box } from 'theme-ui'
import { useRouter } from 'next/router'
import { Grid } from 'mauerwerk'
import useMeasure from 'react-use-measure'
import Loading from './loading'
import Squiggle from './squiggle'

// lightbox
import SimpleReactLightbox, {
  SRLWrapper,
  useLightbox,
} from 'simple-react-lightbox'

const QUERY = gql`
  query ($title: String) {
    galleryCollection(where: { title: $title }) {
      items {
        title
        imagesCollection {
          items {
            url(transform: { width: 1200, quality: 70 })
            loader: url(transform: { width: 5, quality: 1 })
            title
            width
            height
          }
        }
      }
    }
  }
`

export default function Gallery(props) {
  const router = useRouter()
  const pathname = router.asPath
  const [ref, bounds] = useMeasure()
  const { openLightbox } = useLightbox()

  const { data, loading, error } = useQuery(QUERY, {
    variables: {
      title: props.imageset,
    },
  })

  if (loading) {
    return (
      <Box sx={{ width: 'fit-content', margin: '0 auto' }}>
        <Loading />
        <Squiggle />
      </Box>
    )
  }

  if (error) {
    console.error(error)
    return null
  }

  const imageSetImages = data.galleryCollection.items[0].imagesCollection.items

  const options = {
    settings: {
      overlayColor: 'rgba(0, 0, 0, 0.9)',
      autoplaySpeed: 0,
      hideControlsAfter: false,
      disablePanzoom: true,
    },
    buttons: {
      backgroundColor: 'white',
      iconColor: 'black',
      showDownloadButton: false,
    },
    caption: {
      showCaption: false,
    },
    thumbnails: {
      showThumbnails: false,
    },
  }

  const callbacks = {
    onSlideChange: object => handleSlideChange(object),
    onLightboxOpened: object => handleLightboxOpen(object),
    onLightboxClosed: object => handleLightboxClose(object),
  }

  function handleSlideChange(object) {
    if (typeof window !== 'undefined') {
      global.analytics.track('Lightbox Slide Changed', {
        src: object.slides.current.source,
        location: pathname,
        direction: object.action,
      })
    }

    return object
  }

  function handleLightboxOpen(object) {
    if (typeof window !== 'undefined') {
      global.analytics.track('Lightbox Opened', {
        src: object.currentSlide.source,
        location: pathname,
      })
    }

    return object
  }

  function handleLightboxClose(object) {
    if (typeof window !== 'undefined') {
      global.analytics.track('Lightbox Closed', {
        src: object.currentSlide.source,
        location: pathname,
      })
    }

    return object
  }

  const length = imageSetImages.length <= 5 ? imageSetImages.length : 5

  return (
    <Box ref={ref}>
      <SimpleReactLightbox>
        <SRLWrapper options={options} callbacks={callbacks}>
          <Grid
            className="grid"
            // Arbitrary data, should contain keys, possibly heights, etc.
            data={imageSetImages}
            // Key accessor, instructs grid on how to fet individual keys from the data set
            keys={d => d.title}
            // Can be a fixed value or an individual data accessor
            heights={d => {
              const aspect = props.ratio
                ? eval(props.ratio) * bounds.width
                : ((d.height / d.width) * bounds.width) / length
              console.log(aspect)
              return aspect
            }}
            columns={length}
            margin={16}
          >
            {data => (
              <Image
                src={data.url}
                alt={data.title}
                layout="fill"
                placeholder="blur"
                blurDataURL={data.loader}
                objectFit="cover"
                width={data.width}
                height={data.height}
                onClick={() => openLightbox(data.index)}
                className="gallery"
              />
            )}
          </Grid>
        </SRLWrapper>
      </SimpleReactLightbox>
    </Box>
  )
}
<commit_msg>fix: remove messy console log<commit_after>/** @jsxImportSource theme-ui */

// gallery

import Image from 'next/image'
import { useQuery, gql } from '@apollo/client'
import { Box } from 'theme-ui'
import { useRouter } from 'next/router'
import { Grid } from 'mauerwerk'
import useMeasure from 'react-use-measure'
import Loading from './loading'
import Squiggle from './squiggle'

// lightbox
import SimpleReactLightbox, {
  SRLWrapper,
  useLightbox,
} from 'simple-react-lightbox'

const QUERY = gql`
  query ($title: String) {
    galleryCollection(where: { title: $title }) {
      items {
        title
        imagesCollection {
          items {
            url(transform: { width: 1200, quality: 70 })
            loader: url(transform: { width: 5, quality: 1 })
            title
            width
            height
          }
        }
      }
    }
  }
`

export default function Gallery(props) {
  const router = useRouter()
  const pathname = router.asPath
  const [ref, bounds] = useMeasure()
  const { openLightbox } = useLightbox()

  const { data, loading, error } = useQuery(QUERY, {
    variables: {
      title: props.imageset,
    },
  })

  if (loading) {
    return (
      <Box sx={{ width: 'fit-content', margin: '0 auto' }}>
        <Loading />
        <Squiggle />
      </Box>
    )
  }

  if (error) {
    console.error(error)
    return null
  }

  const imageSetImages = data.galleryCollection.items[0].imagesCollection.items

  const options = {
    settings: {
      overlayColor: 'rgba(0, 0, 0, 0.9)',
      autoplaySpeed: 0,
      hideControlsAfter: false,
      disablePanzoom: true,
    },
    buttons: {
      backgroundColor: 'white',
      iconColor: 'black',
      showDownloadButton: false,
    },
    caption: {
      showCaption: false,
    },
    thumbnails: {
      showThumbnails: false,
    },
  }

  const callbacks = {
    onSlideChange: object => handleSlideChange(object),
    onLightboxOpened: object => handleLightboxOpen(object),
    onLightboxClosed: object => handleLightboxClose(object),
  }

  function handleSlideChange(object) {
    if (typeof window !== 'undefined') {
      global.analytics.track('Lightbox Slide Changed', {
        src: object.slides.current.source,
        location: pathname,
        direction: object.action,
      })
    }

    return object
  }

  function handleLightboxOpen(object) {
    if (typeof window !== 'undefined') {
      global.analytics.track('Lightbox Opened', {
        src: object.currentSlide.source,
        location: pathname,
      })
    }

    return object
  }

  function handleLightboxClose(object) {
    if (typeof window !== 'undefined') {
      global.analytics.track('Lightbox Closed', {
        src: object.currentSlide.source,
        location: pathname,
      })
    }

    return object
  }

  const length = imageSetImages.length <= 5 ? imageSetImages.length : 5

  return (
    <Box ref={ref}>
      <SimpleReactLightbox>
        <SRLWrapper options={options} callbacks={callbacks}>
          <Grid
            className="grid"
            // Arbitrary data, should contain keys, possibly heights, etc.
            data={imageSetImages}
            // Key accessor, instructs grid on how to fet individual keys from the data set
            keys={d => d.title}
            // Can be a fixed value or an individual data accessor
            heights={d => {
              const aspect = props.ratio
                ? eval(props.ratio) * bounds.width
                : ((d.height / d.width) * bounds.width) / length
              return aspect
            }}
            columns={length}
            margin={16}
          >
            {data => (
              <Image
                src={data.url}
                alt={data.title}
                layout="fill"
                placeholder="blur"
                blurDataURL={data.loader}
                objectFit="cover"
                width={data.width}
                height={data.height}
                onClick={() => openLightbox(data.index)}
                className="gallery"
              />
            )}
          </Grid>
        </SRLWrapper>
      </SimpleReactLightbox>
    </Box>
  )
}
<|endoftext|>


<commit_before>import React from 'react';
import ReactDOM from 'react-dom';

class ProductCategoryRow extends React.Component {
  render() {
    return (
{this.props.category}
);
  }
}

class ProductRow extends React.Component {
  render() {
    var name = this.props.product.stocked ?
      this.props.product.name :

        {this.props.product.name}
      ;
    return (

        {name}
        {this.props.product.price}

    );
  }
}

class ProductTable extends React.Component {
  render() {
    var rows = [];
    var lastCategory = null;
    this.props.products.forEach((product) => {
      if (product.name.indexOf(this.props.filterText) === -1 || (!product.stocked && this.props.inStockOnly)) {
        return;
      }
      if (product.category !== lastCategory) {
        rows.push(<ProductCategoryRow category={product.category} key={product.category} />);
      }
      rows.push(<ProductRow product={product} key={product.name} />);
      lastCategory = product.category;
    });
    return (

            Name
            Price

{rows}

    );
  }
}

class SearchBar extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
  }

  handleChange() {
    this.props.onUserInput(
      this.filterTextInput.value,
      this.inStockOnlyInput.checked
    );
  }

  render() {
    return (
      <form>
        <input
          type="text"
          placeholder="Search..."
          value={this.props.filterText}
          ref={(input) => this.filterTextInput = input}
          onChange={this.handleChange}
        />

          <input
            type = "checkbox"
            checked={this.props.inStockOnly}
            ref = {(input) => this.inStockOnlyInput = input}
            onChange={this.handleChange}
          />
          {' '}
          Only show products in stock

      </form>
    );
  }
}

class FilterableProductTable extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      filterText: '',
      inStockOnly: false
    };

    this.handleUserInput = this.handleUserInput.bind(this);
  }

  handleUserInput(filterText, inStockOnly) {
    this.setState({
      filterText: filterText,
      inStockOnly: inStockOnly
    });
  }

  render() {
    return (

        <SearchBar
          filterText={this.state.filterText}
          inStockOnly={this.state.inStockOnly}
          onUserInput={this.handleUserInput}
        />
        <ProductTable
          products={this.props.products}
          filterText={this.state.filterText}
          inStockOnly={this.state.inStockOnly}
        />

    );
  }
}

var PRODUCTS = [
  {category: 'Sporting Goods', price: '$49.99', stocked: true, name: 'Football'},
  {category: 'Sporting Goods', price: '$9.99', stocked: true, name: 'Baseball'},
  {category: 'Sporting Goods', price: '$29.99', stocked: false, name: 'Basketball'},
  {category: 'Electronics', price: '$99.99', stocked: true, name: 'iPod Touch'},
  {category: 'Electronics', price: '$399.99', stocked: false, name: 'iPhone 5'},
  {category: 'Electronics', price: '$199.99', stocked: true, name: 'Nexus 7'}
];

ReactDOM.render(
  <FilterableProductTable products={PRODUCTS} />,
  document.getElementById('root')
);

 export default search;
<commit_msg>Delete searchx.js<commit_after><|endoftext|>


<commit_before>var SteamUser = require('../index.js');
var ByteBuffer = require('bytebuffer');

SteamUser.formatCurrency = function(amount, currency) {
	amount = amount.toFixed(2);

	if(!SteamUser.CurrencyData[currency]) {
		return amount;
	}

	var data = SteamUser.CurrencyData[currency];

	if(data.whole) {
		amount = amount.replace('.00', '');
	}

	if(data.commas) {
		amount = amount.replace('.', ',');
	}

	return (data.prepend || '') + amount + (data.append || '');
};

/**
 * Same as emit() except the second argument (the first provided to the callback function) is a SteamID and will be appended to the event name with a hash.
 * @private
 */
SteamUser.prototype._emitIdEvent = function() {
	this.emit.apply(this, arguments);
	arguments[0] += '#' + arguments[1].getSteamID64();
	this.emit.apply(this, arguments);
};

/**
 * Generate a Steam-style TOTP authentication code.
 * @param {Buffer} secret - Your TOTP secret
 * @param {number} [timeOffset=0] - If you know how far off your clock is from the Steam servers, put the offset here in seconds
 * @returns {string}
 */
SteamUser.generateAuthCode = function(secret, timeOffset) {
	timeOffset = timeOffset || 0;

	var buffer = new ByteBuffer(8, ByteBuffer.LITTLE_ENDIAN);
	buffer.writeUint64(Math.floor((Math.floor(Date.now() / 1000) + timeOffset) / 30));
	buffer.flip().reverse();

	var hmac = require('crypto').createHmac('sha1', secret);
	hmac = hmac.update(buffer.toBuffer()).digest();

	var start = hmac[19] & 0x0F;
	hmac = ByteBuffer.wrap(hmac.slice(start, start + 4), ByteBuffer.BIG_ENDIAN);

	var fullcode = hmac.readUint32() & 0x7fffffff;

	var chars = '23456789BCDFGHJKMNPQRTVWXY';

	var code = '';
	for(var i = 0; i < 5; i++) {
		code += chars.charAt(fullcode % chars.length);
		fullcode /= chars.length;
	}

	return code;
};
<commit_msg>Refactored a bit of code in generateAuthCode<commit_after>var SteamUser = require('../index.js');
var ByteBuffer = require('bytebuffer');

SteamUser.formatCurrency = function(amount, currency) {
	amount = amount.toFixed(2);

	if(!SteamUser.CurrencyData[currency]) {
		return amount;
	}

	var data = SteamUser.CurrencyData[currency];

	if(data.whole) {
		amount = amount.replace('.00', '');
	}

	if(data.commas) {
		amount = amount.replace('.', ',');
	}

	return (data.prepend || '') + amount + (data.append || '');
};

/**
 * Same as emit() except the second argument (the first provided to the callback function) is a SteamID and will be appended to the event name with a hash.
 * @private
 */
SteamUser.prototype._emitIdEvent = function() {
	this.emit.apply(this, arguments);
	arguments[0] += '#' + arguments[1].getSteamID64();
	this.emit.apply(this, arguments);
};

/**
 * Generate a Steam-style TOTP authentication code.
 * @param {Buffer} secret - Your TOTP secret
 * @param {number} [timeOffset=0] - If you know how far off your clock is from the Steam servers, put the offset here in seconds
 * @returns {string}
 */
SteamUser.generateAuthCode = function(secret, timeOffset) {
	var time = Math.floor(Date.now() / 1000) + (timeOffset || 0);

	var buffer = new ByteBuffer(8, ByteBuffer.LITTLE_ENDIAN);
	buffer.writeUint64(Math.floor(time / 30));
	buffer.flip().reverse();

	var hmac = require('crypto').createHmac('sha1', secret);
	hmac = hmac.update(buffer.toBuffer()).digest();

	var start = hmac[19] & 0x0F;
	hmac = ByteBuffer.wrap(hmac.slice(start, start + 4), ByteBuffer.BIG_ENDIAN);

	var fullcode = hmac.readUint32() & 0x7fffffff;

	var chars = '23456789BCDFGHJKMNPQRTVWXY';

	var code = '';
	for(var i = 0; i < 5; i++) {
		code += chars.charAt(fullcode % chars.length);
		fullcode /= chars.length;
	}

	return code;
};
<|endoftext|>