<commit_before>import React from 'react';
import { c, msgid } from 'ttag';
import { PLANS, PLAN_TYPES, CYCLE } from 'proton-shared/lib/constants';
import { Info } from 'react-components';
import freePlanSvg from 'design-system/assets/img/pv-images/plans/free.svg';
import basicPlanSvg from 'design-system/assets/img/pv-images/plans/basic.svg';
import plusPlanSvg from 'design-system/assets/img/pv-images/plans/plus.svg';
import visionaryPlanSvg from 'design-system/assets/img/pv-images/plans/visionary.svg';

export const PLAN = {
    FREE: 'free',
    BUNDLE_PLUS: 'plus_vpnplus',
    VISIONARY: PLANS.VISIONARY,
    BASIC: PLANS.VPNBASIC,
    PLUS: PLANS.VPNPLUS,
};

export const PLAN_NAMES = {
    [PLAN.BUNDLE_PLUS]: 'Mail+VPN Plus',
    [PLAN.FREE]: 'Free',
    [PLAN.VISIONARY]: 'Visionary',
    [PLAN.BASIC]: 'Basic',
    [PLAN.PLUS]: 'Plus',
};

export const PLAN_BUNDLES = {
    [PLAN.BUNDLE_PLUS]: [PLANS.PLUS, PLANS.VPNPLUS],
};

export const VPN_PLANS = [PLAN.FREE, PLAN.BASIC, PLAN.PLUS, PLAN.VISIONARY];
export const BEST_DEAL_PLANS = [PLAN.BASIC, PLAN.PLUS, PLAN.VISIONARY];

const getPlanFeatures = (plan, maxConnections, countries) =>
    ({
        [PLAN.FREE]: {
            image: <img width={13} src={freePlanSvg} alt={`${PLAN_NAMES[PLAN.FREE]} plan`} />,
            description: c('Plan Description').t`Privacy and security for everyone`,
            upsell: {
                planName: PLAN.BASIC,
                features: [
                    c('Plan Feature').t`High speed 1 Gbps VPN servers`,
                    c('Plan Feature').t`Access to ${countries.basic.length} countries`,
                    c('Plan Feature').t`Filesharing/P2P support`,
                ],
            },
            features: [
                c('Plan Feature').ngettext(
                    msgid`${maxConnections} VPN connection`,
                    `${maxConnections} VPN connections`,
                    maxConnections
                ),
                c('Plan Feature').t`Servers in ${countries.free.length} countries`,
                c('Plan Feature').t`Medium speed`,
                c('Plan Feature').t`No logs/No ads`,
                <>
                    {c('Plan feature').t`P2P filesharing/BitTorrent support`}
                    <Info
                        title={c('Info').t`Support for file sharing protocols such as BitTorrent.`}
                        url="https://protonvpn.com/support/p2p-vpn-redirection/"
                    />
                </>,
                <>
                    {c('Plan feature').t`Secure Core VPN`}
                    <Info
                        title={c('Info')
                            .t`Defends against threats to VPN privacy by passing your Internet traffic through multiple servers.`}
                        url="https://protonvpn.com/support/secure-core-vpn/"
                    />
                </>,
                <>
                    {c('Plan feature').t`Tor over VPN`}
                    <Info
                        title={c('Info').t`Route your Internet traffic through the Tor network with a single click.`}
                        url="https://protonvpn.com/support/tor-vpn/"
                    />
                </>,
                <>
                    {c('Plan feature').t`Access blocked content`}
                    <Info
                        title={c('Info')
                            .t`Access geo-blocked content (Netflix, Amazon Prime Video, BBC iPlayer, Wikipedia, Facebook, Youtube, etc) no matter where you are.`}
                        url="https://protonvpn.com/support/streaming-guide/"
                    />
                </>,
            ],
        },
        [PLAN.BASIC]: {
            image: <img width={60} src={basicPlanSvg} alt={`${PLAN_NAMES[PLAN.BASIC]} plan`} />,
            description: c('Plan Description').t`Basic privacy features`,
            upsell: {
                planName: PLAN.PLUS,
                features: [
                    c('Plan Feature').t`Highest speed servers (10 Gbps)`,
                    c('Plan Feature').t`Access blocked content`,
                    c('Plan Feature').t`All advanced security features`,
                ],
            },
            features: [
                c('Plan Feature').ngettext(
                    msgid`${maxConnections} VPN connection`,
                    `${maxConnections} VPN connections`,
                    maxConnections
                ),
                c('Plan Feature').t`Servers in ${countries.basic.length} countries`,
                c('Plan Feature').t`High speed`,
                c('Plan Feature').t`No logs/No ads`,
                <>
                    {c('Plan feature').t`P2P filesharing/BitTorrent support`}
                    <Info
                        title={c('Info').t`Support for file sharing protocols such as BitTorrent.`}
                        url="https://protonvpn.com/support/p2p-vpn-redirection/"
                    />
                </>,
                <>
                    {c('Plan feature').t`Secure Core VPN`}
                    <Info
                        title={c('Info')
                            .t`Defends against threats to VPN privacy by passing your Internet traffic through multiple servers.`}
                        url="https://protonvpn.com/support/secure-core-vpn/"
                    />
                </>,
                <>
                    {c('Plan feature').t`Tor over VPN`}
                    <Info
                        title={c('Info').t`Route your Internet traffic through the Tor network with a single click.`}
                        url="https://protonvpn.com/support/tor-vpn/"
                    />
                </>,
                <>
                    {c('Plan feature').t`Access blocked content`}
                    <Info
                        title={c('Info')
                            .t`Access geo-blocked content (Netflix, Amazon Prime Video, BBC iPlayer, Wikipedia, Facebook, Youtube, etc) no matter where you are.`}
                        url="https://protonvpn.com/support/streaming-guide/"
                    />
                </>,
            ],
        },
        [PLAN.PLUS]: {
            image: <img width={60} src={plusPlanSvg} alt={`${PLAN_NAMES[PLAN.PLUS]} plan`} />,
            isBest: true,
            description: c('Plan Description').t`Advanced security features`,
            features: [
                c('Plan Feature').ngettext(
                    msgid`${maxConnections} VPN connection`,
                    `${maxConnections} VPN connections`,
                    maxConnections
                ),
                c('Plan Feature').t`Servers in ${countries.all.length} countries`,
                c('Plan Feature').t`Highest speed (up to 10Gbps)`,
                c('Plan Feature').t`No logs/No ads`,
                <>
                    {c('Plan feature').t`P2P filesharing/BitTorrent support`}
                    <Info
                        title={c('Info').t`Support for file sharing protocols such as BitTorrent.`}
                        url="https://protonvpn.com/support/p2p-vpn-redirection/"
                    />
                </>,
                <>
                    {c('Plan feature').t`Secure Core VPN`}
                    <Info
                        title={c('Info')
                            .t`Defends against threats to VPN privacy by passing your Internet traffic through multiple servers.`}
                        url="https://protonvpn.com/support/secure-core-vpn/"
                    />
                </>,
                <>
                    {c('Plan feature').t`Tor over VPN`}
                    <Info
                        title={c('Info').t`Route your Internet traffic through the Tor network with a single click.`}
                        url="https://protonvpn.com/support/tor-vpn/"
                    />
                </>,
                <>
                    {c('Plan feature').t`Access blocked content`}
                    <Info
                        title={c('Info')
                            .t`Access geo-blocked content (Netflix, Amazon Prime Video, BBC iPlayer, Wikipedia, Facebook, Youtube, etc) no matter where you are.`}
                        url="https://protonvpn.com/support/streaming-guide/"
                    />
                </>,
            ],
        },
        [PLAN.VISIONARY]: {
            image: <img width={100} src={visionaryPlanSvg} alt={`${PLAN_NAMES[PLAN.VISIONARY]} plan`} />,
            description: c('Plan Description').t`The complete privacy suite`,
            features: [
                c('Plan feature').t`All Plus plan features`,
                c('Plan Feature').ngettext(
                    msgid`${maxConnections} VPN connection`,
                    `${maxConnections} VPN connections`,
                    maxConnections
                ),
                <>
                    {c('Plan Feature').t`ProtonMail Visionary account`}
                    <Info
                        title={c('Info')
                            .t`Get access to all the paid features for both ProtonVPN and ProtonMail (the encrypted email service that millions use to protect their data) with one plan.`}
                        url="https://protonmail.com"
                    />
                </>,
            ],
        },
        [PLAN.BUNDLE_PLUS]: {
            image: <img width={100} src={visionaryPlanSvg} alt={`${PLAN_NAMES[PLAN.VISIONARY]} plan`} />,
            description: c('Plan Description').t`Bundle plan`,
            features: [
                c('Plan Feature').ngettext(
                    msgid`${maxConnections} VPN connection`,
                    `${maxConnections} VPN connections`,
                    maxConnections
                ),
                c('Plan Feature').t`Servers in ${countries.all.length} countries`,
                c('Plan Feature').t`Highest speed (10Gbps)`,
                c('Plan Feature').t`No logs/No ads`,
                <>
                    {c('Plan feature').t`P2P filesharing/BitTorrent support`}
                    <Info
                        title={c('Info').t`Support for file sharing protocols such as BitTorrent.`}
                        url="https://protonvpn.com/support/p2p-vpn-redirection/"
                    />
                </>,
                <>
                    {c('Plan feature').t`Secure Core VPN`}
                    <Info
                        title={c('Info')
                            .t`Defends against threats to VPN privacy by passing your Internet traffic through multiple servers.`}
                        url="https://protonvpn.com/support/secure-core-vpn/"
                    />
                </>,
                <>
                    {c('Plan feature').t`Tor over VPN`}
                    <Info
                        title={c('Info').t`Route your Internet traffic through the Tor network with a single click.`}
                        url="https://protonvpn.com/support/tor-vpn/"
                    />
                </>,
                <>
                    {c('Plan feature').t`Access blocked content`}
                    <Info
                        title={c('Info')
                            .t`Access geo-blocked content (Netflix, Amazon Prime Video, BBC iPlayer, Wikipedia, Facebook, Youtube, etc) no matter where you are.`}
                        url="https://protonvpn.com/support/streaming-guide/"
                    />
                </>,
                <>
                    {c('Plan Feature').t`ProtonMail Plus account`}
                    <Info
                        title={c('Info')
                            .t`Get access to all the paid features for both ProtonVPN and ProtonMail (the encrypted email service that millions use to protect their data) with one plan.`}
                        url="https://protonmail.com"
                    />
                </>,
            ],
        },
    }[plan]);

// To use coupon, AmountDue from coupon must be merged into plan.
const getPlanPrice = (plan, cycle) => {
    const monthly = plan.Pricing[CYCLE.MONTHLY];
    const cyclePrice = plan.Pricing[cycle];
    const adjustedTotal = plan.AmountDue;

    const total = typeof adjustedTotal !== 'undefined' ? adjustedTotal : cyclePrice;
    const saved = monthly * cycle - cyclePrice;
    const totalMonthly = total / cycle;

    return { monthly, total, totalMonthly, saved };
};

export const getPlan = (planName, cycle, plans = [], countries = []) => {
    const plan = plans.find(({ Type, Name }) => Type === PLAN_TYPES.PLAN && Name === planName);
    const price = (plan && getPlanPrice(plan, cycle)) || { monthly: 0, total: 0, totalMonthly: 0, saved: 0 };

    return {
        ...getPlanFeatures(planName, plan ? plan.MaxVPN || 0 : 1, countries),
        planName,
        title: PLAN_NAMES[planName],
        ID: plan && plan.ID,
        disabled: !plan && planName !== PLAN.FREE,
        price,
        couponDiscount:
            plan && typeof plan.AmountDue !== 'undefined' ? Math.abs(price.monthly * cycle - plan.AmountDue) : 0,
        couponDescription: plan && plan.CouponDescription,
    };
};
<commit_msg>Add ProtonDrive row in subscription details<commit_after>import React from 'react';
import { c, msgid } from 'ttag';
import { PLANS, PLAN_TYPES, CYCLE } from 'proton-shared/lib/constants';
import { Info } from 'react-components';
import freePlanSvg from 'design-system/assets/img/pv-images/plans/free.svg';
import basicPlanSvg from 'design-system/assets/img/pv-images/plans/basic.svg';
import plusPlanSvg from 'design-system/assets/img/pv-images/plans/plus.svg';
import visionaryPlanSvg from 'design-system/assets/img/pv-images/plans/visionary.svg';

export const PLAN = {
    FREE: 'free',
    BUNDLE_PLUS: 'plus_vpnplus',
    VISIONARY: PLANS.VISIONARY,
    BASIC: PLANS.VPNBASIC,
    PLUS: PLANS.VPNPLUS,
};

export const PLAN_NAMES = {
    [PLAN.BUNDLE_PLUS]: 'Mail+VPN Plus',
    [PLAN.FREE]: 'Free',
    [PLAN.VISIONARY]: 'Visionary',
    [PLAN.BASIC]: 'Basic',
    [PLAN.PLUS]: 'Plus',
};

export const PLAN_BUNDLES = {
    [PLAN.BUNDLE_PLUS]: [PLANS.PLUS, PLANS.VPNPLUS],
};

export const VPN_PLANS = [PLAN.FREE, PLAN.BASIC, PLAN.PLUS, PLAN.VISIONARY];
export const BEST_DEAL_PLANS = [PLAN.BASIC, PLAN.PLUS, PLAN.VISIONARY];

const getPlanFeatures = (plan, maxConnections, countries) =>
    ({
        [PLAN.FREE]: {
            image: <img width={13} src={freePlanSvg} alt={`${PLAN_NAMES[PLAN.FREE]} plan`} />,
            description: c('Plan Description').t`Privacy and security for everyone`,
            upsell: {
                planName: PLAN.BASIC,
                features: [
                    c('Plan Feature').t`High speed 1 Gbps VPN servers`,
                    c('Plan Feature').t`Access to ${countries.basic.length} countries`,
                    c('Plan Feature').t`Filesharing/P2P support`,
                ],
            },
            features: [
                c('Plan Feature').ngettext(
                    msgid`${maxConnections} VPN connection`,
                    `${maxConnections} VPN connections`,
                    maxConnections
                ),
                c('Plan Feature').t`Servers in ${countries.free.length} countries`,
                c('Plan Feature').t`Medium speed`,
                c('Plan Feature').t`No logs/No ads`,
                <>
                    {c('Plan feature').t`P2P filesharing/BitTorrent support`}
                    <Info
                        title={c('Info').t`Support for file sharing protocols such as BitTorrent.`}
                        url="https://protonvpn.com/support/p2p-vpn-redirection/"
                    />
                </>,
                <>
                    {c('Plan feature').t`Secure Core VPN`}
                    <Info
                        title={c('Info')
                            .t`Defends against threats to VPN privacy by passing your Internet traffic through multiple servers.`}
                        url="https://protonvpn.com/support/secure-core-vpn/"
                    />
                </>,
                <>
                    {c('Plan feature').t`Tor over VPN`}
                    <Info
                        title={c('Info').t`Route your Internet traffic through the Tor network with a single click.`}
                        url="https://protonvpn.com/support/tor-vpn/"
                    />
                </>,
                <>
                    {c('Plan feature').t`Access blocked content`}
                    <Info
                        title={c('Info')
                            .t`Access geo-blocked content (Netflix, Amazon Prime Video, BBC iPlayer, Wikipedia, Facebook, Youtube, etc) no matter where you are.`}
                        url="https://protonvpn.com/support/streaming-guide/"
                    />
                </>,
            ],
        },
        [PLAN.BASIC]: {
            image: <img width={60} src={basicPlanSvg} alt={`${PLAN_NAMES[PLAN.BASIC]} plan`} />,
            description: c('Plan Description').t`Basic privacy features`,
            upsell: {
                planName: PLAN.PLUS,
                features: [
                    c('Plan Feature').t`Highest speed servers (10 Gbps)`,
                    c('Plan Feature').t`Access blocked content`,
                    c('Plan Feature').t`All advanced security features`,
                ],
            },
            features: [
                c('Plan Feature').ngettext(
                    msgid`${maxConnections} VPN connection`,
                    `${maxConnections} VPN connections`,
                    maxConnections
                ),
                c('Plan Feature').t`Servers in ${countries.basic.length} countries`,
                c('Plan Feature').t`High speed`,
                c('Plan Feature').t`No logs/No ads`,
                <>
                    {c('Plan feature').t`P2P filesharing/BitTorrent support`}
                    <Info
                        title={c('Info').t`Support for file sharing protocols such as BitTorrent.`}
                        url="https://protonvpn.com/support/p2p-vpn-redirection/"
                    />
                </>,
                <>
                    {c('Plan feature').t`Secure Core VPN`}
                    <Info
                        title={c('Info')
                            .t`Defends against threats to VPN privacy by passing your Internet traffic through multiple servers.`}
                        url="https://protonvpn.com/support/secure-core-vpn/"
                    />
                </>,
                <>
                    {c('Plan feature').t`Tor over VPN`}
                    <Info
                        title={c('Info').t`Route your Internet traffic through the Tor network with a single click.`}
                        url="https://protonvpn.com/support/tor-vpn/"
                    />
                </>,
                <>
                    {c('Plan feature').t`Access blocked content`}
                    <Info
                        title={c('Info')
                            .t`Access geo-blocked content (Netflix, Amazon Prime Video, BBC iPlayer, Wikipedia, Facebook, Youtube, etc) no matter where you are.`}
                        url="https://protonvpn.com/support/streaming-guide/"
                    />
                </>,
            ],
        },
        [PLAN.PLUS]: {
            image: <img width={60} src={plusPlanSvg} alt={`${PLAN_NAMES[PLAN.PLUS]} plan`} />,
            isBest: true,
            description: c('Plan Description').t`Advanced security features`,
            features: [
                c('Plan Feature').ngettext(
                    msgid`${maxConnections} VPN connection`,
                    `${maxConnections} VPN connections`,
                    maxConnections
                ),
                c('Plan Feature').t`Servers in ${countries.all.length} countries`,
                c('Plan Feature').t`Highest speed (up to 10Gbps)`,
                c('Plan Feature').t`No logs/No ads`,
                <>
                    {c('Plan feature').t`P2P filesharing/BitTorrent support`}
                    <Info
                        title={c('Info').t`Support for file sharing protocols such as BitTorrent.`}
                        url="https://protonvpn.com/support/p2p-vpn-redirection/"
                    />
                </>,
                <>
                    {c('Plan feature').t`Secure Core VPN`}
                    <Info
                        title={c('Info')
                            .t`Defends against threats to VPN privacy by passing your Internet traffic through multiple servers.`}
                        url="https://protonvpn.com/support/secure-core-vpn/"
                    />
                </>,
                <>
                    {c('Plan feature').t`Tor over VPN`}
                    <Info
                        title={c('Info').t`Route your Internet traffic through the Tor network with a single click.`}
                        url="https://protonvpn.com/support/tor-vpn/"
                    />
                </>,
                <>
                    {c('Plan feature').t`Access blocked content`}
                    <Info
                        title={c('Info')
                            .t`Access geo-blocked content (Netflix, Amazon Prime Video, BBC iPlayer, Wikipedia, Facebook, Youtube, etc) no matter where you are.`}
                        url="https://protonvpn.com/support/streaming-guide/"
                    />
                </>,
            ],
        },
        [PLAN.VISIONARY]: {
            image: <img width={100} src={visionaryPlanSvg} alt={`${PLAN_NAMES[PLAN.VISIONARY]} plan`} />,
            description: c('Plan Description').t`The complete privacy suite`,
            features: [
                c('Plan feature').t`All Plus plan features`,
                c('Plan Feature').ngettext(
                    msgid`${maxConnections} VPN connection`,
                    `${maxConnections} VPN connections`,
                    maxConnections
                ),
                <>
                    {c('Plan Feature').t`ProtonMail Visionary account`}
                    <Info
                        title={c('Info')
                            .t`Get access to all the paid features for both ProtonVPN and ProtonMail (the encrypted email service that millions use to protect their data) with one plan.`}
                        url="https://protonmail.com"
                    />
                </>,
            ],
        },
        [PLAN.BUNDLE_PLUS]: {
            image: <img width={100} src={visionaryPlanSvg} alt={`${PLAN_NAMES[PLAN.VISIONARY]} plan`} />,
            description: c('Plan Description').t`Bundle plan`,
            features: [
                c('Plan Feature').ngettext(
                    msgid`${maxConnections} VPN connection`,
                    `${maxConnections} VPN connections`,
                    maxConnections
                ),
                c('Plan Feature').t`Servers in ${countries.all.length} countries`,
                c('Plan Feature').t`Highest speed (10Gbps)`,
                c('Plan Feature').t`No logs/No ads`,
                <>
                    {c('Plan feature').t`P2P filesharing/BitTorrent support`}
                    <Info
                        title={c('Info').t`Support for file sharing protocols such as BitTorrent.`}
                        url="https://protonvpn.com/support/p2p-vpn-redirection/"
                    />
                </>,
                <>
                    {c('Plan feature').t`Secure Core VPN`}
                    <Info
                        title={c('Info')
                            .t`Defends against threats to VPN privacy by passing your Internet traffic through multiple servers.`}
                        url="https://protonvpn.com/support/secure-core-vpn/"
                    />
                </>,
                <>
                    {c('Plan feature').t`Tor over VPN`}
                    <Info
                        title={c('Info').t`Route your Internet traffic through the Tor network with a single click.`}
                        url="https://protonvpn.com/support/tor-vpn/"
                    />
                </>,
                <>
                    {c('Plan feature').t`Access blocked content`}
                    <Info
                        title={c('Info')
                            .t`Access geo-blocked content (Netflix, Amazon Prime Video, BBC iPlayer, Wikipedia, Facebook, Youtube, etc) no matter where you are.`}
                        url="https://protonvpn.com/support/streaming-guide/"
                    />
                </>,
                <>
                    {c('Plan Feature').t`ProtonMail Plus account`}
                    <Info
                        title={c('Info')
                            .t`Get access to all the paid features for both ProtonVPN and ProtonMail (the encrypted email service that millions use to protect their data) with one plan.`}
                        url="https://protonmail.com"
                    />
                </>,
                <>
                    ProtonDrive
                    {c('Price').t`FREE`}
                </>,
            ],
        },
    }[plan]);

// To use coupon, AmountDue from coupon must be merged into plan.
const getPlanPrice = (plan, cycle) => {
    const monthly = plan.Pricing[CYCLE.MONTHLY];
    const cyclePrice = plan.Pricing[cycle];
    const adjustedTotal = plan.AmountDue;

    const total = typeof adjustedTotal !== 'undefined' ? adjustedTotal : cyclePrice;
    const saved = monthly * cycle - cyclePrice;
    const totalMonthly = total / cycle;

    return { monthly, total, totalMonthly, saved };
};

export const getPlan = (planName, cycle, plans = [], countries = []) => {
    const plan = plans.find(({ Type, Name }) => Type === PLAN_TYPES.PLAN && Name === planName);
    const price = (plan && getPlanPrice(plan, cycle)) || { monthly: 0, total: 0, totalMonthly: 0, saved: 0 };

    return {
        ...getPlanFeatures(planName, plan ? plan.MaxVPN || 0 : 1, countries),
        planName,
        title: PLAN_NAMES[planName],
        ID: plan && plan.ID,
        disabled: !plan && planName !== PLAN.FREE,
        price,
        couponDiscount:
            plan && typeof plan.AmountDue !== 'undefined' ? Math.abs(price.monthly * cycle - plan.AmountDue) : 0,
        couponDescription: plan && plan.CouponDescription,
    };
};
<|endoftext|>


<commit_before>"use strict";

const ImageData   = require("./ImageData");
const gm = require("gm").subClass({ imageMagick: true });

const cropSpec = /(\d+)x(\d+)([+-]\d+)?([+-]\d+)?(%)?/;

class ImageResizer {

    /**
     * Image Resizer
     * resize image with ImageMagick
     *
     * @constructor
     * @param Number width
     */
    constructor(options) {
        this.options = options;
    }

    /**
     * Execute resize
     *
     * @public
     * @param ImageData image
     * @return Promise
     */
    exec(image) {
        const acl = this.options.acl;

        const width = this.options.size || this.options.width;
        const height = this.options.size || this.options.height;

        return new Promise((resolve, reject) => {
            var img = gm(image.data).geometry(this.options.size.toString());
            if ( "gravity" in this.options ) {
                img = img.gravity(this.options.gravity);
            }
            if ( "crop" in this.options ) {
                var cropArgs = this.options.crop.match(cropSpec);
                const cropWidth = cropArgs[1];
                const cropHeight = cropArgs[2];
                const cropX = cropArgs[3];
                const cropY = cropArgs[4];
                const cropPercent = cropArgs[5];
                img = img.crop(cropWidth, cropHeight, cropX, cropY, cropPercent === "%");
            }

            function toBufferHandler(err, buffer) {
                if (err) {
                    reject(err);
                } else {
                    resolve(new ImageData(
                        image.fileName,
                        image.bucketName,
                        buffer,
                        image.headers,
                        acl
                    ));
                }
            }

            if ( "format" in this.options ) {
              img.toBuffer(this.options.format, toBufferHandler);
            } else {
              img.toBuffer(toBufferHandler);
            }
        });
    }
}

module.exports = ImageResizer;
<commit_msg>Fix indent<commit_after>"use strict";

const ImageData   = require("./ImageData");
const gm = require("gm").subClass({ imageMagick: true });

const cropSpec = /(\d+)x(\d+)([+-]\d+)?([+-]\d+)?(%)?/;

class ImageResizer {

    /**
     * Image Resizer
     * resize image with ImageMagick
     *
     * @constructor
     * @param Number width
     */
    constructor(options) {
        this.options = options;
    }

    /**
     * Execute resize
     *
     * @public
     * @param ImageData image
     * @return Promise
     */
    exec(image) {
        const acl = this.options.acl;

        const width = this.options.size || this.options.width;
        const height = this.options.size || this.options.height;

        return new Promise((resolve, reject) => {
            var img = gm(image.data).geometry(this.options.size.toString());
            if ( "gravity" in this.options ) {
                img = img.gravity(this.options.gravity);
            }
            if ( "crop" in this.options ) {
                var cropArgs = this.options.crop.match(cropSpec);
                const cropWidth = cropArgs[1];
                const cropHeight = cropArgs[2];
                const cropX = cropArgs[3];
                const cropY = cropArgs[4];
                const cropPercent = cropArgs[5];
                img = img.crop(cropWidth, cropHeight, cropX, cropY, cropPercent === "%");
            }

            function toBufferHandler(err, buffer) {
                if (err) {
                    reject(err);
                } else {
                    resolve(new ImageData(
                        image.fileName,
                        image.bucketName,
                        buffer,
                        image.headers,
                        acl
                    ));
                }
            }

            if ( "format" in this.options ) {
                img.toBuffer(this.options.format, toBufferHandler);
            } else {
                img.toBuffer(toBufferHandler);
            }
        });
    }
}

module.exports = ImageResizer;
<|endoftext|>


<commit_before>"use strict";
var md = require("html-md");
var _ = require("lodash");

module.exports = function ($) {

  var makrdown = md($(".mw-content-ltr").html(), {
    inline: true
  });

  return getDestinations(makrdown);
};

var re = "\\[([^\\[]+)\\]\\(([^\\)]+)\\)";
var linksRe = new RegExp(re, "g");
var linksInfoRe = new RegExp(re);

function getLinkStrings(line) {
  return line.match(linksRe) || [];
}

function getLinkInfo(linkString) {
  var info = linkString.match(linksInfoRe) || [];

  if (info[2]) {
    info[2] = info[2].substring(0, info[2].indexOf(" "));
  }
  return info;
}

function hasValidLinks(links) {
  return links.length === 2 && links[0][1] && links[0][2] && links[1][1] && links[1][2];
}

function getDestination(line) {
  var links = getLinkStrings(line).map(getLinkInfo);

  if (hasValidLinks(links)) {
    return {
      city: {
        name: links[0][1],
        url: links[0][2]
      },
      airport: {
        name: links[1][1],
        url: links[1][2]
      }
    };
  }
}

function getDestinations(makrdown) {
  return _.reduce(makrdown.split("\n"), function (destinations, line) {
    var destination = getDestination(line);

    if (destination) {
      destinations.push(destination);
    }
    return destinations;
  }, []);
}

module.exports.getDestination = getDestination;
module.exports.getDestinations = getDestinations;
module.exports.hasValidLinks = hasValidLinks;
module.exports.getLinkInfo = getLinkInfo;
module.exports.getLinkStrings = getLinkStrings;
<commit_msg>moving variables inside functions to avoid globals and use them on test<commit_after>"use strict";
var md = require("html-md");
var _ = require("lodash");

module.exports = function ($) {

  var makrdown = md($(".mw-content-ltr").html(), {
    inline: true
  });

  return getDestinations(makrdown);
};

var re = "\\[([^\\[]+)\\]\\(([^\\)]+)\\)";

function getLinkStrings(line) {
  var linksRe = new RegExp(re, "g");

  return line.match(linksRe) || [];
}

function getLinkInfo(linkString) {
  console.log(linkString);
  var linksInfoRe = new RegExp(re);
  var info = linkString.match(linksInfoRe) || [];

  if (info[2]) {
    info[2] = info[2].substring(0, info[2].indexOf(" "));
  }
  return info;
}

function hasValidLinks(links) {
  return links.length === 2 && links[0][1] && links[0][2] && links[1][1] && links[1][2];
}

function getDestination(line) {
  var links = getLinkStrings(line).map(getLinkInfo);

  if (hasValidLinks(links)) {
    return {
      city: {
        name: links[0][1],
        url: links[0][2]
      },
      airport: {
        name: links[1][1],
        url: links[1][2]
      }
    };
  }
}

function getDestinations(makrdown) {
  return _.reduce(makrdown.split("\n"), function (destinations, line) {
    var destination = getDestination(line);

    if (destination) {
      destinations.push(destination);
    }
    return destinations;
  }, []);
}

module.exports.getDestination = getDestination;
module.exports.getDestinations = getDestinations;
module.exports.hasValidLinks = hasValidLinks;
module.exports.getLinkInfo = getLinkInfo;
module.exports.getLinkStrings = getLinkStrings;
<|endoftext|>


<commit_before>module.exports = function() {
    var now = new Date();
    // console.log(now.toISOString());
    var offset = now.getTimezoneOffset();
    var next = new Date(now).setSeconds(0);
    next = new Date(next).setMinutes(0);
    next = new Date(next).setHours(19 + offset / 60);
    next = new Date(next).setDate(now.getDate() + 7 - now.getDay() + 3);
    // console.log(new Date(next).toISOString());

    var diff = next - Date.now() - offset * 60 * 1000;

    var days = (1000 * 60 * 60 * 24);
    var hours = (60 * 60 * 1000);
    var minutes = (60 * 1000);
    var seconds = 1000;

    var daysLeft = Math.floor(diff / days);
    var hoursLeft = Math.floor(((diff - daysLeft * days) % days) / hours);
    var minutesLeft = Math.floor(((diff - daysLeft * days - hoursLeft * hours) % hours) / minutes);
    var secondsLeft = Math.floor(((diff - daysLeft * days - hoursLeft * hours - minutesLeft * minutes) % minutes) / seconds);

    var tMinus = [daysLeft, hoursLeft, minutesLeft, secondsLeft];

    tMinus.forEach(function(el) {
      return (el < 10) ? "0" + el : el;
    });

    var tMinusDescriptors = ["дней", "часов", "минут", "секунд"];

    tMinusDescriptors[0] = (tMinus[0] >= 1 && tMinus[0] <= 4) ? "дня" : "дней";
    tMinusDescriptors[1] = (tMinus[1] % 10 === 1) ? "час" : "часов";
    tMinusDescriptors[1] = (tMinus[1] % 10 > 1 && tMinus[1] % 10 <= 4) ? "часа" : "часов";
    tMinusDescriptors[2] = (tMinus[2] % 10 === 1) ? "минута" : "минут";
    tMinusDescriptors[2] = (tMinus[2] % 10 > 1 && tMinus[2] % 10 <= 4) ? "минуты" : "минут";
    tMinusDescriptors[3] = (tMinus[3] % 10 === 1) ? "секунда" : "секунд";
    tMinusDescriptors[3] = (tMinus[3] % 10 > 1 && tMinus[3] % 10 <= 4) ? "секунды" : "секунд";

    console.log("T minus " + tMinus[0] + ":" + tMinus[1] + ":" + tMinus[2] + ":" + tMinus[3] + " and counting");
    return ("До бани осталось " +
                tMinus[0] + " " + tMinusDescriptors[0] + " " +
                tMinus[1] + " " + tMinusDescriptors[1] + " " +
                tMinus[2] + " " + tMinusDescriptors[2] + " " +
                tMinus[3] + " " + tMinusDescriptors[3] + ".");
}<commit_msg>minor edit<commit_after>module.exports = function() {
    var now = new Date();
    // console.log(now.toISOString());
    var offset = now.getTimezoneOffset();
    var next = new Date(now).setSeconds(0);
    next = new Date(next).setDate(now.getDate() + 7 - now.getDay() + 3);
    next = new Date(next).setMinutes(0);
    next = new Date(next).setHours(19 + offset / 60);
    // console.log(new Date(next).toISOString());

    var diff = next - Date.now() - offset * 60 * 1000;

    var days = (1000 * 60 * 60 * 24);
    var hours = (60 * 60 * 1000);
    var minutes = (60 * 1000);
    var seconds = 1000;

    var daysLeft = Math.floor(diff / days);
    var hoursLeft = Math.floor(((diff - daysLeft * days) % days) / hours);
    var minutesLeft = Math.floor(((diff - daysLeft * days - hoursLeft * hours) % hours) / minutes);
    var secondsLeft = Math.floor(((diff - daysLeft * days - hoursLeft * hours - minutesLeft * minutes) % minutes) / seconds);

    var tMinus = [daysLeft, hoursLeft, minutesLeft, secondsLeft];

    tMinus.forEach(function(el) {
      return (el < 10) ? "0" + el : el;
    });

    var tMinusDescriptors = ["дней", "часов", "минут", "секунд"];

    tMinusDescriptors[0] = (tMinus[0] >= 1 && tMinus[0] <= 4) ? "дня" : "дней";
    tMinusDescriptors[1] = (tMinus[1] % 10 === 1) ? "час" : "часов";
    tMinusDescriptors[1] = (tMinus[1] % 10 > 1 && tMinus[1] % 10 <= 4) ? "часа" : "часов";
    tMinusDescriptors[2] = (tMinus[2] % 10 === 1) ? "минута" : "минут";
    tMinusDescriptors[2] = (tMinus[2] % 10 > 1 && tMinus[2] % 10 <= 4) ? "минуты" : "минут";
    tMinusDescriptors[3] = (tMinus[3] % 10 === 1) ? "секунда" : "секунд";
    tMinusDescriptors[3] = (tMinus[3] % 10 > 1 && tMinus[3] % 10 <= 4) ? "секунды" : "секунд";

    console.log("T minus " + tMinus[0] + ":" + tMinus[1] + ":" + tMinus[2] + ":" + tMinus[3] + " and counting");
    return ("До бани осталось " +
                tMinus[0] + " " + tMinusDescriptors[0] + " " +
                tMinus[1] + " " + tMinusDescriptors[1] + " " +
                tMinus[2] + " " + tMinusDescriptors[2] + " " +
                tMinus[3] + " " + tMinusDescriptors[3] + ".");
}<|endoftext|>


<commit_before>"use strict";

var testTasks = [
    "exec:testWscript",
    "exec:testRhino"
];
configuration.selenium.forEach(function (browser) {
    testTasks.push("exec:test" + browser.charAt(0).toUpperCase() + browser.substr(1));
});

module.exports = {
    source: ["mocha:source"].concat(testTasks),
    debug: ["mocha:debug", "mochaTest:debug"].concat(testTasks.map(function (name) {
        return name + "Debug";
    })),
    release: ["mocha:release", "mochaTest:release"].concat(testTasks.map(function (name) {
        return name + "Release";
    }))
};
<commit_msg>Parallel linters / quality<commit_after>"use strict";

var testTasks = [
    "exec:testWscript",
    "exec:testRhino"
];
configuration.selenium.forEach(function (browser) {
    testTasks.push("exec:test" + browser.charAt(0).toUpperCase() + browser.substr(1));
});

module.exports = {

    // Linters
    linters: [
        "jshint",
        "eslint"
    ],

    // Code quality tools
    quality: [
        "istanbul",
        "plato"
    ],

    // Tests on sources
    source: [
        "mocha:source"
    ].concat(testTasks),

    // Tests on debug version
    debug: [
        "mocha:debug",
        "mochaTest:debug"
    ].concat(testTasks.map(function (name) {
        return name + "Debug";
    })),

    // Tests on release version
    release: [
        "mocha:release",
        "mochaTest:release"
    ].concat(testTasks.map(function (name) {
        return name + "Release";
    }))
};
<|endoftext|>


<commit_before>// state_machine.js
//  - Interaction machine for messages and events.

var jed = require("jed");

var promise = require("./promise.js");
var Promise = promise.Promise;
var maybe_promise = promise.maybe_promise;
var success = promise.success;

var metrics = require("./metrics.js");
var MetricStore = metrics.MetricStore;

var states = require("./states");
var StateError = states.StateError;

function IMEvent(event, im, data) {

    var self = this;

    self.event = event; // event type
    self.im = im; // interaction machine
    self.data = data; // event data
}

function ConfigReadEvent(im, config) {
    return new IMEvent('config_read', im, {config: config});
}

function SessionNewEvent(im) {
    return new IMEvent('session_new', im, {});
}

function SessionCloseEvent(im, possible_timeout) {
    return new IMEvent('session_close', im,
                       {possible_timeout: possible_timeout});
}

function SessionResumeEvent(im) {
    return new IMEvent('session_resume', im, {});
}

function NewUserEvent(im, user_addr, user) {
    return new IMEvent('new_user', im, {user_addr: user_addr, user: user});
}

function StateEnterEvent(im, state) {
    return new IMEvent('state_enter', im, {state: state});
}

function StateExitEvent(im, state, user) {
    return new IMEvent('state_exit', im, {state:state});
}

function InboundEventEvent(im, event) {
    return new IMEvent('inbound_event', im, {event: event});
}

function InteractionMachine(api, state_creator) {

    var self = this;

    self.api = api;
    self.state_creator = state_creator;

    self.user = null;
    self.user_addr = null;
    self.i18n = null;
    self.i18n_lang = null;
    self.current_state = null;
    self.config = null;
    self.metrics = null;

    self.set_user_lang = function(lang) {
        self.user.lang = lang;
    };

    self.set_user_answer = function(state_name, answer) {
        if (!self.user.answers) self.user.answers = {};
        self.user.answers[state_name] = answer;
    };

    self.get_user_answer = function(state_name) {
        if (!self.user.answers) return null;
        var answer = self.user.answers[state_name];
        return (typeof answer == 'undefined') ? null : answer;
    };

    self.set_user_state = function(state_name) {
        // this just sets the users state
        // actual state changes are handled by switch_state
        self.user.current_state = state_name;
    };

    self.attach = function() {
        self.api.on_unknown_command = self.on_unknown_command;
        self.api.on_inbound_message = self.on_inbound_message;
        self.api.on_inbound_event = self.on_inbound_event;
        self.api.im = self;
    };

    self.on_event = function(event) {
        return self.state_creator.on_event(event);
    };

    self.switch_state = function(state_name) {
        var p = new Promise();
        if (self.current_state && state_name == self.current_state.name) {
            p.callback();
            return p;
        }
        var old_state_name = (self.current_state ? self.current_state.name :
                              self.user.current_state);
        p.add_callback(self.refresh_i18n);
        p.add_callback(function () {
            return self.state_creator.switch_state(state_name, self);
        });
        p.add_callback(function (new_state) {
            var ev_p;
            if (self.current_state) {
                ev_p = self.on_event(new StateExitEvent(self,
                                                        self.current_state));
            } else {
                ev_p = success();
            }
            var on_exit = (self.current_state ?
                           self.current_state.on_exit :
                           function () {});
            var on_enter = (old_state_name != new_state.name ?
                            new_state.on_enter :
                            function () {});
            ev_p.add_callback(function () {
                return maybe_promise(on_exit());
            });
            ev_p.add_callback(function () {
                self.current_state = new_state;
                self.user.current_state = new_state.name;
                return self.on_event(new StateEnterEvent(self, new_state));
            });
            ev_p.add_callback(function() {
                return maybe_promise(new_state.setup_state(self));
            });
            ev_p.add_callback(function() {
                return maybe_promise(on_enter());
            });
            return ev_p;
        });
        p.callback();
        return p;
    };

    self.refresh_i18n = function() {
        var p = new Promise();
        var lang = self.user.lang || self.config.default_lang;
        if (!lang) {
            self.i18n_lang = null;
            self.i18n = new jed({});
            p.callback();
            return p;
        }
        if ((!self.i18n) || (lang != self.i18n_lang)) {
            self.fetch_translation(lang, function (i18n) {
                self.i18n_lang = lang;
                self.i18n = i18n;
                p.callback();
            });
            return p;
        }
        p.callback(); // cached case
        return p;
    };

    self.fetch_config_value = function(key, json, done) {
        self.api.request("config.get", {key: key},
            function(reply) {
                var value = null;
                if (reply.value && json) {
                    try { value = JSON.parse(reply.value); }
                    catch (e) {}
                }
                else {
                    value = reply.value;
                }
                done(value);
            });
    };

    self.fetch_translation = function(lang, done) {
        self.fetch_config_value("translation." + lang, true,
            function(domain_data) {
                var jed_data = {};
                if (domain_data) {
                    jed_data.domain = "messages";
                    jed_data.locale_data = {"messages": domain_data};
                }
                var i18n = new jed(jed_data);
                done(i18n);
            });
    };

    self.setup_config = function() {
        var p = new Promise();
        self.fetch_config_value("config", true,
            function (config) {
                self.config = config || {};
                p.callback();
            });
        p.add_callback(function (reply) {
            return self.on_event(new ConfigReadEvent(self, self.config));
        });
        return p;
    };

    self.setup_metrics = function() {
        var store = self.config.metric_store || 'default';
        self.metrics = new MetricStore(self.api, store);
    };

    self.user_key = function(from_addr) {
        var prefix = (self.config.user_store ?
                      self.config.user_store + '.' : '');
        return "users." + prefix + from_addr;
    };

    self.load_user = function(from_addr, done) {
        var p = new Promise();
        self.user_addr = from_addr;
        self.api.request("kv.get",
            {key: self.user_key(from_addr)},
            function(reply) {
                p.callback(reply);
            });
        p.add_callback(function (reply) {
            self.user = reply.value || {};
            if (!reply.value) {
                return self.on_event(new NewUserEvent(self,
                                                      self.user_addr,
                                                      self.user));
            }
        });
        return p;
    };

    self.store_user = function(from_addr, user) {
        var p = new Promise();
        self.api.request("kv.set",
            {
                key: self.user_key(from_addr),
                value: user
            },
            function(reply) {
                p.callback();
            });
        return p;
    };

    self.on_unknown_command = function(cmd) {
        self.log("Received unknown command: " + JSON.stringify(cmd));
        self.done();
    };

    self.log = function(message) { self.api.log_info(message); };
    self.done = function() { self.api.done(); };

    self.api_request = function(cmd_name, cmd) {
        var p = new Promise();
        self.api.request(cmd_name, cmd, p.callback);
        return p;
    };

    self.reply = function(msg, save_user) {
        var p = self.switch_state(self.user.current_state);

        p.add_callback(function() {
            var continue_session = self.current_state.continue_session();
            if (!continue_session) {
                return self.on_event(new SessionCloseEvent(self, false));
            }
        });

        if (save_user) {
            p.add_callback(function() {
                self.log("Ending in state: " + self.current_state.name);
                self.log("Saving user: " + JSON.stringify(self.user));
                self.store_user(msg.from_addr, self.user);
            });
        }

        p.add_callback(function () {
            var continue_session = self.current_state.continue_session();
            self.api_request("outbound.reply_to", {
                content: self.current_state.display(),
                in_reply_to: msg.message_id,
                continue_session: continue_session
            });
        });

        return p;
    };

    self.on_inbound_message = function(cmd) {
        var msg = cmd.msg;
        var p = new Promise();

        p.add_callback(self.setup_config);
        p.add_callback(self.setup_metrics);
        p.add_callback(function () {
            return self.load_user(msg.from_addr);
        });
        p.add_callback(function () {
            if (msg.content == "!restart") {
                self.user = {};
                msg.content = "";
            }
            self.log("Loaded user " + self.user_addr + ": " + JSON.stringify(self.user));
            self.log("Content: " + msg.content);
            return self.switch_state(self.user.current_state);
        });
        p.add_callback(function () {
            self.log("Switched to state: " + self.current_state.name);
            if (msg.session_event == "close") {
                var ev_p = self.on_event(new SessionCloseEvent(self, true));
                ev_p.add_callback(function () {
                    return self.store_user(msg.from_addr, self.user);
                });
                return ev_p;
            }
            else if (msg.session_event == "new") {
                var ev_p = self.on_event(new SessionNewEvent(self));
                ev_p.add_callback(function () {
                    var state_done = new Promise();
                    self.current_state.new_session_event(function () {
                        state_done.callback();
                    });
                    return state_done;
                });
                ev_p.add_callback(function () {
                    return self.reply(msg, true);
                });
                return ev_p;
            }
            else {
                var ev_p = self.on_event(new SessionResumeEvent(self));
                if (msg.content) {
                    ev_p.add_callback(function() {
                        var state_done = new Promise();
                        self.current_state.input_event(
                            msg.content,
                            function () {
                                state_done.callback();
                            });
                        return state_done;
                    });
                }
                ev_p.add_callback(function() {
                    return self.reply(msg, true);
                });
                return ev_p;
            }
        });
        p.add_callback(self.done);

        p.callback();
    };

    self.on_inbound_event = function(cmd) {
        var event = cmd.msg;

        var p = new Promise();
        p.add_callback(function() {
            return self.on_event(new InboundEventEvent(self, event));
        });
        p.add_callback(self.done);
        p.callback();
    };

}

function StateCreator(start_state) {
    var self = this;

    self.start_state = start_state;
    self.state_creators = {};

    self.on_event = function(event) {
        var handler = self['on_' + event.event];
        if (typeof handler != 'undefined') {
            return maybe_promise(handler(event));
        }
        return success();
    };

    self.add_creator = function(state_name, state_creation_function) {
        if (self.state_creators[state_name]) {
            throw new StateError("Duplicate state '" + state_name + "'");
        }
        self.state_creators[state_name] = state_creation_function;
    };

    self.add_state = function(state, translate) {
        translate = (typeof translate == 'undefined') ? true : translate;
        self.add_creator(state.name, function(state_name, im) {
            if (translate) {
                state.translate(im.i18n);
            }
            return state;
        });
    };

    self.switch_state = function(state_name, im) {
        if (typeof state_name == "undefined")
            // handles new users who have no current state
            state_name = self.start_state;
        if (!self.state_creators[state_name]) {
            // handles users who somehow have an unknown state
            // (possibly they are in a state from a previous version
            // of the js application).
            im.log("Unknown state '" + state_name + "'. Switch to" +
                   " start state, '" + self.start_state + "'.");
            state_name = self.start_state;
        }
        if (!self.state_creators[state_name]) {
            // the only way to arrive here should be if the start state
            // is not a valid state.
            throw new StateError("Unknown start state '" + state_name + "'");
        }
        var creator = self.state_creators[state_name];
        return maybe_promise(creator.call(self, state_name, im));
    };
}

// export

this.InteractionMachine = InteractionMachine;
this.StateCreator = StateCreator;<commit_msg>Better description in message logged when an unknown state is encountered.<commit_after>// state_machine.js
//  - Interaction machine for messages and events.

var jed = require("jed");

var promise = require("./promise.js");
var Promise = promise.Promise;
var maybe_promise = promise.maybe_promise;
var success = promise.success;

var metrics = require("./metrics.js");
var MetricStore = metrics.MetricStore;

var states = require("./states");
var StateError = states.StateError;

function IMEvent(event, im, data) {

    var self = this;

    self.event = event; // event type
    self.im = im; // interaction machine
    self.data = data; // event data
}

function ConfigReadEvent(im, config) {
    return new IMEvent('config_read', im, {config: config});
}

function SessionNewEvent(im) {
    return new IMEvent('session_new', im, {});
}

function SessionCloseEvent(im, possible_timeout) {
    return new IMEvent('session_close', im,
                       {possible_timeout: possible_timeout});
}

function SessionResumeEvent(im) {
    return new IMEvent('session_resume', im, {});
}

function NewUserEvent(im, user_addr, user) {
    return new IMEvent('new_user', im, {user_addr: user_addr, user: user});
}

function StateEnterEvent(im, state) {
    return new IMEvent('state_enter', im, {state: state});
}

function StateExitEvent(im, state, user) {
    return new IMEvent('state_exit', im, {state:state});
}

function InboundEventEvent(im, event) {
    return new IMEvent('inbound_event', im, {event: event});
}

function InteractionMachine(api, state_creator) {

    var self = this;

    self.api = api;
    self.state_creator = state_creator;

    self.user = null;
    self.user_addr = null;
    self.i18n = null;
    self.i18n_lang = null;
    self.current_state = null;
    self.config = null;
    self.metrics = null;

    self.set_user_lang = function(lang) {
        self.user.lang = lang;
    };

    self.set_user_answer = function(state_name, answer) {
        if (!self.user.answers) self.user.answers = {};
        self.user.answers[state_name] = answer;
    };

    self.get_user_answer = function(state_name) {
        if (!self.user.answers) return null;
        var answer = self.user.answers[state_name];
        return (typeof answer == 'undefined') ? null : answer;
    };

    self.set_user_state = function(state_name) {
        // this just sets the users state
        // actual state changes are handled by switch_state
        self.user.current_state = state_name;
    };

    self.attach = function() {
        self.api.on_unknown_command = self.on_unknown_command;
        self.api.on_inbound_message = self.on_inbound_message;
        self.api.on_inbound_event = self.on_inbound_event;
        self.api.im = self;
    };

    self.on_event = function(event) {
        return self.state_creator.on_event(event);
    };

    self.switch_state = function(state_name) {
        var p = new Promise();
        if (self.current_state && state_name == self.current_state.name) {
            p.callback();
            return p;
        }
        var old_state_name = (self.current_state ? self.current_state.name :
                              self.user.current_state);
        p.add_callback(self.refresh_i18n);
        p.add_callback(function () {
            return self.state_creator.switch_state(state_name, self);
        });
        p.add_callback(function (new_state) {
            var ev_p;
            if (self.current_state) {
                ev_p = self.on_event(new StateExitEvent(self,
                                                        self.current_state));
            } else {
                ev_p = success();
            }
            var on_exit = (self.current_state ?
                           self.current_state.on_exit :
                           function () {});
            var on_enter = (old_state_name != new_state.name ?
                            new_state.on_enter :
                            function () {});
            ev_p.add_callback(function () {
                return maybe_promise(on_exit());
            });
            ev_p.add_callback(function () {
                self.current_state = new_state;
                self.user.current_state = new_state.name;
                return self.on_event(new StateEnterEvent(self, new_state));
            });
            ev_p.add_callback(function() {
                return maybe_promise(new_state.setup_state(self));
            });
            ev_p.add_callback(function() {
                return maybe_promise(on_enter());
            });
            return ev_p;
        });
        p.callback();
        return p;
    };

    self.refresh_i18n = function() {
        var p = new Promise();
        var lang = self.user.lang || self.config.default_lang;
        if (!lang) {
            self.i18n_lang = null;
            self.i18n = new jed({});
            p.callback();
            return p;
        }
        if ((!self.i18n) || (lang != self.i18n_lang)) {
            self.fetch_translation(lang, function (i18n) {
                self.i18n_lang = lang;
                self.i18n = i18n;
                p.callback();
            });
            return p;
        }
        p.callback(); // cached case
        return p;
    };

    self.fetch_config_value = function(key, json, done) {
        self.api.request("config.get", {key: key},
            function(reply) {
                var value = null;
                if (reply.value && json) {
                    try { value = JSON.parse(reply.value); }
                    catch (e) {}
                }
                else {
                    value = reply.value;
                }
                done(value);
            });
    };

    self.fetch_translation = function(lang, done) {
        self.fetch_config_value("translation." + lang, true,
            function(domain_data) {
                var jed_data = {};
                if (domain_data) {
                    jed_data.domain = "messages";
                    jed_data.locale_data = {"messages": domain_data};
                }
                var i18n = new jed(jed_data);
                done(i18n);
            });
    };

    self.setup_config = function() {
        var p = new Promise();
        self.fetch_config_value("config", true,
            function (config) {
                self.config = config || {};
                p.callback();
            });
        p.add_callback(function (reply) {
            return self.on_event(new ConfigReadEvent(self, self.config));
        });
        return p;
    };

    self.setup_metrics = function() {
        var store = self.config.metric_store || 'default';
        self.metrics = new MetricStore(self.api, store);
    };

    self.user_key = function(from_addr) {
        var prefix = (self.config.user_store ?
                      self.config.user_store + '.' : '');
        return "users." + prefix + from_addr;
    };

    self.load_user = function(from_addr, done) {
        var p = new Promise();
        self.user_addr = from_addr;
        self.api.request("kv.get",
            {key: self.user_key(from_addr)},
            function(reply) {
                p.callback(reply);
            });
        p.add_callback(function (reply) {
            self.user = reply.value || {};
            if (!reply.value) {
                return self.on_event(new NewUserEvent(self,
                                                      self.user_addr,
                                                      self.user));
            }
        });
        return p;
    };

    self.store_user = function(from_addr, user) {
        var p = new Promise();
        self.api.request("kv.set",
            {
                key: self.user_key(from_addr),
                value: user
            },
            function(reply) {
                p.callback();
            });
        return p;
    };

    self.on_unknown_command = function(cmd) {
        self.log("Received unknown command: " + JSON.stringify(cmd));
        self.done();
    };

    self.log = function(message) { self.api.log_info(message); };
    self.done = function() { self.api.done(); };

    self.api_request = function(cmd_name, cmd) {
        var p = new Promise();
        self.api.request(cmd_name, cmd, p.callback);
        return p;
    };

    self.reply = function(msg, save_user) {
        var p = self.switch_state(self.user.current_state);

        p.add_callback(function() {
            var continue_session = self.current_state.continue_session();
            if (!continue_session) {
                return self.on_event(new SessionCloseEvent(self, false));
            }
        });

        if (save_user) {
            p.add_callback(function() {
                self.log("Ending in state: " + self.current_state.name);
                self.log("Saving user: " + JSON.stringify(self.user));
                self.store_user(msg.from_addr, self.user);
            });
        }

        p.add_callback(function () {
            var continue_session = self.current_state.continue_session();
            self.api_request("outbound.reply_to", {
                content: self.current_state.display(),
                in_reply_to: msg.message_id,
                continue_session: continue_session
            });
        });

        return p;
    };

    self.on_inbound_message = function(cmd) {
        var msg = cmd.msg;
        var p = new Promise();

        p.add_callback(self.setup_config);
        p.add_callback(self.setup_metrics);
        p.add_callback(function () {
            return self.load_user(msg.from_addr);
        });
        p.add_callback(function () {
            if (msg.content == "!restart") {
                self.user = {};
                msg.content = "";
            }
            self.log("Loaded user " + self.user_addr + ": " + JSON.stringify(self.user));
            self.log("Content: " + msg.content);
            return self.switch_state(self.user.current_state);
        });
        p.add_callback(function () {
            self.log("Switched to state: " + self.current_state.name);
            if (msg.session_event == "close") {
                var ev_p = self.on_event(new SessionCloseEvent(self, true));
                ev_p.add_callback(function () {
                    return self.store_user(msg.from_addr, self.user);
                });
                return ev_p;
            }
            else if (msg.session_event == "new") {
                var ev_p = self.on_event(new SessionNewEvent(self));
                ev_p.add_callback(function () {
                    var state_done = new Promise();
                    self.current_state.new_session_event(function () {
                        state_done.callback();
                    });
                    return state_done;
                });
                ev_p.add_callback(function () {
                    return self.reply(msg, true);
                });
                return ev_p;
            }
            else {
                var ev_p = self.on_event(new SessionResumeEvent(self));
                if (msg.content) {
                    ev_p.add_callback(function() {
                        var state_done = new Promise();
                        self.current_state.input_event(
                            msg.content,
                            function () {
                                state_done.callback();
                            });
                        return state_done;
                    });
                }
                ev_p.add_callback(function() {
                    return self.reply(msg, true);
                });
                return ev_p;
            }
        });
        p.add_callback(self.done);

        p.callback();
    };

    self.on_inbound_event = function(cmd) {
        var event = cmd.msg;

        var p = new Promise();
        p.add_callback(function() {
            return self.on_event(new InboundEventEvent(self, event));
        });
        p.add_callback(self.done);
        p.callback();
    };

}

function StateCreator(start_state) {
    var self = this;

    self.start_state = start_state;
    self.state_creators = {};

    self.on_event = function(event) {
        var handler = self['on_' + event.event];
        if (typeof handler != 'undefined') {
            return maybe_promise(handler(event));
        }
        return success();
    };

    self.add_creator = function(state_name, state_creation_function) {
        if (self.state_creators[state_name]) {
            throw new StateError("Duplicate state '" + state_name + "'");
        }
        self.state_creators[state_name] = state_creation_function;
    };

    self.add_state = function(state, translate) {
        translate = (typeof translate == 'undefined') ? true : translate;
        self.add_creator(state.name, function(state_name, im) {
            if (translate) {
                state.translate(im.i18n);
            }
            return state;
        });
    };

    self.switch_state = function(state_name, im) {
        if (typeof state_name == "undefined")
            // handles new users who have no current state
            state_name = self.start_state;
        if (!self.state_creators[state_name]) {
            // handles users who somehow have an unknown state
            // (possibly they are in a state from a previous version
            // of the js application).
            im.log("Unknown state '" + state_name + "'. Switching to" +
                   " start state, '" + self.start_state + "'.");
            state_name = self.start_state;
        }
        if (!self.state_creators[state_name]) {
            // the only way to arrive here should be if the start state
            // is not a valid state.
            throw new StateError("Unknown start state '" + state_name + "'");
        }
        var creator = self.state_creators[state_name];
        return maybe_promise(creator.call(self, state_name, im));
    };
}

// export

this.InteractionMachine = InteractionMachine;
this.StateCreator = StateCreator;<|endoftext|>


<commit_before>/*
 plan
 0. display data in some way
 1. howto: super simple multi select
 2. create them for all the necessary filters (country, age group, ...),
      but automatically! (select agegroup or something), there is probably javascript ability for that
 3. apply them to data with on_update function, ensure they all are applied correctly.
 (think about smart order for performance )
*/

// --- global variables
var full_dataset, filtered_dataset;
var unique_columns, filtered_unique_columns;

var columns = [
    { head: 'Gender', cl: 'title', filter: [],
      html: function(r) { return r.Gender; } },
    { head: 'Riskgroup', cl: 'center', filter: [],
      html: function(r) { return r.Risk; } },
    { head: 'Infection Age', cl: 'num', filter: [],
      html: function(r) { return r.age_at_infection; } },
    { head: 'First Positive Test', cl: 'date', filter: [],
      html: function(r) { return r.first_positive_test_date; } },
    { head: 'Country of Infection', cl: 'center', filter: [],
      html: function(r) { return r.infection; } },
    { head: 'Country of Origin', cl: 'center', filter: [],
      html: function(r) { return r.origin; } },
    { head: 'Year of Birth', cl: 'num', filter: [],
      html: function(r) { return r.year_of_birth; } },
];

// --- entry point
d3.csv("data/patients_final.csv", function (data) {
    console.log(data);
    full_dataset = data;
    filtered_dataset = full_dataset;

    //array with all unique values / column, only needs to be generated once
    unique_columns = columns.map(x => get_unique_column(x.html));
    filtered_unique_columns = unique_columns;

    // create table
    var table = d3.select('.table')
        .append('table').attr('class', 'table');

    // create table header
    table.append('thead').append('tr')
        .selectAll('th')
        .data(columns).enter()
        .append('th')
        .attr('class', function(x) { return x.cl; })
        .text(function(x) { return x.head; });

    table.append('tbody');

    //generate content
    update_select_boxes();
    generate_table();
});

// main update function, reads currently selected filters and applies them before updating the display
function update_filters() {
    console.log('new filter arrays:')
    //get currently selected (or unselected values for all ids in columns.head)
    for (var i = 0; i < columns.length; i++) {
        columns[i].filter = $('#'+columns[i].head).val();
        //if (!columns[i].filter)
    }

    // filter
    filtered_dataset = full_dataset.filter(filter_function);
    filtered_unique_columns = unique_columns.filter(filter_function);

    update_select_boxes();
    generate_table();
}

// returns true if each attribute is eather in the filter list or the filter list is empty
function filter_function(x) {
    // compare each attribute
    for (var i = 0; i < columns.length; i++) {
        // if the filter list for this attribute is empty, we don't drop anything
        if (columns[i].filter && columns[i].filter.length != 0) {
            // if it is not empty it has to contain the entry (we are basically whitelisting)
            if (!columns[i].filter.includes(columns[i].html(x))) {
                return false;
            }
        }
    }
    // each attribute passed, so we include this entry in the filtered dataset
    return true;
}

function update_select_boxes(){

    // create one selection box for each column
    var select_boxes = d3.select('.selectors')
        .selectAll('select')
        .data(columns).enter()
        .append('select')
        .attr('class', 'selectpicker').attr('multiple', 'multiple')
        .attr('id', x => x.head)
        .on('change', x => update_filters());

    // fill them with the appropiate filtered values
    select_boxes.selectAll('option')
        .data((row, i) => filtered_unique_columns[i]).enter()
        .append('option')
        .text(function(x) {return x});

    // necessary call to update the select box display
    $('.selectpicker').selectpicker('refresh');
}

// Generates an array with all unique values of one of the dataset columns
// takes an attr_function of the form function (x) { return x.ATTR }] as parameter
// adapted from https://stackoverflow.com/questions/17780508/selecting-distinct-values-from-a-json
function get_unique_column(attr_function) {
    var lookup = {};
    var result = [];

    for (var item, i = 0; item = full_dataset[i++];) {
      var name = attr_function(item);

      if (!(name in lookup)) {
        lookup[name] = 1;
        result.push(name);
      }
    }
    return result;
}

// code mostly from http://bl.ocks.org/gka/17ee676dc59aa752b4e6 and adapted to our purposes
function generate_table(){

    // DATA JOIN
    // Join new data with old elements, if any.
    var table = d3.select('table').select('tbody');

    // create table body
    table.selectAll('tr')
        .data(filtered_dataset).enter()
        .append('tr')
        .selectAll('td')
        .data(function(row, i) {
            return columns.map(function(c) {
                // compute cell values for this specific row
                var cell = {};
                d3.keys(c).forEach(function(k) {
                    cell[k] = typeof c[k] == 'function' ? c[k](row,i) : c[k];
                });
                return cell;
            });
        }).enter()
        .append('td')
        .html(function(x) { return x.html; })
        .attr('class', function(x) { return x.cl; });

    // Update existing elements
    table.transition()
        .duration(0);

    // Remove old elements as needed.
    table.exit().remove();
}

/*
d3.select("body")
  .selectAll("p")
  .data([4, 8, 15, 16, 23, 42])
  .enter().append("p")
    .text(function(d) { return "I’m number " + d + "!"; });

// Update…
var p = d3.select("body")
  .selectAll("p")
  .data([4, 8, 15, 16, 23, 42])
    .text(function(d) { return d; });

// Enter…
p.enter().append("p")
    .text(function(d) { return d; });

// Exit…
p.exit().remove();
*/

<commit_msg>Displayed data and selector content is now updated with respect to the selected filters<commit_after>// --- global variables
var full_dataset, filtered_dataset;
var unique_columns, filtered_unique_columns;

var columns = [
    { head: 'Gender', cl: 'gender', filter: [],
      html: function(r) { return r.Gender; } },
    { head: 'Riskgroup', cl: 'riskgroup', filter: [],
      html: function(r) { return r.Risk; } },
    { head: 'Infection Age', cl: 'age_at_infection', filter: [],
      html: function(r) { return r.age_at_infection; } },
    { head: 'First Positive Test', cl: 'first_positive_test_date', filter: [],
      html: function(r) { return r.first_positive_test_date; } },
    { head: 'Country of Infection', cl: 'infection', filter: [],
      html: function(r) { return r.infection; } },
    { head: 'Country of Origin', cl: 'origin', filter: [],
      html: function(r) { return r.origin; } },
    { head: 'Year of Birth', cl: 'year_of_birth', filter: [],
      html: function(r) { return r.year_of_birth; } },
];

// --- entry point
d3.csv("data/patients_final.csv", function (data) {
    full_dataset = data;
    filtered_dataset = full_dataset;

    // --- Elements that only have to be created once
    //array with all unique values / column, only needs to be generated once
    unique_columns = columns.map(x => get_unique_column(x.html));
    filtered_unique_columns = unique_columns;

    // create table
    var table = d3.select('.table')
        .append('table').attr('class', 'table');

    // create table header
    table.append('thead').append('tr')
        .selectAll('th')
        .data(columns).enter()
        .append('th')
        .attr('class', function(x) { return x.cl; })
        .text(function(x) { return x.head; });
    table.append('tbody');

    // create one selection box for each column
    var select_boxes = d3.select('.selectors')
        .selectAll('select')
        .data(columns).enter()
        .append('select')
        .attr('class', 'selectpicker').attr('multiple', 'multiple')
        .attr('id', x => x.cl)
        .on('change', x => update_filters());

    //--- generate content
    update_select_boxes();
    generate_table();
});

// main update function, reads currently selected filters and applies them before updating the display
function update_filters() {
    //get currently selected (or unselected values for all ids in columns.head)
    for (var i = 0; i < columns.length; i++) {
        columns[i].filter = $('#'+columns[i].cl).val();
    }

    // filter
    filtered_dataset = full_dataset.filter(filter_function);
    filtered_unique_columns = unique_columns.map(column_filter);

    update_select_boxes();
    generate_table();
}

//
function column_filter(column, i) {
    // no filter selected for this column -> only get possible values
    if (columns[i].filter.length == 0) {
        //TODO: find out which is quicker! // drop completely because it's too slow for full dataset?
        //column.filter(x => filtered_dataset.map(y => columns[i].html(y)).includes(x))
        return get_unique_column(columns[i].html)
    }
    // some filter selected -> display complete column
    return column;
}

// returns true if each attribute is eather in the filter list or the filter list is empty
function filter_function(x) {
    // compare each attribute
    for (var i = 0; i < columns.length; i++) {
        // if the filter list for this attribute is empty, we don't drop anything
        if (columns[i].filter && columns[i].filter.length != 0) {
            // if it is not empty it has to contain the entry (we are basically whitelisting)
            if (!columns[i].filter.includes(columns[i].html(x))) {
                return false;
            }
        }
    }
    // each attribute passed, so we include this entry in the filtered dataset
    return true;
}

function update_select_boxes(){

    // select all select boxes options and connect them with new data
    var select_contents = d3.select('.selectors').selectAll('select').data(columns)
        .selectAll('option')
        .data((row, i) => filtered_unique_columns[i])

    // update existing elements
    select_contents.text(function(x) {return x});

    // create new elements as needed
    select_contents.enter()
        .append('option')
        .text(function(x) {return x});

    // remove old elements
    select_contents.exit().remove();

    // necessary call to update the select box display
    $('.selectpicker').selectpicker('refresh');
}

// Generates an array with all unique values of one of the dataset columns
// takes an attr_function of the form function (x) { return x.ATTR }] as parameter
// adapted from https://stackoverflow.com/questions/17780508/selecting-distinct-values-from-a-json
function get_unique_column(attr_function) {
    var lookup = {};
    var result = [];

    for (var item, i = 0; item = filtered_dataset[i++];) {
      var name = attr_function(item);

      if (!(name in lookup)) {
        lookup[name] = 1;
        result.push(name);
      }
    }
    return result;
}

// code mostly from http://bl.ocks.org/gka/17ee676dc59aa752b4e6 and adapted to our purposes
function generate_table(){

    // DATA JOIN
    // Join new data with old elements, if any.
    var table = d3.select('table').select('tbody')
        .selectAll('tr').data(filtered_dataset);

    // Update existing rows
    table.selectAll('td').data(get_row_function)
        .html(function(x) { return x.html; })
        .attr('class', function(x) { return x.cl; });

    // Add new rows
    table.enter()
        .append('tr')
        .selectAll('td')
        .data(get_row_function).enter()
        .append('td')
        .html(function(x) { return x.html; })
        .attr('class', function(x) { return x.cl; });

    // Remove old elements as needed.
    table.exit().remove();
}

function get_row_function(row, i) {
    return columns.map(function(c) {
        // compute cell values for this specific row
        var cell = {};
        d3.keys(c).forEach(function(k) {
            cell[k] = typeof c[k] == 'function' ? c[k](row,i) : c[k];
        });
        return cell;
    });
}
<|endoftext|>