import "@/public/css/specific.css"; import config from "@/utils/config.json"; import { bricksToCurrency } from "@/utils/utilities"; import { userDetails } from "@/utils/types"; import * as apiTypes from "@/utils/api/types"; import { getAPI } from "@/utils/api"; const itemID = window.location.pathname.split("/")[2]; /** * Adds the locale real-life dollar value next to the amount of bricks an item costs in the text of the purchase button. */ export function irlBrickPrice() { try { const purchaseBtn = document.querySelector( 'button[onclick^="buy"], button[data-price]', )!; const currency = bricksToCurrency( parseInt(purchaseBtn.getAttribute("data-price")!), "USD", ); if (currency) { const spanTag = document.createElement("span"); spanTag.classList.add("text-muted"); spanTag.style.fontSize = "0.7rem"; spanTag.style.fontWeight = "lighter"; spanTag.innerText = ` (${currency})`; purchaseBtn.appendChild(spanTag); } } catch (e) { // The store purchase button has several different ways of being represented, this should // only happen when the item is already owned console.warn("[Poly+] Failure to find purchase button on page."); } const addPrice = function (item: HTMLElement) { const price = item.getElementsByClassName("text-success")[0]; const purchaseBtn = item.querySelector("button[data-listing-id]"); if (price && purchaseBtn) { const currency = bricksToCurrency( parseInt(purchaseBtn.getAttribute("data-price")!), "USD", ); if (currency) { const spanTag = document.createElement("span"); spanTag.classList.add("text-muted"); spanTag.style.fontSize = "0.7rem"; spanTag.style.fontWeight = "lighter"; spanTag.innerText = ` (${currency})`; price.appendChild(spanTag); } } }; const resellers = document.getElementById("resellers-container"); if (resellers) { for (const reseller of resellers.children) { addPrice(reseller as HTMLElement); } const mutations = new MutationObserver((mutations) => { for (const record of mutations) { for (const node of record.addedNodes) { addPrice(node as HTMLElement); } } }); mutations.observe(resellers, { childList: true }); } } /** * Adds a button below the item thumbnail preview allowing the user to preview their avatar with the item they are looking at equipped. * @param user The public-facing details of the authenticated user. */ export function tryOn(user: userDetails) { const publicAPI = getAPI("public"); const itemId = window.location.pathname.split("/")[2]; const favoriteBtn = document.querySelector( 'button[onclick^="toggleFavorite"]', )!; const button = document.createElement("button"); button.classList.add("btn", "btn-outline-primary", "btn-sm", "mt-2"); button.style.width = "50px"; button.innerHTML = ''; const modal = document.createElement("dialog"); modal.classList.add("polyplus-modal"); Object.assign(modal.style, { width: "450px", height: "500px", border: "1px solid #484848", backgroundColor: "#181818", borderRadius: "20px", overflow: "hidden", }); modal.innerHTML = `
Poly+ Item Preview
Try this item on your avatar before purchasing it!
`; document.body.prepend(modal); favoriteBtn.parentElement!.appendChild(button); const loadFrame = async (source: apiTypes.avatarApiSchema) => { console.info("[Poly+] Loading avatar preview with avatar data: ", source); for (const [key, value] of Object.entries(source.colors)) { source.colors[key as keyof typeof source.colors] = "#" + value; } const avatar: { [key: string]: any } = { useCharacter: true, items: [] as Array, headColor: source.colors.head, torsoColor: source.colors.torso, leftArmColor: source.colors.leftArm, rightArmColor: source.colors.rightArm, leftLegColor: source.colors.leftLeg, rightLegColor: source.colors.rightLeg, }; const itemInfo: apiTypes.itemApiSchema = await (await fetch(publicAPI.url + "store/" + itemId)).json(); for ( const item of [ ...source.assets.filter(( item: any, ) => (item.type != itemInfo.type || itemInfo.type == "hat")), { id: itemInfo.id, type: itemInfo.type, }, ] ) { if (item.type == "hat" || item.type == "tool") { const mesh: apiTypes.meshApiSchema = await (await fetch( publicAPI.url + "assets/serve-mesh/" + item.id, )).json(); if (mesh.success) { if (item.type == "hat") { avatar.items.push(mesh.url); } else { avatar.tool = mesh.url; } } } else { const texture: apiTypes.textureApiSchema = await (await fetch( publicAPI.url + "assets/serve/" + item.id + "/Asset", )).json(); if (texture.success) avatar[item.type] = texture.url; } } const frame = document.createElement("iframe"); Object.assign(frame.style, { width: "100%", height: "auto", aspectRatio: "1", borderRadius: "20px", background: "#1e1e1e", }); frame.src = `https://polytoria.com/ptstatic/itemview/#${ btoa(encodeURIComponent(JSON.stringify(avatar))) }`; modal.getElementsByClassName("modal-body")[0].appendChild(frame); }; button.addEventListener("click", async () => { if (!modal.getElementsByTagName("iframe")[0]) { button.innerHTML = ` Loading... `; const avatar = await user.getAvatar(); if (avatar == "disabled") { console.error( "[Poly+] API is disabled, cancelling try-on items loading..", ); const modalBody = modal.getElementsByClassName( "modal-body", )[0] as HTMLDivElement; modalBody.style.marginTop = "50px"; modalBody.innerHTML = `

Sorry! This feature is currently unavailable. Please check back later!

`; modal.showModal(); button.innerHTML = ''; return; } await loadFrame(avatar); button.innerHTML = ''; } modal.showModal(); }); } /** * Replaces the item sales counter with the number of owners the item has, useful for items that were granted by staff or earned via an event. */ export async function accurateOwnerCount() { const publicAPI = getAPI("public"); const counter = document.querySelectorAll(".col.text-center")[2]!; if (!counter || counter.children[1].textContent!.trim() != "0") return; const owners: "disabled" | number = await pullKVCache( "ownerCount", itemID, async () => { if (!publicAPI.enabled) return "disabled"; const res: apiTypes.ownersApiSchema = await (await fetch( publicAPI.url + "store/" + itemID + "/owners", )).json(); return res.total; }, 60000, // 1 minute false, ); if (owners == "disabled") { throw new Error( "[Poly+] API disabled, cancelling accurate item owner count loading..", ); } (counter.children[0] as HTMLHeadingElement).innerText = "Owners"; (counter.children[1] as HTMLHeadingElement).innerText = owners .toLocaleString(); }