diff --git a/addons/html_builder/static/src/builder/builder_actions.js b/addons/html_builder/static/src/builder/builder_actions.js index 30bfc47d9d315..1313f67a94660 100644 --- a/addons/html_builder/static/src/builder/builder_actions.js +++ b/addons/html_builder/static/src/builder/builder_actions.js @@ -1,13 +1,46 @@ import { registry } from "@web/core/registry"; -registry.category("website-builder-actions").add("setClass", { - isActive: ({ editingElement, params }) => { - return editingElement.classList.contains(params); +registry.category("website-builder-actions").add("classAction", { + isActive: ({ editingElement, param: className }) => { + return editingElement.classList.contains(className); }, - apply: ({ editingElement, params }) => { - editingElement.classList.add(params); + apply: ({ editingElement, param: className, value }) => { + editingElement.classList.add(className); }, - clean: ({ editingElement, params }) => { - editingElement.classList.remove(params); + clean: ({ editingElement, param: className, value }) => { + editingElement.classList.remove(className); + }, +}); + +const styleMap = { + borderWidth: { + getValue: (editingElement) => { + return parseInt( + getComputedStyle(editingElement).getPropertyValue("border-width") + ).toString(); + }, + apply: (editingElement, value) => { + const parsedValue = parseInt(value); + const hasBorderClass = editingElement.classList.contains("border"); + if (!parsedValue || parsedValue < 0) { + if (hasBorderClass) { + editingElement.classList.remove("border"); + } + } else { + if (!hasBorderClass) { + editingElement.classList.add("border"); + } + } + editingElement.style.setProperty("border-width", `${parsedValue}px`, "important"); + }, + }, +}; + +registry.category("website-builder-actions").add("styleAction", { + getValue: ({ editingElement, param: styleName }) => { + return styleMap[styleName]?.getValue(editingElement); + }, + apply: ({ editingElement, param: styleName, value }) => { + styleMap[styleName]?.apply(editingElement, value); }, }); diff --git a/addons/html_builder/static/src/builder/builder_helpers.js b/addons/html_builder/static/src/builder/builder_helpers.js index aad4666ad6978..59623c709dd3a 100644 --- a/addons/html_builder/static/src/builder/builder_helpers.js +++ b/addons/html_builder/static/src/builder/builder_helpers.js @@ -1,4 +1,4 @@ -import { useComponent, useState } from "@odoo/owl"; +import { Component, useComponent, useState, useSubEnv, xml } from "@odoo/owl"; import { useBus } from "@web/core/utils/hooks"; export function useDomState(getState) { @@ -9,3 +9,42 @@ export function useDomState(getState) { }); return state; } + +export class WithSubEnv extends Component { + static template = xml``; + static props = { + env: Object, + slots: Object, + }; + + setup() { + useSubEnv(this.props.env); + } +} + +export function useWeComponent() { + const comp = useComponent(); + if (comp.props.applyTo) { + // todo: react to the change of applyTo + // todo: make sure that the code that read env.editingElement properly react to the change if editingElement changes + // todo: make sure that if there is an action that changes the structure of the dom, the applyTo is re-calculed. + useSubEnv({ + editingElement: comp.env.editingElement.querySelector(comp.props.applyTo), + }); + } +} + +export const basicContainerWeWidgetProps = { + applyTo: { type: String, optional: true }, + // preview: { type: Boolean, optional: true }, + // reloadPage: { type: Boolean, optional: true }, + + action: { type: String, optional: true }, + actionParam: { validate: () => true, optional: true }, + + // Shorthand actions. + classAction: { type: String, optional: true }, + attributeAction: { type: String, optional: true }, + dataAttributeAction: { type: String, optional: true }, + styleAction: { type: String, optional: true }, +}; diff --git a/addons/html_builder/static/src/builder/components/Button.js b/addons/html_builder/static/src/builder/components/Button.js index 564bec97acae6..f5507f6248453 100644 --- a/addons/html_builder/static/src/builder/components/Button.js +++ b/addons/html_builder/static/src/builder/components/Button.js @@ -9,7 +9,7 @@ export class Button extends Component { iconImg: { type: String, optional: true }, iconImgAlt: { type: String, optional: true }, onClick: Function, - isActive: Function, + isActive: { Function, optional: true }, }; setup() { diff --git a/addons/html_builder/static/src/builder/components/ButtonGroup.js b/addons/html_builder/static/src/builder/components/ButtonGroup.js index be2acbad3ce7f..21744991fb2cf 100644 --- a/addons/html_builder/static/src/builder/components/ButtonGroup.js +++ b/addons/html_builder/static/src/builder/components/ButtonGroup.js @@ -1,15 +1,15 @@ import { Component, EventBus, useSubEnv } from "@odoo/owl"; +import { basicContainerWeWidgetProps, useWeComponent } from "../builder_helpers"; export class ButtonGroup extends Component { static template = "html_builder.ButtonGroup"; static props = { - activeState: { type: Object, optional: true }, - isActive: { type: Boolean, optional: true }, - onClick: { type: Function, optional: true }, + ...basicContainerWeWidgetProps, slots: { type: Object, optional: true }, }; setup() { + useWeComponent(); const bus = new EventBus(); useSubEnv({ buttonGroupBus: bus, diff --git a/addons/html_builder/static/src/builder/components/NumberInput.js b/addons/html_builder/static/src/builder/components/NumberInput.js new file mode 100644 index 0000000000000..86db096b9b8e5 --- /dev/null +++ b/addons/html_builder/static/src/builder/components/NumberInput.js @@ -0,0 +1,61 @@ +import { Component, useState } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { basicContainerWeWidgetProps, useWeComponent } from "../builder_helpers"; + +const actionsRegistry = registry.category("website-builder-actions"); + +export class NumberInput extends Component { + static template = "html_builder.NumberInput"; + static props = { + ...basicContainerWeWidgetProps, + unit: { type: String, optional: true }, + }; + + setup() { + useWeComponent(); + this.state = useState(this.getState()); + this.applyValue = this.env.editor.shared.makePreviewableOperation((value) => { + for (const [actionId, actionParam] of this.getActions()) { + actionsRegistry.get(actionId).apply({ + editingElement: this.env.editingElement, + param: actionParam, + value, + }); + } + }); + } + getState() { + const [actionId, actionParam] = this.getActions()[0]; + return { + value: actionsRegistry.get(actionId).getValue({ + editingElement: this.env.editingElement, + param: actionParam, + }), + }; + } + getActions() { + const actions = []; + if (this.props.classAction) { + actions.push(["classAction", this.props.classAction]); + } + if (this.props.attributeAction) { + actions.push(["attributeAction", this.props.attributeAction]); + } + if (this.props.dataAttributeAction) { + actions.push(["dataAttributeAction", this.props.dataAttributeAction]); + } + if (this.props.styleAction) { + actions.push(["styleAction", this.props.styleAction]); + } + if (this.props.action) { + actions.push([this.props.action, this.props.actionParam]); + } + return actions; + } + onChange(e) { + this.applyValue.commit(e.target.value); + } + onInput(e) { + this.applyValue.preview(e.target.value); + } +} diff --git a/addons/html_builder/static/src/builder/components/NumberInput.xml b/addons/html_builder/static/src/builder/components/NumberInput.xml new file mode 100644 index 0000000000000..9b27896f2c225 --- /dev/null +++ b/addons/html_builder/static/src/builder/components/NumberInput.xml @@ -0,0 +1,11 @@ + + + + +
+ + +
+
+ +
diff --git a/addons/html_builder/static/src/builder/components/WeButton.js b/addons/html_builder/static/src/builder/components/WeButton.js index bc76070cb1b37..7949855560c6e 100644 --- a/addons/html_builder/static/src/builder/components/WeButton.js +++ b/addons/html_builder/static/src/builder/components/WeButton.js @@ -1,32 +1,47 @@ import { Component } from "@odoo/owl"; import { registry } from "@web/core/registry"; import { useBus } from "@web/core/utils/hooks"; -import { useDomState } from "../builder_helpers"; +import { basicContainerWeWidgetProps, useDomState, useWeComponent } from "../builder_helpers"; const actionsRegistry = registry.category("website-builder-actions"); export class WeButton extends Component { static template = "html_builder.WeButton"; static props = { - actions: Object, + ...basicContainerWeWidgetProps, + title: { type: String, optional: true }, label: { type: String, optional: true }, iconImg: { type: String, optional: true }, iconImgAlt: { type: String, optional: true }, - applyTo: { type: Function, optional: true }, + + actionValue: { + type: [Boolean, String, Number, { type: Array, element: [Boolean, String, Number] }], + optional: true, + }, + + // Shorthand actions values. + classActionValue: { type: [String, Array], optional: true }, + attributeActionValue: { type: [String, Array], optional: true }, + dataAttributeActionValue: { type: [String, Array], optional: true }, + styleActionValue: { type: [String, Array], optional: true }, + + slots: { type: Object, optional: true }, }; setup() { + useWeComponent(); this.state = useDomState(() => ({ isActive: this.isActive(), })); if (this.env.buttonGroupBus) { useBus(this.env.buttonGroupBus, "BEFORE_CALL_ACTIONS", () => { - for (const [actionId, actionParams] of Object.entries(this.props.actions)) { + for (const [actionId, actionParam, actionValue] of this.getActions()) { actionsRegistry.get(actionId).clean({ - editingElement: this.getEditedElement(), - params: actionParams, + editingElement: this.env.editingElement, + param: actionParam, + value: actionValue, }); } }); @@ -37,13 +52,41 @@ export class WeButton extends Component { } callActions() { this.env.buttonGroupBus?.trigger("BEFORE_CALL_ACTIONS"); - for (const [actionId, actionParams] of Object.entries(this.props.actions)) { + for (const [actionId, actionParam, actionValue] of this.getActions()) { actionsRegistry.get(actionId).apply({ - editingElement: this.getEditedElement(), - params: actionParams, + editingElement: this.env.editingElement, + param: actionParam, + value: actionValue, }); } } + getActions() { + const actions = []; + if (this.props.classAction) { + actions.push(["classAction", this.props.classAction, this.props.classActionValue]); + } + if (this.props.attributeAction) { + actions.push([ + "attributeAction", + this.props.attributeAction, + this.props.attributeActionValue, + ]); + } + if (this.props.dataAttributeAction) { + actions.push([ + "dataAttributeAction", + this.props.dataAttributeAction, + this.props.dataAttributeActionValue, + ]); + } + if (this.props.styleAction) { + actions.push(["styleAction", this.props.styleAction, this.props.styleActionValue]); + } + if (this.props.action) { + actions.push([this.props.action, this.props.actionParam, this.props.actionValue]); + } + return actions; + } onClick() { this.call.commit(); } @@ -54,16 +97,12 @@ export class WeButton extends Component { this.call.revert(); } isActive() { - return Object.entries(this.props.actions).every(([actionId, actionParams]) => { + return this.getActions().every(([actionId, actionParam, actionValue]) => { return actionsRegistry.get(actionId).isActive({ - editingElement: this.getEditedElement(), - params: actionParams, + editingElement: this.env.editingElement, + param: actionParam, + value: actionValue, }); }); } - getEditedElement() { - return this.props.applyTo - ? this.env.editingElement.querySelector(this.props.applyTo) - : this.env.editingElement; - } } diff --git a/addons/html_builder/static/src/builder/components/WeButton.xml b/addons/html_builder/static/src/builder/components/WeButton.xml index c81c47b0ad90c..0daf7259a3818 100644 --- a/addons/html_builder/static/src/builder/components/WeButton.xml +++ b/addons/html_builder/static/src/builder/components/WeButton.xml @@ -10,6 +10,7 @@ t-on-mouseleave="() => this.onMouseleave?.(props.id)"> + diff --git a/addons/html_builder/static/src/builder/components/defaultComponents.js b/addons/html_builder/static/src/builder/components/defaultComponents.js index 91fbf192c9b9f..a423605a7ac8d 100644 --- a/addons/html_builder/static/src/builder/components/defaultComponents.js +++ b/addons/html_builder/static/src/builder/components/defaultComponents.js @@ -4,6 +4,7 @@ import { Dropdown } from "@web/core/dropdown/dropdown"; import { ToolboxRow } from "./ToolboxRow"; import { WeButton } from "./WeButton"; import { Button } from "./Button"; +import { NumberInput } from "./NumberInput"; export const defaultOptionComponents = { ToolboxRow, @@ -12,4 +13,5 @@ export const defaultOptionComponents = { ButtonGroup, WeButton, Button, + NumberInput, }; diff --git a/addons/html_builder/static/src/builder/components/options/AddElementOption.xml b/addons/html_builder/static/src/builder/components/options/AddElementOption.xml index 517ec29621f00..c7a442af668b1 100644 --- a/addons/html_builder/static/src/builder/components/options/AddElementOption.xml +++ b/addons/html_builder/static/src/builder/components/options/AddElementOption.xml @@ -4,9 +4,9 @@ -