-
Notifications
You must be signed in to change notification settings - Fork 115
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
298 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
257 changes: 257 additions & 0 deletions
257
addons/html_builder/static/src/builder/options/process_steps_option.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,257 @@ | ||
import { defaultBuilderComponents } from "../builder_components/default_builder_components"; | ||
import { registry } from "@web/core/registry"; | ||
import { coreBuilderActions } from "@html_builder/builder/core_builder_action_plugin"; | ||
import { Plugin } from "@html_editor/plugin"; | ||
import { Component } from "@odoo/owl"; | ||
|
||
class ProcessStepsOptionPlugin extends Plugin { | ||
static id = "ProcessStepsOption"; | ||
selector = ".s_process_steps"; | ||
resources = { | ||
builder_options: { | ||
OptionComponent: ProcessStepsOption, | ||
// template: "html_builder.ProcessStepsOption", | ||
selector: this.selector, | ||
}, | ||
builder_actions: this.getActions(), | ||
}; | ||
getActions() { | ||
return { | ||
changeConnector: { | ||
apply: ({ editingElement, param: className }) => { | ||
coreBuilderActions.classAction.apply({ | ||
editingElement: editingElement, | ||
param: className, | ||
}); | ||
reloadConnectors(editingElement); | ||
let markerEnd = ""; | ||
if ( | ||
[ | ||
"s_process_steps_connector_arrow", | ||
"s_process_steps_connector_curved_arrow", | ||
].includes(className) | ||
) { | ||
const arrowHeadEl = editingElement.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})`; | ||
} | ||
editingElement | ||
.querySelectorAll(".s_process_step_connector path") | ||
.forEach((path) => path.setAttribute("marker-end", markerEnd)); | ||
}, | ||
isApplied: coreBuilderActions.classAction.isApplied, | ||
clean: coreBuilderActions.classAction.clean, | ||
}, | ||
}; | ||
} | ||
} | ||
|
||
const connectorOptionParams = [ | ||
{ key: "", param: "None" }, | ||
{ key: "s_process_steps_connector_line", param: "Line" }, | ||
{ key: "s_process_steps_connector_arrow", param: "Straight arrow" }, | ||
{ key: "s_process_steps_connector_curved_arrow", param: "Curved arrow" }, | ||
]; | ||
|
||
export class ProcessStepsOption extends Component { | ||
static template = "html_builder.ProcessStepsOption"; | ||
static components = { ...defaultBuilderComponents }; | ||
static props = {}; | ||
|
||
setup() { | ||
this.connectorOptionParams = connectorOptionParams; | ||
} | ||
|
||
getConnectorId(connectorOptionParamKey) { | ||
return !connectorOptionParamKey ? "no_connector_opt" : ""; | ||
} | ||
} | ||
registry.category("website-plugins").add(ProcessStepsOptionPlugin.id, ProcessStepsOptionPlugin); | ||
|
||
/** | ||
* Width and position of the connectors should be updated when one of the | ||
* steps is modified. | ||
* | ||
* @private | ||
*/ | ||
function reloadConnectors(editingElement) { | ||
const connectorOptionClasses = connectorOptionParams.map( | ||
(connectorOptionParam) => connectorOptionParam.key | ||
); | ||
const type = | ||
connectorOptionClasses.find( | ||
(connectorOptionClass) => | ||
connectorOptionClass && editingElement.classList.contains(connectorOptionClass) | ||
) || ""; | ||
// As the connectors are only visible in desktop, we can ignore the | ||
// steps that are only visible in mobile. | ||
const stepsEls = editingElement.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 = getStepMainElementRect(stepsEls[i]); | ||
const nextStepMainElementRect = getStepMainElementRect(stepsEls[i + 1]); | ||
const stepSize = getClassSuffixedInteger(stepsEls[i], "col-lg-"); | ||
const nextStepSize = getClassSuffixedInteger(stepsEls[i + 1], "col-lg-"); | ||
const stepOffset = getClassSuffixedInteger(stepsEls[i], "offset-lg-"); | ||
const nextStepOffset = getClassSuffixedInteger(stepsEls[i + 1], "offset-lg-"); | ||
const stepPaddingTop = getClassSuffixedInteger(stepsEls[i], "pt"); | ||
const nextStepPaddingTop = getClassSuffixedInteger(stepsEls[i + 1], "pt"); | ||
const stepHeightDifference = stepPaddingTop - nextStepPaddingTop; | ||
const hCurrentStepIconHeight = stepMainElementRect.height / 2; | ||
const hNextStepIconHeight = nextStepMainElementRect.height / 2; | ||
|
||
connectorEl.style.left = `calc(50% + ${stepMainElementRect.width / 2}px + 16px)`; | ||
connectorEl.style.height = `${ | ||
stepMainElementRect.height + Math.abs(stepHeightDifference) | ||
}px`; | ||
connectorEl.style.width = `calc(${ | ||
(100 * (stepSize / 2 + nextStepOffset + nextStepSize / 2)) / stepSize | ||
}% - ${stepMainElementRect.width / 2}px - ${nextStepMainElementRect.width / 2}px - 32px)`; | ||
|
||
const marginType = stepHeightDifference < 0 ? "marginBottom" : "marginTop"; | ||
connectorEl.style[marginType] = `${0 - Math.abs(stepHeightDifference)}px`; | ||
|
||
const isTheLastColOfRow = | ||
nbBootstrapCols < colsInRow + stepSize + stepOffset + nextStepSize + nextStepOffset; | ||
connectorEl.classList.toggle("d-none", isTheLastColOfRow); | ||
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"); | ||
if (type === "s_process_steps_connector_curved_arrow" && i % 2 === 0) { | ||
connectorEl.style.transform = stepHeightDifference ? "unset" : "scale(1, -1)"; | ||
} else { | ||
connectorEl.style.transform = "unset"; | ||
} | ||
connectorEl.setAttribute("viewBox", `0 0 ${width} ${height}`); | ||
connectorEl | ||
.querySelector("path") | ||
.setAttribute( | ||
"d", | ||
getPath( | ||
type, | ||
width, | ||
height, | ||
stepHeightDifference, | ||
hCurrentStepIconHeight, | ||
hNextStepIconHeight | ||
) | ||
); | ||
} | ||
} | ||
/** | ||
* Returns the number suffixed to the class given in parameter. | ||
* | ||
* @private | ||
* @param {HTMLElement} el | ||
* @param {String} classNamePrefix | ||
* @returns {Integer} | ||
*/ | ||
function 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} | ||
*/ | ||
function 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) => | ||
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} | ||
*/ | ||
function getPath( | ||
type, | ||
width, | ||
height, | ||
stepHeightDifference, | ||
hCurrentStepIconHeight, | ||
hNextStepIconHeight | ||
) { | ||
const hHeight = height / 2; | ||
switch (type) { | ||
case "s_process_steps_connector_line": { | ||
const verticalPaddingFactor = Math.abs(stepHeightDifference) / 8; | ||
if (stepHeightDifference >= 0) { | ||
return `M 0 ${ | ||
stepHeightDifference + hCurrentStepIconHeight - verticalPaddingFactor | ||
} L ${width} ${hNextStepIconHeight + verticalPaddingFactor}`; | ||
} | ||
return `M 0 ${hCurrentStepIconHeight + verticalPaddingFactor} L ${width} ${ | ||
hNextStepIconHeight - stepHeightDifference - verticalPaddingFactor | ||
}`; | ||
} | ||
case "s_process_steps_connector_arrow": { | ||
// When someone plays with the y-axis, it adds the padding in | ||
// multiple of 8px. so here we devide it by 8 to calculate the | ||
// number of padding steps has been added. | ||
const verticalPaddingFactor = (Math.abs(stepHeightDifference) / 8) * 1.5; | ||
if (stepHeightDifference >= 0) { | ||
return `M ${0.05 * width} ${ | ||
stepHeightDifference + hCurrentStepIconHeight - verticalPaddingFactor | ||
} L ${0.95 * width - 6} ${hNextStepIconHeight + verticalPaddingFactor}`; | ||
} | ||
return `M ${0.05 * width} ${hCurrentStepIconHeight + verticalPaddingFactor} L ${ | ||
0.95 * width - 6 | ||
} ${Math.abs(stepHeightDifference) + hNextStepIconHeight - verticalPaddingFactor}`; | ||
} | ||
case "s_process_steps_connector_curved_arrow": { | ||
if (stepHeightDifference == 0) { | ||
return `M ${0.05 * width} ${hHeight * 1.2} Q ${width / 2} ${hHeight * 1.8} ${ | ||
0.95 * width - 6 | ||
} ${hHeight * 1.2}`; | ||
} else if (stepHeightDifference > 0) { | ||
return `M ${0.05 * width} ${stepHeightDifference + hCurrentStepIconHeight} Q ${ | ||
width * 0.75 | ||
} ${height * 0.75} ${0.5 * width - 6} ${hHeight} T ${ | ||
0.95 * width - 6 | ||
} ${hNextStepIconHeight}`; | ||
} | ||
return `M ${0.05 * width} ${hCurrentStepIconHeight} Q ${width * 0.75} ${ | ||
height * 0.005 | ||
} ${0.5 * width - 6} ${hHeight} T ${0.95 * width - 6} ${ | ||
Math.abs(stepHeightDifference) + hNextStepIconHeight | ||
}`; | ||
} | ||
} | ||
return ""; | ||
} |
20 changes: 20 additions & 0 deletions
20
addons/html_builder/static/src/builder/options/process_steps_option.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<templates xml:space="preserve"> | ||
|
||
<t t-name="html_builder.ProcessStepsOption"> | ||
<BuilderRow label.translate="Connector"> | ||
<BuilderSelect> | ||
<t t-foreach="connectorOptionParams" | ||
t-as="connectorOptionParam" t-key="connectorOptionParam_index"> | ||
<BuilderSelectItem action="'changeConnector'" id="getConnectorId(connectorOptionParam.key)" actionParam="connectorOptionParam.key"><t t-out="connectorOptionParam.param"/></BuilderSelectItem> | ||
</t> | ||
</BuilderSelect> | ||
<BuilderColorPicker | ||
styleAction="'stroke'" | ||
dependencies="'!no_connector_opt'" | ||
applyTo="'.s_process_step_connector path'"/> | ||
</BuilderRow> | ||
<!-- TODO: call snippet_options_background_options (probably SectionBackgroundOption)--> | ||
</t> | ||
|
||
</templates> |
16 changes: 16 additions & 0 deletions
16
addons/html_builder/static/tests/options/steps_options.test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { expect, test } from "@odoo/hoot"; | ||
import { contains } from "@web/../tests/web_test_helpers"; | ||
import { defineWebsiteModels, setupWebsiteBuilder } from "../helpers"; | ||
import { insertStructureSnippet } from "./helpers"; | ||
|
||
defineWebsiteModels(); | ||
|
||
test("modify the steps color", async () => { | ||
const { getEditor } = await setupWebsiteBuilder("<div></div>"); | ||
const editor = getEditor(); | ||
await insertStructureSnippet(editor, "s_process_steps"); | ||
await contains(":iframe .s_process_steps").click(); | ||
await contains("[data-label='Connector'] .o_we_color_preview").click(); | ||
await contains(".o-overlay-item [data-color='o-color-1']").click(); | ||
expect(":iframe .s_process_steps .s_process_step path").toHaveClass("bg-o-color-1"); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters