Added color filter table

This commit is contained in:
SwingTheVine 2026-02-17 01:24:36 -05:00
parent 1f2d86f73c
commit fdb571ac6b
14 changed files with 1031 additions and 32 deletions

View file

@ -1,4 +1,15 @@
/* src/overlay.css */
.bm-screenreader {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.bm-window {
position: fixed;
background-color: rgba(21, 48, 99, 0.9);
@ -10,6 +21,7 @@
top: 75px;
left: 60px;
width: auto;
max-height: calc(100vh - 150px);
max-width: calc(100% - 135px);
font-family:
"Roboto Mono",
@ -114,6 +126,23 @@
margin: 0 auto;
fill: #111;
}
.bm-window button.bm-button-trans {
background-color: unset;
}
.bm-button-trans.bm-button-hover-white:hover,
.bm-button-trans.bm-button-hover-white:focus {
background-color: rgba(255, 255, 255, 0.17);
}
.bm-button-trans.bm-button-hover-white:active {
background-color: rgba(255, 255, 255, 0.22);
}
.bm-button-trans.bm-button-hover-black:hover,
.bm-button-trans.bm-button-hover-black:focus {
background-color: rgba(0, 0, 0, 0.17);
}
.bm-button-trans.bm-button-hover-black:active {
background-color: rgba(0, 0, 0, 0.22);
}
input[type=number].bm-input-coords {
appearance: auto;
-moz-appearance: textfield;
@ -176,3 +205,79 @@ input[type=file] {
align-items: center;
gap: 0.5ch;
}
#bm-window-filter .bm-container:has(table) {
max-height: 60vh;
overflow: auto;
}
#bm-window-filter table {
table-layout: fixed;
width: 50ch;
border-collapse: separate;
border-spacing: 0 0.5em;
}
#bm-window-filter tr {
padding: 0.5em 0;
}
.bm-filter-tbl-clr {
display: block;
border: thick double lightgray;
width: fit-content;
height: fit-content;
padding: 1ch;
}
.bm-filter-tbl-clr button {
padding: 0.75em 0.5ch;
}
.bm-filter-tbl-clr svg {
width: 4ch;
isolation: isolate;
}
.bm-filter-tbl-id {
position: relative;
top: 0.75em;
left: 0.5ch;
}
.bm-filter-tbl-prmim {
position: relative;
top: 0.75em;
left: -3.5ch;
}
.bm-filter-tbl-name {
position: relative;
top: -0.75em;
left: -16ch;
text-wrap: nowrap;
}
.bm-filter-tbl-crct {
display: block;
width: 100%;
position: relative;
right: 18ch;
top: 0.75em;
text-align: right;
}
.bm-filter-tbl-totl {
display: block;
width: 100%;
position: relative;
left: -16ch;
top: 0.75em;
text-align: left;
}
.bm-filter-tbl-totl::after {
content: "/";
position: absolute;
left: -1.5ch;
top: 50%;
transform: translateY(-50%);
}
#bm-window-filter tfoot {
display: table-header-group;
}
#bm-window-filter tfoot th {
text-align: left;
}
#bm-window-filter tfoot td {
text-align: right;
padding-left: 1ch;
}

View file

@ -2,7 +2,7 @@
// @name Blue Marble
// @name:en Blue Marble
// @namespace https://github.com/SwingTheVine/
// @version 0.88.179
// @version 0.88.207
// @description A userscript to automate and/or enhance the user experience on Wplace.live. Make sure to comply with the site's Terms of Service, and rules! This script is not affiliated with Wplace.live in any way, use at your own risk. This script is not affiliated with TamperMonkey. The author of this userscript is not responsible for any damages, issues, loss of data, or punishment that may occur as a result of using this script. This script is provided "as is" under the MPL-2.0 license. The "Blue Marble" icon is licensed under CC0 1.0 Universal (CC0 1.0) Public Domain Dedication. The image is owned by NASA.
// @description:en A userscript to automate and/or enhance the user experience on Wplace.live. Make sure to comply with the site's Terms of Service, and rules! This script is not affiliated with Wplace.live in any way, use at your own risk. This script is not affiliated with TamperMonkey. The author of this userscript is not responsible for any damages, issues, loss of data, or punishment that may occur as a result of using this script. This script is provided "as is" under the MPL-2.0 license. The "Blue Marble" icon is licensed under CC0 1.0 Universal (CC0 1.0) Public Domain Dedication. The image is owned by NASA.
// @author SwingTheVine
@ -150,7 +150,7 @@
* 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.<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`.
* @param {function(Overlay, HTMLElement):void} [callback=()=>{}] - Additional JS modification to the `small`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.55.8
* @example
@ -169,11 +169,34 @@
callback(this, small);
return this;
}
/** Adds a `span` to the overlay.
* This `span` element will have properties shared between all `span` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.<string, any>} [additionalProperties={}] - The DOM properties of the `span` that are NOT shared between all overlay `span` elements. These should be camelCase.
* @param {function(Overlay, HTMLSpanElement):void} [callback=()=>{}] - Additional JS modification to the `span`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.55.8
* @example
* // Assume all <span> elements have a shared class (e.g. {'className': 'bar'})
* overlay.addSpan({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body);
* // Output:
* // (Assume <body> already exists in the webpage)
* <body>
* <span id="foo" class="bar">Foobar.</span>
* </body>
*/
addSpan(additionalProperties = {}, callback = () => {
}) {
const properties = {};
const span = __privateMethod(this, _Overlay_instances, createElement_fn).call(this, "span", properties, additionalProperties);
callback(this, span);
return this;
}
/** Adds a `details` to the overlay.
* This `details` element will have properties shared between all `details` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.<string, any>} [additionalProperties={}] - The DOM properties of the `details` that are NOT shared between all overlay `details` elements. These should be camelCase.
* @param {function(Overlay, HTMLParagraphElement):void} [callback=()=>{}] - Additional JS modification to the `details`.
* @param {function(Overlay, HTMLDetailsElement):void} [callback=()=>{}] - Additional JS modification to the `details`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.88.96
* @example
@ -196,7 +219,7 @@
* This `summary` element will have properties shared between all `summary` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.<string, any>} [additionalProperties={}] - The DOM properties of the `summary` that are NOT shared between all overlay `summary` elements. These should be camelCase.
* @param {function(Overlay, HTMLParagraphElement):void} [callback=()=>{}] - Additional JS modification to the `summary`.
* @param {function(Overlay, HTMLElement):void} [callback=()=>{}] - Additional JS modification to the `summary`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.88.96
* @example
@ -338,6 +361,282 @@
callback(this, label, checkbox);
return this;
}
/** Adds an ordered list to the overlay.
* This `ol` element will have properties shared between all `ol` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.<string, any>} [additionalProperties={}] - The DOM properties of the `ol` that are NOT shared between all overlay `ol` elements. These should be camelCase.
* @param {function(Overlay, HTMLOListElement):void} [callback=()=>{}] - Additional JS modification to the `ol`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.88.180
* @example
* // Assume all <ol> elements have a shared class (e.g. {'className': 'bar'})
* overlay.addOl({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body);
* // Output:
* // (Assume <body> already exists in the webpage)
* <body>
* <ol id="foo" class="bar">Foobar.</ol>
* </body>
*/
addOl(additionalProperties = {}, callback = () => {
}) {
const properties = {};
const ol = __privateMethod(this, _Overlay_instances, createElement_fn).call(this, "ol", properties, additionalProperties);
callback(this, ol);
return this;
}
/** Adds an unordered list to the overlay.
* This `ul` element will have properties shared between all `ul` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.<string, any>} [additionalProperties={}] - The DOM properties of the `ul` that are NOT shared between all overlay `ul` elements. These should be camelCase.
* @param {function(Overlay, HTMLUListElement):void} [callback=()=>{}] - Additional JS modification to the `ul`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.88.180
* @example
* // Assume all <ul> elements have a shared class (e.g. {'className': 'bar'})
* overlay.addUl({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body);
* // Output:
* // (Assume <body> already exists in the webpage)
* <body>
* <ul id="foo" class="bar">Foobar.</ul>
* </body>
*/
addUl(additionalProperties = {}, callback = () => {
}) {
const properties = {};
const ul = __privateMethod(this, _Overlay_instances, createElement_fn).call(this, "ul", properties, additionalProperties);
callback(this, ul);
return this;
}
/** Adds a `menu` to the overlay.
* This `menu` element will have properties shared between all `menu` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.<string, any>} [additionalProperties={}] - The DOM properties of the `menu` that are NOT shared between all overlay `menu` elements. These should be camelCase.
* @param {function(Overlay, HTMLMenuElement):void} [callback=()=>{}] - Additional JS modification to the `menu`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.88.180
* @example
* // Assume all <menu> elements have a shared class (e.g. {'className': 'bar'})
* overlay.addMenu({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body);
* // Output:
* // (Assume <body> already exists in the webpage)
* <body>
* <menu id="foo" class="bar">Foobar.</menu>
* </body>
*/
addMenu(additionalProperties = {}, callback = () => {
}) {
const properties = {};
const menu = __privateMethod(this, _Overlay_instances, createElement_fn).call(this, "menu", properties, additionalProperties);
callback(this, menu);
return this;
}
/** Adds a list item to the overlay.
* This `li` element will have properties shared between all `li` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.<string, any>} [additionalProperties={}] - The DOM properties of the `li` that are NOT shared between all overlay `li` elements. These should be camelCase.
* @param {function(Overlay, HTMLLIElement):void} [callback=()=>{}] - Additional JS modification to the `li`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.88.180
* @example
* // Assume all <li> elements have a shared class (e.g. {'className': 'bar'})
* overlay.addLi({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body);
* // Output:
* // (Assume <body> already exists in the webpage)
* <body>
* <li id="foo" class="bar">Foobar.</li>
* </body>
*/
addLi(additionalProperties = {}, callback = () => {
}) {
const properties = {};
const li = __privateMethod(this, _Overlay_instances, createElement_fn).call(this, "li", properties, additionalProperties);
callback(this, li);
return this;
}
/** Adds a table to the overlay.
* This `table` element will have properties shared between all `table` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.<string, any>} [additionalProperties={}] - The DOM properties of the `table` that are NOT shared between all overlay `table` elements. These should be camelCase.
* @param {function(Overlay, HTMLTableElement):void} [callback=()=>{}] - Additional JS modification to the `table`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.88.180
* @example
* // Assume all <table> elements have a shared class (e.g. {'className': 'bar'})
* overlay.addTable({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body);
* // Output:
* // (Assume <body> already exists in the webpage)
* <body>
* <table id="foo" class="bar">Foobar.</table>
* </body>
*/
addTable(additionalProperties = {}, callback = () => {
}) {
const properties = {};
const table = __privateMethod(this, _Overlay_instances, createElement_fn).call(this, "table", properties, additionalProperties);
callback(this, table);
return this;
}
/** Adds a table caption to the overlay.
* This `caption` element will have properties shared between all `caption` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.<string, any>} [additionalProperties={}] - The DOM properties of the `caption` that are NOT shared between all overlay `caption` elements. These should be camelCase.
* @param {function(Overlay, HTMLTableCaptionElement):void} [callback=()=>{}] - Additional JS modification to the `caption`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.88.180
* @example
* // Assume all <caption> elements have a shared class (e.g. {'className': 'bar'})
* overlay.addCaption({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body);
* // Output:
* // (Assume <body> already exists in the webpage)
* <body>
* <caption id="foo" class="bar">Foobar.</caption>
* </body>
*/
addCaption(additionalProperties = {}, callback = () => {
}) {
const properties = {};
const caption = __privateMethod(this, _Overlay_instances, createElement_fn).call(this, "caption", properties, additionalProperties);
callback(this, caption);
return this;
}
/** Adds a table header to the overlay.
* This `thead` element will have properties shared between all `thead` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.<string, any>} [additionalProperties={}] - The DOM properties of the `thead` that are NOT shared between all overlay `thead` elements. These should be camelCase.
* @param {function(Overlay, HTMLTableSectionElement):void} [callback=()=>{}] - Additional JS modification to the `thead`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.88.180
* @example
* // Assume all <thead> elements have a shared class (e.g. {'className': 'bar'})
* overlay.addThead({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body);
* // Output:
* // (Assume <body> already exists in the webpage)
* <body>
* <thead id="foo" class="bar">Foobar.</thead>
* </body>
*/
addThead(additionalProperties = {}, callback = () => {
}) {
const properties = {};
const thead = __privateMethod(this, _Overlay_instances, createElement_fn).call(this, "thead", properties, additionalProperties);
callback(this, thead);
return this;
}
/** Adds a table body to the overlay.
* This `tbody` element will have properties shared between all `tbody` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.<string, any>} [additionalProperties={}] - The DOM properties of the `tbody` that are NOT shared between all overlay `tbody` elements. These should be camelCase.
* @param {function(Overlay, HTMLTableSectionElement):void} [callback=()=>{}] - Additional JS modification to the `tbody`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.88.180
* @example
* // Assume all <tbody> elements have a shared class (e.g. {'className': 'bar'})
* overlay.addTbody({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body);
* // Output:
* // (Assume <body> already exists in the webpage)
* <body>
* <tbody id="foo" class="bar">Foobar.</tbody>
* </body>
*/
addTbody(additionalProperties = {}, callback = () => {
}) {
const properties = {};
const tbody = __privateMethod(this, _Overlay_instances, createElement_fn).call(this, "tbody", properties, additionalProperties);
callback(this, tbody);
return this;
}
/** Adds a table footer to the overlay.
* This `tfoot` element will have properties shared between all `tfoot` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.<string, any>} [additionalProperties={}] - The DOM properties of the `tfoot` that are NOT shared between all overlay `tfoot` elements. These should be camelCase.
* @param {function(Overlay, HTMLTableSectionElement):void} [callback=()=>{}] - Additional JS modification to the `tfoot`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.88.180
* @example
* // Assume all <tfoot> elements have a shared class (e.g. {'className': 'bar'})
* overlay.addTfoot({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body);
* // Output:
* // (Assume <body> already exists in the webpage)
* <body>
* <tfoot id="foo" class="bar">Foobar.</tfoot>
* </body>
*/
addTfoot(additionalProperties = {}, callback = () => {
}) {
const properties = {};
const tfoot = __privateMethod(this, _Overlay_instances, createElement_fn).call(this, "tfoot", properties, additionalProperties);
callback(this, tfoot);
return this;
}
/** Adds a table row to the overlay.
* This `tr` element will have properties shared between all `tr` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.<string, any>} [additionalProperties={}] - The DOM properties of the `tr` that are NOT shared between all overlay `tr` elements. These should be camelCase.
* @param {function(Overlay, HTMLTableRowElement):void} [callback=()=>{}] - Additional JS modification to the `tr`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.88.180
* @example
* // Assume all <tr> elements have a shared class (e.g. {'className': 'bar'})
* overlay.addTr({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body);
* // Output:
* // (Assume <body> already exists in the webpage)
* <body>
* <tr id="foo" class="bar">Foobar.</tr>
* </body>
*/
addTr(additionalProperties = {}, callback = () => {
}) {
const properties = {};
const tr = __privateMethod(this, _Overlay_instances, createElement_fn).call(this, "tr", properties, additionalProperties);
callback(this, tr);
return this;
}
/** Adds a table header (label) cell to the overlay.
* This `th` element will have properties shared between all `th` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.<string, any>} [additionalProperties={}] - The DOM properties of the `th` that are NOT shared between all overlay `th` elements. These should be camelCase.
* @param {function(Overlay, HTMLTableCellElement):void} [callback=()=>{}] - Additional JS modification to the `th`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.88.180
* @example
* // Assume all <th> elements have a shared class (e.g. {'className': 'bar'})
* overlay.addTh({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body);
* // Output:
* // (Assume <body> already exists in the webpage)
* <body>
* <th id="foo" class="bar">Foobar.</th>
* </body>
*/
addTh(additionalProperties = {}, callback = () => {
}) {
const properties = {};
const th = __privateMethod(this, _Overlay_instances, createElement_fn).call(this, "th", properties, additionalProperties);
callback(this, th);
return this;
}
/** Adds a table data cell to the overlay.
* This `td` element will have properties shared between all `td` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.<string, any>} [additionalProperties={}] - The DOM properties of the `td` that are NOT shared between all overlay `td` elements. These should be camelCase.
* @param {function(Overlay, HTMLTableCellElement):void} [callback=()=>{}] - Additional JS modification to the `td`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.88.180
* @example
* // Assume all <td> elements have a shared class (e.g. {'className': 'bar'})
* overlay.addTd({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body);
* // Output:
* // (Assume <body> already exists in the webpage)
* <body>
* <td id="foo" class="bar">Foobar.</td>
* </body>
*/
addTd(additionalProperties = {}, callback = () => {
}) {
const properties = {};
const td = __privateMethod(this, _Overlay_instances, createElement_fn).call(this, "td", properties, additionalProperties);
callback(this, td);
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.
@ -502,7 +801,7 @@
* This dragbar element will have properties shared between all dragbar elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.<string, any>} [additionalProperties={}] - The DOM properties of the dragbar that are NOT shared between all overlay dragbars. These should be camelCase.
* @param {function(Overlay, HTMLTextAreaElement):void} [callback=()=>{}] - Additional JS modification to the dragbar.
* @param {function(Overlay, HTMLDivElement):void} [callback=()=>{}] - Additional JS modification to the dragbar.
* @returns {Overlay} Overlay class instance (this)
* @since 0.88.145
* @example
@ -872,6 +1171,13 @@
}
return array;
}
function calculateRelativeLuminance(array) {
const srgb = array.map((channel) => {
channel /= 255;
return channel <= 0.03928 ? channel / 12.92 : Math.pow((channel + 0.055) / 1.055, 2.4);
});
return 0.2126 * srgb[0] + 0.7152 * srgb[1] + 0.0722 * srgb[2];
}
function colorpaletteForBlueMarble(tolerance) {
const colorpaletteBM = colorpalette;
colorpaletteBM.unshift({ "id": -1, "premium": false, "name": "Erased", "rgb": [222, 250, 206] });
@ -1805,7 +2111,7 @@ Time Since Blink: ${String(Math.floor(elapsed / 6e4)).padStart(2, "0")}:${String
button.onclick = () => instance.handleMinimization(button);
button.ontouchend = () => instance.handleMinimization(button);
}).buildElement().addDiv().buildElement().buildElement().addDiv({ "class": "bm-window-content" }).addDiv({ "class": "bm-container" }).addImg({ "class": "bm-favicon", "src": "https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/assets/Favicon.png" }).buildElement().addHeader(1, { "textContent": name }).buildElement().buildElement().addHr().buildElement().addDiv({ "class": "bm-container" }).addP({ "id": "bm-user-droplets", "textContent": "Droplets:" }).buildElement().addP({ "id": "bm-user-nextlevel", "textContent": "Next level in..." }).buildElement().buildElement().addHr().buildElement().addDiv({ "class": "bm-container" }).addDiv({ "class": "bm-container" }).addButton(
{ "class": "bm-button-circle bm-button-pin", "style": "margin-top: 0;", "innerHTML": '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 4 6"><circle cx="2" cy="2" r="2"></circle><path d="M2 6 L3.7 3 L0.3 3 Z"></path><circle cx="2" cy="2" r="0.7" fill="white"></circle></svg></svg>' },
{ "class": "bm-button-circle bm-button-pin", "style": "margin-top: 0;", "innerHTML": '<svg viewBox="0 0 4 6"><path d="M.5,3.4A2,2 0 1 1 3.5,3.4L2,6"/><circle cx="2" cy="2" r=".7" fill="#fff"/></svg>' },
(instance, button) => {
button.onclick = () => {
const coords2 = instance.apiManager?.coordsTilePixel;
@ -1944,15 +2250,44 @@ Version: ${version}`, "readOnly": true }).buildElement().buildElement().addDiv({
}).buildElement().addButton({ "textContent": "Unselect All" }, (instance, button) => {
button.onclick = () => {
};
}).buildElement().buildElement().addDiv().buildElement().buildElement().buildElement().buildOverlay(document.body);
}).buildElement().buildElement().buildElement().buildElement().buildOverlay(document.body);
overlayFilter.handleDrag("#bm-window-filter.bm-window", "#bm-window-filter .bm-dragbar");
const windowContent = document.querySelector("#bm-window-filter .bm-window-content");
const { palette, LUT: _ } = templateManager.paletteBM;
let allPixelsTotal = 0;
let allPixelsCorrectTotal = 0;
const allPixelsCorrect = /* @__PURE__ */ new Map();
const allPixelsColor = /* @__PURE__ */ new Map();
for (const template of templateManager.templatesArray) {
const total = template.pixelCount?.total ?? 0;
const colors = template.pixelCount?.colors ?? /* @__PURE__ */ new Map();
const correct = template.pixelCount?.correct ?? /* @__PURE__ */ new Map();
allPixelsTotal += total ?? 0;
for (const [colorID, correctPixels] of correct) {
const _correctPixels = Number(correctPixels) || 0;
allPixelsCorrectTotal += _correctPixels;
const allPixelsCorrectSoFar = allPixelsCorrect.get(colorID) ?? 0;
allPixelsCorrect.set(colorID, allPixelsCorrectSoFar + _correctPixels);
}
for (const [colorID, colorPixels] of colors) {
const _colorPixels = Number(colorPixels) || 0;
const allPixelsColorSoFar = allPixelsColor.get(colorID) ?? 0;
allPixelsColor.set(colorID, allPixelsColorSoFar + _colorPixels);
}
}
const colorList = new Overlay(name, version);
colorList.addDiv({ "id": "bm-filter-container-colors", "class": "bm-container" });
colorList.addDiv({ "class": "bm-container" }).addTable({ "class": "bm-container" }).addCaption().addHeader(2, { "textContent": "Pixels In Templates By Palette Color" }).buildElement().buildElement().addTfoot().addTr().addTh({ "textContent": "Total Correct", "scope": "row" }).buildElement().addTd({ "textContent": allPixelsCorrectTotal.toString() }).buildElement().buildElement().addTr().addTh({ "textContent": "Total Pixels", "scope": "row" }).buildElement().addTd({ "textContent": allPixelsTotal.toString() }).buildElement().buildElement().buildElement().addThead({ "class": "bm-screenreader" }).addTr().addTh({ "textContent": "Hide Color", "scope": "col" }).buildElement().addTh({ "textContent": "ID", "scope": "col" }).buildElement().addTh({ "textContent": "Is Premium", "scope": "col" }).buildElement().addTh({ "textContent": "Name", "scope": "col" }).buildElement().addTh({ "textContent": "Correct Pixels", "scope": "col" }).buildElement().addTh({ "textContent": "Total Pixels", "scope": "col" }).buildElement().buildElement().buildElement();
for (const color of palette) {
colorList.addDiv({ "class": "bm-container" }).addP({ "textContent": `Color ID: ${color.id?.toString()?.padStart(2, "0")}, Name: ${color.name}` }).buildElement().buildElement();
const lumin = calculateRelativeLuminance(color.rgb);
const textColorForPaletteColorBackground = 1.05 / (lumin + 0.05) > (lumin + 0.05) / 0.05 ? "white" : "black";
const bgEffectForButtons = textColorForPaletteColorBackground == "white" ? "bm-button-hover-white" : "bm-button-hover-black";
colorList.addTr().addTd().addDiv({ "class": "bm-filter-tbl-clr", "style": `background-color: rgb(${color.rgb?.map((channel) => Number(channel) || 0).join(",")});` }).addButton({ "class": "bm-button-trans " + bgEffectForButtons, "aria-label": `Hide the color ${color.name || "color"} on templates`, "innerHTML": `<svg viewBox="0 .5 6 3"><path d="M0,2Q3-1 6,2Q3,5 0,2H2A1,1 0 1 0 3,1Q3,2 2,2" fill="${textColorForPaletteColorBackground}"/></svg>` }).buildElement().buildElement().buildElement().addTd().addSpan({ "class": "bm-filter-tbl-id", "textContent": `#${color.id}` }).buildElement().buildElement().addTd().addSpan({ "class": "bm-filter-tbl-prmim", "textContent": color.premium ? "\u2605" : "" }).buildElement().buildElement().addTd().addSpan({ "class": "bm-filter-tbl-name", "textContent": color.name }).buildElement().buildElement().addTd().addSpan({ "class": "bm-filter-tbl-crct", "textContent": middleEllipsis(String(allPixelsCorrect.get(color.id) ?? "???"), 7) }).buildElement().buildElement().addTd().addSpan({ "class": "bm-filter-tbl-totl", "textContent": middleEllipsis(String(allPixelsColor.get(color.id) ?? "0"), 7) }).buildElement().buildElement().buildElement();
}
colorList.buildOverlay(windowContent);
function middleEllipsis(text, maxChars) {
if (text.length <= maxChars) return text;
const half = Math.floor((maxChars - 3) / 2);
return text.slice(0, half) + "\u2026" + text.slice(text.length - half);
}
}
})();

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
.bm-18{position:fixed;background-color:#153063e6;color:#fff;padding:10px;border-radius:8px;z-index:9000;transition:all .3s ease,transform 0s;top:75px;left:60px;width:auto;max-width:calc(100% - 135px);font-family:Roboto Mono,Courier New,Monaco,DejaVu Sans Mono,monospace,Arial;letter-spacing:.05em}#bm-Y{max-width:300px}.bm-16{display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:.5ch;background:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="5" height="5"><circle cx="3" cy="3" r="1.5" fill="CornflowerBlue" /></svg>') repeat;cursor:grab;width:100%;height:fit-content}.bm-16.bm-11{cursor:grabbing}.bm-18:has(.bm-16.bm-11){pointer-events:none;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}.bm-16.bm-11{pointer-events:auto}.bm-17{display:inline-block;height:2.5em;margin-right:1ch;vertical-align:middle}.bm-18 h1{display:inline-block;font-size:x-large;font-weight:700;vertical-align:middle}.bm-16 h1{font-size:1.2em;user-select:none;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;text-shadow:3px 0px rgba(21,48,99,.5),-3px 0px rgba(21,48,99,.5),0px 3px rgba(21,48,99,.5),0px -3px rgba(21,48,99,.5),3px 3px rgba(21,48,99,.5),-3px 3px rgba(21,48,99,.5),3px -3px rgba(21,48,99,.5),-3px -3px rgba(21,48,99,.5)}.bm-16 div:has(h1){display:contents}.bm-10{margin:.5em 0}.bm-18 button{background-color:#144eb9;border-radius:1em;padding:0 .75ch}.bm-18 button:hover,.bm-18 button:focus-visible{background-color:#1061e5}.bm-18 button:active,.bm-18 button:disabled{background-color:#2e97ff}.bm-18 button:disabled{text-decoration:line-through}.bm-U{border:white 1px solid;height:1.5em;width:1.5em;margin-top:2px;text-align:center;line-height:1em;padding:0!important}.bm--{vertical-align:middle}.bm-- svg{width:50%;margin:0 auto;fill:#111}input[type=number].bm-W{appearance:auto;-moz-appearance:textfield;width:5.5ch;margin-left:1ch;background-color:#0003;padding:0 .5ch;font-size:small}input[type=number].bm-W::-webkit-outer-spin-button,input[type=number].bm-W::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}div:has(>.bm-_)>button{width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.bm-_,input[type=file]{display:none!important;visibility:hidden!important;position:absolute!important;left:-9999px!important;top:-9999px!important;width:0!important;height:0!important;opacity:0!important;z-index:-9999!important;pointer-events:none!important}.bm-P{overflow:hidden;transition:height .3s cubic-bezier(.4,0,.2,1)}.bm-18 textarea{font-size:small;background-color:#0003;padding:0 .5ch;height:5.25em;width:100%}.bm-18 small{font-size:x-small;color:#d3d3d3}.bm-X{display:flex;align-content:center;justify-content:space-between;align-items:center;gap:.5ch}.bm-flex-center{display:flex;align-content:center;justify-content:center;align-items:center;gap:.5ch}
.bm-13{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.bm-1h{position:fixed;background-color:#153063e6;color:#fff;padding:10px;border-radius:8px;z-index:9000;transition:all .3s ease,transform 0s;top:75px;left:60px;width:auto;max-height:calc(100vh - 150px);max-width:calc(100% - 135px);font-family:Roboto Mono,Courier New,Monaco,DejaVu Sans Mono,monospace,Arial;letter-spacing:.05em}#bm-15{max-width:300px}.bm-1f{display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:.5ch;background:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="5" height="5"><circle cx="3" cy="3" r="1.5" fill="CornflowerBlue" /></svg>') repeat;cursor:grab;width:100%;height:fit-content}.bm-1f.bm-1a{cursor:grabbing}.bm-1h:has(.bm-1f.bm-1a){pointer-events:none;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}.bm-1f.bm-1a{pointer-events:auto}.bm-1g{display:inline-block;height:2.5em;margin-right:1ch;vertical-align:middle}.bm-1h h1{display:inline-block;font-size:x-large;font-weight:700;vertical-align:middle}.bm-1f h1{font-size:1.2em;user-select:none;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;text-shadow:3px 0px rgba(21,48,99,.5),-3px 0px rgba(21,48,99,.5),0px 3px rgba(21,48,99,.5),0px -3px rgba(21,48,99,.5),3px 3px rgba(21,48,99,.5),-3px 3px rgba(21,48,99,.5),3px -3px rgba(21,48,99,.5),-3px -3px rgba(21,48,99,.5)}.bm-1f div:has(h1){display:contents}.bm-19{margin:.5em 0}.bm-1h button{background-color:#144eb9;border-radius:1em;padding:0 .75ch}.bm-1h button:hover,.bm-1h button:focus-visible{background-color:#1061e5}.bm-1h button:active,.bm-1h button:disabled{background-color:#2e97ff}.bm-1h button:disabled{text-decoration:line-through}.bm--{border:white 1px solid;height:1.5em;width:1.5em;margin-top:2px;text-align:center;line-height:1em;padding:0!important}.bm-17{vertical-align:middle}.bm-17 svg{width:50%;margin:0 auto;fill:#111}.bm-1h button.bm-14{background-color:unset}.bm-14.bm-M:hover,.bm-14.bm-M:focus{background-color:#ffffff2b}.bm-14.bm-M:active{background-color:#ffffff38}.bm-14.bm-N:hover,.bm-14.bm-N:focus{background-color:#0000002b}.bm-14.bm-N:active{background-color:#00000038}input[type=number].bm-11{appearance:auto;-moz-appearance:textfield;width:5.5ch;margin-left:1ch;background-color:#0003;padding:0 .5ch;font-size:small}input[type=number].bm-11::-webkit-outer-spin-button,input[type=number].bm-11::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}div:has(>.bm-18)>button{width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.bm-18,input[type=file]{display:none!important;visibility:hidden!important;position:absolute!important;left:-9999px!important;top:-9999px!important;width:0!important;height:0!important;opacity:0!important;z-index:-9999!important;pointer-events:none!important}.bm-U{overflow:hidden;transition:height .3s cubic-bezier(.4,0,.2,1)}.bm-1h textarea{font-size:small;background-color:#0003;padding:0 .5ch;height:5.25em;width:100%}.bm-1h small{font-size:x-small;color:#d3d3d3}.bm-12{display:flex;align-content:center;justify-content:space-between;align-items:center;gap:.5ch}.bm-flex-center{display:flex;align-content:center;justify-content:center;align-items:center;gap:.5ch}#bm-_ .bm-19:has(table){max-height:60vh;overflow:auto}#bm-_ table{table-layout:fixed;width:50ch;border-collapse:separate;border-spacing:0 .5em}#bm-_ tr{padding:.5em 0}.bm-X{display:block;border:thick double lightgray;width:fit-content;height:fit-content;padding:1ch}.bm-X button{padding:.75em .5ch}.bm-X svg{width:4ch;isolation:isolate}.bm-10{position:relative;top:.75em;left:.5ch}.bm-Q{position:relative;top:.75em;left:-3.5ch}.bm-R{position:relative;top:-.75em;left:-16ch;text-wrap:nowrap}.bm-S{display:block;width:100%;position:relative;right:18ch;top:.75em;text-align:right}.bm-T{display:block;width:100%;position:relative;left:-16ch;top:.75em;text-align:left}.bm-T:after{content:"/";position:absolute;left:-1.5ch;top:50%;transform:translateY(-50%)}#bm-_ tfoot{display:table-header-group}#bm-_ tfoot th{text-align:left}#bm-_ tfoot td{text-align:right;padding-left:1ch}

File diff suppressed because one or more lines are too long

View file

@ -51,7 +51,7 @@
<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="https://bluemarble.lol/" target="_blank" rel="noopener noreferrer"><img alt="Blue Marble Website" src="https://img.shields.io/badge/Blue_Marble_Website-crqch-blue?style=flat&logo=globe&logoColor=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-677-black?style=flat"></a>
<a href="" target="_blank" rel="noopener noreferrer"><img alt="Total Patches" src="https://img.shields.io/badge/Total_Patches-705-black?style=flat"></a>
<a href="" target="_blank" rel="noopener noreferrer"><img alt="Total Lines of Code" src="https://img.shields.io/badge/Lines_Of_Code-498-blue?style=flat"></a>
<a href="" target="_blank" rel="noopener noreferrer"><img alt="Total Comments" src="https://img.shields.io/badge/Lines_Of_Comments-498-blue?style=flat"></a>
<a href="" target="_blank" rel="noopener noreferrer"><img alt="Compression" src="https://img.shields.io/badge/Compression-70.19%25-blue"></a>

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "wplace-bluemarble",
"version": "0.88.179",
"version": "0.88.207",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "wplace-bluemarble",
"version": "0.88.179",
"version": "0.88.207",
"devDependencies": {
"esbuild": "^0.25.0",
"jsdoc": "^4.0.5",

View file

@ -1,6 +1,6 @@
{
"name": "wplace-bluemarble",
"version": "0.88.179",
"version": "0.88.207",
"type": "module",
"homepage": "https://bluemarble.lol/",
"repository": {

View file

@ -2,7 +2,7 @@
// @name Blue Marble
// @name:en Blue Marble
// @namespace https://github.com/SwingTheVine/
// @version 0.88.179
// @version 0.88.207
// @description A userscript to automate and/or enhance the user experience on Wplace.live. Make sure to comply with the site's Terms of Service, and rules! This script is not affiliated with Wplace.live in any way, use at your own risk. This script is not affiliated with TamperMonkey. The author of this userscript is not responsible for any damages, issues, loss of data, or punishment that may occur as a result of using this script. This script is provided "as is" under the MPL-2.0 license. The "Blue Marble" icon is licensed under CC0 1.0 Universal (CC0 1.0) Public Domain Dedication. The image is owned by NASA.
// @description:en A userscript to automate and/or enhance the user experience on Wplace.live. Make sure to comply with the site's Terms of Service, and rules! This script is not affiliated with Wplace.live in any way, use at your own risk. This script is not affiliated with TamperMonkey. The author of this userscript is not responsible for any damages, issues, loss of data, or punishment that may occur as a result of using this script. This script is provided "as is" under the MPL-2.0 license. The "Blue Marble" icon is licensed under CC0 1.0 Universal (CC0 1.0) Public Domain Dedication. The image is owned by NASA.
// @author SwingTheVine

View file

@ -208,12 +208,12 @@ export default class Overlay {
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.<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`.
* @param {function(Overlay, HTMLElement):void} [callback=()=>{}] - Additional JS modification to the `small`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.55.8
* @example
@ -234,11 +234,36 @@ export default class Overlay {
return this;
}
/** Adds a `span` to the overlay.
* This `span` element will have properties shared between all `span` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.<string, any>} [additionalProperties={}] - The DOM properties of the `span` that are NOT shared between all overlay `span` elements. These should be camelCase.
* @param {function(Overlay, HTMLSpanElement):void} [callback=()=>{}] - Additional JS modification to the `span`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.55.8
* @example
* // Assume all <span> elements have a shared class (e.g. {'className': 'bar'})
* overlay.addSpan({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body);
* // Output:
* // (Assume <body> already exists in the webpage)
* <body>
* <span id="foo" class="bar">Foobar.</span>
* </body>
*/
addSpan(additionalProperties = {}, callback = () => {}) {
const properties = {}; // Shared <span> DOM properties
const span = this.#createElement('span', properties, additionalProperties); // Creates the <span> element
callback(this, span); // Runs any script passed in through the callback
return this;
}
/** Adds a `details` to the overlay.
* This `details` element will have properties shared between all `details` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.<string, any>} [additionalProperties={}] - The DOM properties of the `details` that are NOT shared between all overlay `details` elements. These should be camelCase.
* @param {function(Overlay, HTMLParagraphElement):void} [callback=()=>{}] - Additional JS modification to the `details`.
* @param {function(Overlay, HTMLDetailsElement):void} [callback=()=>{}] - Additional JS modification to the `details`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.88.96
* @example
@ -263,7 +288,7 @@ export default class Overlay {
* This `summary` element will have properties shared between all `summary` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.<string, any>} [additionalProperties={}] - The DOM properties of the `summary` that are NOT shared between all overlay `summary` elements. These should be camelCase.
* @param {function(Overlay, HTMLParagraphElement):void} [callback=()=>{}] - Additional JS modification to the `summary`.
* @param {function(Overlay, HTMLElement):void} [callback=()=>{}] - Additional JS modification to the `summary`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.88.96
* @example
@ -416,6 +441,306 @@ export default class Overlay {
callback(this, label, checkbox); // Runs any script passed in through the callback
return this;
}
/** Adds an ordered list to the overlay.
* This `ol` element will have properties shared between all `ol` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.<string, any>} [additionalProperties={}] - The DOM properties of the `ol` that are NOT shared between all overlay `ol` elements. These should be camelCase.
* @param {function(Overlay, HTMLOListElement):void} [callback=()=>{}] - Additional JS modification to the `ol`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.88.180
* @example
* // Assume all <ol> elements have a shared class (e.g. {'className': 'bar'})
* overlay.addOl({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body);
* // Output:
* // (Assume <body> already exists in the webpage)
* <body>
* <ol id="foo" class="bar">Foobar.</ol>
* </body>
*/
addOl(additionalProperties = {}, callback = () => {}) {
const properties = {}; // Shared <ol> DOM properties
const ol = this.#createElement('ol', properties, additionalProperties); // Creates the <ol> element
callback(this, ol); // Runs any script passed in through the callback
return this;
}
/** Adds an unordered list to the overlay.
* This `ul` element will have properties shared between all `ul` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.<string, any>} [additionalProperties={}] - The DOM properties of the `ul` that are NOT shared between all overlay `ul` elements. These should be camelCase.
* @param {function(Overlay, HTMLUListElement):void} [callback=()=>{}] - Additional JS modification to the `ul`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.88.180
* @example
* // Assume all <ul> elements have a shared class (e.g. {'className': 'bar'})
* overlay.addUl({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body);
* // Output:
* // (Assume <body> already exists in the webpage)
* <body>
* <ul id="foo" class="bar">Foobar.</ul>
* </body>
*/
addUl(additionalProperties = {}, callback = () => {}) {
const properties = {}; // Shared <ul> DOM properties
const ul = this.#createElement('ul', properties, additionalProperties); // Creates the <ul> element
callback(this, ul); // Runs any script passed in through the callback
return this;
}
/** Adds a `menu` to the overlay.
* This `menu` element will have properties shared between all `menu` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.<string, any>} [additionalProperties={}] - The DOM properties of the `menu` that are NOT shared between all overlay `menu` elements. These should be camelCase.
* @param {function(Overlay, HTMLMenuElement):void} [callback=()=>{}] - Additional JS modification to the `menu`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.88.180
* @example
* // Assume all <menu> elements have a shared class (e.g. {'className': 'bar'})
* overlay.addMenu({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body);
* // Output:
* // (Assume <body> already exists in the webpage)
* <body>
* <menu id="foo" class="bar">Foobar.</menu>
* </body>
*/
addMenu(additionalProperties = {}, callback = () => {}) {
const properties = {}; // Shared <menu> DOM properties
const menu = this.#createElement('menu', properties, additionalProperties); // Creates the <menu> element
callback(this, menu); // Runs any script passed in through the callback
return this;
}
/** Adds a list item to the overlay.
* This `li` element will have properties shared between all `li` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.<string, any>} [additionalProperties={}] - The DOM properties of the `li` that are NOT shared between all overlay `li` elements. These should be camelCase.
* @param {function(Overlay, HTMLLIElement):void} [callback=()=>{}] - Additional JS modification to the `li`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.88.180
* @example
* // Assume all <li> elements have a shared class (e.g. {'className': 'bar'})
* overlay.addLi({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body);
* // Output:
* // (Assume <body> already exists in the webpage)
* <body>
* <li id="foo" class="bar">Foobar.</li>
* </body>
*/
addLi(additionalProperties = {}, callback = () => {}) {
const properties = {}; // Shared <li> DOM properties
const li = this.#createElement('li', properties, additionalProperties); // Creates the <li> element
callback(this, li); // Runs any script passed in through the callback
return this;
}
/** Adds a table to the overlay.
* This `table` element will have properties shared between all `table` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.<string, any>} [additionalProperties={}] - The DOM properties of the `table` that are NOT shared between all overlay `table` elements. These should be camelCase.
* @param {function(Overlay, HTMLTableElement):void} [callback=()=>{}] - Additional JS modification to the `table`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.88.180
* @example
* // Assume all <table> elements have a shared class (e.g. {'className': 'bar'})
* overlay.addTable({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body);
* // Output:
* // (Assume <body> already exists in the webpage)
* <body>
* <table id="foo" class="bar">Foobar.</table>
* </body>
*/
addTable(additionalProperties = {}, callback = () => {}) {
const properties = {}; // Shared <table> DOM properties
const table = this.#createElement('table', properties, additionalProperties); // Creates the <table> element
callback(this, table); // Runs any script passed in through the callback
return this;
}
/** Adds a table caption to the overlay.
* This `caption` element will have properties shared between all `caption` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.<string, any>} [additionalProperties={}] - The DOM properties of the `caption` that are NOT shared between all overlay `caption` elements. These should be camelCase.
* @param {function(Overlay, HTMLTableCaptionElement):void} [callback=()=>{}] - Additional JS modification to the `caption`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.88.180
* @example
* // Assume all <caption> elements have a shared class (e.g. {'className': 'bar'})
* overlay.addCaption({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body);
* // Output:
* // (Assume <body> already exists in the webpage)
* <body>
* <caption id="foo" class="bar">Foobar.</caption>
* </body>
*/
addCaption(additionalProperties = {}, callback = () => {}) {
const properties = {}; // Shared <caption> DOM properties
const caption = this.#createElement('caption', properties, additionalProperties); // Creates the <caption> element
callback(this, caption); // Runs any script passed in through the callback
return this;
}
/** Adds a table header to the overlay.
* This `thead` element will have properties shared between all `thead` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.<string, any>} [additionalProperties={}] - The DOM properties of the `thead` that are NOT shared between all overlay `thead` elements. These should be camelCase.
* @param {function(Overlay, HTMLTableSectionElement):void} [callback=()=>{}] - Additional JS modification to the `thead`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.88.180
* @example
* // Assume all <thead> elements have a shared class (e.g. {'className': 'bar'})
* overlay.addThead({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body);
* // Output:
* // (Assume <body> already exists in the webpage)
* <body>
* <thead id="foo" class="bar">Foobar.</thead>
* </body>
*/
addThead(additionalProperties = {}, callback = () => {}) {
const properties = {}; // Shared <thead> DOM properties
const thead = this.#createElement('thead', properties, additionalProperties); // Creates the <thead> element
callback(this, thead); // Runs any script passed in through the callback
return this;
}
/** Adds a table body to the overlay.
* This `tbody` element will have properties shared between all `tbody` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.<string, any>} [additionalProperties={}] - The DOM properties of the `tbody` that are NOT shared between all overlay `tbody` elements. These should be camelCase.
* @param {function(Overlay, HTMLTableSectionElement):void} [callback=()=>{}] - Additional JS modification to the `tbody`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.88.180
* @example
* // Assume all <tbody> elements have a shared class (e.g. {'className': 'bar'})
* overlay.addTbody({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body);
* // Output:
* // (Assume <body> already exists in the webpage)
* <body>
* <tbody id="foo" class="bar">Foobar.</tbody>
* </body>
*/
addTbody(additionalProperties = {}, callback = () => {}) {
const properties = {}; // Shared <tbody> DOM properties
const tbody = this.#createElement('tbody', properties, additionalProperties); // Creates the <tbody> element
callback(this, tbody); // Runs any script passed in through the callback
return this;
}
/** Adds a table footer to the overlay.
* This `tfoot` element will have properties shared between all `tfoot` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.<string, any>} [additionalProperties={}] - The DOM properties of the `tfoot` that are NOT shared between all overlay `tfoot` elements. These should be camelCase.
* @param {function(Overlay, HTMLTableSectionElement):void} [callback=()=>{}] - Additional JS modification to the `tfoot`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.88.180
* @example
* // Assume all <tfoot> elements have a shared class (e.g. {'className': 'bar'})
* overlay.addTfoot({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body);
* // Output:
* // (Assume <body> already exists in the webpage)
* <body>
* <tfoot id="foo" class="bar">Foobar.</tfoot>
* </body>
*/
addTfoot(additionalProperties = {}, callback = () => {}) {
const properties = {}; // Shared <tfoot> DOM properties
const tfoot = this.#createElement('tfoot', properties, additionalProperties); // Creates the <tfoot> element
callback(this, tfoot); // Runs any script passed in through the callback
return this;
}
/** Adds a table row to the overlay.
* This `tr` element will have properties shared between all `tr` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.<string, any>} [additionalProperties={}] - The DOM properties of the `tr` that are NOT shared between all overlay `tr` elements. These should be camelCase.
* @param {function(Overlay, HTMLTableRowElement):void} [callback=()=>{}] - Additional JS modification to the `tr`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.88.180
* @example
* // Assume all <tr> elements have a shared class (e.g. {'className': 'bar'})
* overlay.addTr({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body);
* // Output:
* // (Assume <body> already exists in the webpage)
* <body>
* <tr id="foo" class="bar">Foobar.</tr>
* </body>
*/
addTr(additionalProperties = {}, callback = () => {}) {
const properties = {}; // Shared <tfoot> DOM properties
const tr = this.#createElement('tr', properties, additionalProperties); // Creates the <tr> element
callback(this, tr); // Runs any script passed in through the callback
return this;
}
/** Adds a table header (label) cell to the overlay.
* This `th` element will have properties shared between all `th` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.<string, any>} [additionalProperties={}] - The DOM properties of the `th` that are NOT shared between all overlay `th` elements. These should be camelCase.
* @param {function(Overlay, HTMLTableCellElement):void} [callback=()=>{}] - Additional JS modification to the `th`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.88.180
* @example
* // Assume all <th> elements have a shared class (e.g. {'className': 'bar'})
* overlay.addTh({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body);
* // Output:
* // (Assume <body> already exists in the webpage)
* <body>
* <th id="foo" class="bar">Foobar.</th>
* </body>
*/
addTh(additionalProperties = {}, callback = () => {}) {
const properties = {}; // Shared <th> DOM properties
const th = this.#createElement('th', properties, additionalProperties); // Creates the <th> element
callback(this, th); // Runs any script passed in through the callback
return this;
}
/** Adds a table data cell to the overlay.
* This `td` element will have properties shared between all `td` elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.<string, any>} [additionalProperties={}] - The DOM properties of the `td` that are NOT shared between all overlay `td` elements. These should be camelCase.
* @param {function(Overlay, HTMLTableCellElement):void} [callback=()=>{}] - Additional JS modification to the `td`.
* @returns {Overlay} Overlay class instance (this)
* @since 0.88.180
* @example
* // Assume all <td> elements have a shared class (e.g. {'className': 'bar'})
* overlay.addTd({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body);
* // Output:
* // (Assume <body> already exists in the webpage)
* <body>
* <td id="foo" class="bar">Foobar.</td>
* </body>
*/
addTd(additionalProperties = {}, callback = () => {}) {
const properties = {}; // Shared <td> DOM properties
const td = this.#createElement('td', properties, additionalProperties); // Creates the <td> element
callback(this, td); // 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.
@ -604,7 +929,7 @@ export default class Overlay {
* This dragbar element will have properties shared between all dragbar elements in the overlay.
* You can override the shared properties by using a callback.
* @param {Object.<string, any>} [additionalProperties={}] - The DOM properties of the dragbar that are NOT shared between all overlay dragbars. These should be camelCase.
* @param {function(Overlay, HTMLTextAreaElement):void} [callback=()=>{}] - Additional JS modification to the dragbar.
* @param {function(Overlay, HTMLDivElement):void} [callback=()=>{}] - Additional JS modification to the dragbar.
* @returns {Overlay} Overlay class instance (this)
* @since 0.88.145
* @example

View file

@ -41,7 +41,8 @@ export default class Template {
this.chunked = chunked;
this.chunked32 = chunked32;
this.tileSize = tileSize;
this.pixelCount = { total: 0, colors: new Map() }; // Total pixel count in template
/** Total pixel count in template @type {{total: number, colors: Map<number, number>, correct?: Map<number, number>}} */
this.pixelCount = { total: 0, colors: new Map() };
}
/** Creates chunks of the template for each tile.

View file

@ -492,7 +492,6 @@ function buildWindowFilter() {
}
}).buildElement()
.buildElement()
.addDiv().buildElement()
.buildElement()
.buildElement().buildOverlay(document.body);
@ -505,9 +504,67 @@ function buildWindowFilter() {
// Obtains the palette Blue Marble currently uses
const { palette: palette, LUT: _ } = templateManager.paletteBM;
// Pixel totals
let allPixelsTotal = 0;
let allPixelsCorrectTotal = 0;
const allPixelsCorrect = new Map();
const allPixelsColor = new Map();
// Sum the pixel totals across all templates.
// If there is no total for a template, it defaults to zero
for (const template of templateManager.templatesArray) {
const total = template.pixelCount?.total ?? 0;
const colors = template.pixelCount?.colors ?? new Map();
const correct = template.pixelCount?.correct ?? new Map();
allPixelsTotal += total ?? 0; // Sums the pixels placed as "total" per everything
// Sums the pixels placed as "correct" per color ID
for (const [colorID, correctPixels] of correct) {
const _correctPixels = Number(correctPixels) || 0; // Boilerplate
allPixelsCorrectTotal += _correctPixels; // Sums the pixels placed as "correct" per everything
const allPixelsCorrectSoFar = allPixelsCorrect.get(colorID) ?? 0; // The total correct pixels for this color ID so far, or zero if none counted so far
allPixelsCorrect.set(colorID, allPixelsCorrectSoFar + _correctPixels);
}
// Sums the color pixels placed as "total" per color ID
for (const [colorID, colorPixels] of colors) {
const _colorPixels = Number(colorPixels) || 0; // Boilerplate
const allPixelsColorSoFar = allPixelsColor.get(colorID) ?? 0; // The total color pixels for this color ID so far, or zero if none counted so far
allPixelsColor.set(colorID, allPixelsColorSoFar + _colorPixels);
}
}
// Creates the color list container
const colorList = new Overlay(name, version);
colorList.addDiv({'id': 'bm-filter-container-colors', 'class': 'bm-container'})
colorList.addDiv({'class': 'bm-container'})
.addTable({'class': 'bm-container'})
.addCaption()
.addHeader(2, {'textContent': 'Pixels In Templates By Palette Color'}).buildElement()
.buildElement()
.addTfoot()
.addTr()
.addTh({'textContent': 'Total Correct', 'scope': 'row'}).buildElement()
.addTd({'textContent': allPixelsCorrectTotal.toString()}).buildElement()
.buildElement()
.addTr()
.addTh({'textContent': 'Total Pixels', 'scope': 'row'}).buildElement()
.addTd({'textContent': allPixelsTotal.toString()}).buildElement()
.buildElement()
.buildElement()
.addThead({'class': 'bm-screenreader'})
.addTr()
.addTh({'textContent': 'Hide Color', 'scope': 'col'}).buildElement()
.addTh({'textContent': 'ID', 'scope': 'col'}).buildElement()
.addTh({'textContent': 'Is Premium', 'scope': 'col'}).buildElement()
.addTh({'textContent': 'Name', 'scope': 'col'}).buildElement()
.addTh({'textContent': 'Correct Pixels', 'scope': 'col'}).buildElement()
.addTh({'textContent': 'Total Pixels', 'scope': 'col'}).buildElement()
.buildElement()
.buildElement()
// Notice that there is no buildElement() for the table here?
// We leave the table open so we can continue to add children.
// For each color in the palette...
for (const color of palette) {
@ -520,19 +577,49 @@ function buildWindowFilter() {
(((1.05) / (lumin + 0.05)) > ((lumin + 0.05) / 0.05))
? 'white' : 'black';
const bgEffectForButtons = (textColorForPaletteColorBackground == 'white') ? 'bm-button-hover-white' : 'bm-button-hover-black';
// <svg viewBox="0 1 12 6"><mask id="a"><path d="M0,0H12V8L0,2" fill="#fff"/></mask><path d="M0,4Q6-2 12,4Q6,10 0,4H4A2,2 0 1 0 6,2Q6,4 4,4ZM1,2L10,6.5L9.5,7L.5,2.5" mask="url(#a)"/></svg>
// Construct the DOM tree
colorList.addDiv({'class': 'bm-container flex-space-between'})
.addDiv({'class': '' + textColorForPaletteColorBackground, 'style': `border: thick double white`})
.addButton({'class': 'bm-button-trans', 'innerHTML': `<svg viewBox="0 .5 6 3"><path d="M0,2Q3-1 6,2Q3,5 0,2H2A1,1 0 1 0 3,1Q3,2 2,2"/></svg>`}).buildElement()
colorList.addTr()
.addTd()
.addDiv({'class': 'bm-filter-tbl-clr', 'style': `background-color: rgb(${color.rgb?.map(channel => Number(channel) || 0).join(',')});`})
.addButton({'class': 'bm-button-trans ' + bgEffectForButtons, 'aria-label': `Hide the color ${color.name || 'color'} on templates`, 'innerHTML': `<svg viewBox="0 .5 6 3"><path d="M0,2Q3-1 6,2Q3,5 0,2H2A1,1 0 1 0 3,1Q3,2 2,2" fill="${textColorForPaletteColorBackground}"/></svg>`}).buildElement()
.buildElement()
.buildElement()
.addTd()
.addSpan({'class': 'bm-filter-tbl-id', 'textContent': `#${color.id}`}).buildElement()
.buildElement()
.addTd()
.addSpan({'class': 'bm-filter-tbl-prmim', 'textContent': color.premium ? '★' : ''}).buildElement()
.buildElement()
.addTd()
.addSpan({'class': 'bm-filter-tbl-name', 'textContent': color.name}).buildElement()
.buildElement()
.addTd()
.addSpan({'class': 'bm-filter-tbl-crct', 'textContent': middleEllipsis(String(allPixelsCorrect.get(color.id) ?? '0'), 7)}).buildElement()
.buildElement()
.addTd()
.addSpan({'class': 'bm-filter-tbl-totl', 'textContent': middleEllipsis(String(allPixelsColor.get(color.id) ?? '0'), 7)}).buildElement()
.buildElement()
.addP({'textContent': `Color ID: ${color.id?.toString()?.padStart(2, '0')}, Name: ${color.name}`}).buildElement()
.buildElement()
}
// Adds the colors to the color container in the filter window
colorList.buildOverlay(windowContent);
// Overflow ellipse but the ellipse is in the middle, not end of the string
function middleEllipsis(text, maxChars) {
if (text.length <= maxChars) return text;
const half = Math.floor((maxChars - 3) / 2);
return (
text.slice(0, half) +
"…" +
text.slice(text.length - half)
);
}
}
function buildOverlayTabTemplate() {

View file

@ -1,5 +1,17 @@
/* @since 0.5.1 */
/* Content with this class is only available to screen readers */
.bm-screenreader {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
/* The Blue Marble window(s) */
.bm-window {
@ -13,6 +25,7 @@
top: 75px;
left: 60px;
width: auto;
max-height: calc(100vh - 150px);
max-width: calc(100% - 135px);
/* Font stack is as follows:
* Highest Priority (Roboto Mono)
@ -152,6 +165,33 @@
fill: #111;
}
/* Transparent buttons */
.bm-window button.bm-button-trans {
background-color: unset;
}
/* Transparent buttons on dark backgrounds when hovered */
.bm-button-trans.bm-button-hover-white:hover,
.bm-button-trans.bm-button-hover-white:focus {
background-color: rgba(255, 255, 255, 0.17);
}
/* Transparent buttons on dark backgrounds when pressed */
.bm-button-trans.bm-button-hover-white:active {
background-color: rgba(255, 255, 255, 0.22);
}
/* Transparent buttons on light backgrounds when hovered */
.bm-button-trans.bm-button-hover-black:hover,
.bm-button-trans.bm-button-hover-black:focus {
background-color: rgba(0, 0, 0, 0.17);
}
/* Transparent buttons on light backgrounds when pressed */
.bm-button-trans.bm-button-hover-black:active {
background-color: rgba(0, 0, 0, 0.22);
}
/* Tile (x, y) & Pixel (x, y) input fields */
input[type="number"].bm-input-coords {
appearance: auto;
@ -230,4 +270,109 @@ input[type="file"] {
justify-content: center;
align-items: center;
gap: 0.5ch;
}
}
/* Filter window table container */
#bm-window-filter .bm-container:has(table) {
max-height: 60vh;
overflow: auto;
}
/* Filter window table */
#bm-window-filter table {
table-layout: fixed;
width: 50ch;
border-collapse: separate;
border-spacing: 0 0.5em;
}
/* Filter window table row */
#bm-window-filter tr {
padding: 0.5em 0;
}
/* Filter window table cell for RGB color display */
.bm-filter-tbl-clr {
display: block;
border: thick double lightgray;
width: fit-content;
height: fit-content;
padding: 1ch;
}
/* Filter window hide color button */
.bm-filter-tbl-clr button {
padding: 0.75em 0.5ch;
}
/* Filter window hide color button SVG */
.bm-filter-tbl-clr svg {
width: 4ch;
isolation: isolate;
}
/* Filter window color ID */
.bm-filter-tbl-id {
position: relative;
top: 0.75em;
left: 0.5ch;
}
/* Filter window color premium */
.bm-filter-tbl-prmim {
position: relative;
top: 0.75em;
left: -3.5ch;
}
/* Filter window color name */
.bm-filter-tbl-name {
position: relative;
top: -0.75em;
left: -16ch;
text-wrap: nowrap;
}
/* Filter window color correct pixels */
.bm-filter-tbl-crct {
display: block;
width: 100%;
position: relative;
right: 18ch;
top: 0.75em;
text-align: right;
}
/* Filter window color total pixels */
.bm-filter-tbl-totl {
display: block;
width: 100%;
position: relative;
left: -16ch;
top: 0.75em;
text-align: left;
}
.bm-filter-tbl-totl::after {
content: "/";
position: absolute;
left: -1.5ch;
top: 50%;
transform: translateY(-50%);
}
/* Filter window table footer */
#bm-window-filter tfoot {
display: table-header-group;
}
/* Filter window table footer header */
#bm-window-filter tfoot th {
text-align: left;
}
/* Filter window table footer data */
#bm-window-filter tfoot td {
text-align: right;
padding-left: 1ch;
}

View file

@ -71,6 +71,7 @@ export default class TemplateManager {
// Template
this.template = null; // The template image.
this.templateState = ''; // The state of the template ('blob', 'proccessing', 'template', etc.)
/** @type {Array<Template>} An Array of Template classes */
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?