Odoo18-Base/addons/website/static/src/snippets/s_process_steps/options.js
2025-01-06 10:57:38 +07:00

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 '';
},
});