This commit is contained in:
Yuri Kuznetsov
2024-09-11 16:24:31 +03:00
parent 76318ada9f
commit 112c2578e9
6 changed files with 447 additions and 458 deletions
+14 -18
View File
@@ -26,26 +26,22 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
define('views/webhook/fields/event', ['views/fields/varchar'], function (Dep) {
import VarcharFieldView from 'views/fields/varchar';
return Dep.extend({
export default class extends VarcharFieldView {
setupOptions: function () {
var itemList = [];
setupOptions() {
const itemList = [];
var scopeList = this.getMetadata().getScopeObjectList();
const scopeList = this.getMetadata().getScopeObjectList()
.sort((v1, v2) => v1.localeCompare(v2));
scopeList = scopeList.sort(function (v1, v2) {
return v1.localeCompare(v2);
}.bind(this));
scopeList.forEach(scope => {
itemList.push(`${scope}.create`);
itemList.push(`${scope}.update`);
itemList.push(`${scope}.delete`);
});
scopeList.forEach(function (scope) {
itemList.push(scope + '.' + 'create');
itemList.push(scope + '.' + 'update');
itemList.push(scope + '.' + 'delete');
}, this);
this.params.options = itemList;
},
});
});
this.params.options = itemList;
}
}
+4 -6
View File
@@ -26,11 +26,9 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
define('views/webhook/fields/user', ['views/fields/link'], function (Dep) {
import LinkFieldView from 'views/fields/link';
return Dep.extend({
export default class extends LinkFieldView {
selectPrimaryFilterName: 'activeApi',
});
});
selectPrimaryFilterName = 'activeApi';
}
+4 -6
View File
@@ -26,11 +26,9 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
define('views/webhook/record/list', ['views/record/list'], function (Dep) {
import ListRecordView from 'views/record/list';
return Dep.extend({
export default class extends ListRecordView {
massActionList: ['remove', 'massUpdate', 'export'],
});
});
massActionList = ['remove', 'massUpdate', 'export']
}
@@ -26,319 +26,322 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
define('views/working-time-calendar/fields/time-ranges', ['views/fields/base'], function (Dep) {
import BaseFieldView from 'views/fields/base';
export default class extends BaseFieldView {
// language=Handlebars
listTemplateContent = `
<div class="item-list">
{{#each itemDataList}}
<span class="item" data-key="{{key}}"
>{{{var viewKey ../this}}}</span>{{#unless isLast}} &nbsp;&middot;&nbsp; {{/unless}}
{{/each}}
</div>
{{#unless itemDataList.length}}
<span class="none-value">{{translate 'None'}}</span>
{{/unless}}
`
// language=Handlebars
detailTemplateContent = `
<div class="item-list">
{{#each itemDataList}}
<div class="item" data-key="{{key}}">
{{{var viewKey ../this}}}
</div>
{{/each}}
</div>
{{#unless itemDataList.length}}
<span class="none-value">{{translate 'None'}}</span>
{{/unless}}
`
// language=Handlebars
editTemplateContent = `
<div class="item-list">
{{#each itemDataList}}
<div class="item" data-key="{{key}}">
{{{var viewKey ../this}}}
</div>
{{/each}}
</div>
<div class="add-item-container margin-top-sm">
<a
role="button"
tabindex="0"
class="add-item"
title="{{translate 'Add'}}"
><span class="fas fa-plus"></span></a>
</div>
`
// noinspection JSCheckFunctionSignatures
data() {
const data = super.data();
data.itemDataList = this.itemKeyList.map((key, i) => {
return {
key: key.toString(),
viewKey: this.composeViewKey(key),
isLast: i === this.itemKeyList.length - 1,
};
});
// noinspection JSValidateTypes
return data;
}
setup() {
super.setup();
this.validations = [
() => this.validateRequired(),
() => this.validateValid(),
];
this.addHandler('click', '.add-item', () => this.addItem());
this.addHandler('click', '.remove-item', (e, target) => {
this.removeItem(parseInt(target.dataset.key));
});
}
prepare() {
this.initItems();
return this.createItemViews();
}
initItems() {
this.itemKeyList = [];
this.getItemListFromModel().forEach((item, i) => {
this.itemKeyList.push(i);
});
}
/**
* @class
* @name Class
* @memberOf module:views/working-time-calendar/fields/time-ranges
* @extends module:views/fields/base
* @returns {Promise}
*/
return Dep.extend(/** @lends module:views/working-time-calendar/fields/time-ranges.Class# */{
createItemView(item, key) {
const viewName = this.isEditMode() ?
'views/working-time-calendar/fields/time-ranges/item-edit' :
'views/working-time-calendar/fields/time-ranges/item-detail';
listTemplateContent: `
<div class="item-list">
{{#each itemDataList}}
<span class="item" data-key="{{key}}"
>{{{var viewKey ../this}}}</span>{{#unless isLast}} &nbsp;&middot;&nbsp; {{/unless}}
{{/each}}
</div>
{{#unless itemDataList.length}}
<span class="none-value">{{translate 'None'}}</span>
{{/unless}}
`,
detailTemplateContent: `
<div class="item-list">
{{#each itemDataList}}
<div class="item" data-key="{{key}}">
{{{var viewKey ../this}}}
</div>
{{/each}}
</div>
{{#unless itemDataList.length}}
<span class="none-value">{{translate 'None'}}</span>
{{/unless}}
`,
editTemplateContent: `
<div class="item-list">
{{#each itemDataList}}
<div class="item" data-key="{{key}}">
{{{var viewKey ../this}}}
</div>
{{/each}}
</div>
<div class="add-item-container margin-top-sm">
<a
role="button"
tabindex="0"
class="add-item"
title="{{translate 'Add'}}"
><span class="fas fa-plus"></span></a>
</div>
`,
fetchEmptyAsNull: false,
validations: ['required', 'valid'],
events: {
'click .add-item': function () {
this.addItem();
},
'click .remove-item': function (e) {
let key = parseInt($(e.currentTarget).attr('data-key'));
this.removeItem(key);
},
},
data: function () {
let data = Dep.prototype.data.call(this);
data.itemDataList = this.itemKeyList.map((key, i) => {
return {
key: key.toString(),
viewKey: this.composeViewKey(key),
isLast: i === this.itemKeyList.length - 1,
};
});
return data;
},
prepare: function () {
this.initItems();
return this.createItemViews();
},
initItems: function () {
this.itemKeyList = [];
this.getItemListFromModel().forEach((item, i) => {
this.itemKeyList.push(i);
});
},
/**
* @returns {Promise}
*/
createItemView: function (item, key) {
let viewName = this.isEditMode() ?
'views/working-time-calendar/fields/time-ranges/item-edit' :
'views/working-time-calendar/fields/time-ranges/item-detail';
return this.createView(
this.composeViewKey(key),
viewName,
{
value: item,
selector: '.item[data-key="' + key + '"]',
key: key,
}
)
.then(view => {
this.listenTo(view, 'change', () => {
this.trigger('change');
});
return view;
});
},
/**
* @returns {Promise}
*/
createItemViews: function () {
this.itemKeyList.forEach(key => {
this.clearView(this.composeViewKey(key));
});
if (!this.model.has(this.name)) {
return Promise.resolve();
return this.createView(
this.composeViewKey(key),
viewName,
{
value: item,
selector: `.item[data-key="${key}"]`,
key: key,
}
let itemList = this.getItemListFromModel();
let promiseList = [];
this.itemKeyList.forEach((key, i) => {
let item = itemList[i];
let promise = this.createItemView(item, key);
promiseList.push(promise);
)
.then(view => {
this.listenTo(view, 'change', () => {
this.trigger('change');
});
return Promise.all(promiseList);
},
getItemView: function (key) {
return this.getView(this.composeViewKey(key));
},
composeViewKey: function (key) {
return 'item-' + key;
},
/**
* @return {[string|null, string|null][]}
*/
getItemListFromModel: function () {
return Espo.Utils.cloneDeep(this.model.get(this.name) || []);
},
addItem: function () {
let itemList = this.getItemListFromModel();
let value = null;
if (itemList.length) {
value = itemList[itemList.length - 1][1];
}
let item = [value, null];
itemList.push(item);
let key = this.itemKeyList[this.itemKeyList.length - 1];
if (typeof key === 'undefined') {
key = 0;
}
key++;
this.itemKeyList.push(key);
this.$el.find('.item-list').append(
$('<div>')
.addClass('item')
.attr('data-key', key)
);
this.createItemView(item, key)
.then(view => view.render())
.then(() => {
this.trigger('change');
});
},
removeItem: function (key) {
let index = this.itemKeyList.indexOf(key);
if (key === -1) {
return;
}
let itemList = this.getItemListFromModel();
this.itemKeyList.splice(index, 1);
itemList.splice(index, 1);
this.model.set(this.name, itemList, {ui: true});
return view;
});
}
/**
* @returns {Promise}
*/
createItemViews() {
this.itemKeyList.forEach(key => {
this.clearView(this.composeViewKey(key));
});
this.$el.find(`.item[data-key="${key}"`).remove();
if (!this.model.has(this.name)) {
return Promise.resolve();
}
this.trigger('change');
},
const itemList = this.getItemListFromModel();
fetch: function () {
let itemList = [];
const promiseList = [];
this.itemKeyList.forEach(key => {
itemList.push(
this.getItemView(key).fetch()
);
this.itemKeyList.forEach((key, i) => {
const item = itemList[i];
const promise = this.createItemView(item, key);
promiseList.push(promise);
});
return Promise.all(promiseList);
}
/**
* @param {string} key
* @return {import('./time-ranges/item-edit').default}
*/
getItemView(key) {
// noinspection JSValidateTypes
return this.getView(this.composeViewKey(key));
}
composeViewKey(key) {
return `item-${key}`;
}
/**
* @return {[string|null, string|null][]}
*/
getItemListFromModel() {
return Espo.Utils.cloneDeep(this.model.get(this.name) || []);
}
addItem() {
const itemList = this.getItemListFromModel();
let value = null;
if (itemList.length) {
value = itemList[itemList.length - 1][1];
}
const item = [value, null];
itemList.push(item);
let key = this.itemKeyList[this.itemKeyList.length - 1];
if (typeof key === 'undefined') {
key = 0;
}
key++;
this.itemKeyList.push(key);
this.$el.find('.item-list').append(
$('<div>')
.addClass('item')
.attr('data-key', key)
);
this.createItemView(item, key)
.then(view => view.render())
.then(() => {
this.trigger('change');
});
}
let data = {};
removeItem(key) {
const index = this.itemKeyList.indexOf(key);
data[this.name] = Espo.Utils.cloneDeep(itemList);
if (key === -1) {
return;
}
if (data[this.name].length === 0) {
data[this.name] = null;
}
const itemList = this.getItemListFromModel();
return data;
},
this.itemKeyList.splice(index, 1);
itemList.splice(index, 1);
validateRequired: function () {
if (!this.isRequired()) {
return false;
}
this.model.set(this.name, itemList, {ui: true});
if (this.getItemListFromModel().length) {
return false;
}
this.clearView(this.composeViewKey(key));
let msg = this.translate('fieldIsRequired', 'messages')
.replace('{field}', this.getLabelText());
this.$el.find(`.item[data-key="${key}"`).remove();
this.showValidationMessage(msg, '.add-item-container');
this.trigger('change');
}
return true;
},
fetch() {
const itemList = [];
validateValid: function () {
if (!this.isRangesInvalid()) {
return false;
}
this.itemKeyList.forEach(key => {
itemList.push(
this.getItemView(key).fetch()
);
});
let msg = this.translate('fieldInvalid', 'messages')
.replace('{field}', this.getLabelText());
const data = {};
this.showValidationMessage(msg, '.add-item-container');
data[this.name] = Espo.Utils.cloneDeep(itemList);
return true;
},
if (data[this.name].length === 0) {
data[this.name] = null;
}
isRangesInvalid: function () {
let itemList = this.getItemListFromModel();
for (let i = 0; i < itemList.length; i++) {
let item = itemList[i];
if (this.isRangeInvalid(item[0], item[1], true)) {
return true;
}
if (i === 0) {
continue;
}
let prevItem = item[i - 1];
if (this.isRangeInvalid(prevItem[1], item[0])) {
return true;
}
}
return data;
}
validateRequired() {
if (!this.isRequired()) {
return false;
},
}
/**
* @param {string|null} from
* @param {string|null} to
* @param {boolean} [noEmpty]
*/
isRangeInvalid: function (from, to, noEmpty) {
if (from === null || to === null) {
if (this.getItemListFromModel().length) {
return false;
}
const msg = this.translate('fieldIsRequired', 'messages')
.replace('{field}', this.getLabelText());
this.showValidationMessage(msg, '.add-item-container');
return true;
}
validateValid() {
if (!this.isRangesInvalid()) {
return false;
}
const msg = this.translate('fieldInvalid', 'messages')
.replace('{field}', this.getLabelText());
this.showValidationMessage(msg, '.add-item-container');
return true;
}
isRangesInvalid() {
const itemList = this.getItemListFromModel();
for (let i = 0; i < itemList.length; i++) {
const item = itemList[i];
if (this.isRangeInvalid(item[0], item[1], true)) {
return true;
}
let fromNumber = parseFloat(from.replace(':', '.'));
let toNumber = parseFloat(to.replace(':', '.'));
if (noEmpty && fromNumber === toNumber) {
return true;
if (i === 0) {
continue;
}
return fromNumber > toNumber;
},
});
});
const prevItem = item[i - 1];
if (this.isRangeInvalid(prevItem[1], item[0])) {
return true;
}
}
return false;
}
/**
* @param {string|null} from
* @param {string|null} to
* @param {boolean} [noEmpty]
*/
isRangeInvalid(from, to, noEmpty) {
if (from === null || to === null) {
return true;
}
const fromNumber = parseFloat(from.replace(':', '.'));
const toNumber = parseFloat(to.replace(':', '.'));
if (noEmpty && fromNumber === toNumber) {
return true;
}
return fromNumber > toNumber;
}
}
@@ -26,45 +26,39 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
define('views/working-time-calendar/fields/time-ranges/item-detail', ['view', 'lib!moment'],
(Dep, /** @param {moment} */moment) => {
import View from 'view';
import moment from 'moment';
/**
* @extends module:view
*/
class Class extends Dep
{
templateContent = `
{{start}}
&nbsp;&nbsp;
{{end}}
`
export default class extends View {
data() {
return {
start: this.convertTimeToDisplay(this.value[0]),
end: this.convertTimeToDisplay(this.value[1]),
};
}
templateContent = `
{{start}}
&nbsp;&nbsp;
{{end}}
`
setup() {
this.value = this.options.value;
}
convertTimeToDisplay(value) {
if (!value) {
return '';
}
let m = moment(value, 'HH:mm');
if (!m.isValid()) {
return '';
}
return m.format(this.getDateTime().timeFormat);
}
data() {
return {
start: this.convertTimeToDisplay(this.value[0]),
end: this.convertTimeToDisplay(this.value[1]),
};
}
return Class;
});
setup() {
this.value = this.options.value;
}
convertTimeToDisplay(value) {
if (!value) {
return '';
}
const m = moment(value, 'HH:mm');
if (!m.isValid()) {
return '';
}
return m.format(this.getDateTime().timeFormat);
}
}
@@ -26,144 +26,144 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
define('views/working-time-calendar/fields/time-ranges/item-edit', ['view', 'lib!moment'], function (Dep, moment) {
import View from 'view';
import moment from 'moment';
return Dep.extend({
export default class extends View {
// language=Handlebars
templateContent: `
<div class="row">
<div class="start-container col-xs-5">
<input
class="form-control"
type="text"
data-name="start"
value="{{start}}"
autocomplete="espo-start"
spellcheck="false"
>
</div>
<div class="start-container col-xs-1 center-align">
<span class="field-row-text-item">&nbsp;&nbsp;</span>
</div>
<div class="end-container col-xs-5">
<input
class="form-control"
type="text"
data-name="end"
value="{{end}}"
autocomplete="espo-end"
spellcheck="false"
>
</div>
<div class="col-xs-1 center-align">
<a
role="button"
tabindex="0"
class="remove-item field-row-text-item"
data-key="{{key}}"
title="{{translate 'Remove'}}"
><span class="fas fa-times"></span></a>
</div>
// language=Handlebars
templateContent = `
<div class="row">
<div class="start-container col-xs-5">
<input
class="form-control"
type="text"
data-name="start"
value="{{start}}"
autocomplete="espo-start"
spellcheck="false"
>
</div>
`,
<div class="start-container col-xs-1 center-align">
<span class="field-row-text-item">&nbsp;&nbsp;</span>
</div>
<div class="end-container col-xs-5">
<input
class="form-control"
type="text"
data-name="end"
value="{{end}}"
autocomplete="espo-end"
spellcheck="false"
>
</div>
<div class="col-xs-1 center-align">
<a
role="button"
tabindex="0"
class="remove-item field-row-text-item"
data-key="{{key}}"
title="{{translate 'Remove'}}"
><span class="fas fa-times"></span></a>
</div>
</div>
`
timeFormatMap: {
'HH:mm': 'H:i',
'hh:mm A': 'h:i A',
'hh:mm a': 'h:i a',
'hh:mmA': 'h:iA',
'hh:mma': 'h:ia',
},
timeFormatMap = {
'HH:mm': 'H:i',
'hh:mm A': 'h:i A',
'hh:mm a': 'h:i a',
'hh:mmA': 'h:iA',
'hh:mma': 'h:ia',
}
minuteStep: 30,
minuteStep = 30
data: function () {
let data = {};
data () {
const data = {};
data.start = this.convertTimeToDisplay(this.value[0]);
data.end = this.convertTimeToDisplay(this.value[1]);
data.start = this.convertTimeToDisplay(this.value[0]);
data.end = this.convertTimeToDisplay(this.value[1]);
data.key = this.key;
data.key = this.key;
return data;
},
return data;
}
setup: function () {
this.value = this.options.value || [null, null];
this.key = this.options.key;
},
setup () {
this.value = this.options.value || [null, null];
this.key = this.options.key;
}
convertTimeToDisplay: function (value) {
if (!value) {
return '';
}
convertTimeToDisplay(value) {
if (!value) {
return '';
}
let m = moment(value, 'HH:mm');
const m = moment(value, 'HH:mm');
if (!m.isValid()) {
return '';
}
if (!m.isValid()) {
return '';
}
return m.format(this.getDateTime().timeFormat);
},
return m.format(this.getDateTime().timeFormat);
}
convertTimeFromDisplay: function (value) {
if (!value) {
return null;
}
convertTimeFromDisplay(value) {
if (!value) {
return null;
}
let m = moment(value, this.getDateTime().timeFormat);
const m = moment(value, this.getDateTime().timeFormat);
if (!m.isValid()) {
return null;
}
if (!m.isValid()) {
return null;
}
return m.format('HH:mm');
},
return m.format('HH:mm');
}
afterRender: function () {
this.$start = this.$el.find('[data-name="start"]');
this.$end = this.$el.find('[data-name="end"]');
afterRender() {
this.$start = this.$el.find('[data-name="start"]');
this.$end = this.$el.find('[data-name="end"]');
this.initTimepicker(this.$start);
this.initTimepicker(this.$end);
this.initTimepicker(this.$start);
this.initTimepicker(this.$end);
this.setMinTime();
this.setMinTime();
this.$start.on('change', () => this.setMinTime());
},
this.$start.on('change', () => this.setMinTime());
}
setMinTime: function () {
let value = this.$start.val();
setMinTime() {
const value = this.$start.val();
this.$end.timepicker('option', 'maxTime', this.convertTimeToDisplay('23:59'));
this.$end.timepicker('option', 'maxTime', this.convertTimeToDisplay('23:59'));
if (!value) {
this.$end.timepicker('option', 'minTime', null);
if (!value) {
this.$end.timepicker('option', 'minTime', null);
return;
}
return;
}
this.$end.timepicker('option', 'minTime', value);
},
this.$end.timepicker('option', 'minTime', value);
}
initTimepicker: function ($el) {
$el.timepicker({
step: this.minuteStep,
timeFormat: this.timeFormatMap[this.getDateTime().timeFormat],
});
initTimepicker($el) {
$el.timepicker({
step: this.minuteStep,
timeFormat: this.timeFormatMap[this.getDateTime().timeFormat],
});
$el.on('change', () => this.trigger('change'));
$el.on('change', () => this.trigger('change'));
$el.attr('autocomplete', 'espo-time-range-item');
}
$el.attr('autocomplete', 'espo-time-range-item');
},
fetch: function () {
return [
this.convertTimeFromDisplay(this.$start.val()),
this.convertTimeFromDisplay(this.$end.val()),
];
},
});
});
fetch() {
return [
this.convertTimeFromDisplay(this.$start.val()),
this.convertTimeFromDisplay(this.$end.val()),
];
}
}