diff --git a/assets/js/AdminBoard.js b/assets/js/AdminBoard.js new file mode 100644 index 0000000..f94af72 --- /dev/null +++ b/assets/js/AdminBoard.js @@ -0,0 +1,756 @@ +// NOTE - Change isTestMode to false prior to actual release ---- !important - You may also change identifier if you want to not show older cards. +const isEncryptedTestMode = true; +const encryptedCardIdentifierPrefix = "test-MDC"; +let isExistingEncryptedCard = false; +let existingDecryptedCardData = {}; +let existingEncryptedCardIdentifier = {}; +let cardMinterName = {} +let existingCardMinterNames = [] + +console.log("Attempting to load AdminBoard.js"); + +const loadAdminBoardPage = async () => { + // Clear existing content on the page + const bodyChildren = document.body.children; + for (let i = bodyChildren.length - 1; i >= 0; i--) { + const child = bodyChildren[i]; + if (!child.classList.contains("menu")) { + child.remove(); + } + } + + // Add the "Minter Board" content + const mainContent = document.createElement("div"); + mainContent.innerHTML = ` +
The Admin Board is an encrypted card publishing board to keep track of minter data for the Minter Admins. Any Admin may publish a card, and related data, make comments on existing cards, and vote on existing card data in support or not of the name on the card. It is essentially a 'project management' tool to assist the Minter Admins in keeping track of the data related to minters they are adding/removing from the minter group.
+More functionality will be added over time. One of the first features will be the ability to output the existing card data 'decisions', to a json formatted list in order to allow crowetic to run his script easily until the final Mintership proposal changes are completed, and the MINTER group is transferred to 'null'.
+ + + + +Refreshing cards...
" + await fetchAllEncryptedCards() + }) + } + + const cancelPublishButton = document.getElementById("cancel-publish-button") + if (cancelPublishButton) { + cancelPublishButton.addEventListener("click", async () => { + const encryptedCardsContainer = document.getElementById("encrypted-cards-container") + encryptedCardsContainer.style.display = "flex"; // Restore visibility + const publishCardView = document.getElementById("publish-card-view") + publishCardView.style.display = "none"; // Hide the publish form + }) + } + const addLinkButton = document.getElementById("add-link-button") + if (addLinkButton) { + addLinkButton.addEventListener("click", async () => { + const linksContainer = document.getElementById("links-container") + const newLinkInput = document.createElement("input") + newLinkInput.type = "text" + newLinkInput.className = "card-link" + newLinkInput.placeholder = "Enter QDN link" + linksContainer.appendChild(newLinkInput) + }) + } + + document.getElementById("publish-card-form").addEventListener("submit", async (event) => { + event.preventDefault(); + await publishEncryptedCard(); + }); + + await createCardMinterNameList(); + await fetchAllEncryptedCards(); +} + +const extractCardsMinterName = (cardIdentifier) => { + // Ensure the identifier starts with the prefix + if (!cardIdentifier.startsWith(`${encryptedCardIdentifierPrefix}-`)) { + throw new Error('Invalid identifier format or prefix mismatch'); + } + + // Split the identifier into parts + const parts = cardIdentifier.split('-'); + + // Ensure the format has at least 3 parts + if (parts.length < 3) { + throw new Error('Invalid identifier format'); + } + + // Extract minterName (everything from the second part to the second-to-last part) + const minterName = parts.slice(2, -1).join('-'); + + // Return the extracted minterName + return minterName; +} + + +const createCardMinterNameList = async () => { + + const response = await qortalRequest({ + action: "SEARCH_QDN_RESOURCES", + service: "MAIL_PRIVATE", + query: `${encryptedCardIdentifierPrefix}`, + mode: "ALL", + }); + + const validatedEncryptedCards = await Promise.all( + response.map(async card => { + const isValid = await validateEncryptedCardIdentifier(card); + return isValid ? card : null; + }) + ) + + const validEncryptedCards = validatedEncryptedCards.filter(card => card !== null); + + if (validEncryptedCards.length === 0) { + console.log(`no matches found, not adding any names to name list.`) + return; + } + + for (const result of validEncryptedCards) { + const minterName = await extractCardsMinterName(result.identifier) + + if (!existingCardMinterNames.includes(minterName)) { + existingCardMinterNames.push(minterName) + console.log(`cardsMinterName: ${minterName} - added to list`) + } + } +}; + +//Main function to load the Minter Cards ---------------------------------------- +const fetchAllEncryptedCards = async () => { + const encryptedCardsContainer = document.getElementById("encrypted-cards-container"); + encryptedCardsContainer.innerHTML = "Loading cards...
"; + + try { + const response = await qortalRequest({ + action: "SEARCH_QDN_RESOURCES", + service: "MAIL_PRIVATE", + query: encryptedCardIdentifierPrefix, + mode: "ALL" + }); + + if (!response || !Array.isArray(response) || response.length === 0) { + encryptedCardsContainer.innerHTML = "No cards found.
"; + return; + } + + // Validate cards and filter + const validatedEncryptedCards = await Promise.all( + response.map(async card => { + const isValid = await validateEncryptedCardIdentifier(card); + return isValid ? card : null; + }) + ); + + const validEncryptedCards = validatedEncryptedCards.filter(card => card !== null); + + if (validEncryptedCards.length === 0) { + encryptedCardsContainer.innerHTML = "No valid cards found.
"; + return; + } + + // Group by identifier and keep only the newest card for each identifier + const latestCardsMap = new Map(); + + validEncryptedCards.forEach(card => { + const timestamp = card.updated || card.created || 0; + const existingCard = latestCardsMap.get(card.identifier); + + if (!existingCard || timestamp > (existingCard.updated || existingCard.created || 0)) { + latestCardsMap.set(card.identifier, card); + } + }); + + // Extract unique cards and sort by timestamp descending + const uniqueValidCards = Array.from(latestCardsMap.values()).sort((a, b) => { + const timestampA = a.updated || a.created || 0; + const timestampB = b.updated || b.created || 0; + return timestampB - timestampA; + }); + + // Display skeleton cards immediately + encryptedCardsContainer.innerHTML = ""; + uniqueValidCards.forEach(card => { + const skeletonHTML = createSkeletonCardHTML(card.identifier); + encryptedCardsContainer.insertAdjacentHTML("beforeend", skeletonHTML); + }); + + // Fetch and update each card + uniqueValidCards.forEach(async card => { + try { + const cardDataResponse = await qortalRequest({ + action: "FETCH_QDN_RESOURCE", + name: card.name, + service: "MAIL_PRIVATE", + identifier: card.identifier, + encoding: "base64" + }); + + if (!cardDataResponse) { + console.warn(`Skipping invalid card: ${JSON.stringify(card)}`); + removeSkeleton(card.identifier); + return; + } + + const decryptedCardData = await decryptAndParseObject(cardDataResponse); + + // Skip cards without polls + if (!decryptedCardData.poll) { + console.warn(`Skipping card with no poll: ${card.identifier}`); + removeSkeleton(card.identifier); + return; + } + + // Fetch poll results + const pollResults = await fetchPollResults(decryptedCardData.poll); + const minterNameFromIdentifier = await extractCardsMinterName(card.identifier); + + // Generate final card HTML + const finalCardHTML = await createEncryptedCardHTML(decryptedCardData, pollResults, card.identifier); + replaceEncryptedSkeleton(card.identifier, finalCardHTML); + } catch (error) { + console.error(`Error processing card ${card.identifier}:`, error); + removeEncryptedSkeleton(card.identifier); // Silently remove skeleton on error + } + }); + + } catch (error) { + console.error("Error loading cards:", error); + encryptedCardsContainer.innerHTML = "Failed to load cards.
"; + } +}; + + +const removeEncryptedSkeleton = (cardIdentifier) => { + const encryptedSkeletonCard = document.getElementById(`skeleton-${cardIdentifier}`); + if (encryptedSkeletonCard) { + encryptedSkeletonCard.remove(); // Remove the skeleton silently + } +}; + +const replaceEncryptedSkeleton = (cardIdentifier, htmlContent) => { + const encryptedSkeletonCard = document.getElementById(`skeleton-${cardIdentifier}`); + if (encryptedSkeletonCard) { + encryptedSkeletonCard.outerHTML = htmlContent; + } +}; + +// Function to create a skeleton card +const createEncryptedSkeletonCardHTML = (cardIdentifier) => { + return ` +${header}
+Published by: ${creator} on ${formattedDate}
+
${decryptedCommentData.creator}:
+${decryptedCommentData.content}
+${timestamp}
+