Skip to content

Commit 8b0b9c7

Browse files
committed
add auto-drawing
1 parent b20dfd9 commit 8b0b9c7

File tree

2 files changed

+254
-1
lines changed

2 files changed

+254
-1
lines changed

src/content_ui.js

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5093,3 +5093,256 @@ try {
50935093
(document.head || document.documentElement || document.body).appendChild(s);
50945094
} catch (e) { console.warn('ensureInjectBmMask failed', e); }
50955095
})();
5096+
5097+
(function WplaceFastPaint() {
5098+
try {
5099+
if (window.__wplace_fast_paint_installed) return;
5100+
window.__wplace_fast_paint_installed = true;
5101+
5102+
const TARGET_SELECTOR = '.rounded-t-box.bg-base-100.border-base-300.w-full.border-t.py-3';
5103+
const STEP = 2; // 每两个像素点击一次
5104+
const BATCH_RESOLVE = 16384; // 预解析 elementFromPoint 每批大小
5105+
const YIELD_EVERY = 65536; // 每多少次点击做一次最小 yield
5106+
const POST_MSG = '__WPLACE_FAST_PAINT_YIELD__' + Math.random();
5107+
5108+
// minimal yield via postMessage (very cheap)
5109+
let yieldResolve = null;
5110+
window.addEventListener('message', (e) => {
5111+
if (e && e.data === POST_MSG && typeof yieldResolve === 'function') {
5112+
const r = yieldResolve; yieldResolve = null; r();
5113+
}
5114+
}, false);
5115+
const minimalYield = () => new Promise(res => { yieldResolve = res; window.postMessage(POST_MSG, '*'); });
5116+
5117+
// 状态
5118+
let waitingForClicks = false;
5119+
let startPt = null;
5120+
let endPt = null;
5121+
let abortFlag = false;
5122+
let overlay = null;
5123+
5124+
// 检查页面是否含目标元素
5125+
function pageHasTarget() {
5126+
try { return !!document.querySelector(TARGET_SELECTOR); } catch (e) { return false; }
5127+
}
5128+
5129+
// overlay 用于绘制矩形(若需要)但不用于消息,消息改为使用 showToast
5130+
function makeOverlay() {
5131+
if (overlay && document.body.contains(overlay)) return overlay;
5132+
const o = document.createElement('div');
5133+
o.id = '__wplace_fast_paint_overlay';
5134+
Object.assign(o.style, {
5135+
position: 'fixed', left: '0', top: '0', width: '100vw', height: '100vh',
5136+
zIndex: 2147483646, pointerEvents: 'none', fontFamily: 'sans-serif'
5137+
});
5138+
document.body.appendChild(o);
5139+
overlay = o;
5140+
return o;
5141+
}
5142+
function clearOverlay() {
5143+
try { if (overlay) overlay.innerHTML = ''; } catch (e) {}
5144+
}
5145+
function toast(msg, timeout = 2000) {
5146+
try {
5147+
if (typeof showToast === 'function') {
5148+
try { showToast(msg, timeout); return; } catch (e) {}
5149+
}
5150+
// fallback to console and simple inline toast if showToast missing
5151+
console.log('[fast-paint] ' + msg);
5152+
try {
5153+
const id = '__wplace_fast_paint_fallback_toast';
5154+
let el = document.getElementById(id);
5155+
if (!el) {
5156+
el = document.createElement('div');
5157+
el.id = id;
5158+
Object.assign(el.style, {
5159+
position: 'fixed', right: '12px', bottom: '12px', zIndex: 2147483647,
5160+
background: 'rgba(0,0,0,0.72)', color: '#e8faff', padding: '8px 10px',
5161+
borderRadius: '6px', fontSize: '13px', pointerEvents: 'none', transition: 'opacity 0.15s linear'
5162+
});
5163+
document.documentElement.appendChild(el);
5164+
}
5165+
el.textContent = msg;
5166+
el.style.opacity = '1';
5167+
setTimeout(() => { try { el.style.opacity = '0'; } catch(_) {} }, timeout);
5168+
} catch (e) {}
5169+
} catch (e) {}
5170+
}
5171+
5172+
function drawRect(left, top, w, h) {
5173+
try {
5174+
const o = makeOverlay();
5175+
o.innerHTML = '';
5176+
const rect = document.createElement('div');
5177+
Object.assign(rect.style, {
5178+
position: 'absolute', left: left + 'px', top: top + 'px',
5179+
width: Math.max(0, w) + 'px', height: Math.max(0, h) + 'px',
5180+
border: '2px dashed rgba(0,200,255,0.9)', background: 'rgba(0,200,255,0.06)',
5181+
boxSizing: 'border-box', pointerEvents: 'none', zIndex: 2147483647
5182+
});
5183+
o.appendChild(rect);
5184+
} catch (e) {}
5185+
}
5186+
5187+
// Synth real-like click: pointerdown -> pointerup -> click + mouse events as fallback
5188+
function dispatchClickAt(x, y) {
5189+
try {
5190+
const target = document.elementFromPoint(x, y) || document.body;
5191+
const opts = { bubbles: true, cancelable: true, composed: true, clientX: x, clientY: y, screenX: window.screenX + x, screenY: window.screenY + y, button: 0, buttons: 1 };
5192+
try { target.dispatchEvent(new PointerEvent('pointerdown', Object.assign({ pointerId: 1, isPrimary: true, pointerType: 'mouse' }, opts))); } catch (e) {}
5193+
try { target.dispatchEvent(new MouseEvent('mousedown', opts)); } catch (e) {}
5194+
try { target.dispatchEvent(new PointerEvent('pointerup', Object.assign({ pointerId: 1, isPrimary: true, pointerType: 'mouse' }, opts))); } catch (e) {}
5195+
try { target.dispatchEvent(new MouseEvent('mouseup', opts)); } catch (e) {}
5196+
try { target.dispatchEvent(new MouseEvent('click', opts)); } catch (e) {}
5197+
} catch (e) {}
5198+
}
5199+
5200+
// 预计算 targets 并执行点击(行主序)
5201+
async function runFastClicks(tl, br, step = STEP) {
5202+
abortFlag = false;
5203+
// 规范化边界(整数)
5204+
const left = Math.max(0, Math.min(tl.x, br.x)) | 0;
5205+
const right = Math.max(0, Math.max(tl.x, br.x)) | 0;
5206+
const top = Math.max(0, Math.min(tl.y, br.y)) | 0;
5207+
const bottom = Math.max(0, Math.max(tl.y, br.y)) | 0;
5208+
5209+
// 预构造所有点(仅坐标)
5210+
const coords = [];
5211+
for (let y = top; y <= bottom; y += step) {
5212+
for (let x = left; x <= right; x += step) {
5213+
coords.push({ x: x | 0, y: y | 0 });
5214+
}
5215+
}
5216+
5217+
// 预解析 elementFromPoint 分批(避免一次阻塞过久)
5218+
for (let s = 0; s < coords.length; s += BATCH_RESOLVE) {
5219+
const e = Math.min(s + BATCH_RESOLVE, coords.length);
5220+
for (let k = s; k < e; k++) {
5221+
const c = coords[k];
5222+
c.el = document.elementFromPoint(c.x, c.y) || document.body;
5223+
}
5224+
if (e < coords.length) await minimalYield();
5225+
}
5226+
5227+
// 现已将元素解析到 coords[].el,开始快速 dispatch
5228+
const total = coords.length;
5229+
let idx = 0;
5230+
toast(`Fast paint started: 0% (0/${total})`, 3000);
5231+
while (idx < total) {
5232+
if (abortFlag) {
5233+
toast('Fast paint cancelled', 2000);
5234+
clearOverlay();
5235+
return { cancelled: true };
5236+
}
5237+
// 每次处理一小批以保持浏览器响应
5238+
const batch = Math.min(8192, total - idx);
5239+
for (let i = 0; i < batch; i++) {
5240+
const c = coords[idx + i];
5241+
const targetEl = (c.el && document.body.contains(c.el)) ? c.el : (document.elementFromPoint(c.x, c.y) || document.body);
5242+
try {
5243+
const opts = { bubbles: true, cancelable: true, composed: true, clientX: c.x, clientY: c.y, screenX: window.screenX + c.x, screenY: window.screenY + c.y, button: 0, buttons: 1 };
5244+
try { targetEl.dispatchEvent(new PointerEvent('pointerdown', Object.assign({ pointerId: 1, isPrimary: true, pointerType: 'mouse' }, opts))); } catch (e) {}
5245+
try { targetEl.dispatchEvent(new MouseEvent('mousedown', opts)); } catch (e) {}
5246+
try { targetEl.dispatchEvent(new PointerEvent('pointerup', Object.assign({ pointerId: 1, isPrimary: true, pointerType: 'mouse' }, opts))); } catch (e) {}
5247+
try { targetEl.dispatchEvent(new MouseEvent('mouseup', opts)); } catch (e) {}
5248+
try { targetEl.dispatchEvent(new MouseEvent('click', opts)); } catch (e) {}
5249+
} catch (e) {}
5250+
}
5251+
idx += batch;
5252+
const pct = Math.round((idx / total) * 100);
5253+
toast(`Fast paint: ${pct}% (${idx}/${total})`, 1500);
5254+
if (idx % YIELD_EVERY === 0) await minimalYield();
5255+
await minimalYield();
5256+
}
5257+
5258+
toast('Fast paint finished', 2000);
5259+
setTimeout(() => { try { clearOverlay(); } catch (e) {} }, 800);
5260+
return { cancelled: false };
5261+
}
5262+
5263+
// capture two clicks (left-top, right-bottom)
5264+
function onCaptureClick(ev) {
5265+
try {
5266+
// ignore clicks inside our UI panel
5267+
const path = ev.composedPath ? ev.composedPath() : (ev.path || []);
5268+
for (const n of path) {
5269+
try { if (n && n.id === 'wplace_versatile_tool') return; } catch (e) {}
5270+
}
5271+
} catch (e) {}
5272+
ev.preventDefault(); ev.stopPropagation();
5273+
5274+
const pt = { x: ev.clientX, y: ev.clientY };
5275+
if (!startPt) {
5276+
startPt = pt;
5277+
drawRect(startPt.x, startPt.y, 1, 1);
5278+
toast('Fast paint: start saved, click bottom-right', 1500);
5279+
return;
5280+
}
5281+
if (!endPt) {
5282+
endPt = pt;
5283+
const left = Math.min(startPt.x, endPt.x);
5284+
const top = Math.min(startPt.y, endPt.y);
5285+
const w = Math.abs(endPt.x - startPt.x);
5286+
const h = Math.abs(endPt.y - startPt.y);
5287+
drawRect(left, top, w, h);
5288+
try { document.removeEventListener('pointerdown', onCaptureClick, true); } catch (e) {}
5289+
waitingForClicks = false;
5290+
runFastClicks(startPt, endPt, STEP).catch(err => { console.error('fast paint error', err); toast('Fast paint error', 2000); setTimeout(clearOverlay, 800); });
5291+
}
5292+
}
5293+
5294+
function cancelCapture() {
5295+
abortFlag = true;
5296+
waitingForClicks = false;
5297+
startPt = null;
5298+
endPt = null;
5299+
try { document.removeEventListener('pointerdown', onCaptureClick, true); } catch (e) {}
5300+
toast('Fast paint cancelled', 1500);
5301+
setTimeout(clearOverlay, 600);
5302+
}
5303+
5304+
// key handler: only activate when page has target element
5305+
function onKeyDown(e) {
5306+
try {
5307+
if (e.repeat) return;
5308+
// ignore typing
5309+
const active = document.activeElement;
5310+
const tag = (active && active.tagName || '').toLowerCase();
5311+
if (active && (active.isContentEditable || tag === 'input' || tag === 'textarea' || tag === 'select')) return;
5312+
5313+
if ((e.key === 'z' || e.key === 'Z') && pageHasTarget()) {
5314+
if (waitingForClicks) return;
5315+
waitingForClicks = true;
5316+
startPt = null; endPt = null; abortFlag = false;
5317+
toast('Fast paint: click top-left then bottom-right (Esc to cancel)', 2000);
5318+
try { document.addEventListener('pointerdown', onCaptureClick, true); } catch (err) {}
5319+
makeOverlay();
5320+
} else if (e.key === 'Escape') {
5321+
if (waitingForClicks || startPt || endPt) cancelCapture();
5322+
}
5323+
} catch (err) {}
5324+
}
5325+
5326+
window.addEventListener('keydown', onKeyDown, true);
5327+
5328+
// expose simple API
5329+
window.__wplace_fast_paint_control = {
5330+
isInstalled: true,
5331+
status: () => ({ waitingForClicks, startPt, endPt, abortFlag, hasTarget: pageHasTarget() }),
5332+
cancel: cancelCapture,
5333+
uninstall: function() {
5334+
try {
5335+
window.removeEventListener('keydown', onKeyDown, true);
5336+
document.removeEventListener('pointerdown', onCaptureClick, true);
5337+
} catch (e) {}
5338+
try { clearOverlay(); } catch (e) {}
5339+
delete window.__wplace_fast_paint_installed;
5340+
delete window.__wplace_fast_paint_control;
5341+
}
5342+
};
5343+
5344+
console.log('Wplace fast paint installed (showToast). Press Z (when target exists), click top-left then bottom-right. Esc to cancel.');
5345+
} catch (e) {
5346+
console.warn('installWplaceFastPaintUsingShowToast failed', e);
5347+
}
5348+
})();

src/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"manifest_version": 3,
33
"name": "Wplace_Versatile_Tool",
4-
"version": "2.4",
4+
"version": "3.0",
55
"description": "A versatile Wplace tool",
66
"permissions": [
77
"scripting",

0 commit comments

Comments
 (0)