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"; 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 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: '350px', 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(config.api.urls.public + "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(config.api.urls.public + "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(config.api.urls.public + "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 = config.api.urls.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 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 (!config.api.enabled) return "disabled"; const res: apiTypes.ownersApiSchema = (await (await fetch(config.api.urls.public + '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(); };