mirror of
https://github.com/SwingTheVine/Wplace-BlueMarble.git
synced 2026-03-11 17:15:38 +00:00
444 lines
19 KiB
HTML
444 lines
19 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>JSDoc: Source: templateManager.js</title>
|
|
|
|
<script src="scripts/prettify/prettify.js"> </script>
|
|
<script src="scripts/prettify/lang-css.js"> </script>
|
|
<!--[if lt IE 9]>
|
|
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
|
<![endif]-->
|
|
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
|
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<div id="main">
|
|
|
|
<h1 class="page-title">Source: templateManager.js</h1>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<section>
|
|
<article>
|
|
<pre class="prettyprint source linenums"><code>import Template from "./Template";
|
|
import { base64ToUint8, numberToEncoded } from "./utils";
|
|
|
|
/** Manages the template system.
|
|
* This class handles all external requests for template modification, creation, and analysis.
|
|
* It serves as the central coordinator between template instances and the user interface.
|
|
* @since 0.55.8
|
|
* @example
|
|
* // JSON structure for a template
|
|
* {
|
|
* "whoami": "BlueMarble",
|
|
* "scriptVersion": "1.13.0",
|
|
* "schemaVersion": "2.1.0",
|
|
* "templates": {
|
|
* "0 $Z": {
|
|
* "name": "My Template",
|
|
* "enabled": true,
|
|
* "tiles": {
|
|
* "1231,0047,183,593": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA",
|
|
* "1231,0048,183,000": "data:image/png;AAAFCAYAAACNbyblAAAAHElEQVQI12P4"
|
|
* }
|
|
* },
|
|
* "1 $Z": {
|
|
* "name": "My Template",
|
|
* "URL": "https://github.com/SwingTheVine/Wplace-BlueMarble/blob/main/dist/assets/Favicon.png",
|
|
* "URLType": "template",
|
|
* "enabled": false,
|
|
* "tiles": {
|
|
* "375,1846,276,188": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA",
|
|
* "376,1846,000,188": "data:image/png;AAAFCAYAAACNbyblAAAAHElEQVQI12P4"
|
|
* }
|
|
* }
|
|
* }
|
|
* }
|
|
*/
|
|
export default class TemplateManager {
|
|
|
|
/** The constructor for the {@link TemplateManager} class.
|
|
* @since 0.55.8
|
|
*/
|
|
constructor(name, version, overlay) {
|
|
|
|
// Meta
|
|
this.name = name; // Name of userscript
|
|
this.version = version; // Version of userscript
|
|
this.overlay = overlay; // The main instance of the Overlay class
|
|
this.templatesVersion = '1.0.0'; // Version of JSON schema
|
|
this.userID = null; // The ID of the current user
|
|
this.encodingBase = '!#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~'; // Characters to use for encoding/decoding
|
|
this.tileSize = 1000; // The number of pixels in a tile. Assumes the tile is square
|
|
this.drawMult = 3; // The enlarged size for each pixel. E.g. when "3", a 1x1 pixel becomes a 1x1 pixel inside a 3x3 area. MUST BE ODD
|
|
|
|
// Template
|
|
this.canvasTemplate = null; // Our canvas
|
|
this.canvasTemplateZoomed = null; // The template when zoomed out
|
|
this.canvasTemplateID = 'bm-canvas'; // Our canvas ID
|
|
this.canvasMainID = 'div#map canvas.maplibregl-canvas'; // The selector for the main canvas
|
|
this.template = null; // The template image.
|
|
this.templateState = ''; // The state of the template ('blob', 'proccessing', 'template', etc.)
|
|
this.templatesArray = []; // All Template instnaces currently loaded (Template)
|
|
this.templatesJSON = null; // All templates currently loaded (JSON)
|
|
this.templatesShouldBeDrawn = true; // Should ALL templates be drawn to the canvas?
|
|
}
|
|
|
|
/** Retrieves the pixel art canvas.
|
|
* If the canvas has been updated/replaced, it retrieves the new one.
|
|
* @param {string} selector - The CSS selector to use to find the canvas.
|
|
* @returns {HTMLCanvasElement|null} The canvas as an HTML Canvas Element, or null if the canvas does not exist
|
|
* @since 0.58.3
|
|
* @deprecated Not in use since 0.63.25
|
|
*/
|
|
/* @__PURE__ */getCanvas() {
|
|
|
|
// If the stored canvas is "fresh", return the stored canvas
|
|
if (document.body.contains(this.canvasTemplate)) {return this.canvasTemplate;}
|
|
// Else, the stored canvas is "stale", get the canvas again
|
|
|
|
// Attempt to find and destroy the "stale" canvas
|
|
document.getElementById(this.canvasTemplateID)?.remove();
|
|
|
|
const canvasMain = document.querySelector(this.canvasMainID);
|
|
|
|
const canvasTemplateNew = document.createElement('canvas');
|
|
canvasTemplateNew.id = this.canvasTemplateID;
|
|
canvasTemplateNew.className = 'maplibregl-canvas';
|
|
canvasTemplateNew.style.position = 'absolute';
|
|
canvasTemplateNew.style.top = '0';
|
|
canvasTemplateNew.style.left = '0';
|
|
canvasTemplateNew.style.height = `${canvasMain?.clientHeight * (window.devicePixelRatio || 1)}px`;
|
|
canvasTemplateNew.style.width = `${canvasMain?.clientWidth * (window.devicePixelRatio || 1)}px`;
|
|
canvasTemplateNew.height = canvasMain?.clientHeight * (window.devicePixelRatio || 1);
|
|
canvasTemplateNew.width = canvasMain?.clientWidth * (window.devicePixelRatio || 1);
|
|
canvasTemplateNew.style.zIndex = '8999';
|
|
canvasTemplateNew.style.pointerEvents = 'none';
|
|
canvasMain?.parentElement?.appendChild(canvasTemplateNew); // Append the newCanvas as a child of the parent of the main canvas
|
|
this.canvasTemplate = canvasTemplateNew; // Store the new canvas
|
|
|
|
window.addEventListener('move', this.onMove);
|
|
window.addEventListener('zoom', this.onZoom);
|
|
window.addEventListener('resize', this.onResize);
|
|
|
|
return this.canvasTemplate; // Return the new canvas
|
|
}
|
|
|
|
/** Creates the JSON object to store templates in
|
|
* @returns {{ whoami: string, scriptVersion: string, schemaVersion: string, templates: Object }} The JSON object
|
|
* @since 0.65.4
|
|
*/
|
|
async createJSON() {
|
|
return {
|
|
"whoami": this.name.replace(' ', ''), // Name of userscript without spaces
|
|
"scriptVersion": this.version, // Version of userscript
|
|
"schemaVersion": this.templatesVersion, // Version of JSON schema
|
|
"templates": {} // The templates
|
|
};
|
|
}
|
|
|
|
/** Creates the template from the inputed file blob
|
|
* @param {File} blob - The file blob to create a template from
|
|
* @param {string} name - The display name of the template
|
|
* @param {Array<number, number, number, number>} coords - The coordinates of the top left corner of the template
|
|
* @since 0.65.77
|
|
*/
|
|
async createTemplate(blob, name, coords) {
|
|
|
|
// Creates the JSON object if it does not already exist
|
|
if (!this.templatesJSON) {this.templatesJSON = await this.createJSON(); console.log(`Creating JSON...`);}
|
|
|
|
this.overlay.handleDisplayStatus(`Creating template at ${coords.join(', ')}...`);
|
|
|
|
// Creates a new template instance
|
|
const template = new Template({
|
|
displayName: name,
|
|
sortID: 0, // Object.keys(this.templatesJSON.templates).length || 0, // Uncomment this to enable multiple templates (1/2)
|
|
authorID: numberToEncoded(this.userID || 0, this.encodingBase),
|
|
file: blob,
|
|
coords: coords
|
|
});
|
|
//template.chunked = await template.createTemplateTiles(this.tileSize); // Chunks the tiles
|
|
const { templateTiles, templateTilesBuffers } = await template.createTemplateTiles(this.tileSize); // Chunks the tiles
|
|
template.chunked = templateTiles; // Stores the chunked tile bitmaps
|
|
|
|
// Appends a child into the templates object
|
|
// The child's name is the number of templates already in the list (sort order) plus the encoded player ID
|
|
this.templatesJSON.templates[`${template.sortID} ${template.authorID}`] = {
|
|
"name": template.displayName, // Display name of template
|
|
"coords": coords.join(', '), // The coords of the template
|
|
"enabled": true,
|
|
"tiles": templateTilesBuffers // Stores the chunked tile buffers
|
|
};
|
|
|
|
this.templatesArray = []; // Remove this to enable multiple templates (2/2)
|
|
this.templatesArray.push(template); // Pushes the Template object instance to the Template Array
|
|
|
|
// ==================== PIXEL COUNT DISPLAY SYSTEM ====================
|
|
// Display pixel count statistics with internationalized number formatting
|
|
// This provides immediate feedback to users about template complexity and size
|
|
const pixelCountFormatted = new Intl.NumberFormat().format(template.pixelCount);
|
|
this.overlay.handleDisplayStatus(`Template created at ${coords.join(', ')}! Total pixels: ${pixelCountFormatted}`);
|
|
|
|
console.log(Object.keys(this.templatesJSON.templates).length);
|
|
console.log(this.templatesJSON);
|
|
console.log(this.templatesArray);
|
|
console.log(JSON.stringify(this.templatesJSON));
|
|
|
|
await this.#storeTemplates();
|
|
}
|
|
|
|
/** Generates a {@link Template} class instance from the JSON object template
|
|
*/
|
|
#loadTemplate() {
|
|
|
|
}
|
|
|
|
/** Stores the JSON object of the loaded templates into TamperMonkey (GreaseMonkey) storage.
|
|
* @since 0.72.7
|
|
*/
|
|
async #storeTemplates() {
|
|
GM.setValue('bmTemplates', JSON.stringify(this.templatesJSON));
|
|
}
|
|
|
|
/** Deletes a template from the JSON object.
|
|
* Also delete's the corrosponding {@link Template} class instance
|
|
*/
|
|
deleteTemplate() {
|
|
|
|
}
|
|
|
|
/** Disables the template from view
|
|
*/
|
|
async disableTemplate() {
|
|
|
|
// Creates the JSON object if it does not already exist
|
|
if (!this.templatesJSON) {this.templatesJSON = await this.createJSON(); console.log(`Creating JSON...`);}
|
|
|
|
|
|
}
|
|
|
|
/** Draws all templates on the specified tile.
|
|
* This method handles the rendering of template overlays on individual tiles.
|
|
* @param {File} tileBlob - The pixels that are placed on a tile
|
|
* @param {Array<number>} tileCoords - The tile coordinates [x, y]
|
|
* @since 0.65.77
|
|
*/
|
|
async drawTemplateOnTile(tileBlob, tileCoords) {
|
|
|
|
// Returns early if no templates should be drawn
|
|
if (!this.templatesShouldBeDrawn) {return tileBlob;}
|
|
|
|
const drawSize = this.tileSize * this.drawMult; // Calculate draw multiplier for scaling
|
|
|
|
// Format tile coordinates with proper padding for consistent lookup
|
|
tileCoords = tileCoords[0].toString().padStart(4, '0') + ',' + tileCoords[1].toString().padStart(4, '0');
|
|
|
|
console.log(`Searching for templates in tile: "${tileCoords}"`);
|
|
|
|
const templateArray = this.templatesArray; // Stores a copy for sorting
|
|
console.log(templateArray);
|
|
|
|
// Sorts the array of Template class instances. 0 = first = lowest draw priority
|
|
templateArray.sort((a, b) => {return a.sortID - b.sortID;});
|
|
|
|
console.log(templateArray);
|
|
|
|
// Retrieves the relavent template tile blobs
|
|
const templatesToDraw = templateArray
|
|
.map(template => {
|
|
const matchingTiles = Object.keys(template.chunked).filter(tile =>
|
|
tile.startsWith(tileCoords)
|
|
);
|
|
|
|
if (matchingTiles.length === 0) {return null;} // Return null when nothing is found
|
|
|
|
// Retrieves the blobs of the templates for this tile
|
|
const matchingTileBlobs = matchingTiles.map(tile => {
|
|
|
|
const coords = tile.split(','); // [x, y, x, y] Tile/pixel coordinates
|
|
|
|
return {
|
|
bitmap: template.chunked[tile],
|
|
tileCoords: [coords[0], coords[1]],
|
|
pixelCoords: [coords[2], coords[3]]
|
|
}
|
|
});
|
|
|
|
return matchingTileBlobs?.[0];
|
|
})
|
|
.filter(Boolean);
|
|
|
|
console.log(templatesToDraw);
|
|
|
|
const templateCount = templatesToDraw?.length || 0; // Number of templates to draw on this tile
|
|
console.log(`templateCount = ${templateCount}`);
|
|
|
|
if (templateCount > 0) {
|
|
|
|
// Calculate total pixel count for templates actively being displayed in this tile
|
|
const totalPixels = templateArray
|
|
.filter(template => {
|
|
// Filter templates to include only those with tiles matching current coordinates
|
|
// This ensures we count pixels only for templates actually being rendered
|
|
const matchingTiles = Object.keys(template.chunked).filter(tile =>
|
|
tile.startsWith(tileCoords)
|
|
);
|
|
return matchingTiles.length > 0;
|
|
})
|
|
.reduce((sum, template) => sum + (template.pixelCount || 0), 0);
|
|
|
|
// Format pixel count with locale-appropriate thousands separators for better readability
|
|
// Examples: "1,234,567" (US), "1.234.567" (DE), "1 234 567" (FR)
|
|
const pixelCountFormatted = new Intl.NumberFormat().format(totalPixels);
|
|
|
|
// Display status information about the templates being rendered
|
|
this.overlay.handleDisplayStatus(
|
|
`Displaying ${templateCount} template${templateCount == 1 ? '' : 's'}.\nTotal pixels: ${pixelCountFormatted}`
|
|
);
|
|
} else {
|
|
this.overlay.handleDisplayStatus(`Displaying ${templateCount} templates.`);
|
|
}
|
|
|
|
const tileBitmap = await createImageBitmap(tileBlob);
|
|
|
|
const canvas = new OffscreenCanvas(drawSize, drawSize);
|
|
const context = canvas.getContext('2d');
|
|
|
|
context.imageSmoothingEnabled = false; // Nearest neighbor
|
|
|
|
// Tells the canvas to ignore anything outside of this area
|
|
context.beginPath();
|
|
context.rect(0, 0, drawSize, drawSize);
|
|
context.clip();
|
|
|
|
context.clearRect(0, 0, drawSize, drawSize); // Draws transparent background
|
|
context.drawImage(tileBitmap, 0, 0, drawSize, drawSize);
|
|
|
|
// For each template in this tile, draw them.
|
|
for (const template of templatesToDraw) {
|
|
console.log(`Template:`);
|
|
console.log(template);
|
|
|
|
// Draws the each template on the tile based on it's relative position
|
|
context.drawImage(template.bitmap, Number(template.pixelCoords[0]) * this.drawMult, Number(template.pixelCoords[1]) * this.drawMult);
|
|
}
|
|
|
|
return await canvas.convertToBlob({ type: 'image/png' });
|
|
}
|
|
|
|
/** Imports the JSON object, and appends it to any JSON object already loaded
|
|
* @param {string} json - The JSON string to parse
|
|
*/
|
|
importJSON(json) {
|
|
|
|
console.log(`Importing JSON...`);
|
|
console.log(json);
|
|
|
|
// If the passed in JSON is a Blue Marble template object...
|
|
if (json?.whoami == 'BlueMarble') {
|
|
this.#parseBlueMarble(json); // ...parse the template object as Blue Marble
|
|
}
|
|
}
|
|
|
|
/** Parses the Blue Marble JSON object
|
|
* @param {string} json - The JSON string to parse
|
|
* @since 0.72.13
|
|
*/
|
|
async #parseBlueMarble(json) {
|
|
|
|
console.log(`Parsing BlueMarble...`);
|
|
|
|
const templates = json.templates;
|
|
|
|
console.log(`BlueMarble length: ${Object.keys(templates).length}`);
|
|
|
|
if (Object.keys(templates).length > 0) {
|
|
|
|
for (const template in templates) {
|
|
|
|
const templateKey = template;
|
|
const templateValue = templates[template];
|
|
console.log(templateKey);
|
|
|
|
if (templates.hasOwnProperty(template)) {
|
|
|
|
const templateKeyArray = templateKey.split(' '); // E.g., "0 $Z" -> ["0", "$Z"]
|
|
const sortID = Number(templateKeyArray?.[0]); // Sort ID of the template
|
|
const authorID = templateKeyArray?.[1] || '0'; // User ID of the person who exported the template
|
|
const displayName = templateValue.name || `Template ${sortID || ''}`; // Display name of the template
|
|
//const coords = templateValue?.coords?.split(',').map(Number); // "1,2,3,4" -> [1, 2, 3, 4]
|
|
const tilesbase64 = templateValue.tiles;
|
|
const templateTiles = {}; // Stores the template bitmap tiles for each tile.
|
|
|
|
for (const tile in tilesbase64) {
|
|
console.log(tile);
|
|
if (tilesbase64.hasOwnProperty(tile)) {
|
|
const encodedTemplateBase64 = tilesbase64[tile];
|
|
const templateUint8Array = base64ToUint8(encodedTemplateBase64); // Base 64 -> Uint8Array
|
|
|
|
const templateBlob = new Blob([templateUint8Array], { type: "image/png" }); // Uint8Array -> Blob
|
|
const templateBitmap = await createImageBitmap(templateBlob) // Blob -> Bitmap
|
|
templateTiles[tile] = templateBitmap;
|
|
}
|
|
}
|
|
|
|
// Creates a new Template class instance
|
|
const template = new Template({
|
|
displayName: displayName,
|
|
sortID: sortID || this.templatesArray?.length || 0,
|
|
authorID: authorID || '',
|
|
//coords: coords
|
|
});
|
|
template.chunked = templateTiles;
|
|
this.templatesArray.push(template);
|
|
console.log(this.templatesArray);
|
|
console.log(`^^^ This ^^^`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Parses the OSU! Place JSON object
|
|
*/
|
|
#parseOSU() {
|
|
|
|
}
|
|
|
|
/** Sets the `templatesShouldBeDrawn` boolean to a value.
|
|
* @param {boolean} value - The value to set the boolean to
|
|
* @since 0.73.7
|
|
*/
|
|
setTemplatesShouldBeDrawn(value) {
|
|
this.templatesShouldBeDrawn = value;
|
|
}
|
|
}
|
|
</code></pre>
|
|
</article>
|
|
</section>
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
<nav>
|
|
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="module.exports.html">exports</a></li><li><a href="module.exports_module.exports.html">exports</a></li></ul><h3>Global</h3><ul><li><a href="global.html#addBr">addBr</a></li><li><a href="global.html#addButton">addButton</a></li><li><a href="global.html#addButtonHelp">addButtonHelp</a></li><li><a href="global.html#addCheckbox">addCheckbox</a></li><li><a href="global.html#addDiv">addDiv</a></li><li><a href="global.html#addHeader">addHeader</a></li><li><a href="global.html#addHr">addHr</a></li><li><a href="global.html#addImg">addImg</a></li><li><a href="global.html#addInput">addInput</a></li><li><a href="global.html#addInputFile">addInputFile</a></li><li><a href="global.html#addP">addP</a></li><li><a href="global.html#addSmall">addSmall</a></li><li><a href="global.html#addTextarea">addTextarea</a></li><li><a href="global.html#base64ToUint8">base64ToUint8</a></li><li><a href="global.html#buildElement">buildElement</a></li><li><a href="global.html#buildOverlay">buildOverlay</a></li><li><a href="global.html#buildOverlayMain">buildOverlayMain</a></li><li><a href="global.html#consoleError">consoleError</a></li><li><a href="global.html#consoleLog">consoleLog</a></li><li><a href="global.html#consoleWarn">consoleWarn</a></li><li><a href="global.html#createJSON">createJSON</a></li><li><a href="global.html#createObserverBody">createObserverBody</a></li><li><a href="global.html#createTemplate">createTemplate</a></li><li><a href="global.html#createTemplateTiles">createTemplateTiles</a></li><li><a href="global.html#deleteTemplate">deleteTemplate</a></li><li><a href="global.html#disableTemplate">disableTemplate</a></li><li><a href="global.html#drawTemplateOnTile">drawTemplateOnTile</a></li><li><a href="global.html#escapeHTML">escapeHTML</a></li><li><a href="global.html#getObserverBody">getObserverBody</a></li><li><a href="global.html#handleDisplayError">handleDisplayError</a></li><li><a href="global.html#handleDisplayStatus">handleDisplayStatus</a></li><li><a href="global.html#handleDrag">handleDrag</a></li><li><a href="global.html#importJSON">importJSON</a></li><li><a href="global.html#inject">inject</a></li><li><a href="global.html#negativeSafeModulo">negativeSafeModulo</a></li><li><a href="global.html#numberToEncoded">numberToEncoded</a></li><li><a href="global.html#observe">observe</a></li><li><a href="global.html#observeBlack">observeBlack</a></li><li><a href="global.html#serverTPtoDisplayTP">serverTPtoDisplayTP</a></li><li><a href="global.html#setApiManager">setApiManager</a></li><li><a href="global.html#setTemplatesShouldBeDrawn">setTemplatesShouldBeDrawn</a></li><li><a href="global.html#spontaneousResponseListener">spontaneousResponseListener</a></li><li><a href="global.html#uint8ToBase64">uint8ToBase64</a></li><li><a href="global.html#updateInnerHTML">updateInnerHTML</a></li></ul>
|
|
</nav>
|
|
|
|
<br class="clear">
|
|
|
|
<footer>
|
|
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Fri Aug 08 2025 16:09:33 GMT-0400 (Eastern Daylight Time)
|
|
</footer>
|
|
|
|
<script> prettyPrint(); </script>
|
|
<script src="scripts/linenumber.js"> </script>
|
|
</body>
|
|
</html>
|