<commit_before>import React from 'react';
import ReactDOM from 'react-dom';
import { observer as globalObserver } from 'binary-common-utils/lib/observer';
import {
    getTokenList,
    removeAllTokens,
    get as getStorage,
    set as setStorage,
    getToken,
} from 'binary-common-utils/lib/storageManager';
import 'jquery-ui/ui/widgets/dialog';
import _Blockly from './blockly';
import { translate } from '../../common/i18n';
import Save from './Dialogs/Save';
import Limits from './Dialogs/Limits';
import Chart from './Dialogs/Chart';
import TradingView from './Dialogs/TradingView';
import { getLanguage } from '../../common/lang';
import { roundBalance, isVirtual } from '../common/tools';
import { symbolPromise } from './shared';
import logHandler from './logger';
import Tour from './tour';
import OfficialVersionWarning from './react-components/OfficialVersionWarning';
import LogTable from './LogTable';
import TradeInfoPanel from './TradeInfoPanel';
import { logoutAllTokens, getOAuthURL, generateLiveApiInstance } from '../../common/appId';
import { updateConfigCurrencies } from '../common/const';

let realityCheckTimeout;

const api = generateLiveApiInstance();

api.events.on('balance', response => {
    const { balance: { balance: b, currency } } = response;

    const balance = roundBalance({ currency, balance: b });
    $('.topMenuBalance').text(`${balance} ${currency}`);
});

const addBalanceForToken = token => {
    api.authorize(token).then(() => {
        api.send({ forget_all: 'balance' }).then(() => {
            api.subscribeToBalance();
        });
    });
};

const chart = new Chart();
const tradingView = new TradingView();

const showRealityCheck = () => {
    $('.blocker').show();
    $('.reality-check').show();
};

const hideRealityCheck = () => {
    $('#rc-err').hide();
    $('.blocker').hide();
    $('.reality-check').hide();
};

const stopRealityCheck = () => {
    clearInterval(realityCheckTimeout);
    realityCheckTimeout = null;
};

const realityCheckInterval = () => {
    realityCheckTimeout = setInterval(() => {
        const now = parseInt(new Date().getTime() / 1000);
        const checkTime = +getStorage('realityCheckTime');
        if (checkTime && now >= checkTime) {
            showRealityCheck();
            stopRealityCheck();
        }
    }, 1000);
};

const startRealityCheck = (time, token) => {
    stopRealityCheck();
    if (time) {
        const start = parseInt(new Date().getTime() / 1000) + time * 60;
        setStorage('realityCheckTime', start);
        realityCheckInterval();
    } else {
        const tokenObj = getToken(token);
        if (tokenObj.hasRealityCheck) {
            const checkTime = +getStorage('realityCheckTime');
            if (!checkTime) {
                showRealityCheck();
            } else {
                realityCheckInterval();
            }
        }
    }
};

const clearRealityCheck = () => {
    setStorage('realityCheckTime', null);
    stopRealityCheck();
};

const resetRealityCheck = token => {
    clearRealityCheck();
    startRealityCheck(null, token);
};

const limits = new Limits();
const saveDialog = new Save();
// const chart = new Chart();

const getActiveToken = (tokenList, activeToken) => {
    const activeTokenObject = tokenList.filter(tokenObject => tokenObject.token === activeToken);
    return activeTokenObject.length ? activeTokenObject[0] : tokenList[0];
};

const updateTokenList = () => {
    const tokenList = getTokenList();
    const loginButton = $('#login');
    const accountList = $('#account-list');
    if (tokenList.length === 0) {
        loginButton.show();
        accountList.hide();
        $('.account-id')
            .removeAttr('value')
            .text('');
        $('.account-type').text('');
        $('.login-id-list')
            .children()
            .remove();
    } else {
        loginButton.hide();
        accountList.show();

        const activeToken = getActiveToken(tokenList, getStorage('activeToken'));
        addBalanceForToken(activeToken.token);
        if (!('loginInfo' in activeToken)) {
            removeAllTokens();
            updateTokenList();
        }
        tokenList.forEach(tokenInfo => {
            const prefix = isVirtual(tokenInfo) ? 'Virtual Account' : `${tokenInfo.loginInfo.currency} Account`;

            if (tokenInfo === activeToken) {
                $('.account-id')
                    .attr('value', `${tokenInfo.token}`)
                    .text(`${tokenInfo.accountName}`);
                $('.account-type').text(`${prefix}`);
            } else {
                $('.login-id-list').append(
                    `
${prefix}
${
                        tokenInfo.accountName
                    }

`
                );
            }
        });
    }
};

const toggleButtonHide = () => {
    if (getTokenList().length) {
        $('#runButton').show();
        $('#showSummary').show();
        $('#logButton').show();
    } else {
        $('#runButton').hide();
        $('#showSummary').hide();
        $('#logButton').hide();
    }
};

export default class View {
    constructor() {
        logHandler();
        this.initPromise = new Promise(resolve => {
            updateConfigCurrencies().then(() => {
                symbolPromise.then(() => {
                    updateTokenList();
                    this.blockly = new _Blockly();
                    this.blockly.initPromise.then(() => {
                        this.setElementActions();
                        initRealityCheck();
                        toggleButtonHide();
                        renderReactComponents();
                        resolve();
                    });
                });
            });
        });
    }

    setFileBrowser() {
        const readFile = (f, dropEvent = {}) => {
            const reader = new FileReader();
            reader.onload = e => this.blockly.load(e.target.result, dropEvent);
            reader.readAsText(f);
        };

        const handleFileSelect = e => {
            let files;
            let dropEvent;
            if (e.type === 'drop') {
                e.stopPropagation();
                e.preventDefault();
                ({ files } = e.dataTransfer);
                dropEvent = e;
            } else {
                ({ files } = e.target);
            }
            files = Array.from(files);
            files.forEach(file => {
                if (file.type.match('text/xml')) {
                    readFile(file, dropEvent);
                } else {
                    globalObserver.emit('ui.log.info', `${translate('File is not supported:')} ${file.name}`);
                }
            });
        };

        const handleDragOver = e => {
            e.stopPropagation();
            e.preventDefault();
            e.dataTransfer.dropEffect = 'copy'; // eslint-disable-line no-param-reassign
        };

        const dropZone = document.body;

        dropZone.addEventListener('dragover', handleDragOver, false);
        dropZone.addEventListener('drop', handleFileSelect, false);

        $('#files').on('change', handleFileSelect);

        $('#open_btn')
            .on('click', () => {
                $.FileDialog({
                    // eslint-disable-line new-cap
                    accept       : '.xml',
                    cancelButton : 'Close',
                    dragMessage  : 'Drop files here',
                    dropheight   : 400,
                    errorMessage : 'An error occured while loading file',
                    multiple     : false,
                    okButton     : 'OK',
                    readAs       : 'DataURL',
                    removeMessage: 'Remove file',
                    title        : 'Load file',
                });
            })
            .on('files.bs.filedialog', ev => {
                handleFileSelect(ev.files);
            })
            .on('cancel.bs.filedialog', ev => {
                handleFileSelect(ev);
            });
    }
    setElementActions() {
        this.setFileBrowser();
        this.addBindings();
        this.addEventHandlers();
    }
    addBindings() {
        const stop = e => {
            if (e) {
                e.preventDefault();
            }
            stopRealityCheck();
            this.stop();
        };

        const logout = () => {
            logoutAllTokens().then(() => {
                updateTokenList();
                globalObserver.emit('ui.log.info', translate('Logged you out!'));
                clearRealityCheck();
            });
        };

        $('.panelExitButton').click(function onClick() {
            $(this)
                .parent()
                .hide();
        });

        $('.draggable-dialog')
            .hide()
            .dialog({
                resizable: false,
                autoOpen : false,
                width    : Math.min(document.body.offsetWidth, 700),
                height   : Math.min(document.body.offsetHeight, 600),
                closeText: '',
                classes  : { 'ui-dialog-titlebar-close': 'icon-close' },
            });

        $('#save-xml').click(() => saveDialog.save().then(arg => this.blockly.save(arg)));

        $('#undo').click(() => {
            this.blockly.undo();
        });

        $('#redo').click(() => {
            this.blockly.redo();
        });

        $('#zoomIn').click(() => {
            this.blockly.zoomOnPlusMinus(true);
        });

        $('#zoomOut').click(() => {
            this.blockly.zoomOnPlusMinus(false);
        });

        $('#rearrange').click(() => {
            this.blockly.cleanUp();
        });

        $('#chartButton').click(() => {
            chart.open();
        });

        $('#tradingViewButton').click(() => {
            tradingView.open();
        });
        const exportContent = {};
        exportContent.summaryPanel = () => {
            globalObserver.emit('summary.export');
        };
        exportContent.logPanel = () => {
            globalObserver.emit('log.export');
        };
        const addExportButtonToPanel = panelId => {
            const buttonHtml =
                '<button class="icon-save" style="position:absolute;top:50%;margin:-10px 0 0 0;right:2em;padding:0.2em"></button>';
            const $button = $(buttonHtml);
            const panelSelector = `[aria-describedby="${panelId}"]`;
            if (!$(`${panelSelector} .icon-save`).length) {
                $button.insertBefore(`${panelSelector} .icon-close`);
                $(`${panelSelector} .icon-close`).blur();
                $($(`${panelSelector} .icon-save`)).click(() => {
                    exportContent[panelId]();
                });
            }
        };
        const showSummary = () => {
            $('#summaryPanel').dialog('open');
            addExportButtonToPanel('summaryPanel');
        };
        const showLog = () => {
            $('#logPanel').dialog('open');
            addExportButtonToPanel('logPanel');
        };

        $('#logButton').click(showLog);

        $('#showSummary').click(showSummary);

        $('#loadXml').click(() => {
            $('#files').click();
        });

        $('#logout, #logout-reality-check').click(() => {
            logout();
            hideRealityCheck();
        });

        const submitRealityCheck = () => {
            const time = parseInt($('#realityDuration').val());
            if (time >= 10 && time <= 60) {
                hideRealityCheck();
                startRealityCheck(time);
            } else {
                $('#rc-err').show();
            }
        };

        $('#continue-trading').click(() => {
            submitRealityCheck();
        });

        $('#realityDuration').keypress(e => {
            const char = String.fromCharCode(e.which);
            if (e.keyCode === 13) {
                submitRealityCheck();
            }
            /* Unicode check is for firefox because it
             * trigger this event when backspace, arrow keys are pressed
             * in chrome it is not triggered
             */
            const unicodeStrings = /[\u0008|\u0000]/;
            if (unicodeStrings.test(char)) return;

            if (!/([0-9])/.test(char)) {
                e.preventDefault();
            }
        });

        const startBot = limitations => {
            $('#stopButton').show();
            $('#runButton').hide();
            showSummary();
            this.blockly.run(limitations);
        };

        $('#runButton').click(() => {
            const token = $('.account-id')
                .first()
                .attr('value');
            const tokenObj = getToken(token);
            if (tokenObj && tokenObj.hasTradeLimitation) {
                limits.getLimits().then(startBot);
            } else {
                startBot();
            }
        });

        $('#stopButton')
            .click(e => stop(e))
            .hide();

        $('#resetButton').click(() => {
            this.blockly.resetWorkspace();
            setTimeout(() => this.blockly.cleanUp(), 0);
        });

        $('.login-id-list').on('click', 'a', e => {
            setStorage('activeToken', $(e.currentTarget).attr('value'));
            location.reload();
        });

        $('#login')
            .bind('click.login', () => {
                document.location = getOAuthURL();
            })
            .text('Log in');

        $('#statement-reality-check').click(() => {
            document.location = `https://www.binary.com/${getLanguage()}/user/statementws.html#no-reality-check`;
        });
        $(document).keydown(e => {
            if (e.which === 189) {
                // Ctrl + -
                if (e.ctrlKey) {
                    this.blockly.zoomOnPlusMinus(false);
                    e.preventDefault();
                }
            } else if (e.which === 187) {
                // Ctrl + +
                if (e.ctrlKey) {
                    this.blockly.zoomOnPlusMinus(true);
                    e.preventDefault();
                }
            }
        });
    }
    stop() {
        this.blockly.stop();
    }
    addEventHandlers() {
        globalObserver.register('Error', error => {
            if (error.error && error.error.error.code === 'InvalidToken') {
                removeAllTokens();
                updateTokenList();
                this.stop();
            }
        });

        globalObserver.register('bot.info', info => {
            if ('profit' in info) {
                const token = $('.account-id')
                    .first()
                    .attr('value');
                const user = getToken(token);
                globalObserver.emit('log.revenue', {
                    user,
                    profit  : info.profit,
                    contract: info.contract,
                });
            }
        });
    }
}

function initRealityCheck() {
    startRealityCheck(
        null,
        $('.account-id')
            .first()
            .attr('value')
    );
}
function renderReactComponents() {
    ReactDOM.render(<Tour />, $('#tour')[0]);
    ReactDOM.render(
        <OfficialVersionWarning
            show={
                !(
                    typeof window.location !== 'undefined' &&
                    window.location.host === 'bot.binary.com' &&
                    window.location.pathname === '/bot.html'
                )
            }
        />,
        $('#footer')[0]
    );
    ReactDOM.render(<TradeInfoPanel />, $('#summaryPanel')[0]);
    ReactDOM.render(<LogTable />, $('#logTable')[0]);
}
<commit_msg>Clear tokens on logout<commit_after>import React from 'react';
import ReactDOM from 'react-dom';
import { observer as globalObserver } from 'binary-common-utils/lib/observer';
import {
    getTokenList,
    removeAllTokens,
    get as getStorage,
    set as setStorage,
    getToken,
} from 'binary-common-utils/lib/storageManager';
import 'jquery-ui/ui/widgets/dialog';
import _Blockly from './blockly';
import { translate } from '../../common/i18n';
import Save from './Dialogs/Save';
import Limits from './Dialogs/Limits';
import Chart from './Dialogs/Chart';
import TradingView from './Dialogs/TradingView';
import { getLanguage } from '../../common/lang';
import { roundBalance, isVirtual } from '../common/tools';
import { symbolPromise } from './shared';
import logHandler from './logger';
import Tour from './tour';
import OfficialVersionWarning from './react-components/OfficialVersionWarning';
import LogTable from './LogTable';
import TradeInfoPanel from './TradeInfoPanel';
import { logoutAllTokens, getOAuthURL, generateLiveApiInstance } from '../../common/appId';
import { updateConfigCurrencies } from '../common/const';

let realityCheckTimeout;

const api = generateLiveApiInstance();

api.events.on('balance', response => {
    const { balance: { balance: b, currency } } = response;

    const balance = roundBalance({ currency, balance: b });
    $('.topMenuBalance').text(`${balance} ${currency}`);
});

const addBalanceForToken = token => {
    api.authorize(token).then(() => {
        api.send({ forget_all: 'balance' }).then(() => {
            api.subscribeToBalance();
        });
    });
};

const chart = new Chart();
const tradingView = new TradingView();

const showRealityCheck = () => {
    $('.blocker').show();
    $('.reality-check').show();
};

const hideRealityCheck = () => {
    $('#rc-err').hide();
    $('.blocker').hide();
    $('.reality-check').hide();
};

const stopRealityCheck = () => {
    clearInterval(realityCheckTimeout);
    realityCheckTimeout = null;
};

const realityCheckInterval = () => {
    realityCheckTimeout = setInterval(() => {
        const now = parseInt(new Date().getTime() / 1000);
        const checkTime = +getStorage('realityCheckTime');
        if (checkTime && now >= checkTime) {
            showRealityCheck();
            stopRealityCheck();
        }
    }, 1000);
};

const startRealityCheck = (time, token) => {
    stopRealityCheck();
    if (time) {
        const start = parseInt(new Date().getTime() / 1000) + time * 60;
        setStorage('realityCheckTime', start);
        realityCheckInterval();
    } else {
        const tokenObj = getToken(token);
        if (tokenObj.hasRealityCheck) {
            const checkTime = +getStorage('realityCheckTime');
            if (!checkTime) {
                showRealityCheck();
            } else {
                realityCheckInterval();
            }
        }
    }
};

const clearRealityCheck = () => {
    setStorage('realityCheckTime', null);
    stopRealityCheck();
};

const resetRealityCheck = token => {
    clearRealityCheck();
    startRealityCheck(null, token);
};

const limits = new Limits();
const saveDialog = new Save();
// const chart = new Chart();

const getActiveToken = (tokenList, activeToken) => {
    const activeTokenObject = tokenList.filter(tokenObject => tokenObject.token === activeToken);
    return activeTokenObject.length ? activeTokenObject[0] : tokenList[0];
};

const updateTokenList = () => {
    const tokenList = getTokenList();
    const loginButton = $('#login');
    const accountList = $('#account-list');
    if (tokenList.length === 0) {
        loginButton.show();
        accountList.hide();
        $('.account-id')
            .removeAttr('value')
            .text('');
        $('.account-type').text('');
        $('.login-id-list')
            .children()
            .remove();
    } else {
        loginButton.hide();
        accountList.show();

        const activeToken = getActiveToken(tokenList, getStorage('activeToken'));
        addBalanceForToken(activeToken.token);
        if (!('loginInfo' in activeToken)) {
            removeAllTokens();
            updateTokenList();
        }
        tokenList.forEach(tokenInfo => {
            const prefix = isVirtual(tokenInfo) ? 'Virtual Account' : `${tokenInfo.loginInfo.currency} Account`;

            if (tokenInfo === activeToken) {
                $('.account-id')
                    .attr('value', `${tokenInfo.token}`)
                    .text(`${tokenInfo.accountName}`);
                $('.account-type').text(`${prefix}`);
            } else {
                $('.login-id-list').append(
                    `
${prefix}
${
                        tokenInfo.accountName
                    }

`
                );
            }
        });
    }
};

const toggleButtonHide = () => {
    if (getTokenList().length) {
        $('#runButton').show();
        $('#showSummary').show();
        $('#logButton').show();
    } else {
        $('#runButton').hide();
        $('#showSummary').hide();
        $('#logButton').hide();
    }
};

export default class View {
    constructor() {
        logHandler();
        this.initPromise = new Promise(resolve => {
            updateConfigCurrencies().then(() => {
                symbolPromise.then(() => {
                    updateTokenList();
                    this.blockly = new _Blockly();
                    this.blockly.initPromise.then(() => {
                        this.setElementActions();
                        initRealityCheck();
                        toggleButtonHide();
                        renderReactComponents();
                        resolve();
                    });
                });
            });
        });
    }

    setFileBrowser() {
        const readFile = (f, dropEvent = {}) => {
            const reader = new FileReader();
            reader.onload = e => this.blockly.load(e.target.result, dropEvent);
            reader.readAsText(f);
        };

        const handleFileSelect = e => {
            let files;
            let dropEvent;
            if (e.type === 'drop') {
                e.stopPropagation();
                e.preventDefault();
                ({ files } = e.dataTransfer);
                dropEvent = e;
            } else {
                ({ files } = e.target);
            }
            files = Array.from(files);
            files.forEach(file => {
                if (file.type.match('text/xml')) {
                    readFile(file, dropEvent);
                } else {
                    globalObserver.emit('ui.log.info', `${translate('File is not supported:')} ${file.name}`);
                }
            });
        };

        const handleDragOver = e => {
            e.stopPropagation();
            e.preventDefault();
            e.dataTransfer.dropEffect = 'copy'; // eslint-disable-line no-param-reassign
        };

        const dropZone = document.body;

        dropZone.addEventListener('dragover', handleDragOver, false);
        dropZone.addEventListener('drop', handleFileSelect, false);

        $('#files').on('change', handleFileSelect);

        $('#open_btn')
            .on('click', () => {
                $.FileDialog({
                    // eslint-disable-line new-cap
                    accept       : '.xml',
                    cancelButton : 'Close',
                    dragMessage  : 'Drop files here',
                    dropheight   : 400,
                    errorMessage : 'An error occured while loading file',
                    multiple     : false,
                    okButton     : 'OK',
                    readAs       : 'DataURL',
                    removeMessage: 'Remove file',
                    title        : 'Load file',
                });
            })
            .on('files.bs.filedialog', ev => {
                handleFileSelect(ev.files);
            })
            .on('cancel.bs.filedialog', ev => {
                handleFileSelect(ev);
            });
    }
    setElementActions() {
        this.setFileBrowser();
        this.addBindings();
        this.addEventHandlers();
    }
    addBindings() {
        const stop = e => {
            if (e) {
                e.preventDefault();
            }
            stopRealityCheck();
            this.stop();
        };

        const logout = () => {
            logoutAllTokens().then(() => {
                updateTokenList();
                globalObserver.emit('ui.log.info', translate('Logged you out!'));
                clearRealityCheck();
                clearActiveTokens();
            });
        };

        const clearActiveTokens = () => {
            setStorage('activeToken', '');
        };

        $('.panelExitButton').click(function onClick() {
            $(this)
                .parent()
                .hide();
        });

        $('.draggable-dialog')
            .hide()
            .dialog({
                resizable: false,
                autoOpen : false,
                width    : Math.min(document.body.offsetWidth, 700),
                height   : Math.min(document.body.offsetHeight, 600),
                closeText: '',
                classes  : { 'ui-dialog-titlebar-close': 'icon-close' },
            });

        $('#save-xml').click(() => saveDialog.save().then(arg => this.blockly.save(arg)));

        $('#undo').click(() => {
            this.blockly.undo();
        });

        $('#redo').click(() => {
            this.blockly.redo();
        });

        $('#zoomIn').click(() => {
            this.blockly.zoomOnPlusMinus(true);
        });

        $('#zoomOut').click(() => {
            this.blockly.zoomOnPlusMinus(false);
        });

        $('#rearrange').click(() => {
            this.blockly.cleanUp();
        });

        $('#chartButton').click(() => {
            chart.open();
        });

        $('#tradingViewButton').click(() => {
            tradingView.open();
        });
        const exportContent = {};
        exportContent.summaryPanel = () => {
            globalObserver.emit('summary.export');
        };
        exportContent.logPanel = () => {
            globalObserver.emit('log.export');
        };
        const addExportButtonToPanel = panelId => {
            const buttonHtml =
                '<button class="icon-save" style="position:absolute;top:50%;margin:-10px 0 0 0;right:2em;padding:0.2em"></button>';
            const $button = $(buttonHtml);
            const panelSelector = `[aria-describedby="${panelId}"]`;
            if (!$(`${panelSelector} .icon-save`).length) {
                $button.insertBefore(`${panelSelector} .icon-close`);
                $(`${panelSelector} .icon-close`).blur();
                $($(`${panelSelector} .icon-save`)).click(() => {
                    exportContent[panelId]();
                });
            }
        };
        const showSummary = () => {
            $('#summaryPanel').dialog('open');
            addExportButtonToPanel('summaryPanel');
        };
        const showLog = () => {
            $('#logPanel').dialog('open');
            addExportButtonToPanel('logPanel');
        };

        $('#logButton').click(showLog);

        $('#showSummary').click(showSummary);

        $('#loadXml').click(() => {
            $('#files').click();
        });

        $('#logout, #logout-reality-check').click(() => {
            logout();
            hideRealityCheck();
        });

        const submitRealityCheck = () => {
            const time = parseInt($('#realityDuration').val());
            if (time >= 10 && time <= 60) {
                hideRealityCheck();
                startRealityCheck(time);
            } else {
                $('#rc-err').show();
            }
        };

        $('#continue-trading').click(() => {
            submitRealityCheck();
        });

        $('#realityDuration').keypress(e => {
            const char = String.fromCharCode(e.which);
            if (e.keyCode === 13) {
                submitRealityCheck();
            }
            /* Unicode check is for firefox because it
             * trigger this event when backspace, arrow keys are pressed
             * in chrome it is not triggered
             */
            const unicodeStrings = /[\u0008|\u0000]/;
            if (unicodeStrings.test(char)) return;

            if (!/([0-9])/.test(char)) {
                e.preventDefault();
            }
        });

        const startBot = limitations => {
            $('#stopButton').show();
            $('#runButton').hide();
            showSummary();
            this.blockly.run(limitations);
        };

        $('#runButton').click(() => {
            const token = $('.account-id')
                .first()
                .attr('value');
            const tokenObj = getToken(token);
            if (tokenObj && tokenObj.hasTradeLimitation) {
                limits.getLimits().then(startBot);
            } else {
                startBot();
            }
        });

        $('#stopButton')
            .click(e => stop(e))
            .hide();

        $('#resetButton').click(() => {
            this.blockly.resetWorkspace();
            setTimeout(() => this.blockly.cleanUp(), 0);
        });

        $('.login-id-list').on('click', 'a', e => {
            setStorage('activeToken', $(e.currentTarget).attr('value'));
            location.reload();
        });

        $('#login')
            .bind('click.login', () => {
                document.location = getOAuthURL();
            })
            .text('Log in');

        $('#statement-reality-check').click(() => {
            document.location = `https://www.binary.com/${getLanguage()}/user/statementws.html#no-reality-check`;
        });
        $(document).keydown(e => {
            if (e.which === 189) {
                // Ctrl + -
                if (e.ctrlKey) {
                    this.blockly.zoomOnPlusMinus(false);
                    e.preventDefault();
                }
            } else if (e.which === 187) {
                // Ctrl + +
                if (e.ctrlKey) {
                    this.blockly.zoomOnPlusMinus(true);
                    e.preventDefault();
                }
            }
        });
    }
    stop() {
        this.blockly.stop();
    }
    addEventHandlers() {
        globalObserver.register('Error', error => {
            if (error.error && error.error.error.code === 'InvalidToken') {
                removeAllTokens();
                updateTokenList();
                this.stop();
            }
        });

        globalObserver.register('bot.info', info => {
            if ('profit' in info) {
                const token = $('.account-id')
                    .first()
                    .attr('value');
                const user = getToken(token);
                globalObserver.emit('log.revenue', {
                    user,
                    profit  : info.profit,
                    contract: info.contract,
                });
            }
        });
    }
}

function initRealityCheck() {
    startRealityCheck(
        null,
        $('.account-id')
            .first()
            .attr('value')
    );
}
function renderReactComponents() {
    ReactDOM.render(<Tour />, $('#tour')[0]);
    ReactDOM.render(
        <OfficialVersionWarning
            show={
                !(
                    typeof window.location !== 'undefined' &&
                    window.location.host === 'bot.binary.com' &&
                    window.location.pathname === '/bot.html'
                )
            }
        />,
        $('#footer')[0]
    );
    ReactDOM.render(<TradeInfoPanel />, $('#summaryPanel')[0]);
    ReactDOM.render(<LogTable />, $('#logTable')[0]);
}
<|endoftext|>


<commit_before>/**
 * --------------------------------------------------------------------------
 * Figuration (v3.0.0): modal.js
 * Licensed under MIT (https://github.com/cast-org/figuration/blob/master/LICENSE)
 * --------------------------------------------------------------------------
 */

(function($) {
    'use strict';

    var CFW_Widget_Modal = function(element, options) {
        this.$body = $(document.body);
        this.$element = $(element);
        this.$target = null;
        this.$dialog = null;
        this.$backdrop = null;
        this.$focusLast = null;
        this.isShown = null;
        this.scrollbarWidth = 0;
        this.scrollbarSide = 'right';
        this.fixedContent = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top';
        this.stickyContent = '.sticky-top';
        this.ignoreBackdropClick = false;

        var parsedData = this.$element.CFW_parseData('modal', CFW_Widget_Modal.DEFAULTS);
        this.settings = $.extend({}, CFW_Widget_Modal.DEFAULTS, parsedData, options);

        this._init();
    };

    CFW_Widget_Modal.DEFAULTS = {
        target       : false,   // Target selector
        animate      : true,    // If modal windows should animate
        unlink       : false,   // If on hide to remove events and attributes from modal and trigger
        dispose      : false,   // If on hide to unlink, then remove modal from DOM
        backdrop     : true,    // Show backdrop, or 'static' for no close on click
        keyboard     : true,    // Close modal on ESC press
        show         : false    // Show modal afer initialize
    };

    CFW_Widget_Modal.prototype = {

        _init : function() {
            var selector = this.$element.CFW_getSelectorFromChain('modal', this.settings.target);
            if (!selector) { return; }
            this.$target = $(selector);
            this.$dialog = this.$target.find('.modal-dialog');

            this.$element.attr('data-cfw', 'modal');

            // Check for presence of ids - set if not present
            // var triggerID = this.$element.CFW_getID('cfw-modal');
            var targetID = this.$target.CFW_getID('cfw-modal');

            // Set ARIA attributes on trigger
            this.$element.attr('aria-controls', targetID);

            // Use '.modal-title' for labelledby
            var $title = this.$target.find('.modal-title');
            if ($title.length) {
                var labelledby = $title.eq(0).CFW_getID('cfw-modal');
                this.$target.attr('aria-labelledby', labelledby);
            }

            // Set ARIA attributes on target
            this.$target.attr({
                'role': 'dialog',
                'aria-hidden': 'true',
                'tabindex': -1
            });
            this.$dialog.attr('role', 'document');

            // Bind click handler
            this.$element.on('click.cfw.modal', $.proxy(this.toggle, this));

            this.$target.data('cfw.modal', this);

            this.$target.CFW_trigger('init.cfw.modal');

            if (this.settings.show) {
                this.show();
            }
        },

        toggle : function(e) {
            if (e) { e.preventDefault(); }
            if (this.isShown) {
                this.hide();
            } else {
                this.show();
            }
        },

        show : function() {
            var $selfRef = this;

            // Bail if already showing
            if (this.isShown) { return; }

            // Start open transition
            if (!this.$target.CFW_trigger('beforeShow.cfw.modal')) {
                return;
            }

            this.isShown = true;

            this.checkScrollbar();
            this.setScrollbar();
            this.$body.addClass('modal-open');

            this.escape();
            this.resize();

            this.$target.on('click.dismiss.cfw.modal', '[data-cfw-dismiss="modal"]', function(e) {
                    if (e) { e.preventDefault(); }
                    $selfRef.hide();
                })
                .data('cfw.modal', this);

            this.$dialog.on('mousedown.dismiss.cfw.modal', function() {
                $selfRef.$target.one('mouseup.dismiss.cfw.modal', function(e) {
                    if ($(e.target).is($selfRef.$target)) $selfRef.ignoreBackdropClick = true;
                });
            });

            this.backdrop(function() {
                $selfRef._showComplete();
            });
        },

        hide : function() {
            // Bail if not showing
            if (!this.isShown) { return; }

            // Start close transition
            if (!this.$target.CFW_trigger('beforeHide.cfw.modal')) {
                return;
            }

            this.isShown = false;

            $(document).off('focusin.cfw.modal');
            this.$target
                .removeClass('in')
                .attr('aria-hidden', true)
                .off('.dismiss.cfw.modal');

            this.$dialog.off('mousedown.dismiss.cfw.modal');

            if (this.$focusLast) {
                this.$focusLast.off('.cfw.' + this.type + '.focusLast');
            }

            this.$target.CFW_transition(null, $.proxy(this._hideComplete, this));
        },

        _showComplete : function() {
            var $selfRef = this;

            if (this.settings.animate) {
                this.$target.addClass('fade');
            }

            if (!this.$target.parent().length) {
                this.$target.appendTo(this.$body); // don't move modals dom position
            }

            this.$target.show().scrollTop(0);

            this.adjustDialog();

            this.$target[0].offsetWidth; // Force Reflow

            this.$target.addClass('in').removeAttr('aria-hidden');

            // Mutation handler
            this.$target
                .attr('data-cfw-mutate', '')
                .CFW_mutationListen()
                .on('mutate.cfw.mutate', function() {
                    $selfRef.handleUpdate();
                });

            this.enforceFocus();
            this.enforceFocusLast();

            function complete() {
                $selfRef.$target.trigger('focus');
                $selfRef.$target
                    .CFW_mutateTrigger()
                    .CFW_trigger('afterShow.cfw.modal');
            }

            this.$target.CFW_transition(null, complete);
        },

        _hideComplete : function() {
            var $selfRef = this;

            this.escape();
            this.resize();

            this.$target
                .off('mutate.cfw.mutate')
                .removeAttr('data-cfw-mutate')
                .CFW_mutationIgnore()
                .hide();
            this.backdrop(function() {
                $selfRef.$body.removeClass('modal-open');
                $selfRef.resetAdjustments();
                $selfRef.resetScrollbar();
                $selfRef.$target
                    .CFW_mutateTrigger()
                    .CFW_trigger('afterHide.cfw.modal');
            });
            this.$element && this.$element.trigger('focus');
        },

        enforceFocus : function() {
            var $selfRef = this;
            $(document)
                .off('focusin.cfw.modal') // guard against infinite focus loop
                .on('focusin.cfw.modal', function(e) {
                    if (document !== e.target && $selfRef.$target[0] !== e.target && !$selfRef.$target.has(e.target).length) {
                        $selfRef.$target.trigger('focus');
                    }
                });
        },

        enforceFocusLast : function() {
            var $selfRef = this;
            // Inject an item to fake loss of focus in case the modal
            // is last tabbable item in document - otherwise focus drops off page
            if (!this.$focusLast) {
                this.$focusLast = $(document.createElement('span'))
                .addClass('modal-focuslast')
                .attr('tabindex', 0)
                .appendTo(this.$target);
            }
            if (this.$focusLast) {
                this.$focusLast
                    .off('focusin.cfw.modal.focusLast')
                    .on('focusin.cfw.modal.focusLast', function() {
                        $selfRef.$target.trigger('focus');
                    });
            }
        },

        escape : function() {
            var $selfRef = this;
            if (this.isShown && this.settings.keyboard) {
                this.$target.on('keydown.dismiss.cfw.modal', function(e) {
                    if (e.which == 27) {
                        e.preventDefault();
                        $selfRef.hide();
                    }
                });
            } else if (!this.isShown) {
                this.$target.off('keydown.dismiss.cfw.modal');
            }
        },

        resize : function() {
            if (this.isShown) {
                $(window).on('resize.cfw.modal', $.proxy(this.handleUpdate, this));
            } else {
                $(window).off('resize.cfw.modal');
            }
        },

        // these following methods are used to handle overflowing modals
        handleUpdate : function() {
            if (this.settings.backdrop) this.adjustBackdrop();
            this.adjustDialog();
        },

        adjustBackdrop : function() {
            this.$backdrop
                .css('height', 0)
                .css('height', this.$target[0].scrollHeight);
        },

        adjustDialog : function() {
            var modalIsOverflowing = this.$target[0].scrollHeight > document.documentElement.clientHeight;

            this.$target.css({
                paddingLeft:  !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '',
                paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : ''
            });
        },

        resetAdjustments : function() {
            this.$target.css({
                paddingLeft: '',
                paddingRight: ''
            });
        },

        checkScrollbar : function() {
            var rect = document.body.getBoundingClientRect();
            this.bodyIsOverflowing = rect.left + rect.right < window.innerWidth;
            this.scrollbarWidth = $.CFW_measureScrollbar();
            this.scrollbarSide =  $('html').CFW_getScrollbarSide();
        },

        setScrollbar : function() {
            var $selfRef = this;
            var sideName = this.scrollbarSide.capitalize();

            if (this.bodyIsOverflowing) {
                // Update fixed element padding
                $(this.fixedContent).each(function() {
                    var $this = $(this);
                    $this.data('cfw.padding-dim', this.style['padding' + sideName] || '');
                    var padding = parseFloat($this.css('padding-' + $selfRef.scrollbarSide) || 0);
                    $this.css('padding-' + $selfRef.scrollbarSide, padding + $selfRef.scrollbarWidth);
                });

                // Update sticky element margin
                $(this.stickyContent).each(function() {
                    var $this = $(this);
                    $this.data('cfw.margin-dim', this.style['margin' + sideName] || '');
                    var margin = parseFloat($this.css('margin-' + $selfRef.scrollbarSide) || 0);
                    $this.css('margin-' + $selfRef.scrollbarSide, -(margin + $selfRef.scrollbarWidth));
                });

                // Update body padding
                this.$body.data('cfw.padding-dim', document.body.style['padding' + sideName] || '');
                var padding = parseFloat(this.$body.css('padding-' + this.scrollbarSide) || 0);
                this.$body.css('padding-' + this.scrollbarSide, padding + this.scrollbarWidth);
            }

            this.$target
                .on('touchmove.cfw.modal', $.proxy(this._scrollBlock, this))
                .CFW_trigger('scrollbarSet.cfw.modal');
        },

        resetScrollbar : function() {
            var $selfRef = this;

            // Restore fixed element padding
            $(this.fixedContent).each(function() {
                var $this = $(this);
                var padding = $this.data('cfw.padding-dim');
                $this.css('padding-' + $selfRef.scrollbarSide, padding);
                $this.removeData('cfw.padding-dim');
            });

            // Restore sticky element margin
            $(this.stickyContent).each(function() {
                var $this = $(this);
                var margin = $this.data('cfw.margin-dim');
                $this.css('margin-' + $selfRef.scrollbarSide, margin);
                $this.removeData('cfw.margin-dim');
            });

            // Restore body padding
            var padding = this.$body.data('cfw.padding-dim');
            if (typeof padding !== undefined) {
                this.$body.css('padding-' + this.scrollbarSide, padding);
                this.$body.removeData('cfw.padding-dim');
            }

            this.$target
                .off('touchmove.cfw.modal')
                .CFW_trigger('scrollbarReset.cfw.modal');
        },

        _scrollBlock : function(e) {
            var top = this.$target[0].scrollTop;
            var totalScroll = this.$target[0].scrollHeight;
            var currentScroll = top + this.$target[0].offsetHeight;

            if (top <= 0 && currentScroll >= totalScroll) {
                e.preventDefault();
            } else if (top === 0) {
                this.$target[0].scrollTop = 1;
            } else if (currentScroll === totalScroll) {
                this.$target[0].scrollTop = top - 1;
            }
        },

        backdrop : function(callback) {
            var $selfRef = this;

            var animate = (this.settings.animate) ? 'fade' : '';

            if (this.isShown && this.settings.backdrop) {
                this.$backdrop = $(document.createElement('div'))
                    .addClass('modal-backdrop ' + animate)
                    .appendTo(this.$body);

                this.$target.on('click.dismiss.cfw.modal', function(e) {
                    if ($selfRef.ignoreBackdropClick) {
                        $selfRef.ignoreBackdropClick = false;
                        return;
                    }
                    if (e.target !== e.currentTarget) { return; }
                    $selfRef.settings.backdrop == 'static'
                        ? $selfRef.$target.trigger('focus')
                        : $selfRef.hide();
                });

                this.$backdrop[0].offsetWidth; // Force Reflow

                this.$backdrop.addClass('in');

                this.$backdrop.CFW_transition(null, callback);
            } else if (!this.isShown && this.$backdrop) {
                this.$backdrop.removeClass('in');

                var callbackRemove = function() {
                    $selfRef.removeBackdrop();
                    callback && callback();
                };

                this.$backdrop.CFW_transition(null, callbackRemove);
            } else if (callback) {
                callback();
            }
        },

        removeBackdrop : function() {
            this.$backdrop && this.$backdrop.remove();
            this.$backdrop = null;
        },

        unlink : function() {
            var $selfRef = this;

            this.$target.CFW_trigger('beforeUnlink.cfw.modal');

            if (this.isShown) {
                this.$target.one('afterHide.cfw.modal', function() {
                    $selfRef._unlinkComplete();
                });
                this.hide();
            } else {
                this._unlinkComplete();
            }
        },

        _unlinkComplete : function() {
            var $target = this.$target;

            this.$target.off('.cfw.modal')
                .removeAttr('aria-labelledby')
                .removeData('cfw.modal');
            this.$element.off('.cfw.modal')
                .removeAttr('data-cfw aria-controls')
                .removeData('cfw.modal');

            this.$body = null;
            this.$element = null;
            this.$target = null;
            this.$dialog = null;
            this.$backdrop = null;
            this.$focusLast = null;
            this.isShown = null;
            this.scrollbarWidth = null;
            this.scrollbarSide = null;
            this.fixedContent = null;
            this.ignoreBackdropClick = null;
            this.settings = null;

            $target.CFW_trigger('afterUnlink.cfw.modal');
        },

        dispose : function() {
            $(document).one('afterUnlink.cfw.modal', this.$target, function(e) {
                var $this = $(e.target);
                $this.CFW_trigger('dispose.cfw.modal');
                $this.remove();
            });
            this.unlink();
        }
    };

    function Plugin(option) {
        var args = [].splice.call(arguments, 1);
        return this.each(function() {
            var $this = $(this);
            var data = $this.data('cfw.modal');
            var options = typeof option === 'object' && option;

            if (!data && /unlink|dispose/.test(option)) {
                return false;
            }
            if (!data) {
                $this.data('cfw.modal', (data = new CFW_Widget_Modal(this, options)));
            }
            if (typeof option === 'string') {
                data[option].apply(data, args);
            }
        });
    }

    $.fn.CFW_Modal = Plugin;
    $.fn.CFW_Modal.Constructor = CFW_Widget_Modal;

})(jQuery);
<commit_msg>Modal: stop infinite loop of updates<commit_after>/**
 * --------------------------------------------------------------------------
 * Figuration (v3.0.0): modal.js
 * Licensed under MIT (https://github.com/cast-org/figuration/blob/master/LICENSE)
 * --------------------------------------------------------------------------
 */

(function($) {
    'use strict';

    var CFW_Widget_Modal = function(element, options) {
        this.$body = $(document.body);
        this.$element = $(element);
        this.$target = null;
        this.$dialog = null;
        this.$backdrop = null;
        this.$focusLast = null;
        this.isShown = null;
        this.scrollbarWidth = 0;
        this.scrollbarSide = 'right';
        this.fixedContent = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top';
        this.stickyContent = '.sticky-top';
        this.ignoreBackdropClick = false;

        var parsedData = this.$element.CFW_parseData('modal', CFW_Widget_Modal.DEFAULTS);
        this.settings = $.extend({}, CFW_Widget_Modal.DEFAULTS, parsedData, options);

        this._init();
    };

    CFW_Widget_Modal.DEFAULTS = {
        target       : false,   // Target selector
        animate      : true,    // If modal windows should animate
        unlink       : false,   // If on hide to remove events and attributes from modal and trigger
        dispose      : false,   // If on hide to unlink, then remove modal from DOM
        backdrop     : true,    // Show backdrop, or 'static' for no close on click
        keyboard     : true,    // Close modal on ESC press
        show         : false    // Show modal afer initialize
    };

    CFW_Widget_Modal.prototype = {

        _init : function() {
            var selector = this.$element.CFW_getSelectorFromChain('modal', this.settings.target);
            if (!selector) { return; }
            this.$target = $(selector);
            this.$dialog = this.$target.find('.modal-dialog');

            this.$element.attr('data-cfw', 'modal');

            // Check for presence of ids - set if not present
            // var triggerID = this.$element.CFW_getID('cfw-modal');
            var targetID = this.$target.CFW_getID('cfw-modal');

            // Set ARIA attributes on trigger
            this.$element.attr('aria-controls', targetID);

            // Use '.modal-title' for labelledby
            var $title = this.$target.find('.modal-title');
            if ($title.length) {
                var labelledby = $title.eq(0).CFW_getID('cfw-modal');
                this.$target.attr('aria-labelledby', labelledby);
            }

            // Set ARIA attributes on target
            this.$target.attr({
                'role': 'dialog',
                'aria-hidden': 'true',
                'tabindex': -1
            });
            this.$dialog.attr('role', 'document');

            // Bind click handler
            this.$element.on('click.cfw.modal', $.proxy(this.toggle, this));

            this.$target.data('cfw.modal', this);

            this.$target.CFW_trigger('init.cfw.modal');

            if (this.settings.show) {
                this.show();
            }
        },

        toggle : function(e) {
            if (e) { e.preventDefault(); }
            if (this.isShown) {
                this.hide();
            } else {
                this.show();
            }
        },

        show : function() {
            var $selfRef = this;

            // Bail if already showing
            if (this.isShown) { return; }

            // Start open transition
            if (!this.$target.CFW_trigger('beforeShow.cfw.modal')) {
                return;
            }

            this.isShown = true;

            this.checkScrollbar();
            this.setScrollbar();
            this.$body.addClass('modal-open');

            this.escape();
            this.resize();

            this.$target.on('click.dismiss.cfw.modal', '[data-cfw-dismiss="modal"]', function(e) {
                    if (e) { e.preventDefault(); }
                    $selfRef.hide();
                })
                .data('cfw.modal', this);

            this.$dialog.on('mousedown.dismiss.cfw.modal', function() {
                $selfRef.$target.one('mouseup.dismiss.cfw.modal', function(e) {
                    if ($(e.target).is($selfRef.$target)) $selfRef.ignoreBackdropClick = true;
                });
            });

            this.backdrop(function() {
                $selfRef._showComplete();
            });
        },

        hide : function() {
            // Bail if not showing
            if (!this.isShown) { return; }

            // Start close transition
            if (!this.$target.CFW_trigger('beforeHide.cfw.modal')) {
                return;
            }

            this.isShown = false;

            $(document).off('focusin.cfw.modal');
            this.$target
                .removeClass('in')
                .attr('aria-hidden', true)
                .off('.dismiss.cfw.modal');

            this.$dialog.off('mousedown.dismiss.cfw.modal');

            if (this.$focusLast) {
                this.$focusLast.off('.cfw.' + this.type + '.focusLast');
            }

            this.$target.CFW_transition(null, $.proxy(this._hideComplete, this));
        },

        _showComplete : function() {
            var $selfRef = this;

            if (this.settings.animate) {
                this.$target.addClass('fade');
            }

            if (!this.$target.parent().length) {
                this.$target.appendTo(this.$body); // don't move modals dom position
            }

            this.$target.show().scrollTop(0);

            this.adjustDialog();

            this.$target[0].offsetWidth; // Force Reflow

            this.$target.addClass('in').removeAttr('aria-hidden');

            // Mutation handler
            this.$target
                .attr('data-cfw-mutate', '')
                // If enabled will cause infinite loop of updates
                // .CFW_mutationListen()
                .on('mutate.cfw.mutate', function() {
                    $selfRef.handleUpdate();
                });

            this.enforceFocus();
            this.enforceFocusLast();

            function complete() {
                $selfRef.$target.trigger('focus');
                $selfRef.$target
                    .CFW_mutateTrigger()
                    .CFW_trigger('afterShow.cfw.modal');
            }

            this.$target.CFW_transition(null, complete);
        },

        _hideComplete : function() {
            var $selfRef = this;

            this.escape();
            this.resize();

            this.$target
                .off('mutate.cfw.mutate')
                .removeAttr('data-cfw-mutate')
                .CFW_mutationIgnore()
                .hide();
            this.backdrop(function() {
                $selfRef.$body.removeClass('modal-open');
                $selfRef.resetAdjustments();
                $selfRef.resetScrollbar();
                $selfRef.$target
                    .CFW_mutateTrigger()
                    .CFW_trigger('afterHide.cfw.modal');
            });
            this.$element && this.$element.trigger('focus');
        },

        enforceFocus : function() {
            var $selfRef = this;
            $(document)
                .off('focusin.cfw.modal') // guard against infinite focus loop
                .on('focusin.cfw.modal', function(e) {
                    if (document !== e.target && $selfRef.$target[0] !== e.target && !$selfRef.$target.has(e.target).length) {
                        $selfRef.$target.trigger('focus');
                    }
                });
        },

        enforceFocusLast : function() {
            var $selfRef = this;
            // Inject an item to fake loss of focus in case the modal
            // is last tabbable item in document - otherwise focus drops off page
            if (!this.$focusLast) {
                this.$focusLast = $(document.createElement('span'))
                .addClass('modal-focuslast')
                .attr('tabindex', 0)
                .appendTo(this.$target);
            }
            if (this.$focusLast) {
                this.$focusLast
                    .off('focusin.cfw.modal.focusLast')
                    .on('focusin.cfw.modal.focusLast', function() {
                        $selfRef.$target.trigger('focus');
                    });
            }
        },

        escape : function() {
            var $selfRef = this;
            if (this.isShown && this.settings.keyboard) {
                this.$target.on('keydown.dismiss.cfw.modal', function(e) {
                    if (e.which == 27) {
                        e.preventDefault();
                        $selfRef.hide();
                    }
                });
            } else if (!this.isShown) {
                this.$target.off('keydown.dismiss.cfw.modal');
            }
        },

        resize : function() {
            if (this.isShown) {
                $(window).on('resize.cfw.modal', $.proxy(this.handleUpdate, this));
            } else {
                $(window).off('resize.cfw.modal');
            }
        },

        // these following methods are used to handle overflowing modals
        handleUpdate : function() {
            if (this.settings.backdrop) this.adjustBackdrop();
            this.adjustDialog();
        },

        adjustBackdrop : function() {
            this.$backdrop
                .css('height', 0)
                .css('height', this.$target[0].scrollHeight);
        },

        adjustDialog : function() {
            var modalIsOverflowing = this.$target[0].scrollHeight > document.documentElement.clientHeight;

            this.$target.css({
                paddingLeft:  !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '',
                paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : ''
            });
        },

        resetAdjustments : function() {
            this.$target.css({
                paddingLeft: '',
                paddingRight: ''
            });
        },

        checkScrollbar : function() {
            var rect = document.body.getBoundingClientRect();
            this.bodyIsOverflowing = rect.left + rect.right < window.innerWidth;
            this.scrollbarWidth = $.CFW_measureScrollbar();
            this.scrollbarSide =  $('html').CFW_getScrollbarSide();
        },

        setScrollbar : function() {
            var $selfRef = this;
            var sideName = this.scrollbarSide.capitalize();

            if (this.bodyIsOverflowing) {
                // Update fixed element padding
                $(this.fixedContent).each(function() {
                    var $this = $(this);
                    $this.data('cfw.padding-dim', this.style['padding' + sideName] || '');
                    var padding = parseFloat($this.css('padding-' + $selfRef.scrollbarSide) || 0);
                    $this.css('padding-' + $selfRef.scrollbarSide, padding + $selfRef.scrollbarWidth);
                });

                // Update sticky element margin
                $(this.stickyContent).each(function() {
                    var $this = $(this);
                    $this.data('cfw.margin-dim', this.style['margin' + sideName] || '');
                    var margin = parseFloat($this.css('margin-' + $selfRef.scrollbarSide) || 0);
                    $this.css('margin-' + $selfRef.scrollbarSide, -(margin + $selfRef.scrollbarWidth));
                });

                // Update body padding
                this.$body.data('cfw.padding-dim', document.body.style['padding' + sideName] || '');
                var padding = parseFloat(this.$body.css('padding-' + this.scrollbarSide) || 0);
                this.$body.css('padding-' + this.scrollbarSide, padding + this.scrollbarWidth);
            }

            this.$target
                .on('touchmove.cfw.modal', $.proxy(this._scrollBlock, this))
                .CFW_trigger('scrollbarSet.cfw.modal');
        },

        resetScrollbar : function() {
            var $selfRef = this;

            // Restore fixed element padding
            $(this.fixedContent).each(function() {
                var $this = $(this);
                var padding = $this.data('cfw.padding-dim');
                $this.css('padding-' + $selfRef.scrollbarSide, padding);
                $this.removeData('cfw.padding-dim');
            });

            // Restore sticky element margin
            $(this.stickyContent).each(function() {
                var $this = $(this);
                var margin = $this.data('cfw.margin-dim');
                $this.css('margin-' + $selfRef.scrollbarSide, margin);
                $this.removeData('cfw.margin-dim');
            });

            // Restore body padding
            var padding = this.$body.data('cfw.padding-dim');
            if (typeof padding !== undefined) {
                this.$body.css('padding-' + this.scrollbarSide, padding);
                this.$body.removeData('cfw.padding-dim');
            }

            this.$target
                .off('touchmove.cfw.modal')
                .CFW_trigger('scrollbarReset.cfw.modal');
        },

        _scrollBlock : function(e) {
            var top = this.$target[0].scrollTop;
            var totalScroll = this.$target[0].scrollHeight;
            var currentScroll = top + this.$target[0].offsetHeight;

            if (top <= 0 && currentScroll >= totalScroll) {
                e.preventDefault();
            } else if (top === 0) {
                this.$target[0].scrollTop = 1;
            } else if (currentScroll === totalScroll) {
                this.$target[0].scrollTop = top - 1;
            }
        },

        backdrop : function(callback) {
            var $selfRef = this;

            var animate = (this.settings.animate) ? 'fade' : '';

            if (this.isShown && this.settings.backdrop) {
                this.$backdrop = $(document.createElement('div'))
                    .addClass('modal-backdrop ' + animate)
                    .appendTo(this.$body);

                this.$target.on('click.dismiss.cfw.modal', function(e) {
                    if ($selfRef.ignoreBackdropClick) {
                        $selfRef.ignoreBackdropClick = false;
                        return;
                    }
                    if (e.target !== e.currentTarget) { return; }
                    $selfRef.settings.backdrop == 'static'
                        ? $selfRef.$target.trigger('focus')
                        : $selfRef.hide();
                });

                this.$backdrop[0].offsetWidth; // Force Reflow

                this.$backdrop.addClass('in');

                this.$backdrop.CFW_transition(null, callback);
            } else if (!this.isShown && this.$backdrop) {
                this.$backdrop.removeClass('in');

                var callbackRemove = function() {
                    $selfRef.removeBackdrop();
                    callback && callback();
                };

                this.$backdrop.CFW_transition(null, callbackRemove);
            } else if (callback) {
                callback();
            }
        },

        removeBackdrop : function() {
            this.$backdrop && this.$backdrop.remove();
            this.$backdrop = null;
        },

        unlink : function() {
            var $selfRef = this;

            this.$target.CFW_trigger('beforeUnlink.cfw.modal');

            if (this.isShown) {
                this.$target.one('afterHide.cfw.modal', function() {
                    $selfRef._unlinkComplete();
                });
                this.hide();
            } else {
                this._unlinkComplete();
            }
        },

        _unlinkComplete : function() {
            var $target = this.$target;

            this.$target.off('.cfw.modal')
                .removeAttr('aria-labelledby')
                .removeData('cfw.modal');
            this.$element.off('.cfw.modal')
                .removeAttr('data-cfw aria-controls')
                .removeData('cfw.modal');

            this.$body = null;
            this.$element = null;
            this.$target = null;
            this.$dialog = null;
            this.$backdrop = null;
            this.$focusLast = null;
            this.isShown = null;
            this.scrollbarWidth = null;
            this.scrollbarSide = null;
            this.fixedContent = null;
            this.ignoreBackdropClick = null;
            this.settings = null;

            $target.CFW_trigger('afterUnlink.cfw.modal');
        },

        dispose : function() {
            $(document).one('afterUnlink.cfw.modal', this.$target, function(e) {
                var $this = $(e.target);
                $this.CFW_trigger('dispose.cfw.modal');
                $this.remove();
            });
            this.unlink();
        }
    };

    function Plugin(option) {
        var args = [].splice.call(arguments, 1);
        return this.each(function() {
            var $this = $(this);
            var data = $this.data('cfw.modal');
            var options = typeof option === 'object' && option;

            if (!data && /unlink|dispose/.test(option)) {
                return false;
            }
            if (!data) {
                $this.data('cfw.modal', (data = new CFW_Widget_Modal(this, options)));
            }
            if (typeof option === 'string') {
                data[option].apply(data, args);
            }
        });
    }

    $.fn.CFW_Modal = Plugin;
    $.fn.CFW_Modal.Constructor = CFW_Widget_Modal;

})(jQuery);
<|endoftext|>


<commit_before>var Pulse = require('./pulse.js');
var BeatEvent = require('./beatevent.js');

var idCount = 0,
    hearts;

function initialize(global_hearts) {
    hearts = global_hearts;
    return Heart;
}

// Heart

function Heart(heartrate, name) {
    this.heartbeat = 0;

    this.events = [];
    this.eventList = {};
    this.eventsExist = false;

    this.id = name || 'heart_' + (Math.random()).toString(36) + idCount++;
    this.heartrate = heartrate || 2500;

    this.pulses = {};

    this.interval = setInterval(createInterval(this), heartrate);
    this.interval.unref();
}

Object.defineProperty(Heart.prototype, 'age', {
    enumerable: true,
    get: function() {
        return this.heartbeat;
    }
});

Object.defineProperty(Heart.prototype, 'name', {
    enumerable: true,
    get: function() {
        return this.id;
    }
});

Heart.prototype.setHeartrate = function(heartrate) {
    if (heartrate) {
        this.heartrate = heartrate;
        clearInterval(this.interval);
        this.interval = setInterval(createInterval(this), heartrate);
        this.interval.unref();
    }

    return this;
};

Heart.prototype.kill = Heart.prototype.destroy = function() {
    clearInterval(this.interval);
    if (hearts[this.id] !== undefined) {
        hearts[this.id] = undefined;
    }
};

// Pulses

Heart.prototype.newPulse = Heart.prototype.createPulse = function(name) {
    var pulse = new Pulse(this, name);

    if (name) {
        this.killPulse(name);
        this.pulses[pulse.id] = pulse;
    }

    return pulse;
};

Heart.prototype.killPulse = function(name) {
    if (this.pulses[name] !== undefined) {
        this.pulses[name] = undefined;
    }
};

Heart.prototype.pulse = function(name) {
    return this.pulses[name];
};

// Events

Heart.prototype.createEvent = function(modulo, options, fn) {
    if (typeof options === 'function') {
        fn = options;
        options = {};
    }

    var name = options.name;
    var repeat = options.repeat;
    var event = new BeatEvent(this, modulo, name, repeat, fn);

    prepHeartForEvents(this);

    if (name) {
        this.killEvent(name);
        this.eventList[name] = event;
    }

    this.events.push(event);
    event.index = this.events.length - 1;

    return event;
};

Heart.prototype.event = function(name) {
    return this.eventList[name];
};

Heart.prototype.killEvent = function(name, beatevent) {
    if (this.eventList[name] !== undefined) {
        this.events[this.eventList[name].index] = undefined;
        this.eventList[name] = undefined;
    } else if (beatevent) {
        this.events[beatevent.index] = undefined;
    }
};

Heart.prototype.killAllEvents = function() {
    this.eventsExist = false;
    this.events = [];
    this.eventList = {};
    clearInterval(this.interval);
    this.interval = setInterval(createInterval(this), this.heartrate);
    this.interval.unref();
};

function prepHeartForEvents(heart) {
    if (heart.eventsExist === false) {
        heart.eventsExist = true;
        clearInterval(heart.interval);
        heart.interval = setInterval(createInterval(heart), heart.heartrate);
        heart.interval.unref();
    }
}

function createInterval(heart) {
    var events = heart.events;
    var executor;

    if (heart.eventsExist === false) {
        executor = function() {
            heart.heartbeat++;
        };
    } else {
        executor = function() {

            heart.heartbeat++;

            for (var i = 0; i < events.length; i++) {
                if (events[i] !== undefined) {
                    events[i].execute();
                    if (events[i].done === true) {
                        events[i].kill();
                    }
                }
            }
        };
    }

    return executor;
}

exports = module.exports = {
    initialize: initialize
};
<commit_msg>Specific check of undefined variables.<commit_after>var Pulse = require('./pulse.js');
var BeatEvent = require('./beatevent.js');

var idCount = 0,
    hearts;

function initialize(global_hearts) {
    hearts = global_hearts;
    return Heart;
}

// Heart

function Heart(heartrate, name) {
    this.heartbeat = 0;

    this.events = [];
    this.eventList = {};
    this.eventsExist = false;

    this.id = name || 'heart_' + (Math.random()).toString(36) + idCount++;
    this.heartrate = heartrate || 2500;

    this.pulses = {};

    this.interval = setInterval(createInterval(this), heartrate);
    this.interval.unref();
}

Object.defineProperty(Heart.prototype, 'age', {
    enumerable: true,
    get: function() {
        return this.heartbeat;
    }
});

Object.defineProperty(Heart.prototype, 'name', {
    enumerable: true,
    get: function() {
        return this.id;
    }
});

Heart.prototype.setHeartrate = function(heartrate) {
    if (heartrate) {
        this.heartrate = heartrate;
        clearInterval(this.interval);
        this.interval = setInterval(createInterval(this), heartrate);
        this.interval.unref();
    }

    return this;
};

Heart.prototype.kill = Heart.prototype.destroy = function() {
    clearInterval(this.interval);
    if (hearts[this.id] !== undefined) {
        hearts[this.id] = undefined;
    }
};

// Pulses

Heart.prototype.newPulse = Heart.prototype.createPulse = function(name) {
    var pulse = new Pulse(this, name);

    if (name) {
        this.killPulse(name);
        this.pulses[pulse.id] = pulse;
    }

    return pulse;
};

Heart.prototype.killPulse = function(name) {
    if (this.pulses[name] !== undefined) {
        this.pulses[name] = undefined;
    }
};

Heart.prototype.pulse = function(name) {
    return this.pulses[name];
};

// Events

Heart.prototype.createEvent = function(modulo, options, fn) {
    if (typeof options === 'function') {
        fn = options;
        options = {};
    }

    var name = options.name;
    var repeat = options.repeat;
    var event = new BeatEvent(this, modulo, name, repeat, fn);

    prepHeartForEvents(this);

    if (name !== undefined) {
        this.killEvent(name);
        this.eventList[name] = event;
    }

    this.events.push(event);
    event.index = this.events.length - 1;

    return event;
};

Heart.prototype.event = function(name) {
    return this.eventList[name];
};

Heart.prototype.killEvent = function(name, beatevent) {
    if (this.eventList[name] !== undefined) {
        this.events[this.eventList[name].index] = undefined;
        this.eventList[name] = undefined;
    } else if (beatevent !== undefined) {
        this.events[beatevent.index] = undefined;
    }
};

Heart.prototype.killAllEvents = function() {
    this.eventsExist = false;
    this.events = [];
    this.eventList = {};
    clearInterval(this.interval);
    this.interval = setInterval(createInterval(this), this.heartrate);
    this.interval.unref();
};

function prepHeartForEvents(heart) {
    if (heart.eventsExist === false) {
        heart.eventsExist = true;
        clearInterval(heart.interval);
        heart.interval = setInterval(createInterval(heart), heart.heartrate);
        heart.interval.unref();
    }
}

function createInterval(heart) {
    var events = heart.events;
    var executor;

    if (heart.eventsExist === false) {
        executor = function() {
            heart.heartbeat++;
        };
    } else {
        executor = function() {

            heart.heartbeat++;

            for (var i = 0; i < events.length; i++) {
                if (events[i] !== undefined) {
                    events[i].execute();
                    if (events[i].done === true) {
                        events[i].kill();
                    }
                }
            }
        };
    }

    return executor;
}

exports = module.exports = {
    initialize: initialize
};
<|endoftext|>


<commit_before>var gulp = require('gulp');
var mocha = require('gulp-mocha');

var files = '*.js';
var libs = 'lib/**/*.js';
var tests = 'test/**/*.js';

gulp.task('test', function(done) {
  gulp.src([files, libs, tests])
    .pipe(mocha({
      reporter: 'spec'
    }));
  done();
});

gulp.task('watch', function(done) {
  gulp.watch([files, libs, tests], ['test']);
  done();
});

gulp.task('default', ['watch']);<commit_msg>avoid stop gulp tasks on test errors<commit_after>var gulp = require('gulp');
var mocha = require('gulp-mocha');

var files = '*.js';
var libs = 'lib/**/*.js';
var tests = 'test/**/*.js';

gulp.task('test', function(done) {
  return gulp.src([files, libs, tests])
    .pipe(mocha({
      reporter: 'spec'
    }));
});

gulp.task('watch', function(done) {
  return gulp.watch([files, libs, tests], ['test']);
});

gulp.task('default', ['watch']);<|endoftext|>