合并企业版代码(未测试,先提交到测试分支)
This commit is contained in:
11
web_enterprise/static/src/legacy/js/apps.js
Normal file
11
web_enterprise/static/src/legacy/js/apps.js
Normal file
@@ -0,0 +1,11 @@
|
||||
odoo.define('web_enterprise.apps', function (require) {
|
||||
"use strict";
|
||||
|
||||
var Apps = require('web.Apps');
|
||||
|
||||
Apps.include({
|
||||
// Do nothing on update count as needactions have been removed in enterprise
|
||||
_on_update_count: function() {},
|
||||
});
|
||||
|
||||
});
|
||||
145
web_enterprise/static/src/legacy/js/control_panel.js
Normal file
145
web_enterprise/static/src/legacy/js/control_panel.js
Normal file
@@ -0,0 +1,145 @@
|
||||
odoo.define('web_enterprise.ControlPanel', function (require) {
|
||||
"use strict";
|
||||
|
||||
const ControlPanel = require('web.ControlPanel');
|
||||
const { device } = require('web.config');
|
||||
const { patch } = require('web.utils');
|
||||
|
||||
const { onMounted, useExternalListener, useRef, useState, useEffect } = owl;
|
||||
const STICKY_CLASS = 'o_mobile_sticky';
|
||||
|
||||
if (!device.isMobile) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Control panel: mobile layout
|
||||
*
|
||||
* This patch handles the scrolling behaviour of the control panel in a mobile
|
||||
* environment: the panel sticks to the top of the window when scrolling into
|
||||
* the view. It is revealed when scrolling up and hiding when scrolling down.
|
||||
* The panel's position is reset to default when at the top of the view.
|
||||
*/
|
||||
patch(ControlPanel.prototype, 'web_enterprise.ControlPanel', {
|
||||
setup() {
|
||||
this._super();
|
||||
|
||||
this.controlPanelRef = useRef("controlPanel");
|
||||
|
||||
this.state = useState({
|
||||
showSearchBar: false,
|
||||
showMobileSearch: false,
|
||||
showViewSwitcher: false,
|
||||
});
|
||||
|
||||
this.onWindowClick = this._onWindowClick.bind(this);
|
||||
this.onScrollThrottled = this._onScrollThrottled.bind(this);
|
||||
|
||||
useExternalListener(window, "click", this.onWindowClick);
|
||||
useEffect(() => {
|
||||
const scrollingEl = this._getScrollingElement();
|
||||
scrollingEl.addEventListener("scroll", this.onScrollThrottled);
|
||||
this.controlPanelRef.el.style.top = "0px";
|
||||
return () => {
|
||||
scrollingEl.removeEventListener("scroll", this.onScrollThrottled);
|
||||
}
|
||||
})
|
||||
onMounted(() => {
|
||||
this.oldScrollTop = 0;
|
||||
this.lastScrollTop = 0;
|
||||
this.initialScrollTop = this._getScrollingElement().scrollTop;
|
||||
});
|
||||
},
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Private
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
_getScrollingElement() {
|
||||
return this.controlPanelRef.el.parentElement;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get today's date (number).
|
||||
* @private
|
||||
* @returns {number}
|
||||
*/
|
||||
_getToday() {
|
||||
return new Date().getDate();
|
||||
},
|
||||
|
||||
/**
|
||||
* Reset mobile search state
|
||||
* @private
|
||||
*/
|
||||
_resetSearchState() {
|
||||
Object.assign(this.state, {
|
||||
showSearchBar: false,
|
||||
showMobileSearch: false,
|
||||
showViewSwitcher: false,
|
||||
});
|
||||
},
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Handlers
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Show or hide the control panel on the top screen.
|
||||
* The function is throttled to avoid refreshing the scroll position more
|
||||
* often than necessary.
|
||||
* @private
|
||||
*/
|
||||
_onScrollThrottled() {
|
||||
if (!this.controlPanelRef.el || this.isScrolling) {
|
||||
return;
|
||||
}
|
||||
this.isScrolling = true;
|
||||
requestAnimationFrame(() => this.isScrolling = false);
|
||||
|
||||
const scrollTop = this._getScrollingElement().scrollTop;
|
||||
const delta = Math.round(scrollTop - this.oldScrollTop);
|
||||
|
||||
if (scrollTop > this.initialScrollTop) {
|
||||
// Beneath initial position => sticky display
|
||||
this.controlPanelRef.el.classList.add(STICKY_CLASS);
|
||||
this.lastScrollTop = delta < 0 ?
|
||||
// Going up
|
||||
Math.min(0, this.lastScrollTop - delta) :
|
||||
// Going down | not moving
|
||||
Math.max(-this.controlPanelRef.el.offsetHeight, -this.controlPanelRef.el.offsetTop - delta);
|
||||
this.controlPanelRef.el.style.top = `${this.lastScrollTop}px`;
|
||||
} else {
|
||||
// Above initial position => standard display
|
||||
this.controlPanelRef.el.classList.remove(STICKY_CLASS);
|
||||
this.lastScrollTop = 0;
|
||||
}
|
||||
this.oldScrollTop = scrollTop;
|
||||
},
|
||||
|
||||
/**
|
||||
* Reset mobile search state on switch view.
|
||||
* @private
|
||||
*/
|
||||
_onSwitchView() {
|
||||
this._resetSearchState();
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onWindowClick(ev) {
|
||||
if (
|
||||
this.state.showViewSwitcher &&
|
||||
!ev.target.closest('.o_cp_switch_buttons')
|
||||
) {
|
||||
this.state.showViewSwitcher = false;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
patch(ControlPanel, 'web_enterprise.ControlPanel', {
|
||||
template: 'web_enterprise._ControlPanel',
|
||||
});
|
||||
});
|
||||
75
web_enterprise/static/src/legacy/js/core/dialog.js
Normal file
75
web_enterprise/static/src/legacy/js/core/dialog.js
Normal file
@@ -0,0 +1,75 @@
|
||||
odoo.define("web_enterprise.Dialog", function(require) {
|
||||
"use strict";
|
||||
|
||||
const config = require("web.config");
|
||||
if (!config.device.isMobile) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Dialog = require("web.Dialog");
|
||||
|
||||
Dialog.include({
|
||||
/**
|
||||
* @override
|
||||
*
|
||||
* @param {Widget} parent
|
||||
* @param {Array} options.headerButtons
|
||||
*/
|
||||
init(parent, { headerButtons }) {
|
||||
this._super.apply(this, arguments);
|
||||
this.headerButtons = headerButtons || [];
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders the header as a "Top App Bar" layout :
|
||||
* - navigation icon: right arrow that closes the dialog;
|
||||
* - title: same as on desktop;
|
||||
* - action buttons: optional actions related to the current screen.
|
||||
* Only applied for fullscreen dialog.
|
||||
*
|
||||
* Also, get the current scroll position for mobile devices in order to
|
||||
* maintain offset while dialog is closed.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
async willStart() {
|
||||
const prom = await this._super.apply(this, arguments);
|
||||
if (this.renderHeader && this.fullscreen) {
|
||||
const $modalHeader = this.$modal.find(".modal-header");
|
||||
$modalHeader.find("button.btn-close").remove();
|
||||
const $navigationBtn = $("<button>", {
|
||||
class: "btn fa fa-arrow-left",
|
||||
"data-bs-dismiss": "modal",
|
||||
"aria-label": "close",
|
||||
});
|
||||
$modalHeader.prepend($navigationBtn);
|
||||
const $btnContainer = $("<div>");
|
||||
this._setButtonsTo($btnContainer, this.headerButtons);
|
||||
$modalHeader.append($btnContainer);
|
||||
}
|
||||
// need to get scrollPosition prior opening the dialog else body will scroll to
|
||||
// top due to fixed position applied on it with help of 'modal-open' class.
|
||||
this.scrollPosition = {
|
||||
top: window.scrollY || document.documentElement.scrollTop,
|
||||
left: window.scrollX || document.documentElement.scrollLeft,
|
||||
};
|
||||
return prom;
|
||||
},
|
||||
|
||||
/**
|
||||
* Scroll to original position while closing modal in mobile devices
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
destroy() {
|
||||
if (this.$modal && $('.modal[role="dialog"]').filter(":visible").length <= 1) {
|
||||
// in case of multiple open dialogs, only reset scroll while closing the last one
|
||||
// (it can be done only if there's no fixed position on body and thus by removing
|
||||
// 'modal-open' class responsible for fixed position)
|
||||
this.$modal.closest("body").removeClass("modal-open");
|
||||
window.scrollTo(this.scrollPosition);
|
||||
}
|
||||
this._super(...arguments);
|
||||
},
|
||||
});
|
||||
});
|
||||
104
web_enterprise/static/src/legacy/js/search_panel_mobile.js
Normal file
104
web_enterprise/static/src/legacy/js/search_panel_mobile.js
Normal file
@@ -0,0 +1,104 @@
|
||||
odoo.define("web.SearchPanel.Small", function (require) {
|
||||
"use strict";
|
||||
|
||||
const SearchPanel = require("web.searchPanel");
|
||||
const { device } = require("web.config");
|
||||
const { patch } = require('web.utils');
|
||||
|
||||
if (!device.isMobile) {
|
||||
return;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
const isFilter = (s) => s.type === "filter";
|
||||
|
||||
/**
|
||||
* @param {Map} values
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
function nameOfCheckedValues(values) {
|
||||
const names = [];
|
||||
for (const [ , value] of values) {
|
||||
if (value.checked) {
|
||||
names.push(value.display_name);
|
||||
}
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
patch(SearchPanel.prototype, "web_enterprise.SearchPanel.Mobile", {
|
||||
setup() {
|
||||
this._super(...arguments);
|
||||
this.state.showMobileSearch = false;
|
||||
},
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
// Private
|
||||
//-----------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns a formatted version of the active categories to populate
|
||||
* the selection banner of the control panel summary.
|
||||
* @private
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
_getCategorySelection() {
|
||||
const activeCategories = this.model.get("sections",
|
||||
(s) => s.type === "category" && s.activeValueId
|
||||
);
|
||||
const selection = [];
|
||||
for (const category of activeCategories) {
|
||||
const parentIds = this._getAncestorValueIds(
|
||||
category,
|
||||
category.activeValueId
|
||||
);
|
||||
const orderedCategoryNames = [
|
||||
...parentIds,
|
||||
category.activeValueId,
|
||||
].map(
|
||||
(valueId) => category.values.get(valueId).display_name
|
||||
);
|
||||
selection.push({
|
||||
values: orderedCategoryNames,
|
||||
icon: category.icon,
|
||||
color: category.color,
|
||||
});
|
||||
}
|
||||
return selection;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a formatted version of the active filters to populate
|
||||
* the selection banner of the control panel summary.
|
||||
* @private
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
_getFilterSelection() {
|
||||
const filters = this.model.get("sections", isFilter);
|
||||
const selection = [];
|
||||
for (const { groups, values, icon, color } of filters) {
|
||||
let filterValues;
|
||||
if (groups) {
|
||||
filterValues = Object.keys(groups)
|
||||
.map((groupId) =>
|
||||
nameOfCheckedValues(groups[groupId].values)
|
||||
)
|
||||
.flat();
|
||||
} else if (values) {
|
||||
filterValues = nameOfCheckedValues(values);
|
||||
}
|
||||
if (filterValues.length) {
|
||||
selection.push({ values: filterValues, icon, color });
|
||||
}
|
||||
}
|
||||
return selection;
|
||||
},
|
||||
});
|
||||
|
||||
patch(SearchPanel, "web_enterprise.SearchPanel.Mobile", {
|
||||
template: "web_enterprise.Legacy.SearchPanel.Mobile",
|
||||
});
|
||||
});
|
||||
121
web_enterprise/static/src/legacy/js/views/barcode_fields.js
Normal file
121
web_enterprise/static/src/legacy/js/views/barcode_fields.js
Normal file
@@ -0,0 +1,121 @@
|
||||
odoo.define('web_mobile.barcode_fields', function (require) {
|
||||
"use strict";
|
||||
|
||||
var field_registry = require('web.field_registry');
|
||||
require('web._field_registry');
|
||||
var relational_fields = require('web.relational_fields');
|
||||
|
||||
const { _t } = require('web.core');
|
||||
const BarcodeScanner = require('@web/webclient/barcode/barcode_scanner');
|
||||
|
||||
/**
|
||||
* Override the Many2One to open a dialog in mobile.
|
||||
*/
|
||||
var FieldMany2OneBarcode = relational_fields.FieldMany2One.extend({
|
||||
template: "FieldMany2OneBarcode",
|
||||
events: _.extend({}, relational_fields.FieldMany2One.prototype.events, {
|
||||
'click .o_barcode': '_onBarcodeButtonClick',
|
||||
}),
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
start: function () {
|
||||
var result = this._super.apply(this, arguments);
|
||||
this._startBarcode();
|
||||
return result;
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* External button is visible
|
||||
*
|
||||
* @return {boolean}
|
||||
* @private
|
||||
*/
|
||||
_isExternalButtonVisible: function () {
|
||||
return this.$external_button.is(':visible');
|
||||
},
|
||||
/**
|
||||
* Hide the search more option
|
||||
*
|
||||
* @param {Array} values
|
||||
*/
|
||||
_manageSearchMore(values) {
|
||||
return values;
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_renderEdit: function () {
|
||||
this._super.apply(this, arguments);
|
||||
// Hide button if a record is set or external button is visible
|
||||
if (this.$barcode_button) {
|
||||
this.$barcode_button.toggle(!this._isExternalButtonVisible());
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Initialisation of barcode button
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_startBarcode: function () {
|
||||
this.$barcode_button = this.$('.o_barcode');
|
||||
// Hide button if a record is set
|
||||
this.$barcode_button.toggle(!this.isSet());
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* On click on button
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
async _onBarcodeButtonClick() {
|
||||
const barcode = await BarcodeScanner.scanBarcode();
|
||||
if (barcode) {
|
||||
this._onBarcodeScanned(barcode);
|
||||
if ('vibrate' in window.navigator) {
|
||||
window.navigator.vibrate(100);
|
||||
}
|
||||
} else {
|
||||
this.displayNotification({
|
||||
type: 'warning',
|
||||
message: 'Please, scan again !',
|
||||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* When barcode is scanned
|
||||
*
|
||||
* @param barcode
|
||||
* @private
|
||||
*/
|
||||
async _onBarcodeScanned(barcode) {
|
||||
const results = await this._search(barcode);
|
||||
const records = results.filter(r => !!r.id);
|
||||
if (records.length === 1) {
|
||||
this._setValue({ id: records[0].id });
|
||||
} else {
|
||||
const dynamicFilters = [{
|
||||
description: _.str.sprintf(_t('Quick search: %s'), barcode),
|
||||
domain: [['id', 'in', records.map(r => r.id)]],
|
||||
}];
|
||||
this._searchCreatePopup("search", false, {}, dynamicFilters);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (BarcodeScanner.isBarcodeScannerSupported()) {
|
||||
field_registry.add('many2one_barcode', FieldMany2OneBarcode);
|
||||
}
|
||||
|
||||
return FieldMany2OneBarcode;
|
||||
});
|
||||
@@ -0,0 +1,114 @@
|
||||
odoo.define('web_enterprise.BasicRenderer', function (require) {
|
||||
"use strict";
|
||||
|
||||
const config = require('web.config');
|
||||
if (!config.device.isMobile) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* This file defines the MobileBasicRenderer, an extension of the BasicRenderer
|
||||
* implementing some tweaks to improve the UX in mobile.
|
||||
*/
|
||||
|
||||
const BasicRenderer = require('web.BasicRenderer');
|
||||
|
||||
BasicRenderer.include({
|
||||
SHOW_AFTER_DELAY: 250,
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.showTimer = undefined;
|
||||
this.tooltipNodes = [];
|
||||
this._onTouchStartTooltipBind = this._onTouchStartTooltip.bind(this);
|
||||
this._onTouchEndTooltipBind = this._onTouchEndTooltip.bind(this);
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
on_attach_callback() {
|
||||
this._super(...arguments);
|
||||
this._addListener();
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
on_detach_callback() {
|
||||
this._removeListeners();
|
||||
this._super(...arguments);
|
||||
},
|
||||
|
||||
_addListener: function () {
|
||||
this.tooltipNodes.forEach((nodeElement) => {
|
||||
nodeElement.addEventListener('touchstart', this._onTouchStartTooltipBind);
|
||||
nodeElement.addEventListener('touchend', this._onTouchEndTooltipBind);
|
||||
nodeElement.classList.add('o_user_select_none');
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Allow to change when the tooltip appears
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_addFieldTooltip: function (widget, $node) {
|
||||
this._super(...arguments);
|
||||
$node = $node.length ? $node : widget.$el;
|
||||
const nodeElement = $node[0];
|
||||
if (!this.tooltipNodes.some(node => node === nodeElement)) {
|
||||
this.tooltipNodes.push(nodeElement);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_getTooltipOptions: function () {
|
||||
return Object.assign({}, this._super(...arguments), {
|
||||
trigger: 'manual',
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_render: function () {
|
||||
return this._super(...arguments).then(() => {
|
||||
this._addListener();
|
||||
});
|
||||
},
|
||||
_removeListeners: function () {
|
||||
while (this.tooltipNodes.length) {
|
||||
const node = this.tooltipNodes.shift();
|
||||
node.removeEventListener('touchstart', this._onTouchStartTooltipBind);
|
||||
node.removeEventListener('touchend', this._onTouchEndTooltipBind);
|
||||
node.classList.remove('o_user_select_none');
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {TouchEvent} event
|
||||
*/
|
||||
_onTouchEndTooltip: function (event) {
|
||||
clearTimeout(this.showTimer);
|
||||
const $node = $(event.target);
|
||||
$node.tooltip('hide');
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {TouchEvent} event
|
||||
*/
|
||||
_onTouchStartTooltip: function (event) {
|
||||
// Exclude children element from this handler
|
||||
if (!event.target.classList.contains('o_user_select_none')) {
|
||||
return;
|
||||
}
|
||||
const $node = $(event.target);
|
||||
clearTimeout(this.showTimer);
|
||||
this.showTimer = setTimeout(() => {
|
||||
$node.tooltip('show');
|
||||
}, this.SHOW_AFTER_DELAY);
|
||||
},
|
||||
});
|
||||
|
||||
});
|
||||
84
web_enterprise/static/src/legacy/js/views/form_renderer.js
Normal file
84
web_enterprise/static/src/legacy/js/views/form_renderer.js
Normal file
@@ -0,0 +1,84 @@
|
||||
odoo.define('web_enterprise.MobileFormRenderer', function (require) {
|
||||
"use strict";
|
||||
|
||||
const config = require('web.config');
|
||||
if (!config.device.isMobile) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* This file defines the MobileFormRenderer, an extension of the FormRenderer
|
||||
* implementing some tweaks to improve the UX in mobile.
|
||||
*/
|
||||
|
||||
const core = require('web.core');
|
||||
const FormRenderer = require('web.FormRenderer');
|
||||
|
||||
const qweb = core.qweb;
|
||||
|
||||
FormRenderer.include({
|
||||
|
||||
/**
|
||||
* Reset the 'state' of the drop down
|
||||
* @override
|
||||
*/
|
||||
updateState: function () {
|
||||
this.isStatusbarButtonsDropdownOpen = undefined;
|
||||
return this._super(...arguments);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Show action drop-down if there are multiple visible buttons/widgets.
|
||||
*
|
||||
* @private
|
||||
* @override
|
||||
*/
|
||||
_renderStatusbarButtons: function (buttons) {
|
||||
// Removes 'o_invisible_modifier' buttons in addition to those that match this selector:
|
||||
// .o_form_view.o_form_editable .oe_read_only {
|
||||
// display: none !important;
|
||||
// }
|
||||
const $visibleButtons = buttons.filter(button => {
|
||||
return !($(button).hasClass('o_invisible_modifier') || (this.mode === 'edit' && $(button).hasClass('oe_read_only')));
|
||||
});
|
||||
|
||||
if ($visibleButtons.length > 1) {
|
||||
const $statusbarButtonsDropdown = $(qweb.render('StatusbarButtonsDropdown', {
|
||||
open: this.isStatusbarButtonsDropdownOpen,
|
||||
}));
|
||||
$statusbarButtonsDropdown.find('.btn-group').on('show.bs.dropdown', () => {
|
||||
this.isStatusbarButtonsDropdownOpen = true;
|
||||
});
|
||||
$statusbarButtonsDropdown.find('.btn-group').on('hide.bs.dropdown', () => {
|
||||
this.isStatusbarButtonsDropdownOpen = false;
|
||||
});
|
||||
const $dropdownMenu = $statusbarButtonsDropdown.find('.dropdown-menu');
|
||||
buttons.forEach(button => {
|
||||
const dropdownButton = $(button).addClass('dropdown-item');
|
||||
return $dropdownMenu.append(dropdownButton);
|
||||
});
|
||||
return $statusbarButtonsDropdown;
|
||||
}
|
||||
buttons.forEach(button => $(button).removeClass('dropdown-item'));
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
/**
|
||||
* Update the UI statusbar button after all modifiers are updated.
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_updateAllModifiers: function () {
|
||||
return this._super.apply(this, arguments).then(() => {
|
||||
const $statusbarButtonsContainer = this.$('.o_statusbar_buttons');
|
||||
const $statusbarButtons = $statusbarButtonsContainer.find('button.btn').toArray();
|
||||
$statusbarButtonsContainer.replaceWith(this._renderStatusbarButtons($statusbarButtons));
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
});
|
||||
40
web_enterprise/static/src/legacy/js/views/form_view.js
Normal file
40
web_enterprise/static/src/legacy/js/views/form_view.js
Normal file
@@ -0,0 +1,40 @@
|
||||
odoo.define('web_mobile.FormView', function (require) {
|
||||
"use strict";
|
||||
|
||||
var config = require('web.config');
|
||||
var FormView = require('web.FormView');
|
||||
var QuickCreateFormView = require('web.QuickCreateFormView');
|
||||
|
||||
/**
|
||||
* We don't want to see the keyboard after the opening of a form view.
|
||||
* The keyboard takes a lot of space and the user doesn't have a global view
|
||||
* on the form.
|
||||
* Plus, some relational fields are overrided (Many2One for example) and the
|
||||
* user have to click on it to set a value. On this kind of field, the autofocus
|
||||
* doesn't make sense because you can't directly type on it.
|
||||
* So, we have to disable the autofocus in mobile.
|
||||
*/
|
||||
FormView.include({
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
|
||||
if (config.device.isMobile) {
|
||||
this.controllerParams.disableAutofocus = true;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
QuickCreateFormView.include({
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
this.controllerParams.disableAutofocus = false;
|
||||
},
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
odoo.define('web.KanbanTabsMobileMixin', function () {
|
||||
"use strict";
|
||||
|
||||
const KanbanTabsMobileMixin = {
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
/**
|
||||
* Update the tabs positions
|
||||
*
|
||||
* @param tabs - All existing tabs in the kanban
|
||||
* @param moveToIndex - The current Active tab in the index
|
||||
* @param $tabsContainer - the jquery container of the tabs
|
||||
* @private
|
||||
*/
|
||||
_computeTabPosition(tabs, moveToIndex, $tabsContainer) {
|
||||
this._computeTabJustification(tabs, $tabsContainer);
|
||||
this._computeTabScrollPosition(tabs, moveToIndex, $tabsContainer);
|
||||
},
|
||||
/**
|
||||
* Update the tabs positions
|
||||
*
|
||||
* @param tabs - All existing tabs in the kanban
|
||||
* @param moveToIndex - The current Active tab in the index
|
||||
* @param $tabsContainer - the jquery container of the tabs
|
||||
* @private
|
||||
*/
|
||||
_computeTabScrollPosition(tabs, moveToIndex, $tabsContainer) {
|
||||
if (tabs.length) {
|
||||
const lastItemIndex = tabs.length - 1;
|
||||
let scrollToLeft = 0;
|
||||
for (let i = 0; i < moveToIndex; i++) {
|
||||
const columnWidth = this._getTabWidth(tabs[i]);
|
||||
// apply
|
||||
if (moveToIndex !== lastItemIndex && i === moveToIndex - 1) {
|
||||
const partialWidth = 0.75;
|
||||
scrollToLeft += columnWidth * partialWidth;
|
||||
} else {
|
||||
scrollToLeft += columnWidth;
|
||||
}
|
||||
}
|
||||
// Apply the scroll x on the tabs
|
||||
// XXX in case of RTL, should we use scrollRight?
|
||||
$tabsContainer.scrollLeft(scrollToLeft);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Compute the justify content of the kanban tab headers
|
||||
* @param tabs - All existing tabs in the kanban
|
||||
* @param $tabsContainer - the jquery container of the tabs
|
||||
* @private
|
||||
*/
|
||||
_computeTabJustification(tabs, $tabsContainer) {
|
||||
if (tabs.length) {
|
||||
// Use to compute the sum of the width of all tab
|
||||
const widthChilds = tabs.reduce((total, column) => total + this._getTabWidth(column), 0);
|
||||
// Apply a space around between child if the parent length is higher then the sum of the child width
|
||||
$tabsContainer.toggleClass('justify-content-between', $tabsContainer.outerWidth() >= widthChilds);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Retrieve the outerWidth of a given tab
|
||||
*
|
||||
* @param tab
|
||||
* @returns {integer} outerWidth of the found column
|
||||
* @abstract
|
||||
*/
|
||||
_getTabWidth(tab) {
|
||||
}
|
||||
};
|
||||
return KanbanTabsMobileMixin;
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
odoo.define('web_enterprise.ListControllerMobile', function (require) {
|
||||
"use strict";
|
||||
|
||||
const config = require('web.config');
|
||||
if (!config.device.isMobile) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ListController = require('web.ListController');
|
||||
|
||||
ListController.include({
|
||||
|
||||
events: Object.assign({}, ListController.prototype.events, {
|
||||
'click .o_discard_selection': '_onDiscardSelection'
|
||||
}),
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* In mobile, we hide the "export" button.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
renderButtons() {
|
||||
this._super(...arguments);
|
||||
this.$buttons.find('.o_list_export_xlsx').hide();
|
||||
},
|
||||
/**
|
||||
* In mobile, we hide the "export" button.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
updateButtons() {
|
||||
this._super(...arguments);
|
||||
this.$buttons.find('.o_list_export_xlsx').hide();
|
||||
},
|
||||
|
||||
/**
|
||||
* In mobile, we let the selection banner be added to the ControlPanel to enable the ActionMenus.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
async updateControlPanel() {
|
||||
const value = await this._super(...arguments);
|
||||
const displayBanner = Boolean(this.$selectionBox);
|
||||
if (displayBanner) {
|
||||
this._controlPanelWrapper.el.querySelector('.o_cp_bottom').prepend(this.$selectionBox[0]);
|
||||
}
|
||||
return value;
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Discard the current selection by unselecting any selected records.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_onDiscardSelection() {
|
||||
this.renderer.$('tbody .o_list_record_selector input:not(":disabled")').prop('checked', false);
|
||||
this.renderer._updateSelection();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,77 @@
|
||||
odoo.define('web_enterprise.ListRenderer', function (require) {
|
||||
"use strict";
|
||||
|
||||
const config = require('web.config');
|
||||
if (config.device.isMobile) {
|
||||
return;
|
||||
}
|
||||
const { qweb } = require('web.core');
|
||||
const ListRenderer = require('web.ListRenderer');
|
||||
const ListView = require('web.ListView');
|
||||
const session = require('web.session');
|
||||
const { patch } = require('web.utils');
|
||||
const PromoteStudioDialog = require('web_enterprise.PromoteStudioDialog');
|
||||
|
||||
patch(ListView.prototype, 'web_enterprise.ListView', {
|
||||
init: function (viewInfo, params) {
|
||||
this._super(viewInfo, params);
|
||||
this.rendererParams.isStudioEditable = params.action && !!params.action.xml_id;
|
||||
},
|
||||
});
|
||||
|
||||
patch(ListRenderer.prototype, 'web_enterprise.ListRenderer', {
|
||||
init: function (parent, state, params) {
|
||||
this._super(...arguments);
|
||||
this.isStudioEditable = params.isStudioEditable;
|
||||
},
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* This function adds a button at the bottom of the optional
|
||||
* columns dropdown menu. This button opens studio if installed and
|
||||
* promote studio if not installed.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_renderOptionalColumnsDropdown: function () {
|
||||
const $optionalColumnsDropdown = this._super(...arguments);
|
||||
if (session.is_system && this.isStudioEditable) {
|
||||
const $dropdownMenu = $optionalColumnsDropdown.find('.dropdown-menu');
|
||||
if (this.optionalColumns.length) {
|
||||
$dropdownMenu.append($("<hr />"));
|
||||
}
|
||||
const $addCustomField = $(qweb.render('web_enterprise.open_studio_button'));
|
||||
$dropdownMenu.append($addCustomField);
|
||||
$addCustomField.click(this._onAddCustomFieldClick.bind(this));
|
||||
}
|
||||
return $optionalColumnsDropdown;
|
||||
},
|
||||
|
||||
/**
|
||||
* This function returns if the optional columns dropdown menu should be rendered.
|
||||
* This function returns true iff there are optional columns or the user is system
|
||||
* admin.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_shouldRenderOptionalColumnsDropdown: function () {
|
||||
return this._super(...arguments) || session.is_system;
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
/**
|
||||
* This function opens studio dialog
|
||||
*
|
||||
* @param {Event} event
|
||||
* @private
|
||||
*/
|
||||
_onAddCustomFieldClick: function (event) {
|
||||
event.stopPropagation();
|
||||
new PromoteStudioDialog(this).open();
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,186 @@
|
||||
odoo.define('web_enterprise.MobileListRenderer', function (require) {
|
||||
"use strict";
|
||||
|
||||
const config = require('web.config');
|
||||
|
||||
if (!config.device.isMobile) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ListRenderer = require('web.ListRenderer');
|
||||
|
||||
ListRenderer.include({
|
||||
|
||||
events: Object.assign({}, ListRenderer.prototype.events, {
|
||||
'touchstart .o_data_row': '_onTouchStartSelectionMode',
|
||||
'touchmove .o_data_row': '_onTouchMoveSelectionMode',
|
||||
'touchend .o_data_row': '_onTouchEndSelectionMode',
|
||||
}),
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.longTouchTimer = null;
|
||||
this.LONG_TOUCH_THRESHOLD = 400;
|
||||
this._onClickCapture = this._onClickCapture.bind(this);
|
||||
this._ignoreEventInSelectionMode = this._ignoreEventInSelectionMode.bind(this);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* In mobile, disable the read-only editable list.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
isEditable() {
|
||||
return this.editable;
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns the jQuery node used to update the selection ; visiblity insensitive.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_getSelectableRecordCheckboxes() {
|
||||
return this.$('tbody .o_list_record_selector input:not(:disabled)');
|
||||
},
|
||||
|
||||
/**
|
||||
* In mobile, disable the read-only editable list.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_isRecordEditable() {
|
||||
return this.editable;
|
||||
},
|
||||
|
||||
/**
|
||||
* Reset the current long-touch timer.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_resetLongTouchTimer() {
|
||||
if (this.longTouchTimer) {
|
||||
clearTimeout(this.longTouchTimer);
|
||||
this.longTouchTimer = null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_updateSelection() {
|
||||
this._super(...arguments);
|
||||
this._getSelectableRecordCheckboxes()
|
||||
.each((index, input) => {
|
||||
$(input).closest('.o_data_row').toggleClass('o_data_row_selected', input.checked);
|
||||
});
|
||||
},
|
||||
|
||||
_isInSelectionMode(ev) {
|
||||
return !!this.selection.length;
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Prevent from opening the record on click when in selection mode.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_onRowClicked(ev) {
|
||||
if (this.selection.length) {
|
||||
ev.preventDefault();
|
||||
return;
|
||||
}
|
||||
this._super(...arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* Following @see _onTouchStartSelectionMode, we cancel the long-touch if it was shorter
|
||||
* than @see LONG_TOUCH_THRESHOLD.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_onTouchEndSelectionMode() {
|
||||
const elapsedTime = Date.now() - this.touchStartMs;
|
||||
if (elapsedTime < this.LONG_TOUCH_THRESHOLD) {
|
||||
this._resetLongTouchTimer();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Following @see _onTouchStartSelectionMode, we cancel the long-touch.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_onTouchMoveSelectionMode() {
|
||||
this._resetLongTouchTimer();
|
||||
},
|
||||
|
||||
/**
|
||||
* We simulate a long-touch on a row by delaying (@see LONG_TOUCH_THRESHOLD)
|
||||
* the actual handler and potentially cancelling it if the user moves or end
|
||||
* its 'touch' before the timer's end.
|
||||
*
|
||||
* @private
|
||||
* @param ev
|
||||
*/
|
||||
_onTouchStartSelectionMode(ev) {
|
||||
this.touchStartMs = Date.now();
|
||||
if (this.longTouchTimer === null) {
|
||||
this.longTouchTimer = setTimeout(() => {
|
||||
$(ev.currentTarget).find('.o_list_record_selector').click();
|
||||
this._resetLongTouchTimer();
|
||||
}, this.LONG_TOUCH_THRESHOLD);
|
||||
}
|
||||
},
|
||||
|
||||
_ignoreEventInSelectionMode(ev) {
|
||||
if (this._isInSelectionMode()) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
}
|
||||
},
|
||||
|
||||
_onClickCapture(ev) {
|
||||
if (!this._isInSelectionMode()) {
|
||||
return;
|
||||
}
|
||||
const currentRow = $(ev.target).closest('.o_data_row');
|
||||
|
||||
if (!currentRow.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ev.target.classList.contains('o_list_record_selector')) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
$('.o_list_record_selector', currentRow).click();
|
||||
}
|
||||
},
|
||||
|
||||
_delegateEvents() {
|
||||
this._super(...arguments);
|
||||
this.$el[0].addEventListener('click', this._onClickCapture, { capture: true });
|
||||
['mouseover', 'mouseout'].forEach(name =>
|
||||
this.$el[0].addEventListener(name, this._ignoreEventInSelectionMode, { capture: true }));
|
||||
},
|
||||
|
||||
_undelegateEvents() {
|
||||
this._super(...arguments);
|
||||
this.$el[0].removeEventListener('click', this._onClickCapture, { capture: true });
|
||||
['mouseover', 'mouseout'].forEach(name =>
|
||||
this.$el[0].removeEventListener(name, this._ignoreEventInSelectionMode, { capture: true }));
|
||||
},
|
||||
|
||||
});
|
||||
});
|
||||
125
web_enterprise/static/src/legacy/js/views/relational_fields.js
Normal file
125
web_enterprise/static/src/legacy/js/views/relational_fields.js
Normal file
@@ -0,0 +1,125 @@
|
||||
odoo.define('web_enterprise.relational_fields', function (require) {
|
||||
"use strict";
|
||||
|
||||
var config = require('web.config');
|
||||
if (!config.device.isMobile) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* In this file, we override some relational fields to improve the UX in mobile.
|
||||
*/
|
||||
|
||||
var core = require('web.core');
|
||||
var relational_fields = require('web.relational_fields');
|
||||
|
||||
var FieldStatus = relational_fields.FieldStatus;
|
||||
var FieldMany2One = relational_fields.FieldMany2One;
|
||||
var FieldX2Many = relational_fields.FieldX2Many;
|
||||
|
||||
var qweb = core.qweb;
|
||||
|
||||
FieldStatus.include({
|
||||
/**
|
||||
* Override the custom behavior of FieldStatus to hide it if it is not set,
|
||||
* in mobile (which is the default behavior for fields).
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isEmpty: function () {
|
||||
return !this.isSet();
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_render: function () {
|
||||
this.$el.html(qweb.render("FieldStatus.content.mobile", {
|
||||
selection: this.status_information,
|
||||
status: _.findWhere(this.status_information, {selected: true}),
|
||||
clickable: this.isClickable,
|
||||
}));
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Override the Many2One to prevent autocomplete and open kanban view in mobile for search.
|
||||
*/
|
||||
|
||||
FieldMany2One.include({
|
||||
|
||||
start: function () {
|
||||
var superRes = this._super.apply(this, arguments);
|
||||
this.$input.prop('readonly', true);
|
||||
return superRes;
|
||||
},
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Don't bind autocomplete in the mobile as it uses a different mechanism
|
||||
* On clicking Many2One will directly open popup with kanban view
|
||||
*
|
||||
* @private
|
||||
* @override
|
||||
*/
|
||||
_bindAutoComplete: function () {},
|
||||
|
||||
/**
|
||||
* override to add selectionMode option to search create popup option
|
||||
*
|
||||
* @private
|
||||
* @override
|
||||
*/
|
||||
_getSearchCreatePopupOptions: function () {
|
||||
var self = this;
|
||||
var searchCreatePopupOptions = this._super.apply(this, arguments);
|
||||
_.extend(searchCreatePopupOptions, {
|
||||
selectionMode: true,
|
||||
on_clear: function () {
|
||||
self.reinitialize(false);
|
||||
},
|
||||
});
|
||||
return searchCreatePopupOptions;
|
||||
},
|
||||
|
||||
/**
|
||||
* We always open Many2One search dialog for select/update field value
|
||||
* instead of autocomplete
|
||||
*
|
||||
* @private
|
||||
* @override
|
||||
*/
|
||||
_toggleAutoComplete: function () {
|
||||
this._searchCreatePopup("search");
|
||||
},
|
||||
});
|
||||
|
||||
FieldX2Many.include({
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_renderButtons: function () {
|
||||
var result = this._super.apply(this, arguments);
|
||||
if (this.$buttons) {
|
||||
this.$buttons
|
||||
.find('.btn-secondary')
|
||||
.removeClass('btn-secondary')
|
||||
.addClass('btn-primary btn-add-record');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
67
web_enterprise/static/src/legacy/js/views/view_dialogs.js
Normal file
67
web_enterprise/static/src/legacy/js/views/view_dialogs.js
Normal file
@@ -0,0 +1,67 @@
|
||||
odoo.define('web_enterprise.view_dialogs', function (require) {
|
||||
"use strict";
|
||||
|
||||
var config = require('web.config');
|
||||
if (!config.device.isMobile) {
|
||||
return;
|
||||
}
|
||||
|
||||
var core = require('web.core');
|
||||
var view_dialogs = require('web.view_dialogs');
|
||||
|
||||
var _t = core._t;
|
||||
|
||||
view_dialogs.FormViewDialog.include({
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
init(parent, options) {
|
||||
options.headerButtons = options.headerButtons || [];
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
/**
|
||||
* Set the "Remove" button to the dialog's header buttons set
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_setRemoveButtonOption(options, btnClasses) {
|
||||
options.headerButtons.push({
|
||||
text: _t("Remove"),
|
||||
classes: `btn-secondary ${btnClasses}`,
|
||||
click: async () => {
|
||||
await this._remove();
|
||||
this.close();
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
view_dialogs.SelectCreateDialog.include({
|
||||
init: function () {
|
||||
this._super.apply(this,arguments);
|
||||
this.on_clear = this.options.on_clear || (function () {});
|
||||
this.viewType = 'kanban';
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_prepareButtons: function () {
|
||||
this._super.apply(this, arguments);
|
||||
if (this.options.disable_multiple_selection) {
|
||||
if (this.options.selectionMode) {
|
||||
this.headerButtons.push({
|
||||
text: _t("Clear"),
|
||||
classes: 'btn-secondary o_clear_button',
|
||||
close: true,
|
||||
click: function () {
|
||||
this.on_clear();
|
||||
},
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.__buttons = this.__buttons.filter(button => !button.classes.split(' ').includes('o_select_button'));
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,96 @@
|
||||
odoo.define('web_enterprise.PromoteStudioDialog', function (require) {
|
||||
"use strict";
|
||||
|
||||
const core = require('web.core');
|
||||
const Dialog = require('web.Dialog');
|
||||
const framework = require('web.framework');
|
||||
const localStorage = require('web.local_storage');
|
||||
const qweb = core.qweb;
|
||||
|
||||
const PromoteStudioDialog = Dialog.extend({
|
||||
events: _.extend({}, Dialog.prototype.events, {
|
||||
'click button.o_install_studio': '_onInstallStudio',
|
||||
}),
|
||||
/**
|
||||
* This init function adds a click listener on window to handle
|
||||
* modal closing.
|
||||
* @override
|
||||
*/
|
||||
init: function (parent, options) {
|
||||
options = _.defaults(options || {}, {
|
||||
$content: $(qweb.render('web_enterprise.install_web_studio')),
|
||||
renderHeader: false,
|
||||
renderFooter: false,
|
||||
size: 'large',
|
||||
});
|
||||
this._super(parent, options);
|
||||
},
|
||||
/**
|
||||
* This function adds an handler for window clicks at the end of start.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
start: async function () {
|
||||
await this._super.apply(this, arguments);
|
||||
core.bus.on('click', this, this._onWindowClick);
|
||||
},
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
/**
|
||||
* This function both installs studio and reload the current view in studio mode.
|
||||
*
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onInstallStudio: async function (event) {
|
||||
event.stopPropagation();
|
||||
this.disableClick = true;
|
||||
framework.blockUI();
|
||||
const modules = await this._rpc({
|
||||
model: 'ir.module.module',
|
||||
method: 'search_read',
|
||||
fields: ['id'],
|
||||
domain: [['name', '=', 'web_studio']],
|
||||
});
|
||||
await this._rpc({
|
||||
model: 'ir.module.module',
|
||||
method: 'button_immediate_install',
|
||||
args: [[modules[0].id]],
|
||||
});
|
||||
// on rpc call return, the framework unblocks the page
|
||||
// make sure to keep the page blocked until the reload ends.
|
||||
framework.blockUI();
|
||||
localStorage.setItem('openStudioOnReload', 'main');
|
||||
this._reloadPage();
|
||||
},
|
||||
/**
|
||||
* Close modal when the user clicks outside the modal WITHOUT propagating
|
||||
* the event.
|
||||
* We must use this function to keep dropdown menu open when the user clicks in the modal, too.
|
||||
* This behaviour cannot be handled by using the modal backdrop.
|
||||
*
|
||||
* @param {Event} event
|
||||
*/
|
||||
_onWindowClick: function (event) {
|
||||
const $modal = $(event.target).closest('.modal-studio');
|
||||
if (!$modal.length && !this.disableClick) {
|
||||
this._onCloseDialog(event);
|
||||
}
|
||||
event.stopPropagation();
|
||||
},
|
||||
//--------------------------------------------------------------------------
|
||||
// Utils
|
||||
//--------------------------------------------------------------------------
|
||||
/**
|
||||
* This function isolate location reload in order to easily mock it (in tests for example)
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_reloadPage: function () {
|
||||
window.location.reload();
|
||||
},
|
||||
});
|
||||
|
||||
return PromoteStudioDialog;
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user