Merge pull request #98 from SwingTheVine/documentation

Updated documentation
This commit is contained in:
SwingTheVine 2025-08-10 21:40:27 -04:00 committed by GitHub
commit 4df36702dc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
44 changed files with 165 additions and 23554 deletions

View file

@ -1,8 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: Community Support & Questions
- name: Community Support & Questions (Discord)
url: https://discord.gg/tpeBPy46hf
about: Join the Discord if you have questions or want to discuss this mod with the community.
about: Join the Discord if you have questions or want to discuss Blue Marble with the community.
- name: Community Support & Questions (GitHub)
url: https://github.com/SwingTheVine/Wplace-BlueMarble/discussions
about: Go to the "Discussion" tab if you have questions or want to discuss Blue Marble with the community.
- name: Partnership Request
url: https://discord.com/channels/796124137042608188/1257365507812888589
about: Open a ticket in the Discord server to discuss a partnership.

View file

@ -1,60 +0,0 @@
name: "Pull Request"
description: "Fill out the following details to submit your PR."
title: "[PR]: "
body:
- type: markdown
attributes:
value: |
## Summary
Please briefly describe the changes.
- type: textarea
id: summary
attributes:
label: Summary
description: Briefly describe what this PR does.
placeholder: |
E.g. Fixes display bug with templates.
E.g. Adds a template tab that users can manage all templates through.
validations:
required: true
- type: textarea
id: related-issues
attributes:
label: Related Issue(s)
description: Link related issues
placeholder: |
E.g. Fixes #14
E.g. Adds #4
validations:
required: false
- type: checkboxes
id: changes
attributes:
label: Type of Changes
options:
- label: Feature
- label: Bug fix
- label: Documentation
- label: Refactoring
- label: Build
- label: Other
- type: checkboxes
id: checklist
attributes:
label: Checklist
options:
- label: The author of this PR has read the CONTRIBUTING guidelines.
- label: This PR follows the Code of Conduct.
- label: This PR follows the project's style of coding and documentation.
- label: Documentation related to this PR has been updated.
- label: Blue Marble has been verified to work correctly for this PR.
- type: textarea
id: additional-notes
attributes:
label: Additional Notes
description: Anything else reviewers should know?

View file

@ -0,0 +1,31 @@
# Pull Request
Fill out the following details to submit your PR.
## Summary
Please briefly describe the changes in your PR.
E.g. Fixes display bug with templates.
E.g. Adds a template tab that users can manage all templates through.
## Related Issue(s)
Link to the related issues your PR would solve here.
E.g. Fixes #14
E.g. Adds #4
## Changes
Select the type of change your PR is:
- [ ] Feature
- [ ] Bug fix
- [ ] Documentation
- [ ] Refactoring
- [ ] Build
- [ ] Other
## Checklist
- [ ] The author of this PR has read the CONTRIBUTING guidelines.
- [ ] This PR follows the Code of Conduct.
- [ ] This PR follows the project's style of coding and documentation.
- [ ] Documentation related to this PR has been updated.
- [ ] Blue Marble has been verified to work correctly for this PR.
## Additional Notes
Anything else reviewers should know?

View file

@ -78,7 +78,10 @@ jobs:
else
echo "README.md was not found. Skipping..."
fi
sed -i 's|\(Latest_Version-\)[^-\ ]*\(-lightblue\)|\1'$current_version'\2|' docs/README.md
sed -i \
-e 's|\(Latest_Version-\)[^-\ ]*\(-lightblue\)|\1'$current_version'\2|' \
-e 's|v[0-9]\+\.[0-9]\+\.[0-9]\+|v'"$current_version"'|g' \
docs/README.md
- name: Update compression badge
run: |
@ -171,3 +174,45 @@ jobs:
git merge --squash origin/auto
git commit -m "v${{ needs.build.outputs.CURRENT_VERSION }}; ${{ needs.build.outputs.TITLE }}" -m "${{ needs.build.outputs.BODY }}" || echo "No changes to commit"
git push origin main
update-wiki:
permissions:
contents: write
runs-on: ubuntu-latest
needs: [update-auto, build, update-requirements] # Needs the update-auto, build, and update-requirements jobs to finish first
steps:
- name: Checkout main branch
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Force update wiki branch
run: |
git fetch origin
git branch -f wiki origin/main
git push origin wiki --force
- name: Checkout wiki branch
run: git checkout wiki
- name: Install dependencies
run: |
npm ci
npm install minami --no-save
- name: Generate JSDoc from jsdoc.json
run: |
npx jsdoc -c jsdoc.json
- name: Commit and push to wiki branch
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}
git add docs
git commit -m "Update wiki via JSDoc" || echo "No changes to commit"
git push origin wiki

View file

@ -25,16 +25,16 @@ const isGitHub = !!process.env?.GITHUB_ACTIONS; // Is this running in a GitHub A
console.log(`${consoleStyle.BLUE}Starting build...${consoleStyle.RESET}`);
// Tries to build the wiki if build.js is run in a GitHub Workflow
if (isGitHub) {
try {
console.log(`Generating JSDoc...`);
execSync(`npx jsdoc src/ -r -d docs -t node_modules/minami`, { stdio: "inherit" });
console.log(`JSDoc built ${consoleStyle.GREEN}successfully${consoleStyle.RESET}`);
} catch (error) {
console.error(`${consoleStyle.RED + consoleStyle.BOLD}Failed to generate JSDoc${consoleStyle.RESET}:`, error);
process.exit(1);
}
}
// if (isGitHub) {
// try {
// console.log(`Generating JSDoc...`);
// execSync(`npx jsdoc src/ -r -d docs -t node_modules/minami`, { stdio: "inherit" });
// console.log(`JSDoc built ${consoleStyle.GREEN}successfully${consoleStyle.RESET}`);
// } catch (error) {
// console.error(`${consoleStyle.RED + consoleStyle.BOLD}Failed to generate JSDoc${consoleStyle.RESET}:`, error);
// process.exit(1);
// }
// }
// Tries to bump the version
try {

File diff suppressed because one or more lines are too long

View file

@ -1,748 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: Overlay.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: Overlay.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/** The overlay builder for the Blue Marble script.
* @description This class handles the overlay UI for the Blue Marble script.
* @since 0.0.2
* @example
* const overlay = new Overlay();
* overlay.addDiv({ 'id': 'overlay' })
* .addDiv({ 'id': 'header' })
* .addHeader(1, {'textContent': 'Your Overlay'}).buildElement()
* .addP({'textContent': 'This is your overlay. It is versatile.'}).buildElement()
* .buildElement() // Marks the end of the header &lt;div>
* .addHr().buildElement()
* .buildOverlay(document.body);
* // Output:
* // (Assume &lt;body> already exists in the webpage)
* &lt;body>
* &lt;div id="overlay">
* &lt;div id="header">
* &lt;h1>Your Overlay&lt;/h1>
* &lt;p>This is your overlay. It is versatile.&lt;/p>
* &lt;/div>
* &lt;hr>
* &lt;/div>
* &lt;/body>
*/
export default class Overlay {
/** Constructor for the Overlay class.
* @param {string} name - The name of the userscript
* @param {string} version - The version of the userscript
* @since 0.0.2
* @see {@link Overlay}
*/
constructor(name, version) {
this.name = name; // Name of userscript
this.version = version; // Version of userscript
this.apiManager = null; // The API manager instance. Later populated when setApiManager is called
this.outputStatusId = 'bm-output-status'; // ID for status element
this.overlay = null; // The overlay root DOM HTMLElement
this.currentParent = null; // The current parent HTMLElement in the overlay
this.parentStack = []; // Tracks the parent elements BEFORE the currentParent so we can nest elements
}
/** Populates the apiManager variable with the apiManager class.
* @param {apiManager} apiManager - The apiManager class instance
* @since 0.41.4
*/
setApiManager(apiManager) {this.apiManager = apiManager;}
/** Creates an element.
* For **internal use** of the {@link Overlay} class.
* @param {string} tag - The tag name as a string.
* @param {Object.&lt;string, any>} [properties={}] - The DOM properties of the element.
* @returns {HTMLElement} HTML Element
* @since 0.43.2
*/
#createElement(tag, properties = {}, additionalProperties={}) {
const element = document.createElement(tag); // Creates the element
// If this is the first element made...
if (!this.overlay) {
this.overlay = element; // Declare it the highest overlay element
this.currentParent = element;
} else {
this.currentParent?.appendChild(element); // ...else delcare it the child of the last element
this.parentStack.push(this.currentParent);
this.currentParent = element;
}
// For every passed in property (shared by all like-elements), apply the it to the element
for (const [property, value] of Object.entries(properties)) {
element[property] = value;
}
// For every passed in additional property, apply the it to the element
for (const [property, value] of Object.entries(additionalProperties)) {
element[property] = value;
}
return element;
}
/** Finishes building an element.
* Call this after you are finished adding children.
* If the element will have no children, call it anyways.
* @returns {Overlay} Overlay class instance (this)
* @since 0.43.2
* @example
* overlay
* .addDiv()
* .addHeader(1).buildElement() // Breaks out of the &lt;h1>
* .addP().buildElement() // Breaks out of the &lt;p>
* .buildElement() // Breaks out of the &lt;div>
* .addHr() // Since there are no more elements, calling buildElement() is optional
* .buildOverlay(document.body);
*/
buildElement() {
if (this.parentStack.length > 0) {
this.currentParent = this.parentStack.pop();
}
return this;
}
/** Finishes building the overlay and displays it.
* Call this when you are done chaining methods.
* @param {HTMLElement} parent - The parent HTMLElement this overlay should be appended to as a child.
* @since 0.43.2
* @example
* overlay
* .addDiv()
* .addP().buildElement()
* .buildElement()
* .buildOverlay(document.body); // Adds DOM structure to document body
* // &lt;div>&lt;p>&lt;/p>&lt;/div>
*/
buildOverlay(parent) {
parent?.appendChild(this.overlay);
// Resets the class-bound variables of this class instance back to default so overlay can be build again later
this.overlay = null;
this.currentParent = null;
this.parentStack = [];
}
/** Adds a `div` to the overlay.
* This `div` element will have properties shared between all `div` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.&lt;string, any>} [additionalProperties={}] - The DOM properties of the `div` that are NOT shared between all overlay `div` elements. These should be camelCase.
* @param {function(Overlay, HTMLDivElement):void} [callback=()=>{}] - Additional JS modification to the `div`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.43.2
* @example
* // Assume all &lt;div> elements have a shared class (e.g. {'className': 'bar'})
* overlay.addDiv({'id': 'foo'}).buildOverlay(document.body);
* // Output:
* // (Assume &lt;body> already exists in the webpage)
* &lt;body>
* &lt;div id="foo" class="bar">&lt;/div>
* &lt;/body>
*/
addDiv(additionalProperties = {}, callback = () => {}) {
const properties = {}; // Shared &lt;div> DOM properties
const div = this.#createElement('div', properties, additionalProperties); // Creates the &lt;div> element
callback(this, div); // Runs any script passed in through the callback
return this;
}
/** Adds a `p` to the overlay.
* This `p` element will have properties shared between all `p` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.&lt;string, any>} [additionalProperties={}] - The DOM properties of the `p` that are NOT shared between all overlay `p` elements. These should be camelCase.
* @param {function(Overlay, HTMLParagraphElement):void} [callback=()=>{}] - Additional JS modification to the `p`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.43.2
* @example
* // Assume all &lt;p> elements have a shared class (e.g. {'className': 'bar'})
* overlay.addP({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body);
* // Output:
* // (Assume &lt;body> already exists in the webpage)
* &lt;body>
* &lt;p id="foo" class="bar">Foobar.&lt;/p>
* &lt;/body>
*/
addP(additionalProperties = {}, callback = () => {}) {
const properties = {}; // Shared &lt;p> DOM properties
const p = this.#createElement('p', properties, additionalProperties); // Creates the &lt;p> element
callback(this, p); // Runs any script passed in through the callback
return this;
}
/** Adds a `small` to the overlay.
* This `small` element will have properties shared between all `small` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.&lt;string, any>} [additionalProperties={}] - The DOM properties of the `small` that are NOT shared between all overlay `small` elements. These should be camelCase.
* @param {function(Overlay, HTMLParagraphElement):void} [callback=()=>{}] - Additional JS modification to the `small`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.55.8
* @example
* // Assume all &lt;small> elements have a shared class (e.g. {'className': 'bar'})
* overlay.addSmall({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body);
* // Output:
* // (Assume &lt;body> already exists in the webpage)
* &lt;body>
* &lt;small id="foo" class="bar">Foobar.&lt;/small>
* &lt;/body>
*/
addSmall(additionalProperties = {}, callback = () => {}) {
const properties = {}; // Shared &lt;small> DOM properties
const small = this.#createElement('small', properties, additionalProperties); // Creates the &lt;small> element
callback(this, small); // Runs any script passed in through the callback
return this;
}
/** Adds a `img` to the overlay.
* This `img` element will have properties shared between all `img` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.&lt;string, any>} [additionalProperties={}] - The DOM properties of the `img` that are NOT shared between all overlay `img` elements. These should be camelCase.
* @param {function(Overlay, HTMLImageElement):void} [callback=()=>{}] - Additional JS modification to the `img`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.43.2
* @example
* // Assume all &lt;img> elements have a shared class (e.g. {'className': 'bar'})
* overlay.addimg({'id': 'foo', 'src': './img.png'}).buildOverlay(document.body);
* // Output:
* // (Assume &lt;body> already exists in the webpage)
* &lt;body>
* &lt;img id="foo" src="./img.png" class="bar">
* &lt;/body>
*/
addImg(additionalProperties = {}, callback = () => {}) {
const properties = {}; // Shared &lt;img> DOM properties
const img = this.#createElement('img', properties, additionalProperties); // Creates the &lt;img> element
callback(this, img); // Runs any script passed in through the callback
return this;
}
/** Adds a header to the overlay.
* This header element will have properties shared between all header elements in the overlay.
* You can override the shared properties by using a callback.
* @param {number} level - The header level. Must be between 1 and 6 (inclusive)
* @param {Object.&lt;string, any>} [additionalProperties={}] - The DOM properties of the header that are NOT shared between all overlay header elements. These should be camelCase.
* @param {function(Overlay, HTMLHeadingElement):void} [callback=()=>{}] - Additional JS modification to the header.
* @returns {Overlay} Overlay class instance (this)
* @since 0.43.7
* @example
* // Assume all header elements have a shared class (e.g. {'className': 'bar'})
* overlay.addHeader(6, {'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body);
* // Output:
* // (Assume &lt;body> already exists in the webpage)
* &lt;body>
* &lt;h6 id="foo" class="bar">Foobar.&lt;/h6>
* &lt;/body>
*/
addHeader(level, additionalProperties = {}, callback = () => {}) {
const properties = {}; // Shared header DOM properties
const header = this.#createElement('h' + level, properties, additionalProperties); // Creates the header element
callback(this, header); // Runs any script passed in through the callback
return this;
}
/** Adds a `hr` to the overlay.
* This `hr` element will have properties shared between all `hr` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.&lt;string, any>} [additionalProperties={}] - The DOM properties of the `hr` that are NOT shared between all overlay `hr` elements. These should be camelCase.
* @param {function(Overlay, HTMLHRElement):void} [callback=()=>{}] - Additional JS modification to the `hr`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.43.7
* @example
* // Assume all &lt;hr> elements have a shared class (e.g. {'className': 'bar'})
* overlay.addhr({'id': 'foo'}).buildOverlay(document.body);
* // Output:
* // (Assume &lt;body> already exists in the webpage)
* &lt;body>
* &lt;hr id="foo" class="bar">
* &lt;/body>
*/
addHr(additionalProperties = {}, callback = () => {}) {
const properties = {}; // Shared &lt;hr> DOM properties
const hr = this.#createElement('hr', properties, additionalProperties); // Creates the &lt;hr> element
callback(this, hr); // Runs any script passed in through the callback
return this;
}
/** Adds a `br` to the overlay.
* This `br` element will have properties shared between all `br` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.&lt;string, any>} [additionalProperties={}] - The DOM properties of the `br` that are NOT shared between all overlay `br` elements. These should be camelCase.
* @param {function(Overlay, HTMLBRElement):void} [callback=()=>{}] - Additional JS modification to the `br`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.43.11
* @example
* // Assume all &lt;br> elements have a shared class (e.g. {'className': 'bar'})
* overlay.addbr({'id': 'foo'}).buildOverlay(document.body);
* // Output:
* // (Assume &lt;body> already exists in the webpage)
* &lt;body>
* &lt;br id="foo" class="bar">
* &lt;/body>
*/
addBr(additionalProperties = {}, callback = () => {}) {
const properties = {}; // Shared &lt;br> DOM properties
const br = this.#createElement('br', properties, additionalProperties); // Creates the &lt;br> element
callback(this, br); // Runs any script passed in through the callback
return this;
}
/** Adds a checkbox to the overlay.
* This checkbox element will have properties shared between all checkbox elements in the overlay.
* You can override the shared properties by using a callback. Note: the checkbox element is inside a label element.
* @param {Object.&lt;string, any>} [additionalProperties={}] - The DOM properties of the checkbox that are NOT shared between all overlay checkbox elements. These should be camelCase.
* @param {function(Overlay, HTMLLabelElement, HTMLInputElement):void} [callback=()=>{}] - Additional JS modification to the checkbox.
* @returns {Overlay} Overlay class instance (this)
* @since 0.43.10
* @example
* // Assume all checkbox elements have a shared class (e.g. {'className': 'bar'})
* overlay.addCheckbox({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body);
* // Output:
* // (Assume &lt;body> already exists in the webpage)
* &lt;body>
* &lt;label>
* &lt;input type="checkbox" id="foo" class="bar">
* "Foobar."
* &lt;/label>
* &lt;/body>
*/
addCheckbox(additionalProperties = {}, callback = () => {}) {
const properties = {'type': 'checkbox'}; // Shared checkbox DOM properties
const label = this.#createElement('label', {'textContent': additionalProperties['textContent'] ?? ''}); // Creates the label element
delete additionalProperties['textContent']; // Deletes 'textContent' DOM property before adding the properties to the checkbox
const checkbox = this.#createElement('input', properties, additionalProperties); // Creates the checkbox element
label.insertBefore(checkbox, label.firstChild); // Makes the checkbox the first child of the label (before the text content)
this.buildElement(); // Signifies that we are done adding children to the checkbox
callback(this, label, checkbox); // Runs any script passed in through the callback
return this;
}
/** Adds a `button` to the overlay.
* This `button` element will have properties shared between all `button` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.&lt;string, any>} [additionalProperties={}] - The DOM properties of the `button` that are NOT shared between all overlay `button` elements. These should be camelCase.
* @param {function(Overlay, HTMLButtonElement):void} [callback=()=>{}] - Additional JS modification to the `button`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.43.12
* @example
* // Assume all &lt;button> elements have a shared class (e.g. {'className': 'bar'})
* overlay.addButton({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body);
* // Output:
* // (Assume &lt;body> already exists in the webpage)
* &lt;body>
* &lt;button id="foo" class="bar">Foobar.&lt;/button>
* &lt;/body>
*/
addButton(additionalProperties = {}, callback = () => {}) {
const properties = {}; // Shared &lt;button> DOM properties
const button = this.#createElement('button', properties, additionalProperties); // Creates the &lt;button> element
callback(this, button); // Runs any script passed in through the callback
return this;
}
/** Adds a help button to the overlay. It will have a "?" icon unless overridden in callback.
* On click, the button will attempt to output the title to the output element (ID defined in Overlay constructor).
* This `button` element will have properties shared between all `button` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.&lt;string, any>} [additionalProperties={}] - The DOM properties of the `button` that are NOT shared between all overlay `button` elements. These should be camelCase.
* @param {function(Overlay, HTMLButtonElement):void} [callback=()=>{}] - Additional JS modification to the `button`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.43.12
* @example
* // Assume all help button elements have a shared class (e.g. {'className': 'bar'})
* overlay.addButtonHelp({'id': 'foo', 'title': 'Foobar.'}).buildOverlay(document.body);
* // Output:
* // (Assume &lt;body> already exists in the webpage)
* &lt;body>
* &lt;button id="foo" class="bar" title="Help: Foobar.">?&lt;/button>
* &lt;/body>
* @example
* // Assume all help button elements have a shared class (e.g. {'className': 'bar'})
* overlay.addButtonHelp({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body);
* // Output:
* // (Assume &lt;body> already exists in the webpage)
* &lt;body>
* &lt;button id="foo" class="bar" title="Help: Foobar.">?&lt;/button>
* &lt;/body>
*/
addButtonHelp(additionalProperties = {}, callback = () => {}) {
const tooltip = additionalProperties['title'] ?? additionalProperties['textContent'] ?? 'Help: No info'; // Retrieves the tooltip
// Makes sure the tooltip is stored in the title property
delete additionalProperties['textContent'];
additionalProperties['title'] = `Help: ${tooltip}`;
// Shared help button DOM properties
const properties = {
'textContent': '?',
'className': 'bm-help',
'onclick': () => {
this.updateInnerHTML(this.outputStatusId, tooltip);
}
};
const help = this.#createElement('button', properties, additionalProperties); // Creates the &lt;button> element
callback(this, help); // Runs any script passed in through the callback
return this;
}
/** Adds a `input` to the overlay.
* This `input` element will have properties shared between all `input` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.&lt;string, any>} [additionalProperties={}] - The DOM properties of the `input` that are NOT shared between all overlay `input` elements. These should be camelCase.
* @param {function(Overlay, HTMLInputElement):void} [callback=()=>{}] - Additional JS modification to the `input`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.43.13
* @example
* // Assume all &lt;input> elements have a shared class (e.g. {'className': 'bar'})
* overlay.addInput({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body);
* // Output:
* // (Assume &lt;body> already exists in the webpage)
* &lt;body>
* &lt;input id="foo" class="bar">Foobar.&lt;/input>
* &lt;/body>
*/
addInput(additionalProperties = {}, callback = () => {}) {
const properties = {}; // Shared &lt;input> DOM properties
const input = this.#createElement('input', properties, additionalProperties); // Creates the &lt;input> element
callback(this, input); // Runs any script passed in through the callback
return this;
}
/** Adds a file input to the overlay with enhanced visibility controls.
* This input element will have properties shared between all file input elements in the overlay.
* Uses multiple hiding methods to prevent browser native text from appearing during minimize/maximize.
* You can override the shared properties by using a callback.
* @param {Object.&lt;string, any>} [additionalProperties={}] - The DOM properties of the file input that are NOT shared between all overlay file input elements. These should be camelCase.
* @param {function(Overlay, HTMLDivElement, HTMLInputElement, HTMLButtonElement):void} [callback=()=>{}] - Additional JS modification to the file input.
* @returns {Overlay} Overlay class instance (this)
* @since 0.43.17
* @example
* // Assume all file input elements have a shared class (e.g. {'className': 'bar'})
* overlay.addInputFile({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body);
* // Output:
* // (Assume &lt;body> already exists in the webpage)
* &lt;body>
* &lt;div>
* &lt;input type="file" id="foo" class="bar" style="display: none">&lt;/input>
* &lt;button>Foobar.&lt;/button>
* &lt;/div>
* &lt;/body>
*/
addInputFile(additionalProperties = {}, callback = () => {}) {
const properties = {
'type': 'file',
'style': 'display: none !important; visibility: hidden !important; position: absolute !important; left: -9999px !important; width: 0 !important; height: 0 !important; opacity: 0 !important;'
}; // Complete file input hiding to prevent native browser text interference
const text = additionalProperties['textContent'] ?? ''; // Retrieves the text content
delete additionalProperties['textContent']; // Deletes the text content before applying the additional properties to the file input
const container = this.#createElement('div'); // Container for file input
const input = this.#createElement('input', properties, additionalProperties); // Creates the file input
this.buildElement(); // Signifies that we are done adding children to the file input
const button = this.#createElement('button', {'textContent': text});
this.buildElement(); // Signifies that we are done adding children to the button
this.buildElement(); // Signifies that we are done adding children to the container
// Prevent file input from being accessible or visible by screen-readers and tabbing
input.setAttribute('tabindex', '-1');
input.setAttribute('aria-hidden', 'true');
button.addEventListener('click', () => {
input.click(); // Clicks the file input
});
// Update button text when file is selected
input.addEventListener('change', () => {
button.style.maxWidth = `${button.offsetWidth}px`;
if (input.files.length > 0) {
button.textContent = input.files[0].name;
} else {
button.textContent = text;
}
});
callback(this, container, input, button); // Runs any script passed in through the callback
return this;
}
/** Adds a `textarea` to the overlay.
* This `textarea` element will have properties shared between all `textarea` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.&lt;string, any>} [additionalProperties={}] - The DOM properties of the `textarea` that are NOT shared between all overlay `textarea` elements. These should be camelCase.
* @param {function(Overlay, HTMLTextAreaElement):void} [callback=()=>{}] - Additional JS modification to the `textarea`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.43.13
* @example
* // Assume all &lt;textarea> elements have a shared class (e.g. {'className': 'bar'})
* overlay.addTextarea({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body);
* // Output:
* // (Assume &lt;body> already exists in the webpage)
* &lt;body>
* &lt;textarea id="foo" class="bar">Foobar.&lt;/textarea>
* &lt;/body>
*/
addTextarea(additionalProperties = {}, callback = () => {}) {
const properties = {}; // Shared &lt;textarea> DOM properties
const textarea = this.#createElement('textarea', properties, additionalProperties); // Creates the &lt;textarea> element
callback(this, textarea); // Runs any script passed in through the callback
return this;
}
/** Updates the inner HTML of the element.
* The element is discovered by it's id.
* If the element is an `input`, it will modify the value attribute instead.
* @param {string} id - The ID of the element to change
* @param {string} html - The HTML/text to update with
* @param {boolean} [doSafe] - (Optional) Should `textContent` be used instead of `innerHTML` to avoid XSS? False by default
* @since 0.24.2
*/
updateInnerHTML(id, html, doSafe=false) {
const element = document.getElementById(id.replace(/^#/, '')); // Retrieve the element from the 'id' (removed the '#')
if (!element) {return;} // Kills itself if the element does not exist
// Input elements don't have innerHTML, so we modify the value attribute instead
if (element instanceof HTMLInputElement) {
element.value = html;
return;
}
if (doSafe) {
element.textContent = html; // Populate element with plain-text HTML/text
} else {
element.innerHTML = html; // Populate element with HTML/text
}
}
/** Handles dragging of the overlay.
* Uses requestAnimationFrame for smooth animations and GPU-accelerated transforms.
* @param {string} moveMe - The ID of the element to be moved
* @param {string} iMoveThings - The ID of the drag handle element
* @since 0.8.2
*/
handleDrag(moveMe, iMoveThings) {
let isDragging = false;
let offsetX, offsetY = 0;
let animationFrame = null;
let currentX = 0;
let currentY = 0;
let targetX = 0;
let targetY = 0;
// Retrieves the elements (allows either '#id' or 'id' to be passed in)
moveMe = document.querySelector(moveMe?.[0] == '#' ? moveMe : '#' + moveMe);
iMoveThings = document.querySelector(iMoveThings?.[0] == '#' ? iMoveThings : '#' + iMoveThings);
// What to do when one of the two elements are not found
if (!moveMe || !iMoveThings) {
this.handleDisplayError(`Can not drag! ${!moveMe ? 'moveMe' : ''} ${!moveMe &amp;&amp; !iMoveThings ? 'and ' : ''}${!iMoveThings ? 'iMoveThings ' : ''}was not found!`);
return; // Kills itself
}
// Smooth animation loop using requestAnimationFrame for optimal performance
const updatePosition = () => {
if (isDragging) {
// Only update DOM if position changed significantly (reduce repaints)
const deltaX = Math.abs(currentX - targetX);
const deltaY = Math.abs(currentY - targetY);
if (deltaX > 0.5 || deltaY > 0.5) {
currentX = targetX;
currentY = targetY;
// Use CSS transform for GPU acceleration instead of left/top
moveMe.style.transform = `translate(${currentX}px, ${currentY}px)`;
moveMe.style.left = '0px';
moveMe.style.top = '0px';
moveMe.style.right = '';
}
animationFrame = requestAnimationFrame(updatePosition);
}
};
// Cache initial position to avoid expensive getBoundingClientRect calls during drag
let initialRect = null;
const startDrag = (clientX, clientY) => {
isDragging = true;
initialRect = moveMe.getBoundingClientRect();
offsetX = clientX - initialRect.left;
offsetY = clientY - initialRect.top;
// Get current position from transform or use element position
const computedStyle = window.getComputedStyle(moveMe);
const transform = computedStyle.transform;
if (transform &amp;&amp; transform !== 'none') {
const matrix = new DOMMatrix(transform);
currentX = matrix.m41;
currentY = matrix.m42;
} else {
currentX = initialRect.left;
currentY = initialRect.top;
}
targetX = currentX;
targetY = currentY;
document.body.style.userSelect = 'none';
iMoveThings.classList.add('dragging');
// Start animation loop
if (animationFrame) {
cancelAnimationFrame(animationFrame);
}
updatePosition();
};
const endDrag = () => {
isDragging = false;
if (animationFrame) {
cancelAnimationFrame(animationFrame);
animationFrame = null;
}
document.body.style.userSelect = '';
iMoveThings.classList.remove('dragging');
};
// Mouse down - start dragging
iMoveThings.addEventListener('mousedown', function(event) {
event.preventDefault();
startDrag(event.clientX, event.clientY);
});
// Touch start - start dragging
iMoveThings.addEventListener('touchstart', function(event) {
const touch = event?.touches?.[0];
if (!touch) {return;}
startDrag(touch.clientX, touch.clientY);
event.preventDefault();
}, { passive: false });
// Mouse move - update target position
document.addEventListener('mousemove', function(event) {
if (isDragging &amp;&amp; initialRect) {
targetX = event.clientX - offsetX;
targetY = event.clientY - offsetY;
}
}, { passive: true });
// Touch move - update target position
document.addEventListener('touchmove', function(event) {
if (isDragging &amp;&amp; initialRect) {
const touch = event?.touches?.[0];
if (!touch) {return;}
targetX = touch.clientX - offsetX;
targetY = touch.clientY - offsetY;
event.preventDefault();
}
}, { passive: false });
// End drag events
document.addEventListener('mouseup', endDrag);
document.addEventListener('touchend', endDrag);
document.addEventListener('touchcancel', endDrag);
}
/** Handles status display.
* This will output plain text into the output Status box.
* Additionally, this will output an info message to the console.
* @param {string} text - The status text to display.
* @since 0.58.4
*/
handleDisplayStatus(text) {
const consoleInfo = console.info; // Creates a copy of the console.info function
consoleInfo(`${this.name}: ${text}`); // Outputs something like "ScriptName: text" as an info message to the console
this.updateInnerHTML(this.outputStatusId, 'Status: ' + text, true); // Update output Status box
}
/** Handles error display.
* This will output plain text into the output Status box.
* Additionally, this will output an error to the console.
* @param {string} text - The error text to display.
* @since 0.41.6
*/
handleDisplayError(text) {
const consoleError = console.error; // Creates a copy of the console.error function
consoleError(`${this.name}: ${text}`); // Outputs something like "ScriptName: text" as an error message to the console
this.updateInnerHTML(this.outputStatusId, 'Error: ' + text, true); // Update output Status box
}
}</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>

View file

@ -3,6 +3,9 @@
<td><a href="#blue-marble">Blue Marble</a></td>
<td valign="top" rowspan="99"><a href="https://discord.gg/tpeBPy46hf"><img alt="Discord Banner" src="https://discord.com/api/guilds/796124137042608188/widget.png?style=banner4"></a></td>
</tr>
<tr>
<td>&emsp;<a href="#quick-guide">Quick Guide</a></td>
</tr>
<tr>
<td>&emsp;<a href="#overview">Overview</a></td>
</tr>
@ -39,19 +42,59 @@
</table>
<h1>Blue Marble</h1>
<a href="https://wplacestatus.sobakintech.xyz" target="_blank" rel="noopener noreferrer"><img alt="Wplace Status" src="https://wplacestatus.sobakintech.xyz/api/badge/15/status"></a>
<a href="" target="_blank" rel="noopener noreferrer"><img alt="Latest Version" src="https://img.shields.io/badge/Latest_Version-0.78.0-lightblue?style=flat"></a>
<a href="https://github.com/SwingTheVine/Wplace-BlueMarble/releases" target="_blank" rel="noopener noreferrer"><img alt="Latest Release" src="https://img.shields.io/github/v/release/SwingTheVine/Wplace-BlueMarble?sort=semver&style=flat&label=Latest%20Release&color=blue"></a>
<a href="https://github.com/SwingTheVine/Wplace-BlueMarble/blob/main/LICENSE.txt" target="_blank" rel="noopener noreferrer"><img alt="Software License: MPL-2.0" src="https://img.shields.io/badge/Software_License-MPL--2.0-slateblue?style=flat"></a>
<a href="https://discord.gg/tpeBPy46hf" target="_blank" rel="noopener noreferrer"><img alt="Contact Me" src="https://img.shields.io/badge/Contact_Me-gray?style=flat&logo=Discord&logoColor=white&logoSize=auto&labelColor=cornflowerblue"></a>
<a href="" target="_blank" rel="noopener noreferrer"><img alt="WakaTime" src="https://img.shields.io/badge/Coding_Time-91hrs_0mins-blue?style=flat&logo=wakatime&logoColor=black&logoSize=auto&labelColor=white"></a>
<a href="" target="_blank" rel="noopener noreferrer"><img alt="WakaTime" src="https://img.shields.io/badge/Coding_Time-111hrs_12mins-blue?style=flat&logo=wakatime&logoColor=black&logoSize=auto&labelColor=white"></a>
<a href="" target="_blank" rel="noopener noreferrer"><img alt="Total Patches" src="https://img.shields.io/badge/Total_Patches-494-black?style=flat"></a>
<a href="" target="_blank" rel="noopener noreferrer"><img alt="Total Lines of Code" src="https://tokei.rs/b1/github/SwingTheVine/Wplace-BlueMarble?category=code"></a>
<a href="" target="_blank" rel="noopener noreferrer"><img alt="Total Comments" src="https://tokei.rs/b1/github/SwingTheVine/Wplace-BlueMarble?category=comments"></a>
<a href="" target="_blank" rel="noopener noreferrer"><img alt="Compression" src="https://img.shields.io/badge/Compression-73.86%25-blue"></a>
<a href="" target="_blank" rel="noopener noreferrer"><img alt="Visitors" src="https://img.shields.io/badge/Visitors-37_847-gainsboro?style=flat"></a>
<a href="" target="_blank" rel="noopener noreferrer"><img alt="Repo Size" src="https://img.shields.io/github/repo-size/SwingTheVine/Wplace-BlueMarble"></a>
<a href="" target="_blank" rel="noopener noreferrer"><img alt="Visitors" src="https://img.shields.io/badge/Visitors-84_851-gainsboro?style=flat"></a>
<a href="" target="_blank" rel="noopener noreferrer"><img alt="Downloads" src="https://img.shields.io/github/downloads/SwingTheVine/Wplace-BlueMarble/total.svg"></a>
<a href="" target="_blank" rel="noopener noreferrer"><img alt="Build" src="https://github.com/SwingTheVine/Wplace-BlueMarble/actions/workflows/build.yml/badge.svg"></a>
<a href="" target="_blank" rel="noopener noreferrer"><img alt="CodeQL" src="https://github.com/SwingTheVine/Wplace-BlueMarble/actions/workflows/github-code-scanning/codeql/badge.svg"></a>
<h2>Quick Guide</h2>
<p>
Press the arrows to reveal the option you want.
<details>
<summary>
<b>I want to download Blue Marble.</b> <sup>(Click to Expand)</sup>
</summary>
<a href="#installation-instructions">Click here</a> to view the installation instructions.
</details>
<details>
<summary>
<b>I want to ask questions about Blue Marble.</b> <sup>(Click to Expand)</sup>
</summary>
<a href="https://discord.gg/tpeBPy46hf" target="_blank" rel="noopener noreferrer">Click here</a> for the Discord server invite to the Blue Marble support server.
<br>
<a href="https://github.com/SwingTheVine/Wplace-BlueMarble/discussions/categories/q-a">Click here</a> for the GitHub help & question page for Blue Marble.
</details>
<details>
<summary>
<b>I want to report a bug.</b> <sup>(Click to Expand)</sup>
</summary>
<a href="https://github.com/SwingTheVine/Wplace-BlueMarble/issues/new/choose">Click here</a> to report a bug, then choose the "Bug Report" option.
</details>
<details>
<summary>
<b>I want to suggest a feature.</b> <sup>(Click to Expand)</sup>
</summary>
<a href="https://github.com/SwingTheVine/Wplace-BlueMarble/issues/new/choose">Click here</a> to suggest a feature, then choose the Feature Request" option.
</details>
<details>
<summary>
<b>I want to contribute.</b> <sup>(Click to Expand)</sup>
</summary>
<a href="https://github.com/SwingTheVine/Wplace-BlueMarble/blob/main/docs/CONTRIBUTING.md">Click here</a> to read the contributing guidelines.
</details>
</p>
<h2>Overview</h2>
<p>
Welcome to Blue Marble! Blue Marble is a userscript for the website <a href="https://wplace.live/" target="_blank" rel="noopener noreferrer">wplace.live</a>. If you like this userscript, please ⭐ the repository! If you wish to contribute to Blue Marble, check out the <a href="https://github.com/SwingTheVine/Wplace-BlueMarble/blob/main/docs/CONTRIBUTING.md" target="_blank" rel="noopener noreferrer">CONTRIBUTING.md</a> file in <code>docs/</code>.
@ -65,7 +108,7 @@
Installation instructions for Blue Marble are below. Click the arrows to expand the instructions you want to see. Blue text is a link.
<details>
<summary>
<b>Install Chrome (Computer)</b> <sup>(Click to Expand)</sup>
<b>Install Chrome</b> <sup>(Click to Expand)</sup>
</summary>
<a href="https://www.youtube.com/watch?v=gg5oiJcftEc" target="_blank" rel="noopener noreferrer"><img alt="Install Tutorial" src="https://img.shields.io/badge/Install_Tutorial-gray?style=flat&logo=YouTube&logoColor=white&logoSize=auto&labelColor=darkred"></a>
<ol>
@ -80,25 +123,15 @@
<br>
<img alt="Enable 'Developer Mode' and 'Allow user scripts'" src="https://github.com/SwingTheVine/Wplace-BlueMarble/blob/main/docs/assets/ComputerChromeInstall3.png"></li>
<li>Enable "Allow user scripts."</li>
<li>Download the <a href="https://github.com/SwingTheVine/Wplace-BlueMarble/releases" target="_blank" rel="noopener noreferrer">BlueMarble.user.js</a> file in the "assets" of the latest release.</li>
<li>Open the TamperMonkey Dashboard.
<li><strong>One-click install:</strong> Click this link to Install Blue Marble directly: <a href="https://github.com/SwingTheVine/Wplace-BlueMarble/releases/download/v0.76.0/BlueMarble.user.js" target="_blank" rel="noopener noreferrer"><strong>Install Blue Marble</strong></a>
<br>
<img alt="Enter the TamperMonkey 'Dashboard'" src="https://github.com/SwingTheVine/Wplace-BlueMarble/blob/main/docs/assets/ComputerChromeInstall4.png"></li>
<li>Drag the <code>BlueMarble.user.js</code> file inside the dashboard of TamperMonkey.
<br>
<img alt="Drag the userscript into the dashboard" src="https://github.com/SwingTheVine/Wplace-BlueMarble/blob/main/docs/assets/ComputerChromeInstall5.png"></li>
<li>Click the "Install" button to install Blue Marble.
<br>
<img alt="Click the 'Install' button" src="https://github.com/SwingTheVine/Wplace-BlueMarble/blob/main/docs/assets/ComputerChromeInstall6.png"></li>
<li>Enable Blue Marble inside the TamperMonkey dashboard.
<br>
<img alt="Enable Blue Marble" src="https://github.com/SwingTheVine/Wplace-BlueMarble/blob/main/docs/assets/ComputerChromeInstall7.png"></li>
TamperMonkey will automatically detect the userscript and prompt you to Install it.</li>
<li>Refresh the <a href="https://wplace.live/" target="_blank" rel="noopener noreferrer">wplace.live</a> webpage.</li>
</ol>
</details>
<details>
<summary>
<b>Install Edge (Computer)</b> <sup>(Click to Expand)</sup>
<b>Install Edge</b> <sup>(Click to Expand)</sup>
</summary>
<ol>
<li>Install the <a href="https://microsoftedge.microsoft.com/addons/detail/iikmkjmpaadaobahmlepeloendndfphd" target="_blank" rel="noopener noreferrer">TamperMonkey</a> plugin for Microsoft Edge.
@ -117,8 +150,8 @@
<img alt="Enter the TamperMonkey 'Dashboard'" src="https://github.com/SwingTheVine/Wplace-BlueMarble/blob/main/docs/assets/ComputerEdgeInstall4.png"></li>
<li>Drag the <code>BlueMarble.user.js</code> file inside the dashboard of TamperMonkey.
<br>
<img alt="Drag the userscript into the dashboard" src="https://github.com/SwingTheVine/Wplace-BlueMarble/blob/main/docs/assets/ComputerEdgeInstall5.png"></li>
<li>Click the "Install" button to install Blue Marble.
<img alt="Drag the userscript into the dashboard" src="https://github.com/SwingTheVine/Wplace-BlueMarble/blob/main/docs/assets/ComputerChromeInstall5.png"></li>
<li>Click the "Install" button to Install Blue Marble.
<br>
<img alt="Click the 'Install' button" src="https://github.com/SwingTheVine/Wplace-BlueMarble/blob/main/docs/assets/ComputerChromeInstall6.png"></li>
<li>Enable Blue Marble inside the TamperMonkey dashboard.
@ -129,25 +162,15 @@
</details>
<details>
<summary>
<b>Install Firefox (Computer)</b> <sup>(Click to Expand)</sup>
<b>Install FireFox</b> <sup>(Click to Expand)</sup>
</summary>
<ol>
<li>Install the <a href="https://addons.mozilla.org/en-US/firefox/addon/tampermonkey/" target="_blank" rel="noopener noreferrer">TamperMonkey</a> plugin for Firefox.
<br>
<img alt="Click the 'Add to Firefox' button" src="https://github.com/SwingTheVine/Wplace-BlueMarble/blob/main/docs/assets/ComputerFirefoxInstall1.png"></li>
<li>Download the <a href="https://github.com/SwingTheVine/Wplace-BlueMarble/releases" target="_blank" rel="noopener noreferrer">BlueMarble.user.js</a> file in the "assets" of the latest release.</li>
<li>Open the TamperMonkey Dashboard.
<img alt="Click the 'Add to FireFox' button" src="https://github.com/SwingTheVine/Wplace-BlueMarble/blob/main/docs/assets/ComputerFireFoxInstall1.png"></li>
<li><strong>One-click install:</strong> Click this link to Install Blue Marble directly: <a href="https://github.com/SwingTheVine/Wplace-BlueMarble/releases/download/v0.76.0/BlueMarble.user.js" target="_blank" rel="noopener noreferrer"><strong>Install Blue Marble</strong></a>
<br>
<img alt="Enter the TamperMonkey 'Dashboard'" src="https://github.com/SwingTheVine/Wplace-BlueMarble/blob/main/docs/assets/ComputerFirefoxInstall2.png"></li>
<li>Drag the <code>BlueMarble.user.js</code> file inside the dashboard of TamperMonkey.
<br>
<img alt="Drag the userscript into the dashboard" src="https://github.com/SwingTheVine/Wplace-BlueMarble/blob/main/docs/assets/ComputerFirefoxInstall3.png"></li>
<li>Click the "Install" button to install Blue Marble.
<br>
<img alt="Click the 'Install' button" src="https://github.com/SwingTheVine/Wplace-BlueMarble/blob/main/docs/assets/ComputerChromeInstall6.png"></li>
<li>Enable Blue Marble inside the TamperMonkey dashboard.
<br>
<img alt="Enable Blue Marble" src="https://github.com/SwingTheVine/Wplace-BlueMarble/blob/main/docs/assets/ComputerChromeInstall7.png"></li>
TamperMonkey will automatically detect the userscript and prompt you to Install it.</li>
<li>Refresh the <a href="https://wplace.live/" target="_blank" rel="noopener noreferrer">wplace.live</a> webpage.</li>
</ol>
</details>

View file

@ -1,230 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: Template.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: Template.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>import { uint8ToBase64 } from "./utils";
/** An instance of a template.
* Handles all mathematics, manipulation, and analysis regarding a single template.
* @since 0.65.2
*/
export default class Template {
/** The constructor for the {@link Template} class with enhanced pixel tracking.
* @param {Object} [params={}] - Object containing all optional parameters
* @param {string} [params.displayName='My template'] - The display name of the template
* @param {number} [params.sortID=0] - The sort number of the template for rendering priority
* @param {string} [params.authorID=''] - The user ID of the person who exported the template (prevents sort ID collisions)
* @param {string} [params.url=''] - The URL to the source image
* @param {File} [params.file=null] - The template file (pre-processed File or processed bitmap)
* @param {Array&lt;number>} [params.coords=null] - The coordinates of the top left corner as (tileX, tileY, pixelX, pixelY)
* @param {Object} [params.chunked=null] - The affected chunks of the template, and their template for each chunk
* @param {number} [params.tileSize=1000] - The size of a tile in pixels (assumes square tiles)
* @param {number} [params.pixelCount=0] - Total number of pixels in the template (calculated automatically during processing)
* @since 0.65.2
*/
constructor({
displayName = 'My template',
sortID = 0,
authorID = '',
url = '',
file = null,
coords = null,
chunked = null,
tileSize = 1000,
} = {}) {
this.displayName = displayName;
this.sortID = sortID;
this.authorID = authorID;
this.url = url;
this.file = file;
this.coords = coords;
this.chunked = chunked;
this.tileSize = tileSize;
this.pixelCount = 0; // Total pixel count in template
}
/** Creates chunks of the template for each tile.
*
* @returns {Object} Collection of template bitmaps &amp; buffers organized by tile coordinates
* @since 0.65.4
*/
async createTemplateTiles() {
console.log('Template coordinates:', this.coords);
const shreadSize = 3; // Scale image factor for pixel art enhancement (must be odd)
const bitmap = await createImageBitmap(this.file); // Create efficient bitmap from uploaded file
const imageWidth = bitmap.width;
const imageHeight = bitmap.height;
// Calculate total pixel count using standard width × height formula
// TODO: Use non-transparent pixels instead of basic width times height
const totalPixels = imageWidth * imageHeight;
console.log(`Template pixel analysis - Dimensions: ${imageWidth}×${imageHeight} = ${totalPixels.toLocaleString()} pixels`);
// Store pixel count in instance property for access by template manager and UI components
this.pixelCount = totalPixels;
const templateTiles = {}; // Holds the template tiles
const templateTilesBuffers = {}; // Holds the buffers of the template tiles
const canvas = new OffscreenCanvas(this.tileSize, this.tileSize);
const context = canvas.getContext('2d', { willReadFrequently: true });
// For every tile...
for (let pixelY = this.coords[3]; pixelY &lt; imageHeight + this.coords[3]; ) {
// Draws the partial tile first, if any
// This calculates the size based on which is smaller:
// A. The top left corner of the current tile to the bottom right corner of the current tile
// B. The top left corner of the current tile to the bottom right corner of the image
const drawSizeY = Math.min(this.tileSize - (pixelY % this.tileSize), imageHeight - (pixelY - this.coords[3]));
console.log(`Math.min(${this.tileSize} - (${pixelY} % ${this.tileSize}), ${imageHeight} - (${pixelY - this.coords[3]}))`);
for (let pixelX = this.coords[2]; pixelX &lt; imageWidth + this.coords[2];) {
console.log(`Pixel X: ${pixelX}\nPixel Y: ${pixelY}`);
// Draws the partial tile first, if any
// This calculates the size based on which is smaller:
// A. The top left corner of the current tile to the bottom right corner of the current tile
// B. The top left corner of the current tile to the bottom right corner of the image
const drawSizeX = Math.min(this.tileSize - (pixelX % this.tileSize), imageWidth - (pixelX - this.coords[2]));
console.log(`Math.min(${this.tileSize} - (${pixelX} % ${this.tileSize}), ${imageWidth} - (${pixelX - this.coords[2]}))`);
console.log(`Draw Size X: ${drawSizeX}\nDraw Size Y: ${drawSizeY}`);
// Change the canvas size and wipe the canvas
const canvasWidth = drawSizeX * shreadSize;// + (pixelX % this.tileSize) * shreadSize;
const canvasHeight = drawSizeY * shreadSize;// + (pixelY % this.tileSize) * shreadSize;
canvas.width = canvasWidth;
canvas.height = canvasHeight;
console.log(`Draw X: ${drawSizeX}\nDraw Y: ${drawSizeY}\nCanvas Width: ${canvasWidth}\nCanvas Height: ${canvasHeight}`);
context.imageSmoothingEnabled = false; // Nearest neighbor
console.log(`Getting X ${pixelX}-${pixelX + drawSizeX}\nGetting Y ${pixelY}-${pixelY + drawSizeY}`);
// Draws the template segment on this tile segment
context.clearRect(0, 0, canvasWidth, canvasHeight); // Clear any previous drawing (only runs when canvas size does not change)
context.drawImage(
bitmap, // Bitmap image to draw
pixelX - this.coords[2], // Coordinate X to draw from
pixelY - this.coords[3], // Coordinate Y to draw from
drawSizeX, // X width to draw from
drawSizeY, // Y height to draw from
0, // Coordinate X to draw at
0, // Coordinate Y to draw at
drawSizeX * shreadSize, // X width to draw at
drawSizeY * shreadSize // Y height to draw at
); // Coordinates and size of draw area of source image, then canvas
// const final = await canvas.convertToBlob({ type: 'image/png' });
// const url = URL.createObjectURL(final); // Creates a blob URL
// window.open(url, '_blank'); // Opens a new tab with blob
// setTimeout(() => URL.revokeObjectURL(url), 60000); // Destroys the blob 1 minute later
const imageData = context.getImageData(0, 0, canvasWidth, canvasHeight); // Data of the image on the canvas
for (let y = 0; y &lt; canvasHeight; y++) {
for (let x = 0; x &lt; canvasWidth; x++) {
// For every pixel...
// ... Make it transparent unless it is the "center"
if (x % shreadSize !== 1 || y % shreadSize !== 1) {
const pixelIndex = (y * canvasWidth + x) * 4; // Find the pixel index in an array where every 4 indexes are 1 pixel
imageData.data[pixelIndex + 3] = 0; // Make the pixel transparent on the alpha channel
// if (!!imageData.data[pixelIndex + 3]) {
// imageData.data[pixelIndex + 3] = 50; // Alpha
// imageData.data[pixelIndex] = 30; // Red
// imageData.data[pixelIndex + 1] = 30; // Green
// imageData.data[pixelIndex + 2] = 30; // Blue
// }
}
}
}
console.log(`Shreaded pixels for ${pixelX}, ${pixelY}`, imageData);
context.putImageData(imageData, 0, 0);
// Creates the "0000,0000,000,000" key name
const templateTileName = `${(this.coords[0] + Math.floor(pixelX / 1000))
.toString()
.padStart(4, '0')},${(this.coords[1] + Math.floor(pixelY / 1000))
.toString()
.padStart(4, '0')},${(pixelX % 1000)
.toString()
.padStart(3, '0')},${(pixelY % 1000).toString().padStart(3, '0')}`;
templateTiles[templateTileName] = await createImageBitmap(canvas); // Creates the bitmap
const canvasBlob = await canvas.convertToBlob();
const canvasBuffer = await canvasBlob.arrayBuffer();
const canvasBufferBytes = Array.from(new Uint8Array(canvasBuffer));
templateTilesBuffers[templateTileName] = uint8ToBase64(canvasBufferBytes); // Stores the buffer
console.log(templateTiles);
pixelX += drawSizeX;
}
pixelY += drawSizeY;
}
console.log('Template Tiles: ', templateTiles);
console.log('Template Tiles Buffers: ', templateTilesBuffers);
return { templateTiles, templateTilesBuffers };
}
}
</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>

View file

@ -1,193 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: apiManager.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: apiManager.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/** ApiManager class for handling API requests, responses, and interactions.
* Note: Fetch spying is done in main.js, not here.
* @since 0.11.1
*/
import TemplateManager from "./templateManager.js";
import { escapeHTML, numberToEncoded, serverTPtoDisplayTP } from "./utils.js";
export default class ApiManager {
/** Constructor for ApiManager class
* @param {TemplateManager} templateManager
* @since 0.11.34
*/
constructor(templateManager) {
this.templateManager = templateManager;
this.disableAll = false; // Should the entire userscript be disabled?
this.coordsTilePixel = []; // Contains the last detected tile/pixel coordinate pair requested
this.templateCoordsTilePixel = []; // Contains the last "enabled" template coords
}
/** Determines if the spontaneously recieved response is something we want.
* Otherwise, we can ignore it.
* Note: Due to aggressive compression, make your calls like `data['jsonData']['name']` instead of `data.jsonData.name`
*
* @param {Overlay} overlay - The Overlay class instance
* @since 0.11.1
*/
spontaneousResponseListener(overlay) {
// Triggers whenever a message is sent
window.addEventListener('message', async (event) => {
const data = event.data; // The data of the message
const dataJSON = data['jsonData']; // The JSON response, if any
// Kills itself if the message was not intended for Blue Marble
if (!(data &amp;&amp; data['source'] === 'blue-marble')) {return;}
// Kills itself if the message has no endpoint (intended for Blue Marble, but not this function)
if (!data['endpoint']) {return;}
// Trims endpoint to the second to last non-number, non-null directoy.
// E.g. "wplace.live/api/pixel/0/0?payload" -> "pixel"
// E.g. "wplace.live/api/files/s0/tiles/0/0/0.png" -> "tiles"
const endpointText = data['endpoint']?.split('?')[0].split('/').filter(s => s &amp;&amp; isNaN(Number(s))).filter(s => s &amp;&amp; !s.includes('.')).pop();
console.log(`%cBlue Marble%c: Recieved message about "%s"`, 'color: cornflowerblue;', '', endpointText);
// Each case is something that Blue Marble can use from the fetch.
// For instance, if the fetch was for "me", we can update the overlay stats
switch (endpointText) {
case 'me': // Request to retrieve user data
// If the game can not retrieve the userdata...
if (dataJSON['status'] &amp;&amp; dataJSON['status']?.toString()[0] != '2') {
// The server is probably down (NOT a 2xx status)
overlay.handleDisplayError(`You are not logged in!\nCould not fetch userdata.`);
return; // Kills itself before attempting to display null userdata
}
const nextLevelPixels = Math.ceil(Math.pow(Math.floor(dataJSON['level']) * Math.pow(30, 0.65), (1/0.65)) - dataJSON['pixelsPainted']); // Calculates pixels to the next level
console.log(dataJSON['id']);
if (!!dataJSON['id'] || dataJSON['id'] === 0) {
console.log(numberToEncoded(
dataJSON['id'],
'!#$%&amp;\'()*+,-./0123456789:;&lt;=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~'
));
}
this.templateManager.userID = dataJSON['id'];
overlay.updateInnerHTML('bm-user-name', `Username: &lt;b>${escapeHTML(dataJSON['name'])}&lt;/b>`); // Updates the text content of the username field
overlay.updateInnerHTML('bm-user-droplets', `Droplets: &lt;b>${new Intl.NumberFormat().format(dataJSON['droplets'])}&lt;/b>`); // Updates the text content of the droplets field
overlay.updateInnerHTML('bm-user-nextlevel', `Next level in &lt;b>${new Intl.NumberFormat().format(nextLevelPixels)}&lt;/b> pixel${nextLevelPixels == 1 ? '' : 's'}`); // Updates the text content of the next level field
break;
case 'pixel': // Request to retrieve pixel data
const coordsTile = data['endpoint'].split('?')[0].split('/').filter(s => s &amp;&amp; !isNaN(Number(s))); // Retrieves the tile coords as [x, y]
const payloadExtractor = new URLSearchParams(data['endpoint'].split('?')[1]); // Declares a new payload deconstructor and passes in the fetch request payload
const coordsPixel = [payloadExtractor.get('x'), payloadExtractor.get('y')]; // Retrieves the deconstructed pixel coords from the payload
// Don't save the coords if there are previous coords that could be used
if (this.coordsTilePixel.length &amp;&amp; (!coordsTile.length || !coordsPixel.length)) {
overlay.handleDisplayError(`Coordinates are malformed!\nDid you try clicking the canvas first?`);
return; // Kills itself
}
this.coordsTilePixel = [...coordsTile, ...coordsPixel]; // Combines the two arrays such that [x, y, x, y]
const displayTP = serverTPtoDisplayTP(coordsTile, coordsPixel);
const spanElements = document.querySelectorAll('span'); // Retrieves all span elements
// For every span element, find the one we want (pixel numbers when canvas clicked)
for (const element of spanElements) {
if (element.textContent.trim().includes(`${displayTP[0]}, ${displayTP[1]}`)) {
let displayCoords = document.querySelector('#bm-display-coords'); // Find the additional pixel coords span
const text = `(Tl X: ${coordsTile[0]}, Tl Y: ${coordsTile[1]}, Px X: ${coordsPixel[0]}, Px Y: ${coordsPixel[1]})`;
// If we could not find the addition coord span, we make it then update the textContent with the new coords
if (!displayCoords) {
displayCoords = document.createElement('span');
displayCoords.id = 'bm-display-coords';
displayCoords.textContent = text;
displayCoords.style = 'margin-left: calc(var(--spacing)*3); font-size: small;';
element.parentNode.parentNode.parentNode.insertAdjacentElement('afterend', displayCoords);
} else {
displayCoords.textContent = text;
}
}
}
break;
case 'tiles':
// Runs only if the tile has the template
let tileCoordsTile = data['endpoint'].split('/');
tileCoordsTile = [parseInt(tileCoordsTile[tileCoordsTile.length - 2]), parseInt(tileCoordsTile[tileCoordsTile.length - 1].replace('.png', ''))];
const blobUUID = data['blobID'];
const blobData = data['blobData'];
const templateBlob = await this.templateManager.drawTemplateOnTile(blobData, tileCoordsTile);
window.postMessage({
source: 'blue-marble',
blobID: blobUUID,
blobData: templateBlob,
blink: data['blink']
});
break;
case 'robots': // Request to retrieve what script types are allowed
this.disableAll = dataJSON['userscript']?.toString().toLowerCase() == 'false'; // Disables Blue Marble if site owner wants userscripts disabled
break;
}
});
}
}</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>

Binary file not shown.

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 116 KiB

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 118 KiB

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 120 KiB

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 114 KiB

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 120 KiB

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 117 KiB

File diff suppressed because it is too large Load diff

View file

@ -1,65 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Home</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">Home</h1>
<h3> </h3>
</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>

View file

@ -1,618 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: main.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: main.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/** The main file. Everything in the userscript is executed from here.
* @since 0.0.0
*/
import Overlay from './Overlay.js';
import Observers from './observers.js';
import ApiManager from './apiManager.js';
import TemplateManager from './templateManager.js';
import { consoleLog, consoleWarn } from './utils.js';
const name = GM_info.script.name.toString(); // Name of userscript
const version = GM_info.script.version.toString(); // Version of userscript
const consoleStyle = 'color: cornflowerblue;'; // The styling for the console logs
/** Injects code into the client
* This code will execute outside of TamperMonkey's sandbox
* @param {*} callback - The code to execute
* @since 0.11.15
*/
function inject(callback) {
const script = document.createElement('script');
script.setAttribute('bm-name', name); // Passes in the name value
script.setAttribute('bm-cStyle', consoleStyle); // Passes in the console style value
script.textContent = `(${callback})();`;
document.documentElement?.appendChild(script);
script.remove();
}
/** What code to execute instantly in the client (webpage) to spy on fetch calls.
* This code will execute outside of TamperMonkey's sandbox.
* @since 0.11.15
*/
inject(() => {
const script = document.currentScript; // Gets the current script HTML Script Element
const name = script?.getAttribute('bm-name') || 'Blue Marble'; // Gets the name value that was passed in. Defaults to "Blue Marble" if nothing was found
const consoleStyle = script?.getAttribute('bm-cStyle') || ''; // Gets the console style value that was passed in. Defaults to no styling if nothing was found
const fetchedBlobQueue = new Map(); // Blobs being processed
window.addEventListener('message', (event) => {
const { source, endpoint, blobID, blobData, blink } = event.data;
const elapsed = Date.now() - blink;
// Since this code does not run in the userscript, we can't use consoleLog().
console.groupCollapsed(`%c${name}%c: ${fetchedBlobQueue.size} Recieved IMAGE message about blob "${blobID}"`, consoleStyle, '');
console.log(`Blob fetch took %c${String(Math.floor(elapsed/60000)).padStart(2,'0')}:${String(Math.floor(elapsed/1000) % 60).padStart(2,'0')}.${String(elapsed % 1000).padStart(3,'0')}%c MM:SS.mmm`, consoleStyle, '');
console.log(fetchedBlobQueue);
console.groupEnd();
// The modified blob won't have an endpoint, so we ignore any message without one.
if ((source == 'blue-marble') &amp;&amp; !!blobID &amp;&amp; !!blobData &amp;&amp; !endpoint) {
const callback = fetchedBlobQueue.get(blobID); // Retrieves the blob based on the UUID
// If the blobID is a valid function...
if (typeof callback === 'function') {
callback(blobData); // ...Retrieve the blob data from the blobID function
} else {
// ...else the blobID is unexpected. We don't know what it is, but we know for sure it is not a blob. This means we ignore it.
consoleWarn(`%c${name}%c: Attempted to retrieve a blob (%s) from queue, but the blobID was not a function! Skipping...`, consoleStyle, '', blobID);
}
fetchedBlobQueue.delete(blobID); // Delete the blob from the queue, because we don't need to process it again
}
});
// Spys on "spontaneous" fetch requests made by the client
const originalFetch = window.fetch; // Saves a copy of the original fetch
// Overrides fetch
window.fetch = async function(...args) {
const response = await originalFetch.apply(this, args); // Sends a fetch
const cloned = response.clone(); // Makes a copy of the response
// Retrieves the endpoint name. Unknown endpoint = "ignore"
const endpointName = ((args[0] instanceof Request) ? args[0]?.url : args[0]) || 'ignore';
// Check Content-Type to only process JSON
const contentType = cloned.headers.get('content-type') || '';
if (contentType.includes('application/json')) {
// Since this code does not run in the userscript, we can't use consoleLog().
console.log(`%c${name}%c: Sending JSON message about endpoint "${endpointName}"`, consoleStyle, '');
// Sends a message about the endpoint it spied on
cloned.json()
.then(jsonData => {
window.postMessage({
source: 'blue-marble',
endpoint: endpointName,
jsonData: jsonData
}, '*');
})
.catch(err => {
console.error(`%c${name}%c: Failed to parse JSON: `, consoleStyle, '', err);
});
} else if (contentType.includes('image/') &amp;&amp; (!endpointName.includes('openfreemap'))) {
// Fetch custom for all images but opensourcemap
const blink = Date.now(); // Current time
const blob = await cloned.blob(); // The original blob
// Since this code does not run in the userscript, we can't use consoleLog().
console.log(`%c${name}%c: ${fetchedBlobQueue.size} Sending IMAGE message about endpoint "${endpointName}"`, consoleStyle, '');
// Returns the manipulated blob
return new Promise((resolve) => {
const blobUUID = crypto.randomUUID(); // Generates a random UUID
// Store the blob while we wait for processing
fetchedBlobQueue.set(blobUUID, (blobProcessed) => {
// The response that triggers when the blob is finished processing
// Creates a new response
resolve(new Response(blobProcessed, {
headers: cloned.headers,
status: cloned.status,
statusText: cloned.statusText
}));
// Since this code does not run in the userscript, we can't use consoleLog().
console.log(`%c${name}%c: ${fetchedBlobQueue.size} Processed blob "${blobUUID}"`, consoleStyle, '');
});
window.postMessage({
source: 'blue-marble',
endpoint: endpointName,
blobID: blobUUID,
blobData: blob,
blink: blink
});
}).catch(exception => {
const elapsed = Date.now();
console.error(`%c${name}%c: Failed to Promise blob!`, consoleStyle, '');
console.groupCollapsed(`%c${name}%c: Details of failed blob Promise:`, consoleStyle, '');
console.log(`Endpoint: ${endpointName}\nThere are ${fetchedBlobQueue.size} blobs processing...\nBlink: ${blink.toLocaleString()}\nTime Since Blink: ${String(Math.floor(elapsed/60000)).padStart(2,'0')}:${String(Math.floor(elapsed/1000) % 60).padStart(2,'0')}.${String(elapsed % 1000).padStart(3,'0')} MM:SS.mmm`);
console.error(`Exception stack:`, exception);
console.groupEnd();
});
// cloned.blob().then(blob => {
// window.postMessage({
// source: 'blue-marble',
// endpoint: endpointName,
// blobData: blob
// }, '*');
// });
}
return response; // Returns the original response
};
});
// Imports the CSS file from dist folder on github
const cssOverlay = GM_getResourceText("CSS-BM-File");
GM_addStyle(cssOverlay);
// Imports the Roboto Mono font family
var stylesheetLink = document.createElement('link');
stylesheetLink.href = 'https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&amp;display=swap';
stylesheetLink.rel = 'preload';
stylesheetLink.as = 'style';
stylesheetLink.onload = function () {
this.onload = null;
this.rel = 'stylesheet';
};
document.head?.appendChild(stylesheetLink);
// CONSTRUCTORS
const observers = new Observers(); // Constructs a new Observers object
const overlayMain = new Overlay(name, version); // Constructs a new Overlay object for the main overlay
const overlayTabTemplate = new Overlay(name, version); // Constructs a Overlay object for the template tab
const templateManager = new TemplateManager(name, version, overlayMain); // Constructs a new TemplateManager object
const apiManager = new ApiManager(templateManager); // Constructs a new ApiManager object
overlayMain.setApiManager(apiManager); // Sets the API manager
const storageTemplates = JSON.parse(GM_getValue('bmTemplates', '{}'));
console.log(storageTemplates);
templateManager.importJSON(storageTemplates); // Loads the templates
buildOverlayMain(); // Builds the main overlay
overlayMain.handleDrag('#bm-overlay', '#bm-bar-drag'); // Creates dragging capability on the drag bar for dragging the overlay
apiManager.spontaneousResponseListener(overlayMain); // Reads spontaneous fetch responces
observeBlack(); // Observes the black palette color
consoleLog(`%c${name}%c (${version}) userscript has loaded!`, 'color: cornflowerblue;', '');
/** Observe the black color, and add the "Move" button.
* @since 0.66.3
*/
function observeBlack() {
const observer = new MutationObserver((mutations, observer) => {
const black = document.querySelector('#color-1'); // Attempt to retrieve the black color element for anchoring
if (!black) {return;} // Black color does not exist yet. Kills iteself
let move = document.querySelector('#bm-button-move'); // Tries to find the move button
// If the move button does not exist, we make a new one
if (!move) {
move = document.createElement('button');
move.id = 'bm-button-move';
move.textContent = 'Move ↑';
move.className = 'btn btn-soft';
move.onclick = function() {
const roundedBox = this.parentNode.parentNode.parentNode.parentNode; // Obtains the rounded box
const shouldMoveUp = (this.textContent == 'Move ↑');
roundedBox.parentNode.className = roundedBox.parentNode.className.replace(shouldMoveUp ? 'bottom' : 'top', shouldMoveUp ? 'top' : 'bottom'); // Moves the rounded box to the top
roundedBox.style.borderTopLeftRadius = shouldMoveUp ? '0px' : 'var(--radius-box)';
roundedBox.style.borderTopRightRadius = shouldMoveUp ? '0px' : 'var(--radius-box)';
roundedBox.style.borderBottomLeftRadius = shouldMoveUp ? 'var(--radius-box)' : '0px';
roundedBox.style.borderBottomRightRadius = shouldMoveUp ? 'var(--radius-box)' : '0px';
this.textContent = shouldMoveUp ? 'Move ↓' : 'Move ↑';
}
// Attempts to find the "Paint Pixel" element for anchoring
const paintPixel = black.parentNode.parentNode.parentNode.parentNode.querySelector('h2');
paintPixel.parentNode?.appendChild(move); // Adds the move button
}
});
observer.observe(document.body, { childList: true, subtree: true });
}
/** Deploys the overlay to the page with minimize/maximize functionality.
* Creates a responsive overlay UI that can toggle between full-featured and minimized states.
*
* Parent/child relationships in the DOM structure below are indicated by indentation.
* @since 0.58.3
*/
function buildOverlayMain() {
let isMinimized = false; // Overlay state tracker (false = maximized, true = minimized)
overlayMain.addDiv({'id': 'bm-overlay', 'style': 'top: 10px; right: 75px;'})
.addDiv({'id': 'bm-contain-header'})
.addDiv({'id': 'bm-bar-drag'}).buildElement()
.addImg({'alt': 'Blue Marble Icon - Click to minimize/maximize', 'src': 'https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/assets/Favicon.png', 'style': 'cursor: pointer;'},
(instance, img) => {
/** Click event handler for overlay minimize/maximize functionality.
*
* Toggles between two distinct UI states:
* 1. MINIMIZED STATE (60×76px):
* - Shows only the Blue Marble icon and drag bar
* - Hides all input fields, buttons, and status information
* - Applies fixed dimensions for consistent appearance
* - Repositions icon with 3px right offset for visual centering
*
* 2. MAXIMIZED STATE (responsive):
* - Restores full functionality with all UI elements
* - Removes fixed dimensions to allow responsive behavior
* - Resets icon positioning to default alignment
* - Shows success message when returning to maximized state
*
* @param {Event} event - The click event object (implicit)
*/
img.addEventListener('click', () => {
isMinimized = !isMinimized; // Toggle the current state
const overlay = document.querySelector('#bm-overlay');
const header = document.querySelector('#bm-contain-header');
const dragBar = document.querySelector('#bm-bar-drag');
const coordsContainer = document.querySelector('#bm-contain-coords');
const coordsButton = document.querySelector('#bm-button-coords');
const createButton = document.querySelector('#bm-button-create');
const enableButton = document.querySelector('#bm-button-enable');
const disableButton = document.querySelector('#bm-button-disable');
const coordInputs = document.querySelectorAll('#bm-contain-coords input');
// Pre-restore original dimensions when switching to maximized state
// This ensures smooth transition and prevents layout issues
if (!isMinimized) {
overlay.style.width = "auto";
overlay.style.maxWidth = "300px";
overlay.style.minWidth = "200px";
overlay.style.padding = "10px";
}
// Define elements that should be hidden/shown during state transitions
// Each element is documented with its purpose for maintainability
const elementsToToggle = [
'#bm-overlay h1', // Main title "Blue Marble"
'#bm-contain-userinfo', // User information section (username, droplets, level)
'#bm-overlay hr', // Visual separator lines
'#bm-contain-automation > *:not(#bm-contain-coords)', // Automation section excluding coordinates
'#bm-input-file-template', // Template file upload interface
'#bm-contain-buttons-action', // Action buttons container
`#${instance.outputStatusId}` // Status log textarea for user feedback
];
// Apply visibility changes to all toggleable elements
elementsToToggle.forEach(selector => {
const elements = document.querySelectorAll(selector);
elements.forEach(element => {
element.style.display = isMinimized ? 'none' : '';
});
});
// Handle coordinate container and button visibility based on state
if (isMinimized) {
// ==================== MINIMIZED STATE CONFIGURATION ====================
// In minimized state, we hide ALL interactive elements except the icon and drag bar
// This creates a clean, unobtrusive interface that maintains only essential functionality
// Hide coordinate input container completely
if (coordsContainer) {
coordsContainer.style.display = 'none';
}
// Hide coordinate button (pin icon)
if (coordsButton) {
coordsButton.style.display = 'none';
}
// Hide create template button
if (createButton) {
createButton.style.display = 'none';
}
// Hide enable templates button
if (enableButton) {
enableButton.style.display = 'none';
}
// Hide disable templates button
if (disableButton) {
disableButton.style.display = 'none';
}
// Hide all coordinate input fields individually (failsafe)
coordInputs.forEach(input => {
input.style.display = 'none';
});
// Apply fixed dimensions for consistent minimized appearance
// These dimensions were chosen to accommodate the icon while remaining compact
overlay.style.width = '60px'; // Fixed width for consistency
overlay.style.height = '76px'; // Fixed height (60px + 16px for better proportions)
overlay.style.maxWidth = '60px'; // Prevent expansion
overlay.style.minWidth = '60px'; // Prevent shrinking
overlay.style.padding = '8px'; // Comfortable padding around icon
// Apply icon positioning for better visual centering in minimized state
// The 3px offset compensates for visual weight distribution
img.style.marginLeft = '3px';
// Configure header layout for minimized state
header.style.textAlign = 'center';
header.style.margin = '0';
header.style.marginBottom = '0';
// Ensure drag bar remains visible and properly spaced
if (dragBar) {
dragBar.style.display = '';
dragBar.style.marginBottom = '0.25em';
}
} else {
// ==================== MAXIMIZED STATE RESTORATION ====================
// In maximized state, we restore all elements to their default functionality
// This involves clearing all style overrides applied during minimization
// Restore coordinate container to default state
if (coordsContainer) {
coordsContainer.style.display = ''; // Show container
coordsContainer.style.flexDirection = ''; // Reset flex layout
coordsContainer.style.justifyContent = ''; // Reset alignment
coordsContainer.style.alignItems = ''; // Reset alignment
coordsContainer.style.gap = ''; // Reset spacing
coordsContainer.style.textAlign = ''; // Reset text alignment
coordsContainer.style.margin = ''; // Reset margins
}
// Restore coordinate button visibility
if (coordsButton) {
coordsButton.style.display = '';
}
// Restore create button visibility and reset positioning
if (createButton) {
createButton.style.display = '';
createButton.style.marginTop = '';
}
// Restore enable button visibility and reset positioning
if (enableButton) {
enableButton.style.display = '';
enableButton.style.marginTop = '';
}
// Restore disable button visibility and reset positioning
if (disableButton) {
disableButton.style.display = '';
disableButton.style.marginTop = '';
}
// Restore all coordinate input fields
coordInputs.forEach(input => {
input.style.display = '';
});
// Reset icon positioning to default (remove minimized state offset)
img.style.marginLeft = '';
// Restore overlay to responsive dimensions
overlay.style.padding = '10px';
// Reset header styling to defaults
header.style.textAlign = '';
header.style.margin = '';
header.style.marginBottom = '';
// Reset drag bar spacing
if (dragBar) {
dragBar.style.marginBottom = '0.5em';
}
// Remove all fixed dimensions to allow responsive behavior
// This ensures the overlay can adapt to content changes
overlay.style.width = '';
overlay.style.height = '';
}
// ==================== ACCESSIBILITY AND USER FEEDBACK ====================
// Update accessibility information for screen readers and tooltips
// Update alt text to reflect current state for screen readers and tooltips
img.alt = isMinimized ?
'Blue Marble Icon - Minimized (Click to maximize)' :
'Blue Marble Icon - Maximized (Click to minimize)';
// No status message needed - state change is visually obvious to users
});
}
).buildElement()
.addHeader(1, {'textContent': name}).buildElement()
.buildElement()
.addHr().buildElement()
.addDiv({'id': 'bm-contain-userinfo'})
.addP({'id': 'bm-user-name', 'textContent': 'Username:'}).buildElement()
.addP({'id': 'bm-user-droplets', 'textContent': 'Droplets:'}).buildElement()
.addP({'id': 'bm-user-nextlevel', 'textContent': 'Next level in...'}).buildElement()
.buildElement()
.addHr().buildElement()
.addDiv({'id': 'bm-contain-automation'})
// .addCheckbox({'id': 'bm-input-stealth', 'textContent': 'Stealth', 'checked': true}).buildElement()
// .addButtonHelp({'title': 'Waits for the website to make requests, instead of sending requests.'}).buildElement()
// .addBr().buildElement()
// .addCheckbox({'id': 'bm-input-possessed', 'textContent': 'Possessed', 'checked': true}).buildElement()
// .addButtonHelp({'title': 'Controls the website as if it were possessed.'}).buildElement()
// .addBr().buildElement()
.addDiv({'id': 'bm-contain-coords'})
.addButton({'id': 'bm-button-coords', 'className': 'bm-help', 'style': 'margin-top: 0;', 'innerHTML': '&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 4 6">&lt;circle cx="2" cy="2" r="2">&lt;/circle>&lt;path d="M2 6 L3.7 3 L0.3 3 Z">&lt;/path>&lt;circle cx="2" cy="2" r="0.7" fill="white">&lt;/circle>&lt;/svg>&lt;/svg>'},
(instance, button) => {
button.onclick = () => {
const coords = instance.apiManager?.coordsTilePixel; // Retrieves the coords from the API manager
if (!coords?.[0]) {
instance.handleDisplayError('Coordinates are malformed! Did you try clicking on the canvas first?');
return;
}
instance.updateInnerHTML('bm-input-tx', coords?.[0] || '');
instance.updateInnerHTML('bm-input-ty', coords?.[1] || '');
instance.updateInnerHTML('bm-input-px', coords?.[2] || '');
instance.updateInnerHTML('bm-input-py', coords?.[3] || '');
}
}
).buildElement()
.addInput({'type': 'number', 'id': 'bm-input-tx', 'placeholder': 'Tl X', 'min': 0, 'max': 2047, 'step': 1, 'required': true}).buildElement()
.addInput({'type': 'number', 'id': 'bm-input-ty', 'placeholder': 'Tl Y', 'min': 0, 'max': 2047, 'step': 1, 'required': true}).buildElement()
.addInput({'type': 'number', 'id': 'bm-input-px', 'placeholder': 'Px X', 'min': 0, 'max': 2047, 'step': 1, 'required': true}).buildElement()
.addInput({'type': 'number', 'id': 'bm-input-py', 'placeholder': 'Px Y', 'min': 0, 'max': 2047, 'step': 1, 'required': true}).buildElement()
.buildElement()
.addInputFile({'id': 'bm-input-file-template', 'textContent': 'Upload Template', 'accept': 'image/png, image/jpeg, image/webp, image/bmp, image/gif'}).buildElement()
.addDiv({'id': 'bm-contain-buttons-template'})
.addButton({'id': 'bm-button-enable', 'textContent': 'Enable'}, (instance, button) => {
button.onclick = () => {
instance.apiManager?.templateManager?.setTemplatesShouldBeDrawn(true);
instance.handleDisplayStatus(`Enabled templates!`);
}
}).buildElement()
.addButton({'id': 'bm-button-create', 'textContent': 'Create'}, (instance, button) => {
button.onclick = () => {
const input = document.querySelector('#bm-input-file-template');
const coordTlX = document.querySelector('#bm-input-tx');
if (!coordTlX.checkValidity()) {coordTlX.reportValidity(); instance.handleDisplayError('Coordinates are malformed! Did you try clicking on the canvas first?'); return;}
const coordTlY = document.querySelector('#bm-input-ty');
if (!coordTlY.checkValidity()) {coordTlY.reportValidity(); instance.handleDisplayError('Coordinates are malformed! Did you try clicking on the canvas first?'); return;}
const coordPxX = document.querySelector('#bm-input-px');
if (!coordPxX.checkValidity()) {coordPxX.reportValidity(); instance.handleDisplayError('Coordinates are malformed! Did you try clicking on the canvas first?'); return;}
const coordPxY = document.querySelector('#bm-input-py');
if (!coordPxY.checkValidity()) {coordPxY.reportValidity(); instance.handleDisplayError('Coordinates are malformed! Did you try clicking on the canvas first?'); return;}
// Kills itself if there is no file
if (!input?.files[0]) {instance.handleDisplayError(`No file selected!`); return;}
templateManager.createTemplate(input.files[0], input.files[0]?.name.replace(/\.[^/.]+$/, ''), [Number(coordTlX.value), Number(coordTlY.value), Number(coordPxX.value), Number(coordPxY.value)]);
// console.log(`TCoords: ${apiManager.templateCoordsTilePixel}\nCoords: ${apiManager.coordsTilePixel}`);
// apiManager.templateCoordsTilePixel = apiManager.coordsTilePixel; // Update template coords
// console.log(`TCoords: ${apiManager.templateCoordsTilePixel}\nCoords: ${apiManager.coordsTilePixel}`);
// templateManager.setTemplateImage(input.files[0]);
instance.handleDisplayStatus(`Drew to canvas!`);
}
}).buildElement()
.addButton({'id': 'bm-button-disable', 'textContent': 'Disable'}, (instance, button) => {
button.onclick = () => {
instance.apiManager?.templateManager?.setTemplatesShouldBeDrawn(false);
instance.handleDisplayStatus(`Disabled templates!`);
}
}).buildElement()
.buildElement()
.addTextarea({'id': overlayMain.outputStatusId, 'placeholder': `Status: Sleeping...\nVersion: ${version}`, 'readOnly': true}).buildElement()
.addDiv({'id': 'bm-contain-buttons-action'})
.addDiv()
// .addButton({'id': 'bm-button-teleport', 'className': 'bm-help', 'textContent': '✈'}).buildElement()
// .addButton({'id': 'bm-button-favorite', 'className': 'bm-help', 'innerHTML': '&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">&lt;polygon points="10,2 12,7.5 18,7.5 13.5,11.5 15.5,18 10,14 4.5,18 6.5,11.5 2,7.5 8,7.5" fill="white">&lt;/polygon>&lt;/svg>'}).buildElement()
// .addButton({'id': 'bm-button-templates', 'className': 'bm-help', 'innerHTML': '🖌'}).buildElement()
.addButton({'id': 'bm-button-convert', 'className': 'bm-help', 'innerHTML': '🎨', 'title': 'Template Color Converter'},
(instance, button) => {
button.addEventListener('click', () => {
window.open('https://pepoafonso.github.io/color_converter_wplace/', '_blank', 'noopener noreferrer');
});
}).buildElement()
.buildElement()
.addSmall({'textContent': 'Made by SwingTheVine', 'style': 'margin-top: auto;'}).buildElement()
.buildElement()
.buildElement()
.buildOverlay(document.body);
}
function buildOverlayTabTemplate() {
overlayTabTemplate.addDiv({'id': 'bm-tab-template', 'style': 'top: 20%; left: 10%;'})
.addDiv()
.addDiv({'className': 'bm-dragbar'}).buildElement()
.addButton({'className': 'bm-button-minimize', 'textContent': '↑'},
(instance, button) => {
button.onclick = () => {
let isMinimized = false;
if (button.textContent == '↑') {
button.textContent = '↓';
} else {
button.textContent = '↑';
isMinimized = true;
}
}
}
).buildElement()
.buildElement()
.buildElement()
.buildOverlay();
}</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>

File diff suppressed because it is too large Load diff

View file

@ -1,222 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Class: exports</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">Class: exports</h1>
<section>
<header>
<h2><span class="attribs"><span class="type-signature"></span></span>exports<span class="signature">(templateManager)</span><span class="type-signature"></span></h2>
</header>
<article>
<div class="container-overview">
<h4 class="name" id="exports"><span class="type-signature"></span>new exports<span class="signature">(templateManager)</span><span class="type-signature"></span></h4>
<div class="description">
Constructor for ApiManager class
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>templateManager</code></td>
<td class="type">
<span class="param-type">TemplateManager</span>
</td>
<td class="description last"></td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-since">Since:</dt>
<dd class="tag-since"><ul class="dummy"><li>0.11.34</li></ul></dd>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="apiManager.js.html">apiManager.js</a>, <a href="apiManager.js.html#line15">line 15</a>
</li></ul></dd>
</dl>
</div>
</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>

View file

@ -1,111 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: observers.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: observers.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/** This class contains all MutationObservers used (which is 1 probably).
* This is not an object, but rather a "collection" of functions (in a class).
* @since 0.43.2
*/
export default class Observers {
/** The constructor for the observer class
* @since 0.43.2
*/
constructor() {
this.observerBody = null;
this.observerBodyTarget = null;
this.targetDisplayCoords = '#bm-display-coords';
}
/** Creates the MutationObserver for document.body
* @param {HTMLElement} target - Targeted element to watch
* @returns {Observers} this (Observers class)
* @since 0.43.2
*/
createObserverBody(target) {
this.observerBodyTarget = target;
this.observerBody = new MutationObserver((mutations) => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (!(node instanceof HTMLElement)) {continue;} // Does not track non-HTMLElements
if (node.matches?.(this.targetDisplayCoords)) {
}
}
}
})
return this;
}
/** Retrieves the MutationObserver that watches document.body
* @returns {MutationObserver}
* @since 0.43.2
*/
getObserverBody() {
return this.observerBody;
}
/** Observe a MutationObserver
* @param {MutationObserver} observer - The MutationObserver
* @param {boolean} watchChildList - (Optional) Should childList be watched? False by default
* @param {boolean} watchSubtree - (Optional) Should childList be watched? False by default
* @since 0.43.2
*/
observe(observer, watchChildList=false, watchSubtree=false) {
observer.observe(this.observerBodyTarget, {
childList: watchChildList,
subtree: watchSubtree
});
}
}</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>

View file

@ -1,25 +0,0 @@
/*global document */
(() => {
const source = document.getElementsByClassName('prettyprint source linenums');
let i = 0;
let lineNumber = 0;
let lineId;
let lines;
let totalLines;
let anchorHash;
if (source && source[0]) {
anchorHash = document.location.hash.substring(1);
lines = source[0].getElementsByTagName('li');
totalLines = lines.length;
for (; i < totalLines; i++) {
lineNumber++;
lineId = `line${lineNumber}`;
lines[i].id = lineId;
if (lineId === anchorHash) {
lines[i].className += ' selected';
}
}
}
})();

View file

@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -1,2 +0,0 @@
PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n "]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com",
/^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]);

View file

@ -1,28 +0,0 @@
var q=null;window.PR_SHOULD_USE_CONTINUATION=!0;
(function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a=
[],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c<i;++c){var j=f[c];if(/\\[bdsw]/i.test(j))a.push(j);else{var j=m(j),d;c+2<i&&"-"===f[c+1]?(d=m(f[c+2]),c+=2):d=j;b.push([j,d]);d<65||j>122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;c<b.length;++c)i=b[c],i[0]<=j[1]+1?j[1]=Math.max(j[1],i[1]):f.push(j=i);b=["["];o&&b.push("^");b.push.apply(b,a);for(c=0;c<
f.length;++c)i=f[c],b.push(e(i[0])),i[1]>i[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c<b;++c){var j=f[c];j==="("?++i:"\\"===j.charAt(0)&&(j=+j.substring(1))&&j<=i&&(d[j]=-1)}for(c=1;c<d.length;++c)-1===d[c]&&(d[c]=++t);for(i=c=0;c<b;++c)j=f[c],j==="("?(++i,d[i]===void 0&&(f[c]="(?:")):"\\"===j.charAt(0)&&
(j=+j.substring(1))&&j<=i&&(f[c]="\\"+d[i]);for(i=c=0;c<b;++c)"^"===f[c]&&"^"!==f[c+1]&&(f[c]="");if(a.ignoreCase&&s)for(c=0;c<b;++c)j=f[c],a=j.charAt(0),j.length>=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p<d;++p){var g=a[p];if(g.ignoreCase)l=!0;else if(/[a-z]/i.test(g.source.replace(/\\u[\da-f]{4}|\\x[\da-f]{2}|\\[^UXux]/gi,""))){s=!0;l=!1;break}}for(var r=
{b:8,t:9,n:10,v:11,f:12,r:13},n=[],p=0,d=a.length;p<d;++p){g=a[p];if(g.global||g.multiline)throw Error(""+g);n.push("(?:"+y(g)+")")}return RegExp(n.join("|"),l?"gi":"g")}function M(a){function m(a){switch(a.nodeType){case 1:if(e.test(a.className))break;for(var g=a.firstChild;g;g=g.nextSibling)m(g);g=a.nodeName;if("BR"===g||"LI"===g)h[s]="\n",t[s<<1]=y++,t[s++<<1|1]=a;break;case 3:case 4:g=a.nodeValue,g.length&&(g=p?g.replace(/\r\n?/g,"\n"):g.replace(/[\t\n\r ]+/g," "),h[s]=g,t[s<<1]=y,y+=g.length,
t[s++<<1|1]=a)}}var e=/(?:^|\s)nocode(?:\s|$)/,h=[],y=0,t=[],s=0,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=document.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);m(a);return{a:h.join("").replace(/\n$/,""),c:t}}function B(a,m,e,h){m&&(a={a:m,d:a},e(a),h.push.apply(h,a.e))}function x(a,m){function e(a){for(var l=a.d,p=[l,"pln"],d=0,g=a.a.match(y)||[],r={},n=0,z=g.length;n<z;++n){var f=g[n],b=r[f],o=void 0,c;if(typeof b===
"string")c=!1;else{var i=h[f.charAt(0)];if(i)o=f.match(i[1]),b=i[0];else{for(c=0;c<t;++c)if(i=m[c],o=f.match(i[1])){b=i[0];break}o||(b="pln")}if((c=b.length>=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m),
l=[],p={},d=0,g=e.length;d<g;++d){var r=e[d],n=r[3];if(n)for(var k=n.length;--k>=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/,
q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/,
q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g,
"");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a),
a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e}
for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g<d.length;++g)e(d[g]);m===(m|0)&&d[0].setAttribute("value",
m);var r=s.createElement("OL");r.className="linenums";for(var n=Math.max(0,m-1|0)||0,g=0,z=d.length;g<z;++g)l=d[g],l.className="L"+(g+n)%10,l.firstChild||l.appendChild(s.createTextNode("\xa0")),r.appendChild(l);a.appendChild(r)}function k(a,m){for(var e=m.length;--e>=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*</.test(m)?"default-markup":"default-code";return A[a]}function E(a){var m=
a.g;try{var e=M(a.h),h=e.a;a.a=h;a.c=e.c;a.d=0;C(m,h)(a);var k=/\bMSIE\b/.test(navigator.userAgent),m=/\n/g,t=a.a,s=t.length,e=0,l=a.c,p=l.length,h=0,d=a.e,g=d.length,a=0;d[g]=s;var r,n;for(n=r=0;n<g;)d[n]!==d[n+2]?(d[r++]=d[n++],d[r++]=d[n++]):n+=2;g=r;for(n=r=0;n<g;){for(var z=d[n],f=d[n+1],b=n+2;b+2<=g&&d[b+1]===f;)b+=2;d[r++]=z;d[r++]=f;n=b}for(d.length=r;h<p;){var o=l[h+2]||s,c=d[a+2]||s,b=Math.min(o,c),i=l[h+1],j;if(i.nodeType!==1&&(j=t.substring(e,b))){k&&(j=j.replace(m,"\r"));i.nodeValue=
j;var u=i.ownerDocument,v=u.createElement("SPAN");v.className=d[a+1];var x=i.parentNode;x.replaceChild(v,i);v.appendChild(i);e<o&&(l[h+1]=i=u.createTextNode(t.substring(b,o)),x.insertBefore(i,v.nextSibling))}e=b;e>=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],
"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"],
H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],
J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+
I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^<?]+/],["dec",/^<!\w[^>]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^<xmp\b[^>]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^<script\b[^>]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),
["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css",
/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),
["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes",
hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p<h.length&&l.now()<e;p++){var n=h[p],k=n.className;if(k.indexOf("prettyprint")>=0){var k=k.match(g),f,b;if(b=
!k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p<h.length?setTimeout(m,
250):a&&a()}for(var e=[document.getElementsByTagName("pre"),document.getElementsByTagName("code"),document.getElementsByTagName("xmp")],h=[],k=0;k<e.length;++k)for(var t=0,s=e[k].length;t<s;++t)h.push(e[k][t]);var e=q,l=Date;l.now||(l={now:function(){return+new Date}});var p=0,d,g=/\blang(?:uage)?-([\w.]+)(?!\S)/;m()};window.PR={createSimpleLexer:x,registerLangHandler:k,sourceDecorator:u,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit",
PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ"}})();

View file

@ -1,358 +0,0 @@
@font-face {
font-family: 'Open Sans';
font-weight: normal;
font-style: normal;
src: url('../fonts/OpenSans-Regular-webfont.eot');
src:
local('Open Sans'),
local('OpenSans'),
url('../fonts/OpenSans-Regular-webfont.eot?#iefix') format('embedded-opentype'),
url('../fonts/OpenSans-Regular-webfont.woff') format('woff'),
url('../fonts/OpenSans-Regular-webfont.svg#open_sansregular') format('svg');
}
@font-face {
font-family: 'Open Sans Light';
font-weight: normal;
font-style: normal;
src: url('../fonts/OpenSans-Light-webfont.eot');
src:
local('Open Sans Light'),
local('OpenSans Light'),
url('../fonts/OpenSans-Light-webfont.eot?#iefix') format('embedded-opentype'),
url('../fonts/OpenSans-Light-webfont.woff') format('woff'),
url('../fonts/OpenSans-Light-webfont.svg#open_sanslight') format('svg');
}
html
{
overflow: auto;
background-color: #fff;
font-size: 14px;
}
body
{
font-family: 'Open Sans', sans-serif;
line-height: 1.5;
color: #4d4e53;
background-color: white;
}
a, a:visited, a:active {
color: #0095dd;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
header
{
display: block;
padding: 0px 4px;
}
tt, code, kbd, samp {
font-family: Consolas, Monaco, 'Andale Mono', monospace;
}
.class-description {
font-size: 130%;
line-height: 140%;
margin-bottom: 1em;
margin-top: 1em;
}
.class-description:empty {
margin: 0;
}
#main {
float: left;
width: 70%;
}
article dl {
margin-bottom: 40px;
}
article img {
max-width: 100%;
}
section
{
display: block;
background-color: #fff;
padding: 12px 24px;
border-bottom: 1px solid #ccc;
margin-right: 30px;
}
.variation {
display: none;
}
.signature-attributes {
font-size: 60%;
color: #aaa;
font-style: italic;
font-weight: lighter;
}
nav
{
display: block;
float: right;
margin-top: 28px;
width: 30%;
box-sizing: border-box;
border-left: 1px solid #ccc;
padding-left: 16px;
}
nav ul {
font-family: 'Lucida Grande', 'Lucida Sans Unicode', arial, sans-serif;
font-size: 100%;
line-height: 17px;
padding: 0;
margin: 0;
list-style-type: none;
}
nav ul a, nav ul a:visited, nav ul a:active {
font-family: Consolas, Monaco, 'Andale Mono', monospace;
line-height: 18px;
color: #4D4E53;
}
nav h3 {
margin-top: 12px;
}
nav li {
margin-top: 6px;
}
footer {
display: block;
padding: 6px;
margin-top: 12px;
font-style: italic;
font-size: 90%;
}
h1, h2, h3, h4 {
font-weight: 200;
margin: 0;
}
h1
{
font-family: 'Open Sans Light', sans-serif;
font-size: 48px;
letter-spacing: -2px;
margin: 12px 24px 20px;
}
h2, h3.subsection-title
{
font-size: 30px;
font-weight: 700;
letter-spacing: -1px;
margin-bottom: 12px;
}
h3
{
font-size: 24px;
letter-spacing: -0.5px;
margin-bottom: 12px;
}
h4
{
font-size: 18px;
letter-spacing: -0.33px;
margin-bottom: 12px;
color: #4d4e53;
}
h5, .container-overview .subsection-title
{
font-size: 120%;
font-weight: bold;
letter-spacing: -0.01em;
margin: 8px 0 3px 0;
}
h6
{
font-size: 100%;
letter-spacing: -0.01em;
margin: 6px 0 3px 0;
font-style: italic;
}
table
{
border-spacing: 0;
border: 0;
border-collapse: collapse;
}
td, th
{
border: 1px solid #ddd;
margin: 0px;
text-align: left;
vertical-align: top;
padding: 4px 6px;
display: table-cell;
}
thead tr
{
background-color: #ddd;
font-weight: bold;
}
th { border-right: 1px solid #aaa; }
tr > th:last-child { border-right: 1px solid #ddd; }
.ancestors, .attribs { color: #999; }
.ancestors a, .attribs a
{
color: #999 !important;
text-decoration: none;
}
.clear
{
clear: both;
}
.important
{
font-weight: bold;
color: #950B02;
}
.yes-def {
text-indent: -1000px;
}
.type-signature {
color: #aaa;
}
.name, .signature {
font-family: Consolas, Monaco, 'Andale Mono', monospace;
}
.details { margin-top: 14px; border-left: 2px solid #DDD; }
.details dt { width: 120px; float: left; padding-left: 10px; padding-top: 6px; }
.details dd { margin-left: 70px; }
.details ul { margin: 0; }
.details ul { list-style-type: none; }
.details li { margin-left: 30px; padding-top: 6px; }
.details pre.prettyprint { margin: 0 }
.details .object-value { padding-top: 0; }
.description {
margin-bottom: 1em;
margin-top: 1em;
}
.code-caption
{
font-style: italic;
font-size: 107%;
margin: 0;
}
.source
{
border: 1px solid #ddd;
width: 80%;
overflow: auto;
}
.prettyprint.source {
width: inherit;
}
.source code
{
font-size: 100%;
line-height: 18px;
display: block;
padding: 4px 12px;
margin: 0;
background-color: #fff;
color: #4D4E53;
}
.prettyprint code span.line
{
display: inline-block;
}
.prettyprint.linenums
{
padding-left: 70px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.prettyprint.linenums ol
{
padding-left: 0;
}
.prettyprint.linenums li
{
border-left: 3px #ddd solid;
}
.prettyprint.linenums li.selected,
.prettyprint.linenums li.selected *
{
background-color: lightyellow;
}
.prettyprint.linenums li *
{
-webkit-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
}
.params .name, .props .name, .name code {
color: #4D4E53;
font-family: Consolas, Monaco, 'Andale Mono', monospace;
font-size: 100%;
}
.params td.description > p:first-child,
.props td.description > p:first-child
{
margin-top: 0;
padding-top: 0;
}
.params td.description > p:last-child,
.props td.description > p:last-child
{
margin-bottom: 0;
padding-bottom: 0;
}
.disabled {
color: #454545;
}

View file

@ -1,111 +0,0 @@
/* JSDoc prettify.js theme */
/* plain text */
.pln {
color: #000000;
font-weight: normal;
font-style: normal;
}
/* string content */
.str {
color: #006400;
font-weight: normal;
font-style: normal;
}
/* a keyword */
.kwd {
color: #000000;
font-weight: bold;
font-style: normal;
}
/* a comment */
.com {
font-weight: normal;
font-style: italic;
}
/* a type name */
.typ {
color: #000000;
font-weight: normal;
font-style: normal;
}
/* a literal value */
.lit {
color: #006400;
font-weight: normal;
font-style: normal;
}
/* punctuation */
.pun {
color: #000000;
font-weight: bold;
font-style: normal;
}
/* lisp open bracket */
.opn {
color: #000000;
font-weight: bold;
font-style: normal;
}
/* lisp close bracket */
.clo {
color: #000000;
font-weight: bold;
font-style: normal;
}
/* a markup tag name */
.tag {
color: #006400;
font-weight: normal;
font-style: normal;
}
/* a markup attribute name */
.atn {
color: #006400;
font-weight: normal;
font-style: normal;
}
/* a markup attribute value */
.atv {
color: #006400;
font-weight: normal;
font-style: normal;
}
/* a declaration */
.dec {
color: #000000;
font-weight: bold;
font-style: normal;
}
/* a variable name */
.var {
color: #000000;
font-weight: normal;
font-style: normal;
}
/* a function name */
.fun {
color: #000000;
font-weight: bold;
font-style: normal;
}
/* Specify class=linenums on a pre to get line numbering */
ol.linenums {
margin-top: 0;
margin-bottom: 0;
}

View file

@ -1,132 +0,0 @@
/* Tomorrow Theme */
/* Original theme - https://github.com/chriskempson/tomorrow-theme */
/* Pretty printing styles. Used with prettify.js. */
/* SPAN elements with the classes below are added by prettyprint. */
/* plain text */
.pln {
color: #4d4d4c; }
@media screen {
/* string content */
.str {
color: #718c00; }
/* a keyword */
.kwd {
color: #8959a8; }
/* a comment */
.com {
color: #8e908c; }
/* a type name */
.typ {
color: #4271ae; }
/* a literal value */
.lit {
color: #f5871f; }
/* punctuation */
.pun {
color: #4d4d4c; }
/* lisp open bracket */
.opn {
color: #4d4d4c; }
/* lisp close bracket */
.clo {
color: #4d4d4c; }
/* a markup tag name */
.tag {
color: #c82829; }
/* a markup attribute name */
.atn {
color: #f5871f; }
/* a markup attribute value */
.atv {
color: #3e999f; }
/* a declaration */
.dec {
color: #f5871f; }
/* a variable name */
.var {
color: #c82829; }
/* a function name */
.fun {
color: #4271ae; } }
/* Use higher contrast and text-weight for printable form. */
@media print, projection {
.str {
color: #060; }
.kwd {
color: #006;
font-weight: bold; }
.com {
color: #600;
font-style: italic; }
.typ {
color: #404;
font-weight: bold; }
.lit {
color: #044; }
.pun, .opn, .clo {
color: #440; }
.tag {
color: #006;
font-weight: bold; }
.atn {
color: #404; }
.atv {
color: #060; } }
/* Style */
/*
pre.prettyprint {
background: white;
font-family: Consolas, Monaco, 'Andale Mono', monospace;
font-size: 12px;
line-height: 1.5;
border: 1px solid #ccc;
padding: 10px; }
*/
/* Specify class=linenums on a pre to get line numbering */
ol.linenums {
margin-top: 0;
margin-bottom: 0; }
/* IE indents via margin-left */
li.L0,
li.L1,
li.L2,
li.L3,
li.L4,
li.L5,
li.L6,
li.L7,
li.L8,
li.L9 {
/* */ }
/* Alternate shading for lines */
li.L1,
li.L3,
li.L5,
li.L7,
li.L9 {
/* */ }

View file

@ -1,444 +0,0 @@
<!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 = '!#$%&amp;\'()*+,-./0123456789:;&lt;=>?@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&lt;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&lt;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>

View file

@ -1,178 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: utils.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: utils.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>
/** Sanitizes HTML to display as plain-text.
* This prevents some Cross Site Scripting (XSS).
* This is handy when you are displaying user-made data, and you *must* use innerHTML.
* @param {string} text - The text to sanitize
* @returns {string} HTML escaped string
* @since 0.44.2
* @example
* const paragraph = document.createElement('p');
* paragraph.innerHTML = escapeHTML('&lt;u>Foobar.&lt;/u>');
* // Output:
* // (Does not include the paragraph element)
* // (Output is not HTML formatted)
* &lt;p>
* "&lt;u>Foobar.&lt;/u>"
* &lt;/p>
*/
export function escapeHTML(text) {
const div = document.createElement('div'); // Creates a div
div.textContent = text; // Puts the text in a PLAIN-TEXT property
return div.innerHTML; // Returns the HTML property of the div
}
/** Converts the server tile-pixel coordinate system to the displayed tile-pixel coordinate system.
* @param {string[]} tile - The tile to convert (as an array like ["12", "124"])
* @param {string[]} pixel - The pixel to convert (as an array like ["12", "124"])
* @returns {number[]} [tile, pixel]
* @since 0.42.4
* @example
* console.log(serverTPtoDisplayTP(['12', '123'], ['34', '567'])); // [34, 3567]
*/
export function serverTPtoDisplayTP(tile, pixel) {
return [((parseInt(tile[0]) % 4) * 1000) + parseInt(pixel[0]), ((parseInt(tile[1]) % 4) * 1000) + parseInt(pixel[1])];
}
/** Negative-Safe Modulo. You can pass negative numbers into this.
* @param {number} a - The first number
* @param {number} b - The second number
* @returns {number} Result
* @author osuplace
* @since 0.55.8
*/
export function negativeSafeModulo(a, b) {
return (a % b + b) % b;
}
/** Bypasses terser's stripping of console function calls.
* This is so the non-obfuscated code will contain debugging console calls, but the distributed version won't.
* However, the distributed version needs to call the console somehow, so this wrapper function is how.
* This is the same as `console.log()`.
* @param {...any} args - Arguments to be passed into the `log()` function of the Console
* @since 0.58.9
*/
export function consoleLog(...args) {((consoleLog) => consoleLog(...args))(console.log);}
/** Bypasses terser's stripping of console function calls.
* This is so the non-obfuscated code will contain debugging console calls, but the distributed version won't.
* However, the distributed version needs to call the console somehow, so this wrapper function is how.
* This is the same as `console.error()`.
* @param {...any} args - Arguments to be passed into the `error()` function of the Console
* @since 0.58.13
*/
export function consoleError(...args) {((consoleError) => consoleError(...args))(console.error);}
/** Bypasses terser's stripping of console function calls.
* This is so the non-obfuscated code will contain debugging console calls, but the distributed version won't.
* However, the distributed version needs to call the console somehow, so this wrapper function is how.
* This is the same as `console.warn()`.
* @param {...any} args - Arguments to be passed into the `warn()` function of the Console
* @since 0.58.13
*/
export function consoleWarn(...args) {((consoleWarn) => consoleWarn(...args))(console.warn);}
/** Encodes a number into a custom encoded string.
* @param {number} number - The number to encode
* @param {string} encoding - The characters to use when encoding
* @since 0.65.2
* @returns {string} Encoded string
* @example
* const encode = '012abcABC'; // Base 9
* console.log(numberToEncoded(0, encode)); // 0
* console.log(numberToEncoded(5, encode)); // c
* console.log(numberToEncoded(15, encode)); // 1A
* console.log(numberToEncoded(12345, encode)); // 1BCaA
*/
export function numberToEncoded(number, encoding) {
if (number === 0) return encoding[0]; // End quickly if number equals 0. No special calculation needed
let result = ''; // The encoded string
const base = encoding.length; // The number of characters used, which determines the base
// Base conversion algorithm
while (number > 0) {
result = encoding[number % base] + result; // Find's the character's encoded value determined by the modulo of the base
number = Math.floor(number / base); // Divides the number by the base so the next iteration can find the next modulo character
}
return result; // The final encoded string
}
/** Converts a Uint8 array to base64 using the browser's built-in binary to ASCII function
* @param {Uint8Array} uint8 - The Uint8Array to convert
* @returns {Uint8Array} The base64 encoded Uint8Array
* @since 0.72.9
*/
export function uint8ToBase64(uint8) {
let binary = '';
for (let i = 0; i &lt; uint8.length; i++) {
binary += String.fromCharCode(uint8[i]);
}
return btoa(binary); // Binary to ASCII
}
/** Decodes a base 64 encoded Uint8 array using the browser's built-in ASCII to binary function
* @param {Uint8Array} base64 - The base 64 encoded Uint8Array to convert
* @returns {Uint8Array} The decoded Uint8Array
* @since 0.72.9
*/
export function base64ToUint8(base64) {
const binary = atob(base64); // ASCII to Binary
const array = new Uint8Array(binary.length);
for (let i = 0; i &lt; binary.length; i++) {
array[i] = binary.charCodeAt(i);
}
return array;
}</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>

17
jsdoc.json Normal file
View file

@ -0,0 +1,17 @@
{
"source": {
"include": ["src"],
"exclude": ["node_modules", "build", "dist"]
},
"opts": {
"destination": "docs",
"template": "node_modules/minami",
"recurse": true,
"encoding": "utf8"
},
"plugins": [],
"templates": {
"cleverLinks": false,
"monospaceLinks": false
}
}