<commit_before>var ko = require('knockout');
require('knockout-punches');
var $ = require('jquery');
var bootbox = require('bootbox');
var Raven = require('raven-js');

var FolderPicker = require('folderpicker');
var $osf = require('osfHelpers');
var ctx = window.contextVars;

ko.punches.enableAll();
/**
 * Knockout view model for citations node settings widget.
 */
var CitationsFolderPickerViewModel = function(name, url, selector, folderPicker) {
    var self = this;

    self.url = url;
    self.properName = name.charAt(0).toUpperCase() + name.slice(1);
    self.selector = selector;
    // CSS selector for the folder picker div
    self.folderPicker = folderPicker;

    // DEFAULTS
    self.accounts = ko.observableArray([]);
    // Auth information
    self.nodeHasAuth = ko.observable(false);
    // whether current user is authorizer of the addon
    self.userIsOwner = ko.observable(false);
    // whether current user has an auth token
    self.userHasAuth = ko.observable(false);
    // whether the auth token is valid
    self.validCredentials = ko.observable(true);
    self.userAccountId = ko.observable('');
    // Currently linked folder, an Object of the form {name: ..., id: ...}
    self.folder = ko.observable({
        name: '',
        id: ''
    });
    self.ownerName = ko.observable('');
    self.urls = ko.observable({});
    // Flashed messages
    self.message = ko.observable('');
    self.messageClass = ko.observable('text-info');
    // Display names
    self.PICKER = 'picker';
    // Current folder display
    self.currentDisplay = ko.observable(false);
    // Currently selected folder name
    self.selected = ko.observable(false);
    self.loading = ko.observable(false);
    // Whether the initial data has been fetched form the server. Used for
    // error handling.
    self.loadedSettings = ko.observable(false);
    // Whether the mendeley folders have been loaded from the server/mendeley API
    self.loadedFolders = ko.observable(false);

    self.messages = {
        INVALID_CRED_OWNER: ko.computed(function() {
            return 'Could not retrieve ' + self.properName + ' settings at ' +
                'this time. The ' + self.properName + ' addon credentials may no longer be valid.' +
                ' Try deauthorizing and reauthorizing ' + self.properName + ' on your account settings page.';
        }),
        INVALID_CRED_NOT_OWNER: ko.computed(function() {
            return 'Could not retrieve ' + self.properName + ' settings at ' +
                'this time. The ' + self.properName + ' addon credentials may no longer be valid.' +
                ' Contact ' + self.ownerName() + ' to verify.';
        }),
        CANT_RETRIEVE_SETTINGS: ko.computed(function() {
            return 'Could not retrieve ' + self.properName + ' settings at ' +
                'this time. Please refresh ' +
                'the page. If the problem persists, email ' +
                'support@osf.io.';
        }),
        UPDATE_ACCOUNTS_ERROR: ko.computed(function() {
            return 'Could not retrieve ' + self.properName + ' account list at ' +
                'this time. Please refresh the page. If the problem persists, email ' +
                'support@osf.io.';
        }),
        DEAUTHORIZE_SUCCESS: ko.computed(function() {
            return 'Deauthorized ' + self.properName + '.';
        }),
        DEAUTHORIZE_FAIL: ko.computed(function() {
            return 'Could not deauthorize because of an error. Please try again later.';
        }),
        CONNECT_ACCOUNT_SUCCESS: ko.computed(function() {
            return 'Successfully created a Mendeley Access Token';
        }),
        SUBMIT_SETTINGS_SUCCESS: ko.computed(function() {
            var overviewURL = window.contextVars.node.urls.web;
            return 'Successfully linked "' + self.folder() + '". Go to the Overview page to view your citations.';
        }),
        SUBMIT_SETTINGS_ERROR: ko.computed(function() {
            return 'Could not change settings. Please try again later.';
        }),
        CONFIRM_DEAUTH: ko.computed(function() {
            return 'Are you sure you want to remove this ' + self.properName + ' authorization?';
        }),
        CONFIRM_AUTH: ko.computed(function() {
            return 'Are you sure you want to authorize this project with your ' + self.properName + ' access token?';
        }),
        TOKEN_IMPORT_ERROR: ko.computed(function() {
            return 'Error occurred while importing access token.';
        }),
        CONNECT_ERROR: ko.computed(function() {
            return 'Could not connect to ' + self.properName + ' at this time. Please try again later.';
        })
    };

    /**
     * Whether or not to show the Import Access Token Button
     */
    self.showImport = ko.computed(function() {
        // Invoke the observables to ensure dependency tracking
        var userHasAuth = self.userHasAuth();
        var nodeHasAuth = self.nodeHasAuth();
        var loaded = self.loadedSettings();
        return userHasAuth && !nodeHasAuth && loaded;
    });

    /** Whether or not to show the full settings pane. */
    self.showSettings = ko.computed(function() {
        return self.nodeHasAuth();
    });

    /** Whether or not to show the Create Access Token button */
    self.showTokenCreateButton = ko.computed(function() {
        // Invoke the observables to ensure dependency tracking
        var userHasAuth = self.userHasAuth();
        var nodeHasAuth = self.nodeHasAuth();
        var loaded = self.loadedSettings();
        return !userHasAuth && !nodeHasAuth && loaded;
    });

    /** Computed functions for the linked and selected folders' display text.*/
    self.folderName = ko.computed(function() {
        var nodeHasAuth = self.nodeHasAuth();
        var folder = self.folder();
        return (nodeHasAuth && folder) ? folder.name : '';
    });

    self.selectedFolderName = ko.computed(function() {
        var userIsOwner = self.userIsOwner();
        var selected = self.selected();
        return (userIsOwner && selected) ? selected.name : '';
    });

    // Initial fetch from server
    self.fetchFromServer();
};

/** Change the flashed message. */
CitationsFolderPickerViewModel.prototype.changeMessage = function(text, css, timeout) {
    var self = this;

    self.message(text);
    var cssClass = css || 'text-info';
    self.messageClass(cssClass);
    if (timeout) {
        // Reset message after timeout period
        setTimeout(function() {
            self.message('');
            self.messageClass('text-info');
        }, timeout);
    }
};

/**
 * Update the view model from data returned from the server.
 */
CitationsFolderPickerViewModel.prototype.updateFromData = function(data) {
    var self = this;
    self.ownerName(data.ownerName);
    self.nodeHasAuth(data.nodeHasAuth);
    self.userIsOwner(data.userIsOwner);
    self.userHasAuth(data.userHasAuth);
    self.userAccountId(data.userAccountId);
    self.folder(data.folder || 'None');
    self.urls(data.urls);
    self.validCredentials(data.validCredentials);
};

CitationsFolderPickerViewModel.prototype.fetchFromServer = function() {
    var self = this;
    var request = $.ajax({
        url: self.url,
        type: 'GET',
        dataType: 'json'
    });

    request.done(function(response) {
        self.updateFromData(response);
        self.loadedSettings(true);
        if (!self.validCredentials()) {
            if (self.userIsOwner()) {
                self.changeMessage(self.messages.INVALID_CRED_OWNER(), 'text-warning');
            } else {
                self.changeMessage(self.messages.INVALID_CRED_NOT_OWNER(), 'text-warning');
            }
        }
    });
    request.fail(function(xhr, textStatus, error) {
        self.changeMessage(self.messages.CANT_RETRIEVE_SETTINGS(), 'text-warning');
        Raven.captureMessage('Could not GET Mendeley settings', {
            url: self.url,
            textStatus: textStatus,
            error: error
        });
    });
};

CitationsFolderPickerViewModel.prototype.updateAccounts = function(callback) {
    var self = this;
    var request = $.get(self.urls().accounts);
    request.done(function(data) {
        self.accounts(data.accounts.map(function(account) {
            return {
                name: account.display_name,
                id: account.id
            };
        }));
        callback();
    });
    request.fail(function(xhr, textStatus, error) {
        self.changeMessage(self.messages.UPDATE_ACCOUNTS_ERROR(), 'text-warning');
        Raven.captureMessage('Could not GET mendeley accounts for user', {
            url: self.url,
            textStatus: textStatus,
            error: error
        });
    });
};
/**
 * Allows a user to create a Menedeley access token from the nodeSettings page
 */
CitationsFolderPickerViewModel.prototype.connectAccount = function() {
    var self = this;

    window.oauthComplete = function(res) {
        // Update view model based on response
        self.changeMessage(self.messages.CONNECT_ACCOUNT_SUCCESS(), 'text-success', 3000);
        self.updateAccounts(function() {
            $osf.postJSON(
                self.urls().importAuth, {
                    external_account_id: self.accounts()[0].id
                }
            ).then(self.onImportSuccess.bind(self), self.onImportError.bind(self));
        });
    };
    window.open(self.urls().auth);
};

CitationsFolderPickerViewModel.prototype.connectExistingAccount = function(account_id) {
    var self = this;

    $osf.postJSON(
        self.urls().importAuth, {
            external_account_id: account_id
        }
    ).then(self.onImportSuccess.bind(self), self.onImportError.bind(self));
};

/**
 * Send a PUT request to change the linked mendeley folder.
 */
CitationsFolderPickerViewModel.prototype.submitSettings = function() {
    var self = this;

    function onSubmitSuccess(response) {
        self.folder(self.selected().name);
        self.changeMessage(self.messages.SUBMIT_SETTINGS_SUCCESS(), 'text-success', 5000);
        self.cancelSelection();
    }

    function onSubmitError() {
        self.changeMessage(self.messages.SUBMIT_SETTINGS_ERROR(), 'text-danger');
    }

    $osf.putJSON(self.urls().config, {
            external_account_id: self.userAccountId(),
            external_list_id: self.selected().id
        })
        .done(onSubmitSuccess)
        .fail(onSubmitError);
};

/**
 * Must be used to update radio buttons and knockout view model simultaneously
 */
CitationsFolderPickerViewModel.prototype.cancelSelection = function() {
    this.selected(null);
};

/** Pop up a confirmation to deauthorize mendeley from this node.
 *  Send DELETE request if confirmed.
 */
CitationsFolderPickerViewModel.prototype.deauthorize = function() {
    var self = this;

    /**
     * Send DELETE request to deauthorize this node.
     */
    function sendDeauth() {
        var request = $.ajax({
            url: self.urls().deauthorize,
            type: 'DELETE'
        });

        request.done(function() {
            // Update observables
            self.nodeHasAuth(false);
            self.cancelSelection();
            self.currentDisplay(null);
            self.changeMessage(self.messages.DEAUTHORIZE_SUCCESS(), 'text-warning', 3000);
        });

        request.fail(function(xhr, textStatus, error) {
            self.changeMessage(self.messages.DEAUTHORIZE_FAIL(), 'text-danger');
            Raven.captureMessage('Could not deauthorize mendeley account from node', {
                url: self.urls().deauthorize,
                textStatus: textStatus,
                error: error
            });
        });

        return request;
    }

    bootbox.confirm({
        title: 'Deauthorize ' + self.properName + '?',
        message: self.messages.CONFIRM_DEAUTH(),
        callback: function(confirmed) {
            if (confirmed) {
                return sendDeauth();
            }
        }
    });
};

// Callback for when PUT request to import user access token
CitationsFolderPickerViewModel.prototype.onImportSuccess = function(response) {
    var self = this;

    var msg = response.message || 'Successfully imported access token from profile.';
    // Update view model based on response
    self.changeMessage(msg, 'text-success', 3000);
    self.updateFromData(response.result);
    self.activatePicker();
};

CitationsFolderPickerViewModel.prototype.onImportError = function(xhr, textStatus, error) {
    var self = this;

    self.changeMessage(self.messages.TOKEN_IMPORT_ERROR(), 'text-danger');
    Raven.captureMessage('Failed to import Mendeley access token', {
        url: self.urls().importAuth,
        textStatus: textStatus,
        error: error
    });
};

/**
 * Send PUT request to import access token from user profile.
 */
CitationsFolderPickerViewModel.prototype.importAuth = function() {
    var self = this;

    self.updateAccounts(function() {
        if (self.accounts().length > 1) {
            bootbox.prompt({
                title: 'Choose Mendeley Access Token to Import',
                inputType: 'select',
                inputOptions: ko.utils.arrayMap(
                    self.accounts(),
                    function(item) {
                        return {
                            text: item.name,
                            value: item.id
                        };
                    }
                ),
                value: self.accounts()[0].id,
                callback: (self.connectExistingAccount.bind(self))
            });
        } else {
            bootbox.confirm({
                title: 'Import ' + self.properName + ' Access Token?',
                message: self.messages.CONFIRM_AUTH(),
                callback: function(confirmed) {
                    if (confirmed) {
                        self.connectExistingAccount.call(self, (self.accounts()[0].id));
                    }
                }
            });
        }
    });
};

/**
 * Activates the HGrid folder picker.
 */
CitationsFolderPickerViewModel.prototype.activatePicker = function() {
    var self = this;

    /** Callback for chooseFolder action.
     *   Just changes the ViewModel's self.selected observable to the selected
     *   folder.
     */
    function onPickFolder(evt, item) {
        evt.preventDefault();
        self.selected({
            name: item.data.name,
            id: item.data.id
        });
        return false; // Prevent event propagation
    }

    self.currentDisplay(self.PICKER);
    // Only load folders if they haven't already been requested
    if (!self.loadedFolders()) {
        // Show loading indicator
        self.loading(true);
        $(self.folderPicker).folderpicker({
            onPickFolder: onPickFolder,
            initialFolderPath: 'mendeley',
            // Fetch mendeley folders with AJAX
            filesData: self.urls().folders,
            // Lazy-load each folder's contents
            // Each row stores its url for fetching the folders it contains
            resolveLazyloadUrl: function(item) {
                return self.urls().folders + item.data.id + '/?view=folders';
            },
            lazyLoadPreprocess: function(data) {
                return data.contents.filter(function(item) {
                    return item.kind === 'folder';
                });
            },
            oddEvenClass: {
                odd: 'addon-folderpicker-odd',
                even: 'addon-folderpicker-even'
            },
            ajaxOptions: {
                error: function(xhr, textStatus, error) {
                    self.loading(false);
                    self.changeMessage(self.messages.CONNECT_ERROR(), 'text-warning');
                    Raven.captureMessage('Could not GET get ' + self.properName + ' contents.', {
                        textStatus: textStatus,
                        error: error
                    });
                }
            },
            folderPickerOnload: function() {
                // Hide loading indicator
                self.loading(false);
            }
        });
    }
};

/**
 * Toggles the visibility of the folder picker.
 */
CitationsFolderPickerViewModel.prototype.togglePicker = function() {
    var self = this;

    // Toggle visibility of folder picker
    var shown = self.currentDisplay() === self.PICKER;
    if (!shown) {
        self.currentDisplay(self.PICKER);
        self.activatePicker();
    } else {
        self.currentDisplay(null);
        // Clear selection
        self.cancelSelection();
    }
};

module.exports = CitationsFolderPickerViewModel;
<commit_msg>Cleaning up citationsFolderPickerViewModel.<commit_after>var ko = require('knockout');
require('knockout-punches');
var $ = require('jquery');
var bootbox = require('bootbox');
var Raven = require('raven-js');

var FolderPicker = require('folderpicker');
var $osf = require('osfHelpers');
var ctx = window.contextVars;

ko.punches.enableAll();
/**
 * Knockout view model for citations node settings widget.
 */
var CitationsFolderPickerViewModel = function(name, url, selector, folderPicker) {
    var self = this;

    self.url = url;
    self.properName = name.charAt(0).toUpperCase() + name.slice(1);
    self.selector = selector;
    // CSS selector for the folder picker div
    self.folderPicker = folderPicker;

    // DEFAULTS
    self.accounts = ko.observableArray([]);
    // Auth information
    self.nodeHasAuth = ko.observable(false);
    // whether current user is authorizer of the addon
    self.userIsOwner = ko.observable(false);
    // whether current user has an auth token
    self.userHasAuth = ko.observable(false);
    // whether the auth token is valid
    self.validCredentials = ko.observable(true);
    self.userAccountId = ko.observable('');
    // Currently linked folder, an Object of the form {name: ..., id: ...}
    self.folder = ko.observable({
        name: '',
        id: ''
    });
    self.ownerName = ko.observable('');
    self.urls = ko.observable({});
    // Flashed messages
    self.message = ko.observable('');
    self.messageClass = ko.observable('text-info');
    // Display names
    self.PICKER = 'picker';
    // Current folder display
    self.currentDisplay = ko.observable(false);
    // Currently selected folder name
    self.selected = ko.observable(false);
    self.loading = ko.observable(false);
    // Whether the initial data has been fetched form the server. Used for
    // error handling.
    self.loadedSettings = ko.observable(false);
    // Whether the folders have been loaded from the API
    self.loadedFolders = ko.observable(false);

    self.messages = {
        INVALID_CRED_OWNER: ko.pureComputed(function() {
            return 'Could not retrieve ' + self.properName + ' settings at ' +
                'this time. The ' + self.properName + ' addon credentials may no longer be valid.' +
                ' Try deauthorizing and reauthorizing ' + self.properName + ' on your account settings page.';
        }),
        INVALID_CRED_NOT_OWNER: ko.pureComputed(function() {
            return 'Could not retrieve ' + self.properName + ' settings at ' +
                'this time. The ' + self.properName + ' addon credentials may no longer be valid.' +
                ' Contact ' + self.ownerName() + ' to verify.';
        }),
        CANT_RETRIEVE_SETTINGS: ko.pureComputed(function() {
            return 'Could not retrieve ' + self.properName + ' settings at ' +
                'this time. Please refresh ' +
                'the page. If the problem persists, email ' +
                'support@osf.io.';
        }),
        UPDATE_ACCOUNTS_ERROR: ko.pureComputed(function() {
            return 'Could not retrieve ' + self.properName + ' account list at ' +
                'this time. Please refresh the page. If the problem persists, email ' +
                'support@osf.io.';
        }),
        DEAUTHORIZE_SUCCESS: ko.pureComputed(function() {
            return 'Deauthorized ' + self.properName + '.';
        }),
        DEAUTHORIZE_FAIL: ko.pureComputed(function() {
            return 'Could not deauthorize because of an error. Please try again later.';
        }),
        CONNECT_ACCOUNT_SUCCESS: ko.pureComputed(function() {
            return 'Successfully created a ' + self.properName + ' Access Token';
        }),
        SUBMIT_SETTINGS_SUCCESS: ko.pureComputed(function() {
            var overviewURL = window.contextVars.node.urls.web;
            return 'Successfully linked "' + self.folder() + '". Go to the Overview page to view your citations.';
        }),
        SUBMIT_SETTINGS_ERROR: ko.pureComputed(function() {
            return 'Could not change settings. Please try again later.';
        }),
        CONFIRM_DEAUTH: ko.pureComputed(function() {
            return 'Are you sure you want to remove this ' + self.properName + ' authorization?';
        }),
        CONFIRM_AUTH: ko.pureComputed(function() {
            return 'Are you sure you want to authorize this project with your ' + self.properName + ' access token?';
        }),
        TOKEN_IMPORT_ERROR: ko.pureComputed(function() {
            return 'Error occurred while importing access token.';
        }),
        CONNECT_ERROR: ko.pureComputed(function() {
            return 'Could not connect to ' + self.properName + ' at this time. Please try again later.';
        })
    };

    /**
     * Whether or not to show the Import Access Token Button
     */
    self.showImport = ko.pureComputed(function() {
        // Invoke the observables to ensure dependency tracking
        var userHasAuth = self.userHasAuth();
        var nodeHasAuth = self.nodeHasAuth();
        var loaded = self.loadedSettings();
        return userHasAuth && !nodeHasAuth && loaded;
    });

    /** Whether or not to show the full settings pane. */
    self.showSettings = ko.pureComputed(function() {
        return self.nodeHasAuth();
    });

    /** Whether or not to show the Create Access Token button */
    self.showTokenCreateButton = ko.pureComputed(function() {
        // Invoke the observables to ensure dependency tracking
        var userHasAuth = self.userHasAuth();
        var nodeHasAuth = self.nodeHasAuth();
        var loaded = self.loadedSettings();
        return !userHasAuth && !nodeHasAuth && loaded;
    });

    /** Computed functions for the linked and selected folders' display text.*/
    self.folderName = ko.pureComputed(function() {
        var nodeHasAuth = self.nodeHasAuth();
        var folder = self.folder();
        return (nodeHasAuth && folder) ? folder.name : '';
    });

    self.selectedFolderName = ko.pureComputed(function() {
        var userIsOwner = self.userIsOwner();
        var selected = self.selected();
        return (userIsOwner && selected) ? selected.name : '';
    });

    // Initial fetch from server
    self.fetchFromServer();
};

/** Change the flashed message. */
CitationsFolderPickerViewModel.prototype.changeMessage = function(text, css, timeout) {
    var self = this;

    self.message(text);
    var cssClass = css || 'text-info';
    self.messageClass(cssClass);
    if (timeout) {
        // Reset message after timeout period
        setTimeout(function() {
            self.message('');
            self.messageClass('text-info');
        }, timeout);
    }
};

/**
 * Update the view model from data returned from the server.
 */
CitationsFolderPickerViewModel.prototype.updateFromData = function(data) {
    var self = this;
    self.ownerName(data.ownerName);
    self.nodeHasAuth(data.nodeHasAuth);
    self.userIsOwner(data.userIsOwner);
    self.userHasAuth(data.userHasAuth);
    self.userAccountId(data.userAccountId);
    self.folder(data.folder || 'None');
    self.urls(data.urls);
    self.validCredentials(data.validCredentials);
};

CitationsFolderPickerViewModel.prototype.fetchFromServer = function() {
    var self = this;
    var request = $.ajax({
        url: self.url,
        type: 'GET',
        dataType: 'json'
    });

    request.done(function(response) {
        self.updateFromData(response);
        self.loadedSettings(true);
        if (!self.validCredentials()) {
            if (self.userIsOwner()) {
                self.changeMessage(self.messages.INVALID_CRED_OWNER(), 'text-warning');
            } else {
                self.changeMessage(self.messages.INVALID_CRED_NOT_OWNER(), 'text-warning');
            }
        }
    });
    request.fail(function(xhr, textStatus, error) {
        self.changeMessage(self.messages.CANT_RETRIEVE_SETTINGS(), 'text-warning');
        Raven.captureMessage('Could not GET ' + self.properName + 'settings', {
            url: self.url,
            textStatus: textStatus,
            error: error
        });
    });
};

CitationsFolderPickerViewModel.prototype.updateAccounts = function(callback) {
    var self = this;
    var request = $.get(self.urls().accounts);
    request.done(function(data) {
        self.accounts(data.accounts.map(function(account) {
            return {
                name: account.display_name,
                id: account.id
            };
        }));
        callback();
    });
    request.fail(function(xhr, textStatus, error) {
        self.changeMessage(self.messages.UPDATE_ACCOUNTS_ERROR(), 'text-warning');
        Raven.captureMessage('Could not GET ' + self.properName + ' accounts for user', {
            url: self.url,
            textStatus: textStatus,
            error: error
        });
    });
};
/**
 * Allows a user to create a Menedeley access token from the nodeSettings page
 */
CitationsFolderPickerViewModel.prototype.connectAccount = function() {
    var self = this;

    window.oauthComplete = function(res) {
        // Update view model based on response
        self.changeMessage(self.messages.CONNECT_ACCOUNT_SUCCESS(), 'text-success', 3000);
        self.updateAccounts(function() {
            $osf.postJSON(
                self.urls().importAuth, {
                    external_account_id: self.accounts()[0].id
                }
            ).then(self.onImportSuccess.bind(self), self.onImportError.bind(self));
        });
    };
    window.open(self.urls().auth);
};

CitationsFolderPickerViewModel.prototype.connectExistingAccount = function(account_id) {
    var self = this;

    $osf.postJSON(
        self.urls().importAuth, {
            external_account_id: account_id
        }
    ).then(self.onImportSuccess.bind(self), self.onImportError.bind(self));
};

/**
 * Send a PUT request to change the linked folder.
 */
CitationsFolderPickerViewModel.prototype.submitSettings = function() {
    var self = this;

    function onSubmitSuccess(response) {
        self.folder(self.selected().name);
        self.changeMessage(self.messages.SUBMIT_SETTINGS_SUCCESS(), 'text-success', 5000);
        self.cancelSelection();
    }

    function onSubmitError() {
        self.changeMessage(self.messages.SUBMIT_SETTINGS_ERROR(), 'text-danger');
    }

    $osf.putJSON(self.urls().config, {
            external_account_id: self.userAccountId(),
            external_list_id: self.selected().id
        })
        .done(onSubmitSuccess)
        .fail(onSubmitError);
};

/**
 * Must be used to update radio buttons and knockout view model simultaneously
 */
CitationsFolderPickerViewModel.prototype.cancelSelection = function() {
    this.selected(null);
};

/** Pop up a confirmation to deauthorize addon from this node.
 *  Send DELETE request if confirmed.
 */
CitationsFolderPickerViewModel.prototype.deauthorize = function() {
    var self = this;

    /**
     * Send DELETE request to deauthorize this node.
     */
    function sendDeauth() {
        var request = $.ajax({
            url: self.urls().deauthorize,
            type: 'DELETE'
        });

        request.done(function() {
            // Update observables
            self.nodeHasAuth(false);
            self.cancelSelection();
            self.currentDisplay(null);
            self.changeMessage(self.messages.DEAUTHORIZE_SUCCESS(), 'text-warning', 3000);
        });

        request.fail(function(xhr, textStatus, error) {
            self.changeMessage(self.messages.DEAUTHORIZE_FAIL(), 'text-danger');
            Raven.captureMessage('Could not deauthorize ' + self.properName + ' account from node', {
                url: self.urls().deauthorize,
                textStatus: textStatus,
                error: error
            });
        });

        return request;
    }

    bootbox.confirm({
        title: 'Deauthorize ' + self.properName + '?',
        message: self.messages.CONFIRM_DEAUTH(),
        callback: function(confirmed) {
            if (confirmed) {
                return sendDeauth();
            }
        }
    });
};

// Callback for when PUT request to import user access token
CitationsFolderPickerViewModel.prototype.onImportSuccess = function(response) {
    var self = this;

    var msg = response.message || 'Successfully imported access token from profile.';
    // Update view model based on response
    self.changeMessage(msg, 'text-success', 3000);
    self.updateFromData(response.result);
    self.activatePicker();
};

CitationsFolderPickerViewModel.prototype.onImportError = function(xhr, textStatus, error) {
    var self = this;

    self.changeMessage(self.messages.TOKEN_IMPORT_ERROR(), 'text-danger');
    Raven.captureMessage('Failed to import ' + self.properName + ' access token', {
        url: self.urls().importAuth,
        textStatus: textStatus,
        error: error
    });
};

/**
 * Send PUT request to import access token from user profile.
 */
CitationsFolderPickerViewModel.prototype.importAuth = function() {
    var self = this;

    self.updateAccounts(function() {
        if (self.accounts().length > 1) {
            bootbox.prompt({
                title: 'Choose ' + self.properName + ' Access Token to Import',
                inputType: 'select',
                inputOptions: ko.utils.arrayMap(
                    self.accounts(),
                    function(item) {
                        return {
                            text: item.name,
                            value: item.id
                        };
                    }
                ),
                value: self.accounts()[0].id,
                callback: (self.connectExistingAccount.bind(self))
            });
        } else {
            bootbox.confirm({
                title: 'Import ' + self.properName + ' Access Token?',
                message: self.messages.CONFIRM_AUTH(),
                callback: function(confirmed) {
                    if (confirmed) {
                        self.connectExistingAccount.call(self, (self.accounts()[0].id));
                    }
                }
            });
        }
    });
};

/**
 * Activates the HGrid folder picker.
 */
CitationsFolderPickerViewModel.prototype.activatePicker = function() {
    var self = this;

    /** Callback for chooseFolder action.
     *   Just changes the ViewModel's self.selected observable to the selected
     *   folder.
     */
    function onPickFolder(evt, item) {
        evt.preventDefault();
        self.selected({
            name: item.data.name,
            id: item.data.id
        });
        return false; // Prevent event propagation
    }

    self.currentDisplay(self.PICKER);
    // Only load folders if they haven't already been requested
    if (!self.loadedFolders()) {
        // Show loading indicator
        self.loading(true);
        $(self.folderPicker).folderpicker({
            onPickFolder: onPickFolder,
            initialFolderPath: '',
            // Fetch folders with AJAX
            filesData: self.urls().folders,
            // Lazy-load each folder's contents
            // Each row stores its url for fetching the folders it contains
            resolveLazyloadUrl: function(item) {
                return self.urls().folders + item.data.id + '/?view=folders';
            },
            lazyLoadPreprocess: function(data) {
                return data.contents.filter(function(item) {
                    return item.kind === 'folder';
                });
            },
            oddEvenClass: {
                odd: 'addon-folderpicker-odd',
                even: 'addon-folderpicker-even'
            },
            ajaxOptions: {
                error: function(xhr, textStatus, error) {
                    self.loading(false);
                    self.changeMessage(self.messages.CONNECT_ERROR(), 'text-warning');
                    Raven.captureMessage('Could not GET get ' + self.properName + ' contents.', {
                        textStatus: textStatus,
                        error: error
                    });
                }
            },
            folderPickerOnload: function() {
                // Hide loading indicator
                self.loading(false);
            }
        });
    }
};

/**
 * Toggles the visibility of the folder picker.
 */
CitationsFolderPickerViewModel.prototype.togglePicker = function() {
    var self = this;

    // Toggle visibility of folder picker
    var shown = self.currentDisplay() === self.PICKER;
    if (!shown) {
        self.currentDisplay(self.PICKER);
        self.activatePicker();
    } else {
        self.currentDisplay(null);
        // Clear selection
        self.cancelSelection();
    }
};

module.exports = CitationsFolderPickerViewModel;
<|endoftext|>


<commit_before>/*
 * nwjs.js
 * Fabian Irsara
 * Copyright 2015, Licensed GPL & MIT
 */
define(['./base', '../API'], function(fileSystem, API){
  return function(){
    if (!! window.nodeRequire) {
      var nw = nodeRequire('nw.gui');
      var fs = nodeRequire('fs');
      var request = nodeRequire('request');
      var mkdirp = nodeRequire('mkdirp');
      var checksum = nodeRequire('checksum');

      fileSystem.dataPath = nw.App.dataPath + '/' + fileSystem.dataSubFolder + '/';
    }

    fileSystem.writeFile = function(filename, data, callback, errorCallback){
      // correct file path
      filename = fileSystem.correctLocalFilePath(filename);

      // get directory from path
      var directory = fileSystem.getFolder(filename);

      // first ensure the directory that should be written to exists
      fileSystem.mkdir(directory, 0744, function(){
        // write data to the file
        fs.writeFile(filename, data, function(err){
          // process result
          if (err) {
            if (errorCallback) {
              errorCallback(err);
            }
          } else {
            if (callback) {
              callback();
            }
          }
        });
      });
    };

    fileSystem.readFile = function(filename, callback, errorCallback){
      // correct file path
      filename = fileSystem.correctLocalFilePath(filename);

      // check if file exists first
      fs.exists(filename, function(exists){
        if (exists) {
          // if it exists: read file
          fs.readFile(filename, function(err, data){
            if (err) {
              errorCallback(err);
            } else {
              if (callback) {
                callback(data.toString());
              }
            }
          });
        } else {
          if (errorCallback) {
            errorCallback();
          }
        }
      });
    };

    fileSystem.readFileBinary = function(filename, callback, errorCallback){
      // correct file path
      filename = fileSystem.correctLocalFilePath(filename);

      // check if file exists first
      fs.exists(filename, function(exists){
        if (exists) {
          // if it exists: read file
          fs.readFile(filename, function(err, data){
            if (err) {
              errorCallback(err);
            } else {
              if (callback) {
                callback(data);
              }
            }
          });
        } else {
          if (errorCallback) {
            errorCallback();
          }
        }
      });
    };

    fileSystem.pipe = function(options){
      // correct file path
      options.local = fileSystem.correctLocalFilePath(options.local);

      // get directory out of local path
      var directory = fileSystem.getFolder(options.local);

      // otherwise ensure local directory exists
      fileSystem.mkdir(directory, 0744, function(){
        // create a temporary file stream (move file to real path when finished correctly)
        var file = fs.createWriteStream(options.local + '_tmp');

        // get request object
        var r = fileSystem.request({url: options.remote, method: options.method, data: options.data});

        // store total and loaded bytes and call progress callback on new data
        var totalBytes = 0;
        var loadedBytes = 0;
        var progress = 0;

        // track if request status is 200
        var fileWasFound = false;

        // store basic request response
        r.on('response', function(response){
          totalBytes = response.headers['content-length'];
          if (response.statusCode === 200) {
            fileWasFound = true;
          }
        });

        // if it has a progress handler
        if (options.progress) {
          // call progress on new data
          r.on('data', function(chunk){
            loadedBytes += chunk.length;
            progress = loadedBytes / totalBytes;
            options.progress(progress);
          });
        }

        r.on('error', function(err){
          if (options.error) {
            options.error(err);
          }

          // TODO: this somehow creates errors when trying to rename file!!!
          // fileSystem.unlink(options.local + '_tmp');
        });

        // when downloading has finished
        r.on('end', function(){
          // if file was found on remote
          if (fileWasFound) {
            // rename temporary file accordingly
            fs.rename(options.local + '_tmp', options.local, function(err){
              // if there was an error while renaming
              if (err) {
                // call error callback
                if (options.error) {
                  options.error(err);
                }
              } else {
                // otherwise: finally call success
                if (options.success) {
                  options.success(options.local);
                }
              }
            });
          } else {
            // if file was not found on remote: call error callback
            if (options.error) {
              options.error();
            }
          }
        });

        // if there was an error while writing the file
        file.on('error', function(err){
          // call error callback
          if (options.error) {
            options.error(err);
          }
        });

        // pipe file to request
        r.pipe(file);
      });
    };

    fileSystem.request = function(options, callback, errorCallback){
      if (typeof options === 'string') {
        options = {url: options};
      }

      if (callback && ! options.success) options.success = callback;
      if (errorCallback && ! options.error) options.error = errorCallback;

      var requestOptions = {url: options.url};

      if (options.url.indexOf(API.endpoint) >= 0) {
        var token = API.get('token');
        if (token && token.length > 0) {
          requestOptions.auth = {bearer: token};
        }
      }

      // pass in request method (GET, POST, PUT, DELETE)
      if (options.method) {
        requestOptions.method = options.method;
      }

      // pass in request data if set
      if (options.data) {
        requestOptions.formData = options.data;
      }

      var success = null, r;

      if (options.success) {
        success = function(err, httpResponse, body){
          if (fileWasFound) {
            if (options.success) {
              options.success(body);
            }
          }
        };

        // request remote url
        r = request(requestOptions, success);
      } else {
        r = request(requestOptions);
      }

      // track if request status is 200
      var fileWasFound = false;

      // store basic request response
      r.on('response', function(response){
        if (response.statusCode === 200) {
          fileWasFound = true;
        }
      });

      if (options.error) {
        r.on('error', options.error);
      }

      return r;
    };

    fileSystem.readdir = function(path, callback){
      // correct file path
      path = fileSystem.correctLocalFilePath(path);

      // check if directory exists first
      fs.exists(path, function(exists){
        // return results. if any error is encountered simply return an empty array
        if (exists) {
          fs.readdir(path, function(err, files){
            if (err) {
              if (callback) {
                callback([]);
              }
            } else {
              if (callback) {
                callback(files);
              }
            }
          });
        } else {
          if (callback) {
            callback([]);
          }
        }
      });
    };

    fileSystem.exists = function(path, callback){
      // correct file path
      path = fileSystem.correctLocalFilePath(path);

      // check if exists
      fs.exists(path, function(exists){
        // return result
        if (callback) {
          callback(exists);
        }
      });
    };

    fileSystem.rmdir = function(path, callback){
      // correct file path
      path = fileSystem.correctLocalFilePath(path);

      // if directory exists
      fs.exists(path, function(exists){
        if (exists) {
          // read it
          fs.readdir(path, function(err, files){
            if (err) {
              // for errors just stop
              if (callback) {
                // Pass the error on to callback
                callback(err, []);
              }

              return;
            }

            // track how many files and directories got removed
            var wait = files.length;
            var count = 0;
            var folderDone = function(err){
              count++;

              // If we cleaned out all the files, continue
              if (count >= wait || err) {
                fs.rmdir(path, callback);
              }
            };

            // Empty directory to bail early
            if (! wait) {
              folderDone();
              return;
            }

            // Remove one or more trailing slash to keep from doubling up
            path = path.replace(/\/+$/, '');

            // loop through files
            files.forEach(function(file){
              // correct current path
              var currentPath = path + '/' + file;

              // get file status
              fs.lstat(currentPath, function(err, stats){
                if (err) {
                  if (callback) {
                    callback(err, []);
                  }

                  return;
                }

                // if it is a directory
                if (stats.isDirectory()) {
                  // remove it recursively
                  fileSystem.rmdir(currentPath, folderDone);
                } else {
                  // otherwise just unlink the file
                  fs.unlink(currentPath, folderDone);
                }
              });
            });
          });
        } else {
          // if folder does not exists
          if (callback) {
            // just call the callback
            callback();
          }
        }
      });
    };

    fileSystem.mkdir = function(path, mode, callback){
      if (path + '/' === fileSystem.dataPath) {
        if (callback) {
          callback();
        }

        return;
      }

      // correct file path
      path = fileSystem.correctLocalFilePath(path);

      mkdirp(path, mode, callback);
    };

    fileSystem.unlink = function(path, callback){
      // correct file path
      path = fileSystem.correctLocalFilePath(path);

      fs.exists(path, function(exists){
        if (exists) {
          fs.lstat(path, function(err, stats){
            if (err) {
              if (callback) callback();
              return;
            }

            // if it is a directory
            if (stats.isDirectory()) {
              // remove it recursively
              fileSystem.rmdir(path, callback);
            } else {
              // otherwise just unlink the file
              fs.unlink(path, callback);
            }
          });
        } else {
          if (callback) callback();
        }
      });
    };

    fileSystem.rename = function(path, newPath, callback){
      // correct file path
      path = fileSystem.correctLocalFilePath(path);
      newPath = fileSystem.correctLocalFilePath(newPath);

      fs.exists(path, function(exists){
        if (exists) {
          fs.rename(path, newPath, callback);
        } else {
          if (callback) callback();
        }
      });
    };

    fileSystem.checksum = function(path, callback){
      // correct file path
      path = fileSystem.correctLocalFilePath(path);

      checksum.file(path, {algorithm: 'md5'}, function(err, sum) {
        if (err) {
          if (callback) {
            callback('');
          }
        } else {
          if (callback) {
            callback(sum);
          }
        }
      });
    };
  };
});
<commit_msg>catch multiple error throwing, only throw once<commit_after>/*
 * nwjs.js
 * Fabian Irsara
 * Copyright 2015, Licensed GPL & MIT
 */
define(['./base', '../API'], function(fileSystem, API){
  return function(){
    if (!! window.nodeRequire) {
      var nw = nodeRequire('nw.gui');
      var fs = nodeRequire('fs');
      var request = nodeRequire('request');
      var mkdirp = nodeRequire('mkdirp');
      var checksum = nodeRequire('checksum');

      fileSystem.dataPath = nw.App.dataPath + '/' + fileSystem.dataSubFolder + '/';
    }

    fileSystem.writeFile = function(filename, data, callback, errorCallback){
      // correct file path
      filename = fileSystem.correctLocalFilePath(filename);

      // get directory from path
      var directory = fileSystem.getFolder(filename);

      // first ensure the directory that should be written to exists
      fileSystem.mkdir(directory, 0744, function(){
        // write data to the file
        fs.writeFile(filename, data, function(err){
          // process result
          if (err) {
            if (errorCallback) {
              errorCallback(err);
            }
          } else {
            if (callback) {
              callback();
            }
          }
        });
      });
    };

    fileSystem.readFile = function(filename, callback, errorCallback){
      // correct file path
      filename = fileSystem.correctLocalFilePath(filename);

      // check if file exists first
      fs.exists(filename, function(exists){
        if (exists) {
          // if it exists: read file
          fs.readFile(filename, function(err, data){
            if (err) {
              errorCallback(err);
            } else {
              if (callback) {
                callback(data.toString());
              }
            }
          });
        } else {
          if (errorCallback) {
            errorCallback();
          }
        }
      });
    };

    fileSystem.readFileBinary = function(filename, callback, errorCallback){
      // correct file path
      filename = fileSystem.correctLocalFilePath(filename);

      // check if file exists first
      fs.exists(filename, function(exists){
        if (exists) {
          // if it exists: read file
          fs.readFile(filename, function(err, data){
            if (err) {
              errorCallback(err);
            } else {
              if (callback) {
                callback(data);
              }
            }
          });
        } else {
          if (errorCallback) {
            errorCallback();
          }
        }
      });
    };

    fileSystem.pipe = function(options){
      // correct file path
      options.local = fileSystem.correctLocalFilePath(options.local);

      // get directory out of local path
      var directory = fileSystem.getFolder(options.local);

      // otherwise ensure local directory exists
      fileSystem.mkdir(directory, 0744, function(){
        // create a temporary file stream (move file to real path when finished correctly)
        var file = fs.createWriteStream(options.local + '_tmp');

        // get request object
        var r = fileSystem.request({url: options.remote, method: options.method, data: options.data});

        // store total and loaded bytes and call progress callback on new data
        var totalBytes = 0;
        var loadedBytes = 0;
        var progress = 0;

        // track if request status is 200
        var fileWasFound = false;
        var didThrowError = false;

        // store basic request response
        r.on('response', function(response){
          totalBytes = response.headers['content-length'];
          if (response.statusCode === 200) {
            fileWasFound = true;
          }
        });

        // if it has a progress handler
        if (options.progress) {
          // call progress on new data
          r.on('data', function(chunk){
            loadedBytes += chunk.length;
            progress = loadedBytes / totalBytes;
            options.progress(progress);
          });
        }

        r.on('error', function(err){
          if (options.error && ! didThrowError) {
            didThrowError = true;
            options.error(err);
          }

          // TODO: this somehow creates errors when trying to rename file!!!
          // fileSystem.unlink(options.local + '_tmp');
        });

        // when downloading has finished
        r.on('end', function(){
          // if file was found on remote
          if (fileWasFound) {
            // rename temporary file accordingly
            fs.rename(options.local + '_tmp', options.local, function(err){
              // if there was an error while renaming
              if (err) {
                // call error callback
                if (options.error && ! didThrowError) {
                  didThrowError = true;
                  options.error(err);
                }
              } else {
                // otherwise: finally call success
                if (options.success) {
                  options.success(options.local);
                }
              }
            });
          } else {
            // if file was not found on remote: call error callback
            if (options.error && ! didThrowError) {
              didThrowError = true;
              options.error();
            }
          }
        });

        // if there was an error while writing the file
        file.on('error', function(err){
          // call error callback
          if (options.error && ! didThrowError) {
            didThrowError = true;
            options.error(err);
          }
        });

        // pipe file to request
        r.pipe(file);
      });
    };

    fileSystem.request = function(options, callback, errorCallback){
      if (typeof options === 'string') {
        options = {url: options};
      }

      if (callback && ! options.success) options.success = callback;
      if (errorCallback && ! options.error) options.error = errorCallback;

      var requestOptions = {url: options.url};

      if (options.url.indexOf(API.endpoint) >= 0) {
        var token = API.get('token');
        if (token && token.length > 0) {
          requestOptions.auth = {bearer: token};
        }
      }

      // pass in request method (GET, POST, PUT, DELETE)
      if (options.method) {
        requestOptions.method = options.method;
      }

      // pass in request data if set
      if (options.data) {
        requestOptions.formData = options.data;
      }

      var success = null, r;

      if (options.success) {
        success = function(err, httpResponse, body){
          if (fileWasFound) {
            if (options.success) {
              options.success(body);
            }
          }
        };

        // request remote url
        r = request(requestOptions, success);
      } else {
        r = request(requestOptions);
      }

      // track if request status is 200
      var fileWasFound = false;

      // store basic request response
      r.on('response', function(response){
        if (response.statusCode === 200) {
          fileWasFound = true;
        }
      });

      if (options.error) {
        r.on('error', options.error);
      }

      return r;
    };

    fileSystem.readdir = function(path, callback){
      // correct file path
      path = fileSystem.correctLocalFilePath(path);

      // check if directory exists first
      fs.exists(path, function(exists){
        // return results. if any error is encountered simply return an empty array
        if (exists) {
          fs.readdir(path, function(err, files){
            if (err) {
              if (callback) {
                callback([]);
              }
            } else {
              if (callback) {
                callback(files);
              }
            }
          });
        } else {
          if (callback) {
            callback([]);
          }
        }
      });
    };

    fileSystem.exists = function(path, callback){
      // correct file path
      path = fileSystem.correctLocalFilePath(path);

      // check if exists
      fs.exists(path, function(exists){
        // return result
        if (callback) {
          callback(exists);
        }
      });
    };

    fileSystem.rmdir = function(path, callback){
      // correct file path
      path = fileSystem.correctLocalFilePath(path);

      // if directory exists
      fs.exists(path, function(exists){
        if (exists) {
          // read it
          fs.readdir(path, function(err, files){
            if (err) {
              // for errors just stop
              if (callback) {
                // Pass the error on to callback
                callback(err, []);
              }

              return;
            }

            // track how many files and directories got removed
            var wait = files.length;
            var count = 0;
            var folderDone = function(err){
              count++;

              // If we cleaned out all the files, continue
              if (count >= wait || err) {
                fs.rmdir(path, callback);
              }
            };

            // Empty directory to bail early
            if (! wait) {
              folderDone();
              return;
            }

            // Remove one or more trailing slash to keep from doubling up
            path = path.replace(/\/+$/, '');

            // loop through files
            files.forEach(function(file){
              // correct current path
              var currentPath = path + '/' + file;

              // get file status
              fs.lstat(currentPath, function(err, stats){
                if (err) {
                  if (callback) {
                    callback(err, []);
                  }

                  return;
                }

                // if it is a directory
                if (stats.isDirectory()) {
                  // remove it recursively
                  fileSystem.rmdir(currentPath, folderDone);
                } else {
                  // otherwise just unlink the file
                  fs.unlink(currentPath, folderDone);
                }
              });
            });
          });
        } else {
          // if folder does not exists
          if (callback) {
            // just call the callback
            callback();
          }
        }
      });
    };

    fileSystem.mkdir = function(path, mode, callback){
      if (path + '/' === fileSystem.dataPath) {
        if (callback) {
          callback();
        }

        return;
      }

      // correct file path
      path = fileSystem.correctLocalFilePath(path);

      mkdirp(path, mode, callback);
    };

    fileSystem.unlink = function(path, callback){
      // correct file path
      path = fileSystem.correctLocalFilePath(path);

      fs.exists(path, function(exists){
        if (exists) {
          fs.lstat(path, function(err, stats){
            if (err) {
              if (callback) callback();
              return;
            }

            // if it is a directory
            if (stats.isDirectory()) {
              // remove it recursively
              fileSystem.rmdir(path, callback);
            } else {
              // otherwise just unlink the file
              fs.unlink(path, callback);
            }
          });
        } else {
          if (callback) callback();
        }
      });
    };

    fileSystem.rename = function(path, newPath, callback){
      // correct file path
      path = fileSystem.correctLocalFilePath(path);
      newPath = fileSystem.correctLocalFilePath(newPath);

      fs.exists(path, function(exists){
        if (exists) {
          fs.rename(path, newPath, callback);
        } else {
          if (callback) callback();
        }
      });
    };

    fileSystem.checksum = function(path, callback){
      // correct file path
      path = fileSystem.correctLocalFilePath(path);

      checksum.file(path, {algorithm: 'md5'}, function(err, sum) {
        if (err) {
          if (callback) {
            callback('');
          }
        } else {
          if (callback) {
            callback(sum);
          }
        }
      });
    };
  };
});
<|endoftext|>


<commit_before>// the eslint "no-unused-expressions" rule is triggered by the `expect`
// assertions in the tests. So, in order to avoid problems with eslint, I'm
// disabling that rule in this file
//
/* eslint-disable no-unused-expressions */

import React from 'react';
import { shallow, render, mount } from 'enzyme';
import chai from 'chai';
import sinon from 'sinon';

import Form from '../Form';

const expect = chai.expect;

/* eslint-disable no-unused-vars */
const debug = (component) => {
    console.info('------------------------------------------------------------');
    console.info(component.debug());
    console.info('------------------------------------------------------------');
};
/* eslint-enable no-unused-vars */

/*

<Dropdown
    options={stateOptions}
    label="State/Province"
    value={this.props.form.state || ''}

    required
    description="The state or province"
    labelColumns={{ xs: 2, md: 3 }}
    dropdownColumns={{ xs: 10, md: 9 }}
    onChange={(value) =>
        this.props.updateField('state', value)
    }
    onValidation={(isValid, message) =>
        this.onValidation('state', isValid, message)
    }
/>

*/

/* *****************************************************************************
a Form component containing a Dropdown
    should include a <Form.Dropdown> as a child
    should be a select.form-control
    should have the options specified in the `options` prop
    should not include a label if none was specified
    should include a label if one was specified
    should select a specific value when value is set
    should select the first item when value is not set
    should select the first item when value is set to an invalid value
*/
describe('a Form component containing a Dropdown', () => {

    it('should include a <Form.Dropdown> as a child', () => {
        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
        ];

        const component = shallow(
            <Form>
                <Form.Dropdown options={options} />
            </Form>
        );

        expect(component.find(Form.Dropdown)).to.have.length(1);
    });

    it('should be a select.form-control', () => {
        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
        ];

        const component = render(
            <Form>
                <Form.Dropdown options={options} />
            </Form>
        );

        expect(component.find('select.form-control')).to.have.length(1);
    });

    it('should have the options specified in the `options` prop', () => {

        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
        ];

        const component = render(
            <Form>
                <Form.Dropdown options={options} />
            </Form>
        );

        expect(component.find('option')).to.have.length(options.length, 'options count');

        options.forEach((opt) => {
            expect(component.find(`option[value="${opt.value}"]`)).to.have.length(1, `value=${opt.value}`);
            expect(component.find(`option[value="${opt.value}"]`).text()).to.equal(opt.name, `name=${opt.name}`);

        });
    });

    it('should not include a label if none was specified', () => {
        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
        ];

        const component = render(
            <Form>
                <Form.Dropdown options={options} />
            </Form>
        );

        expect(component.find('label')).to.have.length(0);
    });

    it('should include a label if one was specified', () => {
        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
        ];

        const label = 'some label';

        const component = render(
            <Form>
                <Form.Dropdown options={options} label={label} />
            </Form>
        );

        expect(component.find('label')).to.have.length(1);
        expect(component.find('label').text()).to.equal(label);
    });

    it('should select a specific value when value is set', () => {
        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
            { value: '3', name: 'three' },
        ];

        const value = '2';

        const component = mount(
            <Form>
                <Form.Dropdown options={options} value={value} />
            </Form>
        );

        expect(component.find('select').props().value).to.equal(value);
    });

    it('should select the first item when value is not set', () => {
        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
            { value: '3', name: 'three' },
        ];

        const component = mount(
            <Form>
                <Form.Dropdown options={options} />
            </Form>
        );

        expect(component.find('select').props().value).to.equal(options[0].value);
    });

    it('should select the first item when value is set to an invalid value', () => {
        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
            { value: '3', name: 'three' },
        ];

        const value = '9';

        const component = mount(
            <Form>
                <Form.Dropdown options={options} value={value} />
            </Form>
        );

        expect(component.find('select').props().value).to.equal(options[0].value);
    });

});

/* *****************************************************************************
the Dropdown element
    should call onChange on value change to a valid value
*/
describe('the Dropdown element', () => {

    it('should call onChange on value change to a valid value', () => {

        const onChange = sinon.spy();

        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
            { value: '3', name: 'three' },
        ];

        const initialValue = '2';

        const finalValue = '3';

        const component = mount(
            <Form>
                <Form.Dropdown options={options} value={initialValue} onChange={onChange} />
            </Form>
        );

        component.find('select').simulate('change', {
            target: { value: finalValue }
        });

        expect(onChange.callCount).to.equal(1);
        expect(onChange.calledWith(finalValue));
    });
});
<commit_msg>updated notes<commit_after>// the eslint "no-unused-expressions" rule is triggered by the `expect`
// assertions in the tests. So, in order to avoid problems with eslint, I'm
// disabling that rule in this file
//
/* eslint-disable no-unused-expressions */

import React from 'react';
import { shallow, render, mount } from 'enzyme';
import chai from 'chai';
import sinon from 'sinon';

import Form from '../Form';

const expect = chai.expect;

/* eslint-disable no-unused-vars */
const debug = (component) => {
    console.info('------------------------------------------------------------');
    console.info(component.debug());
    console.info('------------------------------------------------------------');
};
/* eslint-enable no-unused-vars */

/*

<Dropdown
    options={stateOptions}
    label="State/Province"
    value={this.props.form.state || ''}

    required
    description="The state or province"
    labelColumns={{ xs: 2, md: 3 }}
    dropdownColumns={{ xs: 10, md: 9 }}
    onChange={(value) =>
        this.props.updateField('state', value)
    }
    onValidation={(isValid, message) =>
        this.onValidation('state', isValid, message)
    }
/>

*/

/* *****************************************************************************
a Form component containing a Dropdown
    should include a <Form.Dropdown> as a child
    should be a select.form-control
    should have the options specified in the `options` prop
    should not include a label if none was specified
    should include a label if one was specified
    should select a specific value when value is set
    should select the first item when value is not set
    should select the first item when value is set to an invalid value
*/
describe('a Form component containing a Dropdown', () => {

    it('should include a <Form.Dropdown> as a child', () => {
        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
        ];

        const component = shallow(
            <Form>
                <Form.Dropdown options={options} />
            </Form>
        );

        expect(component.find(Form.Dropdown)).to.have.length(1);
    });

    it('should be a select.form-control', () => {
        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
        ];

        const component = render(
            <Form>
                <Form.Dropdown options={options} />
            </Form>
        );

        expect(component.find('select.form-control')).to.have.length(1);
    });

    it('should have the options specified in the `options` prop', () => {

        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
        ];

        const component = render(
            <Form>
                <Form.Dropdown options={options} />
            </Form>
        );

        expect(component.find('option')).to.have.length(options.length, 'options count');

        options.forEach((opt) => {
            expect(component.find(`option[value="${opt.value}"]`)).to.have.length(1, `value=${opt.value}`);
            expect(component.find(`option[value="${opt.value}"]`).text()).to.equal(opt.name, `name=${opt.name}`);

        });
    });

    it('should not include a label if none was specified', () => {
        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
        ];

        const component = render(
            <Form>
                <Form.Dropdown options={options} />
            </Form>
        );

        expect(component.find('label')).to.have.length(0);
    });

    it('should include a label if one was specified', () => {
        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
        ];

        const label = 'some label';

        const component = render(
            <Form>
                <Form.Dropdown options={options} label={label} />
            </Form>
        );

        expect(component.find('label')).to.have.length(1);
        expect(component.find('label').text()).to.equal(label);
    });

    it('should select a specific value when value is set', () => {
        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
            { value: '3', name: 'three' },
        ];

        const value = '2';

        const component = mount(
            <Form>
                <Form.Dropdown options={options} value={value} />
            </Form>
        );

        expect(component.find('select').props().value).to.equal(value);
    });

    it('should select the first item when value is not set', () => {
        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
            { value: '3', name: 'three' },
        ];

        const component = mount(
            <Form>
                <Form.Dropdown options={options} />
            </Form>
        );

        expect(component.find('select').props().value).to.equal(options[0].value);
    });

    it('should select the first item when value is set to an invalid value', () => {
        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
            { value: '3', name: 'three' },
        ];

        const value = '9';

        const component = mount(
            <Form>
                <Form.Dropdown options={options} value={value} />
            </Form>
        );

        expect(component.find('select').props().value).to.equal(options[0].value);
    });

});

/* *****************************************************************************
the Dropdown element
    should call onChange with the correct value on value change to a valid value
    should call onChange with the first option on value change to a invalid value
*/
describe('the Dropdown element', () => {

    it('should call onChange with the correct value on value change to a valid value', () => {

        const onChange = sinon.spy();

        const options = [
            { value: '1', name: 'one' },
            { value: '2', name: 'two' },
            { value: '3', name: 'three' },
        ];

        const initialValue = '2';

        const finalValue = '3';

        const component = mount(
            <Form>
                <Form.Dropdown options={options} value={initialValue} onChange={onChange} />
            </Form>
        );

        component.find('select').simulate('change', {
            target: { value: finalValue }
        });

        expect(onChange.callCount).to.equal(1);
        expect(onChange.calledWith(finalValue));
    });
});
<|endoftext|>


<commit_before>"use strict";

var googleAnalyticsController = function($scope) {
    $scope.charts =
        [
            {
                reportType: "ga",
                query: {
                    metrics: "ga:sessions",
                    dimensions: "ga:date",
                    "start-date": "30daysAgo",
                    "end-date": "yesterday"
                },
                chart: {
                    container: "chart-container-1",
                    type: "LINE",
                    options: {
                        width: "100%"
                    }
                }
            },
            {
                reportType: "ga",
                query: {
                    metrics: "ga:sessions",
                    dimensions: "ga:browser",
                    "start-date": "30daysAgo",
                    "end-date": "yesterday"
                },
                chart: {
                    container: "chart-container-2",
                    type: "PIE",
                    options: {
                        width: "100%",
                        is3D: true,
                        title: "Browser Usage"
                    }
                }
            }
        ];

    $scope.extraChart = {
        reportType: "ga",
        query: {
            metrics: "ga:sessions",
            dimensions: "ga:date",
            "start-date": "30daysAgo",
            "end-date": "yesterday",
            ids: "ga:81197147" // put your viewID here
        },
        chart: {
            container: "chart-container-3",
            type: "LINE",
            options: {
                width: "100%"
            }
        }
    };

    $scope.queries = [{
        query: {
            ids: "ga:81197147",  // put your viewID here
            metrics: "ga:sessions",
            dimensions: "ga:city"
        }
    }];

    // if a report is ready
    $scope.$on("$gaReportSuccess", function (e, report, element) {
        console.log(report, element);
    });
};

var prototypeName = "analytics-dashboard";
var controllerName = "DashboardController";
var dashModuleName = "DashboardModule";

var configuration = function($stateProvider, $urlRouterProvider, $locationProvider) {
    $urlRouterProvider.otherwise("/")
    // we want the fully functional html5 mode (also removes the # -sign from the URL)
    $locationProvider.html5Mode(true);

    // defining the used states -- can we survive with just one state for the dashboard?
    $stateProvider.state("dashboard", {
        url: "/",
        templateUrl: "public/html/dashboard.html",
        controller: controllerName
    });
};

angular
    .module(dashModuleName, [ ])
    .controller(controllerName, googleAnalyticsController);

angular
    .module(prototypeName, [
        "ui.router",
        "ui.bootstrap",
        "btford.socket-io",
        "ngAnalytics",
        dashModuleName
    ])
    .run(["ngAnalyticsService", function(ngAnalyticsService) {
        // injecting the service with credentials
        ngAnalyticsService.setClientId("332578592932-3c2fb27m0lr5dljnv8f4o9nvhkglqr56.apps.googleusercontent.com")
    }])
    .config(configuration);<commit_msg>Client side browserify modules started on app<commit_after>
"use strict";

//var angularBaseConfiguration = require("./config/analytics-dashboard");

var angularDependencyMapping = [
    // assumes that all the bootstrapping functions have .name and required functions for them to work with angular
  //  require("./dashboard-controllers/bootstrap")
];

/**
 * Entry point for the application. Hotwires everything in place and basically makes sure the application is ready for
 * active use.
 */
var bootstrapApplication = function() {
    console.log("Application bootstrapped");
};

// starting the 'module' names with upper case to distinguish them from the rest
var Application = {
    bootstrap: bootstrapApplication
};

// start the application
Application.bootstrap();

module.exports = Application;
<|endoftext|>


<commit_before>import Vue from 'vue'
import Router from 'vue-router'
import App from './App'

import Wechat from './Wechat'
import Yi from './yi'
import Icon from './demos/Icon'
import Switch from './demos/Switch'
import Radio from './demos/Radio'
import Input from './demos/Input'
import Number from './demos/Number'
import Checklist from './demos/Checklist'
import Selector from './demos/Selector'
import Tip from './demos/Tip'
import XButton from './demos/X-button'
import Textarea from './demos/Textarea'
import Flexbox from './demos/Flexbox'
import Tab from './demos/Tab'
import Swiper from './demos/Swiper'
import Sticky from './demos/Sticky'
import Picker from './demos/Picker'
import Datetime from './demos/Datetime'
import Popup from './demos/Popup'
import Range from './demos/Range'
import Actionsheet from './demos/Actionsheet'
import Clocker from './demos/Clocker'
import Rater from './demos/Rater'
import PopupPicker from './demos/Popup-picker'
import Toast from './demos/Toast'
import Loading from './demos/Loading'
import Alert from './demos/Alert'
import Confirm from './demos/Confirm'
import Progress from './demos/Progress'
import XImg from './demos/XImg'
import XImgScroller from './demos/X-img-scroller'
import Onepx from './demos/1px'
import Orientation from './demos/Orientation'
import Shake from './demos/Shake'
import Cell from './demos/Cell'
import Demo from './demos/Demo'
import Emotion from './demos/Wechat-emotion'
import Search from './demos/Search'
import SearchStatic from './demos/Search-static'
import Donate from './demos/Donate'
import Thanks from './demos/Thanks'
import Spinner from './demos/Spinner'
import Calendar from './demos/Calendar'
import Circle from './demos/Circle'
import Countup from './demos/Countup'
import ColorPicker from './demos/Color-picker'
import Blur from './demos/Blur'
import Scroller from './demos/Scroller'
import Comment from './demos/Comment'
import Pulldown from './demos/Pulldown'
import Pullup from './demos/Pullup'
import PulldownPullup from './demos/PulldownPullup'
import Masker from './demos/Masker'
import Countdown from './demos/Countdown'
import XHeader from './demos/X-header'
import Inview from './demos/Inview'
import InlineCalendar from './demos/Inline-calendar'
import InlineCalendarStartDate from './demos/Inline-calendar-start-date'
import Checker from './demos/Checker'
import ScrollerFull from './demos/Scroller-full'
import NumberRoller from './demos/Number-roller'
import Timeline from './demos/Timeline'
import Step from './demos/Step'
import Tabbar from './demos/Tabbar'
import TabbarLink from './demos/TabbarLink'
import Panel from './demos/Panel'
import Reddot from './demos/Reddot'
import ButtonTab from './demos/Button-tab'
import ScrollerSwiper from './demos/Scroller-swiper'
import ScrollerHeader from './demos/Scroller-header'
import Qrcode from './demos/Qrcode'
import Badge from './demos/Badge'
import Close from './demos/Close'
import Dialog from './demos/Dialog'
import DateFormatter from './demos/Date-formatter'
import Card from './demos/Card'
import Previewer from './demos/Previewer'
import IconLoading from './demos/Icon-loading'
import Test from './demos/Test'
import Issue189 from './demos/Issue189'
import Issue461 from './demos/Issue461'
import Issue414 from './demos/Issue414'
import Divider from './demos/Divider'
import Fullpage from './components/fullpage/DemoBasic'
import Popover from './components/popover/DemoIndex'

// plugins
import Device from './plugins/device'
import DeviceDemo from './demos/Device'
import ToastPlugin from './plugins/toast'
import AlertPlugin from './plugins/alert'

const FastClick = require('fastclick')
FastClick.attach(document.body)

Vue.use(Router)
Vue.config.devtools = true

// $device
Vue.use(Device)
Vue.use(ToastPlugin)
Vue.use(AlertPlugin)

const router = new Router({
  transitionOnLoad: false
})

/**
* sync router params
*/
import { sync } from 'vuex-router-sync'
import store from './vuex/store'

let history = window.sessionStorage
history.clear()
let historyCount = history.getItem('count') * 1 || 0
history.setItem('/', 0)

/**
* sync router loading status
*/
const commit = store.commit || store.dispatch
router.beforeEach(({ to, from, next }) => {
  const toIndex = history.getItem(to.path)
  const fromIndex = history.getItem(from.path)
  if (toIndex) {
    if (toIndex > fromIndex) {
      commit('UPDATE_DIRECTION', 'forward')
    } else {
      commit('UPDATE_DIRECTION', 'reverse')
    }
  } else {
    ++historyCount
    history.setItem('count', historyCount)
    to.path !== '/' && history.setItem(to.path, historyCount)
    commit('UPDATE_DIRECTION', 'forward')
  }
  commit('UPDATE_LOADING', true)
  setTimeout(next, 50)
})
router.afterEach(() => {
  commit('UPDATE_LOADING', false)
})

sync(store, router)

router.map({
  '/': {
    component: function (resolve) {
      require(['./Home'], resolve)
    }
  },
  '/demo/wechat': {
    component: Wechat
  },
  '/demo/yi': {
    component: Yi
  },
  '/component/icon': {
    component: Icon
  },
  '/component/switch': {
    component: Switch
  },
  '/component/radio': {
    component: Radio
  },
  '/component/input': {
    component: Input
  },
  '/component/number': {
    component: Number
  },
  '/component/checklist': {
    component: Checklist
  },
  '/component/selector': {
    component: Selector
  },
  '/component/tip': {
    component: Tip
  },
  '/component/x-button': {
    component: XButton
  },
  '/component/textarea': {
    component: Textarea
  },
  '/component/flexbox': {
    component: Flexbox
  },
  '/component/tab': {
    component: Tab
  },
  '/component/swiper': {
    component: Swiper
  },
  '/component/sticky': {
    component: Sticky
  },
  '/component/picker': {
    component: Picker
  },
  '/component/datetime': {
    component: Datetime
  },
  '/component/popup': {
    component: Popup
  },
  '/component/range': {
    component: Range
  },
  '/component/actionsheet': {
    component: Actionsheet
  },
  '/component/clocker': {
    component: Clocker
  },
  '/component/rater': {
    component: Rater
  },
  '/component/popup-picker': {
    component: PopupPicker
  },
  '/component/address': {
    component: function (resolve) {
      require(['./demos/Address'], resolve)
    }
  },
  '/component/toast': {
    component: Toast
  },
  '/component/loading': {
    component: Loading
  },
  '/component/alert': {
    component: Alert
  },
  '/component/confirm': {
    component: Confirm
  },
  '/component/progress': {
    component: Progress
  },
  '/component/x-img': {
    component: XImg
  },
  '/component/x-img-scroller': {
    component: XImgScroller
  },
  '/component/1px': {
    component: Onepx
  },
  '/component/orientation': {
    component: Orientation
  },
  '/component/shake': {
    component: Shake
  },
  '/component/cell': {
    component: Cell
  },
  '/demo': {
    component: Demo
  },
  '/component/emotion': {
    component: Emotion
  },
  '/component/search': {
    component: Search
  },
  '/component/search-static': {
    component: SearchStatic
  },
  '/project/donate': {
    component: Donate
  },
  '/project/thanks': {
    component: Thanks
  },
  '/component/spinner': {
    component: Spinner
  },
  '/component/calendar': {
    component: Calendar
  },
  '/component/circle': {
    component: Circle
  },
  '/component/countup': {
    component: Countup
  },
  '/component/color-picker': {
    component: ColorPicker
  },
  '/component/blur': {
    component: Blur
  },
  '/component/scroller': {
    component: Scroller
  },
  '/component/comment': {
    component: Comment
  },
  '/component/pulldown': {
    component: Pulldown
  },
  '/component/pullup': {
    component: Pullup
  },
  '/component/pulldown-pullup': {
    component: PulldownPullup
  },
  '/component/masker': {
    component: Masker
  },
  '/component/countdown': {
    component: Countdown
  },
  '/component/inview': {
    component: Inview
  },
  '/component/x-header': {
    component: XHeader
  },
  '/component/inline-calendar': {
    component: InlineCalendar
  },
  '/component/inline-calendar-start-date': {
    component: InlineCalendarStartDate
  },
  '/component/checker': {
    component: Checker
  },
  '/component/scroller/full': {
    component: ScrollerFull
  },
  '/component/scroller/header': {
    component: ScrollerHeader
  },
  '/component/number-roller': {
    component: NumberRoller
  },
  '/component/timeline': {
    component: Timeline
  },
  '/component/step': {
    component: Step
  },
  '/component/tabbar': {
    component: Tabbar
  },
  '/component/tabbar-link': {
    component: TabbarLink
  },
  '/component/panel': {
    component: Panel
  },
  '/component/reddot': {
    component: Reddot
  },
  '/component/button-tab': {
    component: ButtonTab
  },
  '/component/scroller-swiper': {
    component: ScrollerSwiper
  },
  '/component/qrcode': {
    component: Qrcode
  },
  '/component/badge': {
    component: Badge
  },
  '/component/close': {
    component: Close
  },
  '/component/dialog': {
    component: Dialog
  },
  '/component/date-formatter': {
    component: DateFormatter
  },
  '/component/card': {
    component: Card
  },
  '/component/previewer': {
    component: Previewer
  },
  '/component/icon-loading': {
    component: IconLoading
  },
  '/plugin/device': {
    component: DeviceDemo
  },
  '/test': {
    component: Test
  },
  '/issue/189': {
    component: Issue189
  },
  '/issue/461': {
    component: Issue461
  },
  '/issue/414': {
    component: Issue414
  },
  '/component/divider': {
    component: Divider
  },
  '/component/fullpage': {
    component: Fullpage
  },
  '/component/popover': {
    component: Popover
  }
})

router.on('/component/center', {
  component: require('./demos/Center')
})

// save position for demo page
let demoScrollTop = 0
function saveDemoScrollTop () {
  demoScrollTop = window.scrollY
}

router.beforeEach(function (transition) {
  if (transition.to.fullPath !== '/demo') {
    window.removeEventListener('scroll', saveDemoScrollTop, false)
  }
  if (/\/http/.test(transition.to.path)) {
    let url = transition.to.path.split('http')[1]
    window.location.href = `http${url}`
  } else {
    if (/\/demo\/component\/\w+/.test(transition.to.path)) {
      router.go({
        replace: true,
        path: transition.to.path.replace('/demo', ''),
        append: false
      })
    } else {
      transition.next()
    }
  }
})

router.afterEach(function (transition) {
  if (transition.to.path !== '/demo') {
    window.scrollTo(0, 0)
  } else {
    window.removeEventListener('scroll', saveDemoScrollTop, false)
    // if from component page
    if (demoScrollTop && /component/.test(transition.from.path)) {
      setTimeout(function () {
        window.scrollTo(0, demoScrollTop)
      }, 100)
    }
    setTimeout(function () {
      window.addEventListener('scroll', saveDemoScrollTop, false)
    }, 1000)
  }
})

router.start(App, '#app')
<commit_msg>首页动画固定为forward<commit_after>import Vue from 'vue'
import Router from 'vue-router'
import App from './App'

import Wechat from './Wechat'
import Yi from './yi'
import Icon from './demos/Icon'
import Switch from './demos/Switch'
import Radio from './demos/Radio'
import Input from './demos/Input'
import Number from './demos/Number'
import Checklist from './demos/Checklist'
import Selector from './demos/Selector'
import Tip from './demos/Tip'
import XButton from './demos/X-button'
import Textarea from './demos/Textarea'
import Flexbox from './demos/Flexbox'
import Tab from './demos/Tab'
import Swiper from './demos/Swiper'
import Sticky from './demos/Sticky'
import Picker from './demos/Picker'
import Datetime from './demos/Datetime'
import Popup from './demos/Popup'
import Range from './demos/Range'
import Actionsheet from './demos/Actionsheet'
import Clocker from './demos/Clocker'
import Rater from './demos/Rater'
import PopupPicker from './demos/Popup-picker'
import Toast from './demos/Toast'
import Loading from './demos/Loading'
import Alert from './demos/Alert'
import Confirm from './demos/Confirm'
import Progress from './demos/Progress'
import XImg from './demos/XImg'
import XImgScroller from './demos/X-img-scroller'
import Onepx from './demos/1px'
import Orientation from './demos/Orientation'
import Shake from './demos/Shake'
import Cell from './demos/Cell'
import Demo from './demos/Demo'
import Emotion from './demos/Wechat-emotion'
import Search from './demos/Search'
import SearchStatic from './demos/Search-static'
import Donate from './demos/Donate'
import Thanks from './demos/Thanks'
import Spinner from './demos/Spinner'
import Calendar from './demos/Calendar'
import Circle from './demos/Circle'
import Countup from './demos/Countup'
import ColorPicker from './demos/Color-picker'
import Blur from './demos/Blur'
import Scroller from './demos/Scroller'
import Comment from './demos/Comment'
import Pulldown from './demos/Pulldown'
import Pullup from './demos/Pullup'
import PulldownPullup from './demos/PulldownPullup'
import Masker from './demos/Masker'
import Countdown from './demos/Countdown'
import XHeader from './demos/X-header'
import Inview from './demos/Inview'
import InlineCalendar from './demos/Inline-calendar'
import InlineCalendarStartDate from './demos/Inline-calendar-start-date'
import Checker from './demos/Checker'
import ScrollerFull from './demos/Scroller-full'
import NumberRoller from './demos/Number-roller'
import Timeline from './demos/Timeline'
import Step from './demos/Step'
import Tabbar from './demos/Tabbar'
import TabbarLink from './demos/TabbarLink'
import Panel from './demos/Panel'
import Reddot from './demos/Reddot'
import ButtonTab from './demos/Button-tab'
import ScrollerSwiper from './demos/Scroller-swiper'
import ScrollerHeader from './demos/Scroller-header'
import Qrcode from './demos/Qrcode'
import Badge from './demos/Badge'
import Close from './demos/Close'
import Dialog from './demos/Dialog'
import DateFormatter from './demos/Date-formatter'
import Card from './demos/Card'
import Previewer from './demos/Previewer'
import IconLoading from './demos/Icon-loading'
import Test from './demos/Test'
import Issue189 from './demos/Issue189'
import Issue461 from './demos/Issue461'
import Issue414 from './demos/Issue414'
import Divider from './demos/Divider'
import Fullpage from './components/fullpage/DemoBasic'
import Popover from './components/popover/DemoIndex'

// plugins
import Device from './plugins/device'
import DeviceDemo from './demos/Device'
import ToastPlugin from './plugins/toast'
import AlertPlugin from './plugins/alert'

const FastClick = require('fastclick')
FastClick.attach(document.body)

Vue.use(Router)
Vue.config.devtools = true

// $device
Vue.use(Device)
Vue.use(ToastPlugin)
Vue.use(AlertPlugin)

const router = new Router({
  transitionOnLoad: false
})

/**
* sync router params
*/
import { sync } from 'vuex-router-sync'
import store from './vuex/store'

let history = window.sessionStorage
history.clear()
let historyCount = history.getItem('count') * 1 || 0
history.setItem('/', 0)

/**
* sync router loading status
*/
const commit = store.commit || store.dispatch
router.beforeEach(({ to, from, next }) => {
  const toIndex = history.getItem(to.path)
  const fromIndex = history.getItem(from.path)
  if (toIndex) {
    if (toIndex > fromIndex || !fromIndex) {
      commit('UPDATE_DIRECTION', 'forward')
    } else {
      commit('UPDATE_DIRECTION', 'reverse')
    }
  } else {
    ++historyCount
    history.setItem('count', historyCount)
    to.path !== '/' && history.setItem(to.path, historyCount)
    commit('UPDATE_DIRECTION', 'forward')
  }
  commit('UPDATE_LOADING', true)
  setTimeout(next, 50)
})
router.afterEach(() => {
  commit('UPDATE_LOADING', false)
})

sync(store, router)

router.map({
  '/': {
    component: function (resolve) {
      require(['./Home'], resolve)
    }
  },
  '/demo/wechat': {
    component: Wechat
  },
  '/demo/yi': {
    component: Yi
  },
  '/component/icon': {
    component: Icon
  },
  '/component/switch': {
    component: Switch
  },
  '/component/radio': {
    component: Radio
  },
  '/component/input': {
    component: Input
  },
  '/component/number': {
    component: Number
  },
  '/component/checklist': {
    component: Checklist
  },
  '/component/selector': {
    component: Selector
  },
  '/component/tip': {
    component: Tip
  },
  '/component/x-button': {
    component: XButton
  },
  '/component/textarea': {
    component: Textarea
  },
  '/component/flexbox': {
    component: Flexbox
  },
  '/component/tab': {
    component: Tab
  },
  '/component/swiper': {
    component: Swiper
  },
  '/component/sticky': {
    component: Sticky
  },
  '/component/picker': {
    component: Picker
  },
  '/component/datetime': {
    component: Datetime
  },
  '/component/popup': {
    component: Popup
  },
  '/component/range': {
    component: Range
  },
  '/component/actionsheet': {
    component: Actionsheet
  },
  '/component/clocker': {
    component: Clocker
  },
  '/component/rater': {
    component: Rater
  },
  '/component/popup-picker': {
    component: PopupPicker
  },
  '/component/address': {
    component: function (resolve) {
      require(['./demos/Address'], resolve)
    }
  },
  '/component/toast': {
    component: Toast
  },
  '/component/loading': {
    component: Loading
  },
  '/component/alert': {
    component: Alert
  },
  '/component/confirm': {
    component: Confirm
  },
  '/component/progress': {
    component: Progress
  },
  '/component/x-img': {
    component: XImg
  },
  '/component/x-img-scroller': {
    component: XImgScroller
  },
  '/component/1px': {
    component: Onepx
  },
  '/component/orientation': {
    component: Orientation
  },
  '/component/shake': {
    component: Shake
  },
  '/component/cell': {
    component: Cell
  },
  '/demo': {
    component: Demo
  },
  '/component/emotion': {
    component: Emotion
  },
  '/component/search': {
    component: Search
  },
  '/component/search-static': {
    component: SearchStatic
  },
  '/project/donate': {
    component: Donate
  },
  '/project/thanks': {
    component: Thanks
  },
  '/component/spinner': {
    component: Spinner
  },
  '/component/calendar': {
    component: Calendar
  },
  '/component/circle': {
    component: Circle
  },
  '/component/countup': {
    component: Countup
  },
  '/component/color-picker': {
    component: ColorPicker
  },
  '/component/blur': {
    component: Blur
  },
  '/component/scroller': {
    component: Scroller
  },
  '/component/comment': {
    component: Comment
  },
  '/component/pulldown': {
    component: Pulldown
  },
  '/component/pullup': {
    component: Pullup
  },
  '/component/pulldown-pullup': {
    component: PulldownPullup
  },
  '/component/masker': {
    component: Masker
  },
  '/component/countdown': {
    component: Countdown
  },
  '/component/inview': {
    component: Inview
  },
  '/component/x-header': {
    component: XHeader
  },
  '/component/inline-calendar': {
    component: InlineCalendar
  },
  '/component/inline-calendar-start-date': {
    component: InlineCalendarStartDate
  },
  '/component/checker': {
    component: Checker
  },
  '/component/scroller/full': {
    component: ScrollerFull
  },
  '/component/scroller/header': {
    component: ScrollerHeader
  },
  '/component/number-roller': {
    component: NumberRoller
  },
  '/component/timeline': {
    component: Timeline
  },
  '/component/step': {
    component: Step
  },
  '/component/tabbar': {
    component: Tabbar
  },
  '/component/tabbar-link': {
    component: TabbarLink
  },
  '/component/panel': {
    component: Panel
  },
  '/component/reddot': {
    component: Reddot
  },
  '/component/button-tab': {
    component: ButtonTab
  },
  '/component/scroller-swiper': {
    component: ScrollerSwiper
  },
  '/component/qrcode': {
    component: Qrcode
  },
  '/component/badge': {
    component: Badge
  },
  '/component/close': {
    component: Close
  },
  '/component/dialog': {
    component: Dialog
  },
  '/component/date-formatter': {
    component: DateFormatter
  },
  '/component/card': {
    component: Card
  },
  '/component/previewer': {
    component: Previewer
  },
  '/component/icon-loading': {
    component: IconLoading
  },
  '/plugin/device': {
    component: DeviceDemo
  },
  '/test': {
    component: Test
  },
  '/issue/189': {
    component: Issue189
  },
  '/issue/461': {
    component: Issue461
  },
  '/issue/414': {
    component: Issue414
  },
  '/component/divider': {
    component: Divider
  },
  '/component/fullpage': {
    component: Fullpage
  },
  '/component/popover': {
    component: Popover
  }
})

router.on('/component/center', {
  component: require('./demos/Center')
})

// save position for demo page
let demoScrollTop = 0
function saveDemoScrollTop () {
  demoScrollTop = window.scrollY
}

router.beforeEach(function (transition) {
  if (transition.to.fullPath !== '/demo') {
    window.removeEventListener('scroll', saveDemoScrollTop, false)
  }
  if (/\/http/.test(transition.to.path)) {
    let url = transition.to.path.split('http')[1]
    window.location.href = `http${url}`
  } else {
    if (/\/demo\/component\/\w+/.test(transition.to.path)) {
      router.go({
        replace: true,
        path: transition.to.path.replace('/demo', ''),
        append: false
      })
    } else {
      transition.next()
    }
  }
})

router.afterEach(function (transition) {
  if (transition.to.path !== '/demo') {
    window.scrollTo(0, 0)
  } else {
    window.removeEventListener('scroll', saveDemoScrollTop, false)
    // if from component page
    if (demoScrollTop && /component/.test(transition.from.path)) {
      setTimeout(function () {
        window.scrollTo(0, demoScrollTop)
      }, 100)
    }
    setTimeout(function () {
      window.addEventListener('scroll', saveDemoScrollTop, false)
    }, 1000)
  }
})

router.start(App, '#app')
<|endoftext|>