import { useQuery } from "@tanstack/react-query"; import { normalizeSwfAssetToLayer, normalizeZone } from "./shared-types"; export function useItemAppearances(id, options = {}) { return useQuery({ ...options, queryKey: ["items", String(id)], queryFn: () => loadItemAppearancesData(id), }); } async function loadItemAppearancesData(id) { const res = await fetch( `/items/${encodeURIComponent(id)}/appearances.json`, ); if (!res.ok) { throw new Error( `loading item appearances failed: ${res.status} ${res.statusText}`, ); } return res.json().then(normalizeItemAppearancesData); } export function useItemSearch(searchOptions, queryOptions = {}) { // Item searches are considered fresh for an hour, unless the search // includes user-specific filters, in which case React Query will pretty // aggressively reload it! const includesUserSpecificFilters = searchOptions.filters.some( (f) => f.key === "user_closet_hanger_ownership", ); const staleTime = includesUserSpecificFilters ? 0 : 1000 * 60 * 5; return useQuery({ ...queryOptions, queryKey: ["itemSearch", buildItemSearchParams(searchOptions)], queryFn: () => loadItemSearch(searchOptions), staleTime, }); } function buildItemSearchParams({ filters = [], withAppearancesFor = null, page = 1, perPage = 30, }) { const params = new URLSearchParams(); for (const [i, { key, value, isPositive }] of filters.entries()) { params.append(`q[${i}][key]`, key); if (key === "fits") { params.append(`q[${i}][value][species_id]`, value.speciesId); params.append(`q[${i}][value][color_id]`, value.colorId); if (value.altStyleId != null) { params.append(`q[${i}][value][alt_style_id]`, value.altStyleId); } } else { params.append(`q[${i}][value]`, value); } if (isPositive == false) { params.append(`q[${i}][is_positive]`, "false"); } } if (withAppearancesFor != null) { const { speciesId, colorId, altStyleId } = withAppearancesFor; params.append(`with_appearances_for[species_id]`, speciesId); params.append(`with_appearances_for[color_id]`, colorId); if (altStyleId != null) { params.append(`with_appearances_for[alt_style_id]`, altStyleId); } } params.append("page", page); params.append("per_page", perPage); return params.toString(); } async function loadItemSearch(searchOptions) { const params = buildItemSearchParams(searchOptions); const res = await fetch(`/items.json?${params}`); if (!res.ok) { throw new Error( `loading item search failed: ${res.status} ${res.statusText}`, ); } return res .json() .then((data) => normalizeItemSearchData(data, searchOptions)); } window.loadItemSearch = loadItemSearch; function normalizeItemAppearancesData(data) { return { name: data.name, appearances: data.appearances.map((appearance) => ({ body: normalizeBody(appearance.body), swfAssets: appearance.swf_assets.map(normalizeSwfAssetToLayer), })), restrictedZones: data.restricted_zones.map((z) => normalizeZone(z)), }; } function normalizeItemSearchData(data, searchOptions) { return { id: buildItemSearchParams(searchOptions), numTotalPages: data.total_pages, items: data.items.map((item) => ({ id: String(item.id), name: item.name, thumbnailUrl: item.thumbnail_url, isNc: item["nc?"], isPb: item["pb?"], currentUserOwnsThis: item["owned?"], currentUserWantsThis: item["wanted?"], appearanceOn: normalizeItemSearchAppearance( data.appearances[item.id], item, ), })), }; } function normalizeItemSearchAppearance(data, item) { if (data == null) { return null; } return { id: `item-${item.id}-body-${data.body.id}`, layers: data.swf_assets.map(normalizeSwfAssetToLayer), restrictedZones: data.swf_assets .map((a) => a.restricted_zones) .flat() .map(normalizeZone), }; } function normalizeBody(body) { if (String(body.id) === "0") { return { id: "0" }; } return { id: String(body.id), species: { id: String(body.species.id), name: body.species.name, humanName: body.species.human_name, }, }; }