<commit_before>odoo.define('web.Menu', function (require) {
"use strict";

var AppsMenu = require('web.AppsMenu');
var config = require('web.config');
var core = require('web.core');
var dom = require('web.dom');
var SystrayMenu = require('web.SystrayMenu');
var UserMenu = require('web.UserMenu');
var Widget = require('web.Widget');

UserMenu.prototype.sequence = 0; // force UserMenu to be the right-most item in the systray
SystrayMenu.Items.push(UserMenu);

var QWeb = core.qweb;

var Menu = Widget.extend({
    template: 'Menu',
    menusTemplate: 'Menu.sections',
    events: {
        'mouseover .o_menu_sections > li:not(.show)': '_onMouseOverMenu',
        'click .o_menu_brand': '_onAppNameClicked',
    },

    init: function (parent, menu_data) {
        var self = this;
        this._super.apply(this, arguments);

        this.$menu_sections = {};
        this.menu_data = menu_data;

        // Prepare navbar's menus
        var $menu_sections = $(QWeb.render(this.menusTemplate, {
            menu_data: this.menu_data,
        }));
        $menu_sections.filter('section').each(function () {
            self.$menu_sections[parseInt(this.className, 10)] = $(this).children('li');
        });

        // Bus event
        core.bus.on('change_menu_section', this, this.change_menu_section);
    },
    start: function () {
        var self = this;

        this.$menu_apps = this.$('.o_menu_apps');
        this.$menu_brand_placeholder = this.$('.o_menu_brand');
        this.$section_placeholder = this.$('.o_menu_sections');

        // Navbar's menus event handlers
        var on_secondary_menu_click = function (ev) {
            ev.preventDefault();
            var menu_id = $(ev.currentTarget).data('menu');
            var action_id = $(ev.currentTarget).data('action-id');
            self._on_secondary_menu_click(menu_id, action_id);
        };
        var menu_ids = _.keys(this.$menu_sections);
        var primary_menu_id, $section;
        for (var i = 0; i < menu_ids.length; i++) {
            primary_menu_id = menu_ids[i];
            $section = this.$menu_sections[primary_menu_id];
            $section.on('click', 'a[data-menu]', self, on_secondary_menu_click.bind(this));
        }

        // Apps Menu
        this._appsMenu = new AppsMenu(self, this.menu_data);
        var appsMenuProm = this._appsMenu.appendTo(this.$menu_apps);

        // Systray Menu
        this.systray_menu = new SystrayMenu(this);
        var systrayMenuProm = this.systray_menu.attachTo(this.$('.o_menu_systray')).then(function() {
            dom.initAutoMoreMenu(self.$section_placeholder, {
            maxWidth: function () {
                return self.$el.width() - (self.$menu_apps.outerWidth(true) + self.$menu_brand_placeholder.outerWidth(true) + self.systray_menu.$el.outerWidth(true));
            },
            sizeClass: 'SM',
            });
        });

        return Promise.all([this._super.apply(this, arguments), appsMenuProm, systrayMenuProm]);
    },
    change_menu_section: function (primary_menu_id) {
        if (!this.$menu_sections[primary_menu_id]) {
            this._updateMenuBrand();
            return; // unknown menu_id
        }

        if (this.current_primary_menu === primary_menu_id) {
            return; // already in that menu
        }

        if (this.current_primary_menu) {
            this.$menu_sections[this.current_primary_menu].detach();
        }

        // Get back the application name
        for (var i = 0; i < this.menu_data.children.length; i++) {
            if (this.menu_data.children[i].id === primary_menu_id) {
                this._updateMenuBrand(this.menu_data.children[i].name);
                break;
            }
        }

        this.$menu_sections[primary_menu_id].appendTo(this.$section_placeholder);
        this.current_primary_menu = primary_menu_id;

        core.bus.trigger('resize');
    },
    _trigger_menu_clicked: function (menu_id, action_id) {
        this.trigger_up('menu_clicked', {
            id: menu_id,
            action_id: action_id,
            previous_menu_id: this.current_secondary_menu || this.current_primary_menu,
        });
    },
    /**
     * Updates the name of the app in the menu to the value of brandName.
     * If brandName is falsy, hides the menu and its sections.
     *
     * @private
     * @param {brandName} string
     */
    _updateMenuBrand: function (brandName) {
        if (brandName) {
            this.$menu_brand_placeholder.text(brandName).show();
            this.$section_placeholder.show();
        } else {
            this.$menu_brand_placeholder.hide()
            this.$section_placeholder.hide();
        }
    },
    _on_secondary_menu_click: function (menu_id, action_id) {
        var self = this;

        // It is still possible that we don't have an action_id (for example, menu toggler)
        if (action_id) {
            self._trigger_menu_clicked(menu_id, action_id);
            this.current_secondary_menu = menu_id;
        }
    },
    /**
     * Helpers used by web_client in order to restore the state from
     * an url (by restore, read re-synchronize menu and action manager)
     */
    action_id_to_primary_menu_id: function (action_id) {
        var primary_menu_id, found;
        for (var i = 0; i < this.menu_data.children.length && !primary_menu_id; i++) {
            found = this._action_id_in_subtree(this.menu_data.children[i], action_id);
            if (found) {
                primary_menu_id = this.menu_data.children[i].id;
            }
        }
        return primary_menu_id;
    },
    _action_id_in_subtree: function (root, action_id) {
        // action_id can be a string or an integer
        if (root.action && root.action.split(',')[1] === String(action_id)) {
            return true;
        }
        var found;
        for (var i = 0; i < root.children.length && !found; i++) {
            found = this._action_id_in_subtree(root.children[i], action_id);
        }
        return found;
    },
    menu_id_to_action_id: function (menu_id, root) {
        if (!root) {
            root = $.extend(true, {}, this.menu_data);
        }

        if (root.id === menu_id) {
            return root.action.split(',')[1] ;
        }
        for (var i = 0; i < root.children.length; i++) {
            var action_id = this.menu_id_to_action_id(menu_id, root.children[i]);
            if (action_id !== undefined) {
                return action_id;
            }
        }
        return undefined;
    },

    //--------------------------------------------------------------------------
    // Public
    //--------------------------------------------------------------------------

    /**
     * Returns the id of the current primary (first level) menu.
     *
     * @returns {integer}
     */
    getCurrentPrimaryMenu: function () {
        return this.current_primary_menu;
    },
    /**
     * Open the first app
     */
    openFirstApp: function () {
        this._appsMenu.openFirstApp();
    },

    //--------------------------------------------------------------------------
    // Handlers
    //--------------------------------------------------------------------------

    /**
     * When clicking on app name, opens the first action of the app
     *
     * @private
     * @param {MouseEvent} ev
     */
    _onAppNameClicked: function (ev) {
        var actionID = parseInt(this.menu_id_to_action_id(this.current_primary_menu));
        this._trigger_menu_clicked(this.current_primary_menu, actionID);
    },
    /**
     * @private
     * @param {MouseEvent} ev
     */
    _onMouseOverMenu: function (ev) {
        if (config.device.isMobile) {
            return;
        }
        var $target = $(ev.currentTarget);
        var $opened = $target.siblings('.show');
        if ($opened.length) {
            $opened.find('[data-toggle="dropdown"]').dropdown('toggle');
            $opened.removeClass('show');
            $target.find('[data-toggle="dropdown"]').dropdown('toggle');
            $target.addClass('show');
        }
    },
});

return Menu;

});
<commit_msg>[FIX] web: fix more menu<commit_after>odoo.define('web.Menu', function (require) {
"use strict";

var AppsMenu = require('web.AppsMenu');
var config = require('web.config');
var core = require('web.core');
var dom = require('web.dom');
var SystrayMenu = require('web.SystrayMenu');
var UserMenu = require('web.UserMenu');
var Widget = require('web.Widget');

UserMenu.prototype.sequence = 0; // force UserMenu to be the right-most item in the systray
SystrayMenu.Items.push(UserMenu);

var QWeb = core.qweb;

var Menu = Widget.extend({
    template: 'Menu',
    menusTemplate: 'Menu.sections',
    events: {
        'mouseover .o_menu_sections > li:not(.show)': '_onMouseOverMenu',
        'click .o_menu_brand': '_onAppNameClicked',
    },

    init: function (parent, menu_data) {
        var self = this;
        this._super.apply(this, arguments);

        this.$menu_sections = {};
        this.menu_data = menu_data;

        // Prepare navbar's menus
        var $menu_sections = $(QWeb.render(this.menusTemplate, {
            menu_data: this.menu_data,
        }));
        $menu_sections.filter('section').each(function () {
            self.$menu_sections[parseInt(this.className, 10)] = $(this).children('li');
        });

        // Bus event
        core.bus.on('change_menu_section', this, this.change_menu_section);
    },
    start: function () {
        var self = this;

        this.$menu_apps = this.$('.o_menu_apps');
        this.$menu_brand_placeholder = this.$('.o_menu_brand');
        this.$section_placeholder = this.$('.o_menu_sections');

        // Navbar's menus event handlers
        var on_secondary_menu_click = function (ev) {
            ev.preventDefault();
            var menu_id = $(ev.currentTarget).data('menu');
            var action_id = $(ev.currentTarget).data('action-id');
            self._on_secondary_menu_click(menu_id, action_id);
        };
        var menu_ids = _.keys(this.$menu_sections);
        var primary_menu_id, $section;
        for (var i = 0; i < menu_ids.length; i++) {
            primary_menu_id = menu_ids[i];
            $section = this.$menu_sections[primary_menu_id];
            $section.on('click', 'a[data-menu]', self, on_secondary_menu_click.bind(this));
        }

        // Apps Menu
        this._appsMenu = new AppsMenu(self, this.menu_data);
        var appsMenuProm = this._appsMenu.appendTo(this.$menu_apps);

        // Systray Menu
        this.systray_menu = new SystrayMenu(this);
        var systrayMenuProm = this.systray_menu.attachTo(this.$('.o_menu_systray')).then(function() {
            dom.initAutoMoreMenu(self.$section_placeholder, {
            maxWidth: function () {
                return self.$el.width() - (self.$menu_apps.outerWidth(true) + self.$menu_brand_placeholder.outerWidth(true) + self.systray_menu.$el.outerWidth(true));
            },
            sizeClass: 'SM',
            });
        });

        return Promise.all([this._super.apply(this, arguments), appsMenuProm, systrayMenuProm]);
    },
    change_menu_section: function (primary_menu_id) {
        if (!this.$menu_sections[primary_menu_id]) {
            this._updateMenuBrand();
            return; // unknown menu_id
        }

        if (this.current_primary_menu === primary_menu_id) {
            return; // already in that menu
        }

        if (this.current_primary_menu) {
            this.$menu_sections[this.current_primary_menu].detach();
        }

        // Get back the application name
        for (var i = 0; i < this.menu_data.children.length; i++) {
            if (this.menu_data.children[i].id === primary_menu_id) {
                this._updateMenuBrand(this.menu_data.children[i].name);
                break;
            }
        }

        this.$menu_sections[primary_menu_id].appendTo(this.$section_placeholder);
        this.current_primary_menu = primary_menu_id;

        core.bus.trigger('resize');
    },
    _trigger_menu_clicked: function (menu_id, action_id) {
        this.trigger_up('menu_clicked', {
            id: menu_id,
            action_id: action_id,
            previous_menu_id: this.current_secondary_menu || this.current_primary_menu,
        });
    },
    /**
     * Updates the name of the app in the menu to the value of brandName.
     * If brandName is falsy, hides the menu and its sections.
     *
     * @private
     * @param {brandName} string
     */
    _updateMenuBrand: function (brandName) {
        if (brandName) {
            this.$menu_brand_placeholder.text(brandName).show();
            this.$section_placeholder.show();
        } else {
            this.$menu_brand_placeholder.hide()
            this.$section_placeholder.hide();
        }
    },
    _on_secondary_menu_click: function (menu_id, action_id) {
        var self = this;

        // It is still possible that we don't have an action_id (for example, menu toggler)
        if (action_id) {
            self._trigger_menu_clicked(menu_id, action_id);
            this.current_secondary_menu = menu_id;
        }
    },
    /**
     * Helpers used by web_client in order to restore the state from
     * an url (by restore, read re-synchronize menu and action manager)
     */
    action_id_to_primary_menu_id: function (action_id) {
        var primary_menu_id, found;
        for (var i = 0; i < this.menu_data.children.length && !primary_menu_id; i++) {
            found = this._action_id_in_subtree(this.menu_data.children[i], action_id);
            if (found) {
                primary_menu_id = this.menu_data.children[i].id;
            }
        }
        return primary_menu_id;
    },
    _action_id_in_subtree: function (root, action_id) {
        // action_id can be a string or an integer
        if (root.action && root.action.split(',')[1] === String(action_id)) {
            return true;
        }
        var found;
        for (var i = 0; i < root.children.length && !found; i++) {
            found = this._action_id_in_subtree(root.children[i], action_id);
        }
        return found;
    },
    menu_id_to_action_id: function (menu_id, root) {
        if (!root) {
            root = $.extend(true, {}, this.menu_data);
        }

        if (root.id === menu_id) {
            return root.action.split(',')[1] ;
        }
        for (var i = 0; i < root.children.length; i++) {
            var action_id = this.menu_id_to_action_id(menu_id, root.children[i]);
            if (action_id !== undefined) {
                return action_id;
            }
        }
        return undefined;
    },

    //--------------------------------------------------------------------------
    // Public
    //--------------------------------------------------------------------------

    /**
     * Returns the id of the current primary (first level) menu.
     *
     * @returns {integer}
     */
    getCurrentPrimaryMenu: function () {
        return this.current_primary_menu;
    },
    /**
     * Open the first app
     */
    openFirstApp: function () {
        this._appsMenu.openFirstApp();
    },

    //--------------------------------------------------------------------------
    // Handlers
    //--------------------------------------------------------------------------

    /**
     * When clicking on app name, opens the first action of the app
     *
     * @private
     * @param {MouseEvent} ev
     */
    _onAppNameClicked: function (ev) {
        var actionID = parseInt(this.menu_id_to_action_id(this.current_primary_menu));
        this._trigger_menu_clicked(this.current_primary_menu, actionID);
    },
    /**
     * @private
     * @param {MouseEvent} ev
     */
    _onMouseOverMenu: function (ev) {
        if (config.device.isMobile) {
            return;
        }
        var $target = $(ev.currentTarget);
        var $opened = $target.siblings('.show');
        if ($opened.length) {
            $opened.find('[data-toggle="dropdown"]:first').dropdown('toggle');
            $opened.removeClass('show');
            $target.find('[data-toggle="dropdown"]:first').dropdown('toggle');
            $target.addClass('show');
        }
    },
});

return Menu;

});
<|endoftext|>


<commit_before>/*jslint browser: true */

define([
	'module',
	'backbone',
	'backgrid',
	'backgrid-paginator',
	'views/BackgridUrlCell',
	'views/BackgridClientSortingBody',
	'views/BaseView',
	'views/AlertView',
	'views/SearchFilterRowView',
	'hbs!hb_templates/managePublications'
], function (module, Backbone, Backgrid, Paginator, BackgridUrlCell, BackgridClientSortingBody, BaseView,
			 AlertView, SearchFilterRowView, hbTemplate) {
	"use strict";

	var view = BaseView.extend({

		events : {
			'change .page-size-select' : 'changePageSize',
			'click .search-btn' : 'filterPubs',
			'submit .pub-search-form' : 'filterPubs',
			'change #search-term-input' : 'updateQterm',
			'click .add-category-btn' : 'addFilterRow',
			'click .clear-advanced-search-btn' : 'clearFilterRows',
			'click .create-pub-btn' : 'goToEditPubPage'
		},

		template: hbTemplate,

		/*
		 * @param {Object} options
		 *     @prop {String} el - jquery selector where the view should be rendered
		 *     @prop {PublicationCollection} collection
		 */
		initialize : function(options) {
			var self = this;
			BaseView.prototype.initialize.apply(this, arguments);

			// Create filter model, listeners, and holder for filter rows.
			this.filterModel = new Backbone.Model();
			this.listenTo(this.model, 'change:q', this.changeQterm);
			this.filterRowViews = [];

			// Can get rid of this once the edit contributors page is implemented.
			this.context.oldMyPubsEndpoint = module.config().oldMyPubsEndpoint;

			// Set up collection event handlers and then fetch the collection
			this.listenTo(this.collection, 'request', this.showLoadingIndicator);
			this.listenTo(this.collection, 'sync', this.updatePubsListDisplay);

			this.fetchPromise = this.collection.fetch({reset: true});

			var fromRawLookup = function(rawValue, model) {
				return (rawValue) ? rawValue.text : '';
			};
			var sortValueLookup = function(model, colName) {
				return fromRawLookup(model.get(colName), model);
			};
			var sortValueText = function(model, colName) {
				return (model.has(colName)) ? model.get(colName) : '';
			};

			var fromRawFirstAuthor = function(rawValue, model) {
				if ((rawValue) && _.has(rawValue, 'authors') && (_.isArray(rawValue.authors)) && (rawValue.authors.length > 0)) {
					return rawValue.authors[0].text;
				}
				else {
					return '';
				}
			};
			var sortValueFirstAuthor = function(model, colName) {
				return fromRawFirstAuthor(model.get(colName));
			};

			// Create backgrid and paginator views
			var columns = [
				{
					name: 'id',
					label : '',
					editable : false,
					sortable : false,
					cell: BackgridUrlCell.extend({
						router : this.router,
						toFragment : function(rawValue, model) {
							return 'publication/' + rawValue;
						},
						title : 'Click to edit'
					}),
					formatter : {
						fromRaw : function(rawValue, model) {
							return 'Edit'
						}
					}
				}, {
					name: "publicationType",
					label: "Type",
					editable: false,
					sortable : true,
					cell: "string",
					formatter : {
						fromRaw : fromRawLookup
					},

					sortValue : sortValueLookup
				}, {
					name: "seriesTitle",
					label: "USGS Series",
					editable: false,
					sortable : true,
					cell: "string",
					formatter: {
						fromRaw: fromRawLookup
					},
					sortValue : sortValueLookup
				}, {
					name: "seriesNumber",
					label: "Report Number",
					editable: false,
					sortable : true,
					cell: "string",
					sortValue : sortValueText
				}, {
					name: "publicationYear",
					label: "Year",
					editable: false,
					sortable : true,
					cell: "string",
					sortValue : sortValueText
				},{
					name: 'indexId',
					label : 'Index ID',
					editable : false,
					sortable : true,
					cell: 'string',
					sortValue : sortValueText
				}, {
					name: "title",
					label: "Title",
					editable: false,
					sortable : true,
					cell: "string",
					sortValue : sortValueText
				},{
					name: 'contributors',
					label: 'First Author',
					editable : false,
					sortable : true,
					cell: 'string',
					formatter : {
						fromRaw : fromRawFirstAuthor
					},
					sortValue : sortValueFirstAuthor
				}
			];

			// Initialize a new Grid instance
			this.grid = new Backgrid.Grid({
				body : BackgridClientSortingBody,
				columns: columns,
				collection: this.collection,
				className : 'backgrid table-striped table-hover table-bordered'
			});

			// Initialize the paginator
			this.paginator = new Backgrid.Extension.Paginator({
				collection: this.collection,
				goBackFirstOnSort : false
			});

			// Create other child views
			this.alertView = new AlertView({
				el: '.alert-container'
			});
		},

		render : function() {
			var self = this;
			var $pubList;

			BaseView.prototype.render.apply(this, arguments);
			$pubList = this.$('.pub-grid');

			// Set the elements for child views and render if needed.
			this.alertView.setElement(this.$('.alert-container'));

			// Render the grid and attach the root to HTML document
			$pubList.append(this.grid.render().el);

			// Render the paginator
			this.$('.pub-grid-footer').append(this.paginator.render().el);

			this.fetchPromise.fail(function(jqXhr) {
				self.alertView.showDangerAlert('Can\'t retrieve the list of publications: ' + jqXhr.statusText);
			}).always(function() {
				// Make sure indicator is hidden. Need to do this in case the sync signal was sent before the view was rendered
				self.updatePubsListDisplay();
			});
		},

		remove : function() {
			this.grid.remove();
			this.paginator.remove();
			this.alertView.remove();
			_.each(this.filterRowViews, function(view) {
				view.remove();
			});

			BaseView.prototype.remove.apply(this, arguments);
			return this;
		},

		/*
		 * DOM event handlers
		 */
		filterPubs : function(ev) {
			var self = this;

			ev.preventDefault();
			this.collection.updateFilters(this.filterModel.attributes);
			this.collection.getFirstPage()
					.fail(function(jqXhr) {
						self.alertView.showDangerAlert('Can\'t retrieve the list of publications: ' + jqXhr.statusText);
					});
		},

		changePageSize : function(ev) {
			this.collection.setPageSize(parseInt(ev.currentTarget.value));
		},

		updateQterm : function(ev) {
			this.filterModel.set('q', ev.currentTarget.value);
		},

		addFilterRow : function(ev) {
			ev.preventDefault();
			var $rowContainer = this.$('.advanced-search-rows-container');
			var newRow = new SearchFilterRowView({
				el : '.filter-row-container',
				model : this.filterModel
			});
			$rowContainer.append('

');
			this.$('.advanced-search-rows-container').append('<div ');
			newRow.setElement($rowContainer.find('.filter-row-container:last-child')).render();
			this.filterRowViews.push(newRow);
		},

		clearFilterRows : function(ev) {
			var self = this;
			ev.preventDefault();
			_.each(this.filterRowViews, function(view) {
				view.remove();
			});
			this.filterRowViews = [];
		},

		goToEditPubPage : function (ev) {
			this.router.navigate('publication', {trigger: true});
		},

		/*
		 * filterModel event handlers
		 */
		changeQTerm : function() {
			this.$('#search-term-input').val(this.filterModel.get('q'));
		},

		/* collection event handlers */
		showLoadingIndicator : function() {
			this.$('.pubs-loading-indicator').show();
		},

		updatePubsListDisplay : function() {
			this.$('.pubs-loading-indicator').hide();
			this.$('.pubs-count').html(this.collection.state.totalRecords);
		}
	});

	return view;
});<commit_msg>Needed to call preventDefault when clicking Create button.<commit_after>/*jslint browser: true */

define([
	'module',
	'backbone',
	'backgrid',
	'backgrid-paginator',
	'views/BackgridUrlCell',
	'views/BackgridClientSortingBody',
	'views/BaseView',
	'views/AlertView',
	'views/SearchFilterRowView',
	'hbs!hb_templates/managePublications'
], function (module, Backbone, Backgrid, Paginator, BackgridUrlCell, BackgridClientSortingBody, BaseView,
			 AlertView, SearchFilterRowView, hbTemplate) {
	"use strict";

	var view = BaseView.extend({

		events : {
			'change .page-size-select' : 'changePageSize',
			'click .search-btn' : 'filterPubs',
			'submit .pub-search-form' : 'filterPubs',
			'change #search-term-input' : 'updateQterm',
			'click .add-category-btn' : 'addFilterRow',
			'click .clear-advanced-search-btn' : 'clearFilterRows',
			'click .create-pub-btn' : 'goToEditPubPage'
		},

		template: hbTemplate,

		/*
		 * @param {Object} options
		 *     @prop {String} el - jquery selector where the view should be rendered
		 *     @prop {PublicationCollection} collection
		 */
		initialize : function(options) {
			var self = this;
			BaseView.prototype.initialize.apply(this, arguments);

			// Create filter model, listeners, and holder for filter rows.
			this.filterModel = new Backbone.Model();
			this.listenTo(this.model, 'change:q', this.changeQterm);
			this.filterRowViews = [];

			// Can get rid of this once the edit contributors page is implemented.
			this.context.oldMyPubsEndpoint = module.config().oldMyPubsEndpoint;

			// Set up collection event handlers and then fetch the collection
			this.listenTo(this.collection, 'request', this.showLoadingIndicator);
			this.listenTo(this.collection, 'sync', this.updatePubsListDisplay);

			this.fetchPromise = this.collection.fetch({reset: true});

			var fromRawLookup = function(rawValue, model) {
				return (rawValue) ? rawValue.text : '';
			};
			var sortValueLookup = function(model, colName) {
				return fromRawLookup(model.get(colName), model);
			};
			var sortValueText = function(model, colName) {
				return (model.has(colName)) ? model.get(colName) : '';
			};

			var fromRawFirstAuthor = function(rawValue, model) {
				if ((rawValue) && _.has(rawValue, 'authors') && (_.isArray(rawValue.authors)) && (rawValue.authors.length > 0)) {
					return rawValue.authors[0].text;
				}
				else {
					return '';
				}
			};
			var sortValueFirstAuthor = function(model, colName) {
				return fromRawFirstAuthor(model.get(colName));
			};

			// Create backgrid and paginator views
			var columns = [
				{
					name: 'id',
					label : '',
					editable : false,
					sortable : false,
					cell: BackgridUrlCell.extend({
						router : this.router,
						toFragment : function(rawValue, model) {
							return 'publication/' + rawValue;
						},
						title : 'Click to edit'
					}),
					formatter : {
						fromRaw : function(rawValue, model) {
							return 'Edit'
						}
					}
				}, {
					name: "publicationType",
					label: "Type",
					editable: false,
					sortable : true,
					cell: "string",
					formatter : {
						fromRaw : fromRawLookup
					},

					sortValue : sortValueLookup
				}, {
					name: "seriesTitle",
					label: "USGS Series",
					editable: false,
					sortable : true,
					cell: "string",
					formatter: {
						fromRaw: fromRawLookup
					},
					sortValue : sortValueLookup
				}, {
					name: "seriesNumber",
					label: "Report Number",
					editable: false,
					sortable : true,
					cell: "string",
					sortValue : sortValueText
				}, {
					name: "publicationYear",
					label: "Year",
					editable: false,
					sortable : true,
					cell: "string",
					sortValue : sortValueText
				},{
					name: 'indexId',
					label : 'Index ID',
					editable : false,
					sortable : true,
					cell: 'string',
					sortValue : sortValueText
				}, {
					name: "title",
					label: "Title",
					editable: false,
					sortable : true,
					cell: "string",
					sortValue : sortValueText
				},{
					name: 'contributors',
					label: 'First Author',
					editable : false,
					sortable : true,
					cell: 'string',
					formatter : {
						fromRaw : fromRawFirstAuthor
					},
					sortValue : sortValueFirstAuthor
				}
			];

			// Initialize a new Grid instance
			this.grid = new Backgrid.Grid({
				body : BackgridClientSortingBody,
				columns: columns,
				collection: this.collection,
				className : 'backgrid table-striped table-hover table-bordered'
			});

			// Initialize the paginator
			this.paginator = new Backgrid.Extension.Paginator({
				collection: this.collection,
				goBackFirstOnSort : false
			});

			// Create other child views
			this.alertView = new AlertView({
				el: '.alert-container'
			});
		},

		render : function() {
			var self = this;
			var $pubList;

			BaseView.prototype.render.apply(this, arguments);
			$pubList = this.$('.pub-grid');

			// Set the elements for child views and render if needed.
			this.alertView.setElement(this.$('.alert-container'));

			// Render the grid and attach the root to HTML document
			$pubList.append(this.grid.render().el);

			// Render the paginator
			this.$('.pub-grid-footer').append(this.paginator.render().el);

			this.fetchPromise.fail(function(jqXhr) {
				self.alertView.showDangerAlert('Can\'t retrieve the list of publications: ' + jqXhr.statusText);
			}).always(function() {
				// Make sure indicator is hidden. Need to do this in case the sync signal was sent before the view was rendered
				self.updatePubsListDisplay();
			});
		},

		remove : function() {
			this.grid.remove();
			this.paginator.remove();
			this.alertView.remove();
			_.each(this.filterRowViews, function(view) {
				view.remove();
			});

			BaseView.prototype.remove.apply(this, arguments);
			return this;
		},

		/*
		 * DOM event handlers
		 */
		filterPubs : function(ev) {
			var self = this;

			ev.preventDefault();
			this.collection.updateFilters(this.filterModel.attributes);
			this.collection.getFirstPage()
					.fail(function(jqXhr) {
						self.alertView.showDangerAlert('Can\'t retrieve the list of publications: ' + jqXhr.statusText);
					});
		},

		changePageSize : function(ev) {
			this.collection.setPageSize(parseInt(ev.currentTarget.value));
		},

		updateQterm : function(ev) {
			this.filterModel.set('q', ev.currentTarget.value);
		},

		addFilterRow : function(ev) {
			ev.preventDefault();
			var $rowContainer = this.$('.advanced-search-rows-container');
			var newRow = new SearchFilterRowView({
				el : '.filter-row-container',
				model : this.filterModel
			});
			$rowContainer.append('

');
			this.$('.advanced-search-rows-container').append('<div ');
			newRow.setElement($rowContainer.find('.filter-row-container:last-child')).render();
			this.filterRowViews.push(newRow);
		},

		clearFilterRows : function(ev) {
			var self = this;
			ev.preventDefault();
			_.each(this.filterRowViews, function(view) {
				view.remove();
			});
			this.filterRowViews = [];
		},

		goToEditPubPage : function (ev) {
			ev.preventDefault();
			this.router.navigate('publication', {trigger: true});
		},

		/*
		 * filterModel event handlers
		 */
		changeQTerm : function() {
			this.$('#search-term-input').val(this.filterModel.get('q'));
		},

		/* collection event handlers */
		showLoadingIndicator : function() {
			this.$('.pubs-loading-indicator').show();
		},

		updatePubsListDisplay : function() {
			this.$('.pubs-loading-indicator').hide();
			this.$('.pubs-count').html(this.collection.state.totalRecords);
		}
	});

	return view;
});<|endoftext|>


<commit_before>import React, { Component } from 'react';
import LocationsList from "../containers/locations_list";

export default class App extends Component {
  render() {
    return (

        <LocationsList/>

    );
  }
}
<commit_msg>search bar<commit_after>import React, { Component } from 'react';
import LocationsList from "../containers/locations_list";
import SearchBar from "../containers/search_bar";

export default class App extends Component {
  render() {
    return (

        <SearchBar/>
        <LocationsList/>

    );
  }
}
<|endoftext|>


<commit_before>
(function($){
    window.$canvas = null;
    $.fn.eyedrop = function(options) {
        var settings = $.extend({
            'pick': function(x, y, color) {},
            'pickRange': function(startx, starty, startcolor, endx, endy, endcolor) {},
            'offset': 20,
            'width': 5,
            'height': 5,
            'cursor': 'crosshair',
            'display': false,
            'show': true,
            'mode': 'point',
            'picker': $('.picker'),
            '_start': function(x, y) {
                settings._started = true;
                settings.start(x, y);
            },
            'start': function(x, y) {},
            'stop': function() {},
            '_stop': function() {
                settings._started = false;
                settings.stop();
            },
            'cancel': function() {},
            'picking': function(x, y, w, h) {},
            '_pickRange': function(startx, starty, startcolor, endx, endy, endcolor) {
                if (!settings._started) { return; }
                settings.pickRange(startx, starty, startcolor, endx, endy, endcolor);
            },
            '_started': false
        }, options);

        return this.each(function() {
            var $this = $(this);
            var data = $this.data('eyedrop');
            if (!data) {
                $this.data('eyedrop', {});
                data = $this.data('eyedrop');
            }

            var $display = $('.display', settings.picker);
            if (settings.display !== false) {
                settings.picker.addClass('display');
                $display.show();
            } else {
                settings.picker.removeClass('display');
                $display.hide();
            }

            // bind escape to cancel operation
            $('html').keydown(function(e){
                if (e.keyCode == 27) {
                    mouseClicked = false;
                    settings._started = false;
                    settings.cancel();
                }
            });

            // set width, height and background image on source element
            $this.css({
                'cursor': settings.cursor
            });

            if (settings.show) {

                // show or hide eyedropper
                $this.hover(
                    function() {
                        $('.picker').show();
                    },
                    function() {
                        $('.picker').hide();
                    }
                );

                // create canvas
                var imgctx;
                if(!$canvas){
                    $canvas = $('<canvas class="eyedrop" height="'+$this.data('height')+'" width="'+$this.data('width')+'"></canvas>');

                    // draw image into canvas
                    if ($canvas[0].getContext) {
                        imgctx = $canvas[0].getContext("2d");

                        var img = new Image();
                        img.onload = function(){
                            imgctx.drawImage(img, 0, 0);
                        };
                        img.src = $this.data('image');
                    } else {
                        return;
                    }
                } else {
                    imgctx = $canvas[0].getContext("2d");
                }
            }

            // in case of range we need to handle mousedown+move+mouseup and click+move+click
            if (options.mode === 'range') {
                var mouseClicked = false;

                $this.on('mousedown', function(e) {
                    if (mouseClicked === true) {
                        mouseClicked = false;
                        settings._pickRange(
                            data.startx,
                            data.starty,
                            data.startcolor,
                            data.x,
                            data.y,
                            data.color
                        );
                        settings._stop();
                    } else {
                        mouseClicked = true;
                        settings._start(data.x, data.y);
                        settings.pick(data.x, data.y, data.color);
                        data.startx = data.x;
                        data.starty = data.y;
                        data.startcolor = data.color;
                        if (e.preventDefault) {
                            e.preventDefault();
                        }
                    }
                });

                $this.on('mouseup', function() {
                    if (data.startx !== data.x && data.starty !== data.y && mouseClicked === true) {
                        mouseClicked = false;
                        settings._pickRange(
                            data.startx,
                            data.starty,
                            data.startcolor,
                            data.x,
                            data.y,
                            data.color
                        );
                        settings._stop();
                    }
                });
            } else {
                $this.on('mousedown', function() {
                    settings.pick(data.x, data.y, data.color);
                    settings._stop();
                });
            }

            if (settings.show) {
                var ps = [];
                for (var i = 0; i < settings.width * settings.height; i++) {
                    ps[i] = $('#picker' + (i + 1));
                }
            }

            // on mouse movement, show picker
            $this.on('mousemove', function(e) {

                var offset = $this.offset();
                data.x = e.pageX - offset.left;
                data.y = e.pageY - offset.top;

                // move magnifier to the correct location
                var $picker = settings.picker;

                // make sure picker-windows does't get out of the screen
                var offsetLeft = (data.x + offset.left + settings.offset + $picker.outerWidth() > $(window).width()) ? data.x + offset.left - (settings.offset + $picker.outerWidth()) : data.x + offset.left + settings.offset;

                $picker.css({
                    left: offsetLeft,
                    top: data.y + offset.top + settings.offset
                });

                if (settings.show) {

                    // get metadata of surrounding pixels
                    var pixel = imgctx.getImageData(
                        data.x - ((settings.width-1) / 2),
                        data.y - ((settings.height-1) / 2),
                        settings.width,
                        settings.height
                    );

                    // draw surrounding pixels into magnifier
                    for (var i = 0; i < settings.width * settings.height; i++) {

                        var r = pixel.data[0+i*4];
                        var g = pixel.data[1+i*4];
                        var b = pixel.data[2+i*4];
                        var a = pixel.data[3+i*4];

                        ps[i].css('backgroundColor', 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')');

                        if (i == ((settings.width*settings.height-1)/2)) {

                            // invert shadow to be more visible on dark backgrounds (not really inverted but top bit of each component of the RGB triple is toggled)
                            var inverted_rgba = 'inset 0px 0px 1px 1px rgba(' + (r ^ 0x80) + ', ' + (g ^ 0x80) + ', ' + (b ^ 0x80) + ', 0.7)';

                            ps[i].css({
                                '-webkit-box-shadow': inverted_rgba,
                                '-moz-box-shadow': inverted_rgba,
                                'box-shadow': inverted_rgba
                            });

                            var color = Color.rgb(r, g, b, a);
                            var hex = color.hexTriplet();
                            data.color = {
                                r: r,
                                g: g,
                                b: b,
                                a: a,
                                hex: hex
                            };
                        }
                    }
                }

                var x = data.startx < data.x ? data.startx : data.x;
                var y = data.starty < data.y ? data.starty : data.y;
                var width = Math.abs(data.x - data.startx) + 1;
                var height = Math.abs(data.y - data.starty) + 1;
                settings.picking(x, y, width, height);

                // update color-display with current hex-value
                if (settings.display !== false) {
                    $display.html(data.color.hex);
                }
            });
        });
    };

})(jQuery);<commit_msg>fixed a bug on cancel.<commit_after>
(function($){
    window.$canvas = null;
    $.fn.eyedrop = function(options) {
        var settings = $.extend({
            'pick': function(x, y, color) {},
            'pickRange': function(startx, starty, startcolor, endx, endy, endcolor) {},
            'offset': 20,
            'width': 5,
            'height': 5,
            'cursor': 'crosshair',
            'display': false,
            'show': true,
            'mode': 'point',
            'picker': $('.picker'),
            '_start': function(x, y) {
                settings._started = true;
                settings.start(x, y);
            },
            'start': function(x, y) {},
            'stop': function() {},
            '_stop': function() {
                settings._started = false;
                settings.stop();
            },
            'cancel': function() {},
            'picking': function(x, y, w, h) {},
            '_pickRange': function(startx, starty, startcolor, endx, endy, endcolor) {
                if (!settings._started) { return; }
                settings.pickRange(startx, starty, startcolor, endx, endy, endcolor);
            },
            '_started': false
        }, options);

        return this.each(function() {
            var $this = $(this);
            var data = $this.data('eyedrop');
            if (!data) {
                $this.data('eyedrop', {});
                data = $this.data('eyedrop');
            }

            var $display = $('.display', settings.picker);
            if (settings.display !== false) {
                settings.picker.addClass('display');
                $display.show();
            } else {
                settings.picker.removeClass('display');
                $display.hide();
            }

            // bind escape to cancel operation
            $('html').keydown(function(e){
                if (e.keyCode == 27) {
                    mouseClicked = false;
                    settings.cancel();
                    settings._stop();
                }
            });

            // set width, height and background image on source element
            $this.css({
                'cursor': settings.cursor
            });

            if (settings.show) {

                // show or hide eyedropper
                $this.hover(
                    function() {
                        $('.picker').show();
                    },
                    function() {
                        $('.picker').hide();
                    }
                );

                // create canvas
                var imgctx;
                if(!$canvas){
                    $canvas = $('<canvas class="eyedrop" height="'+$this.data('height')+'" width="'+$this.data('width')+'"></canvas>');

                    // draw image into canvas
                    if ($canvas[0].getContext) {
                        imgctx = $canvas[0].getContext("2d");

                        var img = new Image();
                        img.onload = function(){
                            imgctx.drawImage(img, 0, 0);
                        };
                        img.src = $this.data('image');
                    } else {
                        return;
                    }
                } else {
                    imgctx = $canvas[0].getContext("2d");
                }
            }

            // in case of range we need to handle mousedown+move+mouseup and click+move+click
            if (options.mode === 'range') {
                var mouseClicked = false;

                $this.on('mousedown', function(e) {
                    if (mouseClicked === true) {
                        mouseClicked = false;
                        settings._pickRange(
                            data.startx,
                            data.starty,
                            data.startcolor,
                            data.x,
                            data.y,
                            data.color
                        );
                        settings._stop();
                    } else {
                        mouseClicked = true;
                        settings._start(data.x, data.y);
                        settings.pick(data.x, data.y, data.color);
                        data.startx = data.x;
                        data.starty = data.y;
                        data.startcolor = data.color;
                        if (e.preventDefault) {
                            e.preventDefault();
                        }
                    }
                });

                $this.on('mouseup', function() {
                    if (data.startx !== data.x && data.starty !== data.y && mouseClicked === true) {
                        mouseClicked = false;
                        settings._pickRange(
                            data.startx,
                            data.starty,
                            data.startcolor,
                            data.x,
                            data.y,
                            data.color
                        );
                        settings._stop();
                    }
                });
            } else {
                $this.on('mousedown', function() {
                    settings.pick(data.x, data.y, data.color);
                    settings._stop();
                });
            }

            if (settings.show) {
                var ps = [];
                for (var i = 0; i < settings.width * settings.height; i++) {
                    ps[i] = $('#picker' + (i + 1));
                }
            }

            // on mouse movement, show picker
            $this.on('mousemove', function(e) {

                var offset = $this.offset();
                data.x = e.pageX - offset.left;
                data.y = e.pageY - offset.top;

                // move magnifier to the correct location
                var $picker = settings.picker;

                // make sure picker-windows does't get out of the screen
                var offsetLeft = (data.x + offset.left + settings.offset + $picker.outerWidth() > $(window).width()) ? data.x + offset.left - (settings.offset + $picker.outerWidth()) : data.x + offset.left + settings.offset;

                $picker.css({
                    left: offsetLeft,
                    top: data.y + offset.top + settings.offset
                });

                if (settings.show) {

                    // get metadata of surrounding pixels
                    var pixel = imgctx.getImageData(
                        data.x - ((settings.width-1) / 2),
                        data.y - ((settings.height-1) / 2),
                        settings.width,
                        settings.height
                    );

                    // draw surrounding pixels into magnifier
                    for (var i = 0; i < settings.width * settings.height; i++) {

                        var r = pixel.data[0+i*4];
                        var g = pixel.data[1+i*4];
                        var b = pixel.data[2+i*4];
                        var a = pixel.data[3+i*4];

                        ps[i].css('backgroundColor', 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')');

                        if (i == ((settings.width*settings.height-1)/2)) {

                            // invert shadow to be more visible on dark backgrounds (not really inverted but top bit of each component of the RGB triple is toggled)
                            var inverted_rgba = 'inset 0px 0px 1px 1px rgba(' + (r ^ 0x80) + ', ' + (g ^ 0x80) + ', ' + (b ^ 0x80) + ', 0.7)';

                            ps[i].css({
                                '-webkit-box-shadow': inverted_rgba,
                                '-moz-box-shadow': inverted_rgba,
                                'box-shadow': inverted_rgba
                            });

                            var color = Color.rgb(r, g, b, a);
                            var hex = color.hexTriplet();
                            data.color = {
                                r: r,
                                g: g,
                                b: b,
                                a: a,
                                hex: hex
                            };
                        }
                    }
                }

                var x = data.startx < data.x ? data.startx : data.x;
                var y = data.starty < data.y ? data.starty : data.y;
                var width = Math.abs(data.x - data.startx) + 1;
                var height = Math.abs(data.y - data.starty) + 1;
                settings.picking(x, y, width, height);

                // update color-display with current hex-value
                if (settings.display !== false) {
                    $display.html(data.color.hex);
                }
            });
        });
    };

})(jQuery);<|endoftext|>


<commit_before>var handlebars = require('handlebars'),
    scrawl = require('scrawl'),
    path = require('path'),
    fs = require('fs');

var src_file = path.resolve(__dirname, '../lib/index.js');
var tmpl_file = path.resolve(__dirname, '../docs/templates/base.html');
var out_file = path.resolve(__dirname, '../docs/index.html');

module.exports = function (grunt) {

    grunt.registerTask('docs',
        'Builds HTML documentation', function () {
            var src = fs.readFileSync(src_file).toString();
            var comments = scrawl.parse(src).filter(function (x) {
                return x.api === 'public';
            });

            var sections = {};
            comments.forEach(function (c) {
                if (!sections[c.section]) {
                    sections[c.section] = {
                        name: c.section,
                        items: []
                    };
                }
                sections[c.section].items.push(c);
            });

            var tmpl_src = fs.readFileSync(tmpl_file).toString();
            var tmpl = handlebars.compile(tmpl_src);

            var html = tmpl({
                comments: comments,
                sections: sections
            });

            fs.writeFileSync(out_file, html);
        }
    );

};
<commit_msg>ensure that there are no duplicate ids<commit_after>var handlebars = require('handlebars'),
    scrawl = require('scrawl'),
    path = require('path'),
    fs = require('fs');

var src_file = path.resolve(__dirname, '../lib/index.js');
var tmpl_file = path.resolve(__dirname, '../docs/templates/base.html');
var out_file = path.resolve(__dirname, '../docs/index.html');

module.exports = function (grunt) {

    grunt.registerTask('docs',
        'Builds HTML documentation', function () {
            var src = fs.readFileSync(src_file).toString();
            var comments = scrawl.parse(src).filter(function (x) {
                return x.api === 'public';
            });

            var sections = {};
            comments.forEach(function (c) {
                if (!sections[c.section]) {
                    sections[c.section] = {
                        name: c.section,
                        items: []
                    };
                }
                var items = sections[c.section].items,
                    containsId = function (id) {
                        return function (x) { return x.id === id; };
                    },
                    contains = function () {
                        return items.filter(containsId(c.id)).length > 0;
                    };

                if (contains(items, c)) {
                    throw new Error('Duplicate id:' + c.id);
                }
                items.push(c);
            });

            var tmpl_src = fs.readFileSync(tmpl_file).toString();
            var tmpl = handlebars.compile(tmpl_src);

            var html = tmpl({
                comments: comments,
                sections: sections
            });

            fs.writeFileSync(out_file, html);
        }
    );

};
<|endoftext|>


<commit_before>/**
 * This is the {@link module:core/local/watcher|local watcher} implementation
 * based on the {@link https://github.com/paulmillr/chokidar|chokidar} library.
 *
 * It's a library that uses nodejs' watch powered by inotify/fsevents (with a
 * fallback on polling).
 *
 * ## Steps
 *
 * 1. {@link module:core/local/chokidar/initial_scan|initial_scan}
 * 2. {@link module:core/local/chokidar/prepare_events|prepare_events}
 * 3. {@link module:core/local/chokidar/analysis|analysis} (macro step)
 * 4. {@link module:core/local/chokidar/send_to_prep|send_to_prep}
 *
 * @module core/local/chokidar/watcher
 * @flow
 */

const autoBind = require('auto-bind')
const Promise = require('bluebird')
const chokidar = require('chokidar')
const path = require('path')

const analysis = require('./analysis')
const checksumer = require('../checksumer')
const chokidarEvent = require('./event')
const LocalEventBuffer = require('./event_buffer')
const initialScan = require('./initial_scan')
const normalizePaths = require('./normalize_paths')
const prepareEvents = require('./prepare_events')
const sendToPrep = require('./send_to_prep')
const stater = require('../stater')
const syncDir = require('../sync_dir')
const logger = require('../../utils/logger')

/*::
import type { Pouch } from '../../pouch'
import type Prep from '../../prep'
import type { Checksumer } from '../checksumer'
import type { ChokidarEvent } from './event'
import type { InitialScan } from './initial_scan'
import type { LocalEvent } from './local_event'
import type { LocalChange } from './local_change'
import type EventEmitter from 'events'
import fs from 'fs'
*/

const log = logger({
  component: 'ChokidarWatcher'
})
log.chokidar = log.child({
  component: 'Chokidar'
})

/**
 * This file contains the filesystem watcher that will trigger operations when
 * a file or a folder is added/removed/changed locally.
 * Operations will be added to the a common operation queue along with the
 * remote operations triggered by the remoteEventWatcher.
 */
class LocalWatcher {
  /*::
  syncPath: string
  prep: Prep
  pouch: Pouch
  events: EventEmitter
  initialScan: ?InitialScan
  checksumer: Checksumer
  watcher: any // chokidar
  buffer: LocalEventBuffer<ChokidarEvent>
  pendingChanges: LocalChange[]
  running: Promise<void>
  _runningResolve: ?Function
  _runningReject: ?Function
  */

  constructor(
    syncPath /*: string */,
    prep /*: Prep */,
    pouch /*: Pouch */,
    events /*: EventEmitter */
  ) {
    this.syncPath = syncPath
    this.prep = prep
    this.pouch = pouch
    this.events = events
    this.checksumer = checksumer.init()
    this.pendingChanges = []

    // XXX: this.onFlush must be bound before being passed to LocalEventBuffer
    autoBind(this)

    // TODO: Read from config
    const timeoutInMs = process.env.NODE_ENV === 'test' ? 1000 : 10000
    this.buffer = new LocalEventBuffer(timeoutInMs, async rawEvents => {
      try {
        await this.onFlush(rawEvents)
      } catch (err) {
        log.error({ err }, 'onFlushError')
        this._runningReject && this._runningReject(err)
      }
    })
  }

  /** Start chokidar, the filesystem watcher
   *
   * @see https://github.com/paulmillr/chokidar
   */
  start() {
    log.debug('Starting...')

    this.watcher = chokidar.watch('.', {
      // Let paths in events be relative to this base path
      cwd: this.syncPath,
      // Ignore our own .system-tmp-cozy-drive directory
      ignored: /(^|[\/\\])\.system-tmp-cozy-drive/, // eslint-disable-line no-useless-escape
      // Don't follow symlinks
      followSymlinks: false,
      // The stats object is used in methods below
      alwaysStat: true,
      // Watching on Windows seems to lock dirs with subdirs, preventing them
      // from being renamed/moved/deleted.
      usePolling: process.platform === 'win32',
      // Filter out artifacts from editors with atomic writes
      atomic: true,
      // Poll newly created files to detect when the write is finished
      awaitWriteFinish: {
        pollInterval: 200,
        stabilityThreshold: 1000
      },
      // With node 0.10 on linux, only polling is available
      interval: 1000,
      binaryInterval: 2000
    })

    const started = new Promise(resolve => {
      for (let eventType of [
        'add',
        'addDir',
        'change',
        'unlink',
        'unlinkDir'
      ]) {
        this.watcher.on(eventType, (
          path /*: ?string */,
          stats /*: ?fs.Stats */
        ) => {
          const isInitialScan = this.initialScan && !this.initialScan.flushed
          log.chokidar.debug({ path, stats, isInitialScan }, eventType)
          const newEvent = chokidarEvent.build(eventType, path, stats)
          if (newEvent.type !== eventType) {
            log.info(
              { eventType, event: newEvent },
              'fixed wrong fsevents event type'
            )
          }
          this.buffer.push(newEvent)
          this.events.emit('buffering-start')
        })
      }

      // To detect which files&folders have been removed since the last run of
      // cozy-desktop, we keep all the paths seen by chokidar during its
      // initial scan in @paths to compare them with pouchdb database.
      this.initialScan = {
        paths: [],
        emptyDirRetryCount: 3,
        resolve,
        flushed: false
      }

      this.watcher
        .on('ready', () => this.buffer.switchMode('timeout'))
        .on('raw', (event, path, details) =>
          log.chokidar.debug({ event, path, details }, 'raw')
        )
        .on('error', err => {
          if (err.message === 'watch ENOSPC') {
            log.error(
              'Sorry, the kernel is out of inotify watches! ' +
                'See doc/usage/inotify.md for how to solve this issue.'
            )
          } else {
            log.error({ err })
          }
        })

      log.info(`Now watching ${this.syncPath}`)
    })

    this.running = new Promise((resolve, reject) => {
      this._runningResolve = resolve
      this._runningReject = reject
    })
    return started
  }

  // TODO: Start checksuming as soon as an add/change event is buffered
  // TODO: Put flushed event batches in a queue
  async onFlush(rawEvents /*: ChokidarEvent[] */) {
    log.debug(`Flushed ${rawEvents.length} events`)

    if (this.initialScan) this.initialScan.flushed = true
    this.events.emit('buffering-end')
    syncDir.ensureExistsSync(this)
    this.events.emit('local-start')

    const events = await initialScan.step(rawEvents, this)
    if (!events) return

    log.trace('Prepare events...')
    const preparedEvents /*: LocalEvent[] */ = await prepareEvents.step(
      events,
      this
    )
    log.trace('Done with events preparation.')

    const changes /*: LocalChange[] */ = analysis(preparedEvents, this)

    const normalizedChanges /*: LocalChange[] */ = await normalizePaths.step(
      changes,
      this
    )

    // TODO: Don't even acquire lock changes list is empty
    // FIXME: Shouldn't we acquire the lock before preparing the events?
    const release = await this.pouch.lock(this)
    let target = -1
    try {
      await sendToPrep.step(normalizedChanges, this)
      target = (await this.pouch.db.changes({ limit: 1, descending: true }))
        .last_seq
    } finally {
      this.events.emit('sync-target', target)
      release()
      this.events.emit('local-end')
    }
    if (this.initialScan != null) {
      this.initialScan.resolve()
      this.initialScan = null
    }
  }

  async stop(force /*: ?bool */) {
    if (this.watcher) {
      // XXX manually fire events for added file, because chokidar will cancel
      // them if they are still in the awaitWriteFinish period
      for (let relpath in this.watcher._pendingWrites) {
        try {
          const fullpath = path.join(this.watcher.options.cwd, relpath)
          const curStat = await stater.stat(fullpath)
          this.watcher.emit('add', relpath, curStat)
        } catch (err) {
          log.warn({ err }, 'Could not fire remaining add events')
        }
      }
      await this.watcher.close()
      this.watcher = null
    }
    if (this._runningResolve) {
      this._runningResolve()
      this._runningResolve = null
    }
    this.buffer.switchMode('idle')
    if (force) return Promise.resolve()
    // Give some time for awaitWriteFinish events to be managed
    return new Promise(resolve => {
      setTimeout(resolve, 1000)
    })
  }

  /* Helpers */
  async checksum(filePath /*: string */) /*: Promise<string> */ {
    const absPath = path.join(this.syncPath, filePath)
    return this.checksumer.push(absPath)
  }
}

module.exports = LocalWatcher
<commit_msg>core/local/chokidar/watcher: Increase test buffer timeout<commit_after>/**
 * This is the {@link module:core/local/watcher|local watcher} implementation
 * based on the {@link https://github.com/paulmillr/chokidar|chokidar} library.
 *
 * It's a library that uses nodejs' watch powered by inotify/fsevents (with a
 * fallback on polling).
 *
 * ## Steps
 *
 * 1. {@link module:core/local/chokidar/initial_scan|initial_scan}
 * 2. {@link module:core/local/chokidar/prepare_events|prepare_events}
 * 3. {@link module:core/local/chokidar/analysis|analysis} (macro step)
 * 4. {@link module:core/local/chokidar/send_to_prep|send_to_prep}
 *
 * @module core/local/chokidar/watcher
 * @flow
 */

const autoBind = require('auto-bind')
const Promise = require('bluebird')
const chokidar = require('chokidar')
const path = require('path')

const analysis = require('./analysis')
const checksumer = require('../checksumer')
const chokidarEvent = require('./event')
const LocalEventBuffer = require('./event_buffer')
const initialScan = require('./initial_scan')
const normalizePaths = require('./normalize_paths')
const prepareEvents = require('./prepare_events')
const sendToPrep = require('./send_to_prep')
const stater = require('../stater')
const syncDir = require('../sync_dir')
const logger = require('../../utils/logger')

/*::
import type { Pouch } from '../../pouch'
import type Prep from '../../prep'
import type { Checksumer } from '../checksumer'
import type { ChokidarEvent } from './event'
import type { InitialScan } from './initial_scan'
import type { LocalEvent } from './local_event'
import type { LocalChange } from './local_change'
import type EventEmitter from 'events'
import fs from 'fs'
*/

const log = logger({
  component: 'ChokidarWatcher'
})
log.chokidar = log.child({
  component: 'Chokidar'
})

/**
 * This file contains the filesystem watcher that will trigger operations when
 * a file or a folder is added/removed/changed locally.
 * Operations will be added to the a common operation queue along with the
 * remote operations triggered by the remoteEventWatcher.
 */
class LocalWatcher {
  /*::
  syncPath: string
  prep: Prep
  pouch: Pouch
  events: EventEmitter
  initialScan: ?InitialScan
  checksumer: Checksumer
  watcher: any // chokidar
  buffer: LocalEventBuffer<ChokidarEvent>
  pendingChanges: LocalChange[]
  running: Promise<void>
  _runningResolve: ?Function
  _runningReject: ?Function
  */

  constructor(
    syncPath /*: string */,
    prep /*: Prep */,
    pouch /*: Pouch */,
    events /*: EventEmitter */
  ) {
    this.syncPath = syncPath
    this.prep = prep
    this.pouch = pouch
    this.events = events
    this.checksumer = checksumer.init()
    this.pendingChanges = []

    // XXX: this.onFlush must be bound before being passed to LocalEventBuffer
    autoBind(this)

    // TODO: Read from config
    const timeoutInMs = process.env.NODE_ENV === 'test' ? 2000 : 10000
    this.buffer = new LocalEventBuffer(timeoutInMs, async rawEvents => {
      try {
        await this.onFlush(rawEvents)
      } catch (err) {
        log.error({ err }, 'onFlushError')
        this._runningReject && this._runningReject(err)
      }
    })
  }

  /** Start chokidar, the filesystem watcher
   *
   * @see https://github.com/paulmillr/chokidar
   */
  start() {
    log.debug('Starting...')

    this.watcher = chokidar.watch('.', {
      // Let paths in events be relative to this base path
      cwd: this.syncPath,
      // Ignore our own .system-tmp-cozy-drive directory
      ignored: /(^|[\/\\])\.system-tmp-cozy-drive/, // eslint-disable-line no-useless-escape
      // Don't follow symlinks
      followSymlinks: false,
      // The stats object is used in methods below
      alwaysStat: true,
      // Watching on Windows seems to lock dirs with subdirs, preventing them
      // from being renamed/moved/deleted.
      usePolling: process.platform === 'win32',
      // Filter out artifacts from editors with atomic writes
      atomic: true,
      // Poll newly created files to detect when the write is finished
      awaitWriteFinish: {
        pollInterval: 200,
        stabilityThreshold: 1000
      },
      // With node 0.10 on linux, only polling is available
      interval: 1000,
      binaryInterval: 2000
    })

    const started = new Promise(resolve => {
      for (let eventType of [
        'add',
        'addDir',
        'change',
        'unlink',
        'unlinkDir'
      ]) {
        this.watcher.on(eventType, (
          path /*: ?string */,
          stats /*: ?fs.Stats */
        ) => {
          const isInitialScan = this.initialScan && !this.initialScan.flushed
          log.chokidar.debug({ path, stats, isInitialScan }, eventType)
          const newEvent = chokidarEvent.build(eventType, path, stats)
          if (newEvent.type !== eventType) {
            log.info(
              { eventType, event: newEvent },
              'fixed wrong fsevents event type'
            )
          }
          this.buffer.push(newEvent)
          this.events.emit('buffering-start')
        })
      }

      // To detect which files&folders have been removed since the last run of
      // cozy-desktop, we keep all the paths seen by chokidar during its
      // initial scan in @paths to compare them with pouchdb database.
      this.initialScan = {
        paths: [],
        emptyDirRetryCount: 3,
        resolve,
        flushed: false
      }

      this.watcher
        .on('ready', () => this.buffer.switchMode('timeout'))
        .on('raw', (event, path, details) =>
          log.chokidar.debug({ event, path, details }, 'raw')
        )
        .on('error', err => {
          if (err.message === 'watch ENOSPC') {
            log.error(
              'Sorry, the kernel is out of inotify watches! ' +
                'See doc/usage/inotify.md for how to solve this issue.'
            )
          } else {
            log.error({ err })
          }
        })

      log.info(`Now watching ${this.syncPath}`)
    })

    this.running = new Promise((resolve, reject) => {
      this._runningResolve = resolve
      this._runningReject = reject
    })
    return started
  }

  // TODO: Start checksuming as soon as an add/change event is buffered
  // TODO: Put flushed event batches in a queue
  async onFlush(rawEvents /*: ChokidarEvent[] */) {
    log.debug(`Flushed ${rawEvents.length} events`)

    if (this.initialScan) this.initialScan.flushed = true
    this.events.emit('buffering-end')
    syncDir.ensureExistsSync(this)
    this.events.emit('local-start')

    const events = await initialScan.step(rawEvents, this)
    if (!events) return

    log.trace('Prepare events...')
    const preparedEvents /*: LocalEvent[] */ = await prepareEvents.step(
      events,
      this
    )
    log.trace('Done with events preparation.')

    const changes /*: LocalChange[] */ = analysis(preparedEvents, this)

    const normalizedChanges /*: LocalChange[] */ = await normalizePaths.step(
      changes,
      this
    )

    // TODO: Don't even acquire lock changes list is empty
    // FIXME: Shouldn't we acquire the lock before preparing the events?
    const release = await this.pouch.lock(this)
    let target = -1
    try {
      await sendToPrep.step(normalizedChanges, this)
      target = (await this.pouch.db.changes({ limit: 1, descending: true }))
        .last_seq
    } finally {
      this.events.emit('sync-target', target)
      release()
      this.events.emit('local-end')
    }
    if (this.initialScan != null) {
      this.initialScan.resolve()
      this.initialScan = null
    }
  }

  async stop(force /*: ?bool */) {
    log.debug('Stopping watcher...')
    if (this.watcher) {
      // XXX manually fire events for added file, because chokidar will cancel
      // them if they are still in the awaitWriteFinish period
      for (let relpath in this.watcher._pendingWrites) {
        try {
          const fullpath = path.join(this.watcher.options.cwd, relpath)
          const curStat = await stater.stat(fullpath)
          this.watcher.emit('add', relpath, curStat)
        } catch (err) {
          log.warn({ err }, 'Could not fire remaining add events')
        }
      }
      await this.watcher.close()
      this.watcher = null
    }
    if (this._runningResolve) {
      this._runningResolve()
      this._runningResolve = null
    }
    this.buffer.switchMode('idle')
    if (force) return Promise.resolve()
    // Give some time for awaitWriteFinish events to be managed
    return new Promise(resolve => {
      setTimeout(resolve, 1000)
    })
  }

  /* Helpers */
  async checksum(filePath /*: string */) /*: Promise<string> */ {
    const absPath = path.join(this.syncPath, filePath)
    return this.checksumer.push(absPath)
  }
}

module.exports = LocalWatcher
<|endoftext|>


<commit_before>import axios from 'axios'
import Vuex from 'vuex'
import Vue from 'vue'

Vue.use(Vuex)

// we should just use this to get the
// available endpoints, and then go from there
const API_ROOT = '/api/'

const actions = {
  populateEndpoints ({ commit }) {
    axios.get(API_ROOT)
      .then((response) => {
        commit('GET_ENDPOINTS', response.data)
      })
  },
  loadCase ({ commit }, { url }) {
    axios.get(url)
      .then((response) => {
        commit('SET_CASE_IN_PROGRESS', response.data)
      })
  },
  startNewCase ({ commit }, { uri }) {
    axios.post(this.getters.endpoints.cases, {uri: uri})
      .then((response) => {
        commit('SET_CASE_IN_PROGRESS', response.data)
      })
  }
}

const store = new Vuex.Store({
  state: {
    // the case that we are working on, either selected or
    // created on the open and import page
    caseInProgress: {},

    // the top-level URLs returned from a call to the base `/api` URL
    topLevelEndpoints: {}
  },
  getters: {
    caseInProgress (state) {
      return state.caseInProgress
    },
    endpoints (state) {
      return state.topLevelEndpoints
    },
    candidates (state) {
      return state.caseInProgress ? state.caseInProgress.candidates : []
    },
    candidatesExist (state, getters) {
      return getters.candidates && getters.candidates.length > 0
    },
    nodules (state) {
      return state.caseInProgress ? state.caseInProgress.nodules : []
    },
    nodulesExist (state, getters) {
      return getters.nodules && getters.nodules.length > 0
    },
    imagePaths (state) {
      if (state.caseInProgress.series) {
        return state.caseInProgress.series.images.map((x) => x.preview_url)
      } else {
        return []
      }
    }
  },
  mutations: {
    GET_ENDPOINTS (state, endpoints) {
      state.topLevelEndpoints = endpoints
    },
    SET_CASE_IN_PROGRESS (state, _case) {
      state.caseInProgress = _case
    }
  },
  actions
})

export default store
<commit_msg>Add a refreshCase action<commit_after>import axios from 'axios'
import Vuex from 'vuex'
import Vue from 'vue'

Vue.use(Vuex)

// we should just use this to get the
// available endpoints, and then go from there
const API_ROOT = '/api/'

const actions = {
  populateEndpoints ({ commit }) {
    axios.get(API_ROOT)
      .then((response) => {
        commit('GET_ENDPOINTS', response.data)
      })
  },
  loadCase ({ commit }, { url }) {
    axios.get(url)
      .then((response) => {
        commit('SET_CASE_IN_PROGRESS', response.data)
      })
  },
  startNewCase ({ commit }, { uri }) {
    axios.post(this.getters.endpoints.cases, {uri: uri})
      .then((response) => {
        commit('SET_CASE_IN_PROGRESS', response.data)
      })
  },
  refreshCase ({ dispatch }) {
    return dispatch('loadCase', store.state.caseInProgress)
  }
}

const store = new Vuex.Store({
  state: {
    // the case that we are working on, either selected or
    // created on the open and import page
    caseInProgress: {},

    // the top-level URLs returned from a call to the base `/api` URL
    topLevelEndpoints: {}
  },
  getters: {
    caseInProgress (state) {
      return state.caseInProgress
    },
    endpoints (state) {
      return state.topLevelEndpoints
    },
    candidates (state) {
      return state.caseInProgress ? state.caseInProgress.candidates : []
    },
    candidatesExist (state, getters) {
      return getters.candidates && getters.candidates.length > 0
    },
    nodules (state) {
      return state.caseInProgress ? state.caseInProgress.nodules : []
    },
    nodulesExist (state, getters) {
      return getters.nodules && getters.nodules.length > 0
    },
    imagePaths (state) {
      if (state.caseInProgress.series) {
        return state.caseInProgress.series.images.map((x) => x.preview_url)
      } else {
        return []
      }
    }
  },
  mutations: {
    GET_ENDPOINTS (state, endpoints) {
      state.topLevelEndpoints = endpoints
    },
    SET_CASE_IN_PROGRESS (state, _case) {
      state.caseInProgress = _case
    }
  },
  actions
})

export default store
<|endoftext|>