33 files changed - update + hoarders list

- Added new feature: "Collectibles' Hoarders List"

[ IMPROVEMENTS ]

- Reorganized resources folder to be in the root directory rather than a sub-folder of the javascript folder

- Added a new feature: "Quick Library Downloads"

- Basically finished the "Event Items Store Category" feature

- Added more manifest.json information
This commit is contained in:
Index 2024-04-25 17:04:38 -05:00
parent 8b20599662
commit 55ad153d2b
27 changed files with 693 additions and 267 deletions

View file

@ -1,60 +0,0 @@
@font-face {
font-family: LexendRegular;
src: url('Lexend-Regular.ttf');
}
@font-face {
font-family: LexendLight;
src: url('Lexend-Light.ttf');
}
@font-face {
font-family: LexendBold;
src: url('Lexend-Bold.ttf');
}
@font-face {
font-family: LexendBlack;
src: url('Lexend-Black.ttf');
}
body {
margin: 0;
padding: 12px;
font-family: LexendRegular;
background-color: #242424;
color: white;
}
.button {
border-radius: 20px;
background-color: #fff;
color: #424242;
width: 95%;
padding: 10px;
border: none;
font-weight: bold;
text-decoration: none;
}
input {
border-radius: 20px;
background-color: #fff;
color: #007bff;
width: 95%;
padding: 10px;
border: none;
font-weight: bold;
}
h1,.h1 {
font-family: LexendBlack;
}
h2,.h2 {
font-family: LexendBold;
}
hr {
border-color: transparent;
}

View file

@ -1,3 +1,7 @@
/*
FRONTEND FIXES / SPACING IMPROVEMENTS
*/
body[data-URL^="/my/friends"] .col-lg-3 {
margin-bottom: 20px;
}
@ -14,4 +18,32 @@ body[data-URL^="/create/"] .col.d-flex.align-content-between.flex-wrap {
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
}
}
/* ------------------------------------------ */
/*
INLINE EDITING
*/
body[data-polyplus-inlineEditing="true"] .polyplus-inlineEditing-visible {display: block !important;}
.polyplus-inlineEditing-visible {display: none;}
body[data-polyplus-inlineEditing="true"] .polyplus-inlineEditing-hidden {display: none !important;}
.polyplus-inlineEditing-hidden {display: block;}
/* ------------------------------------------ */
/*
MODALS
*/
html:has(.polyplus-modal[open]), body:has(.polyplus-modal[open]) {
overflow: hidden;
}
.polyplus-modal::backdrop {
background: rgba(0, 0, 0, 0.73);
}
/* ------------------------------------------ */

View file

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View file

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

View file

@ -42,7 +42,7 @@ let ItemCardContents = `
if (new URLSearchParams(new URL(window.location).search).get('sandbox') === 'true') {
console.log('Avatar Sandbox!')
LoadFile(chrome.runtime.getURL('js/resources/avatar-sandbox.html'), function(html){
LoadFile(chrome.runtime.getURL('resources/avatar-sandbox.html'), function(html){
PageContainer.innerHTML = html
ItemGrid = document.getElementById('inventory')
Wearing = document.getElementById('wearing')

View file

@ -27,7 +27,7 @@ let Avatar = {
if (new URLSearchParams(new URL(window.location).search).get('sandbox') === 'true') {
console.log('Avatar Sandbox!')
LoadFile(chrome.runtime.getURL('js/resources/avatar-sandbox.html'), function(html){
LoadFile(chrome.runtime.getURL('resources/avatar-sandbox.html'), function(html){
PageContainer.innerHTML = html
ItemGrid = document.getElementById('inventory')
Wearing = document.getElementById('wearing')

View file

@ -172,7 +172,7 @@ SecondaryColumn.insertBefore(NewTitle, SecondaryColumn.children[0]);
async function IRLPrice() {
(async () => {
Utilities = await import(chrome.runtime.getURL('/js/resources/utils.js'));
Utilities = await import(chrome.runtime.getURL('resources/utils.js'));
Utilities = Utilities.default
const TrendingItems = document.getElementById('home-trendingItems')

View file

@ -10,41 +10,46 @@ let CalculateButton;
let Utilities;
if (UserID && !isNaN(UserID)) {
chrome.storage.sync.get(['PolyPlus_Settings'], function(result) {
Settings = result.PolyPlus_Settings || {}
if (Settings.IRLPriceWithCurrencyOn === true) {
(async () => {
Utilities = await import(chrome.runtime.getURL('/js/resources/utils.js'));
Utilities = Utilities.default
(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 || {}
if (Settings.IRLPriceWithCurrencyOn === true) {
IRLPrice()
})();
}
if (Settings.BestFriendsOn === true) {
BestFriends()
}
}
if (Settings.BestFriendsOn === true) {
BestFriends()
}
if (Settings.OutfitCostOn === true) {
CalculateButton = document.createElement('small')
CalculateButton.classList = 'fw-normal text-success'
CalculateButton.style.letterSpacing = '0px'
CalculateButton.innerHTML = `
<a class="text-decoration-underline text-success" style="text-decoration-color: rgb(15, 132, 79) !important;">$ calculate</a>
`
AvatarHeading.appendChild(CalculateButton)
let Calculating = false
CalculateButton.addEventListener('click', function(){
if (Calculating === false) {
Calculating = true
CalculateButton.innerText = '$ Calculating...'
OutfitCost()
}
});
}
});
if (Settings.OutfitCostOn === true) {
CalculateButton = document.createElement('small')
CalculateButton.classList = 'fw-normal text-success'
CalculateButton.style.letterSpacing = '0px'
CalculateButton.setAttribute('data-bs-toggle', 'tooltip')
CalculateButton.setAttribute('data-bs-title', 'Calculate this avatar\'s cost!')
CalculateButton.setAttribute('data-bs-placement', 'right')
CalculateButton.innerHTML = `
<a class="text-decoration-underline text-success" style="text-decoration-color: rgb(15, 132, 79) !important;">$ calculate</a>
`
AvatarHeading.appendChild(CalculateButton)
Utilities.InjectResource('registerTooltips')
let Calculating = false
CalculateButton.addEventListener('click', function(){
if (Calculating === false) {
Calculating = true
CalculateButton.innerText = '$ Calculating...'
OutfitCost()
}
});
}
});
})();
const AvatarIFrame = document.querySelector('[src^="/ptstatic"]')
const DropdownMenu = document.getElementsByClassName('dropdown-menu dropdown-menu-right')[0]

View file

@ -1,7 +1,7 @@
/*
let Currencies;
LoadFile(chrome.runtime.getURL('js/resources/currencies.json'), function(text){
LoadFile(chrome.runtime.getURL('resources/currencies.json'), function(text){
Currencies = JSON.parse(text)
console.log(new Date(Currencies.Date).toLocaleDateString("en-US", {day:"numeric",month:"long",year:"numeric"}), Currencies)
})
@ -9,7 +9,7 @@ LoadFile(chrome.runtime.getURL('js/resources/currencies.json'), function(text){
let Utilities;
(async () => {
Utilities = await import(chrome.runtime.getURL('/js/resources/utils.js'));
Utilities = await import(chrome.runtime.getURL('resources/utils.js'));
Utilities = Utilities.default
})();

View file

@ -1,6 +1,14 @@
const Manifest = chrome.runtime.getManifest()
const SettingsURL = chrome.runtime.getURL('settings.html')
/*
ON INSTALL:
chrome.runtime.onInstalled.addListener(() => {
chrome.tabs.create({url: 'https://polyplus.vercel.app/app/welcome.html'})
});
*/
// WHEN CLICKING ON EXTENSION ICON OPEN THE SETTINGS PAGE
chrome.action.onClicked.addListener((tab) => {
chrome.tabs.create({ active: true, url: SettingsURL });
@ -143,6 +151,20 @@ chrome.contextMenus.onClicked.addListener(function (info, tab){
}
});
chrome.runtime.onMessage.addListener(function (message, sender) {
console.log('hi')
message = message.message || ''
console.log(message)
if (message === 'tooltip') {
console.log('is about tooltip')
chrome.scripting
.executeScript({
target: {tabId: sender.tab.id},
func: UpdateTooltips
})
}
});
/*
GREEN LOGO WHEN EXTENSION APPLIES TO CURRENT TAB PAGE, RED WHEN IT DOESN'T
COMING SOON
@ -196,4 +218,13 @@ function CopyAvatarHash(hash) {
.catch(() => {
alert('Failure to copy avatar hash.')
});
}
function UpdateTooltips() {
const Script = document.createElement('script')
Script.innerHTML = `
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))
`
document.body.appendChild(Script)
}

View file

@ -155,7 +155,7 @@ document.getElementById('edit-setting').addEventListener('click', function(){
});
document.getElementById('reset-settings').addEventListener('click', async function(){
let Utilities = await import(chrome.runtime.getURL('/js/resources/utils.js'))
let Utilities = await import(chrome.runtime.getURL('resources/utils.js'))
Utilities = Utilities.default
chrome.storage.sync.set({ 'PolyPlus_Settings': Utilities.DefaultSettings }, function() {
alert('Successfully reset settings to their defaults!')

View file

@ -1,16 +1,8 @@
var Settings;
let Theme = `
html:has(.polyplus-modal[open]), body:has(.polyplus-modal[open]) {
overflow: hidden;
}
.polyplus-modal::backdrop {
background: rgba(0, 0, 0, 0.73);
}
`;
let Theme = null;
(async () => {
let Utilities = await import(chrome.runtime.getURL('/js/resources/utils.js'));
let Utilities = await import(chrome.runtime.getURL('resources/utils.js'));
Utilities = Utilities.default
chrome.storage.sync.get(["PolyPlus_Settings"], function(result) {

View file

@ -24,7 +24,7 @@ function ForumMentions() {
let match;
while ((match = Regex.exec(text.innerText)) !== null) {
const Username = match[0].substring(1)
FormattedText = FormattedText.replaceAll(match[0], `<a href="/profile/${Username}?ref=${encodeURIComponent(window.location.pathname)}" class="polyplus-mention">${match[0]}</a>`)
FormattedText = FormattedText.replaceAll(match[0], `<a href="/users/@${Username}?ref=${encodeURIComponent(window.location.pathname)}" class="polyplus-mention">${match[0]}</a>`)
}
text.innerHTML = FormattedText
}

View file

@ -8,7 +8,7 @@ chrome.storage.sync.get(['PolyPlus_Settings'], function(result){
if (Settings.IRLPriceWithCurrencyOn === true) {
(async () => {
Utilities = await import(chrome.runtime.getURL('/js/resources/utils.js'));
Utilities = await import(chrome.runtime.getURL('resources/utils.js'));
Utilities = Utilities.default
for (let item of Array.from(StoreItems.children)) {

59
js/library-download.js Normal file
View file

@ -0,0 +1,59 @@
const AssetID = window.location.pathname.split('/')[2]
const LibraryType = document.querySelectorAll('ol a')[1].innerText
const LibraryTypes = [
"Models",
"Audio",
"Decal",
"Mesh"
]
var Success = true;
if (LibraryTypes.indexOf(LibraryType) !== -1) {
chrome.storage.sync.get(['PolyPlus_Settings'], function(result){
Settings = result.PolyPlus_Settings || {};
if (Settings.LibraryDownloadsOn === false) { return }
const Dropdown = document.querySelector('#app div[style] .dropdown-menu li')
const DownloadLink = document.createElement('a')
DownloadLink.classList = 'dropdown-item text-warning'
DownloadLink.href = '#'
DownloadLink.innerHTML = `<i class="fa-duotone fa-download"></i> Download`
Dropdown.insertBefore(DownloadLink, Dropdown.children[Dropdown.children.length-1])
switch(LibraryType) {
case 'Models':
DownloadLink.href = 'https://api.polytoria.com/v1/models/get-model?id=' + AssetID
break
case 'Audio':
const AudioBlob = new Blob([document.getElementsByTagName('audio')[0]], {type: 'octet-steam'})
DownloadLink.href = URL.createObjectURL(AudioBlob)
DownloadLink.download = document.getElementsByTagName('h1')[0].innerText + '.mp3'
case 'Decal':
const DecalBlob = new Blob([document.getElementsByClassName('store-thumbnail')[0]], {type: 'image/png'})
DownloadLink.href = URL.createObjectURL(DecalBlob)
DownloadLink.download = document.getElementsByTagName('h1')[0].innerText + '.png'
break
}
if (LibraryType === 'Mesh') {
let MeshURL = null
DownloadLink.addEventListener('click', async function(){
if (MeshURL !== null) { return }
MeshURL = await fetch('https://api.polytoria.com/v1/assets/serve-mesh/' + AssetID)
MeshURL = await MeshURL.json()
if (MeshURL.success === true) {
DownloadLink.href = MeshURL.url
DownloadLink.download = document.getElementsByTagName('h1')[0].innerText + '.glb'
DownloadLink.click()
} else {
alert('Failure to fetch .glb file for mesh')
}
})
}
});
}

View file

@ -48,8 +48,9 @@ function LoadCreatorTag(element) {
Tag.style.marginLeft = '5px'
Tag.style.verticalAlign = 'text-top'
Tag.innerText = 'CREATOR'
if (Type === 'guilds') { Tag.innerText = 'LEADER' }
NameElement.appendChild(Tag)
//console.log(window.bootstrap)
//new window.bootstrap.Tooltip(Tag, {toggle:"tooltip",title:"This user is the creator of this asset!"})
}
}

View file

@ -12,7 +12,9 @@ let InfoColumns = document.querySelectorAll('#main-content .col:has(#likes-data-
let CalculateRevenueButton;
const AchievementsTab = document.getElementById('achievements-tabpane')
const Achievements = Array.from(AchievementsTab.getElementsByClassName('card'))
const GamepassesTab = document.getElementById('gamepasses-tabpane')
const Achievements = Array.from(AchievementsTab.getElementsByClassName('card')) || []
const Gamepasses = Array.from(GamepassesTab.getElementsByClassName('card')) || []
!(() => {
if (PlaceID === undefined) { return }
@ -42,30 +44,29 @@ const Achievements = Array.from(AchievementsTab.getElementsByClassName('card'))
chrome.storage.sync.get(['PolyPlus_Settings'], async function(result) {
Settings = result.PolyPlus_Settings || {}
Utilities = await import(chrome.runtime.getURL('resources/utils.js'));
Utilities = Utilities.default
if (Settings.PinnedGamesOn === true) {
PinnedGames()
}
// Work in Progress
if (Settings.InlineEditingOn === true) {
if (Settings.InlineEditingOn === true && GameCreator === UserID) {
InlineEditing()
}
// Work in Progress
/*
if (Settings.GameProfilesOn === true) {
GameProfiles()
const Description = document.querySelector('.col:has(#likes-data-container) .card.mcard.mb-2 .card-body.p-3.small')
if (Settings.GameProfilesOn === true && Description !== null) {
const GameProfileRegex = /p\+gp;(#(?:[A-Fa-f0-9]{3}){1,2}\b(;#(?:[A-Fa-f0-9]{3}){1,2}\b)+)/gm
if (GameProfileRegex.test(Description.innerText)) {
const Info = GameProfileRegex.exec(Description.innerText)[1].split(';')
GameProfile(Info)
}
}
*/
if (Settings.IRLPriceWithCurrencyOn === true) {
(async () => {
Utilities = await import(chrome.runtime.getURL('/js/resources/utils.js'));
Utilities = Utilities.default
IRLPrice()
})();
IRLPrice()
}
if (Settings.ShowPlaceRevenueOn === true) {
@ -108,22 +109,7 @@ const Achievements = Array.from(AchievementsTab.getElementsByClassName('card'))
async function PinnedGames() {
chrome.storage.sync.get(['PolyPlus_PinnedGames'], function(result){
PinnedGamesData = result.PolyPlus_PinnedGames || [];
/*
const PinBtn = document.createElement('button');
PinBtn.classList = 'btn btn-warning btn-sm';
PinBtn.style = 'position: absolute; right: 0; margin-right: 7px;'
if (PinnedGamesData[PlaceID]) {
PinBtn.innerHTML = '<i class="fa-duotone fa-star"></i> Un-pin';
} else {
if (PinnedGames.length !== 5) {
PinBtn.innerHTML = '<i class="fa-duotone fa-star"></i> Pin'
} else {
PinBtn.setAttribute('disabled', true)
PinBtn.innerHTML = '<i class="fa-duotone fa-star"></i> Pin (max 5/5)'
}
}
*/
const PinBtn = document.createElement('button');
PinBtn.classList = 'btn btn-warning btn-sm';
PinBtn.style = 'position: absolute; right: 0; margin-right: 7px;'
@ -131,11 +117,11 @@ async function PinnedGames() {
if (PinnedGamesData.includes(parseInt(PlaceID))) {
PinBtn.innerHTML = '<i class="fa-duotone fa-star"></i> Un-pin';
} else {
if (PinnedGamesData.length !== 5) {
if (PinnedGamesData.length !== Utilities.Limits.PinnedGames) {
PinBtn.innerHTML = '<i class="fa-duotone fa-star"></i> Pin'
} else {
PinBtn.setAttribute('disabled', true)
PinBtn.innerHTML = '<i class="fa-duotone fa-star"></i> Pin (max 5/5)'
PinBtn.innerHTML = `<i class="fa-duotone fa-star"></i> Pin (max ${Utilities.Limits.PinnedGames}/${Utilities.Limits.PinnedGames})`
}
}
@ -197,12 +183,12 @@ async function PinnedGames() {
if (PinnedGamesData.includes(parseInt(PlaceID))) {
PinBtn.innerHTML = '<i class="fa-duotone fa-star"></i> Un-pin'
} else {
if (PinnedGamesData.length !== 5) {
if (PinnedGamesData.length !== Utilities.Limits.PinnedGames) {
PinBtn.removeAttribute('disabled')
PinBtn.innerHTML = '<i class="fa-duotone fa-star"></i> Pin'
} else {
PinBtn.setAttribute('disabled', true)
PinBtn.innerHTML = '<i class="fa-duotone fa-star"></i> Pin (max 5/5)'
PinBtn.innerHTML = `<i class="fa-duotone fa-star"></i> Pin (max ${Utilities.Limits.PinnedGames}/${Utilities.Limits.PinnedGames})`
}
}
});
@ -212,14 +198,11 @@ async function PinnedGames() {
}
async function InlineEditing() {
// Fix description editing
// Make it possible to edit description even if the game doesn't initially have a description
// Add the ability to edit the game's genre
// Improve editing visuals overall
if (GameCreator !== UserID) {
return
}
/*
INLINE EDITING TO-DO:
- Make it possible to edit the description even if there is no description initially
- Make it possible to edit the place's genre
*/
let Editing = false
@ -344,9 +327,7 @@ async function InlineEditing() {
});
}
async function GameProfiles(data) {
return
data = Data
async function GameProfile(data) {
document.querySelector('h1.my-0')
.setAttribute('game-key', 'true');
document.querySelector('div[style="min-height: 60vh;"]')
@ -356,16 +337,16 @@ async function GameProfiles(data) {
Style.innerHTML = `
div#app {
background: ${Data.bg} !important;
background: ${data[0]} !important;
}
#gameprofile {
/*font-family: ${Data.font} !important;*/
color: ${Data.text} !important;
/*font-family: no !important;*/
color: ${data[4]} !important;
}
#gameprofile .card {
--bs-card-bg: ${Data.cardBg};
--bs-card-bg: ${data[3]};
}
/*
@ -376,7 +357,7 @@ async function GameProfiles(data) {
*/
#gameprofile .card.mcard [game-key] {
background: linear-gradient(to bottom, ${Data.accent}, ${Data.secondary});
background: linear-gradient(to bottom, ${data[1]}, ${data[2]});
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;

View file

@ -1,3 +1,8 @@
/*
THIS FILE DOES NOT RUN
IT WAS ADDED WHILE I CONSIDER WHETHER OR NOT TO ADD PINNED GAMES TO THE MAIN PLACES PAGE
*/
let Settings;
let PinnedGamesData;

View file

@ -23,7 +23,7 @@ var ItemOwned;
(async () => {
if (!(window.location.href.split('/')[4]) || ItemType === "achievement") {return}
Utilities = await import(chrome.runtime.getURL('/js/resources/utils.js'));
Utilities = await import(chrome.runtime.getURL('resources/utils.js'));
Utilities = Utilities.default
chrome.storage.sync.get(['PolyPlus_Settings'], async function(result){
@ -56,6 +56,10 @@ var ItemOwned;
Sales.children[1].innerText = Owners.toLocaleString()
}
}
if (Settings.HoardersListOn === true && document.getElementById('resellers') !== null) {
HoardersList()
}
})
})();
@ -179,12 +183,6 @@ function TryOnItems() {
}
let AssetType = document.querySelector('.px-4.px-lg-0.text-muted.text-uppercase.mb-3 .badge').innerHTML
console.log(AssetType, MeshTypes[AssetType], MeshTypes[AssetType.toLowerCase()])
/*
if (HatTypes[AssetType.toLowerCase()] !== undefined) {
AssetType = "hat"
}
*/
const ItemThumbnail = document.getElementsByClassName('store-thumbnail')[0]
const IFrame = document.getElementsByClassName('store-thumbnail-3d')[0]
@ -194,6 +192,8 @@ function TryOnItems() {
const TryOnBtn = document.createElement('button')
TryOnBtn.classList = 'btn btn-outline-warning'
TryOnBtn.style = 'position: absolute; bottom: 60px; right: 10px;'
TryOnBtn.setAttribute('data-bs-toggle', 'tooltip')
TryOnBtn.setAttribute('data-bs-title', 'Try this item on your avatar')
TryOnBtn.innerHTML = '<i class="fa-duotone fa-vial"></i>'
TryOnBtn.addEventListener('click', function (){
TryOnModal.showModal()
@ -205,7 +205,7 @@ function TryOnItems() {
TryOnModal.innerHTML = `
<div class="text-muted mb-2" style="font-size: 0.8rem;">
<h5 class="mb-0" style="color: #fff;">Preview</h5>
Try on this item!
Try this avatar on your avatar before purchasing it!
</div>
<div class="modal-body">
<button class="btn btn-primary w-100 mx-auto" onclick="this.parentElement.parentElement.close();">Close</button>
@ -215,6 +215,8 @@ function TryOnItems() {
document.body.prepend(TryOnModal)
ItemThumbnail.parentElement.appendChild(TryOnBtn)
TryOnModal.children[1].prepend(TryIFrame)
Utilities.InjectResource('registerTooltips')
fetch("https://api.polytoria.com/v1/users/:id/avatar".replace(':id', JSON.parse(window.localStorage.getItem('account_info')).ID))
.then(response => {
@ -282,15 +284,15 @@ function TryOnItems() {
})
.then(data => {
switch (AssetType) {
case 'shirt':
Avatar.shirt = data.url
break
case 'pants':
Avatar.pants = data.url
break
case 'face':
Avatar.face = data.url
break
case 'shirt':
Avatar.shirt = data.url
break
case 'pants':
Avatar.pants = data.url
break
case 'face':
Avatar.face = data.url
break
}
TryIFrame.src = 'https://polytoria.com/ptstatic/itemview/#' + btoa(encodeURIComponent(JSON.stringify(Avatar)))
@ -298,9 +300,229 @@ function TryOnItems() {
.catch(error => {
console.error('Fetch error:', error);
});
}
}
})
.catch(error => {
console.error('Fetch error:', error);
console.error('Fetch error:', error);
});
}
async function HoardersList() {
let Page = 0
const Tabs = document.getElementById('store-tabs')
const Owners = (await (await fetch('https://api.polytoria.com/v1/store/' + ItemID + '/owners?limit=100')).json()).inventories
const Formatted = {}
for (let owner of Owners) {
if (Formatted[owner.user.id] !== undefined) {
Formatted[owner.user.id].copies++
Formatted[owner.user.id].serials.push(owner.serial)
} else {
Formatted[owner.user.id] = {
user: owner.user,
copies: 1,
serials: [owner.serial]
}
}
}
const Hoarders = new Promise(async (resolve, reject) => {
const Sorted = Object.values(Formatted).filter((x, index) => x.copies >= 2).sort((a, b) => b.copies - a.copies)
for (let hoarder of Sorted) {
const Avatar = (await (await fetch('https://api.polytoria.com/v1/users/' + hoarder.user.id)).json()).thumbnail.icon;
hoarder.user.avatar = Avatar;
if (Sorted.indexOf(hoarder) === Sorted.length-1) { resolve(Sorted) }
}
})
Hoarders.then(async (hoarders) => {
const AmountOfHoarders = hoarders.length
const Groups = []
while (hoarders.length > 0) {
Groups.push(hoarders.splice(0, 4))
}
const Tab = document.createElement('li')
Tab.classList = 'nav-item'
Tab.innerHTML = `
<a class="nav-link">
<i class="fas fa-calculator me-1"></i>
<span class="d-none d-sm-inline"><span class="pilltitle">Hoarders (${AmountOfHoarders})</span>
</a>
`
Tabs.appendChild(Tab)
const TabContent = document.createElement('div')
TabContent.classList = 'd-none'
TabContent.innerHTML = `
<div id="hoarders-container">
${ Groups[Page].map((x) => `
<div class="card mb-3">
<div class="card-body">
<div class="row">
<div class="col-auto">
<img src="${x.user.avatar}" alt="${x.user.username}" width="72" class="rounded-circle border border-2 border-secondary bg-dark">
</div>
<div class="col d-flex align-items-center">
<div>
<h6 class="mb-1">
<a class="text-reset" href="/users/${x.user.id}">${x.user.username}</a>
</h6>
<small class="text-muted">${x.copies} Copies <i class="fa-solid fa-circle-info" data-bs-toggle="tooltip" data-bs-title="#${x.serials.sort((a, b) => a - b).join(', #')}"></i></small>
</div>
</div>
<div class="col-auto d-flex align-items-center">
<a class="btn btn-warning" type="button" href="/trade/new/${x.user.id}">
<i class="fad fa-exchange-alt me-1"></i>
<span class="d-none d-sm-inline">Trade</span>
</a>
</div>
</div>
</div>
</div>
`).join('')
}
</div>
<nav aria-label="Hoarders">
<ul class="pagination justify-content-center">
<li class="page-item disabled">
<a class="page-link" href="#!" tabindex="-1" id="hoarders-prev-pg">
<i class="fad fa-chevron-left"></i>
</a>
</li>
<li class="page-item active">
<a class="page-link" href="#!" id="hoarders-current-pg">
1
</a>
</li>
<li class="page-item">
<a class="page-link" href="#!" id="hoarders-next-pg">
<i class="fad fa-chevron-right"></i>
</a>
</li>
</ul>
</nav>
`
document.getElementById('owners').parentElement.appendChild(TabContent)
Utilities.InjectResource('registerTooltips')
Array.from(Tabs.children).forEach(tab => {
tab.addEventListener('click', function() {
if (tab === Tab) {
Array.from(Tabs.children).forEach(tab => {tab.children[0].classList.remove('active')})
Array.from(document.getElementById('owners').parentElement.children).forEach(tab => {tab.classList.add('d-none')})
tab.children[0].classList.add('active')
TabContent.classList.remove('d-none')
}
})
})
const Container = document.getElementById('hoarders-container')
const Prev = document.getElementById('hoarders-prev-pg')
const Current = document.getElementById('hoarders-current-pg')
const Next = document.getElementById('hoarders-next-pg')
if (Page > 0) {
Prev.parentElement.classList.remove('disabled')
} else {
Prev.parentElement.classList.add('disabled')
}
if (Page < Groups.length-1) {
Next.parentElement.classList.remove('disabled')
} else {
Next.parentElement.classList.add('disabled')
}
Prev.addEventListener('click', function() {
if (Page > 0) {
Page--
Current.innerText = Page+1
Container.innerHTML = Groups[Page].map((x) => `
<div class="card mb-3">
<div class="card-body">
<div class="row">
<div class="col-auto">
<img src="${x.user.avatar}" alt="${x.user.username}" width="72" class="rounded-circle border border-2 border-secondary bg-dark">
</div>
<div class="col d-flex align-items-center">
<div>
<h6 class="mb-1">
<a class="text-reset" href="/users/${x.user.id}">${x.user.username}</a>
</h6>
<small class="text-muted">${x.copies} Copies <i class="fa-solid fa-circle-info" data-bs-toggle="tooltip" data-bs-title="#${x.serials.sort((a, b) => a - b).join(', #')}"></i></small>
</div>
</div>
<div class="col-auto d-flex align-items-center">
<a class="btn btn-warning" type="button" href="/trade/new/${x.user.id}">
<i class="fad fa-exchange-alt me-1"></i>
<span class="d-none d-sm-inline">Trade</span>
</a>
</div>
</div>
</div>
</div>
`).join('')
Utilities.InjectResource('registerTooltips')
if (Page > 0) {
Prev.parentElement.classList.remove('disabled')
} else {
Prev.parentElement.classList.add('disabled')
}
if (Page < Groups.length-1) {
Next.parentElement.classList.remove('disabled')
} else {
Next.parentElement.classList.add('disabled')
}
}
})
Next.addEventListener('click', function() {
if (Page < Groups.length-1) {
Page++
Current.innerText = Page+1
Container.innerHTML = Groups[Page].map((x) => `
<div class="card mb-3">
<div class="card-body">
<div class="row">
<div class="col-auto">
<img src="${x.user.avatar}" alt="${x.user.username}" width="72" class="rounded-circle border border-2 border-secondary bg-dark">
</div>
<div class="col d-flex align-items-center">
<div>
<h6 class="mb-1">
<a class="text-reset" href="/users/${x.user.id}">${x.user.username}</a>
</h6>
<small class="text-muted">${x.copies} Copies <i class="fa-solid fa-circle-info" data-bs-toggle="tooltip" data-bs-title="#${x.serials.sort((a, b) => a - b).join(', #')}"></i></small>
</div>
</div>
<div class="col-auto d-flex align-items-center">
<a class="btn btn-warning" type="button" href="/trade/new/${x.user.id}">
<i class="fad fa-exchange-alt me-1"></i>
<span class="d-none d-sm-inline">Trade</span>
</a>
</div>
</div>
</div>
</div>
`).join('')
Utilities.InjectResource('registerTooltips')
if (Page > 0) {
Prev.parentElement.classList.remove('disabled')
} else {
Prev.parentElement.classList.add('disabled')
}
if (Page < Groups.length-1) {
Next.parentElement.classList.remove('disabled')
} else {
Next.parentElement.classList.add('disabled')
}
}
})
})
}

View file

@ -1,19 +1,22 @@
const ItemID = window.location.pathname.split('/')[2]
const UserID = JSON.parse(window.localStorage.getItem('account_info')).ID
const ItemGrid = document.getElementById('assets')
const Categories = document.getElementById('store-categories').children[0]
var Settings;
var Inventory = null;
var Utilities;
(async () => {
Utilities = await import(chrome.runtime.getURL('/js/resources/utils.js'));
let EventItemsShown = false
chrome.storage.sync.get(['PolyPlus_Settings'], async function(result){
Settings = result.PolyPlus_Settings || {};
Utilities = await import(chrome.runtime.getURL('resources/utils.js'));
Utilities = Utilities.default
Update()
})();
chrome.storage.sync.get(['PolyPlus_Settings'], function(result){ Settings = result.PolyPlus_Settings || Utilities.DefaultSettings; });
});
async function Update() {
if (Settings.IRLPriceWithCurrencyOn === true) {
@ -30,16 +33,35 @@ async function Update() {
}
if (Settings.EventItemsCatOn === true) {
console.log('is event yay!!')
EventItems()
}
}
let UpdateCount = 0
const observer = new MutationObserver(async function (list){
UpdateCount++
console.log(Settings.EventItemsCatOn, document.getElementById('event-items-pagination'), EventItemsShown, UpdateCount)
if (Settings.EventItemsCatOn === true) {
if (document.getElementById('event-items-pagination') === null) {
ItemGrid.classList.add('itemgrid')
document.getElementById('pagination').style.display = 'block'
document.getElementById('storecat-eventitems').checked = false
} else {
ItemGrid.classList.remove('itemgrid')
document.getElementById('pagination').style.display = 'none'
}
}
for (const record of list) {
for (const element of record.addedNodes) {
if (element.tagName === "DIV" && element.classList.value === 'mb-3 itemCardCont') {
if (Settings.IRLPriceWithCurrencyOn === true) { LoadIRLPrices(element) }
if (Settings.StoreOwnTagOn === true) { LoadOwnedTags(element) }
if (Settings.IRLPriceWithCurrencyOn === true) {
LoadIRLPrices(element)
}
if (Settings.StoreOwnTagOn === true && Inventory !== null) {
LoadOwnedTags(element)
}
}
}
observer.observe(ItemGrid, {attributes: false,childList: true,subtree: false});
@ -49,32 +71,33 @@ const observer = new MutationObserver(async function (list){
observer.observe(ItemGrid, {attributes: false,childList: true,subtree: false});
async function LoadIRLPrices(element) {
//if (element.tagName !=)
//if (element.tagName != "DIV") {return}
if (element.querySelector('small.text-primary')) {return}
if (element.tagName != "DIV" || element.querySelector('small.text-primary')) {return}
const Parent = element.getElementsByTagName('small')[1]
if (Parent.innerText === "") { return }
const Span = document.createElement('span')
Span.classList = 'text-muted polyplus-price-tag'
Span.style.fontSize = '0.7rem'
const Price = Parent.innerText.split(' ')[1]
const IRLResult = await Utilities.CalculateIRL(Price, Settings.IRLPriceWithCurrencyCurrency)
Span.innerText = "($" + IRLResult.result + " " + IRLResult.display + ")"
Parent.appendChild(Span)
if (Parent.innerText !== "") {
const Span = document.createElement('span')
Span.classList = 'text-muted polyplus-price-tag'
Span.style = 'font-size: 0.7rem; font-weight: lighter;'
const Price = Parent.innerText.split(' ')[1]
const IRLResult = await Utilities.CalculateIRL(Price, Settings.IRLPriceWithCurrencyCurrency)
Span.innerText = "($" + IRLResult.result + " " + IRLResult.display + ")"
Parent.appendChild(Span)
}
}
function LoadOwnedTags(element) {
let Item = CheckInventory(parseInt(element.querySelector('[href^="/store/"]').getAttribute('href').split('/')[2]))
if (Item !== null) {
const Tag = document.createElement('span')
Tag.classList = 'badge bg-primary polyplus-own-tag'
Tag.style = 'position: absolute;font-size: 0.7rem;top: 0px;left: 0px;padding: 5.5px;border-top-left-radius: var(--bs-border-radius-lg)!important;border-top-right-radius: 0px;border-bottom-left-radius: 0px;font-size: 0.65rem;'
if (Item.asset.isLimited === false) {
Tag.innerText = "owned"
} else {
Tag.innerHTML = 'owned<br><span class="text-muted" style="font-size: 0.65rem;">#' + Item.serial
}
Tag.classList = `badge ${ (Item.asset.isLimited === false) ? 'bg-primary' : 'bg-warning' } polyplus-own-tag`
Tag.style = 'position: absolute;font-size: 0.9rem;top: 0px;left: 0px;padding: 5.5px;border-top-left-radius: var(--bs-border-radius-lg)!important;border-top-right-radius: 0px;border-bottom-left-radius: 0px;font-size: 0.65rem;'
Tag.innerHTML = "<i class='fas fa-star'></i>"
element.getElementsByTagName('img')[0].parentElement.appendChild(Tag)
if (Item.asset.isLimited === true) {
Tag.setAttribute('data-bs-toggle', 'tooltip')
Tag.setAttribute('data-bs-title', '#' + Item.serial)
Utilities.InjectResource('registerTooltips')
}
}
}
@ -89,8 +112,6 @@ function CheckInventory(id) {
}
function EventItems() {
const Categories = document.getElementById('store-categories').children[0]
const Selector = document.createElement('div')
Selector.classList = 'form-check store-category-check fw-bold'
Selector.style.borderColor = '#B008B0'
@ -100,26 +121,15 @@ function EventItems() {
<i class="fad fa-party-horn"></i> Event Items
</label>
`
Categories.appendChild(Selector)
const CategoryDiv = document.createElement('div')
ItemGrid.parentElement.insertBefore(CategoryDiv, ItemGrid)
Categories.appendChild(Selector)
let EventData = null
let Events = []
let Groups = []
Array.from(Categories.children).forEach(selector => {
if (selector !== Selector) {
selector.children[0].addEventListener('click', function() {
ItemGrid.innerHTML = ``
ItemGrid.classList.add('itemgrid')
})
}
})
let Page = 0
Selector.children[0].addEventListener('click', async function() {
EventItemsEnabled = true
Array.from(Categories.children).forEach(selector => {
selector.classList.remove('active')
})
@ -127,44 +137,137 @@ function EventItems() {
if (EventData === null) {
EventData = await (await fetch('https://polyplus.vercel.app/data/eventItems.json')).json()
Object.values(EventData.eventDetails).forEach((x, index) => {Groups.push({
Object.values(EventData.eventDetails).forEach((x, index) => {Events.push({
...x,
items: EventData.items.filter((x) => x.event === Object.keys(EventData.eventDetails)[index])
})})
while (Events.length > 0) {
Groups.push(Events.splice(0, 3))
Groups.push(Events.splice(0, 5))
}
}
console.log(Groups)
ItemGrid.classList.remove('itemgrid')
ItemGrid.innerHTML = `
${
Groups.map((x, index) => `
<div class="row px-2 px-lg-0" style="animation-delay: 0.24s;">
<div class="col">
<h6 class="dash-ctitle2">${x.date}</h6>
<h5 class="dash-ctitle">${x.name}</h5>
</div>
</div>
<div class="card card-dash mcard mb-3" style="animation-delay: 0.27s;">
<div class="card-body p-0 m-1 scrollFadeContainer">
<div class="d-flex">
${
<div id="p+ei">
${
Groups[Page].map((x, index) => `
<div class="row px-2 px-lg-0" style="animation-delay: 0.24s;">
<div class="col">
<h6 class="dash-ctitle2">${x.date}</h6>
<h5 class="dash-ctitle">${x.name}</h5>
</div>
</div>
<div class="card card-dash mcard mb-3" style="animation-delay: 0.27s;">
<div class="card-body p-0 m-1 scrollFadeContainer">
<div class="d-flex">
${
x.items.map((x) => `
<a href="/store/${x.id}">
<div class="scrollFade card me-2 place-card force-desktop text-center mb-2" style="opacity: 1;">
<div class="card-body">
<img src="${x.thumbnail}" class="place-card-image">
<div>
<div class="mt-2 mb-1 place-card-title">
${x.name}
</div>
</div>
</div>
</div>
</a>
<div class="scrollFade card me-2 place-card force-desktop text-center mb-2" style="opacity: 1;">
<div class="card-body">
<img src="${x.thumbnail}" class="place-card-image">
<div>
<div class="mt-2 mb-1 place-card-title">
${x.name}
</div>
</div>
</div>
</div>
</a>
`).join('')
}
</div>
</div>
</div>
`).join('')
}
</div>
<div class="d-flex justify-content-center mt-3">
<nav id="event-items-pagination">
<ul class="pagination">
<li class="page-item disabled">
<a class="page-link" href="#!" id="p+ei-pagination-first">«</a>
</li>
<li class="page-item disabled">
<a class="page-link" href="#!" id="p+ei-pagination-prev"></a>
</li>
<li class="page-item active">
<a class="page-link">
<span class="visually-hidden">Page</span>
<span id="p+ei-pagination-current">1</span>
</a>
</li>
<li class="page-item">
<a class="page-link" href="#!" id="p+ei-pagination-next"></a>
</li>
<li class="page-item">
<a class="page-link" href="#!" id="p+ei-pagination-last">»</a>
</li>
</ul>
</nav>
</div>
`
const Container = document.getElementById('p+ei')
const Pagination = document.getElementById('event-items-pagination')
//const First = document.getElementById('p+ei-pagination-first')
const Prev = document.getElementById('p+ei-pagination-prev')
const Current = document.getElementById('p+ei-pagination-current')
const Next = document.getElementById('p+ei-pagination-next')
//const Last = document.getElementById('p+ei-pagination-last')
if (Page > 0) {
Prev.parentElement.classList.remove('disabled')
} else {
Prev.parentElement.classList.add('disabled')
}
if (Page < Groups.length-1) {
Next.parentElement.classList.remove('disabled')
} else {
Next.parentElement.classList.add('disabled')
}
Prev.addEventListener('click', function() {
if (Page > 0) {
Page--
UpdateEventItems()
}
})
Next.addEventListener('click', function() {
if (Page < Groups.length-1) {
Page++
UpdateEventItems()
}
})
const UpdateEventItems = function() {
Current.innerText = Page+1
Container.innerHTML = Groups[Page].map((x, index) => `
<div class="row px-2 px-lg-0" style="animation-delay: 0.24s;">
<div class="col">
<h6 class="dash-ctitle2">${x.date}</h6>
<h5 class="dash-ctitle">${x.name}</h5>
</div>
</div>
<div class="card card-dash mcard mb-3" style="animation-delay: 0.27s;">
<div class="card-body p-0 m-1 scrollFadeContainer">
<div class="d-flex">
${
x.items.map((x) => `
<a href="/store/${x.id}">
<div class="scrollFade card me-2 place-card force-desktop text-center mb-2" style="opacity: 1;">
<div class="card-body">
<img src="${x.thumbnail}" class="place-card-image">
<div>
<div class="mt-2 mb-1 place-card-title">
${x.name}
</div>
</div>
</div>
</div>
</a>
`).join('')
}
</div>
@ -172,7 +275,17 @@ function EventItems() {
</div>
</div>
`).join('')
if (Page > 0) {
Prev.parentElement.classList.remove('disabled')
} else {
Prev.parentElement.classList.add('disabled')
}
if (Page < Groups.length-1) {
Next.parentElement.classList.remove('disabled')
} else {
Next.parentElement.classList.add('disabled')
}
}
`
})
}

View file

@ -1,9 +1,12 @@
{
"manifest_version": 3,
"author": "Index",
"name": "Poly+",
"version": "1.11",
"version_name": "Pre-Release Build (stable: v1.1.1)",
"description": "Power-up your Polytoria experience with Poly+! Created by Index.",
"homepage_url": "https://polyplus.vercel.app/",
"download_url": "https://github.com/indexxing/PolyPlus/releases",
"permissions": ["storage", "contextMenus", "tabs", "scripting", "alarms", "notifications"],
"content_scripts": [
{
@ -114,6 +117,11 @@
{
"matches": ["https://polytoria.com/guilds/**"],
"js": ["/js/guilds.js"]
},
{
"matches": ["https://polytoria.com/store/**", "https://polytoria.com/models/**"],
"js": ["/js/library-download.js"]
}
],
"background": {
@ -125,7 +133,7 @@
],
"web_accessible_resources": [
{
"resources": ["js/resources/*"],
"resources": ["resources/*"],
"matches": ["https://polytoria.com/*"]
},
@ -138,16 +146,16 @@
"action": {
"default_title": "Poly+",
"default_icon": {
"16": "/icon.png",
"32": "/icon.png",
"48": "/icon.png",
"128": "/icon.png"
"16": "/icons/icon.png",
"32": "/icons/icon.png",
"48": "/icons/icon.png",
"128": "/icons/icon.png"
}
},
"icons": {
"16": "/icon.png",
"32": "/icon.png",
"48": "/icon.png",
"128": "/icon.png"
"16": "/icons/icon.png",
"32": "/icons/icon.png",
"48": "/icons/icon.png",
"128": "/icons/icon.png"
}
}

View file

@ -0,0 +1,3 @@
var tooltips = document.querySelectorAll('[data-bs-toggle="tooltip"]')
var list = [...tooltips].map(tool => new bootstrap.Tooltip(tool))
console.log(document.currentScript)

View file

@ -2,7 +2,7 @@
HOW TO USE IN CONTENT SCRIPTS:
(async () => {
let Utilities = await import(chrome.runtime.getURL('/js/resources/utils.js'));
let Utilities = await import(chrome.runtime.getURL('resources/utils.js'));
Utilities = Utilities.default
})();
*/
@ -69,11 +69,14 @@ export default {
ShowPlaceRevenueOn: true,
ReplaceItemSalesOn: false
},
Limits: {
PinnedGames: 10
},
CalculateIRL: async function(bricks, to, brickPackage) {
/*
Disabled for now: currency retrieval from currencies.json
const response = await fetch(chrome.runtime.getURL('/js/resources/currencies.json'))
const response = await fetch(chrome.runtime.getURL('resources/currencies.json'))
if (!response.ok) {
throw new Error('Getting currency data failure')
}
@ -141,5 +144,19 @@ export default {
result: Result,
display: Display
}
},
InjectResource: function(path, element) {
// Function by devjin0617 on GitHub
// Gist: https://gist.github.com/devjin0617/3e8d72d94c1b9e69690717a219644c7a
// Slightly modified to use constants and fit the rest of the code style more
// Function only used for registering bootstrap tooltips currently
if (element === undefined) { element = 'body' }
const Node = document.getElementsByTagName(element)[0];
const Script = document.createElement('script');
Script.setAttribute('type', 'text/javascript');
Script.setAttribute('src', chrome.runtime.getURL('resources/' + path + '.js'));
Script.addEventListener('load', function(){Script.remove()})
Node.appendChild(Script);
}
}

View file

@ -50,6 +50,7 @@
p span.title {
font-size: 1.4rem;
font-weight: lighter;
}
span.desc {
@ -319,7 +320,7 @@
</div>
</dialog>
<div id="page">
<h1 class="text-center">Poly+ Settings</h1>
<h1 class="text-center" style="text-shadow: 0px 0px 5px orange; padding-bottom: 5px; margin-bottom: 10px;">Poly+ Settings</h1>
<p class="setting-container" id="pinned-games">
<span class="indicator">&nbsp;</span>
<span class="title">
@ -530,12 +531,29 @@
<br>
<span class="desc">Replace the "Sales" statistic with the "Owners" statistic if the item has 0 sales (most likely meaning it is an item awarded by staff).</span>
</p>
<p class="setting-container" id="hoarders-list">
<span class="indicator">&nbsp;</span>
<span class="title">
Collectibles' Hoarders List
<button class="btn btn-sm toggle-btn" data-setting="HoardersListOn">Toggle</button>
</span>
<br>
<span class="desc">List all the users that have more than 1 copy of a collectible!</span>
</p>
<p class="setting-container" id="hoarders-list">
<span class="indicator">&nbsp;</span>
<span class="title">
Quick Library Downloads
<button class="btn btn-sm toggle-btn" data-setting="LibraryDownloadsOn">Toggle</button>
</span>
<br>
<span class="desc">Quickly download a model (as a <code>.ptmd</code> file), mesh, decal, or sound right from the page showing you details about the asset!</span>
</p>
<div class="card">
<div class="card-body">
<h3>EXPERIMENTAL SETTINGS</h3>
<p>These features are a work in progress.</p>
<hr>
<!--
<p class="setting-container" id="game-profiles">
<span class="indicator">&nbsp;</span>
<span class="title">
@ -549,7 +567,6 @@
<br>
<span style="font-size: 0.8rem; color: orange;">* This feature will be expanded upon in the future.</span>
</p>
-->
<p class="setting-container" id="inline-editing">
<span class="indicator">&nbsp;</span>
<span class="title">

View file

@ -6,7 +6,7 @@ var ExpectedSettings;
var Utilities;
(async () => {
Utilities = await import(chrome.runtime.getURL('/js/resources/utils.js'));
Utilities = await import(chrome.runtime.getURL('resources/utils.js'));
Utilities = Utilities.default
ExpectedSettings = Utilities.DefaultSettings
@ -237,7 +237,7 @@ let CopyThemeJSONBtn = document.getElementById('CopyThemeJSONBtn')
LoadThemeFromJSONBtn.addEventListener('click', function(){
LoadThemeJSON(LoadThemeFromJSONBtn.previousElementSibling.value)
});
document.getElementById('ThemeCreator').getElementsByTagName('button')[1].addEventListener('click', function(){
document.getElementById('theme-creator').getElementsByTagName('button')[1].addEventListener('click', function(){
SaveThemeToJSONInput.value = JSON.stringify(Settings.ThemeCreator)
});
CopyThemeJSONBtn.addEventListener('click', function(){
@ -333,7 +333,7 @@ function CheckForUpdates() {
}
CheckForUpdatesButton.addEventListener('click', CheckForUpdates)
fetch(chrome.runtime.getURL('/js/resources/currencies.json'))
fetch(chrome.runtime.getURL('resources/currencies.json'))
.then(response => {
if (!response.ok) {
throw new Error('Network not ok')