emblem-vault-sdk 2.6.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundle.js +17152 -2396
- package/dist/curated/metadata.json +17149 -2393
- package/dist/index.js +1 -1
- package/dist/utils.js +2 -2
- package/docs/bundle.js +17152 -2396
- package/package.json +1 -1
- package/src/curated/metadata.json +17149 -2393
- package/src/utils.ts +1 -1
- package/utils/compare-metadata.html +603 -25
- package/utils/emblem-site-metadata.json +17149 -2393
- package/utils/sdk-metadata.json +17149 -2393
- package/utils/server.js +132 -0
|
@@ -123,15 +123,24 @@
|
|
|
123
123
|
<h1>Metadata Comparison Tool</h1>
|
|
124
124
|
|
|
125
125
|
<div class="section">
|
|
126
|
-
<h2>Select
|
|
127
|
-
<
|
|
128
|
-
|
|
129
|
-
<
|
|
130
|
-
|
|
126
|
+
<h2>Select Source</h2>
|
|
127
|
+
<div style="margin-bottom: 15px;">
|
|
128
|
+
<label for="collectionSelect">Choose a collection to compare:</label>
|
|
129
|
+
<select id="collectionSelect" onchange="loadComparison('collection')" style="padding: 10px; font-size: 16px; margin-left: 10px;">
|
|
130
|
+
<option value="">Loading collections...</option>
|
|
131
|
+
</select>
|
|
132
|
+
</div>
|
|
133
|
+
<div>
|
|
134
|
+
<label for="traitSelect">Choose a trait file to compare:</label>
|
|
135
|
+
<select id="traitSelect" onchange="loadComparison('trait')" style="padding: 10px; font-size: 16px; margin-left: 10px;">
|
|
136
|
+
<option value="">Loading traits...</option>
|
|
137
|
+
</select>
|
|
138
|
+
</div>
|
|
131
139
|
</div>
|
|
132
140
|
|
|
133
141
|
<div id="status" class="status">Select a collection to begin...</div>
|
|
134
142
|
|
|
143
|
+
<div id="traitVerification" class="section" style="display:none;"></div>
|
|
135
144
|
<div id="summary" class="summary" style="display:none;"></div>
|
|
136
145
|
|
|
137
146
|
<div id="fakeRaresSection" style="display:none;"></div>
|
|
@@ -162,6 +171,190 @@
|
|
|
162
171
|
return props;
|
|
163
172
|
}
|
|
164
173
|
|
|
174
|
+
// Map trait data to Rare Pepe metadata format
|
|
175
|
+
function mapTraitToMetadataFormat(traitItem, projectName) {
|
|
176
|
+
// Detect which format we're dealing with
|
|
177
|
+
const hasTokenId = traitItem.hasOwnProperty('tokenId');
|
|
178
|
+
const hasAsset = traitItem.hasOwnProperty('asset');
|
|
179
|
+
|
|
180
|
+
let mappedData = {
|
|
181
|
+
projectProtocol: "Counterparty"
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
// Map project names to their proper display names and sites
|
|
185
|
+
const projectMappings = {
|
|
186
|
+
'Age_of_Chains': { name: 'Age of Chains', site: 'https://www.ageofchains.com/' },
|
|
187
|
+
'Age_of_Rust': { name: 'Age of Rust', site: 'https://www.ageofrustgame.com/' },
|
|
188
|
+
'Bitgirls': { name: 'Bitgirls', site: 'https://bitcorns.com/' },
|
|
189
|
+
'Force_of_Will': { name: 'Force of Will', site: 'https://forceofwill.cards/' },
|
|
190
|
+
'Oasis_Mining': { name: 'Oasis Mining', site: 'https://www.oasismining.com/' },
|
|
191
|
+
'Sarutobi': { name: 'Sarutobi Island', site: 'https://mandelduck.com/' },
|
|
192
|
+
'Spells_of_Genesis': { name: 'Spells of Genesis', site: 'https://spellsofgenesis.com/' },
|
|
193
|
+
'bitcorn': { name: 'Bitcorn Crops', site: 'https://bitcorns.com/' },
|
|
194
|
+
'memorychain': { name: 'Memorychain', site: 'https://pepecash.cards/' }
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const projectInfo = projectMappings[projectName.replace('.json', '')] || { name: projectName, site: '' };
|
|
198
|
+
mappedData.projectName = projectInfo.name;
|
|
199
|
+
if (projectInfo.site) mappedData.projectSite = projectInfo.site;
|
|
200
|
+
|
|
201
|
+
if (hasTokenId) {
|
|
202
|
+
// Format 1: Age of Chains style (tokenId, assetId, supply, burns)
|
|
203
|
+
const assetName = traitItem.tokenId;
|
|
204
|
+
|
|
205
|
+
mappedData.burned = parseInt(traitItem.burns) || 0;
|
|
206
|
+
mappedData.divisible = 0; // Most NFTs are not divisible
|
|
207
|
+
mappedData.quantity = parseInt(traitItem.supply) || 0;
|
|
208
|
+
mappedData.remaining = mappedData.quantity - mappedData.burned;
|
|
209
|
+
|
|
210
|
+
// Store original data in raw
|
|
211
|
+
mappedData.raw = {
|
|
212
|
+
...traitItem,
|
|
213
|
+
supply: parseInt(traitItem.supply) || 0,
|
|
214
|
+
name: assetName
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// Generate image URL based on project
|
|
218
|
+
const projectFolder = projectName.replace('.json', '').toLowerCase().replace(/_/g, '-');
|
|
219
|
+
mappedData.image = `https://raw.githubusercontent.com/EmblemCompany/vaultImages/master/collection/${projectFolder}/${assetName}.jpg`;
|
|
220
|
+
|
|
221
|
+
} else if (hasAsset) {
|
|
222
|
+
// Format 2: Bitcorn style (asset, series, card_no, quantity, artist_name)
|
|
223
|
+
const assetName = traitItem.asset;
|
|
224
|
+
|
|
225
|
+
mappedData.burned = 0; // No burn info in this format
|
|
226
|
+
mappedData.divisible = traitItem.divisible === true ? 1 : 0;
|
|
227
|
+
mappedData.order = parseInt(traitItem.card_no) || 0;
|
|
228
|
+
mappedData.quantity = parseInt(traitItem.quantity) || 0;
|
|
229
|
+
mappedData.series = parseInt(traitItem.series) || 1;
|
|
230
|
+
mappedData.remaining = mappedData.quantity;
|
|
231
|
+
|
|
232
|
+
// Store original data in raw with artist info
|
|
233
|
+
mappedData.raw = {
|
|
234
|
+
...traitItem,
|
|
235
|
+
name: assetName,
|
|
236
|
+
supply: parseInt(traitItem.quantity) || 0,
|
|
237
|
+
serie: parseInt(traitItem.series) || 1,
|
|
238
|
+
card: parseInt(traitItem.card_no) || 0,
|
|
239
|
+
artist: {
|
|
240
|
+
name: traitItem.artist_name || 'Unknown',
|
|
241
|
+
slug: (traitItem.artist_name || 'Unknown').toLowerCase().replace(/\s+/g, '-')
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
// Use provided image URL or generate one
|
|
246
|
+
if (traitItem.img_url) {
|
|
247
|
+
mappedData.image = traitItem.img_url;
|
|
248
|
+
} else {
|
|
249
|
+
const projectFolder = projectName.replace('.json', '').toLowerCase().replace(/_/g, '-');
|
|
250
|
+
mappedData.image = `https://raw.githubusercontent.com/EmblemCompany/vaultImages/master/collection/${projectFolder}/${assetName}.jpg`;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return mappedData;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function verifyTraitCoverage(traitData, sdk, site) {
|
|
258
|
+
const verification = {
|
|
259
|
+
foundInBoth: [],
|
|
260
|
+
foundInSDKOnly: [],
|
|
261
|
+
foundInSiteOnly: [],
|
|
262
|
+
foundInNone: [],
|
|
263
|
+
propertyMatches: [],
|
|
264
|
+
propertyMismatches: [],
|
|
265
|
+
missingProperties: [],
|
|
266
|
+
total: 0
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
Object.keys(traitData).forEach(key => {
|
|
270
|
+
verification.total++;
|
|
271
|
+
const traitItem = traitData[key];
|
|
272
|
+
const sdkItem = sdk[key];
|
|
273
|
+
const siteItem = site[key];
|
|
274
|
+
const inSDK = sdk.hasOwnProperty(key);
|
|
275
|
+
const inSite = site.hasOwnProperty(key);
|
|
276
|
+
|
|
277
|
+
const itemAnalysis = {
|
|
278
|
+
key: key,
|
|
279
|
+
inSDK: inSDK,
|
|
280
|
+
inSite: inSite,
|
|
281
|
+
traitProps: Object.keys(traitItem).sort(),
|
|
282
|
+
sdkProps: sdkItem ? Object.keys(sdkItem).sort() : [],
|
|
283
|
+
siteProps: siteItem ? Object.keys(siteItem).sort() : [],
|
|
284
|
+
propertyComparison: {}
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
if (inSDK && inSite) {
|
|
288
|
+
verification.foundInBoth.push(key);
|
|
289
|
+
|
|
290
|
+
// Compare properties between trait, SDK, and Site
|
|
291
|
+
const allProps = new Set([
|
|
292
|
+
...itemAnalysis.traitProps,
|
|
293
|
+
...itemAnalysis.sdkProps,
|
|
294
|
+
...itemAnalysis.siteProps
|
|
295
|
+
]);
|
|
296
|
+
|
|
297
|
+
let hasPropertyIssues = false;
|
|
298
|
+
const propertyIssues = {
|
|
299
|
+
key: key,
|
|
300
|
+
missingInSDK: [],
|
|
301
|
+
missingInSite: [],
|
|
302
|
+
valueMismatches: []
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
allProps.forEach(prop => {
|
|
306
|
+
const inTrait = traitItem.hasOwnProperty(prop);
|
|
307
|
+
const inSDKProp = sdkItem && sdkItem.hasOwnProperty(prop);
|
|
308
|
+
const inSiteProp = siteItem && siteItem.hasOwnProperty(prop);
|
|
309
|
+
|
|
310
|
+
// Check if trait property exists in metadata
|
|
311
|
+
if (inTrait) {
|
|
312
|
+
if (!inSDKProp) {
|
|
313
|
+
propertyIssues.missingInSDK.push(prop);
|
|
314
|
+
hasPropertyIssues = true;
|
|
315
|
+
}
|
|
316
|
+
if (!inSiteProp) {
|
|
317
|
+
propertyIssues.missingInSite.push(prop);
|
|
318
|
+
hasPropertyIssues = true;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Check if values match when property exists in all three
|
|
322
|
+
if (inSDKProp && inSiteProp) {
|
|
323
|
+
const traitValue = JSON.stringify(traitItem[prop]);
|
|
324
|
+
const sdkValue = JSON.stringify(sdkItem[prop]);
|
|
325
|
+
const siteValue = JSON.stringify(siteItem[prop]);
|
|
326
|
+
|
|
327
|
+
if (traitValue !== sdkValue || traitValue !== siteValue || sdkValue !== siteValue) {
|
|
328
|
+
propertyIssues.valueMismatches.push({
|
|
329
|
+
prop: prop,
|
|
330
|
+
traitValue: traitItem[prop],
|
|
331
|
+
sdkValue: sdkItem[prop],
|
|
332
|
+
siteValue: siteItem[prop]
|
|
333
|
+
});
|
|
334
|
+
hasPropertyIssues = true;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
if (hasPropertyIssues) {
|
|
341
|
+
verification.propertyMismatches.push(propertyIssues);
|
|
342
|
+
} else {
|
|
343
|
+
verification.propertyMatches.push(key);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
} else if (inSDK && !inSite) {
|
|
347
|
+
verification.foundInSDKOnly.push(key);
|
|
348
|
+
} else if (!inSDK && inSite) {
|
|
349
|
+
verification.foundInSiteOnly.push(key);
|
|
350
|
+
} else {
|
|
351
|
+
verification.foundInNone.push(key);
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
return verification;
|
|
356
|
+
}
|
|
357
|
+
|
|
165
358
|
function compareMetadata(sdk, site, fakeRares) {
|
|
166
359
|
const results = {
|
|
167
360
|
onlyInSDK: [],
|
|
@@ -1854,6 +2047,8 @@
|
|
|
1854
2047
|
}
|
|
1855
2048
|
|
|
1856
2049
|
let missingItemsGlobal = [];
|
|
2050
|
+
let isCurrentSourceTrait = false;
|
|
2051
|
+
let currentTraitData = null;
|
|
1857
2052
|
|
|
1858
2053
|
async function addMissingItemsToBoth() {
|
|
1859
2054
|
const button = document.getElementById('addMissingItemsBtn');
|
|
@@ -1955,6 +2150,100 @@
|
|
|
1955
2150
|
}
|
|
1956
2151
|
}
|
|
1957
2152
|
|
|
2153
|
+
async function applyTraitMappings() {
|
|
2154
|
+
const statusDiv = document.getElementById('applyMappingStatus');
|
|
2155
|
+
|
|
2156
|
+
if (!isCurrentSourceTrait || !currentTraitData) {
|
|
2157
|
+
statusDiv.style.display = 'block';
|
|
2158
|
+
statusDiv.className = 'status error';
|
|
2159
|
+
statusDiv.textContent = 'No trait data loaded. Please select a trait file first.';
|
|
2160
|
+
return;
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
statusDiv.style.display = 'block';
|
|
2164
|
+
statusDiv.className = 'status';
|
|
2165
|
+
statusDiv.textContent = 'Applying trait mappings to existing metadata items...';
|
|
2166
|
+
|
|
2167
|
+
try {
|
|
2168
|
+
// Map trait items to the correct format - only for items that exist in metadata
|
|
2169
|
+
const mappedItems = {};
|
|
2170
|
+
let foundCount = 0;
|
|
2171
|
+
let notFoundCount = 0;
|
|
2172
|
+
|
|
2173
|
+
Object.keys(currentTraitData).forEach(key => {
|
|
2174
|
+
// Only process if the item exists in at least one metadata file
|
|
2175
|
+
if (sdkMetadataGlobal[key] || siteMetadataGlobal[key]) {
|
|
2176
|
+
const traitItem = currentTraitData[key];
|
|
2177
|
+
const mappedData = mapTraitToMetadataFormat(traitItem, currentCollection);
|
|
2178
|
+
|
|
2179
|
+
// Merge with existing metadata if present
|
|
2180
|
+
const existingSDK = sdkMetadataGlobal[key] || {};
|
|
2181
|
+
const existingSite = siteMetadataGlobal[key] || {};
|
|
2182
|
+
|
|
2183
|
+
mappedItems[key] = {
|
|
2184
|
+
...existingSDK, // Preserve existing properties
|
|
2185
|
+
...mappedData, // Apply mapped properties (overwrites conflicts)
|
|
2186
|
+
// Ensure raw data includes both old and new
|
|
2187
|
+
raw: {
|
|
2188
|
+
...existingSDK.raw,
|
|
2189
|
+
...mappedData.raw
|
|
2190
|
+
}
|
|
2191
|
+
};
|
|
2192
|
+
foundCount++;
|
|
2193
|
+
} else {
|
|
2194
|
+
notFoundCount++;
|
|
2195
|
+
console.log(`Skipping ${key} - not found in existing metadata`);
|
|
2196
|
+
}
|
|
2197
|
+
});
|
|
2198
|
+
|
|
2199
|
+
if (foundCount === 0) {
|
|
2200
|
+
statusDiv.className = 'status error';
|
|
2201
|
+
statusDiv.textContent = 'No matching items found in metadata to update.';
|
|
2202
|
+
return;
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
statusDiv.textContent = `Updating ${foundCount} existing items (skipping ${notFoundCount} not found)...`;
|
|
2206
|
+
|
|
2207
|
+
// Send update request to server
|
|
2208
|
+
const response = await fetch('/apply-trait-mappings', {
|
|
2209
|
+
method: 'POST',
|
|
2210
|
+
headers: {
|
|
2211
|
+
'Content-Type': 'application/json'
|
|
2212
|
+
},
|
|
2213
|
+
body: JSON.stringify({ items: mappedItems })
|
|
2214
|
+
});
|
|
2215
|
+
|
|
2216
|
+
const results = await response.json();
|
|
2217
|
+
|
|
2218
|
+
if (!response.ok) {
|
|
2219
|
+
throw new Error(results.error || 'Failed to apply trait mappings');
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
statusDiv.className = 'status success';
|
|
2223
|
+
statusDiv.innerHTML = `
|
|
2224
|
+
<strong>Trait mappings applied to existing items!</strong><br>
|
|
2225
|
+
<div style="margin: 10px 0;">
|
|
2226
|
+
<span style="color: #27ae60;">✓ SDK Updated: ${results.sdkUpdated || 0} items</span> |
|
|
2227
|
+
<span style="color: #27ae60;">✓ Site Updated: ${results.siteUpdated || 0} items</span>
|
|
2228
|
+
</div>
|
|
2229
|
+
<div style="margin: 5px 0; color: #666;">
|
|
2230
|
+
<span>Processed: ${foundCount} items found in metadata</span><br>
|
|
2231
|
+
<span>Skipped: ${notFoundCount} items not in metadata</span>
|
|
2232
|
+
</div>
|
|
2233
|
+
<p style="color: #666; font-size: 12px; margin-top: 10px;">
|
|
2234
|
+
Only existing metadata items were updated with trait properties.
|
|
2235
|
+
Properties have been mapped to match the Rare Pepe format.
|
|
2236
|
+
Reload the page to see the updated metadata comparison.
|
|
2237
|
+
</p>
|
|
2238
|
+
`;
|
|
2239
|
+
|
|
2240
|
+
} catch (error) {
|
|
2241
|
+
statusDiv.className = 'status error';
|
|
2242
|
+
statusDiv.textContent = `Error: ${error.message}`;
|
|
2243
|
+
console.error('Failed to apply trait mappings:', error);
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
|
|
1958
2247
|
async function copyMissingImages() {
|
|
1959
2248
|
const button = document.getElementById('copyImagesBtn');
|
|
1960
2249
|
const statusDiv = document.getElementById('copyStatus');
|
|
@@ -1966,18 +2255,44 @@
|
|
|
1966
2255
|
statusDiv.textContent = `Preparing to copy ${missingItemsGlobal.length} images...`;
|
|
1967
2256
|
|
|
1968
2257
|
try {
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
2258
|
+
let items;
|
|
2259
|
+
let collectionName = currentCollection;
|
|
2260
|
+
|
|
2261
|
+
if (isCurrentSourceTrait) {
|
|
2262
|
+
// For trait files, we need to get the project name from SDK metadata
|
|
2263
|
+
// and handle the different data structure
|
|
2264
|
+
items = missingItemsGlobal.map(item => {
|
|
2265
|
+
// Try to get project info from SDK metadata if the item exists there
|
|
2266
|
+
const sdkItem = sdkMetadataGlobal[item.name];
|
|
2267
|
+
let projectName = 'Unknown';
|
|
2268
|
+
|
|
2269
|
+
if (sdkItem && sdkItem.projectName) {
|
|
2270
|
+
projectName = sdkItem.projectName;
|
|
2271
|
+
// Convert project name to folder format (e.g., "Age of Chains" -> "age-of-chains")
|
|
2272
|
+
collectionName = projectName.toLowerCase().replace(/\s+/g, '-') + '.json';
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
return {
|
|
2276
|
+
name: item.name,
|
|
2277
|
+
image: item.data.img_url || item.data.image || `${item.name}.jpg`,
|
|
2278
|
+
serie: item.data.series || item.data.serie || 1
|
|
2279
|
+
};
|
|
2280
|
+
});
|
|
2281
|
+
} else {
|
|
2282
|
+
// Original logic for collection files
|
|
2283
|
+
items = missingItemsGlobal.map(item => ({
|
|
2284
|
+
name: item.name,
|
|
2285
|
+
image: item.data.image,
|
|
2286
|
+
serie: item.data.serie
|
|
2287
|
+
}));
|
|
2288
|
+
}
|
|
1974
2289
|
|
|
1975
2290
|
const response = await fetch('/copy-images', {
|
|
1976
2291
|
method: 'POST',
|
|
1977
2292
|
headers: {
|
|
1978
2293
|
'Content-Type': 'application/json'
|
|
1979
2294
|
},
|
|
1980
|
-
body: JSON.stringify({ items, collection:
|
|
2295
|
+
body: JSON.stringify({ items, collection: collectionName })
|
|
1981
2296
|
});
|
|
1982
2297
|
|
|
1983
2298
|
const results = await response.json();
|
|
@@ -2063,6 +2378,191 @@
|
|
|
2063
2378
|
}
|
|
2064
2379
|
}
|
|
2065
2380
|
|
|
2381
|
+
function displayTraitVerification(verification, traitFileName) {
|
|
2382
|
+
const section = document.getElementById('traitVerification');
|
|
2383
|
+
|
|
2384
|
+
if (!verification) {
|
|
2385
|
+
section.style.display = 'none';
|
|
2386
|
+
return;
|
|
2387
|
+
}
|
|
2388
|
+
|
|
2389
|
+
section.style.display = 'block';
|
|
2390
|
+
|
|
2391
|
+
const percentInBoth = ((verification.foundInBoth.length / verification.total) * 100).toFixed(1);
|
|
2392
|
+
const percentInSDKOnly = ((verification.foundInSDKOnly.length / verification.total) * 100).toFixed(1);
|
|
2393
|
+
const percentInSiteOnly = ((verification.foundInSiteOnly.length / verification.total) * 100).toFixed(1);
|
|
2394
|
+
const percentInNone = ((verification.foundInNone.length / verification.total) * 100).toFixed(1);
|
|
2395
|
+
|
|
2396
|
+
let html = `
|
|
2397
|
+
<h2>Trait Verification Report for ${traitFileName}</h2>
|
|
2398
|
+
<div class="summary" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin: 20px 0;">
|
|
2399
|
+
<div style="background: #d4edda; padding: 15px; border-radius: 8px;">
|
|
2400
|
+
<div style="font-size: 24px; font-weight: bold; color: #155724;">${verification.foundInBoth.length}</div>
|
|
2401
|
+
<div style="color: #155724;">Found in Both (${percentInBoth}%)</div>
|
|
2402
|
+
</div>
|
|
2403
|
+
<div style="background: #cce5ff; padding: 15px; border-radius: 8px;">
|
|
2404
|
+
<div style="font-size: 24px; font-weight: bold; color: #004085;">${verification.foundInSDKOnly.length}</div>
|
|
2405
|
+
<div style="color: #004085;">SDK Only (${percentInSDKOnly}%)</div>
|
|
2406
|
+
</div>
|
|
2407
|
+
<div style="background: #fff3cd; padding: 15px; border-radius: 8px;">
|
|
2408
|
+
<div style="font-size: 24px; font-weight: bold; color: #856404;">${verification.foundInSiteOnly.length}</div>
|
|
2409
|
+
<div style="color: #856404;">Site Only (${percentInSiteOnly}%)</div>
|
|
2410
|
+
</div>
|
|
2411
|
+
<div style="background: #f8d7da; padding: 15px; border-radius: 8px;">
|
|
2412
|
+
<div style="font-size: 24px; font-weight: bold; color: #721c24;">${verification.foundInNone.length}</div>
|
|
2413
|
+
<div style="color: #721c24;">Not Found (${percentInNone}%)</div>
|
|
2414
|
+
</div>
|
|
2415
|
+
</div>
|
|
2416
|
+
<div style="font-weight: bold; margin: 10px 0;">Total Trait Items: ${verification.total}</div>
|
|
2417
|
+
`;
|
|
2418
|
+
|
|
2419
|
+
// Add property analysis for items found in both
|
|
2420
|
+
if (verification.propertyMatches.length > 0 || verification.propertyMismatches.length > 0) {
|
|
2421
|
+
html += `
|
|
2422
|
+
<div style="margin: 20px 0; padding: 15px; background: #f0f8ff; border-radius: 8px;">
|
|
2423
|
+
<h3 style="color: #004085;">Property Analysis for Items in Both Metadata Files</h3>
|
|
2424
|
+
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin: 10px 0;">
|
|
2425
|
+
<div style="background: #d4edda; padding: 10px; border-radius: 4px;">
|
|
2426
|
+
<strong style="color: #155724;">Perfect Matches: ${verification.propertyMatches.length}</strong>
|
|
2427
|
+
<div style="color: #155724; font-size: 14px;">All properties and values match</div>
|
|
2428
|
+
</div>
|
|
2429
|
+
<div style="background: #fff3cd; padding: 10px; border-radius: 4px;">
|
|
2430
|
+
<strong style="color: #856404;">Property Issues: ${verification.propertyMismatches.length}</strong>
|
|
2431
|
+
<div style="color: #856404; font-size: 14px;">Missing properties or value mismatches</div>
|
|
2432
|
+
</div>
|
|
2433
|
+
</div>
|
|
2434
|
+
<button onclick="applyTraitMappings()" style="margin-top: 10px; padding: 10px 20px; background: #5e72e4; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; font-weight: bold;">
|
|
2435
|
+
Apply Trait Mappings to Metadata
|
|
2436
|
+
</button>
|
|
2437
|
+
<div id="applyMappingStatus" style="display:none; margin-top: 10px;"></div>
|
|
2438
|
+
<p style="color: #666; margin: 10px 0; font-size: 12px;">
|
|
2439
|
+
This will update the SDK and Site metadata with properly mapped trait properties following the Rare Pepe format,
|
|
2440
|
+
ensuring Series, Card, Total Supply, and Artist traits will work correctly.
|
|
2441
|
+
</p>
|
|
2442
|
+
</div>
|
|
2443
|
+
`;
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2446
|
+
// Show property mismatches in detail
|
|
2447
|
+
if (verification.propertyMismatches.length > 0) {
|
|
2448
|
+
html += `
|
|
2449
|
+
<details style="margin: 20px 0; padding: 15px; background: #fff3cd; border-radius: 8px;">
|
|
2450
|
+
<summary style="cursor: pointer; font-weight: bold; color: #856404;">
|
|
2451
|
+
Property Issues Detail (${verification.propertyMismatches.length} items)
|
|
2452
|
+
</summary>
|
|
2453
|
+
<div style="margin-top: 15px;">
|
|
2454
|
+
`;
|
|
2455
|
+
|
|
2456
|
+
verification.propertyMismatches.forEach(issue => {
|
|
2457
|
+
html += `
|
|
2458
|
+
<div style="margin: 10px 0; padding: 10px; background: white; border-radius: 4px; border-left: 4px solid #ff6b6b;">
|
|
2459
|
+
<strong style="color: #2c3e50;">${issue.key}</strong>
|
|
2460
|
+
`;
|
|
2461
|
+
|
|
2462
|
+
if (issue.missingInSDK.length > 0) {
|
|
2463
|
+
html += `
|
|
2464
|
+
<div style="margin: 5px 0;">
|
|
2465
|
+
<span style="color: #e74c3c;">Missing in SDK:</span>
|
|
2466
|
+
${issue.missingInSDK.map(p => `<code style="background: #f8f9fa; padding: 2px 4px; margin: 0 2px;">${p}</code>`).join(', ')}
|
|
2467
|
+
</div>
|
|
2468
|
+
`;
|
|
2469
|
+
}
|
|
2470
|
+
|
|
2471
|
+
if (issue.missingInSite.length > 0) {
|
|
2472
|
+
html += `
|
|
2473
|
+
<div style="margin: 5px 0;">
|
|
2474
|
+
<span style="color: #e74c3c;">Missing in Site:</span>
|
|
2475
|
+
${issue.missingInSite.map(p => `<code style="background: #f8f9fa; padding: 2px 4px; margin: 0 2px;">${p}</code>`).join(', ')}
|
|
2476
|
+
</div>
|
|
2477
|
+
`;
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
if (issue.valueMismatches.length > 0) {
|
|
2481
|
+
html += `
|
|
2482
|
+
<div style="margin: 5px 0;">
|
|
2483
|
+
<span style="color: #e67e22;">Value Mismatches:</span>
|
|
2484
|
+
<table style="width: 100%; margin-top: 5px; border-collapse: collapse;">
|
|
2485
|
+
<thead>
|
|
2486
|
+
<tr style="background: #f8f9fa;">
|
|
2487
|
+
<th style="padding: 5px; border: 1px solid #dee2e6; text-align: left;">Property</th>
|
|
2488
|
+
<th style="padding: 5px; border: 1px solid #dee2e6; text-align: left;">Trait Value</th>
|
|
2489
|
+
<th style="padding: 5px; border: 1px solid #dee2e6; text-align: left;">SDK Value</th>
|
|
2490
|
+
<th style="padding: 5px; border: 1px solid #dee2e6; text-align: left;">Site Value</th>
|
|
2491
|
+
</tr>
|
|
2492
|
+
</thead>
|
|
2493
|
+
<tbody>
|
|
2494
|
+
`;
|
|
2495
|
+
issue.valueMismatches.forEach(mismatch => {
|
|
2496
|
+
html += `
|
|
2497
|
+
<tr>
|
|
2498
|
+
<td style="padding: 5px; border: 1px solid #dee2e6; font-family: monospace;">${mismatch.prop}</td>
|
|
2499
|
+
<td style="padding: 5px; border: 1px solid #dee2e6; font-family: monospace; font-size: 12px;">${JSON.stringify(mismatch.traitValue)}</td>
|
|
2500
|
+
<td style="padding: 5px; border: 1px solid #dee2e6; font-family: monospace; font-size: 12px;">${JSON.stringify(mismatch.sdkValue)}</td>
|
|
2501
|
+
<td style="padding: 5px; border: 1px solid #dee2e6; font-family: monospace; font-size: 12px;">${JSON.stringify(mismatch.siteValue)}</td>
|
|
2502
|
+
</tr>
|
|
2503
|
+
`;
|
|
2504
|
+
});
|
|
2505
|
+
html += `
|
|
2506
|
+
</tbody>
|
|
2507
|
+
</table>
|
|
2508
|
+
</div>
|
|
2509
|
+
`;
|
|
2510
|
+
}
|
|
2511
|
+
|
|
2512
|
+
html += `</div>`;
|
|
2513
|
+
});
|
|
2514
|
+
|
|
2515
|
+
html += `
|
|
2516
|
+
</div>
|
|
2517
|
+
</details>
|
|
2518
|
+
`;
|
|
2519
|
+
}
|
|
2520
|
+
|
|
2521
|
+
// Show details for items not found
|
|
2522
|
+
if (verification.foundInNone.length > 0) {
|
|
2523
|
+
html += `
|
|
2524
|
+
<details style="margin: 20px 0;">
|
|
2525
|
+
<summary style="cursor: pointer; font-weight: bold; color: #721c24;">
|
|
2526
|
+
Items Not Found in Either Metadata (${verification.foundInNone.length})
|
|
2527
|
+
</summary>
|
|
2528
|
+
<div style="margin-top: 10px; padding: 10px; background: #f8f9fa; border-radius: 4px;">
|
|
2529
|
+
${verification.foundInNone.map(item => `<span style="display: inline-block; margin: 3px; padding: 5px 10px; background: white; border: 1px solid #dee2e6; border-radius: 3px; font-family: monospace;">${item}</span>`).join('')}
|
|
2530
|
+
</div>
|
|
2531
|
+
</details>
|
|
2532
|
+
`;
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2535
|
+
// Show details for items only in SDK
|
|
2536
|
+
if (verification.foundInSDKOnly.length > 0) {
|
|
2537
|
+
html += `
|
|
2538
|
+
<details style="margin: 20px 0;">
|
|
2539
|
+
<summary style="cursor: pointer; font-weight: bold; color: #004085;">
|
|
2540
|
+
Items Only in SDK Metadata (${verification.foundInSDKOnly.length})
|
|
2541
|
+
</summary>
|
|
2542
|
+
<div style="margin-top: 10px; padding: 10px; background: #f8f9fa; border-radius: 4px;">
|
|
2543
|
+
${verification.foundInSDKOnly.map(item => `<span style="display: inline-block; margin: 3px; padding: 5px 10px; background: white; border: 1px solid #dee2e6; border-radius: 3px; font-family: monospace;">${item}</span>`).join('')}
|
|
2544
|
+
</div>
|
|
2545
|
+
</details>
|
|
2546
|
+
`;
|
|
2547
|
+
}
|
|
2548
|
+
|
|
2549
|
+
// Show details for items only in Site
|
|
2550
|
+
if (verification.foundInSiteOnly.length > 0) {
|
|
2551
|
+
html += `
|
|
2552
|
+
<details style="margin: 20px 0;">
|
|
2553
|
+
<summary style="cursor: pointer; font-weight: bold; color: #856404;">
|
|
2554
|
+
Items Only in Site Metadata (${verification.foundInSiteOnly.length})
|
|
2555
|
+
</summary>
|
|
2556
|
+
<div style="margin-top: 10px; padding: 10px; background: #f8f9fa; border-radius: 4px;">
|
|
2557
|
+
${verification.foundInSiteOnly.map(item => `<span style="display: inline-block; margin: 3px; padding: 5px 10px; background: white; border: 1px solid #dee2e6; border-radius: 3px; font-family: monospace;">${item}</span>`).join('')}
|
|
2558
|
+
</div>
|
|
2559
|
+
</details>
|
|
2560
|
+
`;
|
|
2561
|
+
}
|
|
2562
|
+
|
|
2563
|
+
section.innerHTML = html;
|
|
2564
|
+
}
|
|
2565
|
+
|
|
2066
2566
|
function displayMissingFromBoth(missing, collectionName = 'collection') {
|
|
2067
2567
|
const section = document.getElementById('fakeRaresSection');
|
|
2068
2568
|
|
|
@@ -2080,10 +2580,17 @@
|
|
|
2080
2580
|
<p style="color: #666; margin: 10px 0;">These items exist in ${collectionName} but are not present in either sdk-metadata.json or emblem-site-metadata.json</p>
|
|
2081
2581
|
<button id="addMissingItemsBtn" onclick="addMissingItemsToBoth()" style="padding: 10px 20px; background: #8e44ad; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; font-weight: bold; margin: 10px 5px 10px 0;">
|
|
2082
2582
|
Add Missing Items to Both SDK & Site (${missing.length})
|
|
2083
|
-
</button
|
|
2583
|
+
</button>`;
|
|
2584
|
+
|
|
2585
|
+
// Only show Copy Images button for collection files, not trait files
|
|
2586
|
+
if (!isCurrentSourceTrait) {
|
|
2587
|
+
html += `
|
|
2084
2588
|
<button id="copyImagesBtn" onclick="copyMissingImages()" style="padding: 10px 20px; background: #27ae60; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; font-weight: bold; margin: 10px 0;">
|
|
2085
2589
|
Copy Missing Images (${missing.length})
|
|
2086
|
-
</button
|
|
2590
|
+
</button>`;
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
html += `
|
|
2087
2594
|
<div id="addMissingItemsStatus" style="display:none;"></div>
|
|
2088
2595
|
<div id="copyStatus" style="display:none;"></div>
|
|
2089
2596
|
<p style="color: #666; margin: 10px 0; font-size: 12px;">The "Add Missing Items" button will create entries in both SDK and Site metadata using the template structure with ${collectionName} data in the 'raw' property.</p>
|
|
@@ -2128,14 +2635,51 @@
|
|
|
2128
2635
|
}
|
|
2129
2636
|
}
|
|
2130
2637
|
|
|
2131
|
-
async function
|
|
2132
|
-
|
|
2133
|
-
|
|
2638
|
+
async function loadTraits() {
|
|
2639
|
+
try {
|
|
2640
|
+
const response = await fetch('/list-traits');
|
|
2641
|
+
const availableTraits = await response.json();
|
|
2642
|
+
|
|
2643
|
+
const select = document.getElementById('traitSelect');
|
|
2644
|
+
select.innerHTML = '<option value="">-- Select a trait file --</option>';
|
|
2134
2645
|
|
|
2135
|
-
|
|
2136
|
-
|
|
2646
|
+
availableTraits.forEach(trait => {
|
|
2647
|
+
const option = document.createElement('option');
|
|
2648
|
+
option.value = trait.filename;
|
|
2649
|
+
option.textContent = trait.name;
|
|
2650
|
+
select.appendChild(option);
|
|
2651
|
+
});
|
|
2652
|
+
} catch (error) {
|
|
2653
|
+
document.getElementById('status').textContent = `Error loading traits: ${error.message}`;
|
|
2654
|
+
document.getElementById('status').className = 'status error';
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
|
|
2658
|
+
async function loadComparison(sourceType) {
|
|
2659
|
+
let selectedFile = '';
|
|
2660
|
+
let isTraitSource = false;
|
|
2661
|
+
|
|
2662
|
+
if (sourceType === 'trait') {
|
|
2663
|
+
const traitSelect = document.getElementById('traitSelect');
|
|
2664
|
+
selectedFile = traitSelect.value;
|
|
2665
|
+
isTraitSource = true;
|
|
2666
|
+
isCurrentSourceTrait = true; // Set global flag
|
|
2667
|
+
// Clear collection dropdown
|
|
2668
|
+
document.getElementById('collectionSelect').value = '';
|
|
2669
|
+
} else {
|
|
2670
|
+
const collectionSelect = document.getElementById('collectionSelect');
|
|
2671
|
+
selectedFile = collectionSelect.value;
|
|
2672
|
+
isCurrentSourceTrait = false; // Set global flag
|
|
2673
|
+
// Clear trait dropdown
|
|
2674
|
+
document.getElementById('traitSelect').value = '';
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
if (!selectedFile) {
|
|
2678
|
+
document.getElementById('status').textContent = 'Select a collection or trait file to begin...';
|
|
2137
2679
|
document.getElementById('status').className = 'status';
|
|
2138
2680
|
// Clear all sections
|
|
2681
|
+
document.getElementById('traitVerification').style.display = 'none';
|
|
2682
|
+
document.getElementById('traitVerification').innerHTML = '';
|
|
2139
2683
|
document.getElementById('summary').style.display = 'none';
|
|
2140
2684
|
document.getElementById('summary').innerHTML = '';
|
|
2141
2685
|
document.getElementById('fakeRaresSection').style.display = 'none';
|
|
@@ -2146,10 +2690,12 @@
|
|
|
2146
2690
|
return;
|
|
2147
2691
|
}
|
|
2148
2692
|
|
|
2149
|
-
currentCollection =
|
|
2693
|
+
currentCollection = selectedFile;
|
|
2150
2694
|
const statusDiv = document.getElementById('status');
|
|
2151
2695
|
|
|
2152
2696
|
// Clear all previous results before loading new collection
|
|
2697
|
+
document.getElementById('traitVerification').style.display = 'none';
|
|
2698
|
+
document.getElementById('traitVerification').innerHTML = '';
|
|
2153
2699
|
document.getElementById('summary').style.display = 'none';
|
|
2154
2700
|
document.getElementById('summary').innerHTML = '';
|
|
2155
2701
|
document.getElementById('fakeRaresSection').style.display = 'none';
|
|
@@ -2166,11 +2712,36 @@
|
|
|
2166
2712
|
statusDiv.textContent = 'Loading emblem-site-metadata.json...';
|
|
2167
2713
|
siteMetadataGlobal = await loadJSON('./emblem-site-metadata.json');
|
|
2168
2714
|
|
|
2169
|
-
statusDiv.textContent = `Loading ${
|
|
2170
|
-
|
|
2171
|
-
|
|
2715
|
+
statusDiv.textContent = `Loading ${selectedFile}...`;
|
|
2716
|
+
let incomingArray;
|
|
2717
|
+
if (isTraitSource) {
|
|
2718
|
+
incomingArray = await loadJSON(`./traits/${selectedFile}`);
|
|
2719
|
+
// Convert traits format to standard format using asset or tokenId as key
|
|
2720
|
+
const convertedData = {};
|
|
2721
|
+
incomingArray.forEach(item => {
|
|
2722
|
+
const key = item.asset || item.tokenId;
|
|
2723
|
+
if (key) {
|
|
2724
|
+
convertedData[key] = item;
|
|
2725
|
+
}
|
|
2726
|
+
});
|
|
2727
|
+
incomingDataGlobal = convertedData;
|
|
2728
|
+
currentTraitData = convertedData; // Save for mapping function
|
|
2729
|
+
} else {
|
|
2730
|
+
incomingArray = await loadJSON(`./incoming/${selectedFile}`);
|
|
2731
|
+
incomingDataGlobal = convertFakeRaresToObject(incomingArray);
|
|
2732
|
+
}
|
|
2172
2733
|
|
|
2173
2734
|
statusDiv.textContent = 'Comparing metadata...';
|
|
2735
|
+
|
|
2736
|
+
// If this is a trait source, run verification
|
|
2737
|
+
if (isTraitSource) {
|
|
2738
|
+
const verification = verifyTraitCoverage(incomingDataGlobal, sdkMetadataGlobal, siteMetadataGlobal);
|
|
2739
|
+
displayTraitVerification(verification, selectedFile);
|
|
2740
|
+
} else {
|
|
2741
|
+
// Hide trait verification for collection sources
|
|
2742
|
+
document.getElementById('traitVerification').style.display = 'none';
|
|
2743
|
+
}
|
|
2744
|
+
|
|
2174
2745
|
const results = compareMetadata(sdkMetadataGlobal, siteMetadataGlobal, incomingDataGlobal);
|
|
2175
2746
|
const missingFromBoth = findMissingFromBoth(incomingDataGlobal, sdkMetadataGlobal, siteMetadataGlobal);
|
|
2176
2747
|
|
|
@@ -2179,11 +2750,17 @@
|
|
|
2179
2750
|
const siteRedundant = findRedundantOldImages(siteMetadataGlobal);
|
|
2180
2751
|
|
|
2181
2752
|
statusDiv.className = 'status success';
|
|
2182
|
-
statusDiv.textContent = `Comparison complete! Found ${missingFromBoth.length} items missing from both SDK and Site. Found ${sdkRedundant.length + siteRedundant.length} redundant oldImage properties.`;
|
|
2183
2753
|
|
|
2184
|
-
|
|
2754
|
+
if (isTraitSource) {
|
|
2755
|
+
const verification = verifyTraitCoverage(incomingDataGlobal, sdkMetadataGlobal, siteMetadataGlobal);
|
|
2756
|
+
statusDiv.textContent = `Trait verification complete! ${verification.foundInBoth.length} items found in both, ${verification.foundInNone.length} missing from both metadata files.`;
|
|
2757
|
+
} else {
|
|
2758
|
+
statusDiv.textContent = `Comparison complete! Found ${missingFromBoth.length} items missing from both SDK and Site. Found ${sdkRedundant.length + siteRedundant.length} redundant oldImage properties.`;
|
|
2759
|
+
}
|
|
2760
|
+
|
|
2761
|
+
displayMissingFromBoth(missingFromBoth, selectedFile);
|
|
2185
2762
|
displayRedundantOldImages(sdkRedundant, siteRedundant);
|
|
2186
|
-
displayResults(results,
|
|
2763
|
+
displayResults(results, selectedFile);
|
|
2187
2764
|
} catch (error) {
|
|
2188
2765
|
statusDiv.className = 'status error';
|
|
2189
2766
|
statusDiv.textContent = `Error: ${error.message}`;
|
|
@@ -2193,6 +2770,7 @@
|
|
|
2193
2770
|
|
|
2194
2771
|
// Auto-run when page loads
|
|
2195
2772
|
loadCollections();
|
|
2773
|
+
loadTraits();
|
|
2196
2774
|
</script>
|
|
2197
2775
|
</body>
|
|
2198
2776
|
</html>
|