Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add carousel option #4250

Open
wants to merge 2 commits into
base: master-mysterious-egg
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@ export class BuilderNumberInput extends Component {
max: { type: Number, optional: true },
id: { type: String, optional: true },
composable: { type: Boolean, optional: true },
applyapplyWithUnit: { type: Boolean, optional: true },
};
static components = { BuilderComponent, BuilderTextInputBase };
static defaultProps = {
composable: false,
applyWithUnit: true,
};

setup() {
Expand Down Expand Up @@ -122,6 +124,7 @@ export class BuilderNumberInput extends Component {
}
const unit = this.props.unit;
const saveUnit = this.props.saveUnit;
const applyWithUnit = this.props.applyWithUnit;
if (unit && saveUnit) {
// Convert value from unit to saveUnit
value = convertNumericToUnit(
Expand All @@ -131,7 +134,7 @@ export class BuilderNumberInput extends Component {
getHtmlStyle(this.env.getEditingElement().ownerDocument)
);
}
if (unit) {
if (unit && applyWithUnit) {
if (saveUnit || saveUnit === "") {
value = value + saveUnit;
} else {
Expand Down
68 changes: 68 additions & 0 deletions addons/html_builder/static/src/interactions/carousel.edit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { Interaction } from "@web/public/interaction";
import { registry } from "@web/core/registry";

export class CarouselEdit extends Interaction {
static selector =
"section:not(.s_carousel_intro_wrapper, .s_carousel_cards_wrapper) > .carousel";
// Prevent enabling the carousel overlay when clicking on the carousel
// controls (indeed we want it to change the carousel slide then enable
// the slide overlay) + See "CarouselItem" option.
dynamicContent = {
".carousel-control-prev, .carousel-control-next, .carousel-indicators": {
"t-on-click": this.throttled(this.onControlClick),
"t-on-keydown": this.onControlKeyDown,
"t-att-class": () => ({ o_we_no_overlay: true }),
},
};
/**
* Slides the carousel when clicking on the carousel controls. This handler
* allows to put the sliding in the mutex, to avoid race conditions.
*
* @param {Event} ev
*/
async onControlClick(ev) {
// Compute to which slide the carousel will slide.
const controlEl = ev.currentTarget;
let direction;
if (controlEl.classList.contains("carousel-control-prev")) {
direction = "prev";
} else if (controlEl.classList.contains("carousel-control-next")) {
direction = "next";
} else {
const indicatorEl = ev.target;
if (
!indicatorEl.matches(".carousel-indicators > *") ||
indicatorEl.classList.contains("active")
) {
return;
}
direction = [...controlEl.children].indexOf(indicatorEl);
}

// Slide the carousel
const editingCarousel = this.el;
const applySpec = { editingElement: editingCarousel, direction: direction };

if (this.services["website_edit"].applyAction) {
this.services["website_edit"].applyAction("slideCarousel", applySpec);
}
}

/**
* Since carousel controls are disabled in edit mode because slides are
* handled manually, we disable the left and right keydown events to prevent
* sliding this way.
*
* @param {Event} ev
*/
onControlKeyDown(ev) {
if (["ArrowLeft", "ArrowRight"].includes(ev.code)) {
ev.preventDefault();
ev.stopPropagation();
}
}
}

registry.category("public.interactions.edit").add("html_builder.carousel_edit", {
Interaction: CarouselEdit,
});
58 changes: 58 additions & 0 deletions addons/html_builder/static/src/plugins/carousel_option.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">

<t t-name="html_builder.CarouselOption">
<BuilderRow label.translate="Slide">
<BuilderButton preview="false" title.translate="Add Slide" action="'addSlide'">Add Slide</BuilderButton>
</BuilderRow>
<BuilderRow label.translate="Style">
<BuilderSelect>
<BuilderSelectItem classAction="''">Classic</BuilderSelectItem>
<BuilderSelectItem classAction="'s_carousel_controllers_indicators_outside'">Indicators outside</BuilderSelectItem>
</BuilderSelect>
</BuilderRow>

<BuilderRow label.translate="Invert colors" level="1">
<BuilderCheckbox classAction="'carousel-dark'"/>
</BuilderRow>

<BuilderRow label.translate="Arrows" level="1">
<BuilderSelect>
<BuilderSelectItem classAction="'s_carousel_default'">Default</BuilderSelectItem>
<BuilderSelectItem classAction="'s_carousel_boxed'">Boxed</BuilderSelectItem>
<BuilderSelectItem classAction="'s_carousel_rounded'">Rounded</BuilderSelectItem>
<BuilderSelectItem classAction="'s_carousel_arrows_hidden'">Hidden</BuilderSelectItem>
</BuilderSelect>
</BuilderRow>

<BuilderRow label.translate="Indicators" applyTo="'.carousel-indicators'" level="1">
<BuilderSelect>
<BuilderSelectItem classAction="''">Bars</BuilderSelectItem>
<BuilderSelectItem classAction="'s_carousel_indicators_dots'">Dots</BuilderSelectItem>
<BuilderSelectItem classAction="'s_carousel_indicators_numbers'">Numbers</BuilderSelectItem>
<BuilderSelectItem classAction="'s_carousel_indicators_hidden'">Hidden</BuilderSelectItem>
</BuilderSelect>
</BuilderRow>

<BuilderRow label.translate="Transition">
<BuilderSelect>
<BuilderSelectItem classAction="'slide'">Slide</BuilderSelectItem>
<BuilderSelectItem classAction="'carousel-fade slide'">Fade</BuilderSelectItem>
<BuilderSelectItem classAction="''">None</BuilderSelectItem>
</BuilderSelect>
</BuilderRow>

<BuilderRow label.translate="Speed">
<BuilderNumberInput dataAttributeAction="'bsInterval'"
default="10"
unit="'s'"
saveUnit="'ms'"
step="0.1"
min="0"
preview="false"
applyWithUnit="false"/>
</BuilderRow>

</t>

</templates>
163 changes: 163 additions & 0 deletions addons/html_builder/static/src/plugins/carousel_option_plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import { Plugin } from "@html_editor/plugin";
import { _t } from "@web/core/l10n/translation";
import { registry } from "@web/core/registry";

export class CarouselOptionPlugin extends Plugin {
static id = "carouselOption";
static dependencies = ["clone", "builder-options"];

resources = {
builder_options: [
{
template: "html_builder.CarouselOption",
selector: "section",
exclude: ".s_carousel_intro_wrapper, .s_carousel_cards_wrapper",
applyTo: ":scope > .carousel",
},
],
builder_actions: this.getActions(),
on_cloned_handlers: this.onCloned.bind(this),
on_will_clone_handlers: this.onWillClone.bind(this),
on_add_element_handlers: this.onAddElement.bind(this),
};

getActions() {
return {
addSlide: {
load: async ({ editingElement }) => this.addSlide(editingElement),
apply: () => {},
},
slideCarousel: {
load: async ({ editingElement, direction: direction }) =>
this.slide(direction, editingElement),
apply: () => {},
},
};
}

async addSlide(editingElement) {
const activeCarouselItem = editingElement.querySelector(".carousel-item.active");
this.dependencies.clone.cloneElement(activeCarouselItem);

await this.slide("next", editingElement);
}

/**
* Slides the carousel in the given direction.
*
* @param {String|Number} direction the direction in which to slide:
* - "prev": the previous slide;
* - "next": the next slide;
* - number: a slide number.
* @param {Element} editingElement the carousel element.
* @returns {Promise}
*/
slide(direction, editingElement) {
editingElement.addEventListener("slide.bs.carousel", () => {
this.slideTimestamp = window.performance.now();
});

return new Promise((resolve) => {
editingElement.addEventListener("slid.bs.carousel", () => {
// slid.bs.carousel is most of the time fired too soon by bootstrap
// since it emulates the transitionEnd with a setTimeout. We wait
// here an extra 20% of the time before retargeting edition, which
// should be enough...
const slideDuration = window.performance.now() - this.slideTimestamp;
setTimeout(() => {
// Setting the active indicator manually, as Bootstrap could
// not do it because the `data-bs-slide-to` attribute is not
// here in edit mode anymore.
const activeSlide = editingElement.querySelector(".carousel-item.active");
const indicatorsEl = editingElement.querySelector(".carousel-indicators");
const activeIndex = [...activeSlide.parentElement.children].indexOf(
activeSlide
);
const activeIndicatorEl = [...indicatorsEl.children][activeIndex];
activeIndicatorEl.classList.add("active");
activeIndicatorEl.setAttribute("aria-current", "true");

resolve();
}, 0.2 * slideDuration);

this.dependencies["builder-options"].updateContainers(
editingElement.querySelector(".carousel-item.active")
);
});

const carouselInstance = window.Carousel.getOrCreateInstance(editingElement, {
ride: false,
pause: true,
});
if (typeof direction === "number") {
carouselInstance.to(direction);
} else {
carouselInstance[direction]();
}
});
}

onWillClone({ originalEl }) {
if (
originalEl.matches(
".s_carousel_wrapper:not(.s_carousel_intro_wrapper, .s_carousel_cards_wrapper) .carousel-item"
)
) {
const editingCarousel = originalEl.closest(".carousel");

const indicatorsEl = editingCarousel.querySelector(".carousel-indicators");
this.controlEls = editingCarousel.querySelectorAll(
".carousel-control-prev, .carousel-control-next, .carousel-indicators"
);
this.controlEls.forEach((control) => {
control.classList.remove("d-none");
});

const newIndicatorEl = this.document.createElement("button");
newIndicatorEl.setAttribute("data-bs-target", "#" + editingCarousel.id);
newIndicatorEl.setAttribute("aria-label", _t("Carousel indicator"));
indicatorsEl.appendChild(newIndicatorEl);
}
}

onCloned({ cloneEl }) {
if (
cloneEl.matches(
".s_carousel_wrapper:not(.s_carousel_intro_wrapper, .s_carousel_cards_wrapper)"
)
) {
this.assignUniqueID(cloneEl);
}
if (
cloneEl.matches(
".s_carousel_wrapper:not(.s_carousel_intro_wrapper, .s_carousel_cards_wrapper) .carousel-item"
)
) {
// Need to remove editor data from the clone so it gets its own.
cloneEl.classList.remove("active");
}
}

onAddElement({ elementToAdd }) {
if (elementToAdd.matches(".s_carousel_wrapper")) {
this.assignUniqueID(elementToAdd);
}
}

assignUniqueID(editingElement) {
const id = "myCarousel" + Date.now();
editingElement.querySelector(".s_carousel").setAttribute("id", id);
editingElement.querySelectorAll("[data-bs-target]").forEach((el) => {
el.setAttribute("data-bs-target", "#" + id);
});
editingElement.querySelectorAll("[data-bs-slide], [data-bs-slide-to]").forEach((el) => {
if (el.hasAttribute("data-bs-target")) {
el.setAttribute("data-bs-target", "#" + id);
} else if (el.hasAttribute("href")) {
el.setAttribute("href", "#" + id);
}
});
}
}

registry.category("website-plugins").add(CarouselOptionPlugin.id, CarouselOptionPlugin);
14 changes: 7 additions & 7 deletions addons/html_editor/static/src/core/history_plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -330,16 +330,16 @@ export class HistoryPlugin extends Plugin {
}
}

handleObserverRecords() {
this.handleNewRecords(this.observer.takeRecords());
handleObserverRecords(filter = true) {
this.handleNewRecords(this.observer.takeRecords(), filter);
}

/**
* @param { MutationRecord[] } records
* @returns { MutationRecord[] } processed records
*/
processNewRecords(records) {
records = this.filterMutationRecords(records);
processNewRecords(records, filter = true) {
records = filter ? this.filterMutationRecords(records) : records;
if (!records.length) {
return [];
}
Expand All @@ -364,8 +364,8 @@ export class HistoryPlugin extends Plugin {
/**
* @param { MutationRecord[] } records
*/
handleNewRecords(records) {
const filteredRecords = this.processNewRecords(records);
handleNewRecords(records, filter = true) {
const filteredRecords = this.processNewRecords(records, filter);
if (filteredRecords.length) {
// TODO modify `handleMutations` of web_studio to handle
// `undoOperation`
Expand Down Expand Up @@ -662,7 +662,7 @@ export class HistoryPlugin extends Plugin {
if (stepState) {
this.stepsStates.set(currentStep.id, stepState);
}
this.handleObserverRecords();
this.handleObserverRecords(stepState !== "undo");
const currentMutationsCount = currentStep.mutations.length;
if (currentMutationsCount === 0) {
return false;
Expand Down