<commit_before>/**
* Copyright 2012-2019, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var d3 = require('d3');
var Registry = require('../../registry');
var appendArrayPointValue = require('../../components/fx/helpers').appendArrayPointValue;
var Fx = require('../../components/fx');
var Lib = require('../../lib');
var Events = require('../../lib/events');

var helpers = require('./helpers');
var pieHelpers = require('../pie/helpers');

var formatValue = pieHelpers.formatPieValue;

module.exports = function attachFxHandlers(sliceTop, entry, gd, cd, opts) {
    var cd0 = cd[0];
    var trace = cd0.trace;
    var hierarchy = cd0.hierarchy;

    var isSunburst = trace.type === 'sunburst';
    var isTreemap = trace.type === 'treemap';

    // hover state vars
    // have we drawn a hover label, so it should be cleared later
    if(!('_hasHoverLabel' in trace)) trace._hasHoverLabel = false;
    // have we emitted a hover event, so later an unhover event should be emitted
    // note that click events do not depend on this - you can still get them
    // with hovermode: false or if you were earlier dragging, then clicked
    // in the same slice that you moused up in
    if(!('_hasHoverEvent' in trace)) trace._hasHoverEvent = false;

    var onMouseOver = function(pt) {
        var fullLayoutNow = gd._fullLayout;

        if(gd._dragging || fullLayoutNow.hovermode === false) return;

        var traceNow = gd._fullData[trace.index];
        var cdi = pt.data.data;
        var ptNumber = cdi.i;
        var isRoot = helpers.isHierarchyRoot(pt);
        var isEntry = helpers.isEntry(pt);
        var parent = isEntry ? pt._parent : pt.parent;
        var parentLabel = !parent ? '' : (parent.data && parent.data.data) ? parent.data.data.label : parent.label;

        var _cast = function(astr) {
            return Lib.castOption(traceNow, ptNumber, astr);
        };

        var hovertemplate = _cast('hovertemplate');
        var hoverinfo = Fx.castHoverinfo(traceNow, fullLayoutNow, ptNumber);
        var separators = fullLayoutNow.separators;

        if(hovertemplate || (hoverinfo && hoverinfo !== 'none' && hoverinfo !== 'skip')) {
            var hoverCenterX;
            var hoverCenterY;
            if(isSunburst) {
                hoverCenterX = cd0.cx + pt.pxmid[0] * (1 - pt.rInscribed);
                hoverCenterY = cd0.cy + pt.pxmid[1] * (1 - pt.rInscribed);
            }
            if(isTreemap) {
                hoverCenterX = pt._hoverX;
                hoverCenterY = pt._hoverY;
            }

            var hoverPt = {};
            var parts = [];
            var thisText = [];
            var hasFlag = function(flag) { return parts.indexOf(flag) !== -1; };
            var getVal = function(d) {
                if(d.hasOwnProperty('hierarchy')) return d.hierarchy.value;
                return d.hasOwnProperty('v') ? d.v : d.value;
            };

            if(hoverinfo) {
                parts = hoverinfo === 'all' ?
                    traceNow._module.attributes.hoverinfo.flags :
                    hoverinfo.split('+');
            }

            hoverPt.label = helpers.getLabelStr(cdi.label);
            if(hasFlag('label') && hoverPt.label) thisText.push(hoverPt.label);

            if(cdi.hasOwnProperty('v')) {
                hoverPt.value = cdi.v;
                hoverPt.valueLabel = formatValue(hoverPt.value, separators);
                if(hasFlag('value')) thisText.push(hoverPt.valueLabel);
            }

            hoverPt.currentPath = pt.currentPath = helpers.getPath(pt.data);
            if(hasFlag('current path') && !isRoot) {
                thisText.push(hoverPt.currentPath);
            }

            var tx;
            var allPercents = [];
            var insertPercent = function() {
                if(allPercents.indexOf(tx) === -1) { // no need to add redundant info
                    thisText.push(tx);
                    allPercents.push(tx);
                }
            };

            var val = getVal(cdi);

            var ref2 = parent;
            if(ref2) {
                hoverPt.percentParent = pt.percentParent = val / getVal(ref2);
                hoverPt.parent = pt.parent = helpers.getLabelString(parentLabel);
                if(hasFlag('percent parent')) {
                    tx = helpers.formatPercent(hoverPt.percentParent, separators) + ' of ' + hoverPt.parent;
                    insertPercent();
                }
            }

            var ref1 = entry;
            if(ref1) {
                hoverPt.percentEntry = pt.percentEntry = val / getVal(ref1);
                hoverPt.entry = pt.entry = helpers.getLabelString(ref1.data.data.label);
                if(hasFlag('percent entry') && !isRoot && !pt.onPathbar) {
                    tx = helpers.formatPercent(hoverPt.percentEntry, separators) + ' of ' + hoverPt.entry;
                    insertPercent();
                }
            }

            var ref0 = hierarchy;
            if(ref0) {
                hoverPt.percentRoot = pt.percentRoot = val / getVal(ref0);
                hoverPt.root = pt.root = helpers.getLabelString(ref0.data.data.label);
                if(hasFlag('percent root') && !isRoot) {
                    tx = helpers.formatPercent(hoverPt.percentRoot, separators) + ' of ' + hoverPt.root;
                    insertPercent();
                }
            }

            hoverPt.text = _cast('hovertext') || _cast('text');
            if(hasFlag('text')) {
                tx = hoverPt.text;
                if(Lib.isValidTextValue(tx)) thisText.push(tx);
            }

            var hoverItems = {
                trace: traceNow,
                y: hoverCenterY,
                text: thisText.join('
'),
                name: (hovertemplate || hasFlag('name')) ? traceNow.name : undefined,
                color: _cast('hoverlabel.bgcolor') || cdi.color,
                borderColor: _cast('hoverlabel.bordercolor'),
                fontFamily: _cast('hoverlabel.font.family'),
                fontSize: _cast('hoverlabel.font.size'),
                fontColor: _cast('hoverlabel.font.color'),
                nameLength: _cast('hoverlabel.namelength'),
                textAlign: _cast('hoverlabel.align'),
                hovertemplate: hovertemplate,
                hovertemplateLabels: hoverPt,
                eventData: [makeEventData(pt, traceNow, opts.eventDataKeys)]
            };

            if(isSunburst) {
                hoverItems.x0 = hoverCenterX - pt.rInscribed * pt.rpx1;
                hoverItems.x1 = hoverCenterX + pt.rInscribed * pt.rpx1;
                hoverItems.idealAlign = pt.pxmid[0] < 0 ? 'left' : 'right';
            }
            if(isTreemap) {
                hoverItems.x = hoverCenterX;
                hoverItems.idealAlign = hoverCenterX < 0 ? 'left' : 'right';
            }

            Fx.loneHover(hoverItems, {
                container: fullLayoutNow._hoverlayer.node(),
                outerContainer: fullLayoutNow._paper.node(),
                gd: gd
            });

            trace._hasHoverLabel = true;
        }

        if(isTreemap) {
            var slice = sliceTop.select('path.surface');
            opts.styleOne(slice, pt, traceNow, {
                hovered: true
            });
        }

        trace._hasHoverEvent = true;
        gd.emit('plotly_hover', {
            points: [makeEventData(pt, traceNow, opts.eventDataKeys)],
            event: d3.event
        });
    };

    var onMouseOut = function(evt) {
        var fullLayoutNow = gd._fullLayout;
        var traceNow = gd._fullData[trace.index];
        var pt = d3.select(this).datum();

        if(trace._hasHoverEvent) {
            evt.originalEvent = d3.event;
            gd.emit('plotly_unhover', {
                points: [makeEventData(pt, traceNow, opts.eventDataKeys)],
                event: d3.event
            });
            trace._hasHoverEvent = false;
        }

        if(trace._hasHoverLabel) {
            Fx.loneUnhover(fullLayoutNow._hoverlayer.node());
            trace._hasHoverLabel = false;
        }

        if(isTreemap) {
            var slice = sliceTop.select('path.surface');
            opts.styleOne(slice, pt, traceNow, {
                hovered: false
            });
        }
    };

    var onClick = function(pt) {
        // TODO: this does not support right-click. If we want to support it, we
        // would likely need to change pie to use dragElement instead of straight
        // mapbox event binding. Or perhaps better, make a simple wrapper with the
        // right mousedown, mousemove, and mouseup handlers just for a left/right click
        // mapbox would use this too.
        var fullLayoutNow = gd._fullLayout;
        var traceNow = gd._fullData[trace.index];

        var clickVal = Events.triggerHandler(gd, 'plotly_' + trace.type + 'click', {
            points: [makeEventData(pt, traceNow, opts.eventDataKeys)],
            event: d3.event
        });

        // 'regular' click event when sunburst/treemap click is disabled or when
        // clicking on leaves or the hierarchy root
        if(
            clickVal === false ||
            isSunburst && (
                helpers.isHierarchyRoot(pt) ||
                helpers.isLeaf(pt)
            )
        ) {
            if(fullLayoutNow.hovermode) {
                gd._hoverdata = [makeEventData(pt, traceNow, opts.eventDataKeys)];
                Fx.click(gd, d3.event);
            }
            return;
        }

        // skip if triggered from dragging a nearby cartesian subplot
        if(gd._dragging) return;

        // skip during transitions, to avoid potential bugs
        // we could remove this check later
        if(gd._transitioning) return;

        // store 'old' level in guiEdit stash, so that subsequent Plotly.react
        // calls with the same uirevision can start from the same entry
        Registry.call('_storeDirectGUIEdit', traceNow, fullLayoutNow._tracePreGUI[traceNow.uid], {
            level: traceNow.level
        });

        var id = helpers.getPtId(pt);
        var nextEntry = helpers.isEntry(pt) ?
            helpers.findEntryWithChild(hierarchy, id) :
            helpers.findEntryWithLevel(hierarchy, id);

        var frame = {
            data: [{level: helpers.getPtId(nextEntry)}],
            traces: [trace.index]
        };

        var animOpts = {
            frame: {
                redraw: false,
                duration: opts.transitionTime
            },
            transition: {
                duration: opts.transitionTime,
                easing: opts.transitionEasing
            },
            mode: 'immediate',
            fromcurrent: true
        };

        Fx.loneUnhover(fullLayoutNow._hoverlayer.node());
        Registry.call('animate', gd, frame, animOpts);
    };

    sliceTop.on('mouseover', onMouseOver);
    sliceTop.on('mouseout', onMouseOut);
    sliceTop.on('click', onClick);
};

function makeEventData(pt, trace, keys) {
    var cdi = pt.data.data;

    var out = {
        curveNumber: trace.index,
        pointNumber: cdi.i,
        data: trace._input,
        fullData: trace,

        // TODO more things like 'children', 'siblings', 'hierarchy?
    };

    for(var i = 0; i < keys.length; i++) {
        var key = keys[i];
        if(key in pt) out[key] = pt[key];
    }
    // handle special case of parent
    if('parent' in pt) out.parent = pt.parent;

    appendArrayPointValue(out, trace, cdi.i);

    return out;
}
<commit_msg>dont touch pt.parent<commit_after>/**
* Copyright 2012-2019, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var d3 = require('d3');
var Registry = require('../../registry');
var appendArrayPointValue = require('../../components/fx/helpers').appendArrayPointValue;
var Fx = require('../../components/fx');
var Lib = require('../../lib');
var Events = require('../../lib/events');

var helpers = require('./helpers');
var pieHelpers = require('../pie/helpers');

var formatValue = pieHelpers.formatPieValue;

module.exports = function attachFxHandlers(sliceTop, entry, gd, cd, opts) {
    var cd0 = cd[0];
    var trace = cd0.trace;
    var hierarchy = cd0.hierarchy;

    var isSunburst = trace.type === 'sunburst';
    var isTreemap = trace.type === 'treemap';

    // hover state vars
    // have we drawn a hover label, so it should be cleared later
    if(!('_hasHoverLabel' in trace)) trace._hasHoverLabel = false;
    // have we emitted a hover event, so later an unhover event should be emitted
    // note that click events do not depend on this - you can still get them
    // with hovermode: false or if you were earlier dragging, then clicked
    // in the same slice that you moused up in
    if(!('_hasHoverEvent' in trace)) trace._hasHoverEvent = false;

    var onMouseOver = function(pt) {
        var fullLayoutNow = gd._fullLayout;

        if(gd._dragging || fullLayoutNow.hovermode === false) return;

        var traceNow = gd._fullData[trace.index];
        var cdi = pt.data.data;
        var ptNumber = cdi.i;
        var isRoot = helpers.isHierarchyRoot(pt);
        var isEntry = helpers.isEntry(pt);
        var parent = isEntry ? pt._parent : pt.parent;
        var parentLabel = !parent ? '' : (parent.data && parent.data.data) ? parent.data.data.label : parent.label;

        var _cast = function(astr) {
            return Lib.castOption(traceNow, ptNumber, astr);
        };

        var hovertemplate = _cast('hovertemplate');
        var hoverinfo = Fx.castHoverinfo(traceNow, fullLayoutNow, ptNumber);
        var separators = fullLayoutNow.separators;

        if(hovertemplate || (hoverinfo && hoverinfo !== 'none' && hoverinfo !== 'skip')) {
            var hoverCenterX;
            var hoverCenterY;
            if(isSunburst) {
                hoverCenterX = cd0.cx + pt.pxmid[0] * (1 - pt.rInscribed);
                hoverCenterY = cd0.cy + pt.pxmid[1] * (1 - pt.rInscribed);
            }
            if(isTreemap) {
                hoverCenterX = pt._hoverX;
                hoverCenterY = pt._hoverY;
            }

            var hoverPt = {};
            var parts = [];
            var thisText = [];
            var hasFlag = function(flag) { return parts.indexOf(flag) !== -1; };
            var getVal = function(d) {
                if(d.hasOwnProperty('hierarchy')) return d.hierarchy.value;
                return d.hasOwnProperty('v') ? d.v : d.value;
            };

            if(hoverinfo) {
                parts = hoverinfo === 'all' ?
                    traceNow._module.attributes.hoverinfo.flags :
                    hoverinfo.split('+');
            }

            hoverPt.label = helpers.getLabelStr(cdi.label);
            if(hasFlag('label') && hoverPt.label) thisText.push(hoverPt.label);

            if(cdi.hasOwnProperty('v')) {
                hoverPt.value = cdi.v;
                hoverPt.valueLabel = formatValue(hoverPt.value, separators);
                if(hasFlag('value')) thisText.push(hoverPt.valueLabel);
            }

            hoverPt.currentPath = pt.currentPath = helpers.getPath(pt.data);
            if(hasFlag('current path') && !isRoot) {
                thisText.push(hoverPt.currentPath);
            }

            var tx;
            var allPercents = [];
            var insertPercent = function() {
                if(allPercents.indexOf(tx) === -1) { // no need to add redundant info
                    thisText.push(tx);
                    allPercents.push(tx);
                }
            };

            var val = getVal(cdi);

            var ref2 = parent;
            if(ref2) {
                hoverPt.percentParent = pt.percentParent = val / getVal(ref2);
                hoverPt.parent = pt.parentString = helpers.getLabelString(parentLabel);
                if(hasFlag('percent parent')) {
                    tx = helpers.formatPercent(hoverPt.percentParent, separators) + ' of ' + hoverPt.parent;
                    insertPercent();
                }
            }

            var ref1 = entry;
            if(ref1) {
                hoverPt.percentEntry = pt.percentEntry = val / getVal(ref1);
                hoverPt.entry = pt.entry = helpers.getLabelString(ref1.data.data.label);
                if(hasFlag('percent entry') && !isRoot && !pt.onPathbar) {
                    tx = helpers.formatPercent(hoverPt.percentEntry, separators) + ' of ' + hoverPt.entry;
                    insertPercent();
                }
            }

            var ref0 = hierarchy;
            if(ref0) {
                hoverPt.percentRoot = pt.percentRoot = val / getVal(ref0);
                hoverPt.root = pt.root = helpers.getLabelString(ref0.data.data.label);
                if(hasFlag('percent root') && !isRoot) {
                    tx = helpers.formatPercent(hoverPt.percentRoot, separators) + ' of ' + hoverPt.root;
                    insertPercent();
                }
            }

            hoverPt.text = _cast('hovertext') || _cast('text');
            if(hasFlag('text')) {
                tx = hoverPt.text;
                if(Lib.isValidTextValue(tx)) thisText.push(tx);
            }

            var hoverItems = {
                trace: traceNow,
                y: hoverCenterY,
                text: thisText.join('
'),
                name: (hovertemplate || hasFlag('name')) ? traceNow.name : undefined,
                color: _cast('hoverlabel.bgcolor') || cdi.color,
                borderColor: _cast('hoverlabel.bordercolor'),
                fontFamily: _cast('hoverlabel.font.family'),
                fontSize: _cast('hoverlabel.font.size'),
                fontColor: _cast('hoverlabel.font.color'),
                nameLength: _cast('hoverlabel.namelength'),
                textAlign: _cast('hoverlabel.align'),
                hovertemplate: hovertemplate,
                hovertemplateLabels: hoverPt,
                eventData: [makeEventData(pt, traceNow, opts.eventDataKeys)]
            };

            if(isSunburst) {
                hoverItems.x0 = hoverCenterX - pt.rInscribed * pt.rpx1;
                hoverItems.x1 = hoverCenterX + pt.rInscribed * pt.rpx1;
                hoverItems.idealAlign = pt.pxmid[0] < 0 ? 'left' : 'right';
            }
            if(isTreemap) {
                hoverItems.x = hoverCenterX;
                hoverItems.idealAlign = hoverCenterX < 0 ? 'left' : 'right';
            }

            Fx.loneHover(hoverItems, {
                container: fullLayoutNow._hoverlayer.node(),
                outerContainer: fullLayoutNow._paper.node(),
                gd: gd
            });

            trace._hasHoverLabel = true;
        }

        if(isTreemap) {
            var slice = sliceTop.select('path.surface');
            opts.styleOne(slice, pt, traceNow, {
                hovered: true
            });
        }

        trace._hasHoverEvent = true;
        gd.emit('plotly_hover', {
            points: [makeEventData(pt, traceNow, opts.eventDataKeys)],
            event: d3.event
        });
    };

    var onMouseOut = function(evt) {
        var fullLayoutNow = gd._fullLayout;
        var traceNow = gd._fullData[trace.index];
        var pt = d3.select(this).datum();

        if(trace._hasHoverEvent) {
            evt.originalEvent = d3.event;
            gd.emit('plotly_unhover', {
                points: [makeEventData(pt, traceNow, opts.eventDataKeys)],
                event: d3.event
            });
            trace._hasHoverEvent = false;
        }

        if(trace._hasHoverLabel) {
            Fx.loneUnhover(fullLayoutNow._hoverlayer.node());
            trace._hasHoverLabel = false;
        }

        if(isTreemap) {
            var slice = sliceTop.select('path.surface');
            opts.styleOne(slice, pt, traceNow, {
                hovered: false
            });
        }
    };

    var onClick = function(pt) {
        // TODO: this does not support right-click. If we want to support it, we
        // would likely need to change pie to use dragElement instead of straight
        // mapbox event binding. Or perhaps better, make a simple wrapper with the
        // right mousedown, mousemove, and mouseup handlers just for a left/right click
        // mapbox would use this too.
        var fullLayoutNow = gd._fullLayout;
        var traceNow = gd._fullData[trace.index];

        var clickVal = Events.triggerHandler(gd, 'plotly_' + trace.type + 'click', {
            points: [makeEventData(pt, traceNow, opts.eventDataKeys)],
            event: d3.event
        });

        // 'regular' click event when sunburst/treemap click is disabled or when
        // clicking on leaves or the hierarchy root
        if(
            clickVal === false ||
            isSunburst && (
                helpers.isHierarchyRoot(pt) ||
                helpers.isLeaf(pt)
            )
        ) {
            if(fullLayoutNow.hovermode) {
                gd._hoverdata = [makeEventData(pt, traceNow, opts.eventDataKeys)];
                Fx.click(gd, d3.event);
            }
            return;
        }

        // skip if triggered from dragging a nearby cartesian subplot
        if(gd._dragging) return;

        // skip during transitions, to avoid potential bugs
        // we could remove this check later
        if(gd._transitioning) return;

        // store 'old' level in guiEdit stash, so that subsequent Plotly.react
        // calls with the same uirevision can start from the same entry
        Registry.call('_storeDirectGUIEdit', traceNow, fullLayoutNow._tracePreGUI[traceNow.uid], {
            level: traceNow.level
        });

        var id = helpers.getPtId(pt);
        var nextEntry = helpers.isEntry(pt) ?
            helpers.findEntryWithChild(hierarchy, id) :
            helpers.findEntryWithLevel(hierarchy, id);

        var frame = {
            data: [{level: helpers.getPtId(nextEntry)}],
            traces: [trace.index]
        };

        var animOpts = {
            frame: {
                redraw: false,
                duration: opts.transitionTime
            },
            transition: {
                duration: opts.transitionTime,
                easing: opts.transitionEasing
            },
            mode: 'immediate',
            fromcurrent: true
        };

        Fx.loneUnhover(fullLayoutNow._hoverlayer.node());
        Registry.call('animate', gd, frame, animOpts);
    };

    sliceTop.on('mouseover', onMouseOver);
    sliceTop.on('mouseout', onMouseOut);
    sliceTop.on('click', onClick);
};

function makeEventData(pt, trace, keys) {
    var cdi = pt.data.data;

    var out = {
        curveNumber: trace.index,
        pointNumber: cdi.i,
        data: trace._input,
        fullData: trace,

        // TODO more things like 'children', 'siblings', 'hierarchy?
    };

    for(var i = 0; i < keys.length; i++) {
        var key = keys[i];
        if(key in pt) out[key] = pt[key];
    }
    // handle special case of parent
    if('parentString' in pt) out.parent = pt.parentString;

    appendArrayPointValue(out, trace, cdi.i);

    return out;
}
<|endoftext|>


<commit_before>'use strict';

module.exports = {
  "payingWith": "Betalen met {{paymentSource}}",
  "chooseAnotherWayToPay": "Kies een andere betaalmethode",
  "chooseAWayToPay": "Kies een betaalwijze",
  "otherWaysToPay": "Andere manieren om te betalen",
  "edit": "Bewerk",
  "doneEditing": "Klaar",
  "editPaymentMethods": "Betaalmethoden aanpassen",
  "CreditCardDeleteConfirmationMessage": "{{secondaryIdentifier}}-kaart eindigend op {{identifier}} verwijderen?",
  "PayPalAccountDeleteConfirmationMessage": "PayPal-rekening {{identifier}} verwijderen?",
  "VenmoAccountDeleteConfirmationMessage": "Weet u zeker dat u Venmo-rekening met gebruikersnaam {{identifier}} wilt verwijderen?",
  "genericDeleteConfirmationMessage": "Weet u zeker dat u deze betaalmethode wilt verwijderen?",
  "deleteCancelButton": "Annuleren",
  "deleteConfirmationButton": "Verwijderen",
  "fieldEmptyForCvv": "Vul een CSC in.",
  "fieldEmptyForExpirationDate": "Vul een vervaldatum in.",
  "fieldEmptyForCardholderName": "Vul een naam voor de kaarthouder in.",
  "fieldTooLongForCardholderName": "De naam van de kaarthouder moet korter zijn dan 256 tekens.",
  "fieldEmptyForNumber": "Vul een nummer in.",
  "fieldEmptyForPostalCode": "Vul een postcode in.",
  "fieldInvalidForCvv": "Deze CSC is ongeldig.",
  "fieldInvalidForExpirationDate": "Deze vervaldatum is ongeldig.",
  "fieldInvalidForNumber": "Dit creditcardnummer is ongeldig.",
  "fieldInvalidForPostalCode": "Deze postcode is ongeldig.",
  "genericError": "Er is iets fout gegaan.",
  "hostedFieldsTokenizationFailOnDuplicateError": "Deze creditcard staat al geregistreerd als een opgeslagen betaalmethode.",
  "hostedFieldsFailedTokenizationError": "Controleer uw gegevens en probeer het opnieuw.",
  "hostedFieldsFieldsInvalidError": "Controleer uw gegevens en probeer het opnieuw.",
  "hostedFieldsTokenizationNetworkErrorError": "Netwerkfout. Probeer het opnieuw.",
  "hostedFieldsTokenizationCvvVerificationFailedError": "De controle van de creditcard is mislukt. Controleer uw gegevens en probeer het opnieuw.",
  "paypalButtonMustBeUsed": "Gebruik de PayPal-knop om door te gaan met uw betaling.",
  "paypalAccountTokenizationFailedError": "Er is iets misgegaan bij het toevoegen van de PayPal-rekening. Probeer het opnieuw.",
  "paypalFlowFailedError": "Er is iets misgegaan bij de verbinding met PayPal. Probeer het opnieuw.",
  "paypalTokenizationRequestActiveError": "De autorisatie van de PayPal-betaling is al in behandeling.",
  "venmoCanceledError": "Er ging iets fout. Controleer of de meest recente versie van de Venmo-app op je apparaat is geïnstalleerd en dat je browser overschakelen naar Venmo ondersteunt.",
  "vaultManagerPaymentMethodDeletionError": "Kan de betaalmethode niet verwijderen, probeer het opnieuw.",
  "venmoAppFailedError": "De Venmo-app is niet aangetroffen op je apparaat.",
  "unsupportedCardTypeError": "Dit type creditcard wordt niet ondersteund. Gebruik een andere creditcard.",
  "applePayTokenizationError": "Er is een netwerkfout opgetreden bij het verwerken van de Apple Pay-betaling. Probeer het opnieuw.",
  "applePayActiveCardError": "Voeg een ondersteunde creditcard toe aan je Apple Pay-wallet.",
  "cardholderNameLabel": "Naam kaarthouder",
  "cardNumberLabel": "Creditcardnummer",
  "cvvLabel": "CVV",
  "cvvThreeDigitLabelSubheading": "(3 cijfers)",
  "cvvFourDigitLabelSubheading": "(4 cijfers)",
  "cardholderNamePlaceholder": "Naam kaarthouder",
  "expirationDateLabel": "Vervaldatum",
  "expirationDateLabelSubheading": "(MM/JJ)",
  "expirationDatePlaceholder": "MM/JJ",
  "postalCodeLabel": "Postcode",
  "saveCardLabel": "Creditcard opslaan",
  "payWithCard": "Betalen met creditcard",
  "endingIn": "Eindigend op {{lastFourCardDigits}}",
  "Apple Pay": "Apple Pay",
  "Venmo": "Venmo",
  "Card": "Creditcard",
  "PayPal": "PayPal",
  "PayPal Credit": "PayPal Credit",
  "Google Pay": "Google Pay",
  "American Express": "American Express",
  "Discover": "Discover",
  "Diners Club": "Diners Club",
  "MasterCard": "Mastercard",
  "Visa": "Visa",
  "JCB": "JCB",
  "Maestro": "Maestro",
  "UnionPay": "UnionPay"
};
<commit_msg>Update NL translation<commit_after>'use strict';

module.exports = {
  "payingWith": "Betalen met {{paymentSource}}",
  "chooseAnotherWayToPay": "Kies een andere betaalmethode",
  "chooseAWayToPay": "Kies een betaalwijze",
  "otherWaysToPay": "Andere manieren om te betalen",
  "edit": "Bewerk",
  "doneEditing": "Klaar",
  "editPaymentMethods": "Betaalmethoden aanpassen",
  "CreditCardDeleteConfirmationMessage": "{{secondaryIdentifier}}-kaart eindigend op {{identifier}} verwijderen?",
  "PayPalAccountDeleteConfirmationMessage": "PayPal-rekening {{identifier}} verwijderen?",
  "VenmoAccountDeleteConfirmationMessage": "Weet u zeker dat u Venmo-rekening met gebruikersnaam {{identifier}} wilt verwijderen?",
  "genericDeleteConfirmationMessage": "Weet u zeker dat u deze betaalmethode wilt verwijderen?",
  "deleteCancelButton": "Annuleren",
  "deleteConfirmationButton": "Verwijderen",
  "fieldEmptyForCvv": "Vul een CSC in.",
  "fieldEmptyForExpirationDate": "Vul een vervaldatum in.",
  "fieldEmptyForCardholderName": "Vul een naam voor de kaarthouder in.",
  "fieldTooLongForCardholderName": "De naam van de kaarthouder moet korter zijn dan 256 tekens.",
  "fieldEmptyForNumber": "Vul een nummer in.",
  "fieldEmptyForPostalCode": "Vul een postcode in.",
  "fieldInvalidForCvv": "Deze CSC is ongeldig.",
  "fieldInvalidForExpirationDate": "Deze vervaldatum is ongeldig.",
  "fieldInvalidForNumber": "Dit creditcardnummer is ongeldig.",
  "fieldInvalidForPostalCode": "Deze postcode is ongeldig.",
  "genericError": "Er is iets fout gegaan.",
  "hostedFieldsTokenizationFailOnDuplicateError": "Deze creditcard staat al geregistreerd als een opgeslagen betaalmethode.",
  "hostedFieldsFailedTokenizationError": "Controleer uw gegevens en probeer het opnieuw.",
  "hostedFieldsFieldsInvalidError": "Controleer uw gegevens en probeer het opnieuw.",
  "hostedFieldsTokenizationNetworkErrorError": "Netwerkfout. Probeer het opnieuw.",
  "hostedFieldsTokenizationCvvVerificationFailedError": "De controle van de creditcard is mislukt. Controleer uw gegevens en probeer het opnieuw.",
  "paypalButtonMustBeUsed": "Gebruik de PayPal-knop om door te gaan met uw betaling.",
  "paypalAccountTokenizationFailedError": "Er is iets misgegaan bij het toevoegen van de PayPal-rekening. Probeer het opnieuw.",
  "paypalFlowFailedError": "Er is iets misgegaan bij de verbinding met PayPal. Probeer het opnieuw.",
  "paypalTokenizationRequestActiveError": "De autorisatie van de PayPal-betaling is al in behandeling.",
  "venmoCanceledError": "Er ging iets fout. Controleer of de meest recente versie van de Venmo-app op je apparaat is geïnstalleerd en dat je browser overschakelen naar Venmo ondersteunt.",
  "vaultManagerPaymentMethodDeletionError": "Kan de betaalmethode niet verwijderen, probeer het opnieuw.",
  "venmoAppFailedError": "De Venmo-app is niet aangetroffen op je apparaat.",
  "unsupportedCardTypeError": "Dit type creditcard wordt niet ondersteund. Gebruik een andere creditcard.",
  "applePayTokenizationError": "Er is een netwerkfout opgetreden bij het verwerken van de Apple Pay-betaling. Probeer het opnieuw.",
  "applePayActiveCardError": "Voeg een ondersteunde creditcard toe aan je Apple Pay-wallet.",
  "cardholderNameLabel": "Naam kaarthouder",
  "cardNumberLabel": "Creditcardnummer",
  "cvvLabel": "CSC",
  "cvvThreeDigitLabelSubheading": "(3 cijfers)",
  "cvvFourDigitLabelSubheading": "(4 cijfers)",
  "cardholderNamePlaceholder": "Naam kaarthouder",
  "expirationDateLabel": "Vervaldatum",
  "expirationDateLabelSubheading": "(MM/JJ)",
  "expirationDatePlaceholder": "MM/JJ",
  "postalCodeLabel": "Postcode",
  "saveCardLabel": "Creditcard opslaan",
  "payWithCard": "Betalen met creditcard",
  "endingIn": "Eindigend op {{lastFourCardDigits}}",
  "Apple Pay": "Apple Pay",
  "Venmo": "Venmo",
  "Card": "Creditcard",
  "PayPal": "PayPal",
  "PayPal Credit": "PayPal Credit",
  "Google Pay": "Google Pay",
  "American Express": "American Express",
  "Discover": "Discover",
  "Diners Club": "Diners Club",
  "MasterCard": "Mastercard",
  "Visa": "Visa",
  "JCB": "JCB",
  "Maestro": "Maestro",
  "UnionPay": "UnionPay"
};
<|endoftext|>


<commit_before>var path = require('path');
var express = require('express');
var webpack = require('webpack');
var config = require('./webpack.config.dev');

var app = express();
var compiler = webpack(config);

app.use(require('webpack-dev-middleware')(compiler, {
  noInfo: true,
  publicPath: config.output.publicPath
}));

app.use(require('webpack-hot-middleware')(compiler));

app.get('*', function(req, res) {
  res.sendFile(path.join(__dirname, 'index.html'));
});

app.listen(8080, 'localhost', function(err) {
  if (err) {
    console.log(err);
    return;
  }

  console.log('Listening at http://localhost:8080');
});
<commit_msg>fix(server): node express route<commit_after>var path = require('path');
var express = require('express');
var webpack = require('webpack');
var config = require('./webpack.config.dev');

var app = express();
var compiler = webpack(config);

app.use(require('webpack-dev-middleware')(compiler, {
  noInfo: true,
  publicPath: config.output.publicPath
}));

app.use(require('webpack-hot-middleware')(compiler));

app.get('/', function(req, res) {
  res.sendFile(path.join(__dirname, 'index.html'));
});

app.listen(8080, 'localhost', function(err) {
  if (err) {
    console.log(err);
    return;
  }

  console.log('Listening at http://localhost:8080');
});
<|endoftext|>


<commit_before>import Field from '../u-field.vue';

export default {
    name: 'u-slider',
    mixins: [Field],
    props: {
        value: { type: Number, default: 0 },
        min: { type: Number, default: 0 },
        max: { type: Number, default: 100 },
        step: { type: Number, default: 1 },
        precision: { type: Number, default: 1, validator: (precision) => precision > 0 },
        // @TODO: 以后再考虑复杂的范围情况
        range: { type: Array, default() { return []; } },
        readonly: { type: Boolean, default: false },
        disabled: { type: Boolean, default: false },
    },
    data() {
        return {
            currentValue: this.value,
            currentRange: this.normalizeRange(this.range),
            grid: { x: 0, y: 0 },
            handleEl: undefined,
        };
    },
    watch: {
        value(value) {
            this.currentValue = value;
            this.handleEl.style.left = this.percent + '%';
        },
        currentValue(value, oldValue) {
            value = +value;

            this.$emit('change', { value, oldValue });
        },
        range(range) {
            this.currentRange = this.normalizeRange(range);
        },
    },
    computed: {
        percent: {
            get() {
                return (this.currentValue - this.min) / (this.max - this.min) * 100;
            },
            set(percent) {
                const value = this.fix(+this.min + (this.max - this.min) * percent / 100);

                this.currentValue = value;
                this.$emit('input', value);
                this.$emit('update:value', value);
            },
        },
        rangeStartPercent() {
            const start = Math.max(this.currentRange[0], this.min);
            return (start - this.min) / (this.max - this.min) * 100;
        },
        rangeEndPercent() {
            const end = Math.min(this.currentRange[1], this.max);
            return (this.max - end) / (this.max - this.min) * 100;
        },
    },
    mounted() {
        this.handleEl = this.$refs.handle;
        this.handleEl.style.left = this.percent + '%';
    },
    methods: {
        normalizeRange(range) {
            range = Array.from(range);
            if (range[0] === undefined)
                range[0] = -Infinity;
            if (range[1] === undefined)
                range[1] = Infinity;
            return range;
        },
        fix(value) {
            // 刻度约束
            this.step && (value = Math.round(value / this.step) * this.step);
            // 范围约束
            value = Math.min(Math.max(this.currentRange[0], value), this.currentRange[1]);
            // 最大最小约束
            value = Math.min(Math.max(this.min, value), this.max);
            // 保留小数位数
            value = +value.toFixed(this.precision < 1 ? -Math.log10(this.precision) : 0);
            return value;
        },
        onDragStart($event) {
            this.grid.x = this.step / (this.max - this.min) * $event.range.width;

            const oldValue = this.currentValue;
            this.percent = $event.left / $event.range.width * 100;
            const percent = this.percent;
            this.handleEl.style.left = percent + '%';
            this.$emit('slide', {
                oldValue,
                value: this.currentValue,
                percent,
            });
        },
        onDrag($event) {
            const oldValue = this.currentValue;
            this.percent = $event.left / $event.range.width * 100;
            const percent = this.percent;
            this.handleEl.style.left = percent + '%';
            this.$emit('slide', {
                oldValue,
                value: this.currentValue,
                percent,
            });
        },
    },
};
<commit_msg>:bug: Fix dynamic change mix and max value handle position not auto dynamic<commit_after>import Field from '../u-field.vue';

export default {
    name: 'u-slider',
    mixins: [Field],
    props: {
        value: { type: Number, default: 0 },
        min: { type: Number, default: 0 },
        max: { type: Number, default: 100 },
        step: { type: Number, default: 1 },
        precision: { type: Number, default: 1, validator: (precision) => precision > 0 },
        // @TODO: 以后再考虑复杂的范围情况
        range: { type: Array, default() { return []; } },
        readonly: { type: Boolean, default: false },
        disabled: { type: Boolean, default: false },
    },
    data() {
        return {
            currentValue: this.value,
            currentRange: this.normalizeRange(this.range),
            grid: { x: 0, y: 0 },
            handleEl: undefined,
        };
    },
    watch: {
        value(value) {
            this.currentValue = value;
            this.handleEl.style.left = this.percent + '%';
        },
        currentValue(value, oldValue) {
            value = +value;

            this.$emit('change', { value, oldValue });
        },
        range(range) {
            this.currentRange = this.normalizeRange(range);
        },
    },
    computed: {
        percent: {
            get() {
                return (this.currentValue - this.min) / (this.max - this.min) * 100;
            },
            set(percent) {
                const value = this.fix(+this.min + (this.max - this.min) * percent / 100);

                this.currentValue = value;
                this.$emit('input', value);
                this.$emit('update:value', value);
            },
        },
        rangeStartPercent() {
            const start = Math.max(this.currentRange[0], this.min);
            return (start - this.min) / (this.max - this.min) * 100;
        },
        rangeEndPercent() {
            const end = Math.min(this.currentRange[1], this.max);
            return (this.max - end) / (this.max - this.min) * 100;
        },
        min(value) {
            this.handleEl.style.left = this.percent + '%';
        },
        max(value) {
            this.handleEl.style.left = this.percent + '%';
        },
    },
    mounted() {
        this.handleEl = this.$refs.handle;
        this.handleEl.style.left = this.percent + '%';
    },
    methods: {
        normalizeRange(range) {
            range = Array.from(range);
            if (range[0] === undefined)
                range[0] = -Infinity;
            if (range[1] === undefined)
                range[1] = Infinity;
            return range;
        },
        fix(value) {
            // 刻度约束
            this.step && (value = Math.round(value / this.step) * this.step);
            // 范围约束
            value = Math.min(Math.max(this.currentRange[0], value), this.currentRange[1]);
            // 最大最小约束
            value = Math.min(Math.max(this.min, value), this.max);
            // 保留小数位数
            value = +value.toFixed(this.precision < 1 ? -Math.log10(this.precision) : 0);
            return value;
        },
        onDragStart($event) {
            this.grid.x = this.step / (this.max - this.min) * $event.range.width;

            const oldValue = this.currentValue;
            this.percent = $event.left / $event.range.width * 100;
            const percent = this.percent;
            this.handleEl.style.left = percent + '%';
            this.$emit('slide', {
                oldValue,
                value: this.currentValue,
                percent,
            });
        },
        onDrag($event) {
            const oldValue = this.currentValue;
            this.percent = $event.left / $event.range.width * 100;
            const percent = this.percent;
            this.handleEl.style.left = percent + '%';
            this.$emit('slide', {
                oldValue,
                value: this.currentValue,
                percent,
            });
        },
    },
};
<|endoftext|>


<commit_before>import React from 'react'
import styled from 'styled-components'
import { Link } from 'gatsby'
import Img from 'gatsby-image'

const ProjectLink = styled(Link)`
  text-decoration: none;
  padding: 0;
  margin: 0;
  @media screen and (min-width: ${props => props.theme.responsive.medium}) {
    transition: all 0.5s;
    display: inline-block;
    h1 {
      color: ${props => props.theme.colors.secondary};
    }
    &:hover div {
      @supports (object-fit: cover) {
        opacity: 1;
        visibility: visible;
      }
    }
    &:hover h1 {
      color: ${props => props.theme.colors.highlight};
    }
  }
`
const Cover = styled.div`
  div {
    height: 100% !important;
    width: 100%;
    object-fit: cover !important;
    display: block;
  }
  @media screen and (min-width: ${props => props.theme.responsive.medium}) {
    position: fixed !important;
    pointer-events: none;
    transition: opacity 0.3s, visibility 0.3s;
    width: calc(50% - 3rem);
    height: calc(100vh - 5.5rem);
    top: 3.5rem;
    left: 2rem;
    z-index: 2;
    opacity: 0;
    visibility: hidden;
    div {
      height: 100% !important;
      object-fit: cover !important;
    }
  }
`
const Title = styled.h1`
  text-transform: uppercase;
  padding: 1rem 2rem 0.5rem;
  @media screen and (min-width: ${props => props.theme.responsive.medium}) {
    padding: 2rem;
    z-index: 3;
  }
`
const Excerpt = styled.p`
  margin-bottom: 0;
  padding: 0.25rem 2rem 2rem;
  font-weight: normal;
  text-transform: none;
`
const PortfolioContent = props => {
  return (
    <ProjectLink key={props.id} to={`/${props.slug}/`}>
      <Cover>

      </Cover>
      <Title>{props.title}</Title>
      <Excerpt
        dangerouslySetInnerHTML={{
          __html: props.excerpt.childMarkdownRemark.excerpt,
        }}
      />
    </ProjectLink>
  )
}

export default PortfolioContent
<commit_msg>Small spacing adjustment<commit_after>import React from 'react'
import styled from 'styled-components'
import { Link } from 'gatsby'
import Img from 'gatsby-image'

const ProjectLink = styled(Link)`
  text-decoration: none;
  padding: 0;
  margin: 0;
  @media screen and (min-width: ${props => props.theme.responsive.medium}) {
    transition: all 0.5s;
    display: inline-block;
    h1 {
      color: ${props => props.theme.colors.secondary};
    }
    &:hover div {
      @supports (object-fit: cover) {
        opacity: 1;
        visibility: visible;
      }
    }
    &:hover h1 {
      color: ${props => props.theme.colors.highlight};
    }
  }
`
const Cover = styled.div`
  div {
    height: 100% !important;
    width: 100%;
    object-fit: cover !important;
    display: block;
  }
  @media screen and (min-width: ${props => props.theme.responsive.medium}) {
    position: fixed !important;
    pointer-events: none;
    transition: opacity 0.3s, visibility 0.3s;
    width: calc(50% - 3rem);
    height: calc(100vh - 5.5rem);
    top: 3.5rem;
    left: 2rem;
    z-index: 2;
    opacity: 0;
    visibility: hidden;
    div {
      height: 100% !important;
      object-fit: cover !important;
    }
  }
`
const Title = styled.h1`
  text-transform: uppercase;
  padding: 1rem 2rem 0;
  @media screen and (min-width: ${props => props.theme.responsive.medium}) {
    padding: 0 2rem;
    z-index: 3;
  }
`
const Excerpt = styled.p`
  margin-bottom: 0;
  padding: 0.25rem 2rem 2rem;
  font-weight: normal;
  text-transform: none;
`
const PortfolioContent = props => {
  return (
    <ProjectLink key={props.id} to={`/${props.slug}/`}>
      <Cover>

      </Cover>
      <Title>{props.title}</Title>
      <Excerpt
        dangerouslySetInnerHTML={{
          __html: props.excerpt.childMarkdownRemark.excerpt,
        }}
      />
    </ProjectLink>
  )
}

export default PortfolioContent
<|endoftext|>


<commit_before>export default function (coordinate = 0, speed = this.params.speed, runCallbacks = true, translateBounds = true, internal) {
  const swiper = this;

  const {
    params,
  } = swiper;

  if (swiper.animating && params.preventInteractionOnTransition) {
    return false;
  }

  const minTranslate = swiper.minTranslate();
  const maxTranslate = swiper.maxTranslate();
  let translate;
  if (translateBounds && coordinate < minTranslate) translate = minTranslate;
  else if (translateBounds && coordinate > maxTranslate) translate = maxTranslate;
  else translate = coordinate;

  // Update progress
  swiper.updateProgress(translate);

  if (speed === 0) {
    swiper.setTransition(0);
    swiper.setTranslate(translate);
    if (runCallbacks) {
      swiper.emit('beforeTransitionStart', speed, internal);
      swiper.emit('transitionEnd');
    }
  } else {
    swiper.setTransition(speed);
    swiper.setTranslate(translate);
    if (runCallbacks) {
      swiper.emit('beforeTransitionStart', speed, internal);
      swiper.emit('transitionStart');
    }
    if (!swiper.animating) {
      swiper.animating = true;
      if (!swiper.onTranslateToWrapperTransitionEnd) {
        swiper.onTranslateToWrapperTransitionEnd = function transitionEnd(e) {
          if (!swiper || swiper.destroyed) return;
          if (e.target !== this) return;
          swiper.$wrapperEl[0].removeEventListener('transitionend', swiper.onTranslateToWrapperTransitionEnd);
          swiper.$wrapperEl[0].removeEventListener('webkitTransitionEnd', swiper.onTranslateToWrapperTransitionEnd);
          swiper.onTranslateToWrapperTransitionEnd = null;
          delete swiper.onTranslateToWrapperTransitionEnd;
          if (runCallbacks) {
            swiper.emit('transitionEnd');
          }
        };
      }
      swiper.$wrapperEl[0].addEventListener('transitionend', swiper.onTranslateToWrapperTransitionEnd);
      swiper.$wrapperEl[0].addEventListener('webkitTransitionEnd', swiper.onTranslateToWrapperTransitionEnd);
    }
  }

  return true;
}
<commit_msg>Resolve bug restricting translateTo to specific bounds.<commit_after>export default function (coordinate = 0, speed = this.params.speed, runCallbacks = true, translateBounds = true, internal) {
  const swiper = this;

  const {
    params,
  } = swiper;

  if (swiper.animating && params.preventInteractionOnTransition) {
    return false;
  }

  const minTranslate = swiper.minTranslate();
  const maxTranslate = swiper.maxTranslate();
  let translate;
  if (translateBounds && coordinate > minTranslate) translate = minTranslate;
  else if (translateBounds && coordinate < maxTranslate) translate = maxTranslate;
  else translate = coordinate;

  // Update progress
  swiper.updateProgress(translate);

  if (speed === 0) {
    swiper.setTransition(0);
    swiper.setTranslate(translate);
    if (runCallbacks) {
      swiper.emit('beforeTransitionStart', speed, internal);
      swiper.emit('transitionEnd');
    }
  } else {
    swiper.setTransition(speed);
    swiper.setTranslate(translate);
    if (runCallbacks) {
      swiper.emit('beforeTransitionStart', speed, internal);
      swiper.emit('transitionStart');
    }
    if (!swiper.animating) {
      swiper.animating = true;
      if (!swiper.onTranslateToWrapperTransitionEnd) {
        swiper.onTranslateToWrapperTransitionEnd = function transitionEnd(e) {
          if (!swiper || swiper.destroyed) return;
          if (e.target !== this) return;
          swiper.$wrapperEl[0].removeEventListener('transitionend', swiper.onTranslateToWrapperTransitionEnd);
          swiper.$wrapperEl[0].removeEventListener('webkitTransitionEnd', swiper.onTranslateToWrapperTransitionEnd);
          swiper.onTranslateToWrapperTransitionEnd = null;
          delete swiper.onTranslateToWrapperTransitionEnd;
          if (runCallbacks) {
            swiper.emit('transitionEnd');
          }
        };
      }
      swiper.$wrapperEl[0].addEventListener('transitionend', swiper.onTranslateToWrapperTransitionEnd);
      swiper.$wrapperEl[0].addEventListener('webkitTransitionEnd', swiper.onTranslateToWrapperTransitionEnd);
    }
  }

  return true;
}
<|endoftext|>


<commit_before>/*
Copyright 2016 OpenMarket Ltd
Copyright 2017, 2018 New Vector Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import {MatrixClientPeg} from '../MatrixClientPeg';
import {getAddressType} from '../UserAddress';
import GroupStore from '../stores/GroupStore';
import {_t} from "../languageHandler";
import * as sdk from "../index";
import Modal from "../Modal";
import SettingsStore from "../settings/SettingsStore";
import {defer} from "./promise";

/**
 * Invites multiple addresses to a room or group, handling rate limiting from the server
 */
export default class MultiInviter {
    /**
     * @param {string} targetId The ID of the room or group to invite to
     */
    constructor(targetId) {
        if (targetId[0] === '+') {
            this.roomId = null;
            this.groupId = targetId;
        } else {
            this.roomId = targetId;
            this.groupId = null;
        }

        this.canceled = false;
        this.addrs = [];
        this.busy = false;
        this.completionStates = {}; // State of each address (invited or error)
        this.errors = {}; // { address: {errorText, errcode} }
        this.deferred = null;
    }

    /**
     * Invite users to this room. This may only be called once per
     * instance of the class.
     *
     * @param {array} addrs Array of addresses to invite
     * @param {string} reason Reason for inviting (optional)
     * @returns {Promise} Resolved when all invitations in the queue are complete
     */
    invite(addrs, reason) {
        if (this.addrs.length > 0) {
            throw new Error("Already inviting/invited");
        }
        this.addrs.push(...addrs);
        this.reason = reason;

        for (const addr of this.addrs) {
            if (getAddressType(addr) === null) {
                this.completionStates[addr] = 'error';
                this.errors[addr] = {
                    errcode: 'M_INVALID',
                    errorText: _t('Unrecognised address'),
                };
            }
        }
        this.deferred = defer();
        this._inviteMore(0);

        return this.deferred.promise;
    }

    /**
     * Stops inviting. Causes promises returned by invite() to be rejected.
     */
    cancel() {
        if (!this.busy) return;

        this._canceled = true;
        this.deferred.reject(new Error('canceled'));
    }

    getCompletionState(addr) {
        return this.completionStates[addr];
    }

    getErrorText(addr) {
        return this.errors[addr] ? this.errors[addr].errorText : null;
    }

    async _inviteToRoom(roomId, addr, ignoreProfile) {
        const addrType = getAddressType(addr);

        if (addrType === 'email') {
            return MatrixClientPeg.get().inviteByEmail(roomId, addr);
        } else if (addrType === 'mx-user-id') {
            const room = MatrixClientPeg.get().getRoom(roomId);
            if (!room) throw new Error("Room not found");

            const member = room.getMember(addr);
            if (member && ['join', 'invite'].includes(member.membership)) {
                throw {errcode: "RIOT.ALREADY_IN_ROOM", error: "Member already invited"};
            }

            if (!ignoreProfile && SettingsStore.getValue("promptBeforeInviteUnknownUsers", this.roomId)) {
                const profile = await MatrixClientPeg.get().getProfileInfo(addr);
                if (!profile) {
                    // noinspection ExceptionCaughtLocallyJS
                    throw new Error("User has no profile");
                }
            }

            return MatrixClientPeg.get().invite(roomId, addr, undefined, this.reason);
        } else {
            throw new Error('Unsupported address');
        }
    }

    _doInvite(address, ignoreProfile) {
        return new Promise((resolve, reject) => {
            console.log(`Inviting ${address}`);

            let doInvite;
            if (this.groupId !== null) {
                doInvite = GroupStore.inviteUserToGroup(this.groupId, address);
            } else {
                doInvite = this._inviteToRoom(this.roomId, address, ignoreProfile);
            }

            doInvite.then(() => {
                if (this._canceled) {
                    return;
                }

                this.completionStates[address] = 'invited';
                delete this.errors[address];

                resolve();
            }).catch((err) => {
                if (this._canceled) {
                    return;
                }

                console.error(err);

                let errorText;
                let fatal = false;
                if (err.errcode === 'M_FORBIDDEN') {
                    fatal = true;
                    errorText = _t('You do not have permission to invite people to this room.');
                } else if (err.errcode === "RIOT.ALREADY_IN_ROOM") {
                    errorText = _t("User %(userId)s is already in the room", {userId: address});
                } else if (err.errcode === 'M_LIMIT_EXCEEDED') {
                    // we're being throttled so wait a bit & try again
                    setTimeout(() => {
                        this._doInvite(address, ignoreProfile).then(resolve, reject);
                    }, 5000);
                    return;
                } else if (['M_NOT_FOUND', 'M_USER_NOT_FOUND', 'RIOT.USER_NOT_FOUND'].includes(err.errcode)) {
                    errorText = _t("User %(user_id)s does not exist", {user_id: address});
                } else if (err.errcode === 'M_PROFILE_UNDISCLOSED') {
                    errorText = _t("User %(user_id)s may or may not exist", {user_id: address});
                } else if (err.errcode === 'M_PROFILE_NOT_FOUND' && !ignoreProfile) {
                    // Invite without the profile check
                    console.warn(`User ${address} does not have a profile - inviting anyways automatically`);
                    this._doInvite(address, true).then(resolve, reject);
                } else if (err.errcode === "M_BAD_STATE") {
                    errorText = _t("The user must be unbanned before they can be invited.");
                } else if (err.errcode === "M_UNSUPPORTED_ROOM_VERSION") {
                    errorText = _t("The user's homeserver does not support the version of the room.");
                } else {
                    errorText = _t('Unknown server error');
                }

                this.completionStates[address] = 'error';
                this.errors[address] = {errorText, errcode: err.errcode};

                this.busy = !fatal;
                this.fatal = fatal;

                if (fatal) {
                    reject();
                } else {
                    resolve();
                }
            });
        });
    }

    _inviteMore(nextIndex, ignoreProfile) {
        if (this._canceled) {
            return;
        }

        if (nextIndex === this.addrs.length) {
            this.busy = false;
            if (Object.keys(this.errors).length > 0 && !this.groupId) {
                // There were problems inviting some people - see if we can invite them
                // without caring if they exist or not.
                const unknownProfileErrors = ['M_NOT_FOUND', 'M_USER_NOT_FOUND', 'M_PROFILE_UNDISCLOSED', 'M_PROFILE_NOT_FOUND', 'RIOT.USER_NOT_FOUND'];
                const unknownProfileUsers = Object.keys(this.errors).filter(a => unknownProfileErrors.includes(this.errors[a].errcode));

                if (unknownProfileUsers.length > 0) {
                    const inviteUnknowns = () => {
                        const promises = unknownProfileUsers.map(u => this._doInvite(u, true));
                        Promise.all(promises).then(() => this.deferred.resolve(this.completionStates));
                    };

                    if (!SettingsStore.getValue("promptBeforeInviteUnknownUsers", this.roomId)) {
                        inviteUnknowns();
                        return;
                    }

                    const AskInviteAnywayDialog = sdk.getComponent("dialogs.AskInviteAnywayDialog");
                    console.log("Showing failed to invite dialog...");
                    Modal.createTrackedDialog('Failed to invite the following users to the room', '', AskInviteAnywayDialog, {
                        unknownProfileUsers: unknownProfileUsers.map(u => {return {userId: u, errorText: this.errors[u].errorText};}),
                        onInviteAnyways: () => inviteUnknowns(),
                        onGiveUp: () => {
                            // Fake all the completion states because we already warned the user
                            for (const addr of unknownProfileUsers) {
                                this.completionStates[addr] = 'invited';
                            }
                            this.deferred.resolve(this.completionStates);
                        },
                    });
                    return;
                }
            }
            this.deferred.resolve(this.completionStates);
            return;
        }

        const addr = this.addrs[nextIndex];

        // don't try to invite it if it's an invalid address
        // (it will already be marked as an error though,
        // so no need to do so again)
        if (getAddressType(addr) === null) {
            this._inviteMore(nextIndex + 1);
            return;
        }

        // don't re-invite (there's no way in the UI to do this, but
        // for sanity's sake)
        if (this.completionStates[addr] === 'invited') {
            this._inviteMore(nextIndex + 1);
            return;
        }

        this._doInvite(addr, ignoreProfile).then(() => {
            this._inviteMore(nextIndex + 1, ignoreProfile);
        }).catch(() => this.deferred.resolve(this.completionStates));
    }
}
<commit_msg>remove references to disused RIOT.USER_NOT_FOUND error code<commit_after>/*
Copyright 2016 OpenMarket Ltd
Copyright 2017, 2018 New Vector Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import {MatrixClientPeg} from '../MatrixClientPeg';
import {getAddressType} from '../UserAddress';
import GroupStore from '../stores/GroupStore';
import {_t} from "../languageHandler";
import * as sdk from "../index";
import Modal from "../Modal";
import SettingsStore from "../settings/SettingsStore";
import {defer} from "./promise";

/**
 * Invites multiple addresses to a room or group, handling rate limiting from the server
 */
export default class MultiInviter {
    /**
     * @param {string} targetId The ID of the room or group to invite to
     */
    constructor(targetId) {
        if (targetId[0] === '+') {
            this.roomId = null;
            this.groupId = targetId;
        } else {
            this.roomId = targetId;
            this.groupId = null;
        }

        this.canceled = false;
        this.addrs = [];
        this.busy = false;
        this.completionStates = {}; // State of each address (invited or error)
        this.errors = {}; // { address: {errorText, errcode} }
        this.deferred = null;
    }

    /**
     * Invite users to this room. This may only be called once per
     * instance of the class.
     *
     * @param {array} addrs Array of addresses to invite
     * @param {string} reason Reason for inviting (optional)
     * @returns {Promise} Resolved when all invitations in the queue are complete
     */
    invite(addrs, reason) {
        if (this.addrs.length > 0) {
            throw new Error("Already inviting/invited");
        }
        this.addrs.push(...addrs);
        this.reason = reason;

        for (const addr of this.addrs) {
            if (getAddressType(addr) === null) {
                this.completionStates[addr] = 'error';
                this.errors[addr] = {
                    errcode: 'M_INVALID',
                    errorText: _t('Unrecognised address'),
                };
            }
        }
        this.deferred = defer();
        this._inviteMore(0);

        return this.deferred.promise;
    }

    /**
     * Stops inviting. Causes promises returned by invite() to be rejected.
     */
    cancel() {
        if (!this.busy) return;

        this._canceled = true;
        this.deferred.reject(new Error('canceled'));
    }

    getCompletionState(addr) {
        return this.completionStates[addr];
    }

    getErrorText(addr) {
        return this.errors[addr] ? this.errors[addr].errorText : null;
    }

    async _inviteToRoom(roomId, addr, ignoreProfile) {
        const addrType = getAddressType(addr);

        if (addrType === 'email') {
            return MatrixClientPeg.get().inviteByEmail(roomId, addr);
        } else if (addrType === 'mx-user-id') {
            const room = MatrixClientPeg.get().getRoom(roomId);
            if (!room) throw new Error("Room not found");

            const member = room.getMember(addr);
            if (member && ['join', 'invite'].includes(member.membership)) {
                throw {errcode: "RIOT.ALREADY_IN_ROOM", error: "Member already invited"};
            }

            if (!ignoreProfile && SettingsStore.getValue("promptBeforeInviteUnknownUsers", this.roomId)) {
                const profile = await MatrixClientPeg.get().getProfileInfo(addr);
                if (!profile) {
                    // noinspection ExceptionCaughtLocallyJS
                    throw new Error("User has no profile");
                }
            }

            return MatrixClientPeg.get().invite(roomId, addr, undefined, this.reason);
        } else {
            throw new Error('Unsupported address');
        }
    }

    _doInvite(address, ignoreProfile) {
        return new Promise((resolve, reject) => {
            console.log(`Inviting ${address}`);

            let doInvite;
            if (this.groupId !== null) {
                doInvite = GroupStore.inviteUserToGroup(this.groupId, address);
            } else {
                doInvite = this._inviteToRoom(this.roomId, address, ignoreProfile);
            }

            doInvite.then(() => {
                if (this._canceled) {
                    return;
                }

                this.completionStates[address] = 'invited';
                delete this.errors[address];

                resolve();
            }).catch((err) => {
                if (this._canceled) {
                    return;
                }

                console.error(err);

                let errorText;
                let fatal = false;
                if (err.errcode === 'M_FORBIDDEN') {
                    fatal = true;
                    errorText = _t('You do not have permission to invite people to this room.');
                } else if (err.errcode === "RIOT.ALREADY_IN_ROOM") {
                    errorText = _t("User %(userId)s is already in the room", {userId: address});
                } else if (err.errcode === 'M_LIMIT_EXCEEDED') {
                    // we're being throttled so wait a bit & try again
                    setTimeout(() => {
                        this._doInvite(address, ignoreProfile).then(resolve, reject);
                    }, 5000);
                    return;
                } else if (['M_NOT_FOUND', 'M_USER_NOT_FOUND'].includes(err.errcode)) {
                    errorText = _t("User %(user_id)s does not exist", {user_id: address});
                } else if (err.errcode === 'M_PROFILE_UNDISCLOSED') {
                    errorText = _t("User %(user_id)s may or may not exist", {user_id: address});
                } else if (err.errcode === 'M_PROFILE_NOT_FOUND' && !ignoreProfile) {
                    // Invite without the profile check
                    console.warn(`User ${address} does not have a profile - inviting anyways automatically`);
                    this._doInvite(address, true).then(resolve, reject);
                } else if (err.errcode === "M_BAD_STATE") {
                    errorText = _t("The user must be unbanned before they can be invited.");
                } else if (err.errcode === "M_UNSUPPORTED_ROOM_VERSION") {
                    errorText = _t("The user's homeserver does not support the version of the room.");
                } else {
                    errorText = _t('Unknown server error');
                }

                this.completionStates[address] = 'error';
                this.errors[address] = {errorText, errcode: err.errcode};

                this.busy = !fatal;
                this.fatal = fatal;

                if (fatal) {
                    reject();
                } else {
                    resolve();
                }
            });
        });
    }

    _inviteMore(nextIndex, ignoreProfile) {
        if (this._canceled) {
            return;
        }

        if (nextIndex === this.addrs.length) {
            this.busy = false;
            if (Object.keys(this.errors).length > 0 && !this.groupId) {
                // There were problems inviting some people - see if we can invite them
                // without caring if they exist or not.
                const unknownProfileErrors = ['M_NOT_FOUND', 'M_USER_NOT_FOUND', 'M_PROFILE_UNDISCLOSED', 'M_PROFILE_NOT_FOUND'];
                const unknownProfileUsers = Object.keys(this.errors).filter(a => unknownProfileErrors.includes(this.errors[a].errcode));

                if (unknownProfileUsers.length > 0) {
                    const inviteUnknowns = () => {
                        const promises = unknownProfileUsers.map(u => this._doInvite(u, true));
                        Promise.all(promises).then(() => this.deferred.resolve(this.completionStates));
                    };

                    if (!SettingsStore.getValue("promptBeforeInviteUnknownUsers", this.roomId)) {
                        inviteUnknowns();
                        return;
                    }

                    const AskInviteAnywayDialog = sdk.getComponent("dialogs.AskInviteAnywayDialog");
                    console.log("Showing failed to invite dialog...");
                    Modal.createTrackedDialog('Failed to invite the following users to the room', '', AskInviteAnywayDialog, {
                        unknownProfileUsers: unknownProfileUsers.map(u => {return {userId: u, errorText: this.errors[u].errorText};}),
                        onInviteAnyways: () => inviteUnknowns(),
                        onGiveUp: () => {
                            // Fake all the completion states because we already warned the user
                            for (const addr of unknownProfileUsers) {
                                this.completionStates[addr] = 'invited';
                            }
                            this.deferred.resolve(this.completionStates);
                        },
                    });
                    return;
                }
            }
            this.deferred.resolve(this.completionStates);
            return;
        }

        const addr = this.addrs[nextIndex];

        // don't try to invite it if it's an invalid address
        // (it will already be marked as an error though,
        // so no need to do so again)
        if (getAddressType(addr) === null) {
            this._inviteMore(nextIndex + 1);
            return;
        }

        // don't re-invite (there's no way in the UI to do this, but
        // for sanity's sake)
        if (this.completionStates[addr] === 'invited') {
            this._inviteMore(nextIndex + 1);
            return;
        }

        this._doInvite(addr, ignoreProfile).then(() => {
            this._inviteMore(nextIndex + 1, ignoreProfile);
        }).catch(() => this.deferred.resolve(this.completionStates));
    }
}
<|endoftext|>


<commit_before>import { LitElement, html } from '/node_modules/@polymer/lit-element/lit-element.js'
import { repeat } from '/node_modules/lit-html/directives/repeat.js'

/*
 * Property of a DMX fixture
 */
class DmxFixtureProperty extends LitElement {

  static get properties() {
    return {
      name: { type: String },
      type: { type: String },
      channels: { type: Number },
      property: { type: Object }
    }
  }

  handleColorChange(e) {
    // Convert hex color to RGB
    const value = e.target.value.match(/[A-Za-z0-9]{2}/g).map(v => parseInt(v, 16))

    this.dispatchEvent(new CustomEvent('change', {
      detail: {
        value,
        name: this.name
      }
    }))
  }

  handleInputChange(e) {
    let { value } = e.target

    if (isNaN(value)) {
      // String
    } else {
      value = parseInt(value, 10)
    }

    this.dispatchEvent(new CustomEvent('change', {
      detail: {
        value,
        name: this.name
      }
    }))
  }

  handleSelectChange(e) {
    const [selectedOption] = e.target.selectedOptions
    const { value } = selectedOption

    this.dispatchEvent(new CustomEvent('change', {
      detail: {
        value,
        name: this.name
      }
    }))
  }

  render() {
    const { channels, property, name } = this

    return html`

      ${name}:

      ${
        property.isRgb
        ? html`<input type="color" @change="${e => this.handleColorChange(e)}">`
        : ''
      }

      ${
        property.isRange
        ? html`<input type="number" value="0" min="0" max="255" @change="${e => this.handleInputChange(e)}">`
        : ''
      }

      ${
        property.isMapped
        ? html`
          <select @change="${e => this.handleSelectChange(e)}">
            ${repeat(property.mapping, mapping => html`
              <option value="${mapping}">${mapping}</option>
            `)}
          </select>
        `
        : ''
      }

      ${
        property.isMultiRange
        ? html`<input type="text" @change="${e => this.handleInputChange(e)}" title="${property.mapping}">`
        : ''
      }

      ${
        property.isMultiRange
        ? html`<input type="number" @change="${e => this.handleInputChange(e)}" title="${property.min} to ${property.max}" min="${property.min}" max="${property.max}">`
        : ''
      }

    `
  }
}

customElements.define('dmx-fixture-property', DmxFixtureProperty)
<commit_msg>fix(dmx): Use hiRes instead of multiRange<commit_after>import { LitElement, html } from '/node_modules/@polymer/lit-element/lit-element.js'
import { repeat } from '/node_modules/lit-html/directives/repeat.js'

/*
 * Property of a DMX fixture
 */
class DmxFixtureProperty extends LitElement {

  static get properties() {
    return {
      name: { type: String },
      type: { type: String },
      channels: { type: Number },
      property: { type: Object }
    }
  }

  handleColorChange(e) {
    // Convert hex color to RGB
    const value = e.target.value.match(/[A-Za-z0-9]{2}/g).map(v => parseInt(v, 16))

    this.dispatchEvent(new CustomEvent('change', {
      detail: {
        value,
        name: this.name
      }
    }))
  }

  handleInputChange(e) {
    let { value } = e.target

    if (isNaN(value)) {
      // String
    } else {
      value = parseInt(value, 10)
    }

    this.dispatchEvent(new CustomEvent('change', {
      detail: {
        value,
        name: this.name
      }
    }))
  }

  handleSelectChange(e) {
    const [selectedOption] = e.target.selectedOptions
    const { value } = selectedOption

    this.dispatchEvent(new CustomEvent('change', {
      detail: {
        value,
        name: this.name
      }
    }))
  }

  render() {
    const { channels, property, name } = this

    return html`

      ${name}:

      ${
        property.isRgb
        ? html`<input type="color" @change="${e => this.handleColorChange(e)}">`
        : ''
      }

      ${
        property.isRange
        ? html`<input type="number" value="0" min="0" max="255" @change="${e => this.handleInputChange(e)}">`
        : ''
      }

      ${
        property.isMapped
        ? html`
          <select @change="${e => this.handleSelectChange(e)}">
            ${repeat(property.mapping, mapping => html`
              <option value="${mapping}">${mapping}</option>
            `)}
          </select>
        `
        : ''
      }

      ${
        property.isMultiRange
        ? html`<input type="text" @change="${e => this.handleInputChange(e)}" title="${JSON.stringify(property.mapping)}">`
        : ''
      }

      ${
        property.isHiRes
        ? html`<input type="number" @change="${e => this.handleInputChange(e)}" title="${property.min} to ${property.max}" min="${property.min}" max="${property.max}">`
        : ''
      }

    `
  }
}

customElements.define('dmx-fixture-property', DmxFixtureProperty)
<|endoftext|>


<commit_before>let chalk = require('chalk');

let errorMessages = {
	init: 'An error occurred while creating the application!',
	start: 'An error occurred while starting the application! Are you in a wrong folder, maybe?',
	build: 'An error occurred while building the application! Are you in a wrong folder, maybe?',
	deploy: 'An error occurred while deploying the application! Are you in a wrong folder, maybe?'
};

module.exports = function (type, details) {
	let message = errorMessages[type];

	console.log(chalk.red.bold(message));

	if (details) {
		console.log();
		console.log(chalk.bgRed.bold('Error details:'));
		console.log();
		console.log(details);
	}
};<commit_msg>support for proxy messages in error handler.<commit_after>let chalk = require('chalk');

let errorMessages = {
	init: 'An error occurred while creating the application!',
	start: 'An error occurred while starting the application! Are you in a wrong folder, maybe?',
	build: 'An error occurred while building the application! Are you in a wrong folder, maybe?',
	deploy: 'An error occurred while deploying the application! Are you in a wrong folder, maybe?',
	proxy: 'An error occurred while configuring proxy rules!'
};

module.exports = function (type, details) {
	let message = errorMessages[type];

	console.log(chalk.red.bold(message));

	if (details) {
		console.log();
		console.log(chalk.bgRed.bold('Error details:'));
		console.log();
		console.log(details);
	}
};<|endoftext|>


<commit_before>import React, { Component } from 'react'
import styles from './JoinedChannelList.scss'
import { connect } from 'react-redux'

@connect(
  state => ({
    channels: state.channels,
  })
)
export default class JoinedChannelList extends Component {

  render() {

    const content = no joined channels

    return (

        {content}

    )
  }
}
<commit_msg>show joined channellist<commit_after>import React, { Component } from 'react'
import styles from './JoinedChannelList.scss'
import { connect } from 'react-redux'

@connect(
  state => ({
    channels: state.channels,
  })
)
export default class JoinedChannelList extends Component {

  render() {

    const content = this.props.channels.joined !== undefined ?

        { this.props.channels.joined.map( (channelId) =>
{this.props.channels.list[channelId].name} ({this.props.channels.list[channelId].member_count})
 )}

      : no joined channels

    return (

        {content}

    )
  }
}
<|endoftext|>


<commit_before>import {EventEmitter} from 'events'
import {lookupPretty} from 'ipfs-geoip'

export default class StatsPoller extends EventEmitter {
  constructor (ipfs, logger) {
    super()
    this.ipfs = ipfs
    this.logger = logger
    this.shouldPoll = false
    this.statsCache = {}
    this.locationsCache = {}
  }

  _poller () {
    const next = () => setTimeout(() => this._poller(), 1000)

    if (!this.shouldPoll) {
      return
    }

    this.ipfs.swarm.peers()
      .then((res) => {
        res = res.sort((a, b) => a.peer.toB58String() > b.peer.toB58String())

        let peers = []

        res.forEach((rawPeer) => {
          let peer = {
            id: rawPeer.peer.toB58String(),
            addr: rawPeer.addr.toString(),
            location: {
              formatted: 'Unknown'
            }
          }

          if (!this.locationsCache[peer.id]) {
            lookupPretty(this.ipfs, [peer.addr], (err, result) => {
              if (err) { return }
              this.locationsCache[peer.id] = result
            })
          } else {
            peer.location = this.locationsCache[peer.id]
          }

          peers.push(peer)
        })

        this.statsCache.peers = peers
        this.emit('change', this.statsCache)
      }, this.logger.error)
      .then(next)

    this.ipfs.id()
      .then((peer) => {
        this.statsCache.node = peer
        this.statsCache.node.location = 'Unknown'

        lookupPretty(this.ipfs, peer.addresses, (err, location) => {
          if (err) { return }

          this.statsCache.node.location = location && location.formatted
          this.emit('change', this.statsCache)
        })
      })
      .catch(this.logger.error)

    this.ipfs.stats.bw()
      .then(stats => {
        this.statsCache.bw = stats
        this.emit('change', this.statsCache)
      })
      .catch(this.logger.error)

    this.ipfs.repo.stat()
      .then(repo => {
        this.statsCache.repo = repo
        this.emit('change', this.statsCache)
      })
      .catch(this.logger.error)
  }

  stop () {
    this.shouldPoll = false
  }

  start () {
    this.shouldPoll = true
    this._poller()
  }
}
<commit_msg>refactor: improve and simplify poller<commit_after>import {EventEmitter} from 'events'
import {lookupPretty} from 'ipfs-geoip'

export default class StatsPoller extends EventEmitter {
  constructor (ipfs, logger) {
    super()
    this.ipfs = ipfs
    this.logger = logger
    this.shouldPoll = false
    this.statsCache = {}
    this.locationsCache = {}
  }

  _poller () {
    if (!this.shouldPoll) {
      return
    }

    Promise.all([
      this.ipfs.id(),
      this.ipfs.swarm.peers(),
      this.ipfs.stats.bw(),
      this.ipfs.repo.stat()
    ]).then(([id, peers, bw, repo]) => {
      this.statsCache.bw = bw
      this.statsCache.repo = repo
      this._handleId(id)
      this._handlePeers(peers)

      this.emit('change', this.statsCache)

      setTimeout(() => {
        this._poller()
      }, 1000)
    }).catch(this.logger.error)
  }

  _handlePeers (raw) {
    const peers = []
    raw = raw.sort((a, b) => a.peer.toB58String() > b.peer.toB58String())

    raw.forEach((rawPeer) => {
      let peer = {
        id: rawPeer.peer.toB58String(),
        addr: rawPeer.addr.toString(),
        location: {
          formatted: 'Unknown'
        }
      }

      if (!this.locationsCache[peer.id]) {
        lookupPretty(this.ipfs, [peer.addr], (err, result) => {
          if (err) { return }
          this.locationsCache[peer.id] = result
        })
      } else {
        peer.location = this.locationsCache[peer.id]
      }

      peers.push(peer)
    })

    this.statsCache.peers = peers
    this.emit('change', this.statsCache)
  }

  _handleId (raw) {
    this.statsCache.node = raw
    this.statsCache.node.location = 'Unknown'

    lookupPretty(this.ipfs, raw.addresses, (err, location) => {
      if (err) { return }

      this.statsCache.node.location = location && location.formatted
      this.emit('change', this.statsCache)
    })
  }

  stop () {
    this.shouldPoll = false
  }

  start () {
    this.shouldPoll = true
    this._poller()
  }
}
<|endoftext|>