mirror of
https://github.com/SwingTheVine/Wplace-BlueMarble.git
synced 2026-05-06 07:38:56 +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
|
||||
import esbuild from 'esbuild';
|
||||
import crypto from 'crypto';
|
||||
import fs from 'fs';
|
||||
import { execSync } from 'child_process';
|
||||
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?'
|
||||
|
||||
/** 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}`);
|
||||
|
||||
// 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');
|
||||
|
||||
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(`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 */
|
||||
div:has(> confetti-piece) {
|
||||
position: absolute;
|
||||
|
|
@ -245,9 +56,6 @@ confetti-piece {
|
|||
"Arial";
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
.bm-window.bm-windowed {
|
||||
max-width: 300px;
|
||||
}
|
||||
.bm-dragbar {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
|
|
@ -423,6 +231,7 @@ input[type=file] {
|
|||
}
|
||||
.bm-window-content {
|
||||
overflow: hidden;
|
||||
max-height: calc(100% - 5px);
|
||||
transition: height 300ms cubic-bezier(.4, 0, .2, 1);
|
||||
}
|
||||
.bm-window textarea {
|
||||
|
|
@ -444,7 +253,7 @@ input[type=file] {
|
|||
margin-left: 5ch;
|
||||
}
|
||||
.bm-window .bm-container.bm-scrollable {
|
||||
max-height: calc(80vh - 150px);
|
||||
max-height: var(--bm-scrollable-max-height, calc(80vh - 150px));
|
||||
overflow: auto;
|
||||
}
|
||||
.bm-flex-between {
|
||||
|
|
@ -476,4 +285,273 @@ input[type=file] {
|
|||
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 */
|
||||
|
||||
/* 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:en Blue Marble
|
||||
// @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: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
|
||||
// @license MPL-2.0
|
||||
// @supportURL https://discord.gg/tpeBPy46hf
|
||||
// @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
|
||||
// @downloadURL https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/BlueMarble-For-GreasyFork.user.js
|
||||
// @match https://wplace.live/*
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
// @grant GM_xmlhttpRequest
|
||||
// @grant GM.download
|
||||
// @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
|
||||
// @noframes
|
||||
// ==/UserScript==
|
||||
|
|
@ -1366,9 +1366,11 @@
|
|||
* @param {string} iMoveThingsSelector - The drag handle element
|
||||
* @since 0.8.2
|
||||
*/
|
||||
handleDrag(moveMeSelector, iMoveThingsSelector) {
|
||||
handleDrag(moveMeSelector, iMoveThingsSelector, options = {}) {
|
||||
const moveMe = document.querySelector(moveMeSelector);
|
||||
const iMoveThings = document.querySelector(iMoveThingsSelector);
|
||||
const onEnd = options?.onEnd ?? (() => {
|
||||
});
|
||||
if (!moveMe || !iMoveThings) {
|
||||
this.handleDisplayError(`Can not drag! ${!moveMe ? "moveMe" : ""} ${!moveMe && !iMoveThings ? "and " : ""}${!iMoveThings ? "iMoveThings " : ""}was not found!`);
|
||||
return;
|
||||
|
|
@ -1438,6 +1440,12 @@
|
|||
document.removeEventListener("mouseup", endDrag);
|
||||
document.removeEventListener("touchend", endDrag);
|
||||
document.removeEventListener("touchcancel", endDrag);
|
||||
onEnd({
|
||||
element: moveMe,
|
||||
x: currentX,
|
||||
y: currentY
|
||||
});
|
||||
initialRect = null;
|
||||
};
|
||||
const onMouseMove = (event) => {
|
||||
if (isDragging && initialRect) {
|
||||
|
|
@ -1467,6 +1475,124 @@
|
|||
event.preventDefault();
|
||||
}, { 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.
|
||||
* This will output plain text into the output Status box.
|
||||
* Additionally, this will output an info message to the console.
|
||||
|
|
@ -1637,15 +1763,28 @@
|
|||
* @since 0.91.39
|
||||
*/
|
||||
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 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);
|
||||
this.userSettingsOld = structuredClone(this.userSettings);
|
||||
this.lastUpdateTime = Date.now();
|
||||
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.
|
||||
* 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`.
|
||||
|
|
@ -2219,7 +2358,7 @@ Getting Y ${pixelY}-${pixelY + drawSizeY}`);
|
|||
};
|
||||
|
||||
// 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 {
|
||||
/** Constructor for the color filter window
|
||||
* @param {*} executor - The executing class
|
||||
|
|
@ -2233,6 +2372,16 @@ Getting Y ${pixelY}-${pixelY + drawSizeY}`);
|
|||
this.windowID = "bm-window-filter";
|
||||
this.colorListID = "bm-filter-flex";
|
||||
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.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>';
|
||||
|
|
@ -2250,6 +2399,16 @@ Getting Y ${pixelY}-${pixelY + drawSizeY}`);
|
|||
this.sortSecondary = "ascending";
|
||||
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.
|
||||
* If another color filter window already exists, we DON'T spawn another!
|
||||
* Parent/child relationships in the DOM structure below are indicated by indentation.
|
||||
|
|
@ -2257,7 +2416,7 @@ Getting Y ${pixelY}-${pixelY + drawSizeY}`);
|
|||
*/
|
||||
buildWindow() {
|
||||
if (document.querySelector(`#${this.windowID}`)) {
|
||||
document.querySelector(`#${this.windowID}`).remove();
|
||||
__privateMethod(this, _WindowFilter_instances, closeWindow_fn).call(this);
|
||||
return;
|
||||
}
|
||||
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) => {
|
||||
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();
|
||||
};
|
||||
button.ontouchend = () => {
|
||||
button.click();
|
||||
};
|
||||
}).buildElement().addButton({ "class": "bm-button-circle", "textContent": "\u2716", "aria-label": 'Close window "Color Filter"' }, (instance, button) => {
|
||||
button.onclick = () => {
|
||||
document.querySelector(`#${this.windowID}`)?.remove();
|
||||
};
|
||||
button.onclick = () => __privateMethod(this, _WindowFilter_instances, closeWindow_fn).call(this);
|
||||
button.ontouchend = () => {
|
||||
button.click();
|
||||
};
|
||||
|
|
@ -2320,10 +2478,14 @@ Getting Y ${pixelY}-${pixelY + drawSizeY}`);
|
|||
*/
|
||||
buildWindowed() {
|
||||
if (document.querySelector(`#${this.windowID}`)) {
|
||||
document.querySelector(`#${this.windowID}`).remove();
|
||||
__privateMethod(this, _WindowFilter_instances, closeWindow_fn).call(this);
|
||||
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 = () => {
|
||||
const windowedColorTotals = document.querySelector("#bm-filter-windowed-color-totals");
|
||||
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) => {
|
||||
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();
|
||||
};
|
||||
button.ontouchend = () => {
|
||||
button.click();
|
||||
};
|
||||
}).buildElement().addButton({ "class": "bm-button-circle", "textContent": "\u2716", "aria-label": 'Close window "Color Filter"' }, (instance, button) => {
|
||||
button.onclick = () => {
|
||||
document.querySelector(`#${this.windowID}`)?.remove();
|
||||
};
|
||||
button.onclick = () => __privateMethod(this, _WindowFilter_instances, closeWindow_fn).call(this);
|
||||
button.ontouchend = () => {
|
||||
button.click();
|
||||
};
|
||||
|
|
@ -2359,8 +2520,15 @@ Getting Y ${pixelY}-${pixelY + drawSizeY}`);
|
|||
};
|
||||
}).buildElement().addButton({ "textContent": "All" }, (instance, button) => {
|
||||
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);
|
||||
this.handleDrag(`#${this.windowID}.bm-window`, `#${this.windowID} .bm-dragbar`);
|
||||
}).buildElement().buildElement().addDiv({ "class": "bm-container bm-scrollable" }).buildElement().buildElement().addDiv({
|
||||
"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`);
|
||||
__privateMethod(this, _WindowFilter_instances, buildColorList_fn).call(this, scrollableContainer);
|
||||
__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();
|
||||
/** 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.
|
||||
* @param {HTMLElement} parentElement - Parent element to add the color list to as a child
|
||||
* @since 0.88.222
|
||||
|
|
@ -2977,7 +3335,7 @@ Version: ${this.version}`, "readOnly": true }).buildElement().buildElement().add
|
|||
*/
|
||||
buildWindowFilter_fn = function() {
|
||||
const windowFilter = new WindowFilter(this);
|
||||
windowFilter.buildWindow();
|
||||
windowFilter.buildPreferredWindow();
|
||||
};
|
||||
coordinateInputPaste_fn = async function(instance, input, event) {
|
||||
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 });
|
||||
}
|
||||
})();
|
||||
|
||||
// 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",
|
||||
"version": "0.91.116",
|
||||
"version": "0.92.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "wplace-bluemarble",
|
||||
"version": "0.91.116",
|
||||
"version": "0.92.1",
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"jsdoc": "^4.0.5",
|
||||
|
|
@ -37,6 +37,7 @@
|
|||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz",
|
||||
"integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.28.0"
|
||||
},
|
||||
|
|
@ -52,6 +53,7 @@
|
|||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz",
|
||||
"integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^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",
|
||||
"integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/linkify-it": "^5",
|
||||
"@types/mdurl": "^2"
|
||||
|
|
@ -741,7 +742,6 @@
|
|||
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
|
||||
"integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1",
|
||||
"entities": "^4.4.0",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "wplace-bluemarble",
|
||||
"version": "0.92.0",
|
||||
"version": "0.92.1",
|
||||
"type": "module",
|
||||
"homepage": "https://bluemarble.lol/",
|
||||
"repository": {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// @name Blue Marble
|
||||
// @name:en Blue Marble
|
||||
// @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: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
|
||||
|
|
|
|||
142
src/Overlay.js
142
src/Overlay.js
|
|
@ -1278,11 +1278,12 @@ export default class Overlay {
|
|||
* @param {string} iMoveThingsSelector - The drag handle element
|
||||
* @since 0.8.2
|
||||
*/
|
||||
handleDrag(moveMeSelector, iMoveThingsSelector) {
|
||||
handleDrag(moveMeSelector, iMoveThingsSelector, options = {}) {
|
||||
|
||||
// Retrieves the elements
|
||||
const moveMe = document.querySelector(moveMeSelector);
|
||||
const iMoveThings = document.querySelector(iMoveThingsSelector);
|
||||
const onEnd = options?.onEnd ?? (() => {});
|
||||
|
||||
// What to do when one of the two elements are not found
|
||||
if (!moveMe || !iMoveThings) {
|
||||
|
|
@ -1375,6 +1376,14 @@ export default class Overlay {
|
|||
document.removeEventListener('mouseup', endDrag);
|
||||
document.removeEventListener('touchend', endDrag);
|
||||
document.removeEventListener('touchcancel', endDrag);
|
||||
|
||||
onEnd({
|
||||
element: moveMe,
|
||||
x: currentX,
|
||||
y: currentY
|
||||
});
|
||||
|
||||
initialRect = null;
|
||||
};
|
||||
|
||||
// Mouse move
|
||||
|
|
@ -1411,6 +1420,135 @@ export default class Overlay {
|
|||
}, { 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.
|
||||
* This will output plain text into the output Status box.
|
||||
* Additionally, this will output an info message to the console.
|
||||
|
|
@ -1434,4 +1572,4 @@ export default class Overlay {
|
|||
consoleError(`${this.name}: ${text}`); // Outputs something like "ScriptName: text" as an error message to the console
|
||||
this.updateInnerHTML(this.outputStatusId, 'Error: ' + text, true); // Update output Status box
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,33 +98,117 @@
|
|||
|
||||
/* 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 */
|
||||
#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;
|
||||
}
|
||||
|
||||
/* Filter color in windowed mode */
|
||||
#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;
|
||||
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 */
|
||||
#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;
|
||||
}
|
||||
|
||||
/* Filter window hide color button */
|
||||
#bm-window-filter.bm-windowed .bm-filter-container-rgb button {
|
||||
padding: 0.5em 0.25ch;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
/* Filter window hide color button SVG in windowed mode */
|
||||
|
|
@ -135,9 +219,13 @@
|
|||
/* Filter window header 2 in windowed mode */
|
||||
#bm-window-filter.bm-windowed .bm-filter-color h2 {
|
||||
font-size: 0.75em;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Filter window dragbar text area in windowed mode */
|
||||
#bm-window-filter #bm-filter-windowed-color-totals {
|
||||
font-size: 1em;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,16 @@ export default class WindowFilter extends Overlay {
|
|||
this.windowID = 'bm-window-filter'; // The ID attribute for this window
|
||||
this.colorListID = 'bm-filter-flex'; // The ID attribute for the color list
|
||||
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} */
|
||||
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?
|
||||
}
|
||||
|
||||
/** 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.
|
||||
* If another color filter window already exists, we DON'T spawn another!
|
||||
* 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 (document.querySelector(`#${this.windowID}`)) {
|
||||
document.querySelector(`#${this.windowID}`).remove();
|
||||
this.#closeWindow();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -79,13 +100,14 @@ export default class WindowFilter extends Overlay {
|
|||
.addDiv({'class': 'bm-flex-center'})
|
||||
.addButton({'class': 'bm-button-circle', 'textContent': '🗗', 'aria-label': 'Switch to windowed mode for "Color Filter"'}, (instance, button) => {
|
||||
button.onclick = () => {
|
||||
document.querySelector(`#${this.windowID}`)?.remove();
|
||||
this.#setWindowModePreference(true);
|
||||
this.#closeWindow();
|
||||
this.buildWindowed();
|
||||
};
|
||||
button.ontouchend = () => {button.click();}; // Needed only to negate weird interaction with dragbar
|
||||
}).buildElement()
|
||||
.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
|
||||
}).buildElement()
|
||||
.buildElement()
|
||||
|
|
@ -202,12 +224,16 @@ export default class WindowFilter extends Overlay {
|
|||
|
||||
// If a color filter wizard window already exists, close it
|
||||
if (document.querySelector(`#${this.windowID}`)) {
|
||||
document.querySelector(`#${this.windowID}`).remove();
|
||||
this.#closeWindow();
|
||||
return;
|
||||
}
|
||||
|
||||
// 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()
|
||||
.addButton({'class': 'bm-button-circle', 'textContent': '▼', 'aria-label': 'Minimize window "Color Filter"', 'data-button-status': 'expanded'}, (instance, button) => {
|
||||
button.onclick = () => {
|
||||
|
|
@ -226,13 +252,14 @@ export default class WindowFilter extends Overlay {
|
|||
.addDiv({'class': 'bm-flex-center'})
|
||||
.addButton({'class': 'bm-button-circle', 'textContent': '🗖', 'aria-label': 'Switch to fullscreen mode for "Color Filter"'}, (instance, button) => {
|
||||
button.onclick = () => {
|
||||
document.querySelector(`#${this.windowID}`)?.remove();
|
||||
this.#setWindowModePreference(false);
|
||||
this.#closeWindow();
|
||||
this.buildWindow();
|
||||
};
|
||||
button.ontouchend = () => {button.click();}; // Needed only to negate weird interaction with dragbar
|
||||
}).buildElement()
|
||||
.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
|
||||
}).buildElement()
|
||||
.buildElement()
|
||||
|
|
@ -261,10 +288,17 @@ export default class WindowFilter extends Overlay {
|
|||
// Color list will appear here
|
||||
.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);
|
||||
|
||||
// Creates dragging capability on the drag bar for dragging the window
|
||||
this.handleDrag(`#${this.windowID}.bm-window`, `#${this.windowID} .bm-dragbar`);
|
||||
this.#initializeWindowedPersistence();
|
||||
|
||||
// Obtains the scrollable container to put the color filter in
|
||||
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);
|
||||
}
|
||||
|
||||
/** 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.
|
||||
* @param {HTMLElement} parentElement - Parent element to add the color list to as a child
|
||||
* @since 0.88.222
|
||||
|
|
@ -705,4 +939,4 @@ export default class WindowFilter extends Overlay {
|
|||
this.timeRemaining = new Date(((this.allPixelsTotal - this.allPixelsCorrectTotal) * 30 * 1000) + Date.now());
|
||||
this.timeRemainingLocalized = localizeDate(this.timeRemaining);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -214,7 +214,7 @@ export default class WindowMain extends Overlay {
|
|||
*/
|
||||
#buildWindowFilter() {
|
||||
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.
|
||||
|
|
@ -254,4 +254,4 @@ export default class WindowMain extends Overlay {
|
|||
instance.updateInnerHTML('bm-input-py', coords?.[3] || '');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,11 +38,6 @@
|
|||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
/* The Blue Marble windowed windows */
|
||||
.bm-window.bm-windowed {
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
/* The drag bar */
|
||||
.bm-dragbar {
|
||||
display: grid;
|
||||
|
|
@ -281,6 +276,7 @@ input[type="file"] {
|
|||
/* Window content container */
|
||||
.bm-window-content {
|
||||
overflow: hidden;
|
||||
max-height: calc(100% - 5px);
|
||||
transition: height 300ms cubic-bezier(.4, 0, .2, 1);
|
||||
}
|
||||
|
||||
|
|
@ -312,7 +308,7 @@ input[type="file"] {
|
|||
|
||||
/* Scrollable container */
|
||||
.bm-window .bm-container.bm-scrollable {
|
||||
max-height: calc(80vh - 150px);
|
||||
max-height: var(--bm-scrollable-max-height, calc(80vh - 150px));
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ import WindowSettings from "./WindowSettings";
|
|||
* "telemetry": 1,
|
||||
* "flags": ["hl-noTrans", "ftr-oWin", "te-noSkip"],
|
||||
* "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 {
|
||||
|
|
@ -45,13 +46,21 @@ export default class SettingsManager extends WindowSettings {
|
|||
* @since 0.91.39
|
||||
*/
|
||||
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
|
||||
const userSettingsCurrent = JSON.stringify(this.userSettings);
|
||||
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 ((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
|
||||
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
|
||||
|
|
@ -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.
|
||||
* 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`.
|
||||
|
|
@ -328,4 +344,4 @@ export default class SettingsManager extends WindowSettings {
|
|||
.buildElement()
|
||||
.buildElement()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue