impress/app/assets/javascripts/outfit-viewer.js
Emi Matchu 57c08b5646 Use "morphing" for smoother item page preview changes
The most notable thing here is that we keep the movie iframes running!
So if you're trying different pets for an animated item, the animation
keeps going while the new pet layers load alongside it.

This is also nice for like, the species/color picker form, so we're not
taking away input elements from people who depend on e.g. keyboard
focus.
2024-07-03 21:52:43 -07:00

89 lines
2.5 KiB
JavaScript

class OutfitLayer extends HTMLElement {
#internals;
constructor() {
super();
this.#internals = this.attachInternals();
}
connectedCallback() {
setTimeout(() => this.#connectToChildren(), 0);
}
disconnectedCallback() {
window.removeEventListener("message", this.#onMessage);
}
#connectToChildren() {
const image = this.querySelector("img");
const iframe = this.querySelector("iframe");
if (image) {
image.addEventListener("load", () => this.#setStatus("loaded"));
image.addEventListener("error", () => this.#setStatus("error"));
this.#setStatus(image.complete ? "loaded" : "loading");
} else if (iframe) {
this.iframe = iframe;
window.addEventListener("message", (m) => this.#onMessage(m));
this.#setStatus("loading");
} else {
throw new Error(
`<outfit-layer> must contain an <img> or <iframe> tag`,
);
}
}
#onMessage({ source, data }) {
if (source !== this.iframe.contentWindow) {
return;
}
if (
data.type === "status" &&
["loaded", "error"].includes(data.status)
) {
this.#setStatus(data.status);
} else {
throw new Error(
`<outfit-layer> got unexpected message: ${JSON.stringify(data)}`,
);
}
}
#setStatus(newStatus) {
this.#internals.states.clear();
this.#internals.states.add(newStatus);
}
}
customElements.define("outfit-layer", OutfitLayer);
// Morph turbo-frames on this page, to reuse asset nodes when we want to—very
// important for movies!—but ensure that it *doesn't* do its usual behavior of
// aggressively reusing existing <outfit-layer> nodes for entirely different
// assets. (It's a lot clearer for managing the loading state, and not showing
// old incorrect layers!) (We also tried using `id` to enforce this… no luck.)
addEventListener("turbo:before-frame-render", (event) => {
if (typeof Idiomorph !== "undefined") {
event.detail.render = (currentElement, newElement) => {
Idiomorph.morph(currentElement, newElement.innerHTML, {
morphStyle: "innerHTML",
callbacks: {
beforeNodeMorphed: (currentNode, newNode) => {
// If Idiomorph wants to transform an <outfit-layer> to
// have a different data-asset-id attribute, we replace
// the node ourselves and abort the morph.
if (
newNode.tagName === "OUTFIT-LAYER" &&
newNode.getAttribute("data-asset-id") !==
currentNode.getAttribute("data-asset-id")
) {
currentNode.replaceWith(newNode);
return false;
}
},
},
});
};
}
});