diff options
Diffstat (limited to 'addons/web/static/src')
8 files changed, 85 insertions, 42 deletions
diff --git a/addons/web/static/src/js/chrome/abstract_web_client.js b/addons/web/static/src/js/chrome/abstract_web_client.js index ea8fdc24a18..7082f26e69d 100644 --- a/addons/web/static/src/js/chrome/abstract_web_client.js +++ b/addons/web/static/src/js/chrome/abstract_web_client.js @@ -205,6 +205,10 @@ var AbstractWebClient = Widget.extend(ServiceProviderMixin, KeyboardNavigationMi }); } } else { + // ignore Chrome video internal error: https://crbug.com/809574 + if (!error && message === 'ResizeObserver loop limit exceeded') { + return; + } var traceback = error ? error.stack : ''; crash_manager.show_error({ type: _t("Odoo Client Error"), diff --git a/addons/web/static/src/js/chrome/apps_menu.js b/addons/web/static/src/js/chrome/apps_menu.js index f6535ed764c..eb7faaa971b 100644 --- a/addons/web/static/src/js/chrome/apps_menu.js +++ b/addons/web/static/src/js/chrome/apps_menu.js @@ -41,6 +41,9 @@ var AppsMenu = Widget.extend({ * Open the first app in the list of apps */ openFirstApp: function () { + if (!this._apps.length) { + return + } var firstApp = this._apps[0]; this._openApp(firstApp); }, diff --git a/addons/web/static/src/js/fields/special_fields.js b/addons/web/static/src/js/fields/special_fields.js index 5057b9de33f..e9dcbc592a3 100644 --- a/addons/web/static/src/js/fields/special_fields.js +++ b/addons/web/static/src/js/fields/special_fields.js @@ -122,7 +122,8 @@ var FieldTimezoneMismatch = FieldSelection.extend({ }); var FieldReportLayout = relational_fields.FieldMany2One.extend({ - supportedFieldTypes: ['many2one', 'selection'], + // this widget is not generic, so we disable its studio use + // supportedFieldTypes: ['many2one', 'selection'], events: _.extend({}, relational_fields.FieldMany2One.prototype.events, { 'click img': '_onImgClicked', }), diff --git a/addons/web/static/src/js/services/crash_manager.js b/addons/web/static/src/js/services/crash_manager.js index e1da816c8e5..31aa178dc1b 100644 --- a/addons/web/static/src/js/services/crash_manager.js +++ b/addons/web/static/src/js/services/crash_manager.js @@ -22,6 +22,7 @@ var map_title ={ var CrashManager = core.Class.extend({ init: function() { this.active = true; + this.isConnected = true; }, enable: function () { this.active = true; @@ -29,8 +30,29 @@ var CrashManager = core.Class.extend({ disable: function () { this.active = false; }, - rpc_error: function(error) { + handleLostConnection: function () { var self = this; + if (!this.isConnected) { + // already handled, nothing to do. This can happen when several + // rpcs are done in parallel and fail because of a lost connection. + return; + } + this.isConnected = false; + var delay = 2000; + core.bus.trigger('connection_lost'); + + setTimeout(function checkConnection() { + ajax.jsonRpc('/web/webclient/version_info', 'call', {}, {shadow:true}).then(function () { + core.bus.trigger('connection_restored'); + self.isConnected = true; + }).fail(function () { + // exponential backoff, with some jitter + delay = (delay * 1.5) + 500*Math.random(); + setTimeout(checkConnection, delay); + }); + }, delay); + }, + rpc_error: function(error) { if (!this.active) { return; } @@ -38,16 +60,7 @@ var CrashManager = core.Class.extend({ return; } if (error.code === -32098) { - core.bus.trigger('connection_lost'); - this.connection_lost = true; - var timeinterval = setInterval(function() { - var options = {shadow: true}; - ajax.jsonRpc('/web/webclient/version_info', 'call', {}, options).then(function () { - clearInterval(timeinterval); - core.bus.trigger('connection_restored'); - self.connection_lost = false; - }); - }, 2000); + this.handleLostConnection(); return; } var handler = core.crash_registry.get(error.data.name, true); diff --git a/addons/web/static/src/js/views/basic/basic_controller.js b/addons/web/static/src/js/views/basic/basic_controller.js index 7f578da077d..f26a9cbe737 100644 --- a/addons/web/static/src/js/views/basic/basic_controller.js +++ b/addons/web/static/src/js/views/basic/basic_controller.js @@ -38,6 +38,10 @@ var BasicController = AbstractController.extend(FieldManagerMixin, { FieldManagerMixin.init.call(this, this.model); this.mode = params.mode || 'readonly'; this.handle = this.initialState.id; + // savingDef is used to ensure that we always wait for pending save + // operations to complete before checking if there are changes to + // discard when discardChanges is called + this.savingDef = $.when(); }, /** * @override @@ -102,14 +106,15 @@ var BasicController = AbstractController.extend(FieldManagerMixin, { return true; }, /** - * Waits for the mutex to be unlocked and then calls _.discardChanges. + * Waits for the mutex to be unlocked and for changes to be saved, then + * calls _.discardChanges. * This ensures that the confirm dialog isn't displayed directly if there is * a pending 'write' rpc. * * @see _.discardChanges */ discardChanges: function (recordID, options) { - return this.mutex.exec(function () {}) + return $.when(this.mutex.getUnlockedDef(), this.savingDef) .then(this._discardChanges.bind(this, recordID || this.handle, options)); }, /** @@ -183,11 +188,12 @@ var BasicController = AbstractController.extend(FieldManagerMixin, { // mutex-protected as commitChanges function of x2m has to be aware of // all final changes made to a row. var self = this; - return this.mutex.getUnlockedDef().then(function () { + this.savingDef = this.mutex.getUnlockedDef().then(function () { return self.renderer.commitChanges(recordID || self.handle).then(function () { return self.mutex.exec(self._saveRecord.bind(self, recordID, options)); }); }); + return this.savingDef; }, /** * @override diff --git a/addons/web/static/src/js/views/field_manager_mixin.js b/addons/web/static/src/js/views/field_manager_mixin.js index 343730cc7a4..bc61e92a89a 100644 --- a/addons/web/static/src/js/views/field_manager_mixin.js +++ b/addons/web/static/src/js/views/field_manager_mixin.js @@ -130,8 +130,10 @@ var FieldManagerMixin = { if ('offset' in data) { params.offset = data.offset; } - this.model.reload(data.id, params).then(function (db_id) { - data.on_success(self.model.get(db_id)); + this.mutex.exec(function () { + return self.model.reload(data.id, params).then(function (db_id) { + data.on_success(self.model.get(db_id)); + }); }); }, /** diff --git a/addons/web/static/src/js/views/kanban/kanban_controller.js b/addons/web/static/src/js/views/kanban/kanban_controller.js index 2383a0ac730..8c43857277c 100644 --- a/addons/web/static/src/js/views/kanban/kanban_controller.js +++ b/addons/web/static/src/js/views/kanban/kanban_controller.js @@ -210,15 +210,17 @@ var KanbanController = BasicController.extend({ */ _onAddColumn: function (ev) { var self = this; - this.model.createGroup(ev.data.value, this.handle).then(function () { - var state = self.model.get(self.handle, {raw: true}); - var ids = _.pluck(state.data, 'res_id').filter(_.isNumber); - return self._resequenceColumns(ids); - }).then(function () { - return self.update({}, {reload: false}); - }).then(function () { - self._updateButtons(); - self.renderer.quickCreateToggleFold(); + this.mutex.exec(function () { + return self.model.createGroup(ev.data.value, this.handle).then(function () { + var state = self.model.get(self.handle, {raw: true}); + var ids = _.pluck(state.data, 'res_id').filter(_.isNumber); + return self._resequenceColumns(ids); + }).then(function () { + return self.update({}, {reload: false}); + }).then(function () { + self._updateButtons(); + self.renderer.quickCreateToggleFold(); + }); }); }, /** @@ -271,11 +273,16 @@ var KanbanController = BasicController.extend({ * @private */ _onButtonNew: function () { + var self = this; var state = this.model.get(this.handle, {raw: true}); var quickCreateEnabled = this.quickCreateEnabled && viewUtils.isQuickCreateEnabled(state); if (this.on_create === 'quick_create' && quickCreateEnabled && state.data.length) { - // Activate the quick create in the first column - this.renderer.addQuickCreate(); + // activate the quick create in the first column when the mutex is + // unlocked, to ensure that there is no pending re-rendering that + // would remove it (e.g. if we are currently adding a new column) + this.mutex.getUnlockedDef().then(function () { + self.renderer.addQuickCreate(); + }); } else if (this.on_create && this.on_create !== 'quick_create') { // Execute the given action this.do_action(this.on_create, { diff --git a/addons/web/static/src/js/views/pivot/pivot_model.js b/addons/web/static/src/js/views/pivot/pivot_model.js index 5a6423fbe34..607cf2d7b2f 100644 --- a/addons/web/static/src/js/views/pivot/pivot_model.js +++ b/addons/web/static/src/js/views/pivot/pivot_model.js @@ -321,7 +321,7 @@ var PivotModel = AbstractModel.extend({ }; } return { - colGroupBys: this.data.main_col.groupbys, + colGroupBys: this.data.main_col.root.groupbys, context: this.data.context, domain: this.data.domain, compare: this.data.compare, @@ -331,7 +331,7 @@ var PivotModel = AbstractModel.extend({ mainColWidth: this.data.main_col.width, measures: this.data.measures, rows: !isRaw && this._computeRows(), - rowGroupBys: this.data.main_row.groupbys, + rowGroupBys: this.data.main_row.root.groupbys, sortedColumn: this.data.sorted_column, }; }, @@ -432,22 +432,19 @@ var PivotModel = AbstractModel.extend({ return this._loadData(); } - var old_row_root = this.data.main_row.root; - var old_col_root = this.data.main_col.root; + var old_row = this.data.main_row; + var old_col = this.data.main_col; return this._loadData().then(function () { - var new_groupby_length; if (!('groupBy' in params) && !('pivot_row_groupby' in (params.context || {}))) { // we only update the row groupbys according to the old groupbys // if we don't have the key 'groupBy' in params. In that case, // we want to have the full open state for the groupbys. - self._updateTree(old_row_root, self.data.main_row.root); - new_groupby_length = self._getHeaderDepth(self.data.main_row.root) - 1; - self.data.main_row.groupbys = old_row_root.groupbys.slice(0, new_groupby_length); + self._updateTree(old_row.root, self.data.main_row.root); + self._updateMainGroupBys(old_row, self.data.main_row); } - self._updateTree(old_col_root, self.data.main_col.root); - new_groupby_length = self._getHeaderDepth(self.data.main_col.root) - 1; - self.data.main_row.groupbys = old_row_root.groupbys.slice(0, new_groupby_length); + self._updateTree(old_col.root, self.data.main_col.root); + self._updateMainGroupBys(old_col, self.data.main_col); }); }, /** @@ -1052,9 +1049,6 @@ var PivotModel = AbstractModel.extend({ } } - this.data.main_row.groupbys = rowGroupBys; - this.data.main_col.groupbys = colGroupBys; - main_row_header.other_root = main_col_header; main_col_header.other_root = main_row_header; @@ -1124,6 +1118,19 @@ var PivotModel = AbstractModel.extend({ } }, /** + * Updates the groupBys that the main[row | col] holds + * + * @private + * @param {Object} old: The main abstract header before reload + * @param {Object} main: The main abstract header after reload + * + */ + _updateMainGroupBys: function (old, main) { + var new_groupby_length = this._getHeaderDepth(main.root) - 1; + var new_groupby_list = old.root.groupbys.slice(0, new_groupby_length); + main.root.groupbys = new_groupby_list; + }, + /** * @param {Object} old_tree * @param {Object} new_tree */ |
