Compare commits

...

5 commits

Author SHA1 Message Date
81a58f8656 Extract outfit-viewer to a separate template
Just cuz this is gonna get more complex down the line!
2024-07-02 22:43:36 -07:00
76d2cc6c21 Add some data attributes to outfit-layer elements
This is mostly for debugging, the zone label especially, to just
eyeball what we're looking at!
2024-07-02 22:38:18 -07:00
e9145251a9 Refactor outfit-layer component to use internal state API, not attrs
Oh right okay, attributes like `status="loading"` are more of an API
for the caller, whereas the internal state API is where you wanna put
things that are meant to be used in CSS selectors and stuff.
2024-07-02 22:34:51 -07:00
3c415e9cd3 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!
2024-07-02 22:24:26 -07:00
857812610a Refactor outfit_viewer_layers helper to just be inlined into template
I forget what complexity was in here previously that made this make
sense before, but now it's just a loop, whatever!
2024-07-02 22:03:43 -07:00
6 changed files with 45 additions and 48 deletions

View file

@ -0,0 +1,31 @@
class OutfitLayer extends HTMLElement {
#internals;
constructor() {
super();
this.#internals = this.attachInternals();
}
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.#internals.states.clear();
this.#internals.states.add(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:state(loading))
background: gray background: gray
.species-color-picker .species-color-picker

View file

@ -244,15 +244,5 @@ module ItemsHelper
def item_header_user_lists_form_state def item_header_user_lists_form_state
cookies.fetch("DTIItemPageUserListsFormState", "closed") cookies.fetch("DTIItemPageUserListsFormState", "closed")
end end
def outfit_viewer_layers(swf_assets)
swf_assets.map { |a| outfit_viewer_layer(a) }.join("\n").html_safe
end
def outfit_viewer_layer(swf_asset)
content_tag :div, class: "outfit-layer" do
image_tag swf_asset.image_url, alt: ""
end
end
end end

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

@ -0,0 +1,9 @@
.outfit-viewer
- outfit.visible_layers.each do |swf_asset|
%outfit-layer{
data: {
"asset-id": swf_asset.id,
"zone": swf_asset.zone.label,
},
}
= image_tag swf_asset.image_url, alt: ""

View file

@ -14,8 +14,7 @@
sorry! sorry!
= turbo_frame_tag "item-preview" do = turbo_frame_tag "item-preview" do
.outfit-viewer = render partial: "outfit_viewer", locals: {outfit: @preview_outfit}
= outfit_viewer_layers @preview_outfit.visible_layers
= form_for item_path(@item), method: :get, class: "species-color-picker", = form_for item_path(@item), method: :get, class: "species-color-picker",
data: {"is-valid": @preview_error.nil?} do |f| data: {"is-valid": @preview_error.nil?} do |f|
@ -40,6 +39,6 @@
%li= link_to(contributor.name, user_contributions_path(contributor)) + format_contribution_count(count) %li= link_to(contributor.name, user_contributions_path(contributor)) + format_contribution_count(count)
%footer= t '.contributors.footer' %footer= t '.contributors.footer'
- content_for :javascripts_body do - content_for :javascripts do
= javascript_include_tag 'item-page', defer: true = javascript_include_tag 'outfit-viewer', async: true