194 lines
8.0 KiB
JavaScript
194 lines
8.0 KiB
JavaScript
/** @odoo-module **/
|
|
|
|
import options from '@web_editor/js/editor/snippets.options';
|
|
import weUtils from '@web_editor/js/common/utils';
|
|
|
|
options.registry.StepsConnector = options.Class.extend({
|
|
/**
|
|
* @override
|
|
*/
|
|
start() {
|
|
this.$target.on('content_changed.StepsConnector', () => this._reloadConnectors());
|
|
return this._super(...arguments);
|
|
},
|
|
/**
|
|
* @override
|
|
*/
|
|
destroy() {
|
|
this._super(...arguments);
|
|
this.$target.off('.StepsConnector');
|
|
},
|
|
|
|
//--------------------------------------------------------------------------
|
|
// Options
|
|
//--------------------------------------------------------------------------
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
selectClass: function (previewMode, value, params) {
|
|
this._super(...arguments);
|
|
if (params.name === 'connector_type') {
|
|
this._reloadConnectors();
|
|
let markerEnd = '';
|
|
if (['s_process_steps_connector_arrow', 's_process_steps_connector_curved_arrow'].includes(value)) {
|
|
const arrowHeadEl = this.$target[0].querySelector('.s_process_steps_arrow_head');
|
|
// The arrowhead id is set here so that they are different per snippet.
|
|
if (!arrowHeadEl.id) {
|
|
arrowHeadEl.id = 's_process_steps_arrow_head' + Date.now();
|
|
}
|
|
markerEnd = `url(#${arrowHeadEl.id})`;
|
|
}
|
|
this.$target[0].querySelectorAll('.s_process_step_connector path').forEach(path => path.setAttribute('marker-end', markerEnd));
|
|
}
|
|
},
|
|
/**
|
|
* Changes arrow heads' fill color.
|
|
*
|
|
* @see this.selectClass for parameters
|
|
*/
|
|
changeColor(previewMode, widgetValue, params) {
|
|
const htmlPropColor = weUtils.getCSSVariableValue(widgetValue);
|
|
const arrowHeadEl = this.$target[0].closest('.s_process_steps').querySelector('.s_process_steps_arrow_head');
|
|
arrowHeadEl.querySelector('path').style.fill = htmlPropColor || widgetValue;
|
|
},
|
|
|
|
//--------------------------------------------------------------------------
|
|
// Public
|
|
//--------------------------------------------------------------------------
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
notify(name) {
|
|
if (['change_column_size', 'change_container_width', 'change_columns', 'move_snippet'].includes(name)) {
|
|
this._reloadConnectors();
|
|
} else {
|
|
this._super(...arguments);
|
|
}
|
|
},
|
|
|
|
//--------------------------------------------------------------------------
|
|
// Private
|
|
//--------------------------------------------------------------------------
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
_computeVisibility() {
|
|
// We don't use the service_context_get intentionally because the
|
|
// connectors are hidden as soon as the page is smaller than 992px
|
|
// (the BS lg breakpoint).
|
|
const isMobileView = weUtils.isMobileView(this.$target[0]);
|
|
return !isMobileView && this._super(...arguments);
|
|
},
|
|
/**
|
|
* Width and position of the connectors should be updated when one of the
|
|
* steps is modified.
|
|
*
|
|
* @private
|
|
*/
|
|
_reloadConnectors() {
|
|
const possibleTypes = this._requestUserValueWidgets('connector_type')[0].getMethodsParams().optionsPossibleValues.selectClass;
|
|
const type = possibleTypes.find(possibleType => possibleType && this.$target[0].classList.contains(possibleType)) || '';
|
|
// As the connectors are only visible in desktop, we can ignore the
|
|
// steps that are only visible in mobile.
|
|
const stepsEls = this.$target[0].querySelectorAll('.s_process_step:not(.o_snippet_desktop_invisible)');
|
|
const nbBootstrapCols = 12;
|
|
let colsInRow = 0;
|
|
|
|
for (let i = 0; i < stepsEls.length - 1; i++) {
|
|
const connectorEl = stepsEls[i].querySelector('.s_process_step_connector');
|
|
const stepMainElementRect = this._getStepMainElementRect(stepsEls[i]);
|
|
const nextStepMainElementRect = this._getStepMainElementRect(stepsEls[i + 1]);
|
|
const stepSize = this._getClassSuffixedInteger(stepsEls[i], 'col-lg-');
|
|
const nextStepSize = this._getClassSuffixedInteger(stepsEls[i + 1], 'col-lg-');
|
|
const stepOffset = this._getClassSuffixedInteger(stepsEls[i], 'offset-lg-');
|
|
const nextStepOffset = this._getClassSuffixedInteger(stepsEls[i + 1], 'offset-lg-');
|
|
const stepPaddingTop = this._getClassSuffixedInteger(stepsEls[i], 'pt');
|
|
const nextStepPaddingTop = this._getClassSuffixedInteger(stepsEls[i + 1], 'pt');
|
|
|
|
connectorEl.style.left = `calc(50% + ${stepMainElementRect.width / 2}px + 16px)`;
|
|
connectorEl.style.height = `${stepMainElementRect.height}px`;
|
|
connectorEl.style.width = `calc(${100 * (stepSize / 2 + nextStepOffset + nextStepSize / 2) / stepSize}% - ${stepMainElementRect.width / 2}px - ${nextStepMainElementRect.width / 2}px - 32px)`;
|
|
|
|
const isTheLastColOfRow = nbBootstrapCols <
|
|
colsInRow + stepSize + stepOffset + nextStepSize + nextStepOffset;
|
|
const isNextStepTooLow = stepMainElementRect.height + stepPaddingTop <
|
|
nextStepPaddingTop;
|
|
connectorEl.classList.toggle('d-none', isTheLastColOfRow || isNextStepTooLow);
|
|
colsInRow = isTheLastColOfRow ? 0 : colsInRow + stepSize + stepOffset;
|
|
// When we are mobile view, the connector is not visible, here we
|
|
// display it quickly just to have its size.
|
|
connectorEl.style.display = 'block';
|
|
const {height, width} = connectorEl.getBoundingClientRect();
|
|
connectorEl.style.removeProperty('display');
|
|
connectorEl.setAttribute('viewBox', `0 0 ${width} ${height}`);
|
|
connectorEl.querySelector('path').setAttribute('d', this._getPath(type, width, height));
|
|
}
|
|
},
|
|
/**
|
|
* Returns the number suffixed to the class given in parameter.
|
|
*
|
|
* @private
|
|
* @param {HTMLElement} el
|
|
* @param {String} classNamePrefix
|
|
* @returns {Integer}
|
|
*/
|
|
_getClassSuffixedInteger(el, classNamePrefix) {
|
|
const className = [...el.classList].find(cl => cl.startsWith(classNamePrefix));
|
|
return className ? parseInt(className.replace(classNamePrefix, '')) : 0;
|
|
},
|
|
/**
|
|
* Returns the step's icon or content bounding rectangle.
|
|
*
|
|
* @private
|
|
* @param {HTMLElement}
|
|
* @returns {object}
|
|
*/
|
|
_getStepMainElementRect(stepEl) {
|
|
const iconEl = stepEl.querySelector(".s_process_step_number");
|
|
if (iconEl) {
|
|
return iconEl.getBoundingClientRect();
|
|
}
|
|
const contentEls = stepEl.querySelectorAll('.s_process_step_content > *');
|
|
// If there is no icon, the biggest text bloc in the content container
|
|
// will be chosen.
|
|
if (contentEls.length) {
|
|
const contentRects = [...contentEls].map(contentEl => {
|
|
const range = document.createRange();
|
|
range.selectNodeContents(contentEl);
|
|
return range.getBoundingClientRect();
|
|
});
|
|
return contentRects.reduce((previous, current) => {
|
|
return current.width > previous.width ? current : previous;
|
|
});
|
|
}
|
|
return {};
|
|
},
|
|
/**
|
|
* Returns the svg path based on the type of connector.
|
|
*
|
|
* @private
|
|
* @param {string} type
|
|
* @param {integer} width
|
|
* @param {integer} height
|
|
* @returns {string}
|
|
*/
|
|
_getPath(type, width, height) {
|
|
const hHeight = height / 2;
|
|
switch (type) {
|
|
case 's_process_steps_connector_line': {
|
|
return `M 0 ${hHeight} L ${width} ${hHeight}`;
|
|
}
|
|
case 's_process_steps_connector_arrow': {
|
|
return `M ${0.05 * width} ${hHeight} L ${0.95 * width - 6} ${hHeight}`;
|
|
}
|
|
case 's_process_steps_connector_curved_arrow': {
|
|
return `M ${0.05 * width} ${hHeight * 1.2} Q ${width / 2} ${hHeight * 1.8}, ${0.95 * width - 6} ${hHeight * 1.2}`;
|
|
}
|
|
}
|
|
return '';
|
|
},
|
|
});
|