<commit_before>/* @flow */

import { ZalgoPromise } from 'zalgo-promise/src';
import * as xcomponent from 'xcomponent/src';
import * as $logger from 'beaver-logger/client';

import { Checkout, enableCheckoutIframe } from '../checkout';
import { config, USERS, SOURCE, ENV, FPTI } from '../../config';
import { redirect as redir, hasMetaViewPort, setLogLevel, forceIframe, getBrowserLocale, getCommonSessionID, request, checkpoint, isIEIntranet, getPageRenderTime, isEligible, getSessionState } from '../../lib';
import { rest } from '../../api';

import { getPopupBridgeOpener, awaitPopupBridgeOpener } from '../checkout/popupBridge';
import { containerTemplate, componentTemplate } from './templates';
import { validateButtonLocale, validateButtonStyle } from './templates/component/validate';
import { awaitBraintreeClient, type BraintreePayPalClient } from './braintree';

getSessionState(session => {
    session.buttonClicked = false;
    session.buttonCancelled = false;
});

export let Button = xcomponent.create({

    tag: 'paypal-button',
    name: 'ppbutton',

    buildUrl(props) : string {
        let env = props.env || config.env;

        return config.buttonUrls[env];
    },

    contexts: {
        iframe: true,
        popup: false
    },

    scrolling: false,

    containerTemplate,
    componentTemplate({ props, htmlDom } : { props : Object, htmlDom : Function }) : HTMLElement {

        let template = htmlDom(componentTemplate({ props }));

        template.addEventListener('click', () => {
            $logger.warn('button_pre_template_click');
        });

        return template;
    },

    sacrificialComponentTemplate: true,

    get version() : string {
        return config.ppobjects ? __FILE_VERSION__ : __MINOR_VERSION__;
    },

    get domain() : Object {
        return config.paypalDomains;
    },

    autoResize: {
        width: false,
        height: true,
        element: 'body'
    },

    validate() {
        if (isIEIntranet()) {
            throw new Error(`Can not render button in IE intranet mode`);
        }

        if (!isEligible()) {
            $logger.warn('button_render_ineligible');
        }
    },

    props: {

        uid: {
            type: 'string',
            value: getCommonSessionID(),
            def() : string {
                return getCommonSessionID();
            },
            queryParam: true
        },

        env: {
            type: 'string',
            required: false,
            queryParam: true,

            def() : string {
                return config.env;
            },

            validate(env) {
                if (!config.paypalUrls[env]) {
                    throw new Error(`Invalid env: ${env}`);
                }
            }
        },

        client: {
            type: 'object',
            required: false,
            def() : Object {
                return {};
            },
            sendToChild: false,

            validate(client, props) {
                let env = props.env || config.env;

                if (!client[env]) {
                    throw new Error(`Client ID not found for env: ${env}`);
                }

                if (client[env].match(/^(.)\1+$/)) {
                    throw new Error(`Invalid client ID: ${client[env]}`);
                }
            }
        },

        source: {
            type: 'string',
            required: false,
            def() : string {
                return SOURCE.MANUAL;
            }
        },

        prefetchLogin: {
            type: 'boolean',
            required: false
        },

        stage: {
            type: 'string',
            required: false,
            queryParam: true,

            def(props) : ?string {
                let env = props.env || config.env;

                if (env === ENV.STAGE || env === ENV.LOCAL) {
                    return config.stage;
                }
            }
        },

        braintree: {
            type: 'object',
            required: false,
            validate(braintree, props) {

                if (!braintree.paypalCheckout) {
                    throw new Error(`Expected Braintree paypal-checkout component to be loaded`);
                }

                if (!props.client) {
                    throw new Error(`Expected client prop to be passed with Braintree authorization keys`);
                }
            },
            decorate(braintree, props) : ?ZalgoPromise<BraintreePayPalClient> {

                if (!braintree) {
                    return;
                }

                let env = props.env || config.env;
                let authorization = props.client[env];

                return awaitBraintreeClient(braintree, authorization);
            }
        },

        payment: {
            type: 'function',
            required: true,
            memoize: false,
            timeout: __TEST__ ? 500 : 10 * 1000,
            alias: 'billingAgreement',

            decorate(original) : Function {
                return function payment() : ZalgoPromise<string> {
                    return new ZalgoPromise((resolve, reject) => {

                        let data = resolve;
                        let actions = reject;

                        data.payment = actions.payment = {
                            create: (options, experience) => {
                                return rest.payment.create(this.props.env, this.props.client, options, experience);
                            }
                        };

                        data.braintree = actions.braintree = {
                            create: (options) => {
                                if (!this.props.braintree) {
                                    throw new Error(`Can not create using Braintree - no braintree client provided`);
                                }

                                return this.props.braintree.then(client => {
                                    return client.createPayment(options);
                                });
                            }
                        };

                        actions.request = request;

                        let context = {
                            props: {
                                env: this.props.env,
                                client: this.props.client
                            }
                        };

                        let result;

                        try {
                            result = original.call(context, data, actions);
                        } catch (err) {
                            return reject(err);
                        }

                        if (result && typeof result.then === 'function') {
                            return result.then(resolve, reject);
                        }

                        if (result !== undefined) {
                            return resolve(result);
                        }

                        let timeout = __TEST__ ? 500 : 10 * 1000;

                        setTimeout(() => {
                            reject(`Timed out waiting ${timeout}ms for payment`);
                        }, timeout);

                    }).then(result => {

                        if (!result) {
                            throw new Error(`No value passed to payment`);
                        }

                        return result;
                    });
                };
            }
        },

        commit: {
            type: 'boolean',
            required: false
        },

        onRender: {
            type: 'function',
            promisify: true,
            required: false,
            decorate(original) : Function {
                return function() : mixed {
                    checkpoint('render_iframe_button', { version: true });
                    $logger.track({
                        [ FPTI.KEY.STATE ]: FPTI.STATE.LOAD,
                        [ FPTI.KEY.TRANSITION ]: FPTI.TRANSITION.BUTTON_RENDER,
                        [ FPTI.KEY.BUTTON_TYPE ]: FPTI.BUTTON_TYPE.IFRAME,
                        [ FPTI.KEY.BUTTON_SOURCE ]: this.props.source
                    });
                    $logger.flush();
                    if (original) {
                        return original.apply(this, arguments);
                    }
                };
            }
        },

        onAuth: {
            type: 'function',
            required: false,

            value() {
                this.onRemember = this.onRemember || new ZalgoPromise();
                this.onRemember.resolve();

                enableCheckoutIframe();
            }
        },

        onRemembered: {
            type: 'function',
            required: false,

            value() {
                this.onRemember = this.onRemember || new ZalgoPromise();
                this.onRemember.resolve();
            }
        },

        onDisplay: {
            type: 'function',
            required: false,

            decorate(original) : Function {
                return function() : ZalgoPromise<void> {
                    return ZalgoPromise.try(() => {

                        this.onRemember = this.onRemember || new ZalgoPromise();

                        if (this.props.displayTo === USERS.REMEMBERED) {
                            $logger.info(`button_render_wait_for_remembered_user`);

                            return this.onRemember.then(() => {
                                $logger.info(`button_render_got_remembered_user`);
                            });
                        }

                    }).then(() => {

                        if (original) {
                            return original.apply(this, arguments);
                        }
                    });
                };
            }
        },

        onAuthorize: {
            type: 'function',
            required: true,

            decorate(original) : Function {
                return function(data, actions) : void | ZalgoPromise<void> {

                    $logger.info('checkout_authorize');

                    $logger.track({
                        [ FPTI.KEY.STATE ]: FPTI.STATE.CHECKOUT,
                        [ FPTI.KEY.TRANSITION ]: FPTI.TRANSITION.CHECKOUT_AUTHORIZE
                    });

                    if (!isEligible()) {
                        $logger.info('button_authorize_ineligible');
                    }

                    $logger.flush();

                    if (this.props.braintree) {
                        return this.props.braintree.then(client => {
                            return client.tokenizePayment(data).then(res => {
                                return original.call(this, { nonce: res.nonce });
                            });
                        });
                    }

                    let redirect = (win, url) => {
                        return ZalgoPromise.all([
                            redir(win || window.top, url || data.returnUrl),
                            actions.close()
                        ]);
                    };

                    return ZalgoPromise.try(() => {
                        return original.call(this, data, { ...actions, redirect });
                    }).catch(err => {
                        return this.error(err);
                    });
                };
            }
        },

        onCancel: {
            type: 'function',
            required: false,
            noop: true,

            decorate(original) : Function {
                return function(data, actions) : void | ZalgoPromise<void> {

                    $logger.info('checkout_cancel');

                    $logger.track({
                        [ FPTI.KEY.STATE ]: FPTI.STATE.CHECKOUT,
                        [ FPTI.KEY.TRANSITION ]: FPTI.TRANSITION.CHECKOUT_CANCEL
                    });

                    $logger.flush();

                    getSessionState(session => {
                        session.buttonCancelled = true;
                    });

                    let redirect = (win, url) => {
                        return ZalgoPromise.all([
                            redir(win || window.top, url || data.cancelUrl),
                            actions.close()
                        ]);
                    };

                    if (original) {
                        return original.call(this, data, { ...actions, redirect });
                    }
                };
            }
        },

        onClick: {
            type: 'function',
            required: false,
            decorate(original) : Function {
                return function() : void {

                    $logger.info('button_click');

                    if (getSessionState(session => session.buttonClicked)) {
                        $logger.info('button_click_multiple');
                    }

                    if (getSessionState(session => session.buttonCancelled)) {
                        $logger.info('button_click_after_cancel');
                    }

                    $logger.track({
                        [ FPTI.KEY.STATE ]: FPTI.STATE.BUTTON,
                        [ FPTI.KEY.TRANSITION ]: FPTI.TRANSITION.BUTTON_CLICK,
                        [ FPTI.KEY.BUTTON_TYPE ]: FPTI.BUTTON_TYPE.IFRAME
                    });

                    $logger.flush();

                    getSessionState(session => {
                        session.buttonClicked = true;
                    });

                    if (original) {
                        return original.apply(this, arguments);
                    }
                };
            }
        },

        locale: {
            type: 'string',
            required: false,
            queryParam: 'locale.x',

            def() : string {
                let { lang, country } = getBrowserLocale();
                return `${lang}_${country}`;
            },

            validate: validateButtonLocale
        },

        style: {
            type: 'object',
            required: false,
            queryParam: true,
            alias: 'buttonStyle',

            def() : Object {
                return {
                    color: 'gold',
                    shape: 'pill',
                    size:  'small',
                    label: 'checkout',
                    fundingicons: false
                };
            },

            validate: validateButtonStyle
        },

        displayTo: {
            type: 'string',
            required: false,
            def() : string {
                return USERS.ALL;
            }
        },

        disableLightbox: {
            type: 'boolean',
            required: false,

            get value() : boolean {
                return !hasMetaViewPort();
            }
        },

        validate: {
            type: 'function',
            required: false
        },

        logLevel: {
            type: 'string',
            required: false,
            get value() : string {
                return config.logLevel;
            }
        },

        popupBridge: {
            type: 'object',
            required: false,
            get value() : Object {
                return {
                    open: getPopupBridgeOpener(),
                    awaitOpener: awaitPopupBridgeOpener
                };
            }
        },

        test: {
            type: 'object',
            required: false,
            def() : Object {
                return { action: 'checkout' };
            }
        }
    }
});

if (Button.isChild()) {

    getPageRenderTime().then(pageRenderTime => {
        $logger.track({
            [ FPTI.KEY.STATE ]: FPTI.STATE.BUTTON,
            [ FPTI.KEY.TRANSITION ]: FPTI.TRANSITION.BUTTON_LOAD,
            [ FPTI.KEY.BUTTON_TYPE ]: FPTI.BUTTON_TYPE.IFRAME,
            [ FPTI.KEY.PAGE_LOAD_TIME ]: pageRenderTime
        });

        $logger.flush();
    });

    if (forceIframe()) {
        $logger.info('force_enable_iframe');
        enableCheckoutIframe({ force: true, time: 30 * 60 * 1000 });
    }

    if (window.xprops.logLevel) {
        setLogLevel(window.xprops.logLevel);
    }

    awaitPopupBridgeOpener();

    if (window.xprops.validate) {

        let enabled = true;

        window.xprops.validate({
            enable() {
                enabled = true;
            },

            disable() {
                enabled = false;
            }
        });

        let renderTo = Checkout.renderTo;

        Checkout.renderTo = function() : ?Promise<Object> {
            if (enabled) {
                return renderTo.apply(this, arguments);
            }
        };
    }
}
<commit_msg>Add more instrumentation around repeat button click behavior<commit_after>/* @flow */

import { ZalgoPromise } from 'zalgo-promise/src';
import * as xcomponent from 'xcomponent/src';
import * as $logger from 'beaver-logger/client';

import { Checkout, enableCheckoutIframe } from '../checkout';
import { config, USERS, SOURCE, ENV, FPTI } from '../../config';
import { redirect as redir, hasMetaViewPort, setLogLevel, forceIframe, getBrowserLocale, getCommonSessionID, request, checkpoint, isIEIntranet, getPageRenderTime, isEligible, getSessionState } from '../../lib';
import { rest } from '../../api';

import { getPopupBridgeOpener, awaitPopupBridgeOpener } from '../checkout/popupBridge';
import { containerTemplate, componentTemplate } from './templates';
import { validateButtonLocale, validateButtonStyle } from './templates/component/validate';
import { awaitBraintreeClient, type BraintreePayPalClient } from './braintree';

getSessionState(session => {
    session.buttonClicked = false;
    session.buttonCancelled = false;
    session.buttonAuthorized = false;
});

export let Button = xcomponent.create({

    tag: 'paypal-button',
    name: 'ppbutton',

    buildUrl(props) : string {
        let env = props.env || config.env;

        return config.buttonUrls[env];
    },

    contexts: {
        iframe: true,
        popup: false
    },

    scrolling: false,

    containerTemplate,
    componentTemplate({ props, htmlDom } : { props : Object, htmlDom : Function }) : HTMLElement {

        let template = htmlDom(componentTemplate({ props }));

        template.addEventListener('click', () => {
            $logger.warn('button_pre_template_click');
        });

        return template;
    },

    sacrificialComponentTemplate: true,

    get version() : string {
        return config.ppobjects ? __FILE_VERSION__ : __MINOR_VERSION__;
    },

    get domain() : Object {
        return config.paypalDomains;
    },

    autoResize: {
        width: false,
        height: true,
        element: 'body'
    },

    validate() {
        if (isIEIntranet()) {
            throw new Error(`Can not render button in IE intranet mode`);
        }

        if (!isEligible()) {
            $logger.warn('button_render_ineligible');
        }
    },

    props: {

        uid: {
            type: 'string',
            value: getCommonSessionID(),
            def() : string {
                return getCommonSessionID();
            },
            queryParam: true
        },

        env: {
            type: 'string',
            required: false,
            queryParam: true,

            def() : string {
                return config.env;
            },

            validate(env) {
                if (!config.paypalUrls[env]) {
                    throw new Error(`Invalid env: ${env}`);
                }
            }
        },

        client: {
            type: 'object',
            required: false,
            def() : Object {
                return {};
            },
            sendToChild: false,

            validate(client, props) {
                let env = props.env || config.env;

                if (!client[env]) {
                    throw new Error(`Client ID not found for env: ${env}`);
                }

                if (client[env].match(/^(.)\1+$/)) {
                    throw new Error(`Invalid client ID: ${client[env]}`);
                }
            }
        },

        source: {
            type: 'string',
            required: false,
            def() : string {
                return SOURCE.MANUAL;
            }
        },

        prefetchLogin: {
            type: 'boolean',
            required: false
        },

        stage: {
            type: 'string',
            required: false,
            queryParam: true,

            def(props) : ?string {
                let env = props.env || config.env;

                if (env === ENV.STAGE || env === ENV.LOCAL) {
                    return config.stage;
                }
            }
        },

        braintree: {
            type: 'object',
            required: false,
            validate(braintree, props) {

                if (!braintree.paypalCheckout) {
                    throw new Error(`Expected Braintree paypal-checkout component to be loaded`);
                }

                if (!props.client) {
                    throw new Error(`Expected client prop to be passed with Braintree authorization keys`);
                }
            },
            decorate(braintree, props) : ?ZalgoPromise<BraintreePayPalClient> {

                if (!braintree) {
                    return;
                }

                let env = props.env || config.env;
                let authorization = props.client[env];

                return awaitBraintreeClient(braintree, authorization);
            }
        },

        payment: {
            type: 'function',
            required: true,
            memoize: false,
            timeout: __TEST__ ? 500 : 10 * 1000,
            alias: 'billingAgreement',

            decorate(original) : Function {
                return function payment() : ZalgoPromise<string> {
                    return new ZalgoPromise((resolve, reject) => {

                        let data = resolve;
                        let actions = reject;

                        data.payment = actions.payment = {
                            create: (options, experience) => {
                                return rest.payment.create(this.props.env, this.props.client, options, experience);
                            }
                        };

                        data.braintree = actions.braintree = {
                            create: (options) => {
                                if (!this.props.braintree) {
                                    throw new Error(`Can not create using Braintree - no braintree client provided`);
                                }

                                return this.props.braintree.then(client => {
                                    return client.createPayment(options);
                                });
                            }
                        };

                        actions.request = request;

                        let context = {
                            props: {
                                env: this.props.env,
                                client: this.props.client
                            }
                        };

                        let result;

                        try {
                            result = original.call(context, data, actions);
                        } catch (err) {
                            return reject(err);
                        }

                        if (result && typeof result.then === 'function') {
                            return result.then(resolve, reject);
                        }

                        if (result !== undefined) {
                            return resolve(result);
                        }

                        let timeout = __TEST__ ? 500 : 10 * 1000;

                        setTimeout(() => {
                            reject(`Timed out waiting ${timeout}ms for payment`);
                        }, timeout);

                    }).then(result => {

                        if (!result) {
                            throw new Error(`No value passed to payment`);
                        }

                        return result;
                    });
                };
            }
        },

        commit: {
            type: 'boolean',
            required: false
        },

        onRender: {
            type: 'function',
            promisify: true,
            required: false,
            decorate(original) : Function {
                return function() : mixed {
                    checkpoint('render_iframe_button', { version: true });
                    $logger.track({
                        [ FPTI.KEY.STATE ]: FPTI.STATE.LOAD,
                        [ FPTI.KEY.TRANSITION ]: FPTI.TRANSITION.BUTTON_RENDER,
                        [ FPTI.KEY.BUTTON_TYPE ]: FPTI.BUTTON_TYPE.IFRAME,
                        [ FPTI.KEY.BUTTON_SOURCE ]: this.props.source
                    });
                    $logger.flush();
                    if (original) {
                        return original.apply(this, arguments);
                    }
                };
            }
        },

        onAuth: {
            type: 'function',
            required: false,

            value() {
                this.onRemember = this.onRemember || new ZalgoPromise();
                this.onRemember.resolve();

                enableCheckoutIframe();
            }
        },

        onRemembered: {
            type: 'function',
            required: false,

            value() {
                this.onRemember = this.onRemember || new ZalgoPromise();
                this.onRemember.resolve();
            }
        },

        onDisplay: {
            type: 'function',
            required: false,

            decorate(original) : Function {
                return function() : ZalgoPromise<void> {
                    return ZalgoPromise.try(() => {

                        this.onRemember = this.onRemember || new ZalgoPromise();

                        if (this.props.displayTo === USERS.REMEMBERED) {
                            $logger.info(`button_render_wait_for_remembered_user`);

                            return this.onRemember.then(() => {
                                $logger.info(`button_render_got_remembered_user`);
                            });
                        }

                    }).then(() => {

                        if (original) {
                            return original.apply(this, arguments);
                        }
                    });
                };
            }
        },

        onAuthorize: {
            type: 'function',
            required: true,

            decorate(original) : Function {
                return function(data, actions) : void | ZalgoPromise<void> {

                    $logger.info('checkout_authorize');

                    if (getSessionState(session => session.buttonAuthorized)) {
                        $logger.info('checkout_authorize_multiple');
                    } else {
                        $logger.info('checkout_authorize_unique');
                    }

                    if (getSessionState(session => session.buttonCancelled)) {
                        $logger.info('checkout_authorize_after_cancel');
                    }

                    getSessionState(session => {
                        session.buttonAuthorized = true;
                    });

                    $logger.track({
                        [ FPTI.KEY.STATE ]: FPTI.STATE.CHECKOUT,
                        [ FPTI.KEY.TRANSITION ]: FPTI.TRANSITION.CHECKOUT_AUTHORIZE
                    });

                    if (!isEligible()) {
                        $logger.info('button_authorize_ineligible');
                    }

                    $logger.flush();

                    if (this.props.braintree) {
                        return this.props.braintree.then(client => {
                            return client.tokenizePayment(data).then(res => {
                                return original.call(this, { nonce: res.nonce });
                            });
                        });
                    }

                    let redirect = (win, url) => {
                        return ZalgoPromise.all([
                            redir(win || window.top, url || data.returnUrl),
                            actions.close()
                        ]);
                    };

                    return ZalgoPromise.try(() => {
                        return original.call(this, data, { ...actions, redirect });
                    }).catch(err => {
                        return this.error(err);
                    });
                };
            }
        },

        onCancel: {
            type: 'function',
            required: false,
            noop: true,

            decorate(original) : Function {
                return function(data, actions) : void | ZalgoPromise<void> {

                    $logger.info('checkout_cancel');

                    if (getSessionState(session => session.buttonCancelled)) {
                        $logger.info('checkout_cancel_multiple');
                    } else {
                        $logger.info('checkout_cancel_unique');
                    }

                    if (getSessionState(session => session.buttonCancelled)) {
                        $logger.info('checkout_cancel_after_cancel');
                    }

                    getSessionState(session => {
                        session.buttonCancelled = true;
                    });

                    $logger.track({
                        [ FPTI.KEY.STATE ]: FPTI.STATE.CHECKOUT,
                        [ FPTI.KEY.TRANSITION ]: FPTI.TRANSITION.CHECKOUT_CANCEL
                    });

                    $logger.flush();

                    let redirect = (win, url) => {
                        return ZalgoPromise.all([
                            redir(win || window.top, url || data.cancelUrl),
                            actions.close()
                        ]);
                    };

                    if (original) {
                        return original.call(this, data, { ...actions, redirect });
                    }
                };
            }
        },

        onClick: {
            type: 'function',
            required: false,
            decorate(original) : Function {
                return function() : void {

                    $logger.info('button_click');

                    if (getSessionState(session => session.buttonClicked)) {
                        $logger.info('button_click_multiple');
                    } else {
                        $logger.info('button_click_unique');
                    }

                    if (getSessionState(session => session.buttonCancelled)) {
                        $logger.info('button_click_after_cancel');
                    }

                    getSessionState(session => {
                        session.buttonClicked = true;
                    });

                    $logger.track({
                        [ FPTI.KEY.STATE ]: FPTI.STATE.BUTTON,
                        [ FPTI.KEY.TRANSITION ]: FPTI.TRANSITION.BUTTON_CLICK,
                        [ FPTI.KEY.BUTTON_TYPE ]: FPTI.BUTTON_TYPE.IFRAME
                    });

                    $logger.flush();

                    if (original) {
                        return original.apply(this, arguments);
                    }
                };
            }
        },

        locale: {
            type: 'string',
            required: false,
            queryParam: 'locale.x',

            def() : string {
                let { lang, country } = getBrowserLocale();
                return `${lang}_${country}`;
            },

            validate: validateButtonLocale
        },

        style: {
            type: 'object',
            required: false,
            queryParam: true,
            alias: 'buttonStyle',

            def() : Object {
                return {
                    color: 'gold',
                    shape: 'pill',
                    size:  'small',
                    label: 'checkout',
                    fundingicons: false
                };
            },

            validate: validateButtonStyle
        },

        displayTo: {
            type: 'string',
            required: false,
            def() : string {
                return USERS.ALL;
            }
        },

        disableLightbox: {
            type: 'boolean',
            required: false,

            get value() : boolean {
                return !hasMetaViewPort();
            }
        },

        validate: {
            type: 'function',
            required: false
        },

        logLevel: {
            type: 'string',
            required: false,
            get value() : string {
                return config.logLevel;
            }
        },

        popupBridge: {
            type: 'object',
            required: false,
            get value() : Object {
                return {
                    open: getPopupBridgeOpener(),
                    awaitOpener: awaitPopupBridgeOpener
                };
            }
        },

        test: {
            type: 'object',
            required: false,
            def() : Object {
                return { action: 'checkout' };
            }
        }
    }
});

if (Button.isChild()) {

    getPageRenderTime().then(pageRenderTime => {
        $logger.track({
            [ FPTI.KEY.STATE ]: FPTI.STATE.BUTTON,
            [ FPTI.KEY.TRANSITION ]: FPTI.TRANSITION.BUTTON_LOAD,
            [ FPTI.KEY.BUTTON_TYPE ]: FPTI.BUTTON_TYPE.IFRAME,
            [ FPTI.KEY.PAGE_LOAD_TIME ]: pageRenderTime
        });

        $logger.flush();
    });

    if (forceIframe()) {
        $logger.info('force_enable_iframe');
        enableCheckoutIframe({ force: true, time: 30 * 60 * 1000 });
    }

    if (window.xprops.logLevel) {
        setLogLevel(window.xprops.logLevel);
    }

    awaitPopupBridgeOpener();

    if (window.xprops.validate) {

        let enabled = true;

        window.xprops.validate({
            enable() {
                enabled = true;
            },

            disable() {
                enabled = false;
            }
        });

        let renderTo = Checkout.renderTo;

        Checkout.renderTo = function() : ?Promise<Object> {
            if (enabled) {
                return renderTo.apply(this, arguments);
            }
        };
    }
}
<|endoftext|>


<commit_before>import {containerless, inject, inlineView} from 'aurelia-framework'
import {EventAggregator} from 'aurelia-event-aggregator'

@containerless()
@inlineView(`
<template>

        <content></content>

</template>
`)
@inject(EventAggregator)
export class MenuToggle {

    _open = false

    constructor(events) {
        this.events = events

        this.setMenuStateFromCookie()
    }

    get open() {
        return this._open
    }

    set open(open) {
        this._open = open

        this.events.publish('menu:toggle-open', this._open)

        document.cookie = `menuopen=${this._open}`
    }

    toggle() {
        this.open = !this.open
    }

    setMenuStateFromCookie() {
        let cookie = document.cookie.replace(/(?:(?:^|.*;\s*)menuopen\s*\=\s*([^;]*).*$)|^.*$/, '$1');

        if (!cookie) return

        this.open = cookie
    }

}
<commit_msg>Fix boolean check<commit_after>import {containerless, inject, inlineView} from 'aurelia-framework'
import {EventAggregator} from 'aurelia-event-aggregator'

@containerless()
@inlineView(`
<template>

        <content></content>

</template>
`)
@inject(EventAggregator)
export class MenuToggle {

    _open = false

    constructor(events) {
        this.events = events

        this.setMenuStateFromCookie()
    }

    get open() {
        return this._open
    }

    set open(open) {
        this._open = open

        this.events.publish('menu:toggle-open', this._open)

        document.cookie = `menuopen=${this._open}`
    }

    toggle() {
        this.open = !this.open
    }

    setMenuStateFromCookie() {
        let cookie = document.cookie.replace(/(?:(?:^|.*;\s*)menuopen\s*\=\s*([^;]*).*$)|^.*$/, '$1')

        this.open = (cookie && (cookie === true || cookie === 'true')) ? true : false
    }

}
<|endoftext|>


<commit_before>'use strict';

var logger = browser.getLogger('components/Source');

/**
 * @description
 * returns all network subnets
 */

exports.getTree = function() {
    logger.info('getting tree');
    return element.all(by.css('.source .tree')).then(function(tree) {
        expect(tree).not.toBeUndefined();
    });
};

/**
 * @description
 * click on router to open details floating panel
 */
exports.getFileContent = function() {
    return element.all(by.css('.source .sourcecode')).then(function(sourcecode) {
        expect(sourcecode).not.toBeUndefined();
    });
};

exports.selectFile = function(fileName) {
    return element.all(by.css('.tree span.clickable')).filter(function (file) {
        return file.getText().then(function (text) {
            return text === fileName;
        });
    }).click();
};

exports.getFileTitle = function(expectedTitle) {
    return element(by.css('.source .sourcecode h4')).getText().then(function(text) {
        expect(text).toBe(expectedTitle);
    });
};

exports.getLoadingMessage = function() {
    //logger.info('getting loading message');
    var locator = by.css('.noPreview p');
    browser.driver.wait(function () {
        console.log('waiting..');
        return browser.driver.isElementPresent(locator);
    }, 10000);
    return browser.driver.findElement(locator).getText(); // use selenium API directly since we don't want angular synced here..
};<commit_msg>add check if element is displayed<commit_after>'use strict';

var logger = browser.getLogger('components/Source');

/**
 * @description
 * returns all network subnets
 */

exports.getTree = function() {
    logger.info('getting tree');
    return element.all(by.css('.source .tree')).then(function(tree) {
        expect(tree).not.toBeUndefined();
    });
};

/**
 * @description
 * click on router to open details floating panel
 */
exports.getFileContent = function() {
    return element.all(by.css('.source .sourcecode')).then(function(sourcecode) {
        expect(sourcecode).not.toBeUndefined();
    });
};

exports.selectFile = function(fileName) {
    return element.all(by.css('.tree span.clickable')).filter(function (file) {
        return file.getText().then(function (text) {
            return text === fileName;
        });
    }).click();
};

exports.getFileTitle = function(expectedTitle) {
    return element(by.css('.source .sourcecode h4')).getText().then(function(text) {
        expect(text).toBe(expectedTitle);
    });
};

exports.getLoadingMessage = function( empty ) {
    //logger.info('getting loading message');
    var locator = by.css('.noPreview p');
    browser.driver.wait(function () {
        return browser.driver.isElementPresent(locator) && browser.driver.findElement(locator).isDisplayed();
    }, 10000);
    return browser.driver.findElement(locator).getText(); // use selenium API directly since we don't want angular synced here..
};<|endoftext|>


<commit_before>/**
* Copyright 2012-2016, 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 Plotly = require('../../plotly');
var Plots = require('../../plots/plots');
var Lib = require('../../lib');
var Color = require('../color');
var Drawing = require('../drawing');
var svgTextUtils = require('../../lib/svg_text_utils');
var anchorUtils = require('../legend/anchor_utils');

var constants = require('./constants');

module.exports = function draw(gd) {
    var fullLayout = gd._fullLayout,
        menuData = makeMenuData(fullLayout);

    /* Update menu data is bound to the header-group.
     * The items in the header group are always present.
     *
     * Upon clicking on a header its corresponding button
     * data is bound to the button-group.
     *
     * We draw all headers in one group before all buttons
     * so that the buttons *always* appear above the headers.
     *
     * Note that only one set of buttons are visible at once.
     *
     * <g container />
     *
     *     <g header-group />
     *         <g item header />
     *         <text item header-arrow />
     *     <g header-group />
     *         <g item header />
     *         <text item header-arrow />
     *     ...
     *
     *     <g button-group />
     *         <g item button />
     *         <g item button />
     *         ...
     */

    // draw update menu container
    var menus = fullLayout._infolayer
        .selectAll('g.' + constants.containerClassName)
        .data(menuData.length > 0 ? [0] : []);

    menus.enter().append('g')
        .classed(constants.containerClassName, true)
        .style('cursor', 'pointer');

    menus.exit().remove();

    // return early if no update menus are visible
    if(menuData.length === 0) return;

    // join header group
    var headerGroups = menus.selectAll('g.' + constants.headerGroupClassName)
        .data(menuData, keyFunction);

    headerGroups.enter().append('g')
        .classed(constants.headerGroupClassName, true);

    // draw button container
    var gButton = menus.selectAll('g.' + constants.buttonGroupClassName)
        .data([0]);

    gButton.enter().append('g')
        .classed(constants.buttonGroupClassName, true)
        .style('pointer-events', 'all');

    // whenever we add new menu,
    if(headerGroups.enter().size()) {

        // attach 'state' variable to node to keep track of the active menu
        // '-1' means no menu is active
        gButton.attr(constants.menuIndexAttrName, '-1');

        // remove all dropped buttons (if any)
        gButton.selectAll('g.' + constants.buttonClassName).remove();
    }

    // remove exiting header, remove dropped buttons and reset margins
    headerGroups.exit().each(function(menuOpts) {
        d3.select(this).remove();
        gButton.selectAll('g.' + constants.buttonClassName).remove();
        Plots.autoMargin(gd, constants.autoMarginIdRoot + menuOpts._index);
    });

    // find dimensions before plotting anything (this mutates menuOpts)
    for(var i = 0; i < menuData.length; i++) {
        var menuOpts = menuData[i];

        // often more convenient than playing with two arguments
        menuOpts._index = i;
        findDimenstions(gd, menuOpts);
    }

    // draw headers!
    headerGroups.each(function(menuOpts) {
        var gHeader = d3.select(this);
        drawHeader(gd, gHeader, gButton, menuOpts);

        // update buttons if they are dropped
        if(areMenuButtonsDropped(gButton, menuOpts)) {
            drawButtons(gd, gHeader, gButton, menuOpts);
        }
    });
};

function makeMenuData(fullLayout) {
    var contOpts = fullLayout[constants.name],
        menuData = [];

    for(var i = 0; i < contOpts.length; i++) {
        var item = contOpts[i];

        if(item.visible) menuData.push(item);
    }

    return menuData;
}

function keyFunction(opts, i) {
    return opts.visible + i;
}

function areMenuButtonsDropped(gButton, menuOpts) {
    var droppedIndex = gButton.attr(constants.menuIndexAttrName);

    return droppedIndex === menuOpts._index;
}

function drawHeader(gd, gHeader, gButton, menuOpts) {
    var header = gHeader.selectAll('g.' + constants.headerClassName)
        .data([0]);

    header.enter().append('g')
        .classed(constants.headerClassName, true)
        .style('pointer-events', 'all');

    var active = menuOpts.active,
        headerOpts = menuOpts.buttons[active] || constants.blankHeaderOpts,
        posOpts = { y: 0, yPad: 0 };

    header
        .call(drawItem, menuOpts, headerOpts)
        .call(setItemPosition, menuOpts, posOpts);

    // draw drop arrow at the right edge
    var arrow = gHeader.selectAll('text.' + constants.headerArrowClassName)
        .data([0]);

    arrow.enter().append('text')
        .classed(constants.headerArrowClassName, true)
        .classed('user-select-none', true)
        .attr('text-anchor', 'end')
        .call(Drawing.font, menuOpts.font)
        .text('▼');

    arrow.attr({
        x: menuOpts.width - constants.arrowOffsetX,
        y: menuOpts.height1 / 2 + constants.textOffsetY
    });

    header.on('click', function() {
        gButton.selectAll('g.' + constants.buttonClassName).remove();

        // if clicked index is same as dropped index => fold
        // otherwise => drop buttons associated with header
        gButton.attr(
            constants.menuIndexAttrName,
            areMenuButtonsDropped(gButton, menuOpts) ? '-1' : String(menuOpts._index)
        );

        drawButtons(gd, gHeader, gButton, menuOpts);
    });

    header.on('mouseover', function() {
        header.call(styleOnMouseOver);
    });

    header.on('mouseout', function() {
        header.call(styleOnMouseOut, menuOpts);
    });

    // translate header group
    Lib.setTranslate(gHeader, menuOpts.lx, menuOpts.ly);
}

function drawButtons(gd, gHeader, gButton, menuOpts) {
    var buttonData = gButton.attr(constants.menuIndexAttrName) !== '-1' ?
        menuOpts.buttons :
        [];

    var buttons = gButton.selectAll('g.' + constants.buttonClassName)
        .data(buttonData);

    buttons.enter().append('g')
        .classed(constants.buttonClassName, true)
        .attr('opacity', '0')
        .transition()
        .attr('opacity', '1');

    buttons.exit()
        .transition()
        .attr('opacity', '0')
        .remove();

    var posOpts = {
        y: menuOpts.height1 + constants.gapButtonHeader,
        yPad: constants.gapButton
    };

    buttons.each(function(buttonOpts, buttonIndex) {
        var button = d3.select(this);

        button
            .call(drawItem, menuOpts, buttonOpts)
            .call(setItemPosition, menuOpts, posOpts);

        button.on('click', function() {
            // update 'active' attribute in menuOpts
            menuOpts._input.active = menuOpts.active = buttonIndex;

            // fold up buttons and redraw header
            gButton.attr(constants.menuIndexAttrName, '-1');
            drawHeader(gd, gHeader, gButton, menuOpts);
            drawButtons(gd, gHeader, gButton, menuOpts);

            // call button method
            var args = buttonOpts.args;
            Plotly[buttonOpts.method](gd, args[0], args[1], args[2]);
        });

        button.on('mouseover', function() {
            button.call(styleOnMouseOver);
        });

        button.on('mouseout', function() {
            button.call(styleOnMouseOut, menuOpts);
            buttons.call(styleButtons, menuOpts);
        });
    });

    buttons.call(styleButtons, menuOpts);

    // translate button group
    Lib.setTranslate(gButton, menuOpts.lx, menuOpts.ly);
}

function drawItem(item, menuOpts, itemOpts) {
    item.call(drawItemRect, menuOpts)
        .call(drawItemText, menuOpts, itemOpts);
}

function drawItemRect(item, menuOpts) {
    var rect = item.selectAll('rect')
        .data([0]);

    rect.enter().append('rect')
        .classed(constants.itemRectClassName, true)
        .attr({
            rx: constants.rx,
            ry: constants.ry,
            'shape-rendering': 'crispEdges'
        });

    rect.call(Color.stroke, menuOpts.bordercolor)
        .call(Color.fill, menuOpts.bgcolor)
        .style('stroke-width', menuOpts.borderwidth + 'px');
}

function drawItemText(item, menuOpts, itemOpts) {
    var text = item.selectAll('text')
        .data([0]);

    text.enter().append('text')
        .classed(constants.itemTextClassName, true)
        .classed('user-select-none', true)
        .attr('text-anchor', 'start');

    text.call(Drawing.font, menuOpts.font)
        .text(itemOpts.label)
        .call(svgTextUtils.convertToTspans);
}

function styleButtons(buttons, menuOpts) {
    var active = menuOpts.active;

    buttons.each(function(buttonOpts, i) {
        var button = d3.select(this);

        if(i === active) {
            button.select('rect.' + constants.itemRectClassName)
                .call(Color.fill, constants.activeColor);
        }
    });
}

function styleOnMouseOver(item) {
    item.select('rect.' + constants.itemRectClassName)
        .call(Color.fill, constants.hoverColor);
}

function styleOnMouseOut(item, menuOpts) {
    item.select('rect.' + constants.itemRectClassName)
        .call(Color.fill, menuOpts.bgcolor);
}

// find item dimensions (this mutates menuOpts)
function findDimenstions(gd, menuOpts) {
    menuOpts.width = 0;
    menuOpts.height = 0;
    menuOpts.height1 = 0;
    menuOpts.lx = 0;
    menuOpts.ly = 0;

    var fakeButtons = gd._tester.selectAll('g.' + constants.buttonClassName)
        .data(menuOpts.buttons);

    fakeButtons.enter().append('g')
        .classed(constants.buttonClassName, true);

    // loop over fake buttons to find width / height
    fakeButtons.each(function(buttonOpts) {
        var button = d3.select(this);

        button.call(drawItem, menuOpts, buttonOpts);

        var text = button.select('.' + constants.itemTextClassName),
            tspans = text.selectAll('tspan');

        // width is given by max width of all buttons
        var tWidth = text.node() && Drawing.bBox(text.node()).width,
            wEff = Math.max(tWidth + constants.textPadX, constants.minWidth);

        // height is determined by item text
        var tHeight = menuOpts.font.size * constants.fontSizeToHeight,
            tLines = tspans[0].length || 1,
            hEff = Math.max(tHeight * tLines, constants.minHeight) + constants.textOffsetY;

        menuOpts.width = Math.max(menuOpts.width, wEff);
        menuOpts.height1 = Math.max(menuOpts.height1, hEff);
        menuOpts.height += menuOpts.height1;
    });

    fakeButtons.remove();

    var graphSize = gd._fullLayout._size;
    menuOpts.lx = graphSize.l + graphSize.w * menuOpts.x;
    menuOpts.ly = graphSize.t + graphSize.h * (1 - menuOpts.y);

    var xanchor = 'left';
    if(anchorUtils.isRightAnchor(menuOpts)) {
        menuOpts.lx -= menuOpts.width;
        xanchor = 'right';
    }
    if(anchorUtils.isCenterAnchor(menuOpts)) {
        menuOpts.lx -= menuOpts.width / 2;
        xanchor = 'center';
    }

    var yanchor = 'top';
    if(anchorUtils.isBottomAnchor(menuOpts)) {
        menuOpts.ly -= menuOpts.height;
        yanchor = 'bottom';
    }
    if(anchorUtils.isMiddleAnchor(menuOpts)) {
        menuOpts.ly -= menuOpts.height / 2;
        yanchor = 'middle';
    }

    menuOpts.width = Math.ceil(menuOpts.width);
    menuOpts.height = Math.ceil(menuOpts.height);
    menuOpts.lx = Math.round(menuOpts.lx);
    menuOpts.ly = Math.round(menuOpts.ly);

    Plots.autoMargin(gd, constants.autoMarginIdRoot + menuOpts._index, {
        x: menuOpts.x,
        y: menuOpts.y,
        l: menuOpts.width * ({right: 1, center: 0.5}[xanchor] || 0),
        r: menuOpts.width * ({left: 1, center: 0.5}[xanchor] || 0),
        b: menuOpts.height * ({top: 1, middle: 0.5}[yanchor] || 0),
        t: menuOpts.height * ({bottom: 1, middle: 0.5}[yanchor] || 0)
    });
}

// set item positions (mutates posOpts)
function setItemPosition(item, menuOpts, posOpts) {
    var rect = item.select('.' + constants.itemRectClassName),
        text = item.select('.' + constants.itemTextClassName),
        tspans = text.selectAll('tspan'),
        borderWidth = menuOpts.borderwidth;

    Lib.setTranslate(item, borderWidth, borderWidth + posOpts.y);

    rect.attr({
        x: 0,
        y: 0,
        width: menuOpts.width,
        height: menuOpts.height1
    });

    var tHeight = menuOpts.font.size * constants.fontSizeToHeight,
        tLines = tspans[0].length || 1,
        spanOffset = ((tLines - 1) * tHeight / 4);

    var textAttrs = {
        x: constants.textOffsetX,
        y: menuOpts.height1 / 2 - spanOffset + constants.textOffsetY
    };

    text.attr(textAttrs);
    tspans.attr(textAttrs);

    posOpts.y += menuOpts.height1 + posOpts.yPad;
}
<commit_msg>fixup update menu header drop/fold toggle<commit_after>/**
* Copyright 2012-2016, 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 Plotly = require('../../plotly');
var Plots = require('../../plots/plots');
var Lib = require('../../lib');
var Color = require('../color');
var Drawing = require('../drawing');
var svgTextUtils = require('../../lib/svg_text_utils');
var anchorUtils = require('../legend/anchor_utils');

var constants = require('./constants');

module.exports = function draw(gd) {
    var fullLayout = gd._fullLayout,
        menuData = makeMenuData(fullLayout);

    /* Update menu data is bound to the header-group.
     * The items in the header group are always present.
     *
     * Upon clicking on a header its corresponding button
     * data is bound to the button-group.
     *
     * We draw all headers in one group before all buttons
     * so that the buttons *always* appear above the headers.
     *
     * Note that only one set of buttons are visible at once.
     *
     * <g container />
     *
     *     <g header-group />
     *         <g item header />
     *         <text item header-arrow />
     *     <g header-group />
     *         <g item header />
     *         <text item header-arrow />
     *     ...
     *
     *     <g button-group />
     *         <g item button />
     *         <g item button />
     *         ...
     */

    // draw update menu container
    var menus = fullLayout._infolayer
        .selectAll('g.' + constants.containerClassName)
        .data(menuData.length > 0 ? [0] : []);

    menus.enter().append('g')
        .classed(constants.containerClassName, true)
        .style('cursor', 'pointer');

    menus.exit().remove();

    // return early if no update menus are visible
    if(menuData.length === 0) return;

    // join header group
    var headerGroups = menus.selectAll('g.' + constants.headerGroupClassName)
        .data(menuData, keyFunction);

    headerGroups.enter().append('g')
        .classed(constants.headerGroupClassName, true);

    // draw button container
    var gButton = menus.selectAll('g.' + constants.buttonGroupClassName)
        .data([0]);

    gButton.enter().append('g')
        .classed(constants.buttonGroupClassName, true)
        .style('pointer-events', 'all');

    // whenever we add new menu,
    if(headerGroups.enter().size()) {

        // attach 'state' variable to node to keep track of the active menu
        // '-1' means no menu is active
        gButton.attr(constants.menuIndexAttrName, '-1');

        // remove all dropped buttons (if any)
        gButton.selectAll('g.' + constants.buttonClassName).remove();
    }

    // remove exiting header, remove dropped buttons and reset margins
    headerGroups.exit().each(function(menuOpts) {
        d3.select(this).remove();
        gButton.selectAll('g.' + constants.buttonClassName).remove();
        Plots.autoMargin(gd, constants.autoMarginIdRoot + menuOpts._index);
    });

    // find dimensions before plotting anything (this mutates menuOpts)
    for(var i = 0; i < menuData.length; i++) {
        var menuOpts = menuData[i];

        // often more convenient than playing with two arguments
        menuOpts._index = i;
        findDimenstions(gd, menuOpts);
    }

    // draw headers!
    headerGroups.each(function(menuOpts) {
        var gHeader = d3.select(this);
        drawHeader(gd, gHeader, gButton, menuOpts);

        // update buttons if they are dropped
        if(areMenuButtonsDropped(gButton, menuOpts)) {
            drawButtons(gd, gHeader, gButton, menuOpts);
        }
    });
};

function makeMenuData(fullLayout) {
    var contOpts = fullLayout[constants.name],
        menuData = [];

    for(var i = 0; i < contOpts.length; i++) {
        var item = contOpts[i];

        if(item.visible) menuData.push(item);
    }

    return menuData;
}

function keyFunction(opts, i) {
    return opts.visible + i;
}

function areMenuButtonsDropped(gButton, menuOpts) {
    var droppedIndex = +gButton.attr(constants.menuIndexAttrName);

    return droppedIndex === menuOpts._index;
}

function drawHeader(gd, gHeader, gButton, menuOpts) {
    var header = gHeader.selectAll('g.' + constants.headerClassName)
        .data([0]);

    header.enter().append('g')
        .classed(constants.headerClassName, true)
        .style('pointer-events', 'all');

    var active = menuOpts.active,
        headerOpts = menuOpts.buttons[active] || constants.blankHeaderOpts,
        posOpts = { y: 0, yPad: 0 };

    header
        .call(drawItem, menuOpts, headerOpts)
        .call(setItemPosition, menuOpts, posOpts);

    // draw drop arrow at the right edge
    var arrow = gHeader.selectAll('text.' + constants.headerArrowClassName)
        .data([0]);

    arrow.enter().append('text')
        .classed(constants.headerArrowClassName, true)
        .classed('user-select-none', true)
        .attr('text-anchor', 'end')
        .call(Drawing.font, menuOpts.font)
        .text('▼');

    arrow.attr({
        x: menuOpts.width - constants.arrowOffsetX,
        y: menuOpts.height1 / 2 + constants.textOffsetY
    });

    header.on('click', function() {
        gButton.selectAll('g.' + constants.buttonClassName).remove();

        // if clicked index is same as dropped index => fold
        // otherwise => drop buttons associated with header
        gButton.attr(
            constants.menuIndexAttrName,
            areMenuButtonsDropped(gButton, menuOpts) ? '-1' : String(menuOpts._index)
        );

        drawButtons(gd, gHeader, gButton, menuOpts);
    });

    header.on('mouseover', function() {
        header.call(styleOnMouseOver);
    });

    header.on('mouseout', function() {
        header.call(styleOnMouseOut, menuOpts);
    });

    // translate header group
    Lib.setTranslate(gHeader, menuOpts.lx, menuOpts.ly);
}

function drawButtons(gd, gHeader, gButton, menuOpts) {
    var buttonData = gButton.attr(constants.menuIndexAttrName) !== '-1' ?
        menuOpts.buttons :
        [];

    var buttons = gButton.selectAll('g.' + constants.buttonClassName)
        .data(buttonData);

    buttons.enter().append('g')
        .classed(constants.buttonClassName, true)
        .attr('opacity', '0')
        .transition()
        .attr('opacity', '1');

    buttons.exit()
        .transition()
        .attr('opacity', '0')
        .remove();

    var posOpts = {
        y: menuOpts.height1 + constants.gapButtonHeader,
        yPad: constants.gapButton
    };

    buttons.each(function(buttonOpts, buttonIndex) {
        var button = d3.select(this);

        button
            .call(drawItem, menuOpts, buttonOpts)
            .call(setItemPosition, menuOpts, posOpts);

        button.on('click', function() {
            // update 'active' attribute in menuOpts
            menuOpts._input.active = menuOpts.active = buttonIndex;

            // fold up buttons and redraw header
            gButton.attr(constants.menuIndexAttrName, '-1');
            drawHeader(gd, gHeader, gButton, menuOpts);
            drawButtons(gd, gHeader, gButton, menuOpts);

            // call button method
            var args = buttonOpts.args;
            Plotly[buttonOpts.method](gd, args[0], args[1], args[2]);
        });

        button.on('mouseover', function() {
            button.call(styleOnMouseOver);
        });

        button.on('mouseout', function() {
            button.call(styleOnMouseOut, menuOpts);
            buttons.call(styleButtons, menuOpts);
        });
    });

    buttons.call(styleButtons, menuOpts);

    // translate button group
    Lib.setTranslate(gButton, menuOpts.lx, menuOpts.ly);
}

function drawItem(item, menuOpts, itemOpts) {
    item.call(drawItemRect, menuOpts)
        .call(drawItemText, menuOpts, itemOpts);
}

function drawItemRect(item, menuOpts) {
    var rect = item.selectAll('rect')
        .data([0]);

    rect.enter().append('rect')
        .classed(constants.itemRectClassName, true)
        .attr({
            rx: constants.rx,
            ry: constants.ry,
            'shape-rendering': 'crispEdges'
        });

    rect.call(Color.stroke, menuOpts.bordercolor)
        .call(Color.fill, menuOpts.bgcolor)
        .style('stroke-width', menuOpts.borderwidth + 'px');
}

function drawItemText(item, menuOpts, itemOpts) {
    var text = item.selectAll('text')
        .data([0]);

    text.enter().append('text')
        .classed(constants.itemTextClassName, true)
        .classed('user-select-none', true)
        .attr('text-anchor', 'start');

    text.call(Drawing.font, menuOpts.font)
        .text(itemOpts.label)
        .call(svgTextUtils.convertToTspans);
}

function styleButtons(buttons, menuOpts) {
    var active = menuOpts.active;

    buttons.each(function(buttonOpts, i) {
        var button = d3.select(this);

        if(i === active) {
            button.select('rect.' + constants.itemRectClassName)
                .call(Color.fill, constants.activeColor);
        }
    });
}

function styleOnMouseOver(item) {
    item.select('rect.' + constants.itemRectClassName)
        .call(Color.fill, constants.hoverColor);
}

function styleOnMouseOut(item, menuOpts) {
    item.select('rect.' + constants.itemRectClassName)
        .call(Color.fill, menuOpts.bgcolor);
}

// find item dimensions (this mutates menuOpts)
function findDimenstions(gd, menuOpts) {
    menuOpts.width = 0;
    menuOpts.height = 0;
    menuOpts.height1 = 0;
    menuOpts.lx = 0;
    menuOpts.ly = 0;

    var fakeButtons = gd._tester.selectAll('g.' + constants.buttonClassName)
        .data(menuOpts.buttons);

    fakeButtons.enter().append('g')
        .classed(constants.buttonClassName, true);

    // loop over fake buttons to find width / height
    fakeButtons.each(function(buttonOpts) {
        var button = d3.select(this);

        button.call(drawItem, menuOpts, buttonOpts);

        var text = button.select('.' + constants.itemTextClassName),
            tspans = text.selectAll('tspan');

        // width is given by max width of all buttons
        var tWidth = text.node() && Drawing.bBox(text.node()).width,
            wEff = Math.max(tWidth + constants.textPadX, constants.minWidth);

        // height is determined by item text
        var tHeight = menuOpts.font.size * constants.fontSizeToHeight,
            tLines = tspans[0].length || 1,
            hEff = Math.max(tHeight * tLines, constants.minHeight) + constants.textOffsetY;

        menuOpts.width = Math.max(menuOpts.width, wEff);
        menuOpts.height1 = Math.max(menuOpts.height1, hEff);
        menuOpts.height += menuOpts.height1;
    });

    fakeButtons.remove();

    var graphSize = gd._fullLayout._size;
    menuOpts.lx = graphSize.l + graphSize.w * menuOpts.x;
    menuOpts.ly = graphSize.t + graphSize.h * (1 - menuOpts.y);

    var xanchor = 'left';
    if(anchorUtils.isRightAnchor(menuOpts)) {
        menuOpts.lx -= menuOpts.width;
        xanchor = 'right';
    }
    if(anchorUtils.isCenterAnchor(menuOpts)) {
        menuOpts.lx -= menuOpts.width / 2;
        xanchor = 'center';
    }

    var yanchor = 'top';
    if(anchorUtils.isBottomAnchor(menuOpts)) {
        menuOpts.ly -= menuOpts.height;
        yanchor = 'bottom';
    }
    if(anchorUtils.isMiddleAnchor(menuOpts)) {
        menuOpts.ly -= menuOpts.height / 2;
        yanchor = 'middle';
    }

    menuOpts.width = Math.ceil(menuOpts.width);
    menuOpts.height = Math.ceil(menuOpts.height);
    menuOpts.lx = Math.round(menuOpts.lx);
    menuOpts.ly = Math.round(menuOpts.ly);

    Plots.autoMargin(gd, constants.autoMarginIdRoot + menuOpts._index, {
        x: menuOpts.x,
        y: menuOpts.y,
        l: menuOpts.width * ({right: 1, center: 0.5}[xanchor] || 0),
        r: menuOpts.width * ({left: 1, center: 0.5}[xanchor] || 0),
        b: menuOpts.height * ({top: 1, middle: 0.5}[yanchor] || 0),
        t: menuOpts.height * ({bottom: 1, middle: 0.5}[yanchor] || 0)
    });
}

// set item positions (mutates posOpts)
function setItemPosition(item, menuOpts, posOpts) {
    var rect = item.select('.' + constants.itemRectClassName),
        text = item.select('.' + constants.itemTextClassName),
        tspans = text.selectAll('tspan'),
        borderWidth = menuOpts.borderwidth;

    Lib.setTranslate(item, borderWidth, borderWidth + posOpts.y);

    rect.attr({
        x: 0,
        y: 0,
        width: menuOpts.width,
        height: menuOpts.height1
    });

    var tHeight = menuOpts.font.size * constants.fontSizeToHeight,
        tLines = tspans[0].length || 1,
        spanOffset = ((tLines - 1) * tHeight / 4);

    var textAttrs = {
        x: constants.textOffsetX,
        y: menuOpts.height1 / 2 - spanOffset + constants.textOffsetY
    };

    text.attr(textAttrs);
    tspans.attr(textAttrs);

    posOpts.y += menuOpts.height1 + posOpts.yPad;
}
<|endoftext|>


<commit_before>const videovlogs=[
{
  "video":"https://www.youtube.com/embed/gNpThxQjxUI",
  "key":"1",
  "desc":"osaka"
}
// { "video":"https://www.youtube.com/watch?v=1oODkyd7JFU",
// 	"key":"2",
// 	"desc":"2"
// },
// {"video":"https://www.youtube.com/watch?v=gNpThxQjxUI",
// 	"key":"3",
// 	"desc":"3"
// }
];

export default videovlogs;<commit_msg>added second video<commit_after>const videovlogs=[
{
  "video":"https://www.youtube.com/embed/gNpThxQjxUI",
  "key":"1",
  "desc":"osaka",
  "video":"https://www.youtube.com/embed/UeKSh9fIeYo",
  "key":"2",
  "desc":"nagasaki"

}
// { "video":"https://www.youtube.com/watch?v=1oODkyd7JFU",
// 	"key":"2",
// 	"desc":"2"
// },
// {"video":"https://www.youtube.com/watch?v=gNpThxQjxUI",
// 	"key":"3",
// 	"desc":"3"
// }
];

export default videovlogs;<|endoftext|>


<commit_before>import React, {Component} from 'react';
import QueryWindow from '../SearchWindow/querybuilder';
import MapWindow from '../MapWindow/mapView';
import TableWindow from '../TableWindow/tableView';
import {ButtonToolbar, Dropdown, Glyphicon, MenuItem} from 'react-bootstrap';
import ReactDOM from 'react-dom';

import MapAlert from '../Alerts/MapAlert';
import TableAlert from '../Alerts/TableAlert';
import HistogramAlert from '../Alerts/HistogramAlert';
import CloudAlert from '../Alerts/CloudAlert';
import CardAlert from '../Alerts/CardAlert';

function isAlert(t) {
    return t == 'MapAlert' || t == 'TableAlert' || t == 'HistogramAlert' || t == 'CloudAlert';
}
const DropdownMenu = Dropdown.Menu;

/* eslint-disable react/no-multi-comp*/
/* eslint-disable react/prop-types*/
const GoldenLayout = require('golden-layout');

class CustomToggle extends React.Component {
    constructor(props, context) {
        super(props, context);

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

    handleClick(e) {
        e.preventDefault();

        this.props.onClick(e);
    }

    render() {
        return (
            <Glyphicon glyph="cog" onClick={this.handleClick} />
        );
    }
}

//configuration for the layout
const layoutConfig = {
    settings: {
        hasHeaders: true
    },
    dimensions: {
        borderWidth: 2,
        headerHeight: 25
    },
    content: [{
        type: 'row',
        content: [{
            type: 'column',
            width: 50,
            content: [
                {
                    type: 'row',
                    height: 50,
                    content: [{
                        type: 'react-component',
                        component: 'Search Criteria',
                        title: 'Search Criteria'
                    }]
                },
                {
                    type: 'row',
                    height: 50,
                    content: [{
                        type: 'column',
                        width: 33,
                        content: [
                            {
                                type: 'react-component',
                                component: 'MapAlert',
                                title: 'Alert1',
                                props: { title: 'Alert 1', color: '#53eb53' }
                            }, {
                                type: 'react-component',
                                component: 'TableAlert',
                                title: 'Alert2',
                                props: { title: 'Alert 2', color: '#eb4f4f' }
                            }
                        ]
                    }, {
                            type: 'column',
                            width: 33,
                            content: [
                                {
                                    type: 'react-component',
                                    component: 'HistogramAlert',
                                    title: 'Alert3',
                                    props: { title: 'Alert 3', color: '#eb9650' }
                                }, {
                                    type: 'react-component',
                                    component: 'CloudAlert',
                                    title: 'Alert4',
                                    props: { title: 'Alert 4', color: '#4febeb' }

                                }
                            ]

                        },
                        {
                            type: 'column',
                            width: 33,
                            content: [

                                {
                                    type: 'react-component',
                                    component: 'CardAlert',
                                    title: 'Alert4',
                                    props: { title: 'Alert 4', color: '#4febeb' }

                                }
                            ]
                        }

                    ]
                }
            ]
        }, {
                type: 'row',
                content: [{
                    type: 'column',
                    width: 50,
                    content: [{
                        type: 'stack',
                        height: 50,
                        content: [{
                            type: 'react-component',
                            component: 'Table',
                            title: 'Table'
                        }, {
                                type: 'react-component',
                                component: 'Map',
                                title: 'Map'
                            }, {
                                type: 'react-component',
                                component: 'Cloud',
                                title: 'Cloud'
                            }, {
                                type: 'react-component',
                                component: 'Histogram',
                                title: 'Histogram'
                            }]
                    }]
                }]
            }]
    }]
};
// eslint-disable-next-line react/no-multi-comp

export default class MainLayout extends Component {

    componentDidMount() {
        const temp = React.createClass({
            render: () => {
                return (
test component 3
)
            }
        });
        const queryWidget = React.createClass({
            render: () => {
                return (<QueryWindow/>)
            }
        });

        var alertCtr = 1;

        const alertWall = React.createClass({
            componentDidMount: function () {

                const color = this.props.color || '#000000';
                this.props.glContainer.tab.titleElement.prevObject.css('background-color', this.props.color);
                this.props.glContainer.tab.setTitle(this.props.title || 'No Title');
                // eslint-disable-next-line react/prop-types
                this.props.glContainer.on('tab', (tab) => {
                    tab.titleElement.prevObject.css('background-color', color);
                });
            },
            render: function () {
                return (
Alert Window {alertCtr++}
)
            }
        })

        const layout = new GoldenLayout(layoutConfig);
        layout.registerComponent('Search Criteria', queryWidget);

        layout.registerComponent('MapAlert', MapAlert);
        layout.registerComponent('TableAlert', TableAlert);
        layout.registerComponent('HistogramAlert', HistogramAlert);
        layout.registerComponent('CloudAlert', CloudAlert);
        layout.registerComponent('Alert Wall', alertWall);
        layout.registerComponent('Histogram', temp);
        layout.registerComponent('CardAlert', CardAlert);

        layout.registerComponent('Cloud', temp);
        layout.registerComponent('Map', MapWindow);
        layout.registerComponent('Table', TableWindow);

        layout.on('stackCreated', function (stack) {
            /*
             * Listening for activeContentItemChanged. This happens initially
             * when the stack is created and everytime the user clicks a tab
             */
            stack.on('activeContentItemChanged', function (contentItem) {
                stack.header.controlsContainer.children('.alertCommands').remove();
                const buttonInstance = (
                    <ButtonToolbar>
                        <Dropdown id="dropdown-custom-1"  >
                            <CustomToggle bsRole="toggle">
                                <Glyphicon glyph="cog" />
                            </CustomToggle>
                            <DropdownMenu className="">
                                <MenuItem eventKey="1">Load in Search Window</MenuItem>
                                <MenuItem eventKey="1">Change perspective ></MenuItem>
                                <MenuItem divider />
                                <MenuItem eventKey="1">Color ></MenuItem>
                                <MenuItem eventKey="1">Audio ></MenuItem>
                                <MenuItem eventKey="1">[x]Active</MenuItem>
                                <MenuItem eventKey="1">[x]Intelligent downsample</MenuItem>
                                <MenuItem divider />
                                <MenuItem eventKey="1">Export to CSV</MenuItem>
                                <MenuItem eventKey="1">Export to PNG</MenuItem>
                                <MenuItem divider />
                                <MenuItem eventKey="1">Save this configuration</MenuItem>
                                <MenuItem eventKey="1">Share this configuration</MenuItem>
                            </DropdownMenu>
                        </Dropdown>
                    </ButtonToolbar>

                );

                // interact with the contentItem
                if (isAlert(contentItem.config.component)) {

                    stack.header.controlsContainer.prepend('

');
                    stack.header.controlsContainer.prepend('

');
                    ReactDOM.render(buttonInstance, stack.header.controlsContainer.children('.cogSettings')[0]);
                }
            });

            /*
             * Accessing the container and updating its state
             */
            //stack.getActiveContentItem().container.extendState({ color: '#faa' });
        });

        layout.init()
    }

    render() {
        return (

)
    }
}
<commit_msg>#32 - Added The Settings to Search Window<commit_after>import React, {Component} from 'react';
import QueryWindow from '../SearchWindow/querybuilder';
import MapWindow from '../MapWindow/mapView';
import TableWindow from '../TableWindow/tableView';
import {ButtonToolbar, Dropdown, Glyphicon, MenuItem} from 'react-bootstrap';
import ReactDOM from 'react-dom';

import MapAlert from '../Alerts/MapAlert';
import TableAlert from '../Alerts/TableAlert';
import HistogramAlert from '../Alerts/HistogramAlert';
import CloudAlert from '../Alerts/CloudAlert';
import CardAlert from '../Alerts/CardAlert';

function isAlert(t) {
    return t == 'MapAlert' || t == 'TableAlert' || t == 'HistogramAlert' || t == 'CloudAlert';
}
const DropdownMenu = Dropdown.Menu;

/* eslint-disable react/no-multi-comp*/
/* eslint-disable react/prop-types*/
const GoldenLayout = require('golden-layout');

class CustomToggle extends React.Component {
    constructor(props, context) {
        super(props, context);

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

    handleClick(e) {
        e.preventDefault();

        this.props.onClick(e);
    }

    render() {
        return (
            <Glyphicon glyph="cog" onClick={this.handleClick} />
        );
    }
}

//configuration for the layout
const layoutConfig = {
    settings: {
        hasHeaders: true
    },
    dimensions: {
        borderWidth: 2,
        headerHeight: 25
    },
    content: [{
        type: 'row',
        content: [{
            type: 'column',
            width: 50,
            content: [
                {
                    type: 'row',
                    height: 50,
                    content: [{
                        type: 'react-component',
                        component: 'Search Criteria',
                        title: 'Search Criteria'
                    }]
                },
                {
                    type: 'row',
                    height: 50,
                    content: [{
                        type: 'column',
                        width: 33,
                        content: [
                            {
                                type: 'react-component',
                                component: 'MapAlert',
                                title: 'Alert1',
                                props: { title: 'Alert 1', color: '#53eb53' }
                            }, {
                                type: 'react-component',
                                component: 'TableAlert',
                                title: 'Alert2',
                                props: { title: 'Alert 2', color: '#eb4f4f' }
                            }
                        ]
                    }, {
                            type: 'column',
                            width: 33,
                            content: [
                                {
                                    type: 'react-component',
                                    component: 'HistogramAlert',
                                    title: 'Alert3',
                                    props: { title: 'Alert 3', color: '#eb9650' }
                                }, {
                                    type: 'react-component',
                                    component: 'CloudAlert',
                                    title: 'Alert4',
                                    props: { title: 'Alert 4', color: '#4febeb' }

                                }
                            ]

                        },
                        {
                            type: 'column',
                            width: 33,
                            content: [

                                {
                                    type: 'react-component',
                                    component: 'CardAlert',
                                    title: 'Alert4',
                                    props: { title: 'Alert 4', color: '#4febeb' }

                                }
                            ]
                        }

                    ]
                }
            ]
        }, {
                type: 'row',
                content: [{
                    type: 'column',
                    width: 50,
                    content: [{
                        type: 'stack',
                        height: 50,
                        content: [{
                            type: 'react-component',
                            component: 'Table',
                            title: 'Table'
                        }, {
                                type: 'react-component',
                                component: 'Map',
                                title: 'Map'
                            }, {
                                type: 'react-component',
                                component: 'Cloud',
                                title: 'Cloud'
                            }, {
                                type: 'react-component',
                                component: 'Histogram',
                                title: 'Histogram'
                            }]
                    }]
                }]
            }]
    }]
};
// eslint-disable-next-line react/no-multi-comp

export default class MainLayout extends Component {

    componentDidMount() {
        const temp = React.createClass({
            render: () => {
                return (
test component 3
)
            }
        });
        const queryWidget = React.createClass({
            render: () => {
                return (<QueryWindow/>)
            }
        });

        var alertCtr = 1;

        const alertWall = React.createClass({
            componentDidMount: function () {

                const color = this.props.color || '#000000';
                this.props.glContainer.tab.titleElement.prevObject.css('background-color', this.props.color);
                this.props.glContainer.tab.setTitle(this.props.title || 'No Title');
                // eslint-disable-next-line react/prop-types
                this.props.glContainer.on('tab', (tab) => {
                    tab.titleElement.prevObject.css('background-color', color);
                });
            },
            render: function () {
                return (
Alert Window {alertCtr++}
)
            }
        })

        const layout = new GoldenLayout(layoutConfig);
        layout.registerComponent('Search Criteria', queryWidget);

        layout.registerComponent('MapAlert', MapAlert);
        layout.registerComponent('TableAlert', TableAlert);
        layout.registerComponent('HistogramAlert', HistogramAlert);
        layout.registerComponent('CloudAlert', CloudAlert);
        layout.registerComponent('Alert Wall', alertWall);
        layout.registerComponent('Histogram', temp);
        layout.registerComponent('CardAlert', CardAlert);

        layout.registerComponent('Cloud', temp);
        layout.registerComponent('Map', MapWindow);
        layout.registerComponent('Table', TableWindow);

        layout.on('stackCreated', function (stack) {
            /*
             * Listening for activeContentItemChanged. This happens initially
             * when the stack is created and everytime the user clicks a tab
             */
            stack.on('activeContentItemChanged', function (contentItem) {
                stack.header.controlsContainer.children('.alertCommands').remove();
                const buttonInstance = (
                    <ButtonToolbar>
                        <Dropdown id="dropdown-custom-1"  >
                            <CustomToggle bsRole="toggle">
                                <Glyphicon glyph="cog" />
                            </CustomToggle>
                            <DropdownMenu className="">
                                <MenuItem eventKey="1">Load in Search Window</MenuItem>
                                <MenuItem eventKey="1">Change perspective ></MenuItem>
                                <MenuItem divider />
                                <MenuItem eventKey="1">Color ></MenuItem>
                                <MenuItem eventKey="1">Audio ></MenuItem>
                                <MenuItem eventKey="1">[x]Active</MenuItem>
                                <MenuItem eventKey="1">[x]Intelligent downsample</MenuItem>
                                <MenuItem divider />
                                <MenuItem eventKey="1">Export to CSV</MenuItem>
                                <MenuItem eventKey="1">Export to PNG</MenuItem>
                                <MenuItem divider />
                                <MenuItem eventKey="1">Save this configuration</MenuItem>
                                <MenuItem eventKey="1">Share this configuration</MenuItem>
                            </DropdownMenu>
                        </Dropdown>
                    </ButtonToolbar>

                );

                const searchWindow = (
                    <ButtonToolbar>
                        <Dropdown id="dropdown-custom-1"  >
                            <CustomToggle bsRole="toggle">
                                <Glyphicon glyph="cog" />
                            </CustomToggle>
                            <DropdownMenu className="">
                                <MenuItem eventKey="1">Save Session</MenuItem>
                                <MenuItem eventKey="1">Restore Session </MenuItem>
                                <MenuItem divider />
                                <MenuItem eventKey="1">Disable Updates</MenuItem>
                            </DropdownMenu>
                        </Dropdown>
                    </ButtonToolbar>

                );

                // interact with the contentItem
                if (isAlert(contentItem.config.component)) {

                    stack.header.controlsContainer.prepend('

');
                    stack.header.controlsContainer.prepend('

');
                    ReactDOM.render(buttonInstance, stack.header.controlsContainer.children('.cogSettings')[0]);
                }
                if (contentItem.config.component == 'Search Criteria') {

                    stack.header.controlsContainer.prepend('

');
                    stack.header.controlsContainer.prepend('

');
                    ReactDOM.render(searchWindow, stack.header.controlsContainer.children('.cogSettings')[0]);
                }
            });

            /*
             * Accessing the container and updating its state
             */
            //stack.getActiveContentItem().container.extendState({ color: '#faa' });
        });

        layout.init()
    }

    render() {
        return (

)
    }
}
<|endoftext|>