Refactor item page outfit-layer to use Web Components

Instead of doing all this listening to Turbo events etc to know when
outfit layers might have changed, making it a custom element and wiring
in the behavior to its actual lifecycle makes it always Just Work!
This commit is contained in:
Emi Matchu 2024-07-02 22:16:37 -07:00
parent 857812610a
commit 3c415e9cd3
4 changed files with 27 additions and 36 deletions

View file

@ -0,0 +1,23 @@
class OutfitLayer extends HTMLElement {
connectedCallback() {
setTimeout(() => this.#initializeImage(), 0);
}
#initializeImage() {
this.image = this.querySelector("img");
if (!this.image) {
throw new Error(`<outfit-layer> must contain an <img> tag`);
}
this.image.addEventListener("load", () => this.#setStatus("loaded"));
this.image.addEventListener("error", () => this.#setStatus("error"));
this.#setStatus(this.image.complete ? "loaded" : "loading");
}
#setStatus(newStatus) {
this.setAttribute("status", newStatus);
}
}
customElements.define("outfit-layer", OutfitLayer);

View file

@ -49,7 +49,7 @@ body.items-show
margin: 0 auto .75em margin: 0 auto .75em
.outfit-layer outfit-layer
display: block display: block
position: absolute position: absolute
inset: 0 inset: 0
@ -58,7 +58,7 @@ body.items-show
width: 100% width: 100%
height: 100% height: 100%
&:has(.outfit-layer[data-status="loading"]) &:has(outfit-layer[status="loading"])
background: gray background: gray
.species-color-picker .species-color-picker

View file

@ -1,32 +0,0 @@
// When we load in a new preview, set the status on the images. We use this in
// our CSS to show a loading state when needed.
function setImageStatuses() {
for (const layer of document.querySelectorAll(".outfit-layer")) {
const isLoaded = layer.querySelector("img").complete;
layer.setAttribute("data-status", isLoaded ? "loaded" : "loading");
}
}
document.addEventListener("turbo:frame-render", () => setImageStatuses());
document.addEventListener("turbo:load", () => setImageStatuses());
// When a preview image loads or fails, update its status. (Note that `load`
// does not fire for images that were loaded from cache, which is why we need
// both this and `setImageStatuses` when rendering new images!)
document.addEventListener(
"load",
({ target }) => {
if (target.matches(".outfit-layer img")) {
target.closest(".outfit-layer").setAttribute("data-status", "loaded");
}
},
{ capture: true },
);
document.addEventListener(
"error",
({ target }) => {
if (target.matches(".outfit-layer img")) {
target.closest(".outfit-layer").setAttribute("data-status", "error");
}
},
{ capture: true },
);

View file

@ -16,7 +16,7 @@
= turbo_frame_tag "item-preview" do = turbo_frame_tag "item-preview" do
.outfit-viewer .outfit-viewer
- @preview_outfit.visible_layers.each do |swf_asset| - @preview_outfit.visible_layers.each do |swf_asset|
.outfit-layer %outfit-layer
= image_tag swf_asset.image_url, alt: "" = image_tag swf_asset.image_url, alt: ""
= form_for item_path(@item), method: :get, class: "species-color-picker", = form_for item_path(@item), method: :get, class: "species-color-picker",
@ -43,5 +43,5 @@
%footer= t '.contributors.footer' %footer= t '.contributors.footer'
- content_for :javascripts do - content_for :javascripts do
= javascript_include_tag 'item-page', async: true = javascript_include_tag 'outfit-viewer', async: true