mirror of
https://github.com/SwingTheVine/Wplace-BlueMarble.git
synced 2026-05-18 06:51:48 +00:00
Release 0.92.1
This commit is contained in:
parent
ed7127d40b
commit
803fb97f97
15 changed files with 1196 additions and 249 deletions
|
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
// ES Module imports
|
// ES Module imports
|
||||||
import esbuild from 'esbuild';
|
import esbuild from 'esbuild';
|
||||||
|
import crypto from 'crypto';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { execSync } from 'child_process';
|
import { execSync } from 'child_process';
|
||||||
import { consoleStyle } from './utils.js';
|
import { consoleStyle } from './utils.js';
|
||||||
|
|
@ -22,6 +23,24 @@ const terser = require('terser');
|
||||||
|
|
||||||
const isGitHub = !!process.env?.GITHUB_ACTIONS; // Is this running in a GitHub Action Workflow?'
|
const isGitHub = !!process.env?.GITHUB_ACTIONS; // Is this running in a GitHub Action Workflow?'
|
||||||
|
|
||||||
|
/** Appends a build hash comment to an output file.
|
||||||
|
* The hash is based on the file contents before the hash comment is added.
|
||||||
|
* @param {string} path - Path to the file
|
||||||
|
* @param {'js' | 'css'} type - Output type for comment syntax
|
||||||
|
* @returns {string} The short build hash
|
||||||
|
* @since 0.92.0
|
||||||
|
*/
|
||||||
|
function appendBuildHashComment(path, type = 'js') {
|
||||||
|
const content = fs.readFileSync(path, 'utf8').trimEnd();
|
||||||
|
const hash = crypto.createHash('sha256').update(content, 'utf8').digest('hex').slice(0, 12);
|
||||||
|
const comment = (type == 'css')
|
||||||
|
? `/* Build Hash: ${hash} */`
|
||||||
|
: `// Build Hash: ${hash}`;
|
||||||
|
|
||||||
|
fs.writeFileSync(path, `${content}\n\n${comment}\n`, 'utf8');
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`${consoleStyle.BLUE}Starting build...${consoleStyle.RESET}`);
|
console.log(`${consoleStyle.BLUE}Starting build...${consoleStyle.RESET}`);
|
||||||
|
|
||||||
// Tries to build the wiki if build.js is run in a GitHub Workflow
|
// Tries to build the wiki if build.js is run in a GitHub Workflow
|
||||||
|
|
@ -214,4 +233,16 @@ esbuild.build({
|
||||||
|
|
||||||
fs.writeFileSync(`dist/${greasyForkName}.user.js`, greasyForkBMjs, 'utf-8');
|
fs.writeFileSync(`dist/${greasyForkName}.user.js`, greasyForkBMjs, 'utf-8');
|
||||||
|
|
||||||
|
const buildHashes = {
|
||||||
|
'BlueMarble.user.css': appendBuildHashComment('dist/BlueMarble.user.css', 'css'),
|
||||||
|
'BlueMarble.user.js': appendBuildHashComment('dist/BlueMarble.user.js', 'js'),
|
||||||
|
[`${standaloneName}.user.js`]: appendBuildHashComment(`dist/${standaloneName}.user.js`, 'js'),
|
||||||
|
[`${greasyForkName}.user.css`]: appendBuildHashComment(`dist/${greasyForkName}.user.css`, 'css'),
|
||||||
|
[`${greasyForkName}.user.js`]: appendBuildHashComment(`dist/${greasyForkName}.user.js`, 'js')
|
||||||
|
};
|
||||||
|
|
||||||
console.log(`${consoleStyle.GREEN + consoleStyle.BOLD + consoleStyle.UNDERLINE}Building complete!${consoleStyle.RESET}`);
|
console.log(`${consoleStyle.GREEN + consoleStyle.BOLD + consoleStyle.UNDERLINE}Building complete!${consoleStyle.RESET}`);
|
||||||
|
console.log(`Build hashes:`);
|
||||||
|
for (const [file, hash] of Object.entries(buildHashes)) {
|
||||||
|
console.log(`- ${file}: ${hash}`);
|
||||||
|
}
|
||||||
|
|
|
||||||
464
dist/BlueMarble-For-GreasyFork.user.css
vendored
464
dist/BlueMarble-For-GreasyFork.user.css
vendored
|
|
@ -1,192 +1,3 @@
|
||||||
/* src/WindowFilter.css */
|
|
||||||
#bm-window-filter p svg {
|
|
||||||
display: inline;
|
|
||||||
height: 1em;
|
|
||||||
fill: white;
|
|
||||||
}
|
|
||||||
#bm-filter-flex {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 1em 3ch;
|
|
||||||
}
|
|
||||||
#bm-window-filter .bm-filter-color {
|
|
||||||
width: fit-content;
|
|
||||||
max-width: 35ch;
|
|
||||||
background-color: rgba(21, 48, 99, 0.9);
|
|
||||||
border-radius: 1em;
|
|
||||||
padding: 0.5em;
|
|
||||||
gap: 1ch;
|
|
||||||
transition: background-color 0.3s ease;
|
|
||||||
}
|
|
||||||
#bm-window-filter .bm-filter-color:hover,
|
|
||||||
#bm-window-filter.bm-filter-color:focus-within {
|
|
||||||
background-color: rgba(17, 40, 85, 0.9);
|
|
||||||
}
|
|
||||||
#bm-window-filter .bm-filter-container-rgb {
|
|
||||||
display: block;
|
|
||||||
border: thick double darkslategray;
|
|
||||||
width: fit-content;
|
|
||||||
height: fit-content;
|
|
||||||
padding: 1ch;
|
|
||||||
}
|
|
||||||
#bm-window-filter .bm-filter-color[data-id="-2"] .bm-filter-container-rgb {
|
|
||||||
background:
|
|
||||||
conic-gradient(
|
|
||||||
#aa0000 0%,
|
|
||||||
#aaaa00 16.6%,
|
|
||||||
#00aa00 33.3%,
|
|
||||||
#00aaaa 50%,
|
|
||||||
#0000aa 66.6%,
|
|
||||||
#aa00aa 83.3%,
|
|
||||||
#aa0000 100%);
|
|
||||||
}
|
|
||||||
#bm-window-filter .bm-filter-color[data-id="-1"] .bm-filter-container-rgb {
|
|
||||||
background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 8 8" width="1em" height="1em"><path d="M0,0V8H16V16H8V0" fill="rgba(0,0,0,0.5)"/></svg>') repeat;
|
|
||||||
background-color: transparent !important;
|
|
||||||
}
|
|
||||||
#bm-window-filter .bm-filter-color[data-id="-1"] .bm-filter-container-rgb svg {
|
|
||||||
fill: white !important;
|
|
||||||
}
|
|
||||||
#bm-window-filter .bm-filter-color[data-id="0"] .bm-filter-container-rgb {
|
|
||||||
background-color: transparent !important;
|
|
||||||
}
|
|
||||||
#bm-window-filter .bm-filter-container-rgb button {
|
|
||||||
padding: 0.75em 0.5ch;
|
|
||||||
}
|
|
||||||
#bm-window-filter .bm-filter-container-rgb svg {
|
|
||||||
width: 4ch;
|
|
||||||
}
|
|
||||||
#bm-window-filter .bm-filter-color > .bm-flex-between {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 0;
|
|
||||||
}
|
|
||||||
#bm-window-filter .bm-filter-color small {
|
|
||||||
font-size: 0.75em;
|
|
||||||
}
|
|
||||||
#bm-window-filter .bm-filter-color.bm-color-hide {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
#bm-window-filter.bm-windowed #bm-filter-flex {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.25em;
|
|
||||||
}
|
|
||||||
#bm-window-filter.bm-windowed .bm-filter-color {
|
|
||||||
width: auto;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
#bm-window-filter.bm-windowed .bm-filter-container-rgb {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
gap: 0.5ch;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0.1em 0.5ch;
|
|
||||||
border: none;
|
|
||||||
border-radius: 1em;
|
|
||||||
}
|
|
||||||
#bm-window-filter.bm-windowed .bm-filter-container-rgb button {
|
|
||||||
padding: 0.5em 0.25ch;
|
|
||||||
}
|
|
||||||
#bm-window-filter.bm-windowed .bm-filter-container-rgb svg {
|
|
||||||
width: 3ch;
|
|
||||||
}
|
|
||||||
#bm-window-filter.bm-windowed .bm-filter-color h2 {
|
|
||||||
font-size: 0.75em;
|
|
||||||
}
|
|
||||||
#bm-window-filter #bm-filter-windowed-color-totals {
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* src/WindowSettings.css */
|
|
||||||
#bm-window-settings div:has(> .bm-highlight-preset-container) {
|
|
||||||
width: fit-content;
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
#bm-window-settings .bm-highlight-preset-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
width: 13%;
|
|
||||||
}
|
|
||||||
#bm-window-settings .bm-highlight-preset-container span {
|
|
||||||
width: fit-content;
|
|
||||||
margin: auto;
|
|
||||||
font-size: 0.7em;
|
|
||||||
}
|
|
||||||
#bm-window-settings .bm-highlight-preset-container button {
|
|
||||||
width: fit-content;
|
|
||||||
padding: 0;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
#bm-window-settings .bm-highlight-preset-container svg {
|
|
||||||
stroke: #333;
|
|
||||||
stroke-width: 0.02px;
|
|
||||||
width: 100%;
|
|
||||||
min-width: 1.5ch;
|
|
||||||
max-width: 14.5ch;
|
|
||||||
}
|
|
||||||
#bm-window-settings .bm-highlight-preset-container button:hover svg,
|
|
||||||
#bm-window-settings .bm-highlight-preset-container button:focus svg {
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
#bm-window-settings .bm-highlight-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
|
||||||
width: 25%;
|
|
||||||
min-width: 3ch;
|
|
||||||
max-width: 15ch;
|
|
||||||
}
|
|
||||||
#bm-window-settings .bm-highlight-grid > button {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0;
|
|
||||||
aspect-ratio: 1 / 1;
|
|
||||||
background-color: white;
|
|
||||||
border: #333 1px solid;
|
|
||||||
border-radius: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
#bm-window-settings .bm-highlight-grid > button[data-status=Incorrect] {
|
|
||||||
background-color: brown;
|
|
||||||
}
|
|
||||||
#bm-window-settings .bm-highlight-grid > button[data-status=Template] {
|
|
||||||
background-color: darkslategray;
|
|
||||||
}
|
|
||||||
#bm-window-settings .bm-highlight-grid > button:hover,
|
|
||||||
#bm-window-settings .bm-highlight-grid > button:focus {
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* src/WindowWizard.css */
|
|
||||||
#bm-wizard-tlist {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
#bm-wizard-tlist > .bm-container {
|
|
||||||
width: 100%;
|
|
||||||
justify-content: flex-start;
|
|
||||||
background-color: rgba(21, 48, 99, 0.9);
|
|
||||||
border-radius: 1em;
|
|
||||||
padding: 0.5em;
|
|
||||||
transition: background-color 0.3s ease;
|
|
||||||
}
|
|
||||||
#bm-wizard-tlist > .bm-container:hover,
|
|
||||||
#bm-wizard-tlist > .bm-container:focus-within {
|
|
||||||
background-color: rgba(17, 40, 85, 0.9);
|
|
||||||
}
|
|
||||||
#bm-wizard-tlist .bm-wizard-template-container-image {
|
|
||||||
height: 100%;
|
|
||||||
font-size: xxx-large;
|
|
||||||
}
|
|
||||||
#bm-wizard-tlist .bm-wizard-template-container-flavor {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* src/confettiManager.css */
|
/* src/confettiManager.css */
|
||||||
div:has(> confetti-piece) {
|
div:has(> confetti-piece) {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
@ -245,9 +56,6 @@ confetti-piece {
|
||||||
"Arial";
|
"Arial";
|
||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05em;
|
||||||
}
|
}
|
||||||
.bm-window.bm-windowed {
|
|
||||||
max-width: 300px;
|
|
||||||
}
|
|
||||||
.bm-dragbar {
|
.bm-dragbar {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto 1fr auto;
|
grid-template-columns: auto 1fr auto;
|
||||||
|
|
@ -423,6 +231,7 @@ input[type=file] {
|
||||||
}
|
}
|
||||||
.bm-window-content {
|
.bm-window-content {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
max-height: calc(100% - 5px);
|
||||||
transition: height 300ms cubic-bezier(.4, 0, .2, 1);
|
transition: height 300ms cubic-bezier(.4, 0, .2, 1);
|
||||||
}
|
}
|
||||||
.bm-window textarea {
|
.bm-window textarea {
|
||||||
|
|
@ -444,7 +253,7 @@ input[type=file] {
|
||||||
margin-left: 5ch;
|
margin-left: 5ch;
|
||||||
}
|
}
|
||||||
.bm-window .bm-container.bm-scrollable {
|
.bm-window .bm-container.bm-scrollable {
|
||||||
max-height: calc(80vh - 150px);
|
max-height: var(--bm-scrollable-max-height, calc(80vh - 150px));
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
.bm-flex-between {
|
.bm-flex-between {
|
||||||
|
|
@ -476,4 +285,273 @@ input[type=file] {
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* src/WindowFilter.css */
|
||||||
|
#bm-window-filter p svg {
|
||||||
|
display: inline;
|
||||||
|
height: 1em;
|
||||||
|
fill: white;
|
||||||
|
}
|
||||||
|
#bm-filter-flex {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 1em 3ch;
|
||||||
|
}
|
||||||
|
#bm-window-filter .bm-filter-color {
|
||||||
|
width: fit-content;
|
||||||
|
max-width: 35ch;
|
||||||
|
background-color: rgba(21, 48, 99, 0.9);
|
||||||
|
border-radius: 1em;
|
||||||
|
padding: 0.5em;
|
||||||
|
gap: 1ch;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
#bm-window-filter .bm-filter-color:hover,
|
||||||
|
#bm-window-filter.bm-filter-color:focus-within {
|
||||||
|
background-color: rgba(17, 40, 85, 0.9);
|
||||||
|
}
|
||||||
|
#bm-window-filter .bm-filter-container-rgb {
|
||||||
|
display: block;
|
||||||
|
border: thick double darkslategray;
|
||||||
|
width: fit-content;
|
||||||
|
height: fit-content;
|
||||||
|
padding: 1ch;
|
||||||
|
}
|
||||||
|
#bm-window-filter .bm-filter-color[data-id="-2"] .bm-filter-container-rgb {
|
||||||
|
background:
|
||||||
|
conic-gradient(
|
||||||
|
#aa0000 0%,
|
||||||
|
#aaaa00 16.6%,
|
||||||
|
#00aa00 33.3%,
|
||||||
|
#00aaaa 50%,
|
||||||
|
#0000aa 66.6%,
|
||||||
|
#aa00aa 83.3%,
|
||||||
|
#aa0000 100%);
|
||||||
|
}
|
||||||
|
#bm-window-filter .bm-filter-color[data-id="-1"] .bm-filter-container-rgb {
|
||||||
|
background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 8 8" width="1em" height="1em"><path d="M0,0V8H16V16H8V0" fill="rgba(0,0,0,0.5)"/></svg>') repeat;
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
#bm-window-filter .bm-filter-color[data-id="-1"] .bm-filter-container-rgb svg {
|
||||||
|
fill: white !important;
|
||||||
|
}
|
||||||
|
#bm-window-filter .bm-filter-color[data-id="0"] .bm-filter-container-rgb {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
#bm-window-filter .bm-filter-container-rgb button {
|
||||||
|
padding: 0.75em 0.5ch;
|
||||||
|
}
|
||||||
|
#bm-window-filter .bm-filter-container-rgb svg {
|
||||||
|
width: 4ch;
|
||||||
|
}
|
||||||
|
#bm-window-filter .bm-filter-color > .bm-flex-between {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
#bm-window-filter .bm-filter-color small {
|
||||||
|
font-size: 0.75em;
|
||||||
|
}
|
||||||
|
#bm-window-filter .bm-filter-color.bm-color-hide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#bm-window-filter.bm-windowed {
|
||||||
|
--bm-scrollable-max-height: 100%;
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto minmax(0, 1fr);
|
||||||
|
width: 300px;
|
||||||
|
height: min(70vh, 32rem);
|
||||||
|
min-width: 260px;
|
||||||
|
min-height: 220px;
|
||||||
|
max-width: min(1000px, calc(100vw - 16px)) !important;
|
||||||
|
max-height: min(1400px, calc(100vh - 16px)) !important;
|
||||||
|
overflow: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: fixed;
|
||||||
|
transition: transform 0s;
|
||||||
|
}
|
||||||
|
#bm-window-filter.bm-windowed .bm-window-content {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto auto auto minmax(0, 1fr);
|
||||||
|
grid-row: 2;
|
||||||
|
min-height: 0;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
#bm-window-filter.bm-windowed #bm-filter-flex {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 0.25em;
|
||||||
|
width: 100%;
|
||||||
|
align-self: stretch;
|
||||||
|
min-width: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
#bm-window-filter.bm-windowed .bm-filter-color {
|
||||||
|
width: 100%;
|
||||||
|
max-width: none;
|
||||||
|
align-self: stretch;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
#bm-window-filter.bm-windowed .bm-filter-color > .bm-flex-between {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
#bm-window-filter.bm-windowed .bm-container.bm-scrollable {
|
||||||
|
display: block;
|
||||||
|
grid-row: 4;
|
||||||
|
min-height: 0;
|
||||||
|
min-width: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
max-height: 100% !important;
|
||||||
|
overflow: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
#bm-window-filter.bm-windowed .bm-resize-corner {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
display: block;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
z-index: 5;
|
||||||
|
cursor: nwse-resize;
|
||||||
|
pointer-events: auto;
|
||||||
|
opacity: 1;
|
||||||
|
touch-action: none;
|
||||||
|
user-select: none;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
#bm-window-filter.bm-windowed .bm-resize-corner:hover,
|
||||||
|
#bm-window-filter.bm-windowed .bm-resize-corner.bm-resizing {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
#bm-window-filter.bm-windowed .bm-filter-container-rgb {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
gap: 0.5ch;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.1em 0.5ch;
|
||||||
|
border: none;
|
||||||
|
border-radius: 1em;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
#bm-window-filter.bm-windowed .bm-filter-container-rgb button {
|
||||||
|
padding: 0.5em 0.25ch;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
#bm-window-filter.bm-windowed .bm-filter-container-rgb svg {
|
||||||
|
width: 3ch;
|
||||||
|
}
|
||||||
|
#bm-window-filter.bm-windowed .bm-filter-color h2 {
|
||||||
|
font-size: 0.75em;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
#bm-window-filter #bm-filter-windowed-color-totals {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* src/WindowSettings.css */
|
||||||
|
#bm-window-settings div:has(> .bm-highlight-preset-container) {
|
||||||
|
width: fit-content;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
#bm-window-settings .bm-highlight-preset-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 13%;
|
||||||
|
}
|
||||||
|
#bm-window-settings .bm-highlight-preset-container span {
|
||||||
|
width: fit-content;
|
||||||
|
margin: auto;
|
||||||
|
font-size: 0.7em;
|
||||||
|
}
|
||||||
|
#bm-window-settings .bm-highlight-preset-container button {
|
||||||
|
width: fit-content;
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
#bm-window-settings .bm-highlight-preset-container svg {
|
||||||
|
stroke: #333;
|
||||||
|
stroke-width: 0.02px;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 1.5ch;
|
||||||
|
max-width: 14.5ch;
|
||||||
|
}
|
||||||
|
#bm-window-settings .bm-highlight-preset-container button:hover svg,
|
||||||
|
#bm-window-settings .bm-highlight-preset-container button:focus svg {
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
#bm-window-settings .bm-highlight-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
width: 25%;
|
||||||
|
min-width: 3ch;
|
||||||
|
max-width: 15ch;
|
||||||
|
}
|
||||||
|
#bm-window-settings .bm-highlight-grid > button {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
background-color: white;
|
||||||
|
border: #333 1px solid;
|
||||||
|
border-radius: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
#bm-window-settings .bm-highlight-grid > button[data-status=Incorrect] {
|
||||||
|
background-color: brown;
|
||||||
|
}
|
||||||
|
#bm-window-settings .bm-highlight-grid > button[data-status=Template] {
|
||||||
|
background-color: darkslategray;
|
||||||
|
}
|
||||||
|
#bm-window-settings .bm-highlight-grid > button:hover,
|
||||||
|
#bm-window-settings .bm-highlight-grid > button:focus {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* src/WindowWizard.css */
|
||||||
|
#bm-wizard-tlist {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
#bm-wizard-tlist > .bm-container {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: flex-start;
|
||||||
|
background-color: rgba(21, 48, 99, 0.9);
|
||||||
|
border-radius: 1em;
|
||||||
|
padding: 0.5em;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
#bm-wizard-tlist > .bm-container:hover,
|
||||||
|
#bm-wizard-tlist > .bm-container:focus-within {
|
||||||
|
background-color: rgba(17, 40, 85, 0.9);
|
||||||
|
}
|
||||||
|
#bm-wizard-tlist .bm-wizard-template-container-image {
|
||||||
|
height: 100%;
|
||||||
|
font-size: xxx-large;
|
||||||
|
}
|
||||||
|
#bm-wizard-tlist .bm-wizard-template-container-flavor {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* src/main.css */
|
/* src/main.css */
|
||||||
|
|
||||||
|
/* Build Hash: 4336138174a3 */
|
||||||
|
|
|
||||||
400
dist/BlueMarble-For-GreasyFork.user.js
vendored
400
dist/BlueMarble-For-GreasyFork.user.js
vendored
|
|
@ -2,14 +2,14 @@
|
||||||
// @name Blue Marble
|
// @name Blue Marble
|
||||||
// @name:en Blue Marble
|
// @name:en Blue Marble
|
||||||
// @namespace https://github.com/SwingTheVine/
|
// @namespace https://github.com/SwingTheVine/
|
||||||
// @version 0.92.0
|
// @version 0.92.1
|
||||||
// @description A userscript to enhance the user experience on Wplace.live. This includes, but is not limited to: uploading images to display locally on a canvas, adding a button to move the Wplace color palette menu, and other QoL features.
|
// @description A userscript to enhance the user experience on Wplace.live. This includes, but is not limited to: uploading images to display locally on a canvas, adding a button to move the Wplace color palette menu, and other QoL features.
|
||||||
// @description:en A userscript to enhance the user experience on Wplace.live. This includes, but is not limited to: uploading images to display locally on a canvas, adding a button to move the Wplace color palette menu, and other QoL features.
|
// @description:en A userscript to enhance the user experience on Wplace.live. This includes, but is not limited to: uploading images to display locally on a canvas, adding a button to move the Wplace color palette menu, and other QoL features.
|
||||||
// @author SwingTheVine
|
// @author SwingTheVine
|
||||||
// @license MPL-2.0
|
// @license MPL-2.0
|
||||||
// @supportURL https://discord.gg/tpeBPy46hf
|
// @supportURL https://discord.gg/tpeBPy46hf
|
||||||
// @homepageURL https://bluemarble.lol/
|
// @homepageURL https://bluemarble.lol/
|
||||||
// @icon https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/78477321232b29c09e3794c360068d7d23a0172c/dist/assets/Favicon.png
|
// @icon https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/2cd51bf91944ae2acb253ea5bbd76f79b7a2edd3/dist/assets/Favicon.png
|
||||||
// @updateURL https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/BlueMarble-For-GreasyFork.user.js
|
// @updateURL https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/BlueMarble-For-GreasyFork.user.js
|
||||||
// @downloadURL https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/BlueMarble-For-GreasyFork.user.js
|
// @downloadURL https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/BlueMarble-For-GreasyFork.user.js
|
||||||
// @match https://wplace.live/*
|
// @match https://wplace.live/*
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
// @grant GM_xmlhttpRequest
|
// @grant GM_xmlhttpRequest
|
||||||
// @grant GM.download
|
// @grant GM.download
|
||||||
// @connect telemetry.thebluecorner.net
|
// @connect telemetry.thebluecorner.net
|
||||||
// @resource CSS-BM-File https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/78477321232b29c09e3794c360068d7d23a0172c/dist/BlueMarble-For-GreasyFork.user.css
|
// @resource CSS-BM-File https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/2cd51bf91944ae2acb253ea5bbd76f79b7a2edd3/dist/BlueMarble-For-GreasyFork.user.css
|
||||||
// @antifeature tracking Anonymous opt-in telemetry data
|
// @antifeature tracking Anonymous opt-in telemetry data
|
||||||
// @noframes
|
// @noframes
|
||||||
// ==/UserScript==
|
// ==/UserScript==
|
||||||
|
|
@ -1366,9 +1366,11 @@
|
||||||
* @param {string} iMoveThingsSelector - The drag handle element
|
* @param {string} iMoveThingsSelector - The drag handle element
|
||||||
* @since 0.8.2
|
* @since 0.8.2
|
||||||
*/
|
*/
|
||||||
handleDrag(moveMeSelector, iMoveThingsSelector) {
|
handleDrag(moveMeSelector, iMoveThingsSelector, options = {}) {
|
||||||
const moveMe = document.querySelector(moveMeSelector);
|
const moveMe = document.querySelector(moveMeSelector);
|
||||||
const iMoveThings = document.querySelector(iMoveThingsSelector);
|
const iMoveThings = document.querySelector(iMoveThingsSelector);
|
||||||
|
const onEnd = options?.onEnd ?? (() => {
|
||||||
|
});
|
||||||
if (!moveMe || !iMoveThings) {
|
if (!moveMe || !iMoveThings) {
|
||||||
this.handleDisplayError(`Can not drag! ${!moveMe ? "moveMe" : ""} ${!moveMe && !iMoveThings ? "and " : ""}${!iMoveThings ? "iMoveThings " : ""}was not found!`);
|
this.handleDisplayError(`Can not drag! ${!moveMe ? "moveMe" : ""} ${!moveMe && !iMoveThings ? "and " : ""}${!iMoveThings ? "iMoveThings " : ""}was not found!`);
|
||||||
return;
|
return;
|
||||||
|
|
@ -1438,6 +1440,12 @@
|
||||||
document.removeEventListener("mouseup", endDrag);
|
document.removeEventListener("mouseup", endDrag);
|
||||||
document.removeEventListener("touchend", endDrag);
|
document.removeEventListener("touchend", endDrag);
|
||||||
document.removeEventListener("touchcancel", endDrag);
|
document.removeEventListener("touchcancel", endDrag);
|
||||||
|
onEnd({
|
||||||
|
element: moveMe,
|
||||||
|
x: currentX,
|
||||||
|
y: currentY
|
||||||
|
});
|
||||||
|
initialRect = null;
|
||||||
};
|
};
|
||||||
const onMouseMove = (event) => {
|
const onMouseMove = (event) => {
|
||||||
if (isDragging && initialRect) {
|
if (isDragging && initialRect) {
|
||||||
|
|
@ -1467,6 +1475,124 @@
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}, { passive: false });
|
}, { passive: false });
|
||||||
}
|
}
|
||||||
|
/** Handles resizing of an overlay window from a resize handle.
|
||||||
|
* @param {string} resizeMeSelector - The element to resize
|
||||||
|
* @param {string} iResizeThingsSelector - The resize handle element
|
||||||
|
* @param {{onEnd?: function({element: HTMLElement, width: number, height: number}): void, minWidth?: number, minHeight?: number, maxWidth?: number, maxHeight?: number}} [options={}]
|
||||||
|
* @since 0.92.0
|
||||||
|
*/
|
||||||
|
handleResize(resizeMeSelector, iResizeThingsSelector, options = {}) {
|
||||||
|
const resizeMe = document.querySelector(resizeMeSelector);
|
||||||
|
const iResizeThings = document.querySelector(iResizeThingsSelector);
|
||||||
|
const onEnd = options?.onEnd ?? (() => {
|
||||||
|
});
|
||||||
|
if (!resizeMe || !iResizeThings) {
|
||||||
|
this.handleDisplayError(`Can not resize! ${!resizeMe ? "resizeMe" : ""} ${!resizeMe && !iResizeThings ? "and " : ""}${!iResizeThings ? "iResizeThings " : ""}was not found!`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let isResizing = false;
|
||||||
|
let startX = 0;
|
||||||
|
let startY = 0;
|
||||||
|
let startWidth = 0;
|
||||||
|
let startHeight = 0;
|
||||||
|
let currentWidth = 0;
|
||||||
|
let currentHeight = 0;
|
||||||
|
let targetWidth = 0;
|
||||||
|
let targetHeight = 0;
|
||||||
|
let animationFrame = null;
|
||||||
|
const getMaximumWidth = () => Number.isFinite(options?.maxWidth) ? options.maxWidth : window.innerWidth - 16;
|
||||||
|
const getMaximumHeight = () => Number.isFinite(options?.maxHeight) ? options.maxHeight : window.innerHeight - 16;
|
||||||
|
const minimumWidth = Number.isFinite(options?.minWidth) ? options.minWidth : 200;
|
||||||
|
const minimumHeight = Number.isFinite(options?.minHeight) ? options.minHeight : 160;
|
||||||
|
const clamp = (value, minimum, maximum) => Math.min(Math.max(value, minimum), Math.max(minimum, maximum));
|
||||||
|
const updateSize = () => {
|
||||||
|
if (isResizing) {
|
||||||
|
const deltaWidth = Math.abs(currentWidth - targetWidth);
|
||||||
|
const deltaHeight = Math.abs(currentHeight - targetHeight);
|
||||||
|
if (deltaWidth > 0.5 || deltaHeight > 0.5) {
|
||||||
|
currentWidth = targetWidth;
|
||||||
|
currentHeight = targetHeight;
|
||||||
|
resizeMe.style.width = `${currentWidth}px`;
|
||||||
|
resizeMe.style.height = `${currentHeight}px`;
|
||||||
|
}
|
||||||
|
animationFrame = requestAnimationFrame(updateSize);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const startResize = (clientX, clientY) => {
|
||||||
|
isResizing = true;
|
||||||
|
startX = clientX;
|
||||||
|
startY = clientY;
|
||||||
|
startWidth = resizeMe.offsetWidth;
|
||||||
|
startHeight = resizeMe.offsetHeight;
|
||||||
|
currentWidth = startWidth;
|
||||||
|
currentHeight = startHeight;
|
||||||
|
targetWidth = startWidth;
|
||||||
|
targetHeight = startHeight;
|
||||||
|
document.body.style.userSelect = "none";
|
||||||
|
iResizeThings.classList.add("bm-resizing");
|
||||||
|
document.addEventListener("mousemove", onMouseMove);
|
||||||
|
document.addEventListener("touchmove", onTouchMove, { passive: false });
|
||||||
|
document.addEventListener("mouseup", endResize);
|
||||||
|
document.addEventListener("touchend", endResize);
|
||||||
|
document.addEventListener("touchcancel", endResize);
|
||||||
|
if (animationFrame) {
|
||||||
|
cancelAnimationFrame(animationFrame);
|
||||||
|
}
|
||||||
|
updateSize();
|
||||||
|
};
|
||||||
|
const endResize = () => {
|
||||||
|
isResizing = false;
|
||||||
|
if (animationFrame) {
|
||||||
|
cancelAnimationFrame(animationFrame);
|
||||||
|
animationFrame = null;
|
||||||
|
}
|
||||||
|
document.body.style.userSelect = "";
|
||||||
|
iResizeThings.classList.remove("bm-resizing");
|
||||||
|
document.removeEventListener("mousemove", onMouseMove);
|
||||||
|
document.removeEventListener("touchmove", onTouchMove);
|
||||||
|
document.removeEventListener("mouseup", endResize);
|
||||||
|
document.removeEventListener("touchend", endResize);
|
||||||
|
document.removeEventListener("touchcancel", endResize);
|
||||||
|
onEnd({
|
||||||
|
element: resizeMe,
|
||||||
|
width: currentWidth,
|
||||||
|
height: currentHeight
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const onMouseMove = (event) => {
|
||||||
|
if (!isResizing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
targetWidth = clamp(startWidth + (event.clientX - startX), minimumWidth, getMaximumWidth());
|
||||||
|
targetHeight = clamp(startHeight + (event.clientY - startY), minimumHeight, getMaximumHeight());
|
||||||
|
};
|
||||||
|
const onTouchMove = (event) => {
|
||||||
|
if (!isResizing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const touch = event?.touches?.[0];
|
||||||
|
if (!touch) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
targetWidth = clamp(startWidth + (touch.clientX - startX), minimumWidth, getMaximumWidth());
|
||||||
|
targetHeight = clamp(startHeight + (touch.clientY - startY), minimumHeight, getMaximumHeight());
|
||||||
|
event.preventDefault();
|
||||||
|
};
|
||||||
|
iResizeThings.addEventListener("mousedown", (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
startResize(event.clientX, event.clientY);
|
||||||
|
});
|
||||||
|
iResizeThings.addEventListener("touchstart", (event) => {
|
||||||
|
const touch = event?.touches?.[0];
|
||||||
|
if (!touch) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
startResize(touch.clientX, touch.clientY);
|
||||||
|
}, { passive: false });
|
||||||
|
}
|
||||||
/** Handles status display.
|
/** Handles status display.
|
||||||
* This will output plain text into the output Status box.
|
* This will output plain text into the output Status box.
|
||||||
* Additionally, this will output an info message to the console.
|
* Additionally, this will output an info message to the console.
|
||||||
|
|
@ -1637,15 +1763,28 @@
|
||||||
* @since 0.91.39
|
* @since 0.91.39
|
||||||
*/
|
*/
|
||||||
async updateUserStorage() {
|
async updateUserStorage() {
|
||||||
|
await this.saveUserStorage();
|
||||||
|
}
|
||||||
|
/** Saves the user settings in userscript storage.
|
||||||
|
* @param {boolean} [force=false] - Should the throttle be ignored?
|
||||||
|
* @since 0.92.0
|
||||||
|
*/
|
||||||
|
async saveUserStorage(force = false) {
|
||||||
const userSettingsCurrent = JSON.stringify(this.userSettings);
|
const userSettingsCurrent = JSON.stringify(this.userSettings);
|
||||||
const userSettingsOld = JSON.stringify(this.userSettingsOld);
|
const userSettingsOld = JSON.stringify(this.userSettingsOld);
|
||||||
if (userSettingsCurrent != userSettingsOld && Date.now() - this.lastUpdateTime > this.updateFrequency) {
|
if (userSettingsCurrent != userSettingsOld && (force || Date.now() - this.lastUpdateTime > this.updateFrequency)) {
|
||||||
await GM.setValue(this.userSettingsSaveLocation, userSettingsCurrent);
|
await GM.setValue(this.userSettingsSaveLocation, userSettingsCurrent);
|
||||||
this.userSettingsOld = structuredClone(this.userSettings);
|
this.userSettingsOld = structuredClone(this.userSettings);
|
||||||
this.lastUpdateTime = Date.now();
|
this.lastUpdateTime = Date.now();
|
||||||
console.log(userSettingsCurrent);
|
console.log(userSettingsCurrent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/** Immediately saves the user settings in userscript storage.
|
||||||
|
* @since 0.92.0
|
||||||
|
*/
|
||||||
|
async saveUserStorageNow() {
|
||||||
|
await this.saveUserStorage(true);
|
||||||
|
}
|
||||||
/** Toggles a boolean flag to the state that was passed in.
|
/** Toggles a boolean flag to the state that was passed in.
|
||||||
* If no state was passed in, the flag will flip to the opposite state.
|
* If no state was passed in, the flag will flip to the opposite state.
|
||||||
* The existence of the flag determines its state. If it exists, it is `true`.
|
* The existence of the flag determines its state. If it exists, it is `true`.
|
||||||
|
|
@ -2219,7 +2358,7 @@ Getting Y ${pixelY}-${pixelY + drawSizeY}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
// src/WindowFilter.js
|
// src/WindowFilter.js
|
||||||
var _WindowFilter_instances, buildColorList_fn, sortColorList_fn, selectColorList_fn, calculatePixelStatistics_fn;
|
var _WindowFilter_instances, getWindowState_fn, setWindowModePreference_fn, closeWindow_fn, cleanupWindowPersistence_fn, clampWindowDimension_fn, clampWindowPosition_fn, restoreWindowState_fn, saveWindowState_fn, scheduleWindowStateSave_fn, initializeWindowedPersistence_fn, buildColorList_fn, sortColorList_fn, selectColorList_fn, calculatePixelStatistics_fn;
|
||||||
var WindowFilter = class extends Overlay {
|
var WindowFilter = class extends Overlay {
|
||||||
/** Constructor for the color filter window
|
/** Constructor for the color filter window
|
||||||
* @param {*} executor - The executing class
|
* @param {*} executor - The executing class
|
||||||
|
|
@ -2233,6 +2372,16 @@ Getting Y ${pixelY}-${pixelY + drawSizeY}`);
|
||||||
this.windowID = "bm-window-filter";
|
this.windowID = "bm-window-filter";
|
||||||
this.colorListID = "bm-filter-flex";
|
this.colorListID = "bm-filter-flex";
|
||||||
this.windowParent = document.body;
|
this.windowParent = document.body;
|
||||||
|
this.settingsManager = executor.settingsManager ?? null;
|
||||||
|
this.windowModeFlag = "ftr-oWin";
|
||||||
|
this.windowStateKey = "windowFilter";
|
||||||
|
this.windowResizeObserver = null;
|
||||||
|
this.windowViewportResizeHandler = null;
|
||||||
|
this.windowSaveTimeout = null;
|
||||||
|
this.windowMinWidth = 260;
|
||||||
|
this.windowMinHeight = 220;
|
||||||
|
this.windowMaxWidth = 1e3;
|
||||||
|
this.windowMaxHeight = 1400;
|
||||||
this.templateManager = executor.apiManager?.templateManager;
|
this.templateManager = executor.apiManager?.templateManager;
|
||||||
this.eyeOpen = '<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>';
|
this.eyeOpen = '<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>';
|
||||||
this.eyeClosed = '<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>';
|
this.eyeClosed = '<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>';
|
||||||
|
|
@ -2250,6 +2399,16 @@ Getting Y ${pixelY}-${pixelY + drawSizeY}`);
|
||||||
this.sortSecondary = "ascending";
|
this.sortSecondary = "ascending";
|
||||||
this.showUnused = false;
|
this.showUnused = false;
|
||||||
}
|
}
|
||||||
|
/** Builds the preferred filter window mode for the user.
|
||||||
|
* @since 0.92.0
|
||||||
|
*/
|
||||||
|
buildPreferredWindow() {
|
||||||
|
if (this.settingsManager?.userSettings?.flags?.includes(this.windowModeFlag)) {
|
||||||
|
this.buildWindowed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.buildWindow();
|
||||||
|
}
|
||||||
/** Spawns a Color Filter window.
|
/** Spawns a Color Filter window.
|
||||||
* If another color filter window already exists, we DON'T spawn another!
|
* If another color filter window already exists, we DON'T spawn another!
|
||||||
* Parent/child relationships in the DOM structure below are indicated by indentation.
|
* Parent/child relationships in the DOM structure below are indicated by indentation.
|
||||||
|
|
@ -2257,7 +2416,7 @@ Getting Y ${pixelY}-${pixelY + drawSizeY}`);
|
||||||
*/
|
*/
|
||||||
buildWindow() {
|
buildWindow() {
|
||||||
if (document.querySelector(`#${this.windowID}`)) {
|
if (document.querySelector(`#${this.windowID}`)) {
|
||||||
document.querySelector(`#${this.windowID}`).remove();
|
__privateMethod(this, _WindowFilter_instances, closeWindow_fn).call(this);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.window = this.addDiv({ "id": this.windowID, "class": "bm-window" }, (instance, div) => {
|
this.window = this.addDiv({ "id": this.windowID, "class": "bm-window" }, (instance, div) => {
|
||||||
|
|
@ -2268,16 +2427,15 @@ Getting Y ${pixelY}-${pixelY + drawSizeY}`);
|
||||||
};
|
};
|
||||||
}).buildElement().addDiv().buildElement().addDiv({ "class": "bm-flex-center" }).addButton({ "class": "bm-button-circle", "textContent": "\u{1F5D7}", "aria-label": 'Switch to windowed mode for "Color Filter"' }, (instance, button) => {
|
}).buildElement().addDiv().buildElement().addDiv({ "class": "bm-flex-center" }).addButton({ "class": "bm-button-circle", "textContent": "\u{1F5D7}", "aria-label": 'Switch to windowed mode for "Color Filter"' }, (instance, button) => {
|
||||||
button.onclick = () => {
|
button.onclick = () => {
|
||||||
document.querySelector(`#${this.windowID}`)?.remove();
|
__privateMethod(this, _WindowFilter_instances, setWindowModePreference_fn).call(this, true);
|
||||||
|
__privateMethod(this, _WindowFilter_instances, closeWindow_fn).call(this);
|
||||||
this.buildWindowed();
|
this.buildWindowed();
|
||||||
};
|
};
|
||||||
button.ontouchend = () => {
|
button.ontouchend = () => {
|
||||||
button.click();
|
button.click();
|
||||||
};
|
};
|
||||||
}).buildElement().addButton({ "class": "bm-button-circle", "textContent": "\u2716", "aria-label": 'Close window "Color Filter"' }, (instance, button) => {
|
}).buildElement().addButton({ "class": "bm-button-circle", "textContent": "\u2716", "aria-label": 'Close window "Color Filter"' }, (instance, button) => {
|
||||||
button.onclick = () => {
|
button.onclick = () => __privateMethod(this, _WindowFilter_instances, closeWindow_fn).call(this);
|
||||||
document.querySelector(`#${this.windowID}`)?.remove();
|
|
||||||
};
|
|
||||||
button.ontouchend = () => {
|
button.ontouchend = () => {
|
||||||
button.click();
|
button.click();
|
||||||
};
|
};
|
||||||
|
|
@ -2320,10 +2478,14 @@ Getting Y ${pixelY}-${pixelY + drawSizeY}`);
|
||||||
*/
|
*/
|
||||||
buildWindowed() {
|
buildWindowed() {
|
||||||
if (document.querySelector(`#${this.windowID}`)) {
|
if (document.querySelector(`#${this.windowID}`)) {
|
||||||
document.querySelector(`#${this.windowID}`).remove();
|
__privateMethod(this, _WindowFilter_instances, closeWindow_fn).call(this);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.window = this.addDiv({ "id": this.windowID, "class": "bm-window bm-windowed" }).addDragbar().addButton({ "class": "bm-button-circle", "textContent": "\u25BC", "aria-label": 'Minimize window "Color Filter"', "data-button-status": "expanded" }, (instance, button) => {
|
this.window = this.addDiv({
|
||||||
|
"id": this.windowID,
|
||||||
|
"class": "bm-window bm-windowed",
|
||||||
|
"style": `width: 300px; height: min(70vh, 32rem); min-width: ${this.windowMinWidth}px; min-height: ${this.windowMinHeight}px; max-width: min(${this.windowMaxWidth}px, calc(100vw - 16px)); max-height: min(${this.windowMaxHeight}px, calc(100vh - 16px));`
|
||||||
|
}).addDragbar().addButton({ "class": "bm-button-circle", "textContent": "\u25BC", "aria-label": 'Minimize window "Color Filter"', "data-button-status": "expanded" }, (instance, button) => {
|
||||||
button.onclick = () => {
|
button.onclick = () => {
|
||||||
const windowedColorTotals = document.querySelector("#bm-filter-windowed-color-totals");
|
const windowedColorTotals = document.querySelector("#bm-filter-windowed-color-totals");
|
||||||
if (windowedColorTotals) {
|
if (windowedColorTotals) {
|
||||||
|
|
@ -2336,16 +2498,15 @@ Getting Y ${pixelY}-${pixelY + drawSizeY}`);
|
||||||
};
|
};
|
||||||
}).buildElement().addDiv().addSpan({ "id": "bm-filter-windowed-color-totals", "class": "bm-dragbar-text", "style": "font-weight: 700;" }).buildElement().buildElement().addDiv({ "class": "bm-flex-center" }).addButton({ "class": "bm-button-circle", "textContent": "\u{1F5D6}", "aria-label": 'Switch to fullscreen mode for "Color Filter"' }, (instance, button) => {
|
}).buildElement().addDiv().addSpan({ "id": "bm-filter-windowed-color-totals", "class": "bm-dragbar-text", "style": "font-weight: 700;" }).buildElement().buildElement().addDiv({ "class": "bm-flex-center" }).addButton({ "class": "bm-button-circle", "textContent": "\u{1F5D6}", "aria-label": 'Switch to fullscreen mode for "Color Filter"' }, (instance, button) => {
|
||||||
button.onclick = () => {
|
button.onclick = () => {
|
||||||
document.querySelector(`#${this.windowID}`)?.remove();
|
__privateMethod(this, _WindowFilter_instances, setWindowModePreference_fn).call(this, false);
|
||||||
|
__privateMethod(this, _WindowFilter_instances, closeWindow_fn).call(this);
|
||||||
this.buildWindow();
|
this.buildWindow();
|
||||||
};
|
};
|
||||||
button.ontouchend = () => {
|
button.ontouchend = () => {
|
||||||
button.click();
|
button.click();
|
||||||
};
|
};
|
||||||
}).buildElement().addButton({ "class": "bm-button-circle", "textContent": "\u2716", "aria-label": 'Close window "Color Filter"' }, (instance, button) => {
|
}).buildElement().addButton({ "class": "bm-button-circle", "textContent": "\u2716", "aria-label": 'Close window "Color Filter"' }, (instance, button) => {
|
||||||
button.onclick = () => {
|
button.onclick = () => __privateMethod(this, _WindowFilter_instances, closeWindow_fn).call(this);
|
||||||
document.querySelector(`#${this.windowID}`)?.remove();
|
|
||||||
};
|
|
||||||
button.ontouchend = () => {
|
button.ontouchend = () => {
|
||||||
button.click();
|
button.click();
|
||||||
};
|
};
|
||||||
|
|
@ -2359,8 +2520,15 @@ Getting Y ${pixelY}-${pixelY + drawSizeY}`);
|
||||||
};
|
};
|
||||||
}).buildElement().addButton({ "textContent": "All" }, (instance, button) => {
|
}).buildElement().addButton({ "textContent": "All" }, (instance, button) => {
|
||||||
button.onclick = () => __privateMethod(this, _WindowFilter_instances, selectColorList_fn).call(this, true);
|
button.onclick = () => __privateMethod(this, _WindowFilter_instances, selectColorList_fn).call(this, true);
|
||||||
}).buildElement().buildElement().addDiv({ "class": "bm-container bm-scrollable" }).buildElement().buildElement().buildElement().buildOverlay(this.windowParent);
|
}).buildElement().buildElement().addDiv({ "class": "bm-container bm-scrollable" }).buildElement().buildElement().addDiv({
|
||||||
this.handleDrag(`#${this.windowID}.bm-window`, `#${this.windowID} .bm-dragbar`);
|
"class": "bm-resize-corner",
|
||||||
|
"title": "Resize Color Filter window",
|
||||||
|
"aria-label": "Resize Color Filter window",
|
||||||
|
"role": "presentation",
|
||||||
|
"textContent": "\u25E2",
|
||||||
|
"style": "position: absolute; right: 0; bottom: 0; width: 28px; height: 28px; display: flex; align-items: flex-end; justify-content: flex-end; padding-right: 4px; padding-bottom: 4px; box-sizing: border-box; z-index: 5; cursor: nwse-resize; pointer-events: auto; touch-action: none; user-select: none; font-size: 8px; line-height: 1; color: rgba(255,255,255,0.95); background: transparent; border: none; box-shadow: none;"
|
||||||
|
}).buildElement().buildElement().buildOverlay(this.windowParent);
|
||||||
|
__privateMethod(this, _WindowFilter_instances, initializeWindowedPersistence_fn).call(this);
|
||||||
const scrollableContainer = document.querySelector(`#${this.windowID} .bm-container.bm-scrollable`);
|
const scrollableContainer = document.querySelector(`#${this.windowID} .bm-container.bm-scrollable`);
|
||||||
__privateMethod(this, _WindowFilter_instances, buildColorList_fn).call(this, scrollableContainer);
|
__privateMethod(this, _WindowFilter_instances, buildColorList_fn).call(this, scrollableContainer);
|
||||||
__privateMethod(this, _WindowFilter_instances, sortColorList_fn).call(this, this.sortPrimary, this.sortSecondary, this.showUnused);
|
__privateMethod(this, _WindowFilter_instances, sortColorList_fn).call(this, this.sortPrimary, this.sortSecondary, this.showUnused);
|
||||||
|
|
@ -2445,6 +2613,196 @@ Getting Y ${pixelY}-${pixelY + drawSizeY}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
_WindowFilter_instances = new WeakSet();
|
_WindowFilter_instances = new WeakSet();
|
||||||
|
/** Retrieves the persisted window state object.
|
||||||
|
* @returns {Object | null}
|
||||||
|
* @since 0.92.0
|
||||||
|
*/
|
||||||
|
getWindowState_fn = function() {
|
||||||
|
var _a, _b;
|
||||||
|
if (!this.settingsManager) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
(_a = this.settingsManager.userSettings)[_b = this.windowStateKey] ?? (_a[_b] = {});
|
||||||
|
return this.settingsManager.userSettings[this.windowStateKey];
|
||||||
|
};
|
||||||
|
/** Updates the preferred window mode setting.
|
||||||
|
* @param {boolean} shouldBeWindowed
|
||||||
|
* @since 0.92.0
|
||||||
|
*/
|
||||||
|
setWindowModePreference_fn = function(shouldBeWindowed) {
|
||||||
|
if (!this.settingsManager) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.settingsManager.toggleFlag(this.windowModeFlag, shouldBeWindowed);
|
||||||
|
void this.settingsManager.saveUserStorageNow();
|
||||||
|
};
|
||||||
|
/** Immediately closes the filter window and cleans up persistence observers.
|
||||||
|
* @since 0.92.0
|
||||||
|
*/
|
||||||
|
closeWindow_fn = function() {
|
||||||
|
const windowElement = document.querySelector(`#${this.windowID}`);
|
||||||
|
if (windowElement?.classList.contains("bm-windowed")) {
|
||||||
|
__privateMethod(this, _WindowFilter_instances, saveWindowState_fn).call(this, windowElement);
|
||||||
|
}
|
||||||
|
__privateMethod(this, _WindowFilter_instances, cleanupWindowPersistence_fn).call(this);
|
||||||
|
windowElement?.remove();
|
||||||
|
};
|
||||||
|
/** Disconnects live observers used for window persistence.
|
||||||
|
* @since 0.92.0
|
||||||
|
*/
|
||||||
|
cleanupWindowPersistence_fn = function() {
|
||||||
|
if (this.windowResizeObserver) {
|
||||||
|
this.windowResizeObserver.disconnect();
|
||||||
|
this.windowResizeObserver = null;
|
||||||
|
}
|
||||||
|
if (this.windowViewportResizeHandler) {
|
||||||
|
window.removeEventListener("resize", this.windowViewportResizeHandler);
|
||||||
|
this.windowViewportResizeHandler = null;
|
||||||
|
}
|
||||||
|
if (this.windowSaveTimeout) {
|
||||||
|
clearTimeout(this.windowSaveTimeout);
|
||||||
|
this.windowSaveTimeout = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/** Returns a clamped dimension value for the window.
|
||||||
|
* @param {number} size - The size in pixels
|
||||||
|
* @param {number} minimum - Minimum allowed size
|
||||||
|
* @param {number} maximum - Maximum allowed size
|
||||||
|
* @returns {number}
|
||||||
|
* @since 0.92.0
|
||||||
|
*/
|
||||||
|
clampWindowDimension_fn = function(size, minimum, maximum) {
|
||||||
|
const resolvedMaximum = Math.max(minimum, maximum);
|
||||||
|
return Math.min(Math.max(Math.round(Number(size) || minimum), minimum), resolvedMaximum);
|
||||||
|
};
|
||||||
|
/** Returns a viewport-safe position for the window.
|
||||||
|
* @param {HTMLElement} windowElement
|
||||||
|
* @param {number} x
|
||||||
|
* @param {number} y
|
||||||
|
* @returns {{x: number, y: number}}
|
||||||
|
* @since 0.92.0
|
||||||
|
*/
|
||||||
|
clampWindowPosition_fn = function(windowElement, x, y) {
|
||||||
|
const margin = 8;
|
||||||
|
const maxX = Math.max(margin, window.innerWidth - windowElement.offsetWidth - margin);
|
||||||
|
const maxY = Math.max(margin, window.innerHeight - windowElement.offsetHeight - margin);
|
||||||
|
return {
|
||||||
|
x: Math.min(Math.max(Math.round(Number(x) || margin), margin), maxX),
|
||||||
|
y: Math.min(Math.max(Math.round(Number(y) || margin), margin), maxY)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** Applies the persisted size and position to the windowed filter.
|
||||||
|
* @param {HTMLElement} windowElement
|
||||||
|
* @since 0.92.0
|
||||||
|
*/
|
||||||
|
restoreWindowState_fn = function(windowElement) {
|
||||||
|
const windowState = __privateMethod(this, _WindowFilter_instances, getWindowState_fn).call(this);
|
||||||
|
if (!windowState || !windowElement) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const width = Number(windowState.width);
|
||||||
|
const height = Number(windowState.height);
|
||||||
|
const hasWidth = Number.isFinite(width);
|
||||||
|
const hasHeight = Number.isFinite(height);
|
||||||
|
if (hasWidth) {
|
||||||
|
windowState.width = __privateMethod(this, _WindowFilter_instances, clampWindowDimension_fn).call(this, width, this.windowMinWidth, Math.min(this.windowMaxWidth, window.innerWidth - 16));
|
||||||
|
windowElement.style.width = `${windowState.width}px`;
|
||||||
|
}
|
||||||
|
if (hasHeight) {
|
||||||
|
windowState.height = __privateMethod(this, _WindowFilter_instances, clampWindowDimension_fn).call(this, height, this.windowMinHeight, Math.min(this.windowMaxHeight, window.innerHeight - 16));
|
||||||
|
windowElement.style.height = `${windowState.height}px`;
|
||||||
|
}
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
if (!windowElement.isConnected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const x = Number(windowState.x);
|
||||||
|
const y = Number(windowState.y);
|
||||||
|
if (!Number.isFinite(x) || !Number.isFinite(y)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const clampedPosition = __privateMethod(this, _WindowFilter_instances, clampWindowPosition_fn).call(this, windowElement, x, y);
|
||||||
|
windowElement.style.left = "0px";
|
||||||
|
windowElement.style.top = "0px";
|
||||||
|
windowElement.style.right = "";
|
||||||
|
windowElement.style.transform = `translate(${clampedPosition.x}px, ${clampedPosition.y}px)`;
|
||||||
|
if (clampedPosition.x != x || clampedPosition.y != y) {
|
||||||
|
windowState.x = clampedPosition.x;
|
||||||
|
windowState.y = clampedPosition.y;
|
||||||
|
void this.settingsManager?.saveUserStorageNow();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
/** Saves the current size and position of the windowed filter.
|
||||||
|
* @param {HTMLElement} windowElement
|
||||||
|
* @since 0.92.0
|
||||||
|
*/
|
||||||
|
saveWindowState_fn = function(windowElement) {
|
||||||
|
const windowState = __privateMethod(this, _WindowFilter_instances, getWindowState_fn).call(this);
|
||||||
|
if (!windowState || !windowElement?.isConnected || !windowElement.classList.contains("bm-windowed")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const rect = windowElement.getBoundingClientRect();
|
||||||
|
const width = __privateMethod(this, _WindowFilter_instances, clampWindowDimension_fn).call(this, rect.width, this.windowMinWidth, Math.min(this.windowMaxWidth, window.innerWidth - 16));
|
||||||
|
const height = __privateMethod(this, _WindowFilter_instances, clampWindowDimension_fn).call(this, rect.height, this.windowMinHeight, Math.min(this.windowMaxHeight, window.innerHeight - 16));
|
||||||
|
if (Math.round(rect.width) != width) {
|
||||||
|
windowElement.style.width = `${width}px`;
|
||||||
|
}
|
||||||
|
if (Math.round(rect.height) != height) {
|
||||||
|
windowElement.style.height = `${height}px`;
|
||||||
|
}
|
||||||
|
const clampedPosition = __privateMethod(this, _WindowFilter_instances, clampWindowPosition_fn).call(this, windowElement, rect.left, rect.top);
|
||||||
|
windowElement.style.left = "0px";
|
||||||
|
windowElement.style.top = "0px";
|
||||||
|
windowElement.style.right = "";
|
||||||
|
windowElement.style.transform = `translate(${clampedPosition.x}px, ${clampedPosition.y}px)`;
|
||||||
|
windowState.x = clampedPosition.x;
|
||||||
|
windowState.y = clampedPosition.y;
|
||||||
|
windowState.width = width;
|
||||||
|
windowState.height = height;
|
||||||
|
void this.settingsManager?.saveUserStorageNow();
|
||||||
|
};
|
||||||
|
/** Debounces persisting the current window size and position.
|
||||||
|
* @param {HTMLElement} windowElement
|
||||||
|
* @param {number} [delay=150]
|
||||||
|
* @since 0.92.0
|
||||||
|
*/
|
||||||
|
scheduleWindowStateSave_fn = function(windowElement, delay = 150) {
|
||||||
|
if (this.windowSaveTimeout) {
|
||||||
|
clearTimeout(this.windowSaveTimeout);
|
||||||
|
}
|
||||||
|
this.windowSaveTimeout = setTimeout(() => {
|
||||||
|
this.windowSaveTimeout = null;
|
||||||
|
__privateMethod(this, _WindowFilter_instances, saveWindowState_fn).call(this, windowElement);
|
||||||
|
}, delay);
|
||||||
|
};
|
||||||
|
/** Enables persistence and resize handling for the windowed filter.
|
||||||
|
* @since 0.92.0
|
||||||
|
*/
|
||||||
|
initializeWindowedPersistence_fn = function() {
|
||||||
|
const windowElement = document.querySelector(`#${this.windowID}.bm-window`);
|
||||||
|
if (!windowElement) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
__privateMethod(this, _WindowFilter_instances, cleanupWindowPersistence_fn).call(this);
|
||||||
|
__privateMethod(this, _WindowFilter_instances, restoreWindowState_fn).call(this, windowElement);
|
||||||
|
this.handleDrag(`#${this.windowID}.bm-window`, `#${this.windowID} .bm-dragbar`, {
|
||||||
|
onEnd: ({ element }) => __privateMethod(this, _WindowFilter_instances, saveWindowState_fn).call(this, element)
|
||||||
|
});
|
||||||
|
this.handleResize(`#${this.windowID}.bm-window`, `#${this.windowID} .bm-resize-corner`, {
|
||||||
|
minWidth: this.windowMinWidth,
|
||||||
|
minHeight: this.windowMinHeight,
|
||||||
|
maxWidth: Math.min(this.windowMaxWidth, window.innerWidth - 16),
|
||||||
|
maxHeight: Math.min(this.windowMaxHeight, window.innerHeight - 16),
|
||||||
|
onEnd: ({ element }) => __privateMethod(this, _WindowFilter_instances, saveWindowState_fn).call(this, element)
|
||||||
|
});
|
||||||
|
if (typeof ResizeObserver == "function") {
|
||||||
|
this.windowResizeObserver = new ResizeObserver(() => __privateMethod(this, _WindowFilter_instances, scheduleWindowStateSave_fn).call(this, windowElement));
|
||||||
|
this.windowResizeObserver.observe(windowElement);
|
||||||
|
}
|
||||||
|
this.windowViewportResizeHandler = () => __privateMethod(this, _WindowFilter_instances, scheduleWindowStateSave_fn).call(this, windowElement, 0);
|
||||||
|
window.addEventListener("resize", this.windowViewportResizeHandler);
|
||||||
|
};
|
||||||
/** Creates the color list container.
|
/** Creates the color list container.
|
||||||
* @param {HTMLElement} parentElement - Parent element to add the color list to as a child
|
* @param {HTMLElement} parentElement - Parent element to add the color list to as a child
|
||||||
* @since 0.88.222
|
* @since 0.88.222
|
||||||
|
|
@ -2977,7 +3335,7 @@ Version: ${this.version}`, "readOnly": true }).buildElement().buildElement().add
|
||||||
*/
|
*/
|
||||||
buildWindowFilter_fn = function() {
|
buildWindowFilter_fn = function() {
|
||||||
const windowFilter = new WindowFilter(this);
|
const windowFilter = new WindowFilter(this);
|
||||||
windowFilter.buildWindow();
|
windowFilter.buildPreferredWindow();
|
||||||
};
|
};
|
||||||
coordinateInputPaste_fn = async function(instance, input, event) {
|
coordinateInputPaste_fn = async function(instance, input, event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
@ -4014,3 +4372,5 @@ Time Since Blink: ${String(Math.floor(elapsed / 6e4)).padStart(2, "0")}:${String
|
||||||
observer.observe(document.body, { childList: true, subtree: true });
|
observer.observe(document.body, { childList: true, subtree: true });
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
// Build Hash: f5ff285a601e
|
||||||
|
|
|
||||||
6
dist/BlueMarble-Standalone.user.js
vendored
6
dist/BlueMarble-Standalone.user.js
vendored
File diff suppressed because one or more lines are too long
4
dist/BlueMarble.user.css
vendored
4
dist/BlueMarble.user.css
vendored
File diff suppressed because one or more lines are too long
6
dist/BlueMarble.user.js
vendored
6
dist/BlueMarble.user.js
vendored
File diff suppressed because one or more lines are too long
8
package-lock.json
generated
8
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "wplace-bluemarble",
|
"name": "wplace-bluemarble",
|
||||||
"version": "0.91.116",
|
"version": "0.92.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "wplace-bluemarble",
|
"name": "wplace-bluemarble",
|
||||||
"version": "0.91.116",
|
"version": "0.92.1",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
"jsdoc": "^4.0.5",
|
"jsdoc": "^4.0.5",
|
||||||
|
|
@ -37,6 +37,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz",
|
||||||
"integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==",
|
"integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/types": "^7.28.0"
|
"@babel/types": "^7.28.0"
|
||||||
},
|
},
|
||||||
|
|
@ -52,6 +53,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz",
|
||||||
"integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
|
"integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-string-parser": "^7.27.1",
|
"@babel/helper-string-parser": "^7.27.1",
|
||||||
"@babel/helper-validator-identifier": "^7.27.1"
|
"@babel/helper-validator-identifier": "^7.27.1"
|
||||||
|
|
@ -544,7 +546,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz",
|
||||||
"integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==",
|
"integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/linkify-it": "^5",
|
"@types/linkify-it": "^5",
|
||||||
"@types/mdurl": "^2"
|
"@types/mdurl": "^2"
|
||||||
|
|
@ -741,7 +742,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
|
||||||
"integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
|
"integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"argparse": "^2.0.1",
|
"argparse": "^2.0.1",
|
||||||
"entities": "^4.4.0",
|
"entities": "^4.4.0",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "wplace-bluemarble",
|
"name": "wplace-bluemarble",
|
||||||
"version": "0.92.0",
|
"version": "0.92.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"homepage": "https://bluemarble.lol/",
|
"homepage": "https://bluemarble.lol/",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
// @name Blue Marble
|
// @name Blue Marble
|
||||||
// @name:en Blue Marble
|
// @name:en Blue Marble
|
||||||
// @namespace https://github.com/SwingTheVine/
|
// @namespace https://github.com/SwingTheVine/
|
||||||
// @version 0.92.0
|
// @version 0.92.1
|
||||||
// @description A userscript to enhance the user experience on Wplace.live. This includes, but is not limited to: uploading images to display locally on a canvas, adding a button to move the Wplace color palette menu, and other QoL features.
|
// @description A userscript to enhance the user experience on Wplace.live. This includes, but is not limited to: uploading images to display locally on a canvas, adding a button to move the Wplace color palette menu, and other QoL features.
|
||||||
// @description:en A userscript to enhance the user experience on Wplace.live. This includes, but is not limited to: uploading images to display locally on a canvas, adding a button to move the Wplace color palette menu, and other QoL features.
|
// @description:en A userscript to enhance the user experience on Wplace.live. This includes, but is not limited to: uploading images to display locally on a canvas, adding a button to move the Wplace color palette menu, and other QoL features.
|
||||||
// @author SwingTheVine
|
// @author SwingTheVine
|
||||||
|
|
|
||||||
140
src/Overlay.js
140
src/Overlay.js
|
|
@ -1278,11 +1278,12 @@ export default class Overlay {
|
||||||
* @param {string} iMoveThingsSelector - The drag handle element
|
* @param {string} iMoveThingsSelector - The drag handle element
|
||||||
* @since 0.8.2
|
* @since 0.8.2
|
||||||
*/
|
*/
|
||||||
handleDrag(moveMeSelector, iMoveThingsSelector) {
|
handleDrag(moveMeSelector, iMoveThingsSelector, options = {}) {
|
||||||
|
|
||||||
// Retrieves the elements
|
// Retrieves the elements
|
||||||
const moveMe = document.querySelector(moveMeSelector);
|
const moveMe = document.querySelector(moveMeSelector);
|
||||||
const iMoveThings = document.querySelector(iMoveThingsSelector);
|
const iMoveThings = document.querySelector(iMoveThingsSelector);
|
||||||
|
const onEnd = options?.onEnd ?? (() => {});
|
||||||
|
|
||||||
// What to do when one of the two elements are not found
|
// What to do when one of the two elements are not found
|
||||||
if (!moveMe || !iMoveThings) {
|
if (!moveMe || !iMoveThings) {
|
||||||
|
|
@ -1375,6 +1376,14 @@ export default class Overlay {
|
||||||
document.removeEventListener('mouseup', endDrag);
|
document.removeEventListener('mouseup', endDrag);
|
||||||
document.removeEventListener('touchend', endDrag);
|
document.removeEventListener('touchend', endDrag);
|
||||||
document.removeEventListener('touchcancel', endDrag);
|
document.removeEventListener('touchcancel', endDrag);
|
||||||
|
|
||||||
|
onEnd({
|
||||||
|
element: moveMe,
|
||||||
|
x: currentX,
|
||||||
|
y: currentY
|
||||||
|
});
|
||||||
|
|
||||||
|
initialRect = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mouse move
|
// Mouse move
|
||||||
|
|
@ -1411,6 +1420,135 @@ export default class Overlay {
|
||||||
}, { passive: false });
|
}, { passive: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Handles resizing of an overlay window from a resize handle.
|
||||||
|
* @param {string} resizeMeSelector - The element to resize
|
||||||
|
* @param {string} iResizeThingsSelector - The resize handle element
|
||||||
|
* @param {{onEnd?: function({element: HTMLElement, width: number, height: number}): void, minWidth?: number, minHeight?: number, maxWidth?: number, maxHeight?: number}} [options={}]
|
||||||
|
* @since 0.92.0
|
||||||
|
*/
|
||||||
|
handleResize(resizeMeSelector, iResizeThingsSelector, options = {}) {
|
||||||
|
|
||||||
|
const resizeMe = document.querySelector(resizeMeSelector);
|
||||||
|
const iResizeThings = document.querySelector(iResizeThingsSelector);
|
||||||
|
const onEnd = options?.onEnd ?? (() => {});
|
||||||
|
|
||||||
|
if (!resizeMe || !iResizeThings) {
|
||||||
|
this.handleDisplayError(`Can not resize! ${!resizeMe ? 'resizeMe' : ''} ${!resizeMe && !iResizeThings ? 'and ' : ''}${!iResizeThings ? 'iResizeThings ' : ''}was not found!`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let isResizing = false;
|
||||||
|
let startX = 0;
|
||||||
|
let startY = 0;
|
||||||
|
let startWidth = 0;
|
||||||
|
let startHeight = 0;
|
||||||
|
let currentWidth = 0;
|
||||||
|
let currentHeight = 0;
|
||||||
|
let targetWidth = 0;
|
||||||
|
let targetHeight = 0;
|
||||||
|
let animationFrame = null;
|
||||||
|
|
||||||
|
const getMaximumWidth = () => Number.isFinite(options?.maxWidth) ? options.maxWidth : window.innerWidth - 16;
|
||||||
|
const getMaximumHeight = () => Number.isFinite(options?.maxHeight) ? options.maxHeight : window.innerHeight - 16;
|
||||||
|
const minimumWidth = Number.isFinite(options?.minWidth) ? options.minWidth : 200;
|
||||||
|
const minimumHeight = Number.isFinite(options?.minHeight) ? options.minHeight : 160;
|
||||||
|
|
||||||
|
const clamp = (value, minimum, maximum) => Math.min(Math.max(value, minimum), Math.max(minimum, maximum));
|
||||||
|
|
||||||
|
const updateSize = () => {
|
||||||
|
if (isResizing) {
|
||||||
|
const deltaWidth = Math.abs(currentWidth - targetWidth);
|
||||||
|
const deltaHeight = Math.abs(currentHeight - targetHeight);
|
||||||
|
|
||||||
|
if (deltaWidth > 0.5 || deltaHeight > 0.5) {
|
||||||
|
currentWidth = targetWidth;
|
||||||
|
currentHeight = targetHeight;
|
||||||
|
resizeMe.style.width = `${currentWidth}px`;
|
||||||
|
resizeMe.style.height = `${currentHeight}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
animationFrame = requestAnimationFrame(updateSize);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const startResize = (clientX, clientY) => {
|
||||||
|
isResizing = true;
|
||||||
|
startX = clientX;
|
||||||
|
startY = clientY;
|
||||||
|
startWidth = resizeMe.offsetWidth;
|
||||||
|
startHeight = resizeMe.offsetHeight;
|
||||||
|
currentWidth = startWidth;
|
||||||
|
currentHeight = startHeight;
|
||||||
|
targetWidth = startWidth;
|
||||||
|
targetHeight = startHeight;
|
||||||
|
|
||||||
|
document.body.style.userSelect = 'none';
|
||||||
|
iResizeThings.classList.add('bm-resizing');
|
||||||
|
|
||||||
|
document.addEventListener('mousemove', onMouseMove);
|
||||||
|
document.addEventListener('touchmove', onTouchMove, { passive: false });
|
||||||
|
document.addEventListener('mouseup', endResize);
|
||||||
|
document.addEventListener('touchend', endResize);
|
||||||
|
document.addEventListener('touchcancel', endResize);
|
||||||
|
|
||||||
|
if (animationFrame) {
|
||||||
|
cancelAnimationFrame(animationFrame);
|
||||||
|
}
|
||||||
|
updateSize();
|
||||||
|
};
|
||||||
|
|
||||||
|
const endResize = () => {
|
||||||
|
isResizing = false;
|
||||||
|
if (animationFrame) {
|
||||||
|
cancelAnimationFrame(animationFrame);
|
||||||
|
animationFrame = null;
|
||||||
|
}
|
||||||
|
document.body.style.userSelect = '';
|
||||||
|
iResizeThings.classList.remove('bm-resizing');
|
||||||
|
|
||||||
|
document.removeEventListener('mousemove', onMouseMove);
|
||||||
|
document.removeEventListener('touchmove', onTouchMove);
|
||||||
|
document.removeEventListener('mouseup', endResize);
|
||||||
|
document.removeEventListener('touchend', endResize);
|
||||||
|
document.removeEventListener('touchcancel', endResize);
|
||||||
|
|
||||||
|
onEnd({
|
||||||
|
element: resizeMe,
|
||||||
|
width: currentWidth,
|
||||||
|
height: currentHeight
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMouseMove = event => {
|
||||||
|
if (!isResizing) {return;}
|
||||||
|
targetWidth = clamp(startWidth + (event.clientX - startX), minimumWidth, getMaximumWidth());
|
||||||
|
targetHeight = clamp(startHeight + (event.clientY - startY), minimumHeight, getMaximumHeight());
|
||||||
|
};
|
||||||
|
|
||||||
|
const onTouchMove = event => {
|
||||||
|
if (!isResizing) {return;}
|
||||||
|
const touch = event?.touches?.[0];
|
||||||
|
if (!touch) {return;}
|
||||||
|
targetWidth = clamp(startWidth + (touch.clientX - startX), minimumWidth, getMaximumWidth());
|
||||||
|
targetHeight = clamp(startHeight + (touch.clientY - startY), minimumHeight, getMaximumHeight());
|
||||||
|
event.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
iResizeThings.addEventListener('mousedown', event => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
startResize(event.clientX, event.clientY);
|
||||||
|
});
|
||||||
|
|
||||||
|
iResizeThings.addEventListener('touchstart', event => {
|
||||||
|
const touch = event?.touches?.[0];
|
||||||
|
if (!touch) {return;}
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
startResize(touch.clientX, touch.clientY);
|
||||||
|
}, { passive: false });
|
||||||
|
}
|
||||||
|
|
||||||
/** Handles status display.
|
/** Handles status display.
|
||||||
* This will output plain text into the output Status box.
|
* This will output plain text into the output Status box.
|
||||||
* Additionally, this will output an info message to the console.
|
* Additionally, this will output an info message to the console.
|
||||||
|
|
|
||||||
|
|
@ -98,33 +98,117 @@
|
||||||
|
|
||||||
/* WINDOWED MODE */
|
/* WINDOWED MODE */
|
||||||
|
|
||||||
|
/* Resizable filter window in windowed mode */
|
||||||
|
#bm-window-filter.bm-windowed {
|
||||||
|
--bm-scrollable-max-height: 100%;
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto minmax(0, 1fr);
|
||||||
|
width: 300px;
|
||||||
|
height: min(70vh, 32rem);
|
||||||
|
min-width: 260px;
|
||||||
|
min-height: 220px;
|
||||||
|
max-width: min(1000px, calc(100vw - 16px)) !important;
|
||||||
|
max-height: min(1400px, calc(100vh - 16px)) !important;
|
||||||
|
overflow: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: fixed;
|
||||||
|
transition: transform 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Keep the content area flexible inside the resizable window */
|
||||||
|
#bm-window-filter.bm-windowed .bm-window-content {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto auto auto minmax(0, 1fr);
|
||||||
|
grid-row: 2;
|
||||||
|
min-height: 0;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
/* Filter flex in windowed mode */
|
/* Filter flex in windowed mode */
|
||||||
#bm-window-filter.bm-windowed #bm-filter-flex {
|
#bm-window-filter.bm-windowed #bm-filter-flex {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
gap: 0.25em;
|
gap: 0.25em;
|
||||||
|
width: 100%;
|
||||||
|
align-self: stretch;
|
||||||
|
min-width: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Filter color in windowed mode */
|
/* Filter color in windowed mode */
|
||||||
#bm-window-filter.bm-windowed .bm-filter-color {
|
#bm-window-filter.bm-windowed .bm-filter-color {
|
||||||
width: auto;
|
width: 100%;
|
||||||
|
max-width: none;
|
||||||
|
align-self: stretch;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bm-window-filter.bm-windowed .bm-filter-color > .bm-flex-between {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Let the scroll area grow and shrink with the resizable window */
|
||||||
|
#bm-window-filter.bm-windowed .bm-container.bm-scrollable {
|
||||||
|
display: block;
|
||||||
|
grid-row: 4;
|
||||||
|
min-height: 0;
|
||||||
|
min-width: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
max-height: 100% !important;
|
||||||
|
overflow: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Visible resize handle in the bottom-right corner */
|
||||||
|
#bm-window-filter.bm-windowed .bm-resize-corner {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
display: block;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
z-index: 5;
|
||||||
|
cursor: nwse-resize;
|
||||||
|
pointer-events: auto;
|
||||||
|
opacity: 1;
|
||||||
|
touch-action: none;
|
||||||
|
user-select: none;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bm-window-filter.bm-windowed .bm-resize-corner:hover,
|
||||||
|
#bm-window-filter.bm-windowed .bm-resize-corner.bm-resizing {
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Filter window container for RGB color display in windowed mode */
|
/* Filter window container for RGB color display in windowed mode */
|
||||||
#bm-window-filter.bm-windowed .bm-filter-container-rgb {
|
#bm-window-filter.bm-windowed .bm-filter-container-rgb {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1 1 auto;
|
||||||
gap: 0.5ch;
|
gap: 0.5ch;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0.1em 0.5ch;
|
padding: 0.1em 0.5ch;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 1em;
|
border-radius: 1em;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Filter window hide color button */
|
/* Filter window hide color button */
|
||||||
#bm-window-filter.bm-windowed .bm-filter-container-rgb button {
|
#bm-window-filter.bm-windowed .bm-filter-container-rgb button {
|
||||||
padding: 0.5em 0.25ch;
|
padding: 0.5em 0.25ch;
|
||||||
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Filter window hide color button SVG in windowed mode */
|
/* Filter window hide color button SVG in windowed mode */
|
||||||
|
|
@ -135,6 +219,10 @@
|
||||||
/* Filter window header 2 in windowed mode */
|
/* Filter window header 2 in windowed mode */
|
||||||
#bm-window-filter.bm-windowed .bm-filter-color h2 {
|
#bm-window-filter.bm-windowed .bm-filter-color h2 {
|
||||||
font-size: 0.75em;
|
font-size: 0.75em;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Filter window dragbar text area in windowed mode */
|
/* Filter window dragbar text area in windowed mode */
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,16 @@ export default class WindowFilter extends Overlay {
|
||||||
this.windowID = 'bm-window-filter'; // The ID attribute for this window
|
this.windowID = 'bm-window-filter'; // The ID attribute for this window
|
||||||
this.colorListID = 'bm-filter-flex'; // The ID attribute for the color list
|
this.colorListID = 'bm-filter-flex'; // The ID attribute for the color list
|
||||||
this.windowParent = document.body; // The parent of the window DOM tree
|
this.windowParent = document.body; // The parent of the window DOM tree
|
||||||
|
this.settingsManager = executor.settingsManager ?? null; // Settings manager from the executor
|
||||||
|
this.windowModeFlag = 'ftr-oWin'; // User setting flag for opening the filter in windowed mode
|
||||||
|
this.windowStateKey = 'windowFilter'; // User setting key for the persisted window state
|
||||||
|
this.windowResizeObserver = null; // Resize observer for the windowed mode
|
||||||
|
this.windowViewportResizeHandler = null; // Resize handler for viewport changes
|
||||||
|
this.windowSaveTimeout = null; // Debounce timer for resize persistence
|
||||||
|
this.windowMinWidth = 260; // Minimum width for the windowed filter
|
||||||
|
this.windowMinHeight = 220; // Minimum height for the windowed filter
|
||||||
|
this.windowMaxWidth = 1000; // Maximum width for the windowed filter
|
||||||
|
this.windowMaxHeight = 1400; // Maximum height for the windowed filter
|
||||||
|
|
||||||
/** The templateManager instance currently being used. @type {TemplateManager} */
|
/** The templateManager instance currently being used. @type {TemplateManager} */
|
||||||
this.templateManager = executor.apiManager?.templateManager;
|
this.templateManager = executor.apiManager?.templateManager;
|
||||||
|
|
@ -51,6 +61,17 @@ export default class WindowFilter extends Overlay {
|
||||||
this.showUnused = false; // Were unused colors shown the last time the user sorted the color list?
|
this.showUnused = false; // Were unused colors shown the last time the user sorted the color list?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Builds the preferred filter window mode for the user.
|
||||||
|
* @since 0.92.0
|
||||||
|
*/
|
||||||
|
buildPreferredWindow() {
|
||||||
|
if (this.settingsManager?.userSettings?.flags?.includes(this.windowModeFlag)) {
|
||||||
|
this.buildWindowed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.buildWindow();
|
||||||
|
}
|
||||||
|
|
||||||
/** Spawns a Color Filter window.
|
/** Spawns a Color Filter window.
|
||||||
* If another color filter window already exists, we DON'T spawn another!
|
* If another color filter window already exists, we DON'T spawn another!
|
||||||
* Parent/child relationships in the DOM structure below are indicated by indentation.
|
* Parent/child relationships in the DOM structure below are indicated by indentation.
|
||||||
|
|
@ -60,7 +81,7 @@ export default class WindowFilter extends Overlay {
|
||||||
|
|
||||||
// If a color filter wizard window already exists, close it
|
// If a color filter wizard window already exists, close it
|
||||||
if (document.querySelector(`#${this.windowID}`)) {
|
if (document.querySelector(`#${this.windowID}`)) {
|
||||||
document.querySelector(`#${this.windowID}`).remove();
|
this.#closeWindow();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,13 +100,14 @@ export default class WindowFilter extends Overlay {
|
||||||
.addDiv({'class': 'bm-flex-center'})
|
.addDiv({'class': 'bm-flex-center'})
|
||||||
.addButton({'class': 'bm-button-circle', 'textContent': '🗗', 'aria-label': 'Switch to windowed mode for "Color Filter"'}, (instance, button) => {
|
.addButton({'class': 'bm-button-circle', 'textContent': '🗗', 'aria-label': 'Switch to windowed mode for "Color Filter"'}, (instance, button) => {
|
||||||
button.onclick = () => {
|
button.onclick = () => {
|
||||||
document.querySelector(`#${this.windowID}`)?.remove();
|
this.#setWindowModePreference(true);
|
||||||
|
this.#closeWindow();
|
||||||
this.buildWindowed();
|
this.buildWindowed();
|
||||||
};
|
};
|
||||||
button.ontouchend = () => {button.click();}; // Needed only to negate weird interaction with dragbar
|
button.ontouchend = () => {button.click();}; // Needed only to negate weird interaction with dragbar
|
||||||
}).buildElement()
|
}).buildElement()
|
||||||
.addButton({'class': 'bm-button-circle', 'textContent': '✖', 'aria-label': 'Close window "Color Filter"'}, (instance, button) => {
|
.addButton({'class': 'bm-button-circle', 'textContent': '✖', 'aria-label': 'Close window "Color Filter"'}, (instance, button) => {
|
||||||
button.onclick = () => {document.querySelector(`#${this.windowID}`)?.remove();};
|
button.onclick = () => this.#closeWindow();
|
||||||
button.ontouchend = () => {button.click();}; // Needed only to negate weird interaction with dragbar
|
button.ontouchend = () => {button.click();}; // Needed only to negate weird interaction with dragbar
|
||||||
}).buildElement()
|
}).buildElement()
|
||||||
.buildElement()
|
.buildElement()
|
||||||
|
|
@ -202,12 +224,16 @@ export default class WindowFilter extends Overlay {
|
||||||
|
|
||||||
// If a color filter wizard window already exists, close it
|
// If a color filter wizard window already exists, close it
|
||||||
if (document.querySelector(`#${this.windowID}`)) {
|
if (document.querySelector(`#${this.windowID}`)) {
|
||||||
document.querySelector(`#${this.windowID}`).remove();
|
this.#closeWindow();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a new windowed color filter window
|
// Creates a new windowed color filter window
|
||||||
this.window = this.addDiv({'id': this.windowID, 'class': 'bm-window bm-windowed'})
|
this.window = this.addDiv({
|
||||||
|
'id': this.windowID,
|
||||||
|
'class': 'bm-window bm-windowed',
|
||||||
|
'style': `width: 300px; height: min(70vh, 32rem); min-width: ${this.windowMinWidth}px; min-height: ${this.windowMinHeight}px; max-width: min(${this.windowMaxWidth}px, calc(100vw - 16px)); max-height: min(${this.windowMaxHeight}px, calc(100vh - 16px));`
|
||||||
|
})
|
||||||
.addDragbar()
|
.addDragbar()
|
||||||
.addButton({'class': 'bm-button-circle', 'textContent': '▼', 'aria-label': 'Minimize window "Color Filter"', 'data-button-status': 'expanded'}, (instance, button) => {
|
.addButton({'class': 'bm-button-circle', 'textContent': '▼', 'aria-label': 'Minimize window "Color Filter"', 'data-button-status': 'expanded'}, (instance, button) => {
|
||||||
button.onclick = () => {
|
button.onclick = () => {
|
||||||
|
|
@ -226,13 +252,14 @@ export default class WindowFilter extends Overlay {
|
||||||
.addDiv({'class': 'bm-flex-center'})
|
.addDiv({'class': 'bm-flex-center'})
|
||||||
.addButton({'class': 'bm-button-circle', 'textContent': '🗖', 'aria-label': 'Switch to fullscreen mode for "Color Filter"'}, (instance, button) => {
|
.addButton({'class': 'bm-button-circle', 'textContent': '🗖', 'aria-label': 'Switch to fullscreen mode for "Color Filter"'}, (instance, button) => {
|
||||||
button.onclick = () => {
|
button.onclick = () => {
|
||||||
document.querySelector(`#${this.windowID}`)?.remove();
|
this.#setWindowModePreference(false);
|
||||||
|
this.#closeWindow();
|
||||||
this.buildWindow();
|
this.buildWindow();
|
||||||
};
|
};
|
||||||
button.ontouchend = () => {button.click();}; // Needed only to negate weird interaction with dragbar
|
button.ontouchend = () => {button.click();}; // Needed only to negate weird interaction with dragbar
|
||||||
}).buildElement()
|
}).buildElement()
|
||||||
.addButton({'class': 'bm-button-circle', 'textContent': '✖', 'aria-label': 'Close window "Color Filter"'}, (instance, button) => {
|
.addButton({'class': 'bm-button-circle', 'textContent': '✖', 'aria-label': 'Close window "Color Filter"'}, (instance, button) => {
|
||||||
button.onclick = () => {document.querySelector(`#${this.windowID}`)?.remove();};
|
button.onclick = () => this.#closeWindow();
|
||||||
button.ontouchend = () => {button.click();}; // Needed only to negate weird interaction with dragbar
|
button.ontouchend = () => {button.click();}; // Needed only to negate weird interaction with dragbar
|
||||||
}).buildElement()
|
}).buildElement()
|
||||||
.buildElement()
|
.buildElement()
|
||||||
|
|
@ -261,10 +288,17 @@ export default class WindowFilter extends Overlay {
|
||||||
// Color list will appear here
|
// Color list will appear here
|
||||||
.buildElement()
|
.buildElement()
|
||||||
.buildElement()
|
.buildElement()
|
||||||
|
.addDiv({
|
||||||
|
'class': 'bm-resize-corner',
|
||||||
|
'title': 'Resize Color Filter window',
|
||||||
|
'aria-label': 'Resize Color Filter window',
|
||||||
|
'role': 'presentation',
|
||||||
|
'textContent': '◢',
|
||||||
|
'style': 'position: absolute; right: 0; bottom: 0; width: 28px; height: 28px; display: flex; align-items: flex-end; justify-content: flex-end; padding-right: 4px; padding-bottom: 4px; box-sizing: border-box; z-index: 5; cursor: nwse-resize; pointer-events: auto; touch-action: none; user-select: none; font-size: 8px; line-height: 1; color: rgba(255,255,255,0.95); background: transparent; border: none; box-shadow: none;'
|
||||||
|
}).buildElement()
|
||||||
.buildElement().buildOverlay(this.windowParent);
|
.buildElement().buildOverlay(this.windowParent);
|
||||||
|
|
||||||
// Creates dragging capability on the drag bar for dragging the window
|
this.#initializeWindowedPersistence();
|
||||||
this.handleDrag(`#${this.windowID}.bm-window`, `#${this.windowID} .bm-dragbar`);
|
|
||||||
|
|
||||||
// Obtains the scrollable container to put the color filter in
|
// Obtains the scrollable container to put the color filter in
|
||||||
const scrollableContainer = document.querySelector(`#${this.windowID} .bm-container.bm-scrollable`);
|
const scrollableContainer = document.querySelector(`#${this.windowID} .bm-container.bm-scrollable`);
|
||||||
|
|
@ -274,6 +308,206 @@ export default class WindowFilter extends Overlay {
|
||||||
this.#sortColorList(this.sortPrimary, this.sortSecondary, this.showUnused);
|
this.#sortColorList(this.sortPrimary, this.sortSecondary, this.showUnused);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Retrieves the persisted window state object.
|
||||||
|
* @returns {Object | null}
|
||||||
|
* @since 0.92.0
|
||||||
|
*/
|
||||||
|
#getWindowState() {
|
||||||
|
if (!this.settingsManager) {return null;}
|
||||||
|
this.settingsManager.userSettings[this.windowStateKey] ??= {};
|
||||||
|
return this.settingsManager.userSettings[this.windowStateKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Updates the preferred window mode setting.
|
||||||
|
* @param {boolean} shouldBeWindowed
|
||||||
|
* @since 0.92.0
|
||||||
|
*/
|
||||||
|
#setWindowModePreference(shouldBeWindowed) {
|
||||||
|
if (!this.settingsManager) {return;}
|
||||||
|
this.settingsManager.toggleFlag(this.windowModeFlag, shouldBeWindowed);
|
||||||
|
void this.settingsManager.saveUserStorageNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Immediately closes the filter window and cleans up persistence observers.
|
||||||
|
* @since 0.92.0
|
||||||
|
*/
|
||||||
|
#closeWindow() {
|
||||||
|
const windowElement = document.querySelector(`#${this.windowID}`);
|
||||||
|
if (windowElement?.classList.contains('bm-windowed')) {
|
||||||
|
this.#saveWindowState(windowElement);
|
||||||
|
}
|
||||||
|
this.#cleanupWindowPersistence();
|
||||||
|
windowElement?.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Disconnects live observers used for window persistence.
|
||||||
|
* @since 0.92.0
|
||||||
|
*/
|
||||||
|
#cleanupWindowPersistence() {
|
||||||
|
if (this.windowResizeObserver) {
|
||||||
|
this.windowResizeObserver.disconnect();
|
||||||
|
this.windowResizeObserver = null;
|
||||||
|
}
|
||||||
|
if (this.windowViewportResizeHandler) {
|
||||||
|
window.removeEventListener('resize', this.windowViewportResizeHandler);
|
||||||
|
this.windowViewportResizeHandler = null;
|
||||||
|
}
|
||||||
|
if (this.windowSaveTimeout) {
|
||||||
|
clearTimeout(this.windowSaveTimeout);
|
||||||
|
this.windowSaveTimeout = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a clamped dimension value for the window.
|
||||||
|
* @param {number} size - The size in pixels
|
||||||
|
* @param {number} minimum - Minimum allowed size
|
||||||
|
* @param {number} maximum - Maximum allowed size
|
||||||
|
* @returns {number}
|
||||||
|
* @since 0.92.0
|
||||||
|
*/
|
||||||
|
#clampWindowDimension(size, minimum, maximum) {
|
||||||
|
const resolvedMaximum = Math.max(minimum, maximum);
|
||||||
|
return Math.min(Math.max(Math.round(Number(size) || minimum), minimum), resolvedMaximum);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a viewport-safe position for the window.
|
||||||
|
* @param {HTMLElement} windowElement
|
||||||
|
* @param {number} x
|
||||||
|
* @param {number} y
|
||||||
|
* @returns {{x: number, y: number}}
|
||||||
|
* @since 0.92.0
|
||||||
|
*/
|
||||||
|
#clampWindowPosition(windowElement, x, y) {
|
||||||
|
const margin = 8;
|
||||||
|
const maxX = Math.max(margin, window.innerWidth - windowElement.offsetWidth - margin);
|
||||||
|
const maxY = Math.max(margin, window.innerHeight - windowElement.offsetHeight - margin);
|
||||||
|
return {
|
||||||
|
x: Math.min(Math.max(Math.round(Number(x) || margin), margin), maxX),
|
||||||
|
y: Math.min(Math.max(Math.round(Number(y) || margin), margin), maxY)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Applies the persisted size and position to the windowed filter.
|
||||||
|
* @param {HTMLElement} windowElement
|
||||||
|
* @since 0.92.0
|
||||||
|
*/
|
||||||
|
#restoreWindowState(windowElement) {
|
||||||
|
const windowState = this.#getWindowState();
|
||||||
|
if (!windowState || !windowElement) {return;}
|
||||||
|
|
||||||
|
const width = Number(windowState.width);
|
||||||
|
const height = Number(windowState.height);
|
||||||
|
const hasWidth = Number.isFinite(width);
|
||||||
|
const hasHeight = Number.isFinite(height);
|
||||||
|
|
||||||
|
if (hasWidth) {
|
||||||
|
windowState.width = this.#clampWindowDimension(width, this.windowMinWidth, Math.min(this.windowMaxWidth, window.innerWidth - 16));
|
||||||
|
windowElement.style.width = `${windowState.width}px`;
|
||||||
|
}
|
||||||
|
if (hasHeight) {
|
||||||
|
windowState.height = this.#clampWindowDimension(height, this.windowMinHeight, Math.min(this.windowMaxHeight, window.innerHeight - 16));
|
||||||
|
windowElement.style.height = `${windowState.height}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
if (!windowElement.isConnected) {return;}
|
||||||
|
|
||||||
|
const x = Number(windowState.x);
|
||||||
|
const y = Number(windowState.y);
|
||||||
|
if (!Number.isFinite(x) || !Number.isFinite(y)) {return;}
|
||||||
|
|
||||||
|
const clampedPosition = this.#clampWindowPosition(windowElement, x, y);
|
||||||
|
windowElement.style.left = '0px';
|
||||||
|
windowElement.style.top = '0px';
|
||||||
|
windowElement.style.right = '';
|
||||||
|
windowElement.style.transform = `translate(${clampedPosition.x}px, ${clampedPosition.y}px)`;
|
||||||
|
|
||||||
|
if ((clampedPosition.x != x) || (clampedPosition.y != y)) {
|
||||||
|
windowState.x = clampedPosition.x;
|
||||||
|
windowState.y = clampedPosition.y;
|
||||||
|
void this.settingsManager?.saveUserStorageNow();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Saves the current size and position of the windowed filter.
|
||||||
|
* @param {HTMLElement} windowElement
|
||||||
|
* @since 0.92.0
|
||||||
|
*/
|
||||||
|
#saveWindowState(windowElement) {
|
||||||
|
const windowState = this.#getWindowState();
|
||||||
|
if (!windowState || !windowElement?.isConnected || !windowElement.classList.contains('bm-windowed')) {return;}
|
||||||
|
|
||||||
|
const rect = windowElement.getBoundingClientRect();
|
||||||
|
const width = this.#clampWindowDimension(rect.width, this.windowMinWidth, Math.min(this.windowMaxWidth, window.innerWidth - 16));
|
||||||
|
const height = this.#clampWindowDimension(rect.height, this.windowMinHeight, Math.min(this.windowMaxHeight, window.innerHeight - 16));
|
||||||
|
|
||||||
|
if (Math.round(rect.width) != width) {
|
||||||
|
windowElement.style.width = `${width}px`;
|
||||||
|
}
|
||||||
|
if (Math.round(rect.height) != height) {
|
||||||
|
windowElement.style.height = `${height}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const clampedPosition = this.#clampWindowPosition(windowElement, rect.left, rect.top);
|
||||||
|
windowElement.style.left = '0px';
|
||||||
|
windowElement.style.top = '0px';
|
||||||
|
windowElement.style.right = '';
|
||||||
|
windowElement.style.transform = `translate(${clampedPosition.x}px, ${clampedPosition.y}px)`;
|
||||||
|
|
||||||
|
windowState.x = clampedPosition.x;
|
||||||
|
windowState.y = clampedPosition.y;
|
||||||
|
windowState.width = width;
|
||||||
|
windowState.height = height;
|
||||||
|
|
||||||
|
void this.settingsManager?.saveUserStorageNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Debounces persisting the current window size and position.
|
||||||
|
* @param {HTMLElement} windowElement
|
||||||
|
* @param {number} [delay=150]
|
||||||
|
* @since 0.92.0
|
||||||
|
*/
|
||||||
|
#scheduleWindowStateSave(windowElement, delay = 150) {
|
||||||
|
if (this.windowSaveTimeout) {
|
||||||
|
clearTimeout(this.windowSaveTimeout);
|
||||||
|
}
|
||||||
|
this.windowSaveTimeout = setTimeout(() => {
|
||||||
|
this.windowSaveTimeout = null;
|
||||||
|
this.#saveWindowState(windowElement);
|
||||||
|
}, delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Enables persistence and resize handling for the windowed filter.
|
||||||
|
* @since 0.92.0
|
||||||
|
*/
|
||||||
|
#initializeWindowedPersistence() {
|
||||||
|
const windowElement = document.querySelector(`#${this.windowID}.bm-window`);
|
||||||
|
if (!windowElement) {return;}
|
||||||
|
|
||||||
|
this.#cleanupWindowPersistence();
|
||||||
|
this.#restoreWindowState(windowElement);
|
||||||
|
|
||||||
|
this.handleDrag(`#${this.windowID}.bm-window`, `#${this.windowID} .bm-dragbar`, {
|
||||||
|
onEnd: ({element}) => this.#saveWindowState(element)
|
||||||
|
});
|
||||||
|
this.handleResize(`#${this.windowID}.bm-window`, `#${this.windowID} .bm-resize-corner`, {
|
||||||
|
minWidth: this.windowMinWidth,
|
||||||
|
minHeight: this.windowMinHeight,
|
||||||
|
maxWidth: Math.min(this.windowMaxWidth, window.innerWidth - 16),
|
||||||
|
maxHeight: Math.min(this.windowMaxHeight, window.innerHeight - 16),
|
||||||
|
onEnd: ({element}) => this.#saveWindowState(element)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (typeof ResizeObserver == 'function') {
|
||||||
|
this.windowResizeObserver = new ResizeObserver(() => this.#scheduleWindowStateSave(windowElement));
|
||||||
|
this.windowResizeObserver.observe(windowElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.windowViewportResizeHandler = () => this.#scheduleWindowStateSave(windowElement, 0);
|
||||||
|
window.addEventListener('resize', this.windowViewportResizeHandler);
|
||||||
|
}
|
||||||
|
|
||||||
/** Creates the color list container.
|
/** Creates the color list container.
|
||||||
* @param {HTMLElement} parentElement - Parent element to add the color list to as a child
|
* @param {HTMLElement} parentElement - Parent element to add the color list to as a child
|
||||||
* @since 0.88.222
|
* @since 0.88.222
|
||||||
|
|
|
||||||
|
|
@ -214,7 +214,7 @@ export default class WindowMain extends Overlay {
|
||||||
*/
|
*/
|
||||||
#buildWindowFilter() {
|
#buildWindowFilter() {
|
||||||
const windowFilter = new WindowFilter(this); // Creates a new color filter window instance
|
const windowFilter = new WindowFilter(this); // Creates a new color filter window instance
|
||||||
windowFilter.buildWindow();
|
windowFilter.buildPreferredWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Handles pasting into the coordinate input boxes in the main Blue Marble window.
|
/** Handles pasting into the coordinate input boxes in the main Blue Marble window.
|
||||||
|
|
|
||||||
|
|
@ -38,11 +38,6 @@
|
||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The Blue Marble windowed windows */
|
|
||||||
.bm-window.bm-windowed {
|
|
||||||
max-width: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The drag bar */
|
/* The drag bar */
|
||||||
.bm-dragbar {
|
.bm-dragbar {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|
@ -281,6 +276,7 @@ input[type="file"] {
|
||||||
/* Window content container */
|
/* Window content container */
|
||||||
.bm-window-content {
|
.bm-window-content {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
max-height: calc(100% - 5px);
|
||||||
transition: height 300ms cubic-bezier(.4, 0, .2, 1);
|
transition: height 300ms cubic-bezier(.4, 0, .2, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -312,7 +308,7 @@ input[type="file"] {
|
||||||
|
|
||||||
/* Scrollable container */
|
/* Scrollable container */
|
||||||
.bm-window .bm-container.bm-scrollable {
|
.bm-window .bm-container.bm-scrollable {
|
||||||
max-height: calc(80vh - 150px);
|
max-height: var(--bm-scrollable-max-height, calc(80vh - 150px));
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,8 @@ import WindowSettings from "./WindowSettings";
|
||||||
* "telemetry": 1,
|
* "telemetry": 1,
|
||||||
* "flags": ["hl-noTrans", "ftr-oWin", "te-noSkip"],
|
* "flags": ["hl-noTrans", "ftr-oWin", "te-noSkip"],
|
||||||
* "highlight": [[1,0,-1],[1,-1,0],[2,1,0],[1,0,1]],
|
* "highlight": [[1,0,-1],[1,-1,0],[2,1,0],[1,0,1]],
|
||||||
* "filter": [-2,0,4,5,6,29,63]
|
* "filter": [-2,0,4,5,6,29,63],
|
||||||
|
* "windowFilter": {"x": 60, "y": 75, "width": 300, "height": 420}
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
export default class SettingsManager extends WindowSettings {
|
export default class SettingsManager extends WindowSettings {
|
||||||
|
|
@ -45,13 +46,21 @@ export default class SettingsManager extends WindowSettings {
|
||||||
* @since 0.91.39
|
* @since 0.91.39
|
||||||
*/
|
*/
|
||||||
async updateUserStorage() {
|
async updateUserStorage() {
|
||||||
|
await this.saveUserStorage();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Saves the user settings in userscript storage.
|
||||||
|
* @param {boolean} [force=false] - Should the throttle be ignored?
|
||||||
|
* @since 0.92.0
|
||||||
|
*/
|
||||||
|
async saveUserStorage(force = false) {
|
||||||
|
|
||||||
// Turns the objects into a string
|
// Turns the objects into a string
|
||||||
const userSettingsCurrent = JSON.stringify(this.userSettings);
|
const userSettingsCurrent = JSON.stringify(this.userSettings);
|
||||||
const userSettingsOld = JSON.stringify(this.userSettingsOld);
|
const userSettingsOld = JSON.stringify(this.userSettingsOld);
|
||||||
|
|
||||||
// If the user settings have changed, AND the last update to user storage was over 5 seconds ago (5sec throttle)...
|
// If the user settings have changed, AND the last update to user storage was over 5 seconds ago (5sec throttle)...
|
||||||
if ((userSettingsCurrent != userSettingsOld) && ((Date.now() - this.lastUpdateTime) > this.updateFrequency)) {
|
if ((userSettingsCurrent != userSettingsOld) && (force || ((Date.now() - this.lastUpdateTime) > this.updateFrequency))) {
|
||||||
await GM.setValue(this.userSettingsSaveLocation, userSettingsCurrent); // Updates user storage
|
await GM.setValue(this.userSettingsSaveLocation, userSettingsCurrent); // Updates user storage
|
||||||
this.userSettingsOld = structuredClone(this.userSettings); // Updates the old user settings with a duplicate of the current user settings
|
this.userSettingsOld = structuredClone(this.userSettings); // Updates the old user settings with a duplicate of the current user settings
|
||||||
this.lastUpdateTime = Date.now(); // Updates the variable that contains the last time updated
|
this.lastUpdateTime = Date.now(); // Updates the variable that contains the last time updated
|
||||||
|
|
@ -59,6 +68,13 @@ export default class SettingsManager extends WindowSettings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Immediately saves the user settings in userscript storage.
|
||||||
|
* @since 0.92.0
|
||||||
|
*/
|
||||||
|
async saveUserStorageNow() {
|
||||||
|
await this.saveUserStorage(true);
|
||||||
|
}
|
||||||
|
|
||||||
/** Toggles a boolean flag to the state that was passed in.
|
/** Toggles a boolean flag to the state that was passed in.
|
||||||
* If no state was passed in, the flag will flip to the opposite state.
|
* If no state was passed in, the flag will flip to the opposite state.
|
||||||
* The existence of the flag determines its state. If it exists, it is `true`.
|
* The existence of the flag determines its state. If it exists, it is `true`.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue