This commit is contained in:
Yuri Kuznetsov
2024-08-20 11:09:16 +03:00
parent dcab0ad7d0
commit a12940e36c
@@ -26,498 +26,554 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
define('views/settings/fields/dashboard-layout', ['views/fields/base', 'lib!gridstack'], function (Dep, GridStack) {
import BaseFieldView from 'views/fields/base';
import GridStack from 'gridstack';
return Dep.extend({
export default class SettingsDashboardLayoutFieldView extends BaseFieldView {
detailTemplate: 'settings/fields/dashboard-layout/detail',
editTemplate: 'settings/fields/dashboard-layout/edit',
detailTemplate = 'settings/fields/dashboard-layout/detail'
editTemplate = 'settings/fields/dashboard-layout/edit'
validationElementSelector: 'button[data-action="addDashlet"]',
validationElementSelector = 'button[data-action="addDashlet"]'
WIDTH_MULTIPLIER: 3,
WIDTH_MULTIPLIER = 3
events: {
'click button[data-action="selectTab"]': function (e) {
var tab = parseInt($(e.currentTarget).data('tab'));
this.selectTab(tab);
},
'click [data-action="removeDashlet"]': function (e) {
var id = $(e.currentTarget).data('id');
this.removeDashlet(id);
},
'click [data-action="editDashlet"]': function (e) {
var id = $(e.currentTarget).data('id');
var name = $(e.currentTarget).data('name');
data() {
return {
dashboardLayout: this.dashboardLayout,
currentTab: this.currentTab,
isEmpty: this.isEmpty(),
};
}
this.editDashlet(id, name);
},
'click button[data-action="editTabs"]': function () {
this.editTabs();
},
'click button[data-action="addDashlet"]': function () {
this.createView('addDashlet', 'views/modals/add-dashlet', {
parentType: this.model.entityType,
}, view => {
view.render();
/**
* @protected
* @return {boolean}
*/
hasLocked() {
return this.model.entityType === 'Preferences';
}
this.listenToOnce(view, 'add', (name) => {
this.addDashlet(name);
});
});
},
},
setup() {
this.addActionHandler('selectTab', (e, target) => {
const tab = parseInt(target.dataset.tab);
data: function () {
return {
dashboardLayout: this.dashboardLayout,
currentTab: this.currentTab,
isEmpty: this.isEmpty(),
};
},
this.selectTab(tab);
});
hasLocked: function () {
return this.model.entityType === 'Preferences';
},
this.addActionHandler('removeDashlet', (e, target) => {
const id = target.dataset.id;
setup: function () {
this.dashboardLayout = Espo.Utils.cloneDeep(this.model.get(this.name) || []);
this.dashletsOptions = Espo.Utils.cloneDeep(this.model.get('dashletsOptions') || {});
this.removeDashlet(id);
});
this.addActionHandler('editDashlet', (e, target) => {
const id = target.dataset.id;
const name = target.dataset.name;
this.editDashlet(id, name);
});
this.addActionHandler('editTabs', () => this.editTabs());
this.addActionHandler('addDashlet', () => {
this.createView('addDashlet', 'views/modals/add-dashlet', {
parentType: this.model.entityType,
}, view => {
view.render();
this.listenToOnce(view, 'add', name => this.addDashlet(name));
});
});
this.dashboardLayout = Espo.Utils.cloneDeep(this.model.get(this.name) || []);
this.dashletsOptions = Espo.Utils.cloneDeep(this.model.get('dashletsOptions') || {});
if (this.hasLocked()) {
this.dashboardLocked = this.model.get('dashboardLocked') || false;
}
this.listenTo(this.model, 'change', () => {
if (this.model.hasChanged(this.name)) {
this.dashboardLayout = Espo.Utils.cloneDeep(this.model.get(this.name) || []);
}
if (this.model.hasChanged('dashletsOptions')) {
this.dashletsOptions = Espo.Utils.cloneDeep(this.model.get('dashletsOptions') || {});
}
if (this.model.hasChanged(this.name)) {
if (this.dashboardLayout.length) {
if (this.isDetailMode()) {
this.selectTab(0);
}
}
}
if (this.hasLocked()) {
this.dashboardLocked = this.model.get('dashboardLocked') || false;
}
});
this.listenTo(this.model, 'change', () => {
if (this.model.hasChanged(this.name)) {
this.dashboardLayout = Espo.Utils.cloneDeep(this.model.get(this.name) || []);
}
this.currentTab = -1;
this.currentTabLayout = null;
if (this.model.hasChanged('dashletsOptions')) {
this.dashletsOptions = Espo.Utils.cloneDeep(this.model.get('dashletsOptions') || {});
}
if (this.dashboardLayout.length) {
this.selectTab(0);
}
}
if (this.model.hasChanged(this.name)) {
if (this.dashboardLayout.length) {
if (this.isDetailMode()) {
this.selectTab(0);
}
}
}
/**
* @protected
* @param {number} tab
*/
selectTab(tab) {
this.currentTab = tab;
this.setupCurrentTabLayout();
if (this.hasLocked()) {
this.dashboardLocked = this.model.get('dashboardLocked') || false;
}
});
if (this.isRendered()) {
this.reRender()
.then(() => {
this.$el
.find(`[data-action="selectTab"][data-tab="${tab}"]`)
.focus();
})
}
}
this.currentTab = -1;
/**
* @protected
*/
setupCurrentTabLayout() {
if (!~this.currentTab) {
this.currentTabLayout = null;
}
if (this.dashboardLayout.length) {
this.selectTab(0);
let tabLayout = this.dashboardLayout[this.currentTab].layout || [];
tabLayout = GridStack.Utils.sort(tabLayout);
this.currentTabLayout = tabLayout;
}
/**
* @protected
* @param {string} id
* @param {string} name
*/
addDashletHtml(id, name) {
const $item = this.prepareGridstackItem(id, name);
this.grid.addWidget(
$item.get(0),
{
x: 0,
y: 0,
w: 2 * this.WIDTH_MULTIPLIER,
h: 2,
}
},
);
}
selectTab: function (tab) {
this.currentTab = tab;
this.setupCurrentTabLayout();
/**
* @private
* @return {string}
*/
generateId() {
return (Math.floor(Math.random() * 10000001)).toString();
}
if (this.isRendered()) {
this.reRender()
.then(() => {
this.$el
.find(`[data-action="selectTab"][data-tab="${tab}"]`)
.focus();
})
}
},
/**
* @protected
* @param {string} name
*/
addDashlet(name) {
const id = 'd' + (Math.floor(Math.random() * 1000001)).toString();
setupCurrentTabLayout: function () {
if (!~this.currentTab) {
this.currentTabLayout = null;
}
var tabLayout = this.dashboardLayout[this.currentTab].layout || [];
tabLayout = GridStack.Utils.sort(tabLayout);
this.currentTabLayout = tabLayout;
},
addDashletHtml: function (id, name) {
var $item = this.prepareGridstackItem(id, name);
this.grid.addWidget(
$item.get(0),
{
x: 0,
y: 0,
w: 2 * this.WIDTH_MULTIPLIER,
h: 2,
}
);
},
generateId: function () {
return (Math.floor(Math.random() * 10000001)).toString();
},
addDashlet: function (name) {
var id = 'd' + (Math.floor(Math.random() * 1000001)).toString();
if (!~this.currentTab) {
this.dashboardLayout.push({
name: 'My Espo',
layout: [],
id: this.generateId(),
});
this.currentTab = 0;
this.setupCurrentTabLayout();
this.once('after:render', () => {
setTimeout(() => {
this.addDashletHtml(id, name);
this.fetchLayout();
}, 50);
});
this.reRender();
}
else {
this.addDashletHtml(id, name);
this.fetchLayout();
}
},
removeDashlet: function (id) {
let $item = this.$gridstack.find('.grid-stack-item[data-id="'+id+'"]');
this.grid.removeWidget($item.get(0), true);
var layout = this.dashboardLayout[this.currentTab].layout;
layout.forEach((o, i) => {
if (o.id === id) {
layout.splice(i, 1);
}
if (!~this.currentTab) {
this.dashboardLayout.push({
name: 'My Espo',
layout: [],
id: this.generateId(),
});
delete this.dashletsOptions[id];
this.currentTab = 0;
this.setupCurrentTabLayout();
},
editTabs: function () {
let options = {
dashboardLayout: this.dashboardLayout,
tabListIsNotRequired: true,
};
this.once('after:render', () => {
setTimeout(() => {
this.addDashletHtml(id, name);
this.fetchLayout();
}, 50);
});
if (this.hasLocked()) {
options.dashboardLocked = this.dashboardLocked;
this.reRender();
} else {
this.addDashletHtml(id, name);
this.fetchLayout();
}
}
/**
* @protected
* @param {string} id
*/
removeDashlet(id) {
const $item = this.$gridstack.find('.grid-stack-item[data-id="' + id + '"]');
this.grid.removeWidget($item.get(0), true);
const layout = this.dashboardLayout[this.currentTab].layout;
layout.forEach((o, i) => {
if (o.id === id) {
layout.splice(i, 1);
}
});
this.createView('editTabs', 'views/modals/edit-dashboard', options, view => {
view.render();
delete this.dashletsOptions[id];
this.listenToOnce(view, 'after:save', data => {
view.close();
this.setupCurrentTabLayout();
}
let dashboardLayout = [];
/**
* @protected
*/
editTabs() {
const options = {
dashboardLayout: this.dashboardLayout,
tabListIsNotRequired: true,
};
data.dashboardTabList.forEach(name => {
var layout = [];
var id = this.generateId();
if (this.hasLocked()) {
options.dashboardLocked = this.dashboardLocked;
}
this.dashboardLayout.forEach(d => {
if (d.name === name) {
layout = d.layout;
id = d.id;
}
});
this.createView('editTabs', 'views/modals/edit-dashboard', options, view => {
view.render();
if (name in data.renameMap) {
name = data.renameMap[name];
this.listenToOnce(view, 'after:save', data => {
view.close();
const dashboardLayout = [];
data.dashboardTabList.forEach(name => {
let layout = [];
let id = this.generateId();
this.dashboardLayout.forEach(d => {
if (d.name === name) {
layout = d.layout;
id = d.id;
}
dashboardLayout.push({
name: name,
layout: layout,
id: id,
});
});
this.dashboardLayout = dashboardLayout;
if (this.hasLocked()) {
this.dashboardLocked = data.dashboardLocked;
if (name in data.renameMap) {
name = data.renameMap[name];
}
this.selectTab(0);
this.deleteNotExistingDashletsOptions();
dashboardLayout.push({
name: name,
layout: layout,
id: id,
});
});
});
},
deleteNotExistingDashletsOptions: function () {
var idListMet = [];
this.dashboardLayout = dashboardLayout;
(this.dashboardLayout || []).forEach((itemTab) => {
(itemTab.layout || []).forEach((item) => {
idListMet.push(item.id);
});
});
Object.keys(this.dashletsOptions).forEach((id) => {
if (!~idListMet.indexOf(id)) {
delete this.dashletsOptions[id];
}
});
},
editDashlet: function (id, name) {
var options = this.dashletsOptions[id] || {};
options = Espo.Utils.cloneDeep(options);
var defaultOptions = this.getMetadata().get(['dashlets', name , 'options', 'defaults']) || {};
Object.keys(defaultOptions).forEach((item) => {
if (item in options) {
return;
if (this.hasLocked()) {
this.dashboardLocked = data.dashboardLocked;
}
options[item] = Espo.Utils.cloneDeep(defaultOptions[item]);
});
this.selectTab(0);
if (!('title' in options)) {
options.title = this.translate(name, 'dashlets');
this.deleteNotExistingDashletsOptions();
});
});
}
/**
* @private
*/
deleteNotExistingDashletsOptions() {
const idListMet = [];
(this.dashboardLayout || []).forEach((itemTab) => {
(itemTab.layout || []).forEach((item) => {
idListMet.push(item.id);
});
});
Object.keys(this.dashletsOptions).forEach((id) => {
if (!~idListMet.indexOf(id)) {
delete this.dashletsOptions[id];
}
});
}
var optionsView = this.getMetadata().get(['dashlets', name, 'options', 'view']) ||
'views/dashlets/options/base';
/**
* @protected
* @param {string} id
* @param {string} name
*/
editDashlet(id, name) {
let options = this.dashletsOptions[id] || {};
options = Espo.Utils.cloneDeep(options);
this.createView('options', optionsView, {
name: name,
optionsData: options,
fields: this.getMetadata().get(['dashlets', name, 'options', 'fields']) || {},
userId: this.model.entityType === 'Preferences' ? this.model.id : null,
}, view => {
view.render();
const defaultOptions = this.getMetadata().get(['dashlets', name, 'options', 'defaults']) || {};
this.listenToOnce(view, 'save', (attributes) => {
this.dashletsOptions[id] = attributes;
view.close();
if ('title' in attributes) {
var title = attributes.title;
if (!title) {
title = this.translate(name, 'dashlets');
}
this.$el.find('[data-id="'+id+'"] .panel-title').text(title);
}
});
});
},
fetchLayout: function () {
if (!~this.currentTab) {
Object.keys(defaultOptions).forEach((item) => {
if (item in options) {
return;
}
this.dashboardLayout[this.currentTab].layout = _.map(this.$gridstack.find('.grid-stack-item'), el => {
var $el = $(el);
options[item] = Espo.Utils.cloneDeep(defaultOptions[item]);
});
let x = $el.attr('gs-x');
let y = $el.attr('gs-y');
let h = $el.attr('gs-h');
let w = $el.attr('gs-w');
if (!('title' in options)) {
options.title = this.translate(name, 'dashlets');
}
return {
id: $el.data('id'),
name: $el.data('name'),
x: x / this.WIDTH_MULTIPLIER,
y: y,
width: w / this.WIDTH_MULTIPLIER,
height: h,
};
const optionsView = this.getMetadata().get(['dashlets', name, 'options', 'view']) ||
'views/dashlets/options/base';
this.createView('options', optionsView, {
name: name,
optionsData: options,
fields: this.getMetadata().get(['dashlets', name, 'options', 'fields']) || {},
userId: this.model.entityType === 'Preferences' ? this.model.id : null,
}, view => {
view.render();
this.listenToOnce(view, 'save', (attributes) => {
this.dashletsOptions[id] = attributes;
view.close();
if ('title' in attributes) {
let title = attributes.title;
if (!title) {
title = this.translate(name, 'dashlets');
}
this.$el.find('[data-id="'+id+'"] .panel-title').text(title);
}
});
});
}
/**
* @protected
*/
fetchLayout() {
if (!~this.currentTab) {
return;
}
this.dashboardLayout[this.currentTab].layout = _.map(this.$gridstack.find('.grid-stack-item'), el => {
const $el = $(el);
const x = $el.attr('gs-x');
const y = $el.attr('gs-y');
const h = $el.attr('gs-h');
const w = $el.attr('gs-w');
return {
id: $el.data('id'),
name: $el.data('name'),
x: x / this.WIDTH_MULTIPLIER,
y: y,
width: w / this.WIDTH_MULTIPLIER,
height: h,
};
});
this.setupCurrentTabLayout();
}
afterRender() {
if (this.currentTabLayout) {
const $gridstack = this.$gridstack = this.$el.find('> .grid-stack');
const grid = this.grid = GridStack.init({
minWidth: 4,
cellHeight: 60,
margin: 10,
column: 12,
resizable: {
handles: 'se',
helper: false
},
disableOneColumnMode: true,
animate: false,
staticGrid: this.mode !== 'edit',
disableResize: this.mode !== 'edit',
disableDrag: this.mode !== 'edit',
});
this.setupCurrentTabLayout();
},
grid.removeAll();
afterRender: function () {
if (this.currentTabLayout) {
var $gridstack = this.$gridstack = this.$el.find('> .grid-stack');
this.currentTabLayout.forEach((o) => {
const $item = this.prepareGridstackItem(o.id, o.name);
var grid = this.grid = GridStack.init({
minWidth: 4,
cellHeight: 60,
margin: 10,
column: 12,
resizable: {
handles: 'se',
helper: false
},
disableOneColumnMode: true,
animate: false,
staticGrid: this.mode !== 'edit',
disableResize: this.mode !== 'edit',
disableDrag: this.mode !== 'edit',
});
this.grid.addWidget(
$item.get(0),
{
x: o.x * this.WIDTH_MULTIPLIER,
y: o.y,
w: o.width * this.WIDTH_MULTIPLIER,
h: o.height,
}
);
});
grid.removeAll();
$gridstack.find(' .grid-stack-item').css('position', 'absolute');
this.currentTabLayout.forEach((o) => {
var $item = this.prepareGridstackItem(o.id, o.name);
$gridstack.on('change', () => {
this.fetchLayout();
this.trigger('change');
});
}
}
this.grid.addWidget(
$item.get(0),
{
x: o.x * this.WIDTH_MULTIPLIER,
y: o.y,
w: o.width * this.WIDTH_MULTIPLIER,
h: o.height,
}
);
});
/**
* @private
* @param {string} id
* @param {string} name
* @return {jQuery|JQuery}
*/
prepareGridstackItem(id, name) {
const $item = $('<div>').addClass('grid-stack-item');
let actionsHtml = '';
$gridstack.find(' .grid-stack-item').css('position', 'absolute');
$gridstack.on('change', (e, itemList) => {
this.fetchLayout();
this.trigger('change');
});
}
},
prepareGridstackItem: function (id, name) {
let $item = $('<div>').addClass('grid-stack-item');
let actionsHtml = '';
if (this.isEditMode()) {
actionsHtml +=
$('<div>')
.addClass('btn-group pull-right')
.append(
$('<button>')
.addClass('btn btn-default')
.attr('data-action', 'removeDashlet')
.attr('data-id', id)
.attr('title', this.translate('Remove'))
.append(
$('<span>').addClass('fas fa-times')
)
)
.get(0)
.outerHTML;
actionsHtml += $('<div>')
if (this.isEditMode()) {
actionsHtml +=
$('<div>')
.addClass('btn-group pull-right')
.append(
$('<button>')
.addClass('btn btn-default')
.attr('data-action', 'editDashlet')
.attr('data-action', 'removeDashlet')
.attr('data-id', id)
.attr('data-name', name)
.attr('title', this.translate('Edit'))
.attr('title', this.translate('Remove'))
.append(
$('<span>')
.addClass('fas fa-pencil-alt fa-sm')
.css({
position: 'relative',
top: '-1px',
})
$('<span>').addClass('fas fa-times')
)
)
.get(0)
.outerHTML;
}
let title = this.getOption(id, 'title');
if (!title) {
title = this.translate(name, 'dashlets');
}
let headerHtml = $('<div>')
.addClass('panel-heading')
.append(actionsHtml)
actionsHtml += $('<div>')
.addClass('btn-group pull-right')
.append(
$('<h4>').addClass('panel-title').text(title)
$('<button>')
.addClass('btn btn-default')
.attr('data-action', 'editDashlet')
.attr('data-id', id)
.attr('data-name', name)
.attr('title', this.translate('Edit'))
.append(
$('<span>')
.addClass('fas fa-pencil-alt fa-sm')
.css({
position: 'relative',
top: '-1px',
})
)
)
.get(0).outerHTML;
.get(0)
.outerHTML;
}
let $container =
$('<div>')
.addClass('grid-stack-item-content panel panel-default')
.append(headerHtml);
let title = this.getOption(id, 'title');
$container.attr('data-id', id);
$container.attr('data-name', name);
$item.attr('data-id', id);
$item.attr('data-name', name);
$item.append($container);
if (!title) {
title = this.translate(name, 'dashlets');
}
return $item;
},
const headerHtml = $('<div>')
.addClass('panel-heading')
.append(actionsHtml)
.append(
$('<h4>').addClass('panel-title').text(title)
)
.get(0).outerHTML;
getOption: function (id, optionName) {
var options = (this.model.get('dashletsOptions') || {})[id] || {};
const $container =
$('<div>')
.addClass('grid-stack-item-content panel panel-default')
.append(headerHtml);
return options[optionName];
},
$container.attr('data-id', id);
$container.attr('data-name', name);
$item.attr('data-id', id);
$item.attr('data-name', name);
$item.append($container);
isEmpty: function () {
var isEmpty = true;
return $item;
}
if (this.dashboardLayout && this.dashboardLayout.length) {
this.dashboardLayout.forEach((item) => {
if (item.layout && item.layout.length) {
isEmpty = false;
}
});
}
/**
* @param {string} id
* @param {string} optionName
* @return {*}
*/
getOption(id, optionName) {
const options = (this.model.get('dashletsOptions') || {})[id] || {};
return isEmpty;
},
return options[optionName];
}
validateRequired: function () {
if (!this.isRequired()) {
return;
}
/**
* @protected
* @return {boolean}
*/
isEmpty() {
let isEmpty = true;
if (this.isEmpty()) {
var msg = this.translate('fieldIsRequired', 'messages').replace('{field}', this.getLabelText());
this.showValidationMessage(msg);
if (this.dashboardLayout && this.dashboardLayout.length) {
this.dashboardLayout.forEach((item) => {
if (item.layout && item.layout.length) {
isEmpty = false;
}
});
}
return true;
}
},
return isEmpty;
}
fetch: function () {
let data = {};
validateRequired() {
if (!this.isRequired()) {
return;
}
if (!this.dashboardLayout || !this.dashboardLayout.length) {
data[this.name] = null;
data['dashletsOptions'] = {};
if (this.isEmpty()) {
const msg = this.translate('fieldIsRequired', 'messages').replace('{field}', this.getLabelText());
this.showValidationMessage(msg);
return data;
}
return true;
}
}
data[this.name] = Espo.Utils.cloneDeep(this.dashboardLayout);
fetch() {
const data = {};
data.dashletsOptions = Espo.Utils.cloneDeep(this.dashletsOptions);
if (this.hasLocked()) {
data.dashboardLocked = this.dashboardLocked;
}
if (!this.dashboardLayout || !this.dashboardLayout.length) {
data[this.name] = null;
data['dashletsOptions'] = {};
return data;
},
});
});
}
data[this.name] = Espo.Utils.cloneDeep(this.dashboardLayout);
data.dashletsOptions = Espo.Utils.cloneDeep(this.dashletsOptions);
if (this.hasLocked()) {
data.dashboardLocked = this.dashboardLocked;
}
return data;
}
}