let Utilities; const Container = document.querySelector('.container.p-0.p-lg-5') const ItemCache = { 24122: { type: "hat", accessoryType: "hat", name: "Polytoria Cap", price: 0, creator: { name: "Polytoria", id: 1 }, thumbnail: "https://c0.ptacdn.com/thumbnails/assets/RR0VPd5hX30Fx5APwRBGObotf1xD1DRT.png", asset: "https://c0.ptacdn.com/assets/InBsL5bpJdp84ZPZGQMeHuyCBlo-uOv7.glb" }, 24118: { type: "shirt", name: "Green Polytoria Flannel", price: 0, creator: { name: "Polytoria", id: 1 }, thumbnail: "https://c0.ptacdn.com/thumbnails/assets/s7l57JugjbZfWTKAQ0cqTohOBraRbX5E.png", asset: "https://c0.ptacdn.com/assets/uWrrnFGwgNN5W171vqYTWY7E639rKiXK.png" }, 24123: { type: "pants", name: "Jeans", price: 0, creator: { name: "Polytoria", id: 1 }, thumbnail: "https://c0.ptacdn.com/thumbnails/assets/anebTuFMLg8NKhRL3ab7hbzCfmcsFqGO.png", asset: "https://c0.ptacdn.com/assets/HD6TFdXD8CaflRNmd84VCNyNsmTB0SH3.png" }, 37582: { type: "torso", name: "Slim Body", price: 0, creator: { name: "Polytoria", id: 1 }, thumbnail: "https://c0.ptacdn.com/thumbnails/assets/f_k-ZN_xmA_ALZiJQanOKT-Y4qq5kI1b.png", asset: "https://c0.ptacdn.com/assets/qoqZ2qPyaGvB3MLGgJZ6oLWTz-xxGo-8.glb" } } let Avatar = { useCharacter: true, items: [24122], shirt: 24118, pants: 24123, headColor: '#e0e0e0', torsoColor: '#e0e0e0', leftArmColor: '#e0e0e0', rightArmColor: '#e0e0e0', leftLegColor: '#e0e0e0', rightLegColor: '#e0e0e0' }; /* Discovery */ let Page = 1 let PageCount = 1 let Search = "" let Sort = "createdAt" let Order = "desc" let ShowOffsale = true let TabSelected = "hat" let RetroItems = null /* Customization */ let SelectedBodyPart !(async () => { Utilities = await import(chrome.runtime.getURL('resources/utils.js')); Utilities = Utilities.default; chrome.storage.sync.get(['PolyPlus_Settings'], function(result){ Settings = result.PolyPlus_Settings || Utilities.DefaultSettings; if (Settings.AvatarSandboxOn || 1 === 1) { if (new URLSearchParams(window.location.search).has('sandbox')) { document.title = 'Poly+ Avatar Sandbox' PageLoad() } else { const SandboxButton = document.createElement('a'); SandboxButton.classList = 'btn btn-outline-success w-100 mt-3'; SandboxButton.href = '?sandbox=true'; SandboxButton.innerHTML = ' Avatar Sandbox'; document.getElementById('cont-move').parentElement.appendChild(SandboxButton); } } }) })(); async function PageLoad() { const PageContents = (await ((await fetch(chrome.runtime.getURL('resources/avatar-sandbox.html'))).text())) Container.innerHTML = PageContents Utilities.InjectResource("registerTooltips") IFrame = document.getElementById('viewFrame') UpdateAvatar() LoadItems() const Tabs = document.getElementById('tabs') Array.from(Tabs.children).forEach((element) => { element.addEventListener('click', function () { let Link = element.getElementsByTagName('a')[0]; if (!Link.classList.contains('active')) { Link.classList.add('active'); Tabs.querySelector(`[data-tab="${TabSelected}"]`).classList.remove('active'); TabSelected = Link.getAttribute('data-tab'); ItemSearch.previousElementSibling.value = '' Page = 1; Search = "" LoadItems(); } }); }); const BodyColorsModal = document.getElementById('p+body_colors') const BodyParts = Array.from(document.getElementsByClassName('bodypart')) BodyParts.forEach(part => { part.addEventListener('click', function(){ SelectedBodyPart = part.id BodyColorsModal.showModal() }) }) const BodyColors = Array.from(document.getElementsByClassName('colorpicker-color')) BodyColors.forEach(color => { color.addEventListener('click', function(){ Avatar[SelectedBodyPart+'Color'] = color.style.backgroundColor BodyColorsModal.close() UpdateAvatar() }) }) const ItemSearch = document.getElementById('search-btn') ItemSearch.addEventListener('click', function(){ Search = ItemSearch.previousElementSibling.value Page = 1 LoadItems() }) const ItemSort = document.getElementById('item-sort') ItemSort.addEventListener('change', function(){ Sort = ItemSort.options[ItemSort.selectedIndex].value Page = 1 LoadItems() }) const ItemOrder = document.getElementById('item-order') ItemOrder.addEventListener('change', function(){ Order = ItemOrder.options[ItemOrder.selectedIndex].value Page = 1 LoadItems() }) /* Public API does not have an offsale parameter document.getElementById('show-offsale').addEventListener('change', function(){ ShowOffsale = !ShowOffsale console.log(ShowOffsale) Page = 1 LoadItems() }) */ // Pagination is annoying const First = document.getElementById('pagination-first'); const Prev = document.getElementById('pagination-prev'); const Next = document.getElementById('pagination-next'); const Last = document.getElementById('pagination-last'); if (Page > 0) { Prev.parentElement.classList.remove('disabled'); First.parentElement.classList.remove('disabled'); } else { Prev.parentElement.classList.add('disabled'); First.parentElement.classList.add('disabled'); } First.addEventListener('click', function () { if (Page > 1) { Page = 1; LoadItems(); } }); Prev.addEventListener('click', function () { if (Page > 1) { Page--; LoadItems(); } }); Next.addEventListener('click', function () { if (Page < PageCount) { Page++; LoadItems(); } }); Last.addEventListener('click', function () { if (Page < PageCount) { Page = PageCount; LoadItems(); } }); const ClearButton = document.getElementById('clear'); ClearButton.addEventListener('click', function () { Avatar = { useCharacter: true, items: [24122], shirt: 24118, pants: 24123, headColor: '#e0e0e0', torsoColor: '#e0e0e0', leftArmColor: '#e0e0e0', rightArmColor: '#e0e0e0', leftLegColor: '#e0e0e0', rightLegColor: '#e0e0e0' }; UpdateAvatar(); }); const LoadMyselfButton = document.getElementById('myself'); LoadMyselfButton.addEventListener('click', function () { LoadUser(JSON.parse(window.localStorage.getItem('p+account_info')).ID); }); const JSONUploadButton = document.getElementById('jsonUpload'); JSONUploadButton.addEventListener('change', function () { const Reader = new FileReader(); Reader.addEventListener('loadend', function () { Avatar = JSON.parse(Reader.result); UpdateAvatar(); JSONUploadButton.value = ''; }); Reader.readAsText(JSONUploadButton.files[0]); }); const JSONSaveButton = document.getElementById('jsonSave'); JSONSaveButton.addEventListener('click', function () { const Download = document.createElement('a'); Download.href = URL.createObjectURL( new Blob([JSON.stringify(Avatar)], { type: 'application/json' }) ); Download.setAttribute('download', 'AvatarSandbox.json'); document.body.appendChild(Download); Download.click(); document.body.removeChild(Download); }); const LoadAsset = document.getElementById('load-asset') const LoadAssetType = document.getElementById('load-asset-type') LoadAsset.addEventListener('click', function(){ const SelectedType = LoadAssetType.options[LoadAssetType.selectedIndex].value if (SelectedType !== 'user') { if (SelectedType === 'hat') { Avatar.items.push(LoadAsset.previousElementSibling.value); } else { if (!isNaN(LoadAsset.previousElementSibling.value)) { Avatar[SelectedType] = parseInt(LoadAsset.previousElementSibling.value) } else { Avatar[SelectedType] = LoadAsset.previousElementSibling.value } } UpdateAvatar(); } else { LoadUser(LoadAsset.previousElementSibling.value) } }) document.getElementById('view-cache').addEventListener('click', function(){ console.log('Cache: ', ItemCache) }) } async function UpdateAvatar() { // Hats, Tools: https://api.polytoria.com/v1/assets/serve-mesh/ID // or: https://api.polytoria.com/v1/assets/serve/ID/Asset const FormattedAvatar = structuredClone(Avatar) const AccessoryPromise = [...Avatar.items, Avatar.tool, Avatar.torso].filter((x) => x !== undefined && !x.toString().startsWith('http') && !x.toString().startsWith('data:')).map(async (x, index) => { if (ItemCache[x] === undefined) { try { const ItemDetails = (await (await fetch('https://api.polytoria.com/v1/store/' + x)).json()) ItemCache[x] = { type: ItemDetails.type, name: ItemDetails.name, price: ItemDetails.price, creator: { name: ItemDetails.creator.name, id: ItemDetails.creator.id }, thumbnail: ItemDetails.thumbnail, asset: undefined } if (ItemDetails.type === 'hat') { ItemCache[x].accessoryType = ItemDetails.accessoryType } } catch(error) { ItemCache[x] = { type: "unknown", name: "#" + x, price: null, creator: null, thumbnail: "https://c0.ptacdn.com/static/images/broken.136e44ee.png", asset: undefined, ribbon: "unknown" } } } if (ItemCache[x].asset === undefined) { const MeshURL = (await (await fetch('https://api.polytoria.com/v1/assets/serve-mesh/' + x)).json()) if (MeshURL.success) { ItemCache[x].asset = MeshURL.url if (["mesh", "decal", "audio"].indexOf(ItemCache[x].type) !== -1) { ItemCache[x].type = document.getElementById('load-asset-type').options[document.getElementById('load-asset-type').selectedIndex].value } if (ItemCache[x].type === 'hat') { FormattedAvatar.items[index] = MeshURL.url } else { FormattedAvatar[ItemCache[x].type] = MeshURL.url } } } else { if (ItemCache[x].type === 'hat') { FormattedAvatar.items[index] = ItemCache[x].asset } else { FormattedAvatar[ItemCache[x].type] = ItemCache[x].asset } } }) const TexturePromise = [Avatar.shirt, Avatar.pants, Avatar.face].filter((x) => x !== undefined && !x.toString().startsWith('http') && !x.toString().startsWith('data:') && x !== undefined).map(async (x, index) => { if (ItemCache[x] === undefined) { try { const ItemDetails = (await (await fetch('https://api.polytoria.com/v1/store/' + x)).json()) ItemCache[x] = { type: ItemDetails.type, name: ItemDetails.name, price: ItemDetails.price, creator: { name: ItemDetails.creator.name, id: ItemDetails.creator.id }, thumbnail: ItemDetails.thumbnail, asset: undefined } if (ItemDetails.price === 0) { if (ItemDetails.sales === 0) { ItemCache[x].price = null } else { ItemCache[x].price = 0 } } } catch(error) { ItemCache[x] = { type: "unknown", name: "#" + x, price: null, creator: null, thumbnail: "https://c0.ptacdn.com/static/images/broken.136e44ee.png", asset: undefined, ribbon: "unknown" } } } if (ItemCache[x].asset === undefined) { const TextureURL = (await (await fetch('https://api.polytoria.com/v1/assets/serve/' + x + '/Asset')).json()) if (TextureURL.success) { ItemCache[x].asset = TextureURL.url if (x === Avatar.shirt) { FormattedAvatar.shirt = TextureURL.url } else if (x === Avatar.pants) { FormattedAvatar.pants = TextureURL.url } else if (x === Avatar.face) { FormattedAvatar.face = TextureURL.url } } } else { if (x === Avatar.shirt) { FormattedAvatar.shirt = ItemCache[x].asset } else if (x === Avatar.pants) { FormattedAvatar.pants = ItemCache[x].asset } else if (x === Avatar.face) { FormattedAvatar.face = ItemCache[x].asset } } }) if (Avatar.face === undefined) { FormattedAvatar.face = "https://c0.ptacdn.com/static/3dview/DefaultFace.png" } await Promise.all(AccessoryPromise) await Promise.all(TexturePromise) console.log('Real Avatar: ', Avatar) console.log('Formatted: ', FormattedAvatar) IFrame.addEventListener('load', function(){ IFrame.src = 'https://polytoria.com/ptstatic/itemview/#' + btoa(encodeURIComponent(JSON.stringify(FormattedAvatar))) }) IFrame.src = 'about:blank' UpdateBodyColors() LoadWearing() } async function LoadUser(id) { fetch('https://api.polytoria.com/v1/users/' + id + '/avatar') .then((response) => { if (!response.ok) { throw new Error('Network not ok'); } return response.json(); }) .then((data) => { Avatar.items = []; data.assets.forEach((item) => { if (ItemCache[item.id] === undefined) { ItemCache[item.id] = { type: item.type, name: item.name, price: null, creator: null, thumbnail: item.thumbnail, asset: item.path } if (item.type === 'hat' || item.type === 'tool' || item.type === 'torso') { ItemCache[item.id].creator = { id: 1, name: "Polytoria" } } } if (item.type === 'hat') { ItemCache[item.id].accessoryType = item.accessoryType Avatar.items.push(item.id) } else { Avatar[item.type] = item.id } }); Avatar.headColor = '#' + data.colors.head || '#cdcdcd'; Avatar.torsoColor = '#' + data.colors.torso || '#cdcdcd'; Avatar.leftArmColor = '#' + data.colors.leftArm || '#cdcdcd'; Avatar.rightArmColor = '#' + data.colors.rightArm || '#cdcdcd'; Avatar.leftLegColor = '#' + data.colors.leftLeg || '#cdcdcd'; Avatar.rightLegColor = '#' + data.colors.rightLeg || '#cdcdcd'; UpdateAvatar(); }) .catch((error) => { console.log(error); }); } async function LoadItems() { document.getElementById('inventory').innerHTML = '' let Items; if (TabSelected !== 'retro') { Items = (await (await fetch('https://api.polytoria.com/v1/store?limit=12&order=' + Order + '&sort=' + Sort + '&showOffsale=' + ShowOffsale + '&types[]='+ TabSelected +'&search=' + Search + '&page=' + Page)).json()) } else { if (RetroItems === null) { Items = (await (await fetch('https://poly-upd-archival.pages.dev/data.json')).json()) Object.values(Items).forEach((item, index) => { item.id = Object.keys(Items)[index] item.thumbnail = 'https://poly-archive.pages.dev/assets/thumbnails/' + item.id + '.png' item.creator = { id: 1, name: "Polytoria" } item.asset = 'https://poly-upd-archival.pages.dev/glb/' + item.id + '.glb' ItemCache[item.id] = item }) const PaginationItems = Object.values(Items) let Groups = [] while (PaginationItems.length > 0) { Groups.push(PaginationItems.splice(0, 12)); } Items = { assets: Groups[Page - 1], pages: Groups.length } RetroItems = Groups } else { console.log('use cache') Items = { assets: RetroItems[Page - 1], pages: RetroItems.length } } } PageCount = Items.pages if (Page < PageCount) { document.getElementById('pagination-next').classList.remove('disabled'); document.getElementById('pagination-last').classList.remove('disabled'); } else { document.getElementById('pagination-next').classList.add('disabled'); document.getElementById('pagination-last').classList.add('disabled'); } if (Page > 1 && PageCount > 1) { document.getElementById('pagination-prev').classList.remove('disabled'); document.getElementById('pagination-first').classList.remove('disabled'); } else { document.getElementById('pagination-prev').classList.add('disabled'); document.getElementById('pagination-first').classList.add('disabled'); } document.getElementById('pagination-current').innerText = Page Items.assets.forEach(item => { const ItemColumn = document.createElement('div') ItemColumn.classList = 'col-auto' ItemColumn.innerHTML = `