Skip to content

Commit

Permalink
add input, implement new API and few fix for props validation in debu…
Browse files Browse the repository at this point in the history
…g mode
  • Loading branch information
Goaman authored and FrancoisGe committed Nov 14, 2024
1 parent abfff67 commit aeacbc1
Show file tree
Hide file tree
Showing 18 changed files with 251 additions and 49 deletions.
47 changes: 40 additions & 7 deletions addons/html_builder/static/src/builder/builder_actions.js
Original file line number Diff line number Diff line change
@@ -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);
},
});
41 changes: 40 additions & 1 deletion addons/html_builder/static/src/builder/builder_helpers.js
Original file line number Diff line number Diff line change
@@ -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) {
Expand All @@ -9,3 +9,42 @@ export function useDomState(getState) {
});
return state;
}

export class WithSubEnv extends Component {
static template = xml`<t t-slot="default" />`;
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 },
};
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
61 changes: 61 additions & 0 deletions addons/html_builder/static/src/builder/components/NumberInput.js
Original file line number Diff line number Diff line change
@@ -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);
}
}
11 changes: 11 additions & 0 deletions addons/html_builder/static/src/builder/components/NumberInput.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">

<t t-name="html_builder.NumberInput">
<div class="d-flex flex-row flex-nowrap align-items-center" style="width: 60px">
<input type="text" autocomplete="chrome-off" class="text-end" t-on-change="this.onChange" t-on-input="this.onInput" t-att-value="this.state.value" />
<span t-out="props.unit" />
</div>
</t>

</templates>
73 changes: 56 additions & 17 deletions addons/html_builder/static/src/builder/components/WeButton.js
Original file line number Diff line number Diff line change
@@ -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,
});
}
});
Expand All @@ -35,13 +50,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();
}
Expand All @@ -52,16 +95,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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
t-on-mouseleave="() => this.onMouseleave?.(props.id)">
<img t-if="props.iconImg" t-att-src="props.iconImg" t-att-alt="props.iconImgAlt"/>
<t t-if="props.label" t-out="props.label"/>
<t t-slot="content"/>
</button>
</t>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -12,4 +13,5 @@ export const defaultOptionComponents = {
ButtonGroup,
WeButton,
Button,
NumberInput,
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
<t t-name="html_builder.AddElementOption">
<ToolboxRow label="'└ Add element'">
<ButtonGroup>
<Button id="'text'" label="'Text'" onClick="this.addText"/>
<Button id="'image'" label="'Image'" onClick="this.addImage"/>
<Button id="'button'" label="'Button'" onClick="this.addButton"/>
<Button label="'Text'" onClick="this.addText"/>
<Button label="'Image'" onClick="this.addImage"/>
<Button label="'Button'" onClick="this.addButton"/>
</ButtonGroup>
</ToolboxRow>
</t>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Component } from "@odoo/owl";
import { defaultOptionComponents } from "../defaultComponents";

export class BorderOption extends Component {
static template = "html_builder.BorderOption";
static components = {
...defaultOptionComponents,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">

<t t-name="html_builder.BorderOption">
<ToolboxRow label.translate="Border">
<NumberInput styleAction="'borderWidth'" unit="'px'" />
</ToolboxRow>
</t>

</templates>
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@
</t>
<t t-else="">
<ToolboxRow label="'└ Hor. Alignment'">
<ButtonGroup>
<WeButton actions="{setClass: 'align-items-start'}" title="'Align Top'" iconImg="'/website/static/src/img/snippets_options/align_top.svg'" applyTo="'.row'" />
<WeButton actions="{setClass: 'align-items-center'}" title="'Align Middle'" iconImg="'/website/static/src/img/snippets_options/align_middle.svg'" applyTo="'.row'" />
<WeButton actions="{setClass: 'align-items-end'}" title="'Align Bottom'" iconImg="'/website/static/src/img/snippets_options/align_bottom.svg'" applyTo="'.row'" />
<WeButton actions="{setClass: 'align-items-stretch'}" title="'Stretch'" iconImg="'/website/static/src/img/snippets_options/align_stretch.svg'" applyTo="'.row'" />
<ButtonGroup applyTo="'.row'">
<WeButton classAction="'align-items-start'" title="'Align Top'" iconImg="'/website/static/src/img/snippets_options/align_top.svg'" />
<WeButton classAction="'align-items-center'" title="'Align Middle'" iconImg="'/website/static/src/img/snippets_options/align_middle.svg'" />
<WeButton classAction="'align-items-end'" title="'Align Bottom'" iconImg="'/website/static/src/img/snippets_options/align_bottom.svg'" />
<WeButton classAction="'align-items-stretch'" title="'Stretch'" iconImg="'/website/static/src/img/snippets_options/align_stretch.svg'" />
</ButtonGroup>
</ToolboxRow>
</t>
Expand Down
Loading

0 comments on commit aeacbc1

Please sign in to comment.