metadata navbar menu

This commit is contained in:
Yuri Kuznetsov
2024-06-21 15:42:48 +03:00
parent 3f819500d3
commit b1de15339a
5 changed files with 229 additions and 65 deletions
@@ -18,5 +18,46 @@
"order": 15,
"disabled": false
}
},
"menuItems": {
"admin": {
"order": 0,
"groupIndex": 1,
"link": "#Admin",
"labelTranslation": "Global.labels.Administration",
"accessDataList": [
{
"isAdminOnly": true
}
]
},
"preferences": {
"order": 1,
"groupIndex": 1,
"link": "#Preferences",
"labelTranslation": "Global.labels.Preferences"
},
"lastViewed": {
"order": 0,
"groupIndex": 5,
"link": "#LastViewed",
"labelTranslation": "Global.scopeNamesPlural.LastViewed",
"configCheck": "!actionHistoryDisabled",
"handler": "handlers/navbar-menu",
"actionFunction": "lastViewed"
},
"about": {
"order": 0,
"groupIndex": 10,
"link": "#LastViewed",
"labelTranslation": "Global.labels.About"
},
"logout": {
"order": 1,
"groupIndex": 10,
"labelTranslation": "Global.labels.Log Out",
"handler": "handlers/navbar-menu",
"actionFunction": "logout"
}
}
}
+5 -5
View File
@@ -195,11 +195,11 @@
{{#each menuDataList}}
{{#unless divider}}
<li><a
{{#if link}}href="{{link}}"{{else}}role="button"{{/if}}
tabindex="0"
class="nav-link{{#if action}} action{{/if}}"{{#if action}}
data-action="{{action}}"{{/if}}
>{{#if html}}{{{html}}}{{else}}{{label}}{{/if}}</a></li>
{{#if name}}data-name="{{name}}"{{/if}}
{{#if link}}href="{{link}}"{{else}}role="button"{{/if}}
tabindex="0"
class="nav-link{{#if handler}} action{{/if}}"
>{{#if html}}{{{html}}}{{else}}{{label}}{{/if}}</a></li>
{{else}}
<li class="divider"></li>
{{/unless}}
+48
View File
@@ -0,0 +1,48 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
import ActionHandler from 'action-handler';
class NavbarMenuHandler extends ActionHandler {
logout() {
this.view.getRouter().logout();
}
// noinspection JSUnusedGlobalSymbols
lastViewed() {
Espo.Ui.notify(' ... ');
this.view.createView('dialog', 'views/modals/last-viewed', {}, view => {
view.render();
Espo.Ui.notify(false);
});
}
}
export default NavbarMenuHandler;
+89 -59
View File
@@ -64,10 +64,6 @@ class NavbarSiteView extends View {
this.switchSideMenu();
},
/** @this NavbarSiteView */
'click a.action': function (e) {
Espo.Utils.handleAction(this, e.originalEvent, e.currentTarget);
},
/** @this NavbarSiteView */
'click [data-action="toggleCollapsable"]': function () {
this.toggleCollapsable();
},
@@ -91,7 +87,7 @@ class NavbarSiteView extends View {
tabDefsList1: this.tabDefsList.filter(item => !item.isInMore),
tabDefsList2: this.tabDefsList.filter(item => item.isInMore),
title: this.options.title,
menuDataList: this.getMenuDataList(),
menuDataList: this.menuDataList,
userId: this.getUser().id,
logoSrc: this.getLogoSrc(),
itemDataList: this.getItemDataList(),
@@ -362,6 +358,24 @@ class NavbarSiteView extends View {
}
setup() {
this.addHandler('click', 'a.action', (/** MouseEvent */event, target) => {
let actionData;
const name = target.dataset.name;
if (name) {
const item = this.menuDataList.find(it => it.name === name);
if (item.handler && item.actionFunction) {
actionData = {
handler: item.handler,
actionFunction: item.actionFunction,
};
}
}
Espo.Utils.handleAction(this, event, target, actionData);
});
this.getRouter().on('routed', (e) => {
if (e.controller) {
this.selectTab(e.controller);
@@ -424,6 +438,8 @@ class NavbarSiteView extends View {
this.$body.removeClass('has-navbar');
});
this.setupMenu();
}
getItemDataList() {
@@ -1292,82 +1308,96 @@ class NavbarSiteView extends View {
/**
* @typedef {Object} MenuDataItem
* @property {string} [link]
* @property {string} [name]
* @property {string} [html]
* @property {string} [handler]
* @property {string} [actionFunction]
* @property {true} [divider]
*/
/**
* @return {MenuDataItem[]}
*/
getMenuDataList() {
setupMenu() {
let avatarHtml = this.getHelper().getAvatarHtml(this.getUser().id, 'small', 20, 'avatar-link');
if (avatarHtml) {
avatarHtml += ' ';
}
/** @type {MenuDataItem[]}*/
let list = [
/** @type {MenuDataItem[]} */
this.menuDataList = [
{
link: '#User/view/' + this.getUser().id,
link: `#User/view/${this.getUser().id}`,
html: avatarHtml + this.getHelper().escapeString(this.getUser().get('name')),
},
{divider: true}
];
if (this.getUser().isAdmin()) {
list.push({
link: '#Admin',
label: this.getLanguage().translate('Administration'),
});
}
/**
* @type {Record<string, {
* order?: number,
* groupIndex?: number,
* link?: string,
* labelTranslation?: string,
* configCheck?: string,
* disabled:? boolean,
* handler?: string,
* actionFunction?: string,
* accessDataList?: module:utils~AccessDefs[],
* }>} items
*/
const items = this.getMetadata().get('app.clientNavbar.menuItems') || {};
list.push({
link: '#Preferences',
label: this.getLanguage().translate('Preferences'),
const nameList = Object.keys(items).sort((n1, n2) => {
const o1 = items[n1].order;
const o2 = items[n2].order;
const g1 = items[n1].groupIndex;
const g2 = items[n2].groupIndex;
if (g2 === g1) {
return o1 - o2;
}
return g1 - g2;
});
if (!this.getConfig().get('actionHistoryDisabled')) {
list.push({divider: true});
let currentGroup = 0;
list.push({
action: 'showLastViewed',
link: '#LastViewed',
label: this.getLanguage().translate('LastViewed', 'scopeNamesPlural'),
for (const name of nameList) {
const item = items[name];
if (item.groupIndex !== currentGroup) {
currentGroup = item.groupIndex;
this.menuDataList.push({divider: true});
}
if (item.disabled) {
continue;
}
if (
item.configCheck &&
!Espo.Utils.checkActionAvailability(this.getHelper(), item)
) {
continue;
}
if (
item.accessDataList &&
!Espo.Utils.checkAccessDataList(item.accessDataList, this.getAcl(), this.getUser())
) {
continue;
}
this.menuDataList.push({
name: name,
link: item.link,
label: this.getLanguage().translatePath(item.labelTranslation),
handler: item.handler,
actionFunction: item.actionFunction,
});
}
list = list.concat([
{
divider: true
},
{
link: '#About',
label: this.getLanguage().translate('About')
},
{
action: 'logout',
label: this.getLanguage().translate('Log Out')
},
]);
return list;
}
// noinspection JSUnusedGlobalSymbols
actionLogout() {
this.getRouter().logout();
}
// noinspection JSUnusedGlobalSymbols
actionShowLastViewed() {
Espo.Ui.notify(' ... ');
this.createView('dialog', 'views/modals/last-viewed', {}, (view) => {
view.render();
Espo.Ui.notify(false);
});
}
showMoreTabs() {
+46 -1
View File
@@ -6,7 +6,7 @@
"type": "object",
"properties": {
"items": {
"description": "Navbar item definitions",
"description": "Navbar item definitions.",
"type": "object",
"additionalProperties": {
"description": "An item.",
@@ -30,6 +30,51 @@
}
}
}
},
"menuItems": {
"description": "Menu item definitions.",
"type": "object",
"additionalProperties": {
"description": "An item.",
"type": "object",
"properties": {
"labelTranslation": {
"type": "string",
"description": "A label translation path."
},
"link": {
"type": "string",
"description": "A link."
},
"order": {
"type": "integer",
"description": "An order position."
},
"groupIndex": {
"type": "integer",
"description": "A group index. Groups are separated by a divider."
},
"disabled": {
"type": "boolean",
"description": "An item will be hidden."
},
"handler": {
"type": "string",
"description": "A handler class."
},
"actionFunction": {
"type": "string",
"description": "An action function in the handler."
},
"configCheck": {
"type": "string",
"description": "A config path to check. Path items are separated by a dot. If a config value is not empty, then the action is allowed. The `!` prefix reverses the check."
},
"accessDataList": {
"$ref": "../clientDefs.json#/definitions/accessDataList"
}
}
}
}
}
}