I like making these more concise and consistent across the dropdown and the cards, I'm still not 100% sold on the design but it seems ok for now!
At first I had it in the dropdown as "Background (3)", but realized that conflicts with the usual pattern of like, saying how many items match a certain filter…
Using this to get an at-a-glance check on how Neopets IDs are typically assigned for body-specific items… looks like it's increasing, and alphabetical by species? (not by species ID?)
I think this will be generally useful to minimize switching around for common operations, but also I'm thinking of building a bulk assign tool for things with broken body IDs, and this will be the place for it to live, I think
Inspired by the "Flying in an Airplane" bug (item 82287), where the official SVG (and I think SWF) were visually glitched and included both zones in the image, but the official PNG was correct.
This flag lets us use the PNG, like the official player does—but only for this item, while still keeping SVGs for everyone else!
Oops, I never actually saw the practically invisible text in light mode! Let's make it actually dark in light mode item pages, and still dark in all wardrobe pages!
I'm gonna extend `itemSearch` to also look up the total number of results, and the fragmentation between `itemSearch` and `itemSearchToFit` finally caught up with me :p
I've deprecated `itemSearchToFit`, and moved the fit parameters into a new optional `fitsPet` parameter for `itemSearch`.
I'm going to keep `itemSearchToFit` around for now, because old JS builds still use it, and I'd like to avoid disrupting folks. But I'm not going to add the new total results field to the results object it returns, and that's gonna be okay!
I was starting to write a Cypress test, and noticed there was no placeholder to use for searching, and I don't know how that escaped my notice for so long! I guess I commented it out for some reason, but I forget why, and this seems fine now! (Looks like we removed it when we added zone suggestions? Idk!)
Sentry issue IMPRESS-2020-20 doesn't have a clear backtrace, but it looks like the usual thing where we trigger an Apollo query directly, and forget to catch a potential error in the returned promise. I noticed that the last thing the user did was type in the search bar, and got a _caught_ error for the initial search!
Scanning the SearchPanel file, I think it's likely that this was a failure in `fetchMore` for the infinite pagination.
I'm a bit worried as to _why_ we were doing infinite scrolling stuff when there were no results? I wasn't able to repro a scroll event on the empty results list, but it's plausible that it could happen. I've added a gate to not send this request when there's an error!
Previously, if you typed a pet name on the homepage, but its pose wasn't labeled in our database, you'd get a black empty screen. Now, we redirect to the UNKNOWN pose, or whatever exists for us to use!
I think it's confusing that the poses in the dropdown start with the emotion word, but are grouped by the gender presentation word! It's also different than the precedence order! I've reordered them.
Oops, we were removing the last word of the search query if you picked a suggestion from Advanced Search! That behavior is meant for the case where you're _typing_ a filter name 😅
Sooo, I added this more graceful regex and error logging… then realized that this shouldn't be happening in the first place, because we should only be removing the last word of the query if you picked the filter via typing, not advanced search!
I'm glad to have the assertion error and the new handler, but I'll fix the cause too in the next change :p
We were getting away with singular stuff like "Hat" in the filter text for a while, but once it became "Hat you own" it got too weird imo!
Now, we say "Hats" and "Hats you own" in the filter text. We keep the singular in the search suggestion, but with the "Zone:" prefix, which is something I've been wanting anyway. (It should help with the show all suggestions UI coming soon, too.)
Oops, the old condition depending on `queryFilterText` to implicitly check for filter presence. But now that we always show "Items" as prefix text for the filters on this page, the reset button was always showing!
Use our new util function instead.
I switched from my `_NoAuthRequired` opname hack, to a more robust `context` argument, and it's opt-in!
This should make queries without user data faster by default. We'll need to remember to specify this in order to get user data, but it shouldn't be something we'd like, ship without remembering—the feature just won't work until we do!
Still getting some chunk load errors in my Sentry reporting! My hunch is these are the culprit. I hooope that after this the errors are pretty much gone! If not, then I'm missing something about what causes these failures…
Someone wrote in how, when your search query ends with a string that creates Advanced Search suggestions, clicking on items in the list requires two clicks: one to blur and dismiss the suggestions, and one to actually click the item.
Here, I'm experimenting with just leaving the suggestions open. It doesn't feel _great_, but it definitely feels _better_ than before on this edge case, and I thiiink this only affects this edge case in practice? We'll see if it feels goofy in some cases I forgot tho!
Woo, it's looking pretty good, I think!
I didn't bother with pagination yet, since I feel like that'll be a bit of a design and eng lift unto itself... but I figured people would appreciate the ability to look up individual items, even if the rest isn't ready yet 😅
Previously, when you navigated directly to an outfit by typing the URL into the browser or following an external link, the name would stay as "Untitled outfit", even after the outfit loaded.
This was because, when you render an `Editable` Chakra component with `value={undefined}`, it permanently enters "uncontrolled" mode, and providing a value later doesn't change that.
But tbh passing `undefined` down from outfit state wasn't my intention! But yeah, turns out the `?.` operator returns `undefined` rather than `null`, which I guess makes sense!
So, I've fixed this on both ends. I'm now passing more `null`s down via outfit state, because I think that's a more expected value in general.
But also, for the `Editable`, I'm making a point of passing in an empty string as `value`, so that this component will be resilient to upstream changes in the future. (It's pretty brittle to _depend_ on the difference between `null` and `undefined`, as we saw here 😅)
Previously, when you clicked on a saved outfit from Your Outfits, the back button would take you back to the homepage, which was confusing for scanning through stuff! Now, it goes back to Your Outfits if it's yours.
I'm not suuure this is the behavior we want? But it seems intuitive enough!
Previously, if you navigated to /outfits/new without a species or color in the query string, we'd show a blank outfit page, with the species/color picker hidden. Now, we default to a Blue Acara instead!
We don't do anything to handle _invalid_ species/color IDs, but I don't super mind that, because in practice that would require some call site to malform the URL, and I don't super expect that.
This resolves more of the _cause_ of Sentry issue IMPRESS-2020-8, but I'm still wondering how a user got to the URL `/outfits/new?[object+Object]=&objects[]=35185&objects[]=67084`. I'm wondering if the pet loader on the homepage has a bug in Safari? I feel like I heard something like that from the feedback form, too...
If the species/color of the current outfit aren't available yet (e.g. a saved outfit is still loading in), hide the picker altogether. This is because the picker can't handle change events during that time, and it's easier to just hide all this than to add special case handlers like disabled states! (And, while placeholders are often helpful, I'm not sure the placeholder dropdowns are any better than empty space in this case.)
This can also happen when the user loads a page without a species/color ready, like just going straight to `/outfits/new`. I think I might want to add a handler for that, though.
Resolves the direct cause of Sentry issue IMPRESS-2020-8, though I'm not sure how the user got to the URL `/outfits/new?[object+Object]=&objects[]=35185&objects[]=67084` in the first place...
Two fixes in here, for when image downloads fail!
1) Actually catch the error, and show UI feedback
2) Throw it as an actual exception, so the console message will have a stack trace
Additionally, debugging this was a bit trickier than normal, because I didn't fully understand that the image `onerror` argument is an error _event_, not an Error object. So, Sentry captured the uncaught promise rejection, but it didn't have trace information, because it wasn't an Error. Whereas now, if I forget to catch `loadImage` calls in the future, we'll get a real trace! both in the console for debugging, and in Sentry if it makes it to prod :)
Oops, if you try to show PosePicker before we have a species/color ready, it sends a bad GraphQL request. No visible user impact, just an unnecessary network call and an error in the console! This happens when you're loading an outfit by ID.
Here, we hide PosePicker if there's no species/color ready yet. This stops the extra request from firing!
When loading an outfit in the wardrobe page, there was an awkward state where the outfit preview loading spinner would vanish and then reappear.
This was because `useOutfitState` briefly reported `loading: false`, then fixed itself after almost immediately—but our OutfitPreview component has a delay before re-showing the spinner.
In this change, we smooth out the loading state, by enabling the second GQL request to start immediately once the first request is done, instead of waiting on a callback to finish.
To help the load time for outfits feel shorter, we now reuse the outfit thumbnail from the Your Outfits page as a placeholder!
This doesn't add any overhead when the thumbnail data _isn't_ in your session cache, e.g. if you navigate to the outfit directly. But if we have the thumbnail on hand already, we just show it, easy peasy!
That'll still show up when the outfit is still loading, but this lets us use the Apollo cache to show the name instantly if you're clicking through a link from Your Outfits
Still a pretty limited early version, no saving _back_ to the server. But you can click from the Your Outfits page and see the outfit for real! :3 We have a WIPCallout explaining the basics.
This seemed to only show up in dev? But right, I guess it's not happy about passing stuff from ClassNames into a Popover Portal. Move it inside, fixed!
This has been bothering me for a long time, but I couldn't really figure out what to do about it. But tweaking the site bg color a smidge has helped us really add texture to the cards I want to have pop out, like the outfit polaroids!
I kinda went all-in in a burst, but tbh I think it looks great :3
I haven't really touched the wardrobe page with it yet though, that'll probably need some tweaking... for now I'm overriding it to keep the old background!
Looks like there was some kind of runtime conflict when running @emotion/css and @emotion/react at the same time in this app? Some styles would just get clobbered, making things look all weird.
Here, I've removed our @emotion/css dependency, and use the `<ClassNames>` utility element from `@emotion/react` instead. I'm not thrilled about the solution, but it seems okay for now...
...one other thing I tried was passing a `css` prop to Chakra elements, which seemed to work, but to clobber the element's own Emotion-based styles. I assumed that the Babel macro wouldn't help us, and wouldn't convert css props to className props for non-HTML elements... but I suppose I'm not sure!
Anyway, I don't love this syntax... but I'm happy for the site to be working again. I wonder if we can find something better.
I saw the short-near-the-front and it just frankly looked awkward? Not sure why I liked it before?
I think this medium at the end of the list is better aesthatically, though it's starting to get a bit messy with the different colors mixed around… but I think there's also a semantic argument that we're keeping the facts about the item together, and the _user-specific_ stuff separate at the end… (putting it at the front would be a good semantic argument too, but I think the NC/NP alignment is too important)
In a previous change, I moved the margin for item badges onto an ItemBadge element… but I didn't think through how that would break the spacing for the loading state of ItemPage. Now, the loading skeleton items _contained_ the badge margin, and so the spacing between badges was shiny skeleton-y.
Here, I replace ZoneBadgesList with a function that just returns the elements, and go back to using Chakra's Wrap component. That will apply the margin to direct children, and the zone badges are direct children now.
One option I'm thinking of in hindsight is an idea I had earlier: Chakra hacks the margin onto _React_ children, but could we use CSS direct child selector instead? A bit trickier to resolve the margin size to the theme's value, but plenty doable… something to consider!
When we decided to start out with /api/assetProxy, we didn't know how much the load would be in practice, so we just went ahead and tried it! Turns out, it was too high, and Vercel shut down our deployment 😅
Now, we've off-loaded this to a Fastly CDN proxy, which should run even faster and more efficiently, without adding pressure to Vercel servers and pushing our usage numbers! And I suspect we're gonna stay comfortably in Fastly's free tier :) but we'll see!
(Though, as always, if Neopets can finally upgrade their own stuff to HTTPS, we'll get to tear down this whole proxy altogether!)
So I broke the Download button when we switched to impress-2020.openneo.net, and I forgot to update the Amazon S3 config.
But in addition to that, I'm making some code changes here, to make downloads faster: we now use exactly the same URL and crossOrigin configuration between the <img> tag on the page, and the image that the Download button requests, which ensures that it can use the cached copy instead of loading new stuff. (There were two main cases: 1. it always loaded the PNGs instead of the SVG, which doesn't matter for quality if we're rendering a 600x600 bitmap anyway, but is good caching, and 2. send `crossOrigin` on the <img> tag, which isn't necessary there, but is necessary for Download, and having them match means we can use the cached copy.)
Previously I tried to be clever and pre-optimize by putting all the layers onto one canvas… I think this probably helped by batching their paints, but it made fades less smooth by not taking advantage of native CSS transitions, and it made us dip into JS way more often than necessary.
Here, I take the simpler approach: just layers of <img> and <canvas> tags, with each animated layer on its own canvas, and letting the browser handle transitions and compositing, and separate `setInterval` timers to manage their framerates.
I have a suspicion that batching the paints could help performance more, but honestly, maybe that batching is already happening somehow, because things look pretty great on my big-screen stress test now; and so if it _is_ relevant, I want to wait and see after testing on low-power devices.
I think it's great that we hide the button when it's not relevant, but that makes it hard to know that it exists. Here, we do some cute tricks to blink up the "Paused" button when it first appears, even if the user doesn't have the controls visible right now
We did this a while back too, but I guess something changed in Apollo: I guess it used to return identical item objects from the cache on its own, and now it returns brand new item objects. So we gotta do the object caching hacks ourselves!
This speeds up add/remove item state updates from 500ms to 100ms on my Mac, because we stop re-rendering all the Item components and their complex Chakra children.
This is especially worth doing now, because animations make long updates much more noticeable! (It interrupts the animation 😅)
This is really very cute, but too many items it turns out are lod despite not actually being animated 🙃 it's helpful for looking for test cases tho, so I'm keeping it, but support only!
I also ended up really liking the icon-badge+tooltip design as a way to summarize lil things, so I'm trying Own/Want short badges in the same style.
Initially the spinner was only used in OutfitPreview, where the background was always pretty dark. Now that we use it in more general contexts, we need a light/dark distinction!
Also went and standardized out the `size` props
Here, we extract a lean WardrobePageLayout component, so that we can bundle it into the main app as a loading state for WardrobePage.
This means that clicking Start from the homepage will, instead of flashing the screen to white while WardrobePage loads, show the correctly-sized black/white page layout instead.
Essentially I want the center-y visual balance around the image, and the name and _one_ line of tags. If there are extra tags, I want that to go down on its own, rather than bringing down the image+buttons to center against them.
The single-line-of-tags case is the most common, and I think it makes things feel more consistent for all the items to stick to that basic layout, while trying to keep that layout feeling center-y
I was getting annoyed by how, when you're using search, trying on an item will remove conflicting stuff, and then if you decide you don't like what you tried the old stuff _doesn't come back_
As of this change, it does! When you start a new search, we save the outfit state, and then whenever you change the items we ask "hey can these old ones safely be re-worn again?" and re-wear them if so.
On the search panel, not all items have the remove button, and it's confusing to have the other buttons be in inconsistent positions!
Move the Remove button to the left side of the list, so that everything else is positioned the same regardless
Okay, we handle the new pages correctly! Still some weird bugs when you send requests near each other? Probably wise to migrate to Apollo's new way of doing this