[IMP] web: new navbar in community
The navbar in community now looks more like the navbar in enterprise. Here is a sum-up of the changes in community: - The navbar in community is now in blue, instead of black. - No more app list that almost fills the whole navbar: there is now an "Apps Menu" in the left-most side of the navbar. - No more sidebar for the menus inside an app: those menus are now located in the middle part of the navbar. The navbar in community does not support mobile (< 768px width), but it should still somewhat usable on most mobile devices. As for the website navbar, it has been unchanged and should still support mobile layout. Task-ID 1878649
This commit is contained in:
@@ -24,6 +24,7 @@ This module provides the core of the Odoo Web Client.
|
||||
'qweb': [
|
||||
"static/src/xml/base.xml",
|
||||
"static/src/xml/kanban.xml",
|
||||
"static/src/xml/menu.xml",
|
||||
"static/src/xml/rainbow_man.xml",
|
||||
"static/src/xml/report.xml",
|
||||
"static/src/xml/web_calendar.xml",
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
odoo.define('web.AppsMenu', function (require) {
|
||||
"use strict";
|
||||
|
||||
var Widget = require('web.Widget');
|
||||
|
||||
var AppsMenu = Widget.extend({
|
||||
template: 'AppsMenu',
|
||||
events: {
|
||||
'click .o_app': '_onAppsMenuItemClicked',
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
* @param {web.Widget} parent
|
||||
* @param {Object} menuData
|
||||
* @param {Object[]} menuData.children
|
||||
*/
|
||||
init: function (parent, menuData) {
|
||||
this._super.apply(this, arguments);
|
||||
this._activeApp = undefined;
|
||||
this._apps = _.map(menuData.children, function (appMenuData) {
|
||||
return {
|
||||
actionID: parseInt(appMenuData.action.split(',')[1]),
|
||||
menuID: appMenuData.id,
|
||||
name: appMenuData.name,
|
||||
xmlID: appMenuData.xmlid,
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @returns {Object}
|
||||
*/
|
||||
getActiveApp: function () {
|
||||
return this._activeApp || {};
|
||||
},
|
||||
/**
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
getApps: function () {
|
||||
return this._apps;
|
||||
},
|
||||
/**
|
||||
* Open the given app
|
||||
*/
|
||||
openApp: function () {},
|
||||
/**
|
||||
* Open the first app in the list of apps
|
||||
*/
|
||||
openFirstApp: function () {
|
||||
var firstApp = this._apps[0];
|
||||
this._openApp(firstApp);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Object} app
|
||||
*/
|
||||
_openApp: function (app) {
|
||||
this._setActiveApp(app);
|
||||
this.trigger_up('app_clicked', {
|
||||
action_id: app.actionID,
|
||||
menu_id: app.menuID,
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Object} app
|
||||
*/
|
||||
_setActiveApp: function (app) {
|
||||
this._activeApp = app;
|
||||
this.renderElement();
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Called when clicking on an item in the apps menu.
|
||||
*
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onAppsMenuItemClicked: function (ev) {
|
||||
var $target = $(ev.currentTarget);
|
||||
var actionID = $target.data('action-id');
|
||||
var menuID = $target.data('menu-id');
|
||||
var app = _.findWhere(this._apps, { actionID: actionID, menuID: menuID });
|
||||
this._openApp(app);
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
return AppsMenu;
|
||||
|
||||
});
|
||||
@@ -1,164 +1,140 @@
|
||||
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 session = require('web.session');
|
||||
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({
|
||||
init: function() {
|
||||
this._super.apply(this, arguments);
|
||||
this.is_bound = $.Deferred();
|
||||
this.data = {data:{children:[]}};
|
||||
core.bus.on('change_menu_section', this, this.on_change_top_menu);
|
||||
template: 'Menu',
|
||||
menusTemplate: 'Menu.sections',
|
||||
events: {
|
||||
'mouseover .o_menu_sections > li:not(.show)': '_onMouseOverMenu',
|
||||
'click .o_menu_brand': '_onAppNameClicked',
|
||||
},
|
||||
start: function() {
|
||||
this._super.apply(this, arguments);
|
||||
return this.bind_menu();
|
||||
},
|
||||
do_reload: function() {
|
||||
|
||||
init: function (parent, menu_data) {
|
||||
var self = this;
|
||||
self.bind_menu();
|
||||
},
|
||||
bind_menu: function() {
|
||||
var self = this;
|
||||
this.$secondary_menus = this.$el.parents().find('.o_sub_menu');
|
||||
this.$secondary_menus.on('click', 'a[data-menu]', this.on_menu_click);
|
||||
this.$el.on('click', 'a[data-menu]', function (event) {
|
||||
event.preventDefault();
|
||||
var menu_id = $(event.currentTarget).data('menu');
|
||||
core.bus.trigger('change_menu_section', menu_id);
|
||||
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');
|
||||
});
|
||||
|
||||
// Hide second level submenus
|
||||
this.$secondary_menus.find('.oe_menu_toggler').siblings('.oe_secondary_submenu').addClass('o_hidden');
|
||||
if (self.current_menu) {
|
||||
self.open_menu(self.current_menu);
|
||||
}
|
||||
this.trigger('menu_bound');
|
||||
|
||||
dom.initAutoMoreMenu(this.$el);
|
||||
|
||||
this.is_bound.resolve();
|
||||
// Bus event
|
||||
core.bus.on('change_menu_section', this, this._onChangeMenuSection);
|
||||
},
|
||||
/**
|
||||
* Opens a given menu by id, as if a user had browsed to that menu by hand
|
||||
* except does not trigger any event on the way
|
||||
*
|
||||
* @param {Number} id database id of the terminal menu to select
|
||||
*/
|
||||
open_menu: function (id) {
|
||||
this.current_menu = id;
|
||||
session.active_id = id;
|
||||
var $clicked_menu, $sub_menu, $main_menu;
|
||||
$clicked_menu = this.$el.add(this.$secondary_menus).find('a[data-menu=' + id + ']');
|
||||
this.trigger('open_menu', id, $clicked_menu);
|
||||
|
||||
if (this.$secondary_menus.has($clicked_menu).length) {
|
||||
$sub_menu = $clicked_menu.parents('.oe_secondary_menu');
|
||||
$main_menu = this.$el.find('a[data-menu=' + $sub_menu.data('menu-parent') + ']');
|
||||
} else {
|
||||
$sub_menu = this.$secondary_menus.find('.oe_secondary_menu[data-menu-parent=' + $clicked_menu.attr('data-menu') + ']');
|
||||
$main_menu = $clicked_menu;
|
||||
}
|
||||
|
||||
this.current_primary_menu = $main_menu.data('menu');
|
||||
|
||||
// Activate current main menu
|
||||
this.$el.find('.active').removeClass('active');
|
||||
$main_menu.parent().addClass('active');
|
||||
|
||||
// Show current sub menu
|
||||
this.$secondary_menus.find('.oe_secondary_menu').hide();
|
||||
$sub_menu.show();
|
||||
|
||||
// Hide/Show the leftbar menu depending of the presence of sub-items
|
||||
this.$secondary_menus.toggleClass('o_hidden', !$sub_menu.children().length);
|
||||
|
||||
// Activate current menu item and show parents
|
||||
this.$secondary_menus.find('.active').removeClass('active');
|
||||
if ($main_menu !== $clicked_menu) {
|
||||
$clicked_menu.parents().removeClass('o_hidden');
|
||||
if ($clicked_menu.is('.oe_menu_toggler')) {
|
||||
$clicked_menu.toggleClass('oe_menu_opened').siblings('.oe_secondary_submenu:first').toggleClass('o_hidden');
|
||||
} else {
|
||||
$clicked_menu.parent().addClass('active');
|
||||
}
|
||||
}
|
||||
// add a tooltip to cropped menu items
|
||||
this.$secondary_menus.find('.oe_secondary_submenu li a span').each(function() {
|
||||
$(this).tooltip(this.scrollWidth > this.clientWidth ? {title: $(this).text().trim(), placement: 'right'} : 'dispose');
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Call open_menu on a menu_item that matches the action_id
|
||||
*
|
||||
* If `menuID` is a match on this action, open this menu_item.
|
||||
* Otherwise open the first menu_item that matches the action_id.
|
||||
*
|
||||
* @param {Number} id the action_id to match
|
||||
* @param {Number} [menuID] a menu ID that may match with provided action
|
||||
*/
|
||||
open_action: function (id, menuID) {
|
||||
var $menu = this.$el.add(this.$secondary_menus).find('a[data-action-id="' + id + '"]');
|
||||
if (!(menuID && $menu.filter("[data-menu='" + menuID + "']").length)) {
|
||||
// menuID doesn't match action, so pick first menu_item
|
||||
menuID = $menu.data('menu');
|
||||
}
|
||||
if (menuID) {
|
||||
this.open_menu(menuID);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Process a click on a menu item
|
||||
*
|
||||
* @param {Number} id the menu_id
|
||||
*/
|
||||
menu_click: function(id) {
|
||||
if (!id) { return; }
|
||||
|
||||
// find back the menuitem in dom to get the action
|
||||
var $item = this.$el.find('a[data-menu=' + id + ']');
|
||||
if (!$item.length) {
|
||||
$item = this.$secondary_menus.find('a[data-menu=' + id + ']');
|
||||
}
|
||||
var action_id = $item.data('action-id');
|
||||
// If first level menu doesnt have action trigger first leaf
|
||||
if (!action_id) {
|
||||
if(this.$el.has($item).length) {
|
||||
var $sub_menu = this.$secondary_menus.find('.oe_secondary_menu[data-menu-parent=' + id + ']');
|
||||
var $items = $sub_menu.find('a[data-action-id]').filter('[data-action-id!=""]');
|
||||
if($items.length) {
|
||||
action_id = $items.data('action-id');
|
||||
id = $items.data('menu');
|
||||
}
|
||||
}
|
||||
}
|
||||
if (action_id) {
|
||||
this.trigger('menu_click', {
|
||||
action_id: action_id,
|
||||
id: id,
|
||||
previous_menu_id: this.current_menu // Here we don't know if action will fail (in which case we have to revert menu)
|
||||
}, $item);
|
||||
} else {
|
||||
console.log('Menu no action found web test 04 will fail');
|
||||
}
|
||||
this.open_menu(id);
|
||||
},
|
||||
|
||||
/**
|
||||
* Change the current top menu
|
||||
*
|
||||
* @param {int} [menu_id] the top menu id
|
||||
*/
|
||||
on_change_top_menu: function(menu_id) {
|
||||
start: function () {
|
||||
var self = this;
|
||||
this.menu_click(menu_id);
|
||||
|
||||
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);
|
||||
this._appsMenu.appendTo(this.$menu_apps);
|
||||
|
||||
// Systray Menu
|
||||
this.systray_menu = new SystrayMenu(this);
|
||||
this.systray_menu.attachTo(this.$('.o_menu_systray'));
|
||||
|
||||
dom.initAutoMoreMenu(this.$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 this._super.apply(this, arguments);
|
||||
},
|
||||
on_menu_click: function(ev) {
|
||||
ev.preventDefault();
|
||||
this.menu_click($(ev.currentTarget).data('menu'));
|
||||
_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,
|
||||
});
|
||||
},
|
||||
_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;
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -173,7 +149,73 @@ var Menu = Widget.extend({
|
||||
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 {integer} primaryMenuID
|
||||
*/
|
||||
_onChangeMenuSection: function (primaryMenuID) {
|
||||
if (!this.$menu_sections[primaryMenuID]) {
|
||||
return; // unknown menu_id
|
||||
}
|
||||
|
||||
if (this.current_primary_menu === primaryMenuID) {
|
||||
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 === primaryMenuID) {
|
||||
this.$menu_brand_placeholder.text(this.menu_data.children[i].name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.$menu_sections[primaryMenuID].appendTo(this.$section_placeholder);
|
||||
this.current_primary_menu = primaryMenuID;
|
||||
|
||||
core.bus.trigger('resize');
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onMouseOverMenu: function (ev) {
|
||||
if (config.device.isMobile) {
|
||||
return;
|
||||
}
|
||||
var $target = $(ev.currentTarget);
|
||||
var $opened = $target.siblings('.show');
|
||||
if ($opened.length) {
|
||||
$target.find('[data-toggle="dropdown"]').dropdown('toggle');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return Menu;
|
||||
|
||||
});
|
||||
|
||||
@@ -6,149 +6,164 @@ var config = require('web.config');
|
||||
var core = require('web.core');
|
||||
var data_manager = require('web.data_manager');
|
||||
var dom = require('web.dom');
|
||||
var framework = require('web.framework');
|
||||
var Menu = require('web.Menu');
|
||||
var session = require('web.session');
|
||||
var SystrayMenu = require('web.SystrayMenu');
|
||||
var UserMenu = require('web.UserMenu');
|
||||
|
||||
return AbstractWebClient.extend({
|
||||
events: _.extend({}, AbstractWebClient.prototype.events, {
|
||||
'click .oe_logo_edit_admin': 'logo_edit',
|
||||
'click .oe_logo img': function(ev) {
|
||||
ev.preventDefault();
|
||||
return this.clear_uncommitted_changes().then(function() {
|
||||
framework.redirect("/web" + (config.debug ? "?debug" : ""));
|
||||
});
|
||||
},
|
||||
custom_events: _.extend({}, AbstractWebClient.prototype.custom_events, {
|
||||
app_clicked: 'on_app_clicked',
|
||||
menu_clicked: 'on_menu_clicked',
|
||||
}),
|
||||
show_application: function() {
|
||||
var self = this;
|
||||
|
||||
this.toggle_bars(true);
|
||||
this.set_title();
|
||||
this.update_logo();
|
||||
|
||||
// Menu is rendered server-side thus we don't want the widget to create any dom
|
||||
this.menu = new Menu(this);
|
||||
this.menu.setElement(this.$el.parents().find('.oe_application_menu_placeholder'));
|
||||
this.menu.on('menu_click', this, this.on_menu_action);
|
||||
|
||||
// Create the user menu (rendered client-side)
|
||||
this.user_menu = new UserMenu(this);
|
||||
var $user_menu_placeholder = $('body').find('.oe_user_menu_placeholder').show();
|
||||
var user_menu_loaded = this.user_menu.appendTo($user_menu_placeholder);
|
||||
|
||||
// Create the systray menu (rendered server-side)
|
||||
this.systray_menu = new SystrayMenu(this);
|
||||
this.systray_menu.setElement(this.$el.parents().find('.oe_systray'));
|
||||
var systray_menu_loaded = this.systray_menu.start();
|
||||
|
||||
// Start the menu once both systray and user menus are rendered
|
||||
// to prevent overflows while loading
|
||||
return $.when(systray_menu_loaded, user_menu_loaded).then(function() {
|
||||
self.bind_hashchange();
|
||||
self.menu.$el.removeClass('d-none');
|
||||
self.menu.start();
|
||||
start: function () {
|
||||
core.bus.on('change_menu_section', this, function (menuID) {
|
||||
this.do_push_state(_.extend($.bbq.getState(), {
|
||||
menu_id: menuID,
|
||||
}));
|
||||
});
|
||||
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
toggle_bars: function(value) {
|
||||
this.$('tr:has(td.navbar),.oe_leftbar').toggle(value);
|
||||
},
|
||||
update_logo: function(reload) {
|
||||
var company = session.company_id;
|
||||
var img = session.url('/web/binary/company_logo' + '?db=' + session.db + (company ? '&company=' + company : ''));
|
||||
this.$('.o_sub_menu_logo img').attr('src', '').attr('src', img + (reload ? "&t=" + Date.now() : ''));
|
||||
this.$('.oe_logo_edit').toggleClass('oe_logo_edit_admin', session.is_superuser);
|
||||
},
|
||||
logo_edit: function(ev) {
|
||||
bind_events: function () {
|
||||
var self = this;
|
||||
ev.preventDefault();
|
||||
this._rpc({
|
||||
model: 'res.users',
|
||||
method: 'read',
|
||||
args: [[session.uid], ['company_id']],
|
||||
this._super.apply(this, arguments);
|
||||
|
||||
/*
|
||||
Small patch to allow having a link with a href towards an anchor. Since odoo use hashtag
|
||||
to represent the current state of the view, we can't easily distinguish between a link
|
||||
towards an anchor and a link towards anoter view/state. If we want to navigate towards an
|
||||
anchor, we must not change the hash of the url otherwise we will be redirected to the app
|
||||
switcher instead.
|
||||
To check if we have an anchor, first check if we have an href attributes starting with #.
|
||||
Try to find a element in the DOM using JQuery selector.
|
||||
If we have a match, it means that it is probably a link to an anchor, so we jump to that anchor.
|
||||
*/
|
||||
this.$el.on('click', 'a', function (ev) {
|
||||
var disable_anchor = ev.target.attributes.disable_anchor;
|
||||
if (disable_anchor && disable_anchor.value === "true") {
|
||||
return;
|
||||
}
|
||||
|
||||
var href = ev.target.attributes.href;
|
||||
if (href) {
|
||||
if (href.value[0] === '#' && href.value.length > 1) {
|
||||
if (self.$("[id='"+href.value.substr(1)+"']").length) {
|
||||
ev.preventDefault();
|
||||
self.trigger_up('scrollTo', {'selector': href.value});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
load_menus: function () {
|
||||
return this._rpc({
|
||||
model: 'ir.ui.menu',
|
||||
method: 'load_menus',
|
||||
args: [config.debug],
|
||||
context: session.user_context,
|
||||
})
|
||||
.then(function(data) {
|
||||
self._rpc({
|
||||
route: '/web/action/load',
|
||||
params: { action_id: 'base.action_res_company_form' },
|
||||
})
|
||||
.done(function(result) {
|
||||
result.res_id = data[0].company_id[0];
|
||||
result.target = "new";
|
||||
result.views = [[false, 'form']];
|
||||
result.flags = {
|
||||
action_buttons: true,
|
||||
headless: true,
|
||||
};
|
||||
self.action_manager.doAction(result, {
|
||||
on_close: self.update_logo.bind(self, true),
|
||||
});
|
||||
});
|
||||
.then(function (menuData) {
|
||||
// Compute action_id if not defined on a top menu item
|
||||
for (var i = 0; i < menuData.children.length; i++) {
|
||||
var child = menuData.children[i];
|
||||
if (child.action === false) {
|
||||
while (child.children && child.children.length) {
|
||||
child = child.children[0];
|
||||
if (child.action) {
|
||||
menuData.children[i].action = child.action;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return menuData;
|
||||
});
|
||||
return false;
|
||||
},
|
||||
bind_hashchange: function() {
|
||||
show_application: function () {
|
||||
var self = this;
|
||||
$(window).bind('hashchange', this.on_hashchange);
|
||||
var didHashChanged = false;
|
||||
$(window).one('hashchange', function () {
|
||||
didHashChanged = true;
|
||||
});
|
||||
this.set_title();
|
||||
|
||||
var state = $.bbq.getState(true);
|
||||
if (_.isEmpty(state) || state.action === "login") {
|
||||
self.menu.is_bound.done(function() {
|
||||
self._rpc({
|
||||
return this.instanciate_menu_widgets().then(function () {
|
||||
$(window).bind('hashchange', self.on_hashchange);
|
||||
|
||||
// If the url's state is empty, we execute the user's home action if there is one (we
|
||||
// show the first app if not)
|
||||
if (_.isEmpty($.bbq.getState(true))) {
|
||||
return self._rpc({
|
||||
model: 'res.users',
|
||||
method: 'read',
|
||||
args: [[session.uid], ['action_id']],
|
||||
args: [session.uid, ["action_id"]],
|
||||
})
|
||||
.done(function(result) {
|
||||
var amDef = self.action_manager.dp.current_def;
|
||||
if (didHashChanged || (amDef && amDef.state() === 'pending')) {
|
||||
return;
|
||||
}
|
||||
.then(function (result) {
|
||||
var data = result[0];
|
||||
if(data.action_id) {
|
||||
self.action_manager.doAction(data.action_id[0]);
|
||||
self.menu.open_action(data.action_id[0]);
|
||||
if (data.action_id) {
|
||||
return self.do_action(data.action_id[0]).then(function () {
|
||||
self.menu.change_menu_section(self.menu.action_id_to_primary_menu_id(data.action_id[0]));
|
||||
});
|
||||
} else {
|
||||
var first_menu_id = self.menu.$el.find("a:first").data("menu");
|
||||
if(first_menu_id) {
|
||||
self.menu.menu_click(first_menu_id);
|
||||
self.menu.openFirstApp();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return self.on_hashchange();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
instanciate_menu_widgets: function () {
|
||||
var self = this;
|
||||
var defs = [];
|
||||
return this.load_menus().then(function (menuData) {
|
||||
self.menu_data = menuData;
|
||||
|
||||
// Here, we instanciate every menu widgets and we immediately append them into dummy
|
||||
// document fragments, so that their `start` method are executed before inserting them
|
||||
// into the DOM.
|
||||
if (self.menu) {
|
||||
self.menu.destroy();
|
||||
}
|
||||
self.menu = new Menu(self, menuData);
|
||||
defs.push(self.menu.prependTo(self.$el));
|
||||
return $.when.apply($, defs);
|
||||
});
|
||||
},
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// URL state handling
|
||||
// --------------------------------------------------------------
|
||||
on_hashchange: function (event) {
|
||||
if (this._ignore_hashchange) {
|
||||
this._ignore_hashchange = false;
|
||||
return $.when();
|
||||
}
|
||||
|
||||
var self = this;
|
||||
return this.clear_uncommitted_changes().then(function () {
|
||||
var stringstate = $.bbq.getState(false);
|
||||
if (!_.isEqual(self._current_state, stringstate)) {
|
||||
var state = $.bbq.getState(true);
|
||||
if (state.action || (state.model && (state.view_type || state.id))) {
|
||||
return self.action_manager.loadState(state, !!self._current_state).then(function () {
|
||||
if (state.menu_id) {
|
||||
if (state.menu_id !== self.menu.current_primary_menu) {
|
||||
core.bus.trigger('change_menu_section', state.menu_id);
|
||||
}
|
||||
} else {
|
||||
var action = self.action_manager.getCurrentAction();
|
||||
if (action) {
|
||||
var menu_id = self.menu.action_id_to_primary_menu_id(action.id);
|
||||
if (menu_id) {
|
||||
core.bus.trigger('change_menu_section', menu_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
$(window).trigger('hashchange');
|
||||
}
|
||||
},
|
||||
on_hashchange: function(event) {
|
||||
if (this._ignore_hashchange) {
|
||||
this._ignore_hashchange = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
this.clear_uncommitted_changes().then(function () {
|
||||
var stringstate = event.getState(false);
|
||||
if (!_.isEqual(self._current_state, stringstate)) {
|
||||
var state = event.getState(true);
|
||||
if(!state.action && state.menu_id) {
|
||||
self.menu.is_bound.done(function() {
|
||||
self.menu.menu_click(state.menu_id);
|
||||
} else if (state.menu_id) {
|
||||
var action_id = self.menu.menu_id_to_action_id(state.menu_id);
|
||||
return self.do_action(action_id, {clear_breadcrumbs: true}).then(function () {
|
||||
core.bus.trigger('change_menu_section', state.menu_id);
|
||||
});
|
||||
} else {
|
||||
self.action_manager.loadState(state, !!self._current_state).then(function () {
|
||||
var action = self.action_manager.getCurrentAction();
|
||||
if (action) {
|
||||
self.menu.open_action(action.id, state.menu_id);
|
||||
}
|
||||
});
|
||||
self.menu.openFirstApp();
|
||||
}
|
||||
}
|
||||
self._current_state = stringstate;
|
||||
@@ -159,17 +174,88 @@ return AbstractWebClient.extend({
|
||||
}
|
||||
});
|
||||
},
|
||||
on_menu_action: function(options) {
|
||||
this.action_manager.doAction(options.action_id, {
|
||||
clear_breadcrumbs: true,
|
||||
action_menu_id: options.id,
|
||||
});
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// Menu handling
|
||||
// --------------------------------------------------------------
|
||||
on_app_clicked: function (ev) {
|
||||
var self = this;
|
||||
return this.menu_dm.add(data_manager.load_action(ev.data.action_id))
|
||||
.then(function (result) {
|
||||
return self.action_mutex.exec(function () {
|
||||
var completed = $.Deferred();
|
||||
var options = _.extend({}, ev.data.options, {
|
||||
clear_breadcrumbs: true,
|
||||
action_menu_id: ev.data.menu_id,
|
||||
});
|
||||
$.when(self._openMenu(result, options)).fail(function () {
|
||||
completed.resolve();
|
||||
}).done(function () {
|
||||
self._on_app_clicked_done(ev)
|
||||
.then(completed.resolve.bind(completed))
|
||||
.fail(completed.reject.bind(completed));
|
||||
});
|
||||
setTimeout(function () {
|
||||
completed.resolve();
|
||||
}, 2000);
|
||||
return completed;
|
||||
});
|
||||
});
|
||||
},
|
||||
toggle_fullscreen: function(fullscreen) {
|
||||
this._super(fullscreen);
|
||||
if (!fullscreen) {
|
||||
core.bus.trigger('resize');
|
||||
}
|
||||
_on_app_clicked_done: function (ev) {
|
||||
core.bus.trigger('change_menu_section', ev.data.menu_id);
|
||||
return $.Deferred().resolve();
|
||||
},
|
||||
on_menu_clicked: function (ev) {
|
||||
var self = this;
|
||||
return this.menu_dm.add(data_manager.load_action(ev.data.action_id))
|
||||
.then(function (result) {
|
||||
return self.action_mutex.exec(function () {
|
||||
var completed = $.Deferred();
|
||||
$.when(self._openMenu(result, {
|
||||
clear_breadcrumbs: true,
|
||||
})).always(function () {
|
||||
completed.resolve();
|
||||
});
|
||||
|
||||
setTimeout(function () {
|
||||
completed.resolve();
|
||||
}, 2000);
|
||||
|
||||
return completed;
|
||||
});
|
||||
}).always(function () {
|
||||
self.$el.removeClass('o_mobile_menu_opened');
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Open the action linked to a menu.
|
||||
* This function is mostly used to allow override in other modules.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} action
|
||||
* @param {Object} options
|
||||
* @returns {Deferred}
|
||||
*/
|
||||
_openMenu: function (action, options) {
|
||||
return this.do_action(action, options);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns the left and top scroll positions of the main scrolling area
|
||||
* (i.e. the action manager in desktop).
|
||||
*
|
||||
* @returns {Object} with keys left and top
|
||||
*/
|
||||
getScrollPosition: function () {
|
||||
return {
|
||||
left: this.action_manager.el.scrollLeft,
|
||||
top: this.action_manager.el.scrollTop,
|
||||
};
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -178,29 +264,22 @@ return AbstractWebClient.extend({
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_onGetScrollPosition: function (ev) {
|
||||
ev.data.callback({
|
||||
left: this.action_manager.el.scrollLeft,
|
||||
top: this.action_manager.el.scrollTop,
|
||||
});
|
||||
ev.data.callback(this.getScrollPosition());
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_onScrollTo: function (ev) {
|
||||
var offset;
|
||||
if (ev.data.selector) {
|
||||
var offset = {top: ev.data.top, left: ev.data.left || 0};
|
||||
if (!offset.top) {
|
||||
offset = dom.getPosition(document.querySelector(ev.data.selector));
|
||||
// substract the position of the ActionManager as it is the
|
||||
// scrolling element
|
||||
var actionManagerOffset = dom.getPosition(this.action_manager.el);
|
||||
offset.left -= actionManagerOffset.left;
|
||||
offset.top -= actionManagerOffset.top;
|
||||
} else {
|
||||
offset = {top: ev.data.top || 0, left: ev.data.left || 0};
|
||||
// Substract the position of the action_manager as it is the scrolling part
|
||||
offset.top -= dom.getPosition(this.action_manager.el).top;
|
||||
}
|
||||
|
||||
this.action_manager.el.scrollTop = offset.top;
|
||||
this.action_manager.el.scrollLeft = offset.left;
|
||||
},
|
||||
|
||||
@@ -822,7 +822,7 @@ if (config.debug) {
|
||||
*/
|
||||
current_action_updated: function (action, controller) {
|
||||
this._super.apply(this, arguments);
|
||||
var debugManager = _.find(this.systray_menu.widgets, function(item) {
|
||||
var debugManager = _.find(this.menu.systray_menu.widgets, function(item) {
|
||||
return item instanceof DebugManager;
|
||||
});
|
||||
debugManager.update('action', action, controller && controller.widget);
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
$listOfAppMenuItems = $(".o_app, .o_menuitem")
|
||||
} else {
|
||||
console.log("Odoo flavor: Community")
|
||||
$listOfAppMenuItems = $('#oe_main_menu_navbar a.oe_menu_leaf, #oe_main_menu_navbar a.oe_menu_toggler');
|
||||
$listOfAppMenuItems = $('#oe_main_menu_navbar a.oe_menu_leaf, #oe_main_menu_navbar a.o_app');
|
||||
}
|
||||
console.log('Found ', $listOfAppMenuItems.length, 'apps to test');
|
||||
|
||||
@@ -94,12 +94,7 @@
|
||||
testedApps.push(element.dataset.menuXmlid);
|
||||
return testMenuItem(element).then(function () {
|
||||
var $subMenuItems;
|
||||
var isEnterprise = odoo.session_info.server_version_info[5] === 'e';
|
||||
if (isEnterprise) {
|
||||
$subMenuItems = $('.o_menu_entry_lvl_1, .o_menu_entry_lvl_2, .o_menu_entry_lvl_3, .o_menu_entry_lvl_4');
|
||||
} else {
|
||||
$subMenuItems = $('div.o_sub_menu_content .oe_menu_leaf');
|
||||
}
|
||||
$subMenuItems = $('.o_menu_entry_lvl_1, .o_menu_entry_lvl_2, .o_menu_entry_lvl_3, .o_menu_entry_lvl_4');
|
||||
var testMenuDef = $.when();
|
||||
testMenuDef = chainDeferred($subMenuItems, testMenuDef, testMenuItem);
|
||||
return testMenuDef;
|
||||
|
||||
@@ -28,6 +28,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.o_app {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
> .o_menu_brand {
|
||||
display: block;
|
||||
float: left;
|
||||
@@ -37,13 +41,7 @@
|
||||
font-size: 22px;
|
||||
font-weight: 500;
|
||||
line-height: $o-navbar-height;
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
float: none;
|
||||
margin: 0;
|
||||
border-bottom: 1px solid $o-navbar-inverse-link-hover-bg;
|
||||
color: transparent;
|
||||
}
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
> .o_menu_toggle {
|
||||
@@ -146,79 +144,4 @@
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
transition: height 200ms linear 0s;
|
||||
|
||||
position: relative;
|
||||
height: $o-navbar-height;
|
||||
overflow: hidden;
|
||||
|
||||
> ul {
|
||||
> li {
|
||||
float: none;
|
||||
.dropdown-backdrop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dropdown-menu.show {
|
||||
max-height: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.o_menu_sections {
|
||||
width: 100%;
|
||||
|
||||
.dropdown-menu.show {
|
||||
position: static;
|
||||
float: none;
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
overflow: visible;
|
||||
|
||||
> .dropdown-item {
|
||||
background-color: transparent;
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.o_menu_systray {
|
||||
@include o-position-absolute(0px, $o-navbar-height, auto, $o-navbar-height);
|
||||
height: $o-navbar-height;
|
||||
text-align: right;
|
||||
|
||||
> li {
|
||||
display: inline-block;
|
||||
|
||||
.dropdown-menu.show {
|
||||
@include o-position-absolute($o-navbar-height, 0, 0, 0);
|
||||
position: fixed;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.o_user_menu .oe_topbar_name {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
body.o_mobile_menu_opened > .o_main_navbar {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
.o_switch_company_menu > .dropdown-menu {
|
||||
padding-top: 0px;
|
||||
.bg-info {
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
|
||||
.o_main_navbar {
|
||||
.o_app {
|
||||
@include media-breakpoint-down(sm) {
|
||||
float: none;
|
||||
margin: 0;
|
||||
border-bottom: 1px solid $o-navbar-inverse-link-hover-bg;
|
||||
color: transparent !important;
|
||||
}
|
||||
}
|
||||
|
||||
> .o_menu_brand {
|
||||
@include media-breakpoint-down(sm) {
|
||||
float: none;
|
||||
margin: 0;
|
||||
border-bottom: 1px solid $o-navbar-inverse-link-hover-bg;
|
||||
color: transparent !important;
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
transition: height 200ms linear 0s;
|
||||
|
||||
position: relative;
|
||||
height: $o-navbar-height;
|
||||
overflow: hidden;
|
||||
|
||||
> ul {
|
||||
> li {
|
||||
float: none;
|
||||
.dropdown-backdrop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dropdown-menu.show {
|
||||
max-height: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.o_menu_sections {
|
||||
width: 100%;
|
||||
|
||||
.dropdown-menu.show {
|
||||
position: static;
|
||||
float: none;
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
overflow: visible;
|
||||
|
||||
> .dropdown-item {
|
||||
background-color: transparent;
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.o_menu_systray {
|
||||
@include o-position-absolute(0px, $o-navbar-height, auto, $o-navbar-height);
|
||||
height: $o-navbar-height;
|
||||
text-align: right;
|
||||
|
||||
> li {
|
||||
display: inline-block;
|
||||
|
||||
.dropdown-menu.show {
|
||||
@include o-position-absolute($o-navbar-height, 0, 0, 0);
|
||||
position: fixed;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.o_user_menu .oe_topbar_name {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
body.o_mobile_menu_opened > .o_main_navbar {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
.o_switch_company_menu > .dropdown-menu {
|
||||
padding-top: 0px;
|
||||
.bg-info {
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,7 +74,7 @@ $o-avatar-size: 90px;
|
||||
$o-statusbar-height: 33px;
|
||||
|
||||
$o-label-font-size-factor: 0.8;
|
||||
$o-navbar-height: 34px;
|
||||
$o-navbar-height: 46px;
|
||||
|
||||
$o-nb-calendar-colors: 24;
|
||||
|
||||
|
||||
@@ -24,17 +24,11 @@ select {
|
||||
//------------------------------------------------------------------------------
|
||||
// Loading
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
.o_loading {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 50%;
|
||||
padding: 4px 12px;
|
||||
background-color: $o-enterprise-color; // FIXME probably supposed to be primary to be auto-adapted in enterprise ?
|
||||
background-color: $o-brand-odoo;
|
||||
color: white;
|
||||
text-align: center;
|
||||
border-top: none;
|
||||
border-bottom-right-radius: 8px;
|
||||
border-bottom-left-radius: 8px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -1,176 +1,90 @@
|
||||
// ------------------------------------------------------------------
|
||||
// Base layout rules, use the 'webclient.scss' file for styling
|
||||
// ------------------------------------------------------------------
|
||||
html {
|
||||
height: 100%;
|
||||
|
||||
.o_web_client {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
overflow: hidden;
|
||||
|
||||
&.o_fullscreen {
|
||||
#oe_main_menu_navbar {
|
||||
display: none;
|
||||
}
|
||||
.o_main .o_sub_menu {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.navbar {
|
||||
flex: 0 0 auto;
|
||||
|
||||
li > a {
|
||||
padding: 4px 8px;
|
||||
margin-top: 2px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.oe_topbar_avatar {
|
||||
max-height: 18px;
|
||||
width: 18px;
|
||||
margin-top: -4px;
|
||||
}
|
||||
|
||||
.oe_systray, .oe_user_menu_placeholder {
|
||||
> li {
|
||||
position: relative;
|
||||
padding: 4px 0px;
|
||||
|
||||
> a {
|
||||
@include o-hover-text-color($navbar-dark-color, $navbar-dark-active-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .o_main {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
min-height: 0; // Allow flex to shrink this element as much as it wants
|
||||
.o_web_client {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
|
||||
.o_sub_menu {
|
||||
flex: 0 0 220px;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
border-right: 1px solid darken($o-control-panel-background-color, 20%);
|
||||
background-color: $o-control-panel-background-color;
|
||||
|
||||
.o_sub_menu_logo {
|
||||
flex: 0 0 auto;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
|
||||
> img {
|
||||
margin: 14px 0;
|
||||
}
|
||||
|
||||
.oe_logo_edit {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
width: 100%;
|
||||
padding: 4px;
|
||||
margin: 14px 0;
|
||||
display: none;
|
||||
color: #eee;
|
||||
background: rgba(37,37,37,0.9);
|
||||
}
|
||||
&:hover .oe_logo_edit_admin {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.o_sub_menu_content {
|
||||
flex: 1 1 auto;
|
||||
height:100%;
|
||||
overflow-y: auto;
|
||||
|
||||
.oe_secondary_menu_section {
|
||||
font-weight: bold;
|
||||
margin-left: 8px;
|
||||
color: $o-brand-primary;
|
||||
}
|
||||
.oe_secondary_submenu {
|
||||
margin: 3px 0px 10px;
|
||||
line-height: 1.1em;
|
||||
>li:not(.active) > a {
|
||||
color: $o-main-text-color;
|
||||
}
|
||||
> li > a {
|
||||
padding: 4px 4px 4px 18px;
|
||||
> .badge {
|
||||
margin-top: -1px;
|
||||
}
|
||||
}
|
||||
.oe_secondary_submenu {
|
||||
> li > a {
|
||||
padding-left: 30px;
|
||||
}
|
||||
.oe_secondary_submenu > li > a {
|
||||
padding-left: 42px;
|
||||
}
|
||||
}
|
||||
.oe_menu_text {
|
||||
@include o-text-overflow;
|
||||
max-width: 85%;
|
||||
margin-top: 1px;
|
||||
}
|
||||
.oe_menu_toggler:before {
|
||||
display: inline-block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
content: "&darr";
|
||||
opacity: 0.5;
|
||||
text-indent: -99999px;
|
||||
vertical-align: top;
|
||||
margin-left: -12px;
|
||||
margin-top: 4px;
|
||||
margin-right: 4px;
|
||||
border-top: 4px solid transparent;
|
||||
border-bottom: 4px solid transparent;
|
||||
border-left: 4px solid #4c4c4c;
|
||||
}
|
||||
.oe_menu_opened:before {
|
||||
margin-top: 6px;
|
||||
margin-left: -16px;
|
||||
margin-right: 4px;
|
||||
border-left: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
border-top: 4px solid #4c4c4c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_sub_menu_footer {
|
||||
flex: 0 0 auto;
|
||||
text-align: center;
|
||||
|
||||
> a {
|
||||
font-weight: bold;
|
||||
> span {
|
||||
color: $o-enterprise-color; // FIXME probably supposed to be primary to be auto-adapted in enterprise ?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_main_content {
|
||||
> .o_main {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
min-height: 0; // Allow flex to shrink this element as much as it wants
|
||||
height: 100%;
|
||||
|
||||
.o_control_panel {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.o_content {
|
||||
.o_main_content {
|
||||
flex: 1 1 auto;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
|
||||
> .o_view_controller {
|
||||
@include o-position-absolute(0, 0, 0, 0); // Get the 100% height of its flex parent
|
||||
.o_control_panel {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.o_content {
|
||||
flex: 1 1 auto;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
|
||||
> .o_view_controller {
|
||||
@include o-position-absolute(0, 0, 0, 0); // Get the 100% height of its flex parent
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.o_fullscreen {
|
||||
.o_main_navbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.o_main_navbar {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.o_control_panel {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.o_content {
|
||||
direction: ltr; //Define direction attribute here so when rtlcss preprocessor run, it converts it to rtl
|
||||
flex: 1 1 auto;
|
||||
position: relative; // Allow to redistribute the 100% height to its child
|
||||
|
||||
> .o_view_controller {
|
||||
position: absolute; // Get the 100% height of its flex parent
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
direction: ltr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_loading {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: $zindex-modal + 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media print {
|
||||
html .o_web_client {
|
||||
.o_main_navbar {
|
||||
display: none;
|
||||
}
|
||||
.o_content {
|
||||
position: static;
|
||||
overflow: visible;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1531,7 +1531,7 @@
|
||||
|
||||
<t t-name="UserMenu">
|
||||
<li class="o_user_menu">
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" aria-expanded="false" href="#">
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" data-display="static" aria-expanded="false" href="#">
|
||||
<img class="rounded-circle oe_topbar_avatar" t-att-src="_s + '/web/static/src/img/user_menu_avatar.png'" alt="Avatar"/>
|
||||
<span class="oe_topbar_name"/>
|
||||
</a>
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<header t-name="Menu">
|
||||
<nav class="o_main_navbar">
|
||||
<ul class="o_menu_apps"/>
|
||||
<a class="o_menu_brand" role="button"/>
|
||||
<ul class="o_menu_sections" role="menu"/>
|
||||
<ul class="o_menu_systray" role="menu"/>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<!--
|
||||
@param {web.AppsMenu} widget
|
||||
-->
|
||||
<t t-name="AppsMenu">
|
||||
<li class="dropdown">
|
||||
<a class="dropdown-toggle full" data-toggle="dropdown" href="#">
|
||||
<i class="fa fa-th-large"/>
|
||||
</a>
|
||||
<t t-set="activeApp" t-value="widget.getActiveApp()"/>
|
||||
<div class="dropdown-menu" role="menu">
|
||||
<t t-foreach="widget.getApps()" t-as="app">
|
||||
<a role="menuitem"
|
||||
t-attf-class="dropdown-item o_app #{activeApp.menuID === app.menuID ? 'active' : ''}"
|
||||
t-att-data-menu-id="app.menuID"
|
||||
t-att-data-menu-xmlid="app.xmlID"
|
||||
t-att-data-action-id="app.actionID">
|
||||
<t t-esc="app.name"/>
|
||||
</a>
|
||||
</t>
|
||||
</div>
|
||||
</li>
|
||||
</t>
|
||||
|
||||
<t t-name="Menu.link">
|
||||
<t t-set="depth" t-value="(depth || 0) + 1"/>
|
||||
|
||||
<t t-if="!menu.children.length">
|
||||
<t t-set="_link">
|
||||
<a role="menuitem" t-attf-href="#menu_id=#{menu.id}&action=#{menu.action ? menu.action.split(',')[1] : ''}"
|
||||
t-att-data-menu="menu.id"
|
||||
t-att-data-action-model="menu.action ? menu.action.split(',')[0] : ''"
|
||||
t-att-data-action-id="menu.action ? menu.action.split(',')[1] : ''"
|
||||
t-att-data-menu-xmlid="menu.xmlid"
|
||||
t-attf-class="#{inNavbar ? '' : 'dropdown-item '}o_menu_entry_lvl_#{depth}"
|
||||
data-toggle="collapse" data-target="#o_navbar_collapse.in">
|
||||
<span><t t-esc="menu.name"/></span>
|
||||
</a>
|
||||
</t>
|
||||
<li t-if="inNavbar">
|
||||
<t t-raw="_link"/>
|
||||
</li>
|
||||
<t t-else="">
|
||||
<t t-raw="_link"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<div t-attf-class="dropdown-header o_menu_header_lvl_#{depth}">
|
||||
<span><t t-esc="menu.name"/></span>
|
||||
</div>
|
||||
<t t-foreach="menu.children" t-as="menu">
|
||||
<t t-call="Menu.link"/>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<!-- separate first level dropdown from the others to avoid nested dropdown -->
|
||||
<t t-name="Menu.sections">
|
||||
<t t-foreach="menu_data.children" t-as="first_level_menu">
|
||||
<section t-att-class="first_level_menu.id">
|
||||
<t t-foreach="first_level_menu.children" t-as="second_level_menu">
|
||||
<t t-if="!second_level_menu.children.length">
|
||||
<t t-call="Menu.link">
|
||||
<t t-set="inNavbar" t-value="true"/>
|
||||
<t t-set="menu" t-value="second_level_menu"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<li>
|
||||
<a href="#" class="dropdown-toggle o-no-caret o_menu_header_lvl_1" t-att-data-menu-xmlid="second_level_menu.xmlid" data-toggle="dropdown" role="button" aria-expanded="false">
|
||||
<t t-esc="second_level_menu.name"/>
|
||||
</a>
|
||||
<div class="dropdown-menu" role="menu">
|
||||
<t t-foreach="second_level_menu.children" t-as="third_level_menu">
|
||||
<t t-call="Menu.link">
|
||||
<t t-set="depth" t-value="1"/>
|
||||
<t t-set="menu" t-value="third_level_menu"/>
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
</li>
|
||||
</t>
|
||||
</t>
|
||||
</section>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-extend="DashboardShare">
|
||||
<t t-jquery=".user-heading h3" t-operation="replace">
|
||||
<h3>
|
||||
Odoo <t t-esc="server_version"/> (Enterprise Edition)
|
||||
</h3>
|
||||
</t>
|
||||
<t t-jquery=".user-heading h3" t-operation="after">
|
||||
<t t-set="expiration_date" t-value="new moment(widget.data.expiration_date).format('LL')"/>
|
||||
<t t-if="expiration_date">
|
||||
<h5>Database expiration: <t t-esc="expiration_date"/></h5>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-extend="UserMenu.Actions">
|
||||
<t t-jquery=".dropdown-divider" t-operation="before">
|
||||
<a href="#" data-menu="shortcuts" class="dropdown-item d-none d-md-inline-block">Shortcuts</a>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<div t-name="UserMenu.shortcuts">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="offset-md-2 col-md-8">
|
||||
<table class="o_shortcut_table text-center">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:50%;">Description</th>
|
||||
<th style="width:25%;" class="text-center">Windows/Linux</th>
|
||||
<th style="width:25%;" class="text-center">Mac</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left">Save a record</td>
|
||||
<td>
|
||||
<span class="o_key">Alt</span> + <span class="o_key">s</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="o_key">Control</span> + <span class="o_key">Alt</span> + <span class="o_key">s</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left">Edit a record</td>
|
||||
<td>
|
||||
<span class="o_key">Alt</span> + <span class="o_key">a</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="o_key">Control</span> + <span class="o_key">Alt</span> + <span class="o_key">a</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left">Discard a record modification</td>
|
||||
<td>
|
||||
<span class="o_key">Alt</span> + <span class="o_key">j</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="o_key">Control</span> + <span class="o_key">Alt</span> + <span class="o_key">j</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left">Create a new record</td>
|
||||
<td>
|
||||
<span class="o_key">Alt</span> + <span class="o_key">c</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="o_key">Control</span> + <span class="o_key">Alt</span> + <span class="o_key">c</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left">Open to list view</td>
|
||||
<td>
|
||||
<span class="o_key">Alt</span> + <span class="o_key">l</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="o_key">Control</span> + <span class="o_key">Alt</span> + <span class="o_key">l</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left">Open to kanban view</td>
|
||||
<td>
|
||||
<span class="o_key">Alt</span> + <span class="o_key">k</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="o_key">Control</span> + <span class="o_key">Alt</span> + <span class="o_key">k</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left">Open the previous record</td>
|
||||
<td>
|
||||
<span class="o_key">Alt</span> + <span class="o_key">p</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="o_key">Control</span> + <span class="o_key">Alt</span> + <span class="o_key">p</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left">Open the next record</td>
|
||||
<td>
|
||||
<span class="o_key">Alt</span> + <span class="o_key">n</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="o_key">Control</span> + <span class="o_key">Alt</span> + <span class="o_key">n</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left">Open app menu</td>
|
||||
<td>
|
||||
<span class="o_key">Alt</span> + <span class="o_key">h</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="o_key">Control</span> + <span class="o_key">Alt</span> + <span class="o_key">h</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<t t-extend="FormSelection">
|
||||
<t t-jquery=".dropdown-menu.state">$(this).addClass("dropdown-menu-right");</t>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -235,6 +235,7 @@
|
||||
<script type="text/javascript" src="/web/static/src/js/widgets/switch_company_menu.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/chrome/user_menu.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/chrome/menu.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/chrome/apps_menu.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/views/search/search_view.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/views/search/search_view_mobile.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/views/search/search_filters.js"></script>
|
||||
@@ -315,6 +316,7 @@
|
||||
|
||||
<t t-call="web._assets_bootstrap"/>
|
||||
<link rel="stylesheet" type="text/scss" href="/web/static/lib/tempusdominus/tempusdominus.scss"/>
|
||||
<link rel="stylesheet" type="text/scss" href="/web/static/src/scss/navbar_mobile.scss"/>
|
||||
|
||||
<script type="text/javascript" src="/web/static/src/js/services/session.js"></script>
|
||||
</template>
|
||||
@@ -414,65 +416,6 @@
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="web.menu">
|
||||
<ul class="nav navbar-nav navbar-left d-none oe_application_menu_placeholder" role="menu">
|
||||
<li t-foreach="menu_data['children']" t-as="menu" class="nav-item">
|
||||
<t t-call="web.menu_link">
|
||||
<t t-set="_classes" t-value="'nav-link'"/>
|
||||
</t>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="nav navbar-nav flex-nowrap ml-auto oe_systray" style="display: none;"/>
|
||||
<ul class="nav navbar-nav flex-nowrap oe_user_menu_placeholder" style="display: none;"/>
|
||||
</template>
|
||||
<template id="web.menu_secondary">
|
||||
<a class="o_sub_menu_logo" t-att-href="'/web/?debug' if debug else '/web'">
|
||||
<span class="oe_logo_edit">Edit Company data</span>
|
||||
<img src='/web/binary/company_logo' alt="Logo"/>
|
||||
</a>
|
||||
<div class="o_sub_menu_content">
|
||||
<t t-foreach="menu_data['children']" t-as="menu">
|
||||
<div style="display: none" class="oe_secondary_menu" t-att-data-menu-parent="menu['id']">
|
||||
<t t-foreach="menu['children']" t-as="menu">
|
||||
<div class="oe_secondary_menu_section" t-att-data-menu-xmlid="menu.get('xmlid')">
|
||||
<t t-if="menu['children']"><t t-esc="menu['name']"/></t>
|
||||
<t t-if="not menu['children']"><t t-call="web.menu_link"/></t>
|
||||
</div>
|
||||
<t t-call="web.menu_secondary_submenu"/>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
<div class="o_sub_menu_footer">
|
||||
Powered by <a href="http://www.odoo.com?utm_source=db&utm_medium=auth" target="_blank"><span>Odoo</span></a>
|
||||
</div>
|
||||
</template>
|
||||
<template id="web.menu_secondary_submenu">
|
||||
<ul t-if="menu['children']" class="oe_secondary_submenu nav nav-pills flex-column">
|
||||
<li t-foreach="menu['children']" t-as="menu" class="nav-item">
|
||||
<t t-call="web.menu_link">
|
||||
<t t-set="_classes" t-value="'nav-link'"/>
|
||||
</t>
|
||||
<t t-call="web.menu_secondary_submenu"/>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
<template id="web.menu_link">
|
||||
<t t-set="debug_param" t-value="'?&debug=' if debug else ''"/>
|
||||
<a t-att-href="'/web%s#menu_id=%s&action=%s' % (debug_param, menu['id'], menu['action'] and menu['action'].split(',')[1] or '')"
|
||||
role="menuitem"
|
||||
t-attf-class="#{_classes} #{'oe_menu_toggler' if menu.get('children') else 'oe_menu_leaf'}"
|
||||
t-att-data-menu="menu['id']"
|
||||
t-att-data-menu-xmlid="menu.get('xmlid')"
|
||||
t-att-data-action-model="menu['action'] and menu['action'].split(',')[0] or None"
|
||||
t-att-data-action-id="menu['action'] and menu['action'].split(',')[1] or None">
|
||||
<span class="oe_menu_text">
|
||||
<t t-esc="menu['name']"/>
|
||||
</span>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<template id="web.js_tests_assets">
|
||||
<link type="text/css" rel="stylesheet" href="/web/static/lib/qunit/qunit-2.3.0.css"/>
|
||||
<script type="text/javascript" src="/web/static/lib/qunit/qunit-2.3.0.js"></script>
|
||||
@@ -760,21 +703,7 @@
|
||||
</t>
|
||||
<t t-set="head" t-value="head_web + (head or '')"/>
|
||||
<t t-set="body_classname" t-value="'o_web_client'"/>
|
||||
|
||||
<header id="oe_main_menu_navbar">
|
||||
<nav class="navbar navbar-expand-md navbar-dark bg-dark" groups="base.group_user,base.group_portal">
|
||||
<button type="button" class="navbar-toggler" data-toggle="collapse" data-target=".navbar-collapse">
|
||||
<span class="navbar-toggler-icon"/>
|
||||
</button>
|
||||
<div class="navbar-collapse collapse">
|
||||
<t t-call="web.menu"/>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
<div class="o_main">
|
||||
<aside class="o_sub_menu" groups="base.group_user,base.group_portal" aria-label="Submenu">
|
||||
<t t-call="web.menu_secondary"/>
|
||||
</aside>
|
||||
<main class="o_main_content"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
Reference in New Issue
Block a user